1
16
17 package typed
18
19 import (
20 "errors"
21 "fmt"
22 "strings"
23
24 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
25 "sigs.k8s.io/structured-merge-diff/v4/schema"
26 "sigs.k8s.io/structured-merge-diff/v4/value"
27 )
28
29
30 type ValidationError struct {
31 Path string
32 ErrorMessage string
33 }
34
35
36 func (ve ValidationError) Error() string {
37 if len(ve.Path) == 0 {
38 return ve.ErrorMessage
39 }
40 return fmt.Sprintf("%s: %v", ve.Path, ve.ErrorMessage)
41 }
42
43
44 type ValidationErrors []ValidationError
45
46
47
48 func (errs ValidationErrors) Error() string {
49 if len(errs) == 1 {
50 return errs[0].Error()
51 }
52 messages := []string{"errors:"}
53 for _, e := range errs {
54 messages = append(messages, " "+e.Error())
55 }
56 return strings.Join(messages, "\n")
57 }
58
59
60 func (errs ValidationErrors) WithPath(p string) ValidationErrors {
61 for i := range errs {
62 errs[i].Path = p
63 }
64 return errs
65 }
66
67
68
69 func (errs ValidationErrors) WithPrefix(prefix string) ValidationErrors {
70 for i := range errs {
71 errs[i].Path = prefix + errs[i].Path
72 }
73 return errs
74 }
75
76
77
78
79 func (errs ValidationErrors) WithLazyPrefix(fn func() string) ValidationErrors {
80 if len(errs) == 0 {
81 return errs
82 }
83 prefix := ""
84 if fn != nil {
85 prefix = fn()
86 }
87 for i := range errs {
88 errs[i].Path = prefix + errs[i].Path
89 }
90 return errs
91 }
92
93 func errorf(format string, args ...interface{}) ValidationErrors {
94 return ValidationErrors{{
95 ErrorMessage: fmt.Sprintf(format, args...),
96 }}
97 }
98
99 type atomHandler interface {
100 doScalar(*schema.Scalar) ValidationErrors
101 doList(*schema.List) ValidationErrors
102 doMap(*schema.Map) ValidationErrors
103 }
104
105 func resolveSchema(s *schema.Schema, tr schema.TypeRef, v value.Value, ah atomHandler) ValidationErrors {
106 a, ok := s.Resolve(tr)
107 if !ok {
108 typeName := "inlined type"
109 if tr.NamedType != nil {
110 typeName = *tr.NamedType
111 }
112 return errorf("schema error: no type found matching: %v", typeName)
113 }
114
115 a = deduceAtom(a, v)
116 return handleAtom(a, tr, ah)
117 }
118
119
120
121
122
123 func deduceAtom(atom schema.Atom, val value.Value) schema.Atom {
124 switch {
125 case val == nil:
126 case val.IsFloat(), val.IsInt(), val.IsString(), val.IsBool():
127 if atom.Scalar != nil {
128 return schema.Atom{Scalar: atom.Scalar}
129 }
130 case val.IsList():
131 if atom.List != nil {
132 return schema.Atom{List: atom.List}
133 }
134 case val.IsMap():
135 if atom.Map != nil {
136 return schema.Atom{Map: atom.Map}
137 }
138 }
139 return atom
140 }
141
142 func handleAtom(a schema.Atom, tr schema.TypeRef, ah atomHandler) ValidationErrors {
143 switch {
144 case a.Map != nil:
145 return ah.doMap(a.Map)
146 case a.Scalar != nil:
147 return ah.doScalar(a.Scalar)
148 case a.List != nil:
149 return ah.doList(a.List)
150 }
151
152 name := "inlined"
153 if tr.NamedType != nil {
154 name = "named type: " + *tr.NamedType
155 }
156
157 return errorf("schema error: invalid atom: %v", name)
158 }
159
160
161 func listValue(a value.Allocator, val value.Value) (value.List, error) {
162 if val.IsNull() {
163
164 return nil, nil
165 }
166 if !val.IsList() {
167 return nil, fmt.Errorf("expected list, got %v", val)
168 }
169 return val.AsListUsing(a), nil
170 }
171
172
173 func mapValue(a value.Allocator, val value.Value) (value.Map, error) {
174 if val == nil {
175 return nil, fmt.Errorf("expected map, got nil")
176 }
177 if val.IsNull() {
178
179 return nil, nil
180 }
181 if !val.IsMap() {
182 return nil, fmt.Errorf("expected map, got %v", val)
183 }
184 return val.AsMapUsing(a), nil
185 }
186
187 func getAssociativeKeyDefault(s *schema.Schema, list *schema.List, fieldName string) (interface{}, error) {
188 atom, ok := s.Resolve(list.ElementType)
189 if !ok {
190 return nil, errors.New("invalid elementType for list")
191 }
192 if atom.Map == nil {
193 return nil, errors.New("associative list may not have non-map types")
194 }
195
196 field, _ := atom.Map.FindField(fieldName)
197 return field.Default, nil
198 }
199
200 func keyedAssociativeListItemToPathElement(a value.Allocator, s *schema.Schema, list *schema.List, child value.Value) (fieldpath.PathElement, error) {
201 pe := fieldpath.PathElement{}
202 if child.IsNull() {
203
204 return pe, errors.New("associative list with keys may not have a null element")
205 }
206 if !child.IsMap() {
207 return pe, errors.New("associative list with keys may not have non-map elements")
208 }
209 keyMap := value.FieldList{}
210 m := child.AsMapUsing(a)
211 defer a.Free(m)
212 for _, fieldName := range list.Keys {
213 if val, ok := m.Get(fieldName); ok {
214 keyMap = append(keyMap, value.Field{Name: fieldName, Value: val})
215 } else if def, err := getAssociativeKeyDefault(s, list, fieldName); err != nil {
216 return pe, fmt.Errorf("couldn't find default value for %v: %v", fieldName, err)
217 } else if def != nil {
218 keyMap = append(keyMap, value.Field{Name: fieldName, Value: value.NewValueInterface(def)})
219 } else {
220 return pe, fmt.Errorf("associative list with keys has an element that omits key field %q (and doesn't have default value)", fieldName)
221 }
222 }
223 keyMap.Sort()
224 pe.Key = &keyMap
225 return pe, nil
226 }
227
228 func setItemToPathElement(child value.Value) (fieldpath.PathElement, error) {
229 pe := fieldpath.PathElement{}
230 switch {
231 case child.IsMap():
232
233 return pe, errors.New("associative list without keys has an element that's a map type")
234 case child.IsList():
235
236
237
238 return pe, errors.New("not supported: associative list with lists as elements")
239 case child.IsNull():
240 return pe, errors.New("associative list without keys has an element that's an explicit null")
241 default:
242
243 pe.Value = &child
244 return pe, nil
245 }
246 }
247
248 func listItemToPathElement(a value.Allocator, s *schema.Schema, list *schema.List, child value.Value) (fieldpath.PathElement, error) {
249 if list.ElementRelationship != schema.Associative {
250 return fieldpath.PathElement{}, errors.New("invalid indexing of non-associative list")
251 }
252
253 if len(list.Keys) > 0 {
254 return keyedAssociativeListItemToPathElement(a, s, list, child)
255 }
256
257
258 return setItemToPathElement(child)
259 }
260
View as plain text