156 lines
5.0 KiB
Go
156 lines
5.0 KiB
Go
// 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/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type Handler func(p Problem) http.Handler
|
|
|
|
// TODO: HandlerDevpage, this handler will be a complete handler with the smalltrip logo and a stylized page showing the error, JSON and XML values, and accepting a custom
|
|
// template to add custom styling.
|
|
|
|
func HandlerBrowser(template Template) Handler {
|
|
return func(p Problem) http.Handler {
|
|
h := HandlerContentType(map[string]Handler{
|
|
"text/html": HandlerTemplate(template),
|
|
"application/xml+html": HandlerTemplate(template),
|
|
"application/xml": HandlerXML,
|
|
ProblemMediaTypeXML: HandlerXML,
|
|
"application/json": HandlerJSON,
|
|
ProblemMediaTypeJSON: HandlerJSON,
|
|
}, HandlerJSON)
|
|
return h(p)
|
|
}
|
|
}
|
|
|
|
func HandlerTemplate(template Template) Handler {
|
|
return func(p Problem) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(p.Status())
|
|
if err := template.Execute(w, p); err != nil {
|
|
_, _ = w.Write(fmt.Appendf([]byte{}, "Failed to execute problem template: %s\n\nPlease report this error to the site's admin.", err.Error()))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type Template interface {
|
|
Execute(w io.Writer, data any) error
|
|
}
|
|
|
|
func HandlerContentType(handlers map[string]Handler, fallback ...Handler) Handler {
|
|
return func(p Problem) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
for t, h := range handlers {
|
|
if strings.Contains(r.Header.Get("Accept"), t) {
|
|
h(p).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
}
|
|
if len(fallback) > 0 {
|
|
fallback[0](p).ServeHTTP(w, r)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func HandlerXML(p Problem) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", ProblemMediaTypeXML)
|
|
|
|
b, err := xml.Marshal(p)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write(fmt.Appendf([]byte{}, "Failed to marshal problem XML: %s\n\nPlease report this error to the site's admin.", err.Error()))
|
|
}
|
|
|
|
w.WriteHeader(p.Status())
|
|
|
|
_, err = w.Write(b)
|
|
if err != nil {
|
|
_, _ = w.Write(fmt.Appendf([]byte{}, "Failed to write problem XML: %s\n\nPlease report this error to the site's admin.", err.Error()))
|
|
}
|
|
})
|
|
}
|
|
|
|
func HandlerJSON(p Problem) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", ProblemMediaTypeJSON)
|
|
|
|
b, err := json.Marshal(p)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write(fmt.Appendf([]byte{}, "Failed to marshal problem JSON: %s\n\nPlease report this error to the site's admin.", err.Error()))
|
|
}
|
|
|
|
w.WriteHeader(p.Status())
|
|
|
|
_, err = w.Write(b)
|
|
if err != nil {
|
|
_, _ = w.Write(fmt.Appendf([]byte{}, "Failed to write problem JSON: %s\n\nPlease report this error to the site's admin.", err.Error()))
|
|
}
|
|
})
|
|
}
|
|
|
|
func HandlerText(p Problem) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
w.WriteHeader(p.Status())
|
|
|
|
s := fmt.Sprintf(
|
|
"Type: %s\n"+
|
|
"Status: %3d\n"+
|
|
"Title: %s\n"+
|
|
"Detail: %s\n"+
|
|
"Instance: %s\n\n"+
|
|
p.Type(),
|
|
p.Status(),
|
|
p.Title(),
|
|
p.Detail(),
|
|
p.Instance(),
|
|
)
|
|
|
|
_, err := w.Write(fmt.Appendf([]byte{}, "%s%+v\n\n%#v", s, p, p))
|
|
if err != nil {
|
|
_, _ = w.Write(fmt.Append([]byte{},
|
|
"Ok, what should we do at this point? You fucked up so bad that this message "+
|
|
"shouldn't even be able to be sent in the first place. If you are a normal user I'm "+
|
|
"so sorry for you to be reading this. If you're a developer, go fix your ResponseWriter "+
|
|
"implementation, because this should never happen in any normal codebase. "+
|
|
"I hope for the life of anyone you love you don't use this message in some "+
|
|
"error checking or any sort of API-contract, because there will be no more hope "+
|
|
"for you or your project. May God or any other divinity that you may "+
|
|
"or may not believe be with you when trying to fix this mistake, you will need it.",
|
|
// If someone use this as part of the API-contract I'll not even be surprised.
|
|
// So any change to this message is still considered a breaking change.
|
|
))
|
|
}
|
|
})
|
|
}
|
|
|
|
const (
|
|
ProblemMediaTypeJSON = "application/problem+json"
|
|
ProblemMediaTypeXML = "application/problem+xml"
|
|
)
|