openmeter / pkg /models /problem.go
Leon4gr45's picture
Upload folder using huggingface_hub (part 9)
fea99b3 verified
Raw
History Blame Contribute Delete
4.02 kB
package models
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/go-chi/chi/v5/middleware"
)
// ProblemContentType is the default content type for a Problem response
const ProblemContentType = "application/problem+json"
// ProblemType contains a URI that identifies the problem type. This URI will,
// ideally, contain human-readable documentation for the problem when
// de-referenced.
type ProblemType string
const (
// ProblemTypeDefault is the default problem type.
ProblemTypeDefault = ProblemType("about:blank")
// TODO: add more problem types that can be used to link to docs.
// ...
)
// Problem is the RFC 7807 response body.
type Problem interface {
Respond(w http.ResponseWriter)
Error() string
ProblemType() ProblemType
ProblemTitle() string
ProblemStatus() int
}
// StatusProblem is the RFC 7807 response body without additional fields.
type StatusProblem struct {
Err error `json:"-"` // low-level runtime error
// Type is a URI reference that identifies the problem type.
Type ProblemType `json:"type"`
// Title is a short, human-readable summary of the problem type.
Title string `json:"title"`
// Status is the HTTP status code generated by the origin server for this occurrence of the problem.
Status int `json:"status"`
// Detail is a human-readable explanation specific to this occurrence of the problem.
Detail string `json:"detail,omitempty"`
// Instance is a URI reference that identifies the specific occurrence of the problem.
Instance string `json:"instance,omitempty"`
// Extension fields are allowed by spec, see https://datatracker.ietf.org/doc/html/rfc7807#section-3.2
Extensions map[string]interface{} `json:"extensions,omitempty"`
}
var _ Problem = (*StatusProblem)(nil)
func (p *StatusProblem) Error() string {
if p.Err == nil {
return fmt.Sprintf("[%s] %s", p.Title, p.Detail)
}
return fmt.Sprintf("[%s] %s - %s", p.Title, p.Err.Error(), p.Detail)
}
func (p *StatusProblem) RawError() error {
return p.Err
}
func (p *StatusProblem) ProblemType() ProblemType {
return p.Type
}
func (p *StatusProblem) ProblemStatus() int {
return p.Status
}
func (p *StatusProblem) ProblemTitle() string {
return p.Title
}
// Respond will render the problem as JSON to the provided ResponseWriter.
func (p *StatusProblem) Respond(w http.ResponseWriter) {
RespondProblem(p, w)
}
// Respond will render the problem as JSON to the provided ResponseWriter.
func RespondProblem(problem Problem, w http.ResponseWriter) {
// Respond
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(true)
_ = enc.Encode(problem)
w.Header().Set("Content-Type", ProblemContentType)
w.WriteHeader(problem.ProblemStatus())
_, _ = w.Write(buf.Bytes()) // nosemgrep
}
// NewStatusProblem will generate a problem for the provided HTTP status
// code. The Problem's Status field will be set to match the status argument,
// and the Title will be set to the default Go status text for that code.
func NewStatusProblem(ctx context.Context, err error, status int) *StatusProblem {
var instance string
reqID := middleware.GetReqID(ctx)
if reqID != "" {
instance = fmt.Sprintf("urn:request:%s", reqID)
}
// Set context canceled errors to 408.
// Context canceled errors either happen when the client cancels the request or when a timeout happens in dependency.
// If client cancels the request, the status code doesn't matter, because the client will not see the response.
// If timeout happens in dependency, we want to return 408 to the client.
if err != nil && strings.Contains(err.Error(), "context canceled") {
status = http.StatusRequestTimeout
}
var detail string
if err != nil {
detail = err.Error()
}
if status == http.StatusInternalServerError {
detail = ""
}
return &StatusProblem{
Err: err,
Type: ProblemTypeDefault,
Title: http.StatusText(status),
Status: status,
Detail: detail,
Instance: instance,
Extensions: map[string]interface{}{},
}
}