// Copyright 2025-present Gustavo "Guz" L. de Mello // Copyright 2025-present The Lored.dev Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package problem import ( "encoding/xml" "fmt" "net/http" "slices" "text/template" ) 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"` XMLName xml.Name `json:"-" xml:"problem"` handler Handler `json:"-" xml:"-"` } 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, } for _, opt := range opts { opt(&p) } return p } var ( DefaultTypeURI = "about:blank" DefaultTemplate = template.Must(template.New("x-smalltrip-problem-template").Parse(`
{{ .Type }}
{{ .Detail }}
{{if .Instance}}Instance: {{ .Instance }}
{{end}}{{printf "%#v" .}}
`))
DefaultHandler = HandlerMiddleware(HandlerBrowser(DefaultTemplate))
)
func (p RegisteredProblem) Type() string {
return p.TypeURI
}
func (p RegisteredProblem) Title() string {
return p.TypeTitle
}
func (p RegisteredProblem) Status() int {
return p.StatusCode
}
func (p RegisteredProblem) Detail() string {
return p.DetailMessage
}
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)
}
func WithType(t string) Option {
return func(p *RegisteredProblem) {
p.TypeURI = t
}
}
func WithTitle(t string) Option {
return func(p *RegisteredProblem) {
p.TypeTitle = t
}
}
func WithStatus(s int) Option {
return func(p *RegisteredProblem) {
if p.TypeTitle == "" {
p.TypeTitle = http.StatusText(s)
}
p.StatusCode = s
}
}
func WithDetail(d string) Option {
return func(p *RegisteredProblem) {
p.DetailMessage = d
}
}
func WithDetailf(f string, args ...any) Option {
return func(p *RegisteredProblem) {
p.DetailMessage = fmt.Sprintf(f, args...)
}
}
func WithError(err error) Option {
return func(p *RegisteredProblem) {
p.DetailMessage = err.Error()
}
}
func WithInstance(i string) Option {
return func(p *RegisteredProblem) {
p.InstanceURI = i
}
}
type Option func(*RegisteredProblem)