2025-07-30 19:14:46 -03:00
// 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.
2025-07-04 19:15:26 -03:00
package problem
import (
"encoding/json"
"encoding/xml"
"fmt"
2025-07-30 19:14:43 -03:00
"io"
2025-07-04 19:15:26 -03:00
"net/http"
"strings"
)
2025-07-29 19:18:28 -03:00
type Handler func ( p Problem ) http . Handler
2025-07-30 19:14:43 -03:00
// 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 )
}
}
2025-07-30 19:14:42 -03:00
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
2025-07-29 19:18:28 -03:00
}
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 )
}
} )
}
2025-07-04 19:15:26 -03:00
}
2025-07-29 19:18:28 -03:00
func HandlerXML ( p Problem ) http . Handler {
2025-07-30 19:14:42 -03:00
return http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2025-07-29 19:18:28 -03:00
w . Header ( ) . Set ( "Content-Type" , ProblemMediaTypeXML )
2025-07-04 19:15:26 -03:00
b , err := xml . Marshal ( p )
if err != nil {
2025-07-30 19:14:42 -03:00
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 ( ) ) )
2025-07-04 19:15:26 -03:00
}
2025-07-29 19:18:28 -03:00
w . WriteHeader ( p . Status ( ) )
2025-07-04 19:15:26 -03:00
_ , err = w . Write ( b )
if err != nil {
2025-07-30 19:14:42 -03:00
_ , _ = w . Write ( fmt . Appendf ( [ ] byte { } , "Failed to write problem XML: %s\n\nPlease report this error to the site's admin." , err . Error ( ) ) )
2025-07-04 19:15:26 -03:00
}
} )
}
2025-07-29 19:18:28 -03:00
func HandlerJSON ( p Problem ) http . Handler {
2025-07-30 19:14:42 -03:00
return http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2025-07-04 19:15:26 -03:00
w . Header ( ) . Set ( "Content-Type" , ProblemMediaTypeJSON )
b , err := json . Marshal ( p )
if err != nil {
2025-07-30 19:14:42 -03:00
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 ( ) ) )
2025-07-04 19:15:26 -03:00
}
2025-07-29 19:18:28 -03:00
w . WriteHeader ( p . Status ( ) )
2025-07-04 19:15:26 -03:00
_ , err = w . Write ( b )
if err != nil {
2025-07-30 19:14:42 -03:00
_ , _ = w . Write ( fmt . Appendf ( [ ] byte { } , "Failed to write problem JSON: %s\n\nPlease report this error to the site's admin." , err . Error ( ) ) )
2025-07-04 19:15:26 -03:00
}
} )
}
2025-07-29 19:18:28 -03:00
func HandlerText ( p Problem ) http . Handler {
2025-07-30 19:14:42 -03:00
return http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2025-07-04 19:15:26 -03:00
w . Header ( ) . Set ( "Content-Type" , "text/plain" )
2025-07-29 19:18:28 -03:00
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 ( ) ,
)
2025-07-04 19:15:26 -03:00
_ , 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"
)