-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathobject.go
More file actions
114 lines (98 loc) · 2.34 KB
/
object.go
File metadata and controls
114 lines (98 loc) · 2.34 KB
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
113
114
package check
import (
"fmt"
"sort"
)
// ObjectSchema validates map[string]any objects with named fields.
type ObjectSchema struct {
optional bool
fields map[string]Schema
strict bool // if true, disallow extra keys
}
func NewObject(fields map[string]Schema) *ObjectSchema {
return &ObjectSchema{fields: fields}
}
// clone returns a copy of this schema to preserve immutability in chains.
func (s *ObjectSchema) clone() *ObjectSchema {
copy := *s
if s.fields != nil {
copyFields := make(map[string]Schema)
for k, v := range s.fields {
copyFields[k] = v
}
copy.fields = copyFields
}
return ©
}
func (s *ObjectSchema) Optional() *ObjectSchema {
s = s.clone()
s.optional = true
return s
}
// Strict rejects objects with keys not defined in the schema.
func (s *ObjectSchema) Strict() *ObjectSchema {
s = s.clone()
s.strict = true
return s
}
func (s *ObjectSchema) IsOptional() bool { return s.optional }
func (s *ObjectSchema) Parse(value any) (any, error) {
errs := s.Validate(value)
if len(errs) > 0 {
return nil, ValidationErrors(errs)
}
if value == nil {
return nil, nil
}
// Safe: Validate already confirmed it's a map[string]any
obj := value.(map[string]any)
out := make(map[string]any, len(s.fields))
for key, schema := range s.fields {
val := obj[key]
parsed, _ := schema.Parse(val)
if parsed != nil || val != nil {
out[key] = parsed
}
}
return out, nil
}
func (s *ObjectSchema) Validate(value any) []ValidationError {
if value == nil {
if s.optional {
return nil
}
return []ValidationError{{Message: "required value is missing"}}
}
obj, ok := value.(map[string]any)
if !ok {
return []ValidationError{{Message: fmt.Sprintf("expected object, got %T", value), Value: value}}
}
var errs []ValidationError
// Validate each defined field
for key, schema := range s.fields {
val, exists := obj[key]
if !exists {
val = nil
}
fieldErrs := schema.Validate(val)
errs = append(errs, prefixErrors(key, fieldErrs)...)
}
// Strict mode: reject extra keys
if s.strict {
extras := []string{}
for key := range obj {
if _, defined := s.fields[key]; !defined {
extras = append(extras, key)
}
}
sort.Strings(extras)
for _, key := range extras {
errs = append(errs, ValidationError{
Path: key,
Message: "unknown field",
Value: obj[key],
})
}
}
return errs
}