diff --git a/HISTORY.md b/HISTORY.md index 113350c1..63e3d7e6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,6 +28,9 @@ The codebase for Dependency Injection, Internationalization and localization and ## Fixes and Improvements +- Add `Context.SetJSONOptions` to customize on a higher level the JSON options on `Context.JSON` calls. +- Add new `sso` sub-package which helps on any user type auth using JWT (access & refresh tokens) and a cookie (optional). + - Add `Party.EnsureStaticBindings` which, if called, the MVC binder panics if a struct's input binding depends on the HTTP request data instead of a static dependency. This is useful to make sure your API crafted through `Party.PartyConfigure` depends only on struct values you already defined at `Party.RegisterDependency` == will never use reflection at serve-time (maximum performance). - Add a new [x/sqlx](/x/sqlx/) sub-package ([example](_examples/database/sqlx/main.go)). diff --git a/context/context.go b/context/context.go index e1944f4e..9d87a409 100644 --- a/context/context.go +++ b/context/context.go @@ -3761,6 +3761,16 @@ type JSON struct { // Optional context cancelation of encoder when Iris optimizations field is enabled. // On JSON method this is automatically binded to the request context. Context stdContext.Context + + // ErrorHandler can be optionally registered to fire a customized + // error to the client on JSON write failures. + ErrorHandler ErrorHandler +} + +// ErrorHandler describes a context error handler. As for today this is only used +// to globally or per-party or per-route handle JSON writes error. +type ErrorHandler interface { + HandleContextError(ctx *Context, err error) } // IsDefault reports whether this JSON options structure holds the default values. @@ -3771,7 +3781,8 @@ func (j *JSON) IsDefault() bool { j.Prefix == DefaultJSONOptions.Prefix && j.ASCII == DefaultJSONOptions.ASCII && j.Secure == DefaultJSONOptions.Secure && - j.Proto == DefaultJSONOptions.Proto + j.Proto == DefaultJSONOptions.Proto && + j.ErrorHandler == nil } // GetContext returns the option's Context or the HTTP request's one. @@ -3942,18 +3953,55 @@ func stringToBytes(s string) []byte { // inside `ctx.JSON`. var DefaultJSONOptions = JSON{} +const jsonOptionsContextKey = "iris.context.json_options" + +// SetJSONOptions stores the given JSON options to the handler +// for any next Context.JSON calls. Note that the Context.JSON's +// variadic options have priority over these given options. +func (ctx *Context) SetJSONOptions(opts JSON) { + ctx.values.Set(jsonOptionsContextKey, opts) +} + +func (ctx *Context) getJSONOptions() (JSON, bool) { + if v := ctx.values.Get(jsonOptionsContextKey); v != nil { + opts, ok := v.(JSON) + return opts, ok + } + + return DefaultJSONOptions, false +} + // JSON marshals the given interface object and writes the JSON response to the client. // If the value is a compatible `proto.Message` one // then it only uses the options.Proto settings to marshal. func (ctx *Context) JSON(v interface{}, opts ...JSON) (n int, err error) { + if n, err = ctx.writeJSON(v, opts...); err != nil { + if opts, ok := ctx.getJSONOptions(); ok { + opts.ErrorHandler.HandleContextError(ctx, err) + } // keep the error so the caller has control over further actions. + } + + return +} + +func (ctx *Context) writeJSON(v interface{}, opts ...JSON) (n int, err error) { ctx.ContentType(ContentJSONHeaderValue) shouldOptimize := ctx.shouldOptimize() + options := DefaultJSONOptions optsLength := len(opts) + if optsLength > 0 { + options = opts[0] + } else { + if opt, ok := ctx.getJSONOptions(); ok { + opts = []JSON{opt} + optsLength = 1 + } + } if shouldOptimize && optsLength == 0 { // if no options given and optimizations are enabled. // try handle proto or easyjson. - if handled, n, err := handleJSONResponseValue(ctx, v, DefaultJSONOptions); handled { + if handled, n, err := handleJSONResponseValue(ctx, v, options); handled { return n, err } @@ -3966,11 +4014,6 @@ func (ctx *Context) JSON(v interface{}, opts ...JSON) (n int, err error) { return ctx.Write(result) } - options := DefaultJSONOptions - if optsLength > 0 { - options = opts[0] - } - if options.StreamingJSON { if shouldOptimize { // jsoniterConfig := jsoniter.Config{ diff --git a/x/errors/errors.go b/x/errors/errors.go index be9ee8c3..2ef2438a 100644 --- a/x/errors/errors.go +++ b/x/errors/errors.go @@ -248,7 +248,7 @@ type Error struct { } // Error method completes the error interface. It just returns the canonical name, status code, message and details. -func (err Error) Error() string { +func (err *Error) Error() string { if err.Message == "" { err.Message = "" } @@ -312,6 +312,6 @@ func fail(ctx *context.Context, codeName ErrorCodeName, msg, details string, val Validation: validationErrors, } - // ctx.SetErr(err) + // ctx.SetErr(&err) ctx.StopWithJSON(errorCode.Status, err) }