feat(smallstrip,problems): 400s codes constructors and problems
This commit is contained in:
385
smalltrip/problem/400.go
Normal file
385
smalltrip/problem/400.go
Normal file
@@ -0,0 +1,385 @@
|
||||
package problem
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewBadRequest(detail string, opts ...Option) BadRequest {
|
||||
return BadRequest(NewDetailed(http.StatusBadRequest, detail, opts...))
|
||||
}
|
||||
|
||||
type BadRequest Problem
|
||||
|
||||
func NewUnauthorized(scheme AuthScheme, opts ...Option) Unauthorized {
|
||||
return Unauthorized{
|
||||
Problem: NewDetailed(
|
||||
http.StatusUnauthorized,
|
||||
fmt.Sprintf("You must authenticate using the %q scheme", scheme.Title),
|
||||
opts...,
|
||||
),
|
||||
Authentication: scheme,
|
||||
}
|
||||
}
|
||||
|
||||
type Unauthorized struct {
|
||||
Problem
|
||||
Authentication AuthScheme `json:"authentication,omitempty" xml:"authentication,omitempty"`
|
||||
}
|
||||
|
||||
func (p Unauthorized) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if p.Authentication.Title != "" {
|
||||
w.Header().Set("WWW-Authenticate", p.Authentication.Title)
|
||||
}
|
||||
p.handler(p).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewPaymentRequired(opts ...Option) PaymentRequired {
|
||||
return PaymentRequired(NewStatus(http.StatusPaymentRequired, opts...))
|
||||
}
|
||||
|
||||
type PaymentRequired Problem
|
||||
|
||||
func NewForbidden(opts ...Option) Forbidden {
|
||||
return Forbidden(NewStatus(http.StatusForbidden, opts...))
|
||||
}
|
||||
|
||||
type Forbidden Problem
|
||||
|
||||
func NewNotFound(opts ...Option) NotFound {
|
||||
return NotFound(NewStatus(http.StatusNotFound, opts...))
|
||||
}
|
||||
|
||||
type NotFound Problem
|
||||
|
||||
func NewMethodNotAllowed[T string | []string](allow T, opts ...Option) MethodNotAllowed {
|
||||
p := MethodNotAllowed{
|
||||
Problem: NewStatus(http.StatusMethodNotAllowed, opts...),
|
||||
}
|
||||
if as, ok := any(allow).([]string); ok {
|
||||
p.Allowed = as
|
||||
} else {
|
||||
p.Allowed = []string{any(allow).(string)}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type MethodNotAllowed struct {
|
||||
Problem
|
||||
Allowed []string `json:"allowed,omitempty" xml:"allowed,omitempty"`
|
||||
}
|
||||
|
||||
func (p MethodNotAllowed) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if len(p.Allowed) > 0 {
|
||||
w.Header().Set("Allow", strings.Join(p.Allowed, ", "))
|
||||
}
|
||||
p.handler(p).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewNotAcceptable[T string | []string](header NegotiationHeader, allow T, opts ...Option) NotAcceptable {
|
||||
p := NotAcceptable{
|
||||
Problem: NewDetailed(http.StatusMethodNotAllowed, fmt.Sprintf(
|
||||
"Cannot provide a response matching the list of acceptable values defined by %q header", header),
|
||||
opts...,
|
||||
),
|
||||
}
|
||||
if as, ok := any(allow).([]string); ok {
|
||||
p.Allowed = as
|
||||
} else {
|
||||
p.Allowed = []string{any(allow).(string)}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
type NotAcceptable struct {
|
||||
Problem
|
||||
Allowed []string `json:"allowed,omitempty" xml:"allowed,omitempty"`
|
||||
}
|
||||
|
||||
func (p NotAcceptable) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
p.handler(p).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
const (
|
||||
NegotiationHeaderAccept NegotiationHeader = "Accept"
|
||||
NegotiationHeaderAcceptEncoding NegotiationHeader = "AcceptEncoding"
|
||||
NegotiationHeaderAcceptLanguage NegotiationHeader = "AcceptLanguage"
|
||||
)
|
||||
|
||||
type NegotiationHeader string
|
||||
|
||||
func NewProxyAuthRequired(scheme AuthScheme, opts ...Option) ProxyAuthRequired {
|
||||
return ProxyAuthRequired{
|
||||
Problem: NewDetailed(
|
||||
http.StatusProxyAuthRequired,
|
||||
fmt.Sprintf("You must authenticate on the proxy using the %q scheme", scheme.Title),
|
||||
opts...,
|
||||
),
|
||||
Authentication: scheme,
|
||||
}
|
||||
}
|
||||
|
||||
type ProxyAuthRequired struct {
|
||||
Problem
|
||||
Authentication AuthScheme `json:"authentication,omitempty" xml:"authentication,omitempty"`
|
||||
}
|
||||
|
||||
func (p ProxyAuthRequired) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if p.Authentication.Title != "" {
|
||||
w.Header().Set("Proxy-Authenticate", p.Authentication.Title)
|
||||
}
|
||||
p.handler(p).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
var (
|
||||
AuthSchemeBasic = AuthScheme{Title: "Basic", Type: "https://datatracker.ietf.org/doc/html/rfc7617"}
|
||||
AuthSchemeBearer = AuthScheme{Title: "Bearer", Type: "https://datatracker.ietf.org/doc/html/rfc6750"}
|
||||
AuthSchemeDigest = AuthScheme{Title: "Digest", Type: "https://datatracker.ietf.org/doc/html/rfc7616"}
|
||||
AuthSchemeHOBA = AuthScheme{Title: "HOBA", Type: "https://datatracker.ietf.org/doc/html/rfc7486"}
|
||||
AuthSchemeMutual = AuthScheme{Title: "Mutual", Type: "https://datatracker.ietf.org/doc/html/rfc8120"}
|
||||
AuthSchemeNegotiate = AuthScheme{Title: "Negotiate", Type: "https://datatracker.ietf.org/doc/html/rfc4599"}
|
||||
AuthSchemeVAPID = AuthScheme{Title: "VAPID", Type: "https://datatracker.ietf.org/doc/html/rfc8292"}
|
||||
AuthSchemeSCRAM = AuthScheme{Title: "SCRAM", Type: "https://datatracker.ietf.org/doc/html/rfc8292"}
|
||||
AuthSchemeAWS4HMACSHA256 = AuthScheme{Title: "AWS4-HMAC-SHA256", Type: "https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html"}
|
||||
)
|
||||
|
||||
type AuthScheme struct {
|
||||
Type string `json:"type,omitempty" xml:"type,omitempty"`
|
||||
Title string `json:"title,omitempty" xml:"title,omitempty"`
|
||||
}
|
||||
|
||||
func NewRequestTimeout(opts ...Option) RequestTimeout {
|
||||
return RequestTimeout(NewStatus(http.StatusRequestTimeout, opts...))
|
||||
}
|
||||
|
||||
type RequestTimeout Problem
|
||||
|
||||
func (p RequestTimeout) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Connection", "close")
|
||||
p.handler(p).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewConflict(opts ...Option) Conflict {
|
||||
return Conflict(NewStatus(http.StatusConflict, opts...))
|
||||
}
|
||||
|
||||
type Conflict Problem
|
||||
|
||||
func NewGone(opts ...Option) Gone {
|
||||
return Gone(NewStatus(http.StatusGone, opts...))
|
||||
}
|
||||
|
||||
type Gone Problem
|
||||
|
||||
func NewLengthRequired(opts ...Option) LengthRequired {
|
||||
return LengthRequired(NewStatus(http.StatusLengthRequired, opts...))
|
||||
}
|
||||
|
||||
type LengthRequired Problem
|
||||
|
||||
func NewPreconditionFailed(opts ...Option) PreconditionFailed {
|
||||
return PreconditionFailed(NewStatus(http.StatusPreconditionFailed, opts...))
|
||||
}
|
||||
|
||||
type PreconditionFailed Problem
|
||||
|
||||
func NewContentTooLarge(opts ...Option) ContentTooLarge {
|
||||
return ContentTooLarge(NewStatus(http.StatusRequestEntityTooLarge, opts...))
|
||||
}
|
||||
|
||||
type ContentTooLarge Problem
|
||||
|
||||
func NewURITooLong(opts ...Option) URITooLong {
|
||||
return URITooLong(NewStatus(http.StatusRequestURITooLong, opts...))
|
||||
}
|
||||
|
||||
type URITooLong Problem
|
||||
|
||||
func NewUnsupportedMediaType(opts ...Option) UnsupportedMediaType {
|
||||
return UnsupportedMediaType(NewStatus(http.StatusUnsupportedMediaType, opts...))
|
||||
}
|
||||
|
||||
type UnsupportedMediaType Problem
|
||||
|
||||
func NewRangeNotSatisfiable(unit string, contentRange int, opts ...Option) RangeNotSatisfiable {
|
||||
return RangeNotSatisfiable{
|
||||
Problem: NewStatus(http.StatusGone, opts...),
|
||||
Range: fmt.Sprintf("%s */%d", unit, contentRange),
|
||||
}
|
||||
}
|
||||
|
||||
type RangeNotSatisfiable struct {
|
||||
Problem
|
||||
Range string `json:"range" xml:"range"`
|
||||
}
|
||||
|
||||
func (p RangeNotSatisfiable) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Range", p.Range)
|
||||
p.handler(p).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewExpectationFailed(opts ...Option) ExpectationFailed {
|
||||
return ExpectationFailed(NewStatus(http.StatusExpectationFailed, opts...))
|
||||
}
|
||||
|
||||
type ExpectationFailed Problem
|
||||
|
||||
func NewTeapot(opts ...Option) Teapot {
|
||||
return Teapot(NewStatus(http.StatusTeapot, opts...))
|
||||
}
|
||||
|
||||
type Teapot Problem
|
||||
|
||||
func NewMisdirectedRequest(opts ...Option) MisdirectedRequest {
|
||||
return MisdirectedRequest(NewStatus(http.StatusMisdirectedRequest, opts...))
|
||||
}
|
||||
|
||||
type MisdirectedRequest Problem
|
||||
|
||||
func NewUnprocessableContent(opts ...Option) UnprocessableContent {
|
||||
return UnprocessableContent(NewStatus(http.StatusUnprocessableEntity, opts...))
|
||||
}
|
||||
|
||||
type UnprocessableContent Problem
|
||||
|
||||
// TODO?: Should the response of this be different and follow WebDAV's XML format?
|
||||
func NewLocked(opts ...Option) Locked {
|
||||
return Locked(NewStatus(http.StatusLocked, opts...))
|
||||
}
|
||||
|
||||
type Locked Problem
|
||||
|
||||
func NewFailedDependency(opts ...Option) FailedDependency {
|
||||
return FailedDependency(NewStatus(http.StatusFailedDependency, opts...))
|
||||
}
|
||||
|
||||
type FailedDependency Problem
|
||||
|
||||
func NewTooEarly(opts ...Option) TooEarly {
|
||||
return TooEarly(NewStatus(http.StatusTooEarly, opts...))
|
||||
}
|
||||
|
||||
type TooEarly Problem
|
||||
|
||||
func NewUpgradeRequired(protocol Protocol, opts ...Option) UpgradeRequired {
|
||||
return UpgradeRequired{
|
||||
Problem: NewStatus(http.StatusUpgradeRequired, opts...),
|
||||
Protocol: protocol,
|
||||
}
|
||||
}
|
||||
|
||||
type UpgradeRequired struct {
|
||||
Problem
|
||||
Protocol Protocol `json:"protocol" xml:"protocol"`
|
||||
}
|
||||
|
||||
func (p UpgradeRequired) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Upgrade", string(p.Protocol))
|
||||
p.handler(p).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
const (
|
||||
Http1_1 Protocol = "HTTP/1.1"
|
||||
Http2_0 Protocol = "HTTP/2.0"
|
||||
Http3_0 Protocol = "HTTP/3.0"
|
||||
)
|
||||
|
||||
type Protocol string
|
||||
|
||||
func NewPreconditionRequired(opts ...Option) PreconditionRequired {
|
||||
return PreconditionRequired(NewStatus(http.StatusPreconditionRequired, opts...))
|
||||
}
|
||||
|
||||
type PreconditionRequired Problem
|
||||
|
||||
func NewTooManyRequests[T time.Time | time.Duration](retryAfter T, opts ...Option) TooManyRequests[T] {
|
||||
p := NewStatus(http.StatusTooManyRequests, opts...)
|
||||
return TooManyRequests[T]{Problem: p, RetryAfter: retryAfter}
|
||||
}
|
||||
|
||||
type TooManyRequests[T time.Time | time.Duration] struct {
|
||||
Problem
|
||||
RetryAfter T `json:"retryAfter" xml:"retry-after"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ json.Marshaler = TooManyRequests[time.Time]{}
|
||||
_ xml.Marshaler = TooManyRequests[time.Time]{}
|
||||
)
|
||||
|
||||
func (p TooManyRequests[T]) MarshalJSON() ([]byte, error) {
|
||||
switch t := any(p.RetryAfter).(type) {
|
||||
case time.Time:
|
||||
return json.Marshal(struct {
|
||||
Problem
|
||||
RetryAfter string `json:"retryAfter,omitempty"`
|
||||
}{
|
||||
Problem: p.Problem,
|
||||
RetryAfter: t.Format(time.RFC3339),
|
||||
})
|
||||
case time.Duration:
|
||||
return json.Marshal(struct {
|
||||
Problem
|
||||
RetryAfter int `json:"retryAfter,omitempty"`
|
||||
}{
|
||||
Problem: p.Problem,
|
||||
RetryAfter: int(t.Seconds()),
|
||||
})
|
||||
default:
|
||||
return nil, errors.New("problems-not-implemented: RetryAfter is not of type time.Time or time.Duration")
|
||||
}
|
||||
}
|
||||
|
||||
func (p TooManyRequests[T]) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
|
||||
switch t := any(p.RetryAfter).(type) {
|
||||
case time.Time:
|
||||
return e.Encode(struct {
|
||||
Problem
|
||||
RetryAfter string `xml:"retry-after,omitempty"`
|
||||
}{
|
||||
Problem: p.Problem,
|
||||
RetryAfter: t.Format(time.RFC3339),
|
||||
})
|
||||
case time.Duration:
|
||||
return e.Encode(struct {
|
||||
Problem
|
||||
RetryAfter int `xml:"retry-after,omitempty"`
|
||||
}{
|
||||
Problem: p.Problem,
|
||||
RetryAfter: int(t.Seconds()),
|
||||
})
|
||||
default:
|
||||
return errors.New("problems-not-implemented: RetryAfter is not of type time.Time or time.Duration")
|
||||
}
|
||||
}
|
||||
|
||||
func (p TooManyRequests[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch t := any(p.RetryAfter).(type) {
|
||||
case time.Time:
|
||||
if !t.IsZero() {
|
||||
w.Header().Set("Retry-After", t.Format(http.TimeFormat))
|
||||
}
|
||||
case time.Duration:
|
||||
if t != 0 {
|
||||
w.Header().Set("Retry-After", fmt.Sprintf("%.0f", t.Seconds()))
|
||||
}
|
||||
}
|
||||
p.handler(p).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewRequestHeaderFieldsTooLarge(opts ...Option) RequestHeaderFieldsTooLarge {
|
||||
return RequestHeaderFieldsTooLarge(NewStatus(http.StatusRequestHeaderFieldsTooLarge, opts...))
|
||||
}
|
||||
|
||||
type RequestHeaderFieldsTooLarge Problem
|
||||
|
||||
func NewUnavailableForLegalReasons(opts ...Option) UnavailableForLegalReasons {
|
||||
return UnavailableForLegalReasons(NewStatus(http.StatusUnavailableForLegalReasons, opts...))
|
||||
}
|
||||
|
||||
type UnavailableForLegalReasons Problem
|
||||
Reference in New Issue
Block a user