/
validation.go
112 lines (96 loc) · 3.5 KB
/
validation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Copyright © 2018 by PACE Telematics GmbH. All rights reserved.
// Created at 2018/08/24 by Vincent Landgraf
package runtime
import (
"fmt"
"net/http"
"strings"
"time"
valid "github.com/asaskevich/govalidator"
"github.com/pace/bricks/pkg/isotime"
)
func init() {
valid.CustomTypeTagMap.Set("iso8601", valid.CustomTypeValidator(func(i interface{}, o interface{}) bool {
switch v := i.(type) {
case time.Time:
return true
case string:
_, err := isotime.ParseISO8601(v)
return err == nil
}
return false
}))
}
// ValidateParameters checks the given struct and returns true if the struct
// is valid according to the specification (declared with go-validator struct tags)
// In case of an error, an jsonapi error message will be directly send to the client
func ValidateParameters(w http.ResponseWriter, r *http.Request, data interface{}) bool {
return ValidateStruct(w, r, data, "parameter")
}
// ValidateRequest checks the given struct and returns true if the struct
// is valid according to the specification (declared with go-validator struct tags)
// In case of an error, an jsonapi error message will be directly send to the client
func ValidateRequest(w http.ResponseWriter, r *http.Request, data interface{}) bool {
return ValidateStruct(w, r, data, "pointer")
}
// ValidateStruct checks the given struct and returns true if the struct
// is valid according to the specification (declared with go-validator struct tags)
// In case of an error, an jsonapi error message will be directly send to the client
// The passed source is the source for validation errors (e.g. pointer for data or parameter)
func ValidateStruct(w http.ResponseWriter, r *http.Request, data interface{}, source string) bool {
ok, err := valid.ValidateStruct(data)
if !ok {
switch errs := err.(type) {
case valid.Errors:
var e Errors
generateValidationErrors(errs, &e, source)
WriteError(w, http.StatusUnprocessableEntity, e)
case error:
panic(err) // programming error, e.g. not used with struct
default:
panic(fmt.Errorf("unhandled error case: %s", err))
}
return false
}
return true
}
// convert govalidator errors into jsonapi errors
func generateValidationErrors(validErrors valid.Errors, jsonapiErrors *Errors, source string) {
for _, err := range validErrors {
switch e := err.(type) {
case valid.Errors:
generateValidationErrors(e, jsonapiErrors, source)
case valid.Error:
*jsonapiErrors = append(*jsonapiErrors, generateValidationError(e, source))
default:
panic(fmt.Errorf("unhandled error case: %s", e))
}
}
}
// BUG(vil): the govalidation error has no reference to the
// original StructField. That makes it impossible to generate
// correct pointers.
// Since the actual data structure and the incoming JSON are very
// different, fork and add struct field tags. Add custom tag
// and use a custom tag to produce correct source pointer/parameter.
// https://github.com/pace/bricks/issues/10
// generateValidationError generates a new jsonapi error based
// on the given govalidator error
func generateValidationError(e valid.Error, source string) *Error {
path := ""
for _, p := range append(e.Path, e.Name) {
path += "/" + strings.ToLower(p)
}
// params are prefixed with param remove this until above
// described bug is fixed with this simple string replace
if source == "parameter" {
path = strings.Replace(path, "/param", "", 1)
}
return &Error{
Title: fmt.Sprintf("%s is invalid", e.Name),
Detail: e.Err.Error(),
Source: &map[string]interface{}{
source: path,
},
}
}