forked from DataDog/jsonapi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
member_names.go
99 lines (87 loc) · 2.84 KB
/
member_names.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
package jsonapi
import (
"encoding/json"
"fmt"
"regexp"
)
var (
defaultNameRegex *regexp.Regexp
strictNameRegex *regexp.Regexp
)
func init() {
// properties of the default name regex:
// - at least one character, no case requirements
// - dash, space, and underscore allowed anywhere except the ends of the string
// - no characters below unicode 0080 allowed
defaultNameRegex = regexp.MustCompile(`^([a-zA-Z\d]|[^\x{0000}-\x{0080}])(([a-zA-Z\d]|[^\x{0000}-\x{0080}])|[-_ ]([a-zA-Z\d]|[^\x{0000}-\x{0080}]))*$`)
// properties of the strict name regex:
// - at least one lower case letter
// - camel case, and must end with a lower case letter
// - may have digits inside the word
strictNameRegex = regexp.MustCompile(`^[a-z]+(|(\d|([A-Z\d][a-z\d]+))*([A-Z\d][a-z\d]*[a-z]))$`)
}
// MemberNameValidationMode controls how document member names are checked for correctness.
type MemberNameValidationMode int
const (
// DefaultValidation verifies that member names are valid according to the spec in
// https://jsonapi.org/format/#document-member-names.
//
// Note that this validation mode allows for non-URL-safe member names.
DefaultValidation MemberNameValidationMode = iota
// DisableValidation turns off member name validation for convenience or performance-saving
// reasons.
//
// Note that this validation mode allows member names that do NOT conform to the JSON:API spec.
DisableValidation
// StrictValidation verifies that member names are valid according to the spec in
// https://jsonapi.org/format/#document-member-names, and follow recommendations from
// https://jsonapi.org/recommendations/#naming.
//
// Note that these names are always URL-safe.
StrictValidation
)
func isValidMemberName(name string, mode MemberNameValidationMode) bool {
switch mode {
case DisableValidation:
return true
case StrictValidation:
return strictNameRegex.MatchString(name)
default:
return defaultNameRegex.MatchString(name)
}
}
func validateMapMemberNames(m map[string]any, mode MemberNameValidationMode) error {
for member, val := range m {
if !isValidMemberName(member, mode) {
return &MemberNameValidationError{member}
}
switch nested := val.(type) {
case map[string]any:
if err := validateMapMemberNames(nested, mode); err != nil {
return err
}
case []any:
for _, entry := range nested {
if subMap, ok := entry.(map[string]any); ok {
if err := validateMapMemberNames(subMap, mode); err != nil {
return err
}
}
}
default:
continue
}
}
return nil
}
func validateJSONMemberNames(b []byte, mode MemberNameValidationMode) error {
// do not unmarshal if validation is disabled
if mode == DisableValidation {
return nil
}
var m map[string]any
if err := json.Unmarshal(b, &m); err != nil {
return fmt.Errorf("unexpected unmarshal failure: %w", err)
}
return validateMapMemberNames(m, mode)
}