mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
add ProblemOptions https://github.com/kataras/iris/issues/1335#issuecomment-521546506
Former-commit-id: 6c03439d21175f120c37a7d8dd067a0d10de837a
This commit is contained in:
parent
e0af8ab29a
commit
c9e9a4b3bc
|
@ -82,5 +82,27 @@ func problemExample(ctx iris.Context) {
|
||||||
|
|
||||||
// Response like JSON but with indent of " " and
|
// Response like JSON but with indent of " " and
|
||||||
// content type of "application/problem+json"
|
// content type of "application/problem+json"
|
||||||
ctx.Problem(newProductProblem("product name", "problem error details"))
|
ctx.Problem(newProductProblem("product name", "problem error details"), iris.ProblemOptions{
|
||||||
|
// Optional JSON renderer settings.
|
||||||
|
JSON: iris.JSON{
|
||||||
|
Indent: " ",
|
||||||
|
},
|
||||||
|
// Sets the "Retry-After" response header.
|
||||||
|
//
|
||||||
|
// Can accept:
|
||||||
|
// time.Time for HTTP-Date,
|
||||||
|
// time.Duration, int64, float64, int for seconds
|
||||||
|
// or string for date or duration.
|
||||||
|
// Examples:
|
||||||
|
// time.Now().Add(5 * time.Minute),
|
||||||
|
// 300 * time.Second,
|
||||||
|
// "5m",
|
||||||
|
//
|
||||||
|
RetryAfter: 300,
|
||||||
|
// A function that, if specified, can dynamically set
|
||||||
|
// retry-after based on the request. Useful for ProblemOptions reusability.
|
||||||
|
// Overrides the RetryAfter field.
|
||||||
|
//
|
||||||
|
// RetryAfterFunc: func(iris.Context) interface{} { [...] }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -779,13 +779,14 @@ type Context interface {
|
||||||
// JSON marshals the given interface object and writes the JSON response.
|
// JSON marshals the given interface object and writes the JSON response.
|
||||||
JSON(v interface{}, options ...JSON) (int, error)
|
JSON(v interface{}, options ...JSON) (int, error)
|
||||||
// Problem writes a JSON problem response.
|
// Problem writes a JSON problem response.
|
||||||
// Order of fields are not always the same.
|
// Order of Problem fields are not always rendered the same.
|
||||||
//
|
//
|
||||||
// Behaves exactly like `Context.JSON`
|
// Behaves exactly like `Context.JSON`
|
||||||
// but with indent of " " and a content type of "application/problem+json" instead.
|
// but with default ProblemOptions.JSON indent of " " and
|
||||||
|
// a response content type of "application/problem+json" instead.
|
||||||
//
|
//
|
||||||
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
||||||
Problem(v interface{}, opts ...JSON) (int, error)
|
Problem(v interface{}, opts ...ProblemOptions) (int, error)
|
||||||
// JSONP marshals the given interface object and writes the JSON response.
|
// JSONP marshals the given interface object and writes the JSON response.
|
||||||
JSONP(v interface{}, options ...JSONP) (int, error)
|
JSONP(v interface{}, options ...JSONP) (int, error)
|
||||||
// XML marshals the given interface object and writes the XML response.
|
// XML marshals the given interface object and writes the XML response.
|
||||||
|
@ -3187,18 +3188,21 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Problem writes a JSON problem response.
|
// Problem writes a JSON problem response.
|
||||||
// Order of fields are not always the same.
|
// Order of Problem fields are not always rendered the same.
|
||||||
//
|
//
|
||||||
// Behaves exactly like `Context.JSON`
|
// Behaves exactly like `Context.JSON`
|
||||||
// but with indent of " " and a content type of "application/problem+json" instead.
|
// but with default ProblemOptions.JSON indent of " " and
|
||||||
|
// a response content type of "application/problem+json" instead.
|
||||||
//
|
//
|
||||||
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
||||||
func (ctx *context) Problem(v interface{}, opts ...JSON) (int, error) {
|
func (ctx *context) Problem(v interface{}, opts ...ProblemOptions) (int, error) {
|
||||||
options := DefaultJSONOptions
|
options := DefaultProblemOptions
|
||||||
if len(opts) > 0 {
|
if len(opts) > 0 {
|
||||||
options = opts[0]
|
options = opts[0]
|
||||||
} else {
|
// Currently apply only if custom options passsed, otherwise,
|
||||||
options.Indent = " "
|
// with the current settings, it's not required.
|
||||||
|
// This may change in the future though.
|
||||||
|
options.Apply(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p, ok := v.(Problem); ok {
|
if p, ok := v.(Problem); ok {
|
||||||
|
@ -3209,7 +3213,7 @@ func (ctx *context) Problem(v interface{}, opts ...JSON) (int, error) {
|
||||||
|
|
||||||
ctx.contentTypeOnce(ContentJSONProblemHeaderValue, "")
|
ctx.contentTypeOnce(ContentJSONProblemHeaderValue, "")
|
||||||
|
|
||||||
return ctx.JSON(v, options)
|
return ctx.JSON(v, options.JSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -2,7 +2,10 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Problem Details for HTTP APIs.
|
// Problem Details for HTTP APIs.
|
||||||
|
@ -84,6 +87,28 @@ func (p Problem) updateTypeToAbsolute(ctx Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
problemTempKeyPrefix = "@temp_"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TempKey sets a temporary key-value pair, which is being removed
|
||||||
|
// on the its first get.
|
||||||
|
func (p Problem) TempKey(key string, value interface{}) Problem {
|
||||||
|
return p.Key(problemTempKeyPrefix+key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTempKey returns the temp value based on "key" and removes it.
|
||||||
|
func (p Problem) GetTempKey(key string) interface{} {
|
||||||
|
key = problemTempKeyPrefix + key
|
||||||
|
v, ok := p[key]
|
||||||
|
if ok {
|
||||||
|
delete(p, key)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Key sets a custom key-value pair.
|
// Key sets a custom key-value pair.
|
||||||
func (p Problem) Key(key string, value interface{}) Problem {
|
func (p Problem) Key(key string, value interface{}) Problem {
|
||||||
p[key] = value
|
p[key] = value
|
||||||
|
@ -170,3 +195,85 @@ func (p Problem) Error() string {
|
||||||
|
|
||||||
return fmt.Sprintf("[%d] %s", p["status"], p["title"])
|
return fmt.Sprintf("[%d] %s", p["status"], p["title"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultProblemOptions the default options for `Context.Problem` method.
|
||||||
|
var DefaultProblemOptions = ProblemOptions{
|
||||||
|
JSON: JSON{Indent: " "},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProblemOptions the optional settings when server replies with a Problem.
|
||||||
|
// See `Context.Problem` method and `Problem` type for more details.
|
||||||
|
type ProblemOptions struct {
|
||||||
|
// JSON are the optional JSON renderer options.
|
||||||
|
JSON JSON
|
||||||
|
|
||||||
|
// RetryAfter sets the Retry-After response header.
|
||||||
|
// https://tools.ietf.org/html/rfc7231#section-7.1.3
|
||||||
|
// The value can be one of those:
|
||||||
|
// time.Time
|
||||||
|
// time.Duration for seconds
|
||||||
|
// int64, int, float64 for seconds
|
||||||
|
// string for duration string or for datetime string.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// time.Now().Add(5 * time.Minute),
|
||||||
|
// 300 * time.Second,
|
||||||
|
// "5m",
|
||||||
|
// 300
|
||||||
|
RetryAfter interface{}
|
||||||
|
// A function that, if specified, can dynamically set
|
||||||
|
// retry-after based on the request. Useful for ProblemOptions reusability.
|
||||||
|
// Should return time.Time, time.Duration, int64, int, float64 or string.
|
||||||
|
//
|
||||||
|
// Overrides the RetryAfter field.
|
||||||
|
RetryAfterFunc func(Context) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDurationToSeconds(dur time.Duration) int64 {
|
||||||
|
return int64(math.Round(dur.Seconds()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ProblemOptions) parseRetryAfter(value interface{}, timeLayout string) string {
|
||||||
|
// https://tools.ietf.org/html/rfc7231#section-7.1.3
|
||||||
|
// Retry-After = HTTP-date / delay-seconds
|
||||||
|
switch v := value.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.FormatInt(v, 10)
|
||||||
|
case int:
|
||||||
|
return o.parseRetryAfter(int64(v), timeLayout)
|
||||||
|
case float64:
|
||||||
|
return o.parseRetryAfter(int64(math.Round(v)), timeLayout)
|
||||||
|
case time.Time:
|
||||||
|
return v.Format(timeLayout)
|
||||||
|
case time.Duration:
|
||||||
|
return o.parseRetryAfter(parseDurationToSeconds(v), timeLayout)
|
||||||
|
case string:
|
||||||
|
dur, err := time.ParseDuration(v)
|
||||||
|
if err != nil {
|
||||||
|
t, err := time.Parse(timeLayout, v)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.parseRetryAfter(t, timeLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.parseRetryAfter(parseDurationToSeconds(dur), timeLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply accepts a Context and applies specific response-time options.
|
||||||
|
func (o *ProblemOptions) Apply(ctx Context) {
|
||||||
|
retryAfterHeaderValue := ""
|
||||||
|
timeLayout := ctx.Application().ConfigurationReadOnly().GetTimeFormat()
|
||||||
|
|
||||||
|
if o.RetryAfterFunc != nil {
|
||||||
|
retryAfterHeaderValue = o.parseRetryAfter(o.RetryAfterFunc(ctx), timeLayout)
|
||||||
|
} else if o.RetryAfter != nil {
|
||||||
|
retryAfterHeaderValue = o.parseRetryAfter(o.RetryAfter, timeLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Header("Retry-After", retryAfterHeaderValue)
|
||||||
|
}
|
||||||
|
|
11
go19.go
11
go19.go
|
@ -56,8 +56,17 @@ type (
|
||||||
//
|
//
|
||||||
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
// Read more at: https://github.com/kataras/iris/wiki/Routing-error-handlers
|
||||||
//
|
//
|
||||||
// It is an alias of `context.Problem` type.
|
// It is an alias of the `context#Problem` type.
|
||||||
Problem = context.Problem
|
Problem = context.Problem
|
||||||
|
// ProblemOptions the optional settings when server replies with a Problem.
|
||||||
|
// See `Context.Problem` method and `Problem` type for more details.
|
||||||
|
//
|
||||||
|
// It is an alias of the `context#ProblemOptions` type.
|
||||||
|
ProblemOptions = context.ProblemOptions
|
||||||
|
// JSON the optional settings for JSON renderer.
|
||||||
|
//
|
||||||
|
// It is an alias of the `context#JSON` type.
|
||||||
|
JSON = context.JSON
|
||||||
|
|
||||||
// Supervisor is a shortcut of the `host#Supervisor`.
|
// Supervisor is a shortcut of the `host#Supervisor`.
|
||||||
// Used to add supervisor configurators on common Runners
|
// Used to add supervisor configurators on common Runners
|
||||||
|
|
Loading…
Reference in New Issue
Block a user