mirror of
https://github.com/kataras/iris.git
synced 2025-03-15 06:46:26 +01:00
add 'context.StopWithStatus, StopWithJSON, StopWithProblem' and update the json-struct-validation example
Former-commit-id: dd0347f22324ef4913be284082b8afc6229206a8
This commit is contained in:
parent
ad154ea479
commit
978718454a
10
HISTORY.md
10
HISTORY.md
|
@ -169,14 +169,18 @@ Other Improvements:
|
||||||
|
|
||||||
New Context Methods:
|
New Context Methods:
|
||||||
|
|
||||||
|
- `context.StopWithStatus(int)` stops the handlers chain and writes the status code
|
||||||
|
- `context.StopWithJSON(int, interface{})` stops the handlers chain, writes the status code and sends a JSON response
|
||||||
|
- `context.StopWithProblem(int, iris.Problem)` stops the handlers, writes the status code and sends an `application/problem+json` response
|
||||||
- `context.Protobuf(proto.Message)` sends protobuf to the client
|
- `context.Protobuf(proto.Message)` sends protobuf to the client
|
||||||
- `context.MsgPack(interface{})` sends msgpack format data to the client
|
- `context.MsgPack(interface{})` sends msgpack format data to the client
|
||||||
- `context.ReadProtobuf(ptr)` binds request body to a proto message
|
- `context.ReadProtobuf(ptr)` binds request body to a proto message
|
||||||
- `context.ReadMsgPack(ptr)` binds request body of a msgpack format to a struct
|
- `context.ReadMsgPack(ptr)` binds request body of a msgpack format to a struct
|
||||||
- `context.ReadBody(ptr)` binds the request body to the "ptr" depending on the request's Method and ContentType
|
- `context.ReadBody(ptr)` binds the request body to the "ptr" depending on the request's Method and Content-Type
|
||||||
- `context.Defer(Handler)` works like `Party.Done` but for the request life-cycle.
|
- `context.SetSameSite(http.SameSite)` to set cookie "SameSite" option (respectful by sessions too)
|
||||||
|
- `context.Defer(Handler)` works like `Party.Done` but for the request life-cycle instead
|
||||||
- `context.ReflectValue() []reflect.Value` stores and returns the `[]reflect.ValueOf(context)`
|
- `context.ReflectValue() []reflect.Value` stores and returns the `[]reflect.ValueOf(context)`
|
||||||
- `context.Controller() reflect.Value` returns the current MVC Controller value (when fired from inside a controller's method).
|
- `context.Controller() reflect.Value` returns the current MVC Controller value.
|
||||||
|
|
||||||
Breaking Changes:
|
Breaking Changes:
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,14 @@ func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
app.Validator = validator.New()
|
app.Validator = validator.New()
|
||||||
|
|
||||||
app.Post("/user", postUser)
|
userRouter := app.Party("/user")
|
||||||
|
{
|
||||||
|
userRouter.Get("/validation-errors", resolveErrorsDocumentation)
|
||||||
|
userRouter.Post("/", postUser)
|
||||||
|
}
|
||||||
|
|
||||||
// Use Postman or any tool to perform a POST request
|
// Use Postman or any tool to perform a POST request
|
||||||
// to the http://localhost:8080/user with RAW BODY:
|
// to the http://localhost:8080/user with RAW BODY of:
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
"fname": "",
|
"fname": "",
|
||||||
|
@ -38,26 +42,30 @@ func main() {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
/* The response should be:
|
/* The response should be:
|
||||||
{
|
{
|
||||||
"fields": [
|
"title": "Validation error",
|
||||||
{
|
"detail": "One or more fields failed to be validated",
|
||||||
"tag": "required",
|
"type": "http://localhost:8080/user/validation-errors",
|
||||||
"namespace": "User.FirstName",
|
"status": 400,
|
||||||
"kind": "string",
|
"fields": [
|
||||||
"type": "string",
|
{
|
||||||
"value": "",
|
"tag": "required",
|
||||||
"param": ""
|
"namespace": "User.FirstName",
|
||||||
},
|
"kind": "string",
|
||||||
{
|
"type": "string",
|
||||||
"tag": "required",
|
"value": "",
|
||||||
"namespace": "User.LastName",
|
"param": ""
|
||||||
"kind": "string",
|
},
|
||||||
"type": "string",
|
{
|
||||||
"value": "",
|
"tag": "required",
|
||||||
"param": ""
|
"namespace": "User.LastName",
|
||||||
}
|
"kind": "string",
|
||||||
]
|
"type": "string",
|
||||||
}
|
"value": "",
|
||||||
|
"param": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
app.Listen(":8080")
|
app.Listen(":8080")
|
||||||
}
|
}
|
||||||
|
@ -89,11 +97,7 @@ type validationError struct {
|
||||||
Param string `json:"param"`
|
Param string `json:"param"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type errorsResponse struct {
|
func wrapValidationErrors(errs validator.ValidationErrors) []validationError {
|
||||||
ValidationErrors []validationError `json:"fields,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapValidationErrors(errs validator.ValidationErrors) errorsResponse {
|
|
||||||
validationErrors := make([]validationError, 0, len(errs))
|
validationErrors := make([]validationError, 0, len(errs))
|
||||||
for _, validationErr := range errs {
|
for _, validationErr := range errs {
|
||||||
validationErrors = append(validationErrors, validationError{
|
validationErrors = append(validationErrors, validationError{
|
||||||
|
@ -106,26 +110,37 @@ func wrapValidationErrors(errs validator.ValidationErrors) errorsResponse {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorsResponse{
|
return validationErrors
|
||||||
ValidationErrors: validationErrors,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func postUser(ctx iris.Context) {
|
func postUser(ctx iris.Context) {
|
||||||
var user User
|
var user User
|
||||||
err := ctx.ReadJSON(&user)
|
err := ctx.ReadJSON(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Handle the error, below you will find the right way to do that...
|
||||||
|
|
||||||
if errs, ok := err.(validator.ValidationErrors); ok {
|
if errs, ok := err.(validator.ValidationErrors); ok {
|
||||||
response := wrapValidationErrors(errs)
|
// Wrap the errors with JSON format, the underline library returns the errors as interface.
|
||||||
ctx.StatusCode(iris.StatusBadRequest)
|
validationErrors := wrapValidationErrors(errs)
|
||||||
ctx.JSON(response)
|
|
||||||
|
// Fire an application/json+problem response and stop the handlers chain.
|
||||||
|
ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().
|
||||||
|
Title("Validation error").
|
||||||
|
Detail("One or more fields failed to be validated").
|
||||||
|
Type("/user/validation-errors").
|
||||||
|
Key("errors", validationErrors))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.StatusCode(iris.StatusInternalServerError)
|
// It's probably an internal JSON error, let's dont give more info here.
|
||||||
ctx.WriteString(err.Error())
|
ctx.StopWithStatus(iris.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(iris.Map{"message": "OK"})
|
ctx.JSON(iris.Map{"message": "OK"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveErrorsDocumentation(ctx iris.Context) {
|
||||||
|
ctx.WriteString("A page that should document to web developers or users of the API on how to resolve the validation errors")
|
||||||
|
}
|
||||||
|
|
|
@ -249,12 +249,32 @@ type Context interface {
|
||||||
// Skip skips/ignores the next handler from the handlers chain,
|
// Skip skips/ignores the next handler from the handlers chain,
|
||||||
// it should be used inside a middleware.
|
// it should be used inside a middleware.
|
||||||
Skip()
|
Skip()
|
||||||
// StopExecution if called then the following .Next calls are ignored,
|
// StopExecution stops the handlers chain of this request.
|
||||||
|
// Meaning that any following `Next` calls are ignored,
|
||||||
// as a result the next handlers in the chain will not be fire.
|
// as a result the next handlers in the chain will not be fire.
|
||||||
StopExecution()
|
StopExecution()
|
||||||
// IsStopped checks and returns true if the current position of the Context is 255,
|
// IsStopped reports whether the current position of the context's handlers is -1,
|
||||||
// means that the StopExecution() was called.
|
// means that the StopExecution() was called at least once.
|
||||||
IsStopped() bool
|
IsStopped() bool
|
||||||
|
// StopWithJSON stops the handlers chain and writes the "statusCode".
|
||||||
|
//
|
||||||
|
// If the status code is a failure one then
|
||||||
|
// it will also fire the specified error code handler.
|
||||||
|
StopWithStatus(statusCode int)
|
||||||
|
// StopWithJSON stops the handlers chain, writes the status code
|
||||||
|
// and sends a JSON response.
|
||||||
|
//
|
||||||
|
// If the status code is a failure one then
|
||||||
|
// it will also fire the specified error code handler.
|
||||||
|
StopWithJSON(statusCode int, jsonObject interface{})
|
||||||
|
// StopWithProblem stops the handlers chain, writes the status code
|
||||||
|
// and sends an application/problem+json response.
|
||||||
|
// See `iris.NewProblem` to build a "problem" value correctly.
|
||||||
|
//
|
||||||
|
// If the status code is a failure one then
|
||||||
|
// it will also fire the specified error code handler.
|
||||||
|
StopWithProblem(statusCode int, problem Problem)
|
||||||
|
|
||||||
// OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev)
|
// OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev)
|
||||||
// when the underlying connection has gone away.
|
// when the underlying connection has gone away.
|
||||||
//
|
//
|
||||||
|
@ -1452,18 +1472,50 @@ func (ctx *context) Skip() {
|
||||||
|
|
||||||
const stopExecutionIndex = -1 // I don't set to a max value because we want to be able to reuse the handlers even if stopped with .Skip
|
const stopExecutionIndex = -1 // I don't set to a max value because we want to be able to reuse the handlers even if stopped with .Skip
|
||||||
|
|
||||||
// StopExecution if called then the following .Next calls are ignored,
|
// StopExecution stops the handlers chain of this request.
|
||||||
|
// Meaning that any following `Next` calls are ignored,
|
||||||
// as a result the next handlers in the chain will not be fire.
|
// as a result the next handlers in the chain will not be fire.
|
||||||
func (ctx *context) StopExecution() {
|
func (ctx *context) StopExecution() {
|
||||||
ctx.currentHandlerIndex = stopExecutionIndex
|
ctx.currentHandlerIndex = stopExecutionIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsStopped checks and returns true if the current position of the context is -1,
|
// IsStopped reports whether the current position of the context's handlers is -1,
|
||||||
// means that the StopExecution() was called.
|
// means that the StopExecution() was called at least once.
|
||||||
func (ctx *context) IsStopped() bool {
|
func (ctx *context) IsStopped() bool {
|
||||||
return ctx.currentHandlerIndex == stopExecutionIndex
|
return ctx.currentHandlerIndex == stopExecutionIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopWithJSON stops the handlers chain and writes the "statusCode".
|
||||||
|
//
|
||||||
|
// If the status code is a failure one then
|
||||||
|
// it will also fire the specified error code handler.
|
||||||
|
func (ctx *context) StopWithStatus(statusCode int) {
|
||||||
|
ctx.StopExecution()
|
||||||
|
ctx.StatusCode(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopWithJSON stops the handlers chain, writes the status code
|
||||||
|
// and sends a JSON response.
|
||||||
|
//
|
||||||
|
// If the status code is a failure one then
|
||||||
|
// it will also fire the specified error code handler.
|
||||||
|
func (ctx *context) StopWithJSON(statusCode int, jsonObject interface{}) {
|
||||||
|
ctx.StopWithStatus(statusCode)
|
||||||
|
ctx.JSON(jsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopWithProblem stops the handlers chain, writes the status code
|
||||||
|
// and sends an application/problem+json response.
|
||||||
|
// See `iris.NewProblem` to build a "problem" value correctly.
|
||||||
|
//
|
||||||
|
// If the status code is a failure one then
|
||||||
|
// it will also fire the specified error code handler.
|
||||||
|
func (ctx *context) StopWithProblem(statusCode int, problem Problem) {
|
||||||
|
ctx.StopWithStatus(statusCode)
|
||||||
|
problem.Status(statusCode)
|
||||||
|
ctx.Problem(problem)
|
||||||
|
}
|
||||||
|
|
||||||
// OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev)
|
// OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev)
|
||||||
// when the underlying connection has gone away.
|
// when the underlying connection has gone away.
|
||||||
//
|
//
|
||||||
|
@ -3641,7 +3693,13 @@ func (ctx *context) Problem(v interface{}, opts ...ProblemOptions) (int, error)
|
||||||
// }
|
// }
|
||||||
p.updateURIsToAbs(ctx)
|
p.updateURIsToAbs(ctx)
|
||||||
code, _ := p.getStatus()
|
code, _ := p.getStatus()
|
||||||
ctx.StatusCode(code)
|
if code == 0 { // get the current status code and set it to the problem.
|
||||||
|
code = ctx.GetStatusCode()
|
||||||
|
ctx.StatusCode(code)
|
||||||
|
} else {
|
||||||
|
// send the problem's status code
|
||||||
|
ctx.StatusCode(code)
|
||||||
|
}
|
||||||
|
|
||||||
if options.RenderXML {
|
if options.RenderXML {
|
||||||
ctx.contentTypeOnce(ContentXMLProblemHeaderValue, "")
|
ctx.contentTypeOnce(ContentXMLProblemHeaderValue, "")
|
||||||
|
|
|
@ -78,7 +78,7 @@ func (p Problem) updateURIsToAbs(ctx Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if uriRef := p.getURI("type"); uriRef != "" {
|
if uriRef := p.getURI("type"); uriRef != "" && !strings.HasPrefix(uriRef, "http") {
|
||||||
p.Type(ctx.AbsoluteURI(uriRef))
|
p.Type(ctx.AbsoluteURI(uriRef))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ func (p Problem) Key(key string, value interface{}) Problem {
|
||||||
//
|
//
|
||||||
// Empty URI or "about:blank", when used as a problem type,
|
// 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.
|
// indicates that the problem has no additional semantics beyond that of the HTTP status code.
|
||||||
// When "about:blank" is used,
|
// When "about:blank" is used and "title" was not set-ed,
|
||||||
// the title is being automatically set the same as the recommended HTTP status phrase for that code
|
// 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.
|
// (e.g., "Not Found" for 404, and so on) on `Status` call.
|
||||||
//
|
//
|
||||||
|
@ -151,15 +151,16 @@ func (p Problem) Title(title string) Problem {
|
||||||
func (p Problem) Status(statusCode int) Problem {
|
func (p Problem) Status(statusCode int) Problem {
|
||||||
shouldOverrideTitle := !p.keyExists("title")
|
shouldOverrideTitle := !p.keyExists("title")
|
||||||
|
|
||||||
if !shouldOverrideTitle {
|
// if !shouldOverrideTitle {
|
||||||
typ, found := p["type"]
|
// typ, found := p["type"]
|
||||||
shouldOverrideTitle = !found || isEmptyTypeURI(typ.(string))
|
// shouldOverrideTitle = !found || isEmptyTypeURI(typ.(string))
|
||||||
}
|
// }
|
||||||
|
|
||||||
if shouldOverrideTitle {
|
if shouldOverrideTitle {
|
||||||
// Set title by code.
|
// Set title by code.
|
||||||
p.Title(http.StatusText(statusCode))
|
p.Title(http.StatusText(statusCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.Key("status", statusCode)
|
return p.Key("status", statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user