mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 02:31:04 +01:00
Add new x/errors/validation package to make your life even more easier (using Generics)
This commit is contained in:
parent
8f2deb6873
commit
104bea0a58
|
@ -23,6 +23,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
|
||||||
|
|
||||||
Changes apply to `main` branch.
|
Changes apply to `main` branch.
|
||||||
|
|
||||||
|
- Simplify how validation errors on `/x/errors` package works. A new `x/errors/validation` sub-package added to make your life easier (using the powerful Generics feature).
|
||||||
- Add `x/errors.OK`, `Create`, `NoContent` and `NoContentOrNotModified` package-level generic functions as custom service method caller helpers. Example can be found [here](_examples/routing/http-wire-errors/service/main.go).
|
- Add `x/errors.OK`, `Create`, `NoContent` and `NoContentOrNotModified` package-level generic functions as custom service method caller helpers. Example can be found [here](_examples/routing/http-wire-errors/service/main.go).
|
||||||
- Add `x/errors.ReadPayload`, `ReadQuery`, `ReadPaginationOptions`, `Handle`, `HandleCreate`, `HandleCreateResponse`, `HandleUpdate` and `HandleDelete` package-level functions as helpers for common actions.
|
- Add `x/errors.ReadPayload`, `ReadQuery`, `ReadPaginationOptions`, `Handle`, `HandleCreate`, `HandleCreateResponse`, `HandleUpdate` and `HandleDelete` package-level functions as helpers for common actions.
|
||||||
- Add `x/jsonx.GetSimpleDateRange(date, jsonx.WeekRange, time.Monday, time.Sunday)` which returns all dates between the given range and start/end weekday values for WeekRange.
|
- Add `x/jsonx.GetSimpleDateRange(date, jsonx.WeekRange, time.Monday, time.Sunday)` which returns all dates between the given range and start/end weekday values for WeekRange.
|
||||||
|
|
|
@ -54,8 +54,7 @@
|
||||||
* [Basic](routing/basic/main.go)
|
* [Basic](routing/basic/main.go)
|
||||||
* [Custom HTTP Errors](routing/http-errors/main.go)
|
* [Custom HTTP Errors](routing/http-errors/main.go)
|
||||||
* [HTTP Wire Errors](routing/http-wire-errors/main.go) **NEW**
|
* [HTTP Wire Errors](routing/http-wire-errors/main.go) **NEW**
|
||||||
* [Custom Validation Errors](routing/http-wire-errors/custom-validation-errors/main.go)
|
* [Service and Validation](routing/http-wire-errors/service/main.go) **NEW**
|
||||||
* [Service](routing/http-wire-errors/service/main.go) **NEW**
|
|
||||||
* [Not Found - Intelligence](routing/intelligence/main.go)
|
* [Not Found - Intelligence](routing/intelligence/main.go)
|
||||||
* [Not Found - Suggest Closest Paths](routing/intelligence/manual/main.go)
|
* [Not Found - Suggest Closest Paths](routing/intelligence/manual/main.go)
|
||||||
* [Dynamic Path](routing/dynamic-path/main.go)
|
* [Dynamic Path](routing/dynamic-path/main.go)
|
||||||
|
|
|
@ -51,7 +51,6 @@
|
||||||
- [Basic](routing/basic/main.go)
|
- [Basic](routing/basic/main.go)
|
||||||
- [Custom HTTP Errors](routing/http-errors/main.go)
|
- [Custom HTTP Errors](routing/http-errors/main.go)
|
||||||
- [HTTP Wire Errors](routing/http-wire-errors/main.go) **新範例**
|
- [HTTP Wire Errors](routing/http-wire-errors/main.go) **新範例**
|
||||||
- [Custom Validation Errors](routing/http-wire-errors/custom-validation-errors/main.go)
|
|
||||||
- [Not Found - Intelligence](routing/intelligence/main.go)
|
- [Not Found - Intelligence](routing/intelligence/main.go)
|
||||||
- [Not Found - Suggest Closest Paths](routing/intelligence/manual/main.go)
|
- [Not Found - Suggest Closest Paths](routing/intelligence/manual/main.go)
|
||||||
- [Dynamic Path](routing/dynamic-path/main.go)
|
- [Dynamic Path](routing/dynamic-path/main.go)
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/v12"
|
|
||||||
"github.com/kataras/iris/v12/x/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
app := newApp()
|
|
||||||
app.Listen(":8080")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newApp() *iris.Application {
|
|
||||||
app := iris.New()
|
|
||||||
app.Get("/", fireCustomValidationError)
|
|
||||||
app.Get("/multi", fireCustomValidationErrors)
|
|
||||||
app.Get("/invalid", fireInvalidError)
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
type MyValidationError struct {
|
|
||||||
Field string `json:"field"`
|
|
||||||
Value interface{} `json:"value"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
Timestamp int64 `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err MyValidationError) Error() string {
|
|
||||||
return fmt.Sprintf("field %q got invalid value of %v: reason: %s", err.Field, err.Value, err.Reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error, GetField, GetValue and GetReason completes
|
|
||||||
// the x/errors.ValidationError interface which can be used
|
|
||||||
// for faster rendering without the necessity of registering a custom
|
|
||||||
// type (see at the end of the example).
|
|
||||||
//
|
|
||||||
// func (err MyValidationError) GetField() string {
|
|
||||||
// return err.Field
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (err MyValidationError) GetValue() interface{} {
|
|
||||||
// return err.Value
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (err MyValidationError) GetReason() string {
|
|
||||||
// return err.Reason
|
|
||||||
// }
|
|
||||||
|
|
||||||
const shouldFail = true
|
|
||||||
|
|
||||||
func fireCustomValidationError(ctx iris.Context) {
|
|
||||||
if shouldFail {
|
|
||||||
err := MyValidationError{
|
|
||||||
Field: "username",
|
|
||||||
Value: "",
|
|
||||||
Reason: "empty string",
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// The "validation" field, when used, is always rendering as
|
|
||||||
// a JSON array, NOT a single object.
|
|
||||||
errors.InvalidArgument.Err(ctx, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.WriteString("OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally register custom types that you may need
|
|
||||||
// to be rendered as validation errors if the given "ErrorCodeName.Err.err"
|
|
||||||
// input parameter is matched with one of these. Register once, at initialiation.
|
|
||||||
func init() {
|
|
||||||
mapper := errors.NewValidationErrorTypeMapper(MyValidationError{} /*, OtherCustomType{} */)
|
|
||||||
errors.RegisterValidationErrorMapper(mapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A custom type of the example validation error type
|
|
||||||
// in order to complete the error interface, so it can be
|
|
||||||
// pass through the errors.InvalidArgument.Err method.
|
|
||||||
type MyValidationErrors []MyValidationError
|
|
||||||
|
|
||||||
func (m MyValidationErrors) Error() string {
|
|
||||||
return "to be an error"
|
|
||||||
}
|
|
||||||
|
|
||||||
func fireCustomValidationErrors(ctx iris.Context) {
|
|
||||||
if shouldFail {
|
|
||||||
errs := MyValidationErrors{
|
|
||||||
{
|
|
||||||
Field: "username",
|
|
||||||
Value: "",
|
|
||||||
Reason: "empty string",
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Field: "birth_date",
|
|
||||||
Value: "2022-01-01",
|
|
||||||
Reason: "too young",
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
errors.InvalidArgument.Err(ctx, errs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.WriteString("OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
func fireInvalidError(ctx iris.Context) {
|
|
||||||
if shouldFail {
|
|
||||||
errors.InvalidArgument.Err(ctx, fmt.Errorf("just a custom error text"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.WriteString("OK")
|
|
||||||
}
|
|
|
@ -6,12 +6,13 @@ import (
|
||||||
|
|
||||||
"github.com/kataras/iris/v12"
|
"github.com/kataras/iris/v12"
|
||||||
"github.com/kataras/iris/v12/x/errors"
|
"github.com/kataras/iris/v12/x/errors"
|
||||||
|
"github.com/kataras/iris/v12/x/errors/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
service := new(myService)
|
service := new(myService)
|
||||||
|
|
||||||
app.Post("/", createHandler(service))
|
app.Post("/", createHandler(service))
|
||||||
app.Get("/", listHandler(service))
|
app.Get("/", listHandler(service))
|
||||||
app.Delete("/{id:string}", deleteHandler(service))
|
app.Delete("/{id:string}", deleteHandler(service))
|
||||||
|
@ -70,16 +71,65 @@ type (
|
||||||
myService struct{}
|
myService struct{}
|
||||||
|
|
||||||
CreateRequest struct {
|
CreateRequest struct {
|
||||||
Fullname string
|
Fullname string `json:"fullname"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbies []string `json:"hobbies"`
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateResponse struct {
|
CreateResponse struct {
|
||||||
ID string
|
ID string `json:"id"`
|
||||||
Firstname string
|
Firstname string `json:"firstname"`
|
||||||
Lastname string
|
Lastname string `json:"lastname"`
|
||||||
|
Age int `json:"age"`
|
||||||
|
Hobbies []string `json:"hobbies"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ValidateContext implements the errors.ContextValidator interface.
|
||||||
|
// It validates the request body and returns an error if the request body is invalid.
|
||||||
|
// You can also alter the "r" CreateRequest before calling the service method,
|
||||||
|
// e.g. give a default value to a field if it's empty or set an ID based on a path parameter.
|
||||||
|
func (r *CreateRequest) ValidateContext(ctx iris.Context) error {
|
||||||
|
// To pass custom validation functions:
|
||||||
|
// return validation.Join(
|
||||||
|
// validation.String("fullname", r.Fullname).Func(customStringFuncHere),
|
||||||
|
// OR
|
||||||
|
// validation.Field("any_field", r.AnyFieldValue).Func(customAnyFuncHere))
|
||||||
|
return validation.Join(
|
||||||
|
validation.String("fullname", r.Fullname).NotEmpty().Fullname().Length(3, 50),
|
||||||
|
validation.Number("age", r.Age).InRange(18, 130),
|
||||||
|
validation.Slice("hobbies", r.Hobbies).Length(1, 10),
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Example Output:
|
||||||
|
{
|
||||||
|
"http_error_code": {
|
||||||
|
"canonical_name": "INVALID_ARGUMENT",
|
||||||
|
"status": 400
|
||||||
|
},
|
||||||
|
"message": "validation failure",
|
||||||
|
"details": "fields were invalid",
|
||||||
|
"validation": [
|
||||||
|
{
|
||||||
|
"field": "fullname",
|
||||||
|
"value": "",
|
||||||
|
"reason": "must not be empty, must contain first and last name, must be between 3 and 50 characters"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "age",
|
||||||
|
"value": 0,
|
||||||
|
"reason": "must be in range of [18, 130]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field": "hobbies",
|
||||||
|
"value": null,
|
||||||
|
"reason": "must be between 1 and 10 elements"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
func (s *myService) Create(ctx context.Context, in CreateRequest) (CreateResponse, error) {
|
func (s *myService) Create(ctx context.Context, in CreateRequest) (CreateResponse, error) {
|
||||||
arr := strings.Split(in.Fullname, " ")
|
arr := strings.Split(in.Fullname, " ")
|
||||||
firstname, lastname := arr[0], arr[1]
|
firstname, lastname := arr[0], arr[1]
|
||||||
|
@ -89,6 +139,8 @@ func (s *myService) Create(ctx context.Context, in CreateRequest) (CreateRespons
|
||||||
ID: id,
|
ID: id,
|
||||||
Firstname: firstname,
|
Firstname: firstname,
|
||||||
Lastname: lastname,
|
Lastname: lastname,
|
||||||
|
Age: in.Age,
|
||||||
|
Hobbies: in.Hobbies,
|
||||||
}
|
}
|
||||||
return resp, nil // , errors.New("create: test error")
|
return resp, nil // , errors.New("create: test error")
|
||||||
}
|
}
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -32,16 +32,17 @@ require (
|
||||||
github.com/mailgun/raymond/v2 v2.0.48
|
github.com/mailgun/raymond/v2 v2.0.48
|
||||||
github.com/mailru/easyjson v0.7.7
|
github.com/mailru/easyjson v0.7.7
|
||||||
github.com/microcosm-cc/bluemonday v1.0.26
|
github.com/microcosm-cc/bluemonday v1.0.26
|
||||||
github.com/redis/go-redis/v9 v9.3.1
|
github.com/redis/go-redis/v9 v9.4.0
|
||||||
github.com/schollz/closestmatch v2.1.0+incompatible
|
github.com/schollz/closestmatch v2.1.0+incompatible
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12
|
github.com/shirou/gopsutil/v3 v3.23.12
|
||||||
github.com/tdewolff/minify/v2 v2.20.10
|
github.com/tdewolff/minify/v2 v2.20.12
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||||
github.com/yosssi/ace v0.0.5
|
github.com/yosssi/ace v0.0.5
|
||||||
go.etcd.io/bbolt v1.3.8
|
go.etcd.io/bbolt v1.3.8
|
||||||
golang.org/x/crypto v0.17.0
|
golang.org/x/crypto v0.17.0
|
||||||
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc
|
||||||
golang.org/x/net v0.19.0
|
golang.org/x/net v0.19.0
|
||||||
golang.org/x/sys v0.15.0
|
golang.org/x/sys v0.16.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
google.golang.org/protobuf v1.32.0
|
google.golang.org/protobuf v1.32.0
|
||||||
|
@ -107,7 +108,6 @@ require (
|
||||||
github.com/yudai/gojsondiff v1.0.0 // indirect
|
github.com/yudai/gojsondiff v1.0.0 // indirect
|
||||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
moul.io/http2curl/v2 v2.3.0 // indirect
|
moul.io/http2curl/v2 v2.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
18
go.sum
generated
18
go.sum
generated
|
@ -185,8 +185,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
|
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
|
||||||
github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
@ -226,12 +226,13 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||||
github.com/tdewolff/minify/v2 v2.20.10 h1:iz9IkdRqD2pyneib/AvTas23RRG5TnuUFNcNVKmL/jU=
|
github.com/tdewolff/minify/v2 v2.20.12 h1:ie5+91QGUUeEDbLkexhx2tlI9BQgwwnfY+/Qdj4BlQ4=
|
||||||
github.com/tdewolff/minify/v2 v2.20.10/go.mod h1:xSJ9fXIfyuEMex88JT4jl8GvXnl/RzWNdqD96AqKlX0=
|
github.com/tdewolff/minify/v2 v2.20.12/go.mod h1:8ktdncc9Rh41MkTX2KYaicHT9+VnpvIDjCyIVsr/nN8=
|
||||||
github.com/tdewolff/parse/v2 v2.7.7 h1:V+50eFDH7Piw4IBwH8D8FtYeYbZp3T4SCtIvmBSIMyc=
|
github.com/tdewolff/parse/v2 v2.7.7 h1:V+50eFDH7Piw4IBwH8D8FtYeYbZp3T4SCtIvmBSIMyc=
|
||||||
github.com/tdewolff/parse/v2 v2.7.7/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
github.com/tdewolff/parse/v2 v2.7.7/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52 h1:gAQliwn+zJrkjAHVcBEYW/RFvd2St4yYimisvozAYlA=
|
|
||||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
|
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||||
|
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
@ -272,8 +273,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
|
@ -306,8 +307,9 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
|
|
@ -174,8 +174,21 @@ func HandleError(ctx *context.Context, err error) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if vErrs, ok := AsValidationErrors(err); ok {
|
if vErr, ok := err.(ValidationError); ok {
|
||||||
InvalidArgument.Data(ctx, "validation failure", vErrs)
|
if vErr == nil {
|
||||||
|
return false // consider as not error for any case, this should never happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidArgument.Validation(ctx, vErr)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if vErrs, ok := err.(ValidationErrors); ok {
|
||||||
|
if len(vErrs) == 0 {
|
||||||
|
return false // consider as not error for any case, this should never happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidArgument.Validation(ctx, vErrs...)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,9 +296,20 @@ func (e ErrorCodeName) Err(ctx *context.Context, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if validationErrors, ok := AsValidationErrors(err); ok {
|
if vErr, ok := err.(ValidationError); ok {
|
||||||
e.validation(ctx, validationErrors)
|
if vErr == nil {
|
||||||
return
|
return // consider as not error for any case, this should never happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Validation(ctx, vErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vErrs, ok := err.(ValidationErrors); ok {
|
||||||
|
if len(vErrs) == 0 {
|
||||||
|
return // consider as not error for any case, this should never happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Validation(ctx, vErrs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's already an Error type then send it directly.
|
// If it's already an Error type then send it directly.
|
||||||
|
|
|
@ -115,6 +115,12 @@ type ResponseOnlyErrorFunc[T any] interface {
|
||||||
func(stdContext.Context, T) error
|
func(stdContext.Context, T) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContextValidator is an interface which can be implemented by a request payload struct
|
||||||
|
// in order to validate the context before calling a service function.
|
||||||
|
type ContextValidator interface {
|
||||||
|
ValidateContext(*context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) (R, bool) {
|
func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fnInput ...T) (R, bool) {
|
||||||
var req T
|
var req T
|
||||||
switch len(fnInput) {
|
switch len(fnInput) {
|
||||||
|
@ -131,6 +137,16 @@ func bindResponse[T, R any, F ResponseFunc[T, R]](ctx *context.Context, fn F, fn
|
||||||
panic("invalid number of arguments")
|
panic("invalid number of arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if contextValidator, ok := any(&req).(ContextValidator); ok {
|
||||||
|
err := contextValidator.ValidateContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if HandleError(ctx, err) {
|
||||||
|
var resp R
|
||||||
|
return resp, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := fn(ctx, req)
|
resp, err := fn(ctx, req)
|
||||||
return resp, !HandleError(ctx, err)
|
return resp, !HandleError(ctx, err)
|
||||||
}
|
}
|
||||||
|
@ -208,14 +224,7 @@ func ReadPayload[T any](ctx *context.Context) (T, bool) {
|
||||||
return payload, false
|
return payload, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !handleJSONError(ctx, err) {
|
HandleError(ctx, err)
|
||||||
if vErrs, ok := AsValidationErrors(err); ok {
|
|
||||||
InvalidArgument.Data(ctx, "validation failure", vErrs)
|
|
||||||
} else {
|
|
||||||
InvalidArgument.Details(ctx, "unable to parse body", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload, false
|
return payload, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,11 +238,7 @@ func ReadQuery[T any](ctx *context.Context) (T, bool) {
|
||||||
var payload T
|
var payload T
|
||||||
err := ctx.ReadQuery(&payload)
|
err := ctx.ReadQuery(&payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if vErrs, ok := AsValidationErrors(err); ok {
|
HandleError(ctx, err)
|
||||||
InvalidArgument.Data(ctx, "validation failure", vErrs)
|
|
||||||
} else {
|
|
||||||
InvalidArgument.Details(ctx, "unable to parse query", err.Error())
|
|
||||||
}
|
|
||||||
return payload, false
|
return payload, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
92
x/errors/validation/error.go
Normal file
92
x/errors/validation/error.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12/x/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FieldError describes a field validation error.
|
||||||
|
// It completes the errors.ValidationError interface.
|
||||||
|
type FieldError[T any] struct {
|
||||||
|
Field string `json:"field"`
|
||||||
|
Value T `json:"value"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field returns a new validation error.
|
||||||
|
//
|
||||||
|
// Use its Func method to add validations over this field.
|
||||||
|
func Field[T any](field string, value T) *FieldError[T] {
|
||||||
|
return &FieldError[T]{Field: field, Value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error completes the standard error interface.
|
||||||
|
func (e *FieldError[T]) Error() string {
|
||||||
|
return fmt.Sprintf("field %q got invalid value of %v: reason: %s", e.Field, e.Value, e.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetField returns the field name.
|
||||||
|
func (e *FieldError[T]) GetField() string {
|
||||||
|
return e.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue returns the value of the field.
|
||||||
|
func (e *FieldError[T]) GetValue() any {
|
||||||
|
return e.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReason returns the reason of the validation error.
|
||||||
|
func (e *FieldError[T]) GetReason() string {
|
||||||
|
return e.Reason
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero reports whether the error is nil or has an empty reason.
|
||||||
|
func (e *FieldError[T]) IsZero() bool {
|
||||||
|
return e == nil || e.Reason == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FieldError[T]) joinReason(reason string) {
|
||||||
|
if reason == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Reason == "" {
|
||||||
|
e.Reason = reason
|
||||||
|
} else {
|
||||||
|
e.Reason += ", " + reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Func accepts a variadic number of functions which accept the value of the field
|
||||||
|
// and return a string message if the value is invalid.
|
||||||
|
// It joins the reasons into one.
|
||||||
|
func (e *FieldError[T]) Func(fns ...func(value T) string) *FieldError[T] {
|
||||||
|
for _, fn := range fns {
|
||||||
|
e.joinReason(fn(e.Value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join joins the given validation errors into one.
|
||||||
|
func Join(errs ...errors.ValidationError) error { // note that here we return the standard error type instead of the errors.ValidationError in order to make the error nil instead of ValidationErrors(nil) on empty slice.
|
||||||
|
if len(errs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
joinedErrs := make(errors.ValidationErrors, 0, len(errs))
|
||||||
|
for _, err := range errs {
|
||||||
|
if err == nil || err.GetReason() == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
joinedErrs = append(joinedErrs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(joinedErrs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return joinedErrs
|
||||||
|
}
|
105
x/errors/validation/number.go
Normal file
105
x/errors/validation/number.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NumberValue is a type constraint that accepts any numeric type.
|
||||||
|
type NumberValue interface {
|
||||||
|
constraints.Integer | constraints.Float
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumberError describes a number field validation error.
|
||||||
|
type NumberError[T NumberValue] struct{ *FieldError[T] }
|
||||||
|
|
||||||
|
// Number returns a new number validation error.
|
||||||
|
func Number[T NumberValue](field string, value T) *NumberError[T] {
|
||||||
|
return &NumberError[T]{Field(field, value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positive adds an error if the value is not positive.
|
||||||
|
func (e *NumberError[T]) Positive() *NumberError[T] {
|
||||||
|
e.Func(Positive)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative adds an error if the value is not negative.
|
||||||
|
func (e *NumberError[T]) Negative() *NumberError[T] {
|
||||||
|
e.Func(Negative)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero reports whether the value is zero.
|
||||||
|
func (e *NumberError[T]) Zero() *NumberError[T] {
|
||||||
|
e.Func(Zero)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonZero adds an error if the value is zero.
|
||||||
|
func (e *NumberError[T]) NonZero() *NumberError[T] {
|
||||||
|
e.Func(NonZero)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRange adds an error if the value is not in the range.
|
||||||
|
func (e *NumberError[T]) InRange(min, max T) *NumberError[T] {
|
||||||
|
e.Func(InRange(min, max))
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positive accepts any numeric type and
|
||||||
|
// returns a message if the value is not positive.
|
||||||
|
func Positive[T NumberValue](n T) string {
|
||||||
|
if n <= 0 {
|
||||||
|
return "must be positive"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative accepts any numeric type and returns a message if the value is not negative.
|
||||||
|
func Negative[T NumberValue](n T) string {
|
||||||
|
if n >= 0 {
|
||||||
|
return "must be negative"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero accepts any numeric type and returns a message if the value is not zero.
|
||||||
|
func Zero[T NumberValue](n T) string {
|
||||||
|
if n != 0 {
|
||||||
|
return "must be zero"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonZero accepts any numeric type and returns a message if the value is not zero.
|
||||||
|
func NonZero[T NumberValue](n T) string {
|
||||||
|
if n == 0 {
|
||||||
|
return "must not be zero"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// InRange accepts any numeric type and returns a message if the value is not in the range.
|
||||||
|
func InRange[T NumberValue](min, max T) func(T) string {
|
||||||
|
return func(n T) string {
|
||||||
|
if n < min || n > max {
|
||||||
|
return "must be in range of " + FormatRange(min, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatRange returns a string representation of a range of values, such as "[1, 10]".
|
||||||
|
// It uses a type constraint NumberValue, which means that the parameters must be numeric types
|
||||||
|
// that support comparison and formatting operations.
|
||||||
|
func FormatRange[T NumberValue](min, max T) string {
|
||||||
|
return fmt.Sprintf("[%v, %v]", min, max)
|
||||||
|
}
|
57
x/errors/validation/slice.go
Normal file
57
x/errors/validation/slice.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type SliceValue[T any] interface {
|
||||||
|
~[]T
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceError describes a slice field validation error.
|
||||||
|
type SliceError[T any, V SliceValue[T]] struct{ *FieldError[V] }
|
||||||
|
|
||||||
|
// Slice returns a new slice validation error.
|
||||||
|
func Slice[T any, V SliceValue[T]](field string, value V) *SliceError[T, V] {
|
||||||
|
return &SliceError[T, V]{Field(field, value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEmpty adds an error if the slice is empty.
|
||||||
|
func (e *SliceError[T, V]) NotEmpty() *SliceError[T, V] {
|
||||||
|
e.Func(NotEmptySlice)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length adds an error if the slice length is not in the given range.
|
||||||
|
func (e *SliceError[T, V]) Length(min, max int) *SliceError[T, V] {
|
||||||
|
e.Func(SliceLength[T, V](min, max))
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEmptySlice accepts any slice and returns a message if the value is empty.
|
||||||
|
func NotEmptySlice[T any, V SliceValue[T]](s V) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return "must not be empty"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceLength accepts any slice and returns a message if the length is not in the given range.
|
||||||
|
func SliceLength[T any, V SliceValue[T]](min, max int) func(s V) string {
|
||||||
|
return func(s V) string {
|
||||||
|
n := len(s)
|
||||||
|
|
||||||
|
if min == max {
|
||||||
|
if n != min {
|
||||||
|
return fmt.Sprintf("must be %d elements", min)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < min || n > max {
|
||||||
|
return fmt.Sprintf("must be between %d and %d elements", min, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
69
x/errors/validation/string.go
Normal file
69
x/errors/validation/string.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StringError describes a string field validation error.
|
||||||
|
type StringError struct{ *FieldError[string] }
|
||||||
|
|
||||||
|
// String returns a new string validation error.
|
||||||
|
func String(field string, value string) *StringError {
|
||||||
|
return &StringError{Field(field, value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEmpty adds an error if the string is empty.
|
||||||
|
func (e *StringError) NotEmpty() *StringError {
|
||||||
|
e.Func(NotEmpty)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fullname adds an error if the string is not a full name.
|
||||||
|
func (e *StringError) Fullname() *StringError {
|
||||||
|
e.Func(Fullname)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length adds an error if the string length is not in the given range.
|
||||||
|
func (e *StringError) Length(min, max int) *StringError {
|
||||||
|
e.Func(StringLength(min, max))
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEmpty accepts any string and returns a message if the value is empty.
|
||||||
|
func NotEmpty(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return "must not be empty"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fullname accepts any string and returns a message if the value is not a full name.
|
||||||
|
func Fullname(s string) string {
|
||||||
|
if len(strings.Split(s, " ")) < 2 {
|
||||||
|
return "must contain first and last name"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringLength accepts any string and returns a message if the length is not in the given range.
|
||||||
|
func StringLength(min, max int) func(s string) string {
|
||||||
|
return func(s string) string {
|
||||||
|
n := len(s)
|
||||||
|
|
||||||
|
if min == max {
|
||||||
|
if n != min {
|
||||||
|
return fmt.Sprintf("must be %d characters", min)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < min || n > max {
|
||||||
|
return fmt.Sprintf("must be between %d and %d characters", min, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -11,10 +10,6 @@ import (
|
||||||
// it can by mapped to a validation error.
|
// it can by mapped to a validation error.
|
||||||
//
|
//
|
||||||
// A validation error(s) can be given by ErrorCodeName's Validation or Err methods.
|
// A validation error(s) can be given by ErrorCodeName's Validation or Err methods.
|
||||||
//
|
|
||||||
// Example can be found at:
|
|
||||||
//
|
|
||||||
// https://github.com/kataras/iris/tree/main/_examples/routing/http-wire-errors/custom-validation-errors
|
|
||||||
type ValidationError interface {
|
type ValidationError interface {
|
||||||
error
|
error
|
||||||
|
|
||||||
|
@ -43,123 +38,3 @@ func (errs ValidationErrors) Error() string {
|
||||||
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidationErrorMapper is the interface which
|
|
||||||
// custom validation error mappers should complete.
|
|
||||||
type ValidationErrorMapper interface {
|
|
||||||
// The implementation must check the given "err"
|
|
||||||
// and make decision if it's an error of validation
|
|
||||||
// and if so it should return the value (err or another one)
|
|
||||||
// and true as the last output argument.
|
|
||||||
//
|
|
||||||
// Outputs:
|
|
||||||
// 1. the validation error(s) value
|
|
||||||
// 2. true if the interface{} is an array, otherise false
|
|
||||||
// 3. true if it's a validation error or false if not.
|
|
||||||
MapValidationErrors(err error) (interface{}, bool, bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidationErrorMapperFunc is an "ValidationErrorMapper" but in type of a function.
|
|
||||||
type ValidationErrorMapperFunc func(err error) (interface{}, bool, bool)
|
|
||||||
|
|
||||||
// MapValidationErrors completes the "ValidationErrorMapper" interface.
|
|
||||||
func (v ValidationErrorMapperFunc) MapValidationErrors(err error) (interface{}, bool, bool) {
|
|
||||||
return v(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// read-only at serve time, holds the validation error mappers.
|
|
||||||
var validationErrorMappers []ValidationErrorMapper = []ValidationErrorMapper{
|
|
||||||
ValidationErrorMapperFunc(func(err error) (interface{}, bool, bool) {
|
|
||||||
switch e := err.(type) {
|
|
||||||
case ValidationError:
|
|
||||||
return e, false, true
|
|
||||||
case ValidationErrors:
|
|
||||||
return e, true, true
|
|
||||||
default:
|
|
||||||
return nil, false, false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterValidationErrorMapper registers a custom
|
|
||||||
// implementation of validation error mapping.
|
|
||||||
// Call it on program initilization, main() or init() functions.
|
|
||||||
func RegisterValidationErrorMapper(m ValidationErrorMapper) {
|
|
||||||
validationErrorMappers = append(validationErrorMappers, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterValidationErrorMapperFunc registers a custom
|
|
||||||
// function implementation of validation error mapping.
|
|
||||||
// Call it on program initilization, main() or init() functions.
|
|
||||||
func RegisterValidationErrorMapperFunc(fn func(err error) (interface{}, bool, bool)) {
|
|
||||||
validationErrorMappers = append(validationErrorMappers, ValidationErrorMapperFunc(fn))
|
|
||||||
}
|
|
||||||
|
|
||||||
type validationErrorTypeMapper struct {
|
|
||||||
types []reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ValidationErrorMapper = (*validationErrorTypeMapper)(nil)
|
|
||||||
|
|
||||||
func (v *validationErrorTypeMapper) MapValidationErrors(err error) (interface{}, bool, bool) {
|
|
||||||
errType := reflect.TypeOf(err)
|
|
||||||
for _, typ := range v.types {
|
|
||||||
if equalTypes(errType, typ) {
|
|
||||||
return err, false, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// a slice is given but the underline type is registered.
|
|
||||||
if errType.Kind() == reflect.Slice {
|
|
||||||
if equalTypes(errType.Elem(), typ) {
|
|
||||||
return err, true, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func equalTypes(err reflect.Type, binding reflect.Type) bool {
|
|
||||||
return err == binding
|
|
||||||
// return binding.AssignableTo(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewValidationErrorTypeMapper returns a validation error mapper
|
|
||||||
// which compares the error with one or more of the given "types",
|
|
||||||
// through reflection. Each of the given types MUST complete the
|
|
||||||
// standard error type, so it can be passed through the error code.
|
|
||||||
func NewValidationErrorTypeMapper(types ...error) ValidationErrorMapper {
|
|
||||||
typs := make([]reflect.Type, 0, len(types))
|
|
||||||
for _, typ := range types {
|
|
||||||
v, ok := typ.(reflect.Type)
|
|
||||||
if !ok {
|
|
||||||
v = reflect.TypeOf(typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
typs = append(typs, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &validationErrorTypeMapper{
|
|
||||||
types: typs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsValidationErrors reports wheether the given "err" is a type of validation error(s).
|
|
||||||
// Its behavior can be modified before serve-time
|
|
||||||
// through the "RegisterValidationErrorMapper" function.
|
|
||||||
func AsValidationErrors(err error) (interface{}, bool) {
|
|
||||||
if err == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range validationErrorMappers {
|
|
||||||
if errs, isArray, ok := m.MapValidationErrors(err); ok {
|
|
||||||
if !isArray { // ensure always-array on Validation field of the http error.
|
|
||||||
return []interface{}{errs}, true
|
|
||||||
}
|
|
||||||
return errs, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user