~bigbes/sr-ht-ecore

ref: 66c00770b754f41c97da16007cb37c1863d0474e sr-ht-ecore/pages/error.go -rw-r--r-- 4.6 KiB
66c00770 — Eugene Blikh instconf: one reading of the instance config origins 9 days 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
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.
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
}