2025-07-04 19:15:24 -03:00
|
|
|
package problem
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/xml"
|
|
|
|
|
"fmt"
|
2025-07-04 19:15:26 -03:00
|
|
|
"net/http"
|
2025-07-04 19:15:24 -03:00
|
|
|
"slices"
|
2025-07-30 19:14:43 -03:00
|
|
|
"text/template"
|
2025-07-04 19:15:24 -03:00
|
|
|
)
|
|
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
type Problem interface {
|
|
|
|
|
Type() string
|
|
|
|
|
Title() string
|
|
|
|
|
Status() int
|
|
|
|
|
Detail() string
|
|
|
|
|
Instance() string
|
|
|
|
|
|
|
|
|
|
Handler(self Problem) http.Handler
|
|
|
|
|
|
|
|
|
|
http.Handler
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type RegisteredProblem struct {
|
|
|
|
|
TypeURI string `json:"type,omitempty" xml:"type,omitempty"`
|
|
|
|
|
TypeTitle string `json:"title,omitempty" xml:"title,omitempty"`
|
|
|
|
|
StatusCode int `json:"status,omitempty" xml:"status,omitempty"`
|
|
|
|
|
DetailMessage string `json:"detail,omitempty" xml:"detail,omitempty"`
|
|
|
|
|
InstanceURI string `json:"instance,omitempty" xml:"instance,omitempty"`
|
2025-07-04 19:15:24 -03:00
|
|
|
|
|
|
|
|
XMLName xml.Name `json:"-" xml:"problem"`
|
|
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
handler Handler `json:"-" xml:"-"`
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
func NewStatus(s int, opts ...Option) RegisteredProblem {
|
|
|
|
|
return New(slices.Concat([]Option{WithStatus(s)}, opts)...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewDetailed(s int, detail string, opts ...Option) RegisteredProblem {
|
|
|
|
|
return New(slices.Concat([]Option{WithStatus(s), WithDetail(detail)}, opts)...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func New(opts ...Option) RegisteredProblem {
|
|
|
|
|
p := RegisteredProblem{
|
|
|
|
|
TypeURI: DefaultTypeURI,
|
|
|
|
|
handler: DefaultHandler,
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
2025-07-29 19:18:27 -03:00
|
|
|
|
2025-07-04 19:15:24 -03:00
|
|
|
for _, opt := range opts {
|
|
|
|
|
opt(&p)
|
|
|
|
|
}
|
|
|
|
|
return p
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
var (
|
2025-07-30 19:14:43 -03:00
|
|
|
DefaultTypeURI = "about:blank"
|
|
|
|
|
DefaultTemplate = template.Must(template.New("x-smalltrip-problem-template").Parse(`
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<title>{{ .Status }} - {{ .Title }}</title>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<h1>{{.Status}} - {{ .Title }}</h1>
|
|
|
|
|
<p><code>{{ .Type }}</code></p>
|
|
|
|
|
<p>{{ .Detail }}</p>
|
|
|
|
|
{{if .Instance}}
|
|
|
|
|
<p>Instance: {{ .Instance }}</p>
|
|
|
|
|
{{end}}
|
|
|
|
|
<code>{{printf "%#v" .}}<code>
|
|
|
|
|
</body>
|
|
|
|
|
<html>
|
|
|
|
|
`))
|
|
|
|
|
DefaultHandler = HandlerMiddleware(HandlerBrowser(DefaultTemplate))
|
2025-07-29 19:18:27 -03:00
|
|
|
)
|
2025-07-04 19:15:24 -03:00
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
func (p RegisteredProblem) Type() string {
|
|
|
|
|
return p.TypeURI
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
func (p RegisteredProblem) Title() string {
|
|
|
|
|
return p.TypeTitle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p RegisteredProblem) Status() int {
|
|
|
|
|
return p.StatusCode
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
func (p RegisteredProblem) Detail() string {
|
|
|
|
|
return p.DetailMessage
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
func (p RegisteredProblem) Instance() string {
|
|
|
|
|
return p.InstanceURI
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p RegisteredProblem) Handler(self Problem) http.Handler {
|
|
|
|
|
return p.handler(self)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p RegisteredProblem) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
p.Handler(p).ServeHTTP(w, r)
|
2025-07-04 19:15:26 -03:00
|
|
|
}
|
|
|
|
|
|
2025-07-04 19:15:24 -03:00
|
|
|
func WithType(t string) Option {
|
2025-07-29 19:18:27 -03:00
|
|
|
return func(p *RegisteredProblem) {
|
|
|
|
|
p.TypeURI = t
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WithTitle(t string) Option {
|
2025-07-29 19:18:27 -03:00
|
|
|
return func(p *RegisteredProblem) {
|
|
|
|
|
p.TypeTitle = t
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WithStatus(s int) Option {
|
2025-07-29 19:18:27 -03:00
|
|
|
return func(p *RegisteredProblem) {
|
|
|
|
|
if p.TypeTitle == "" {
|
|
|
|
|
p.TypeTitle = http.StatusText(s)
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
2025-07-29 19:18:27 -03:00
|
|
|
p.StatusCode = s
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WithDetail(d string) Option {
|
2025-07-29 19:18:27 -03:00
|
|
|
return func(p *RegisteredProblem) {
|
|
|
|
|
p.DetailMessage = d
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WithDetailf(f string, args ...any) Option {
|
2025-07-29 19:18:27 -03:00
|
|
|
return func(p *RegisteredProblem) {
|
|
|
|
|
p.DetailMessage = fmt.Sprintf(f, args...)
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WithError(err error) Option {
|
2025-07-29 19:18:27 -03:00
|
|
|
return func(p *RegisteredProblem) {
|
|
|
|
|
p.DetailMessage = err.Error()
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WithInstance(i string) Option {
|
2025-07-29 19:18:27 -03:00
|
|
|
return func(p *RegisteredProblem) {
|
|
|
|
|
p.InstanceURI = i
|
2025-07-04 19:15:24 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 19:18:27 -03:00
|
|
|
type Option func(*RegisteredProblem)
|