From 9ce32a5f6b41202f25fb680b7273c631735929fc Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L de Mello" Date: Fri, 4 Jul 2025 19:15:24 -0300 Subject: [PATCH] feat(smalltrip,problems): new problem package to handle 400 and 500 error codes This is planned to replace the exceptions package, having a nicer and more extendable API, based on the RFC 9457 (HTTP problems) --- smalltrip/problem/problem.go | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 smalltrip/problem/problem.go diff --git a/smalltrip/problem/problem.go b/smalltrip/problem/problem.go new file mode 100644 index 0000000..b6c7502 --- /dev/null +++ b/smalltrip/problem/problem.go @@ -0,0 +1,92 @@ +package problem + +import ( + "encoding/xml" + "fmt" + "slices" +) + +type Problem struct { + Type string `json:"type,omitempty" xml:"type,omitempty"` + Title string `json:"title,omitempty" xml:"title,omitempty"` + Status int `json:"status,omitempty" xml:"status,omitempty"` + Detail string `json:"detail,omitempty" xml:"detail,omitempty"` + Instance string `json:"instance,omitempty" xml:"instance,omitempty"` + + XMLName xml.Name `json:"-" xml:"problem"` + + handler func(any) http.Handler `json:"-" xml:"-"` +} + +func New(opts ...Option) Problem { + p := Problem{ + Type: DefaultType, + handler: ProblemHandler, + } + for _, opt := range opts { + opt(&p) + } + return p +} + +const DefaultType = "about:blank" + +func NewStatus(s int, opts ...Option) Problem { + return New(slices.Concat([]Option{WithStatus(s)}, opts)...) +} + +func NewDetailed(s int, detail string, opts ...Option) Problem { + return New(slices.Concat([]Option{WithStatus(s), WithDetail(detail)}, opts)...) +} + +func (p Problem) StatusCode() int { + return p.Status +} + +func WithType(t string) Option { + return func(p *Problem) { + p.Type = t + } +} + +func WithTitle(t string) Option { + return func(p *Problem) { + p.Title = t + } +} + +func WithStatus(s int) Option { + return func(p *Problem) { + if p.Title == "" { + p.Title = http.StatusText(s) + } + + p.Status = s + } +} + +func WithDetail(d string) Option { + return func(p *Problem) { + p.Detail = d + } +} + +func WithDetailf(f string, args ...any) Option { + return func(p *Problem) { + p.Detail = fmt.Sprintf(f, args...) + } +} + +func WithError(err error) Option { + return func(p *Problem) { + p.Detail = err.Error() + } +} + +func WithInstance(i string) Option { + return func(p *Problem) { + p.Instance = i + } +} + +type Option func(*Problem)