1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package schema 18 19 import ( 20 "sync" 21 ) 22 23 // Schema is a list of named types. 24 // 25 // Schema types are indexed in a map before the first search so this type 26 // should be considered immutable. 27 type Schema struct { 28 Types []TypeDef `yaml:"types,omitempty"` 29 30 once sync.Once 31 m map[string]TypeDef 32 33 lock sync.Mutex 34 // Cached results of resolving type references to atoms. Only stores 35 // type references which require fields of Atom to be overriden. 36 resolvedTypes map[TypeRef]Atom 37 } 38 39 // A TypeSpecifier references a particular type in a schema. 40 type TypeSpecifier struct { 41 Type TypeRef `yaml:"type,omitempty"` 42 Schema Schema `yaml:"schema,omitempty"` 43 } 44 45 // TypeDef represents a named type in a schema. 46 type TypeDef struct { 47 // Top level types should be named. Every type must have a unique name. 48 Name string `yaml:"name,omitempty"` 49 50 Atom `yaml:"atom,omitempty,inline"` 51 } 52 53 // TypeRef either refers to a named type or declares an inlined type. 54 type TypeRef struct { 55 // Either the name or one member of Atom should be set. 56 NamedType *string `yaml:"namedType,omitempty"` 57 Inlined Atom `yaml:",inline,omitempty"` 58 59 // If this reference refers to a map-type or list-type, this field overrides 60 // the `ElementRelationship` of the referred type when resolved. 61 // If this field is nil, then it has no effect. 62 // See `Map` and `List` for more information about `ElementRelationship` 63 ElementRelationship *ElementRelationship `yaml:"elementRelationship,omitempty"` 64 } 65 66 // Atom represents the smallest possible pieces of the type system. 67 // Each set field in the Atom represents a possible type for the object. 68 // If none of the fields are set, any object will fail validation against the atom. 69 type Atom struct { 70 *Scalar `yaml:"scalar,omitempty"` 71 *List `yaml:"list,omitempty"` 72 *Map `yaml:"map,omitempty"` 73 } 74 75 // Scalar (AKA "primitive") represents a type which has a single value which is 76 // either numeric, string, or boolean, or untyped for any of them. 77 // 78 // TODO: split numeric into float/int? Something even more fine-grained? 79 type Scalar string 80 81 const ( 82 Numeric = Scalar("numeric") 83 String = Scalar("string") 84 Boolean = Scalar("boolean") 85 Untyped = Scalar("untyped") 86 ) 87 88 // ElementRelationship is an enum of the different possible relationships 89 // between the elements of container types (maps, lists). 90 type ElementRelationship string 91 92 const ( 93 // Associative only applies to lists (see the documentation there). 94 Associative = ElementRelationship("associative") 95 // Atomic makes container types (lists, maps) behave 96 // as scalars / leaf fields 97 Atomic = ElementRelationship("atomic") 98 // Separable means the items of the container type have no particular 99 // relationship (default behavior for maps). 100 Separable = ElementRelationship("separable") 101 ) 102 103 // Map is a key-value pair. Its default semantics are the same as an 104 // associative list, but: 105 // - It is serialized differently: 106 // map: {"k": {"value": "v"}} 107 // list: [{"key": "k", "value": "v"}] 108 // - Keys must be string typed. 109 // - Keys can't have multiple components. 110 // 111 // Optionally, maps may be atomic (for example, imagine representing an RGB 112 // color value--it doesn't make sense to have different actors own the R and G 113 // values). 114 // 115 // Maps may also represent a type which is composed of a number of different fields. 116 // Each field has a name and a type. 117 // 118 // Fields are indexed in a map before the first search so this type 119 // should be considered immutable. 120 type Map struct { 121 // Each struct field appears exactly once in this list. The order in 122 // this list defines the canonical field ordering. 123 Fields []StructField `yaml:"fields,omitempty"` 124 125 // A Union is a grouping of fields with special rules. It may refer to 126 // one or more fields in the above list. A given field from the above 127 // list may be referenced in exactly 0 or 1 places in the below list. 128 // One can have multiple unions in the same struct, but the fields can't 129 // overlap between unions. 130 Unions []Union `yaml:"unions,omitempty"` 131 132 // ElementType is the type of the structs's unknown fields. 133 ElementType TypeRef `yaml:"elementType,omitempty"` 134 135 // ElementRelationship states the relationship between the map's items. 136 // * `separable` (or unset) implies that each element is 100% independent. 137 // * `atomic` implies that all elements depend on each other, and this 138 // is effectively a scalar / leaf field; it doesn't make sense for 139 // separate actors to set the elements. Example: an RGB color struct; 140 // it would never make sense to "own" only one component of the 141 // color. 142 // The default behavior for maps is `separable`; it's permitted to 143 // leave this unset to get the default behavior. 144 ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"` 145 146 once sync.Once 147 m map[string]StructField 148 } 149 150 // FindField is a convenience function that returns the referenced StructField, 151 // if it exists, or (nil, false) if it doesn't. 152 func (m *Map) FindField(name string) (StructField, bool) { 153 m.once.Do(func() { 154 m.m = make(map[string]StructField, len(m.Fields)) 155 for _, field := range m.Fields { 156 m.m[field.Name] = field 157 } 158 }) 159 sf, ok := m.m[name] 160 return sf, ok 161 } 162 163 // CopyInto this instance of Map into the other 164 // If other is nil this method does nothing. 165 // If other is already initialized, overwrites it with this instance 166 // Warning: Not thread safe 167 func (m *Map) CopyInto(dst *Map) { 168 if dst == nil { 169 return 170 } 171 172 // Map type is considered immutable so sharing references 173 dst.Fields = m.Fields 174 dst.ElementType = m.ElementType 175 dst.Unions = m.Unions 176 dst.ElementRelationship = m.ElementRelationship 177 178 if m.m != nil { 179 // If cache is non-nil then the once token had been consumed. 180 // Must reset token and use it again to ensure same semantics. 181 dst.once = sync.Once{} 182 dst.once.Do(func() { 183 dst.m = m.m 184 }) 185 } 186 } 187 188 // UnionFields are mapping between the fields that are part of the union and 189 // their discriminated value. The discriminated value has to be set, and 190 // should not conflict with other discriminated value in the list. 191 type UnionField struct { 192 // FieldName is the name of the field that is part of the union. This 193 // is the serialized form of the field. 194 FieldName string `yaml:"fieldName"` 195 // Discriminatorvalue is the value of the discriminator to 196 // select that field. If the union doesn't have a discriminator, 197 // this field is ignored. 198 DiscriminatorValue string `yaml:"discriminatorValue"` 199 } 200 201 // Union, or oneof, means that only one of multiple fields of a structure can be 202 // set at a time. Setting the discriminator helps clearing oher fields: 203 // - If discriminator changed to non-nil, and a new field has been added 204 // that doesn't match, an error is returned, 205 // - If discriminator hasn't changed and two fields or more are set, an 206 // error is returned, 207 // - If discriminator changed to non-nil, all other fields but the 208 // discriminated one will be cleared, 209 // - Otherwise, If only one field is left, update discriminator to that value. 210 type Union struct { 211 // Discriminator, if present, is the name of the field that 212 // discriminates fields in the union. The mapping between the value of 213 // the discriminator and the field is done by using the Fields list 214 // below. 215 Discriminator *string `yaml:"discriminator,omitempty"` 216 217 // DeduceInvalidDiscriminator indicates if the discriminator 218 // should be updated automatically based on the fields set. This 219 // typically defaults to false since we don't want to deduce by 220 // default (the behavior exists to maintain compatibility on 221 // existing types and shouldn't be used for new types). 222 DeduceInvalidDiscriminator bool `yaml:"deduceInvalidDiscriminator,omitempty"` 223 224 // This is the list of fields that belong to this union. All the 225 // fields present in here have to be part of the parent 226 // structure. Discriminator (if oneOf has one), is NOT included in 227 // this list. The value for field is how we map the name of the field 228 // to actual value for discriminator. 229 Fields []UnionField `yaml:"fields,omitempty"` 230 } 231 232 // StructField pairs a field name with a field type. 233 type StructField struct { 234 // Name is the field name. 235 Name string `yaml:"name,omitempty"` 236 // Type is the field type. 237 Type TypeRef `yaml:"type,omitempty"` 238 // Default value for the field, nil if not present. 239 Default interface{} `yaml:"default,omitempty"` 240 } 241 242 // List represents a type which contains a zero or more elements, all of the 243 // same subtype. Lists may be either associative: each element is more or less 244 // independent and could be managed by separate entities in the system; or 245 // atomic, where the elements are heavily dependent on each other: it is not 246 // sensible to change one element without considering the ramifications on all 247 // the other elements. 248 type List struct { 249 // ElementType is the type of the list's elements. 250 ElementType TypeRef `yaml:"elementType,omitempty"` 251 252 // ElementRelationship states the relationship between the list's elements 253 // and must have one of these values: 254 // * `atomic`: the list is treated as a single entity, like a scalar. 255 // * `associative`: 256 // - If the list element is a scalar, the list is treated as a set. 257 // - If the list element is a map, the list is treated as a map. 258 // There is no default for this value for lists; all schemas must 259 // explicitly state the element relationship for all lists. 260 ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"` 261 262 // Iff ElementRelationship is `associative`, and the element type is 263 // map, then Keys must have non-zero length, and it lists the fields 264 // of the element's map type which are to be used as the keys of the 265 // list. 266 // 267 // TODO: change this to "non-atomic struct" above and make the code reflect this. 268 // 269 // Each key must refer to a single field name (no nesting, not JSONPath). 270 Keys []string `yaml:"keys,omitempty"` 271 } 272 273 // FindNamedType is a convenience function that returns the referenced TypeDef, 274 // if it exists, or (nil, false) if it doesn't. 275 func (s *Schema) FindNamedType(name string) (TypeDef, bool) { 276 s.once.Do(func() { 277 s.m = make(map[string]TypeDef, len(s.Types)) 278 for _, t := range s.Types { 279 s.m[t.Name] = t 280 } 281 }) 282 t, ok := s.m[name] 283 return t, ok 284 } 285 286 func (s *Schema) resolveNoOverrides(tr TypeRef) (Atom, bool) { 287 result := Atom{} 288 289 if tr.NamedType != nil { 290 t, ok := s.FindNamedType(*tr.NamedType) 291 if !ok { 292 return Atom{}, false 293 } 294 295 result = t.Atom 296 } else { 297 result = tr.Inlined 298 } 299 300 return result, true 301 } 302 303 // Resolve is a convenience function which returns the atom referenced, whether 304 // it is inline or named. Returns (Atom{}, false) if the type can't be resolved. 305 // 306 // This allows callers to not care about the difference between a (possibly 307 // inlined) reference and a definition. 308 func (s *Schema) Resolve(tr TypeRef) (Atom, bool) { 309 // If this is a plain reference with no overrides, just return the type 310 if tr.ElementRelationship == nil { 311 return s.resolveNoOverrides(tr) 312 } 313 314 s.lock.Lock() 315 defer s.lock.Unlock() 316 317 if s.resolvedTypes == nil { 318 s.resolvedTypes = make(map[TypeRef]Atom) 319 } 320 321 var result Atom 322 var exists bool 323 324 // Return cached result if available 325 // If not, calculate result and cache it 326 if result, exists = s.resolvedTypes[tr]; !exists { 327 if result, exists = s.resolveNoOverrides(tr); exists { 328 // Allow field-level electives to override the referred type's modifiers 329 switch { 330 case result.Map != nil: 331 mapCopy := Map{} 332 result.Map.CopyInto(&mapCopy) 333 mapCopy.ElementRelationship = *tr.ElementRelationship 334 result.Map = &mapCopy 335 case result.List != nil: 336 listCopy := *result.List 337 listCopy.ElementRelationship = *tr.ElementRelationship 338 result.List = &listCopy 339 case result.Scalar != nil: 340 return Atom{}, false 341 default: 342 return Atom{}, false 343 } 344 } else { 345 return Atom{}, false 346 } 347 348 // Save result. If it is nil, that is also recorded as not existing. 349 s.resolvedTypes[tr] = result 350 } 351 352 return result, true 353 } 354 355 // Clones this instance of Schema into the other 356 // If other is nil this method does nothing. 357 // If other is already initialized, overwrites it with this instance 358 // Warning: Not thread safe 359 func (s *Schema) CopyInto(dst *Schema) { 360 if dst == nil { 361 return 362 } 363 364 // Schema type is considered immutable so sharing references 365 dst.Types = s.Types 366 367 if s.m != nil { 368 // If cache is non-nil then the once token had been consumed. 369 // Must reset token and use it again to ensure same semantics. 370 dst.once = sync.Once{} 371 dst.once.Do(func() { 372 dst.m = s.m 373 }) 374 } 375 } 376