mirror of
https://github.com/kataras/iris.git
synced 2025-12-18 02:17:05 +00:00
implement Problem Details for HTTP APIs #1335
Former-commit-id: ff789b6d535080c88e05c81ab3fb7d9689801ec7
This commit is contained in:
172
context/problem.go
Normal file
172
context/problem.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Problem Details for HTTP APIs.
|
||||
// Pass a Problem value to `context.Problem` to
|
||||
// write an "application/problem+json" response.
|
||||
//
|
||||
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
||||
type Problem map[string]interface{}
|
||||
|
||||
// NewProblem retruns a new Problem.
|
||||
// Head over to the `Problem` type godoc for more.
|
||||
func NewProblem() Problem {
|
||||
p := make(Problem)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p Problem) keyExists(key string) bool {
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, found := p[key]
|
||||
return found
|
||||
}
|
||||
|
||||
// DefaultProblemStatusCode is being sent to the client
|
||||
// when Problem's status is not a valid one.
|
||||
var DefaultProblemStatusCode = http.StatusBadRequest
|
||||
|
||||
func (p Problem) getStatus() (int, bool) {
|
||||
statusField, found := p["status"]
|
||||
if !found {
|
||||
return DefaultProblemStatusCode, false
|
||||
}
|
||||
|
||||
status, ok := statusField.(int)
|
||||
if !ok {
|
||||
return DefaultProblemStatusCode, false
|
||||
}
|
||||
|
||||
if !StatusCodeNotSuccessful(status) {
|
||||
return DefaultProblemStatusCode, false
|
||||
}
|
||||
|
||||
return status, true
|
||||
}
|
||||
|
||||
func isEmptyTypeURI(uri string) bool {
|
||||
return uri == "" || uri == "about:blank"
|
||||
}
|
||||
|
||||
func (p Problem) getType() string {
|
||||
typeField, found := p["type"]
|
||||
if found {
|
||||
if typ, ok := typeField.(string); ok {
|
||||
if !isEmptyTypeURI(typ) {
|
||||
return typ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Updates "type" field to absolute URI, recursively.
|
||||
func (p Problem) updateTypeToAbsolute(ctx Context) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if uriRef := p.getType(); uriRef != "" {
|
||||
p.Type(ctx.AbsoluteURI(uriRef))
|
||||
}
|
||||
|
||||
if cause, ok := p["cause"]; ok {
|
||||
if causeP, ok := cause.(Problem); ok {
|
||||
causeP.updateTypeToAbsolute(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Key sets a custom key-value pair.
|
||||
func (p Problem) Key(key string, value interface{}) Problem {
|
||||
p[key] = value
|
||||
return p
|
||||
}
|
||||
|
||||
// Type URI SHOULD resolve to HTML [W3C.REC-html5-20141028]
|
||||
// documentation that explains how to resolve the problem.
|
||||
// Example: "https://example.net/validation-error"
|
||||
//
|
||||
// Empty URI or "about:blank", when used as a problem type,
|
||||
// indicates that the problem has no additional semantics beyond that of the HTTP status code.
|
||||
// When "about:blank" is used,
|
||||
// the title is being automatically set the same as the recommended HTTP status phrase for that code
|
||||
// (e.g., "Not Found" for 404, and so on) on `Status` call.
|
||||
//
|
||||
// Relative paths are also valid when writing this Problem to an Iris Context.
|
||||
func (p Problem) Type(uri string) Problem {
|
||||
return p.Key("type", uri)
|
||||
}
|
||||
|
||||
// Title sets the problem's title field.
|
||||
// Example: "Your request parameters didn't validate."
|
||||
// It is set to status Code text if missing,
|
||||
// (e.g., "Not Found" for 404, and so on).
|
||||
func (p Problem) Title(title string) Problem {
|
||||
return p.Key("title", title)
|
||||
}
|
||||
|
||||
// Status sets HTTP error code for problem's status field.
|
||||
// Example: 404
|
||||
//
|
||||
// It is required.
|
||||
func (p Problem) Status(statusCode int) Problem {
|
||||
shouldOverrideTitle := !p.keyExists("title")
|
||||
|
||||
if !shouldOverrideTitle {
|
||||
typ, found := p["type"]
|
||||
shouldOverrideTitle = !found || isEmptyTypeURI(typ.(string))
|
||||
}
|
||||
|
||||
if shouldOverrideTitle {
|
||||
// Set title by code.
|
||||
p.Title(http.StatusText(statusCode))
|
||||
}
|
||||
return p.Key("status", statusCode)
|
||||
}
|
||||
|
||||
// Detail sets the problem's detail field.
|
||||
// Example: "Optional details about the error...".
|
||||
func (p Problem) Detail(detail string) Problem {
|
||||
return p.Key("detail", detail)
|
||||
}
|
||||
|
||||
// Cause sets the problem's cause field.
|
||||
// Any chain of problems.
|
||||
func (p Problem) Cause(cause Problem) Problem {
|
||||
if !cause.Validate() {
|
||||
return p
|
||||
}
|
||||
|
||||
return p.Key("cause", cause)
|
||||
}
|
||||
|
||||
// Validate reports whether this Problem value is a valid problem one.
|
||||
func (p Problem) Validate() bool {
|
||||
// A nil problem is not a valid one.
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return p.keyExists("type") &&
|
||||
p.keyExists("title") &&
|
||||
p.keyExists("status")
|
||||
}
|
||||
|
||||
// Error method completes the go error.
|
||||
// Returns the "[Status] Title" string form of this Problem.
|
||||
// If Problem is not a valid one, it returns "invalid problem".
|
||||
func (p Problem) Error() string {
|
||||
if !p.Validate() {
|
||||
return "invalid problem"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[%d] %s", p["status"], p["title"])
|
||||
}
|
||||
Reference in New Issue
Block a user