~bigbes/sr-ht-ecore

ref: 89fa694cbf548ecd642b8101825b7a49b48f975a sr-ht-ecore/pages/error.go -rw-r--r-- 6.2 KiB
89fa694c — Eugene Blikh metapat: the meta.sr.ht PAT plane a federated endpoint needs a day ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package pages

import (
	"embed"
	"net/http"
)

// sharedFS holds this package's own templates: the error partial parsed into
// every set, and the error page used by a service that ships none.
//
//go:embed templates
var sharedFS embed.FS

const (
	sharedDir = "templates"

	// errorPartialFile defines ErrorPartial and nothing else, so that it can be
	// parsed into every page set without bringing a content block with it.
	errorPartialFile = "error.tmpl"

	// errorPageFile is the default error page: a content block that is one
	// invocation of ErrorPartial.
	errorPageFile = "error.html"
)

// ErrorPage is the name Load registers the error page under, and the name to
// pass Render for it. It is a constant because a service's `fail` names it on
// every arm of its switch.
const ErrorPage = "error"

// ErrorPartial is the shared error body: the status line, the message and the
// way back. Its dot is an ErrorData — not a view struct — so a service that
// wants its own error.html around it invokes it as
// {{template "srht-error" .Data}}.
const ErrorPartial = "srht-error"

// The messages of the error pages a surface produces on its own. They are
// constants, and shared ones, for the reason the donors gave: the visibility
// rules of these services require "somebody else's private thing" and "no such
// thing" to be indistinguishable, and two 404s that differed in their prose
// would rebuild the distinction the status code was chosen to erase.
//
// A 400 has no constant deliberately. It is the one class that describes
// something the viewer just typed — "1y is not a duration", "grants are not
// lower case" — and an error page that replaced that with a house phrase would
// send them back to the form with nothing to change.
const (
	NotFoundMessage     = "There is nothing here."
	UnauthorizedMessage = "You need to be logged in to do that."
	ForbiddenMessage    = "You may not do that."
	MethodMessage       = "That is not something you can do to this page."
	InternalMessage     = "Something went wrong on our side. It has been logged."
	UnavailableMessage  = "Something we depend on is not answering. Try again in a moment."
)

// Message is the standard message for a status, or "" for a status that has
// none — a 400 above all, whose message is the caller's own text.
// The machine-facing halves of the same table. A REST surface answers a caller
// that parses, not a person that reads, and every service on the instance keeps
// its 404 body byte-identical on purpose: two spellings of "not found" are two
// facts a client can accidentally distinguish, which is exactly what the shared
// status was chosen to prevent.
//
// They live beside the page sentences rather than in a second package because
// they are one table read two ways — and because the service that tried to
// reuse RenderRefusals on its REST surface could not, for want of these.
const (
	APINotFoundMessage     = "not found"
	APIUnauthorizedMessage = "unauthorized"
	APIForbiddenMessage    = "forbidden"
	APIMethodMessage       = "method not allowed"
	APIInternalMessage     = "internal server error"
	APIUnavailableMessage  = "service unavailable"
)

// APIMessage is Message for a machine-facing surface: the same statuses, in the
// register a JSON client expects. An unmapped status returns "", which the
// caller renders as it likes — usually http.StatusText.
func APIMessage(status int) string {
	switch status {
	case http.StatusUnauthorized:
		return APIUnauthorizedMessage
	case http.StatusForbidden:
		return APIForbiddenMessage
	case http.StatusNotFound:
		return APINotFoundMessage
	case http.StatusMethodNotAllowed:
		return APIMethodMessage
	case http.StatusInternalServerError:
		return APIInternalMessage
	case http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
		return APIUnavailableMessage
	default:
		return ""
	}
}

func Message(status int) string {
	switch status {
	case http.StatusUnauthorized:
		return UnauthorizedMessage
	case http.StatusForbidden:
		return ForbiddenMessage
	case http.StatusNotFound:
		return NotFoundMessage
	case http.StatusMethodNotAllowed:
		return MethodMessage
	case http.StatusInternalServerError:
		return InternalMessage
	case http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
		return UnavailableMessage
	default:
		return ""
	}
}

// A Link is a href and the text that carries it.
type Link struct {
	Href string
	Text string
}

// DefaultBack is the way back off an error page for a service that names none:
// its own root, which every one of these services answers with a landing page.
var DefaultBack = Link{Href: "/", Text: "Back to the start"}

// ErrorData is the payload of the error page. A service puts it where its view
// struct keeps page payload — the field the layout's content sees as .Data in
// all of the donors:
//
//	vd := s.view(r, http.StatusText(status))
//	vd.Data = pages.Error(status, message).BackTo("/tokens", "Back to your tokens")
//	err := s.pages.Render(w, status, pages.ErrorPage, vd)
type ErrorData struct {
	Status     int
	StatusText string

	// Message is what the viewer can act on. It is never the raw error from
	// below: those name tables, queries and paths. It goes through
	// html/template, which escapes it — which matters, because the messages
	// that are not constants quote what the caller typed.
	Message string

	// Back is the way off this page. Empty renders no link at all rather than a
	// link to nowhere, which is what a hand-built ErrorData would otherwise get.
	Back Link
}

// Error builds the payload for a status. An empty message takes the standard
// one for that status (Message), so a caller that has nothing of its own to add
// says nothing rather than inventing a phrase.
func Error(status int, message string) ErrorData {
	if message == "" {
		message = Message(status)
	}
	return ErrorData{
		Status:     status,
		StatusText: http.StatusText(status),
		Message:    message,
		Back:       DefaultBack,
	}
}

// BackTo replaces the way back, for the services whose landing page is not "/"
// or whose word for it is not "back to the start". It returns a copy, so it
// chains off Error.
func (d ErrorData) BackTo(href, text string) ErrorData {
	d.Back = Link{Href: href, Text: text}
	return d
}