1/* 2Copyright 2018 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package schema 18 19import "sync" 20 21// Schema is a list of named types. 22// 23// Schema types are indexed in a map before the first search so this type 24// should be considered immutable. 25type Schema struct { 26 Types []TypeDef `yaml:"types,omitempty"` 27 28 once sync.Once 29 m map[string]TypeDef 30} 31 32// A TypeSpecifier references a particular type in a schema. 33type TypeSpecifier struct { 34 Type TypeRef `yaml:"type,omitempty"` 35 Schema Schema `yaml:"schema,omitempty"` 36} 37 38// TypeDef represents a named type in a schema. 39type TypeDef struct { 40 // Top level types should be named. Every type must have a unique name. 41 Name string `yaml:"name,omitempty"` 42 43 Atom `yaml:"atom,omitempty,inline"` 44} 45 46// TypeRef either refers to a named type or declares an inlined type. 47type TypeRef struct { 48 // Either the name or one member of Atom should be set. 49 NamedType *string `yaml:"namedType,omitempty"` 50 Inlined Atom `yaml:",inline,omitempty"` 51} 52 53// Atom represents the smallest possible pieces of the type system. 54// Each set field in the Atom represents a possible type for the object. 55// If none of the fields are set, any object will fail validation against the atom. 56type Atom struct { 57 *Scalar `yaml:"scalar,omitempty"` 58 *List `yaml:"list,omitempty"` 59 *Map `yaml:"map,omitempty"` 60} 61 62// Scalar (AKA "primitive") represents a type which has a single value which is 63// either numeric, string, or boolean. 64// 65// TODO: split numeric into float/int? Something even more fine-grained? 66type Scalar string 67 68const ( 69 Numeric = Scalar("numeric") 70 String = Scalar("string") 71 Boolean = Scalar("boolean") 72) 73 74// ElementRelationship is an enum of the different possible relationships 75// between the elements of container types (maps, lists). 76type ElementRelationship string 77 78const ( 79 // Associative only applies to lists (see the documentation there). 80 Associative = ElementRelationship("associative") 81 // Atomic makes container types (lists, maps) behave 82 // as scalars / leaf fields 83 Atomic = ElementRelationship("atomic") 84 // Separable means the items of the container type have no particular 85 // relationship (default behavior for maps). 86 Separable = ElementRelationship("separable") 87) 88 89// Map is a key-value pair. Its default semantics are the same as an 90// associative list, but: 91// * It is serialized differently: 92// map: {"k": {"value": "v"}} 93// list: [{"key": "k", "value": "v"}] 94// * Keys must be string typed. 95// * Keys can't have multiple components. 96// 97// Optionally, maps may be atomic (for example, imagine representing an RGB 98// color value--it doesn't make sense to have different actors own the R and G 99// values). 100// 101// Maps may also represent a type which is composed of a number of different fields. 102// Each field has a name and a type. 103// 104// Fields are indexed in a map before the first search so this type 105// should be considered immutable. 106type Map struct { 107 // Each struct field appears exactly once in this list. The order in 108 // this list defines the canonical field ordering. 109 Fields []StructField `yaml:"fields,omitempty"` 110 111 // A Union is a grouping of fields with special rules. It may refer to 112 // one or more fields in the above list. A given field from the above 113 // list may be referenced in exactly 0 or 1 places in the below list. 114 // One can have multiple unions in the same struct, but the fields can't 115 // overlap between unions. 116 Unions []Union `yaml:"unions,omitempty"` 117 118 // ElementType is the type of the structs's unknown fields. 119 ElementType TypeRef `yaml:"elementType,omitempty"` 120 121 // ElementRelationship states the relationship between the map's items. 122 // * `separable` (or unset) implies that each element is 100% independent. 123 // * `atomic` implies that all elements depend on each other, and this 124 // is effectively a scalar / leaf field; it doesn't make sense for 125 // separate actors to set the elements. Example: an RGB color struct; 126 // it would never make sense to "own" only one component of the 127 // color. 128 // The default behavior for maps is `separable`; it's permitted to 129 // leave this unset to get the default behavior. 130 ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"` 131 132 once sync.Once 133 m map[string]StructField 134} 135 136// FindField is a convenience function that returns the referenced StructField, 137// if it exists, or (nil, false) if it doesn't. 138func (m *Map) FindField(name string) (StructField, bool) { 139 m.once.Do(func() { 140 m.m = make(map[string]StructField, len(m.Fields)) 141 for _, field := range m.Fields { 142 m.m[field.Name] = field 143 } 144 }) 145 sf, ok := m.m[name] 146 return sf, ok 147} 148 149// UnionFields are mapping between the fields that are part of the union and 150// their discriminated value. The discriminated value has to be set, and 151// should not conflict with other discriminated value in the list. 152type UnionField struct { 153 // FieldName is the name of the field that is part of the union. This 154 // is the serialized form of the field. 155 FieldName string `yaml:"fieldName"` 156 // Discriminatorvalue is the value of the discriminator to 157 // select that field. If the union doesn't have a discriminator, 158 // this field is ignored. 159 DiscriminatorValue string `yaml:"discriminatorValue"` 160} 161 162// Union, or oneof, means that only one of multiple fields of a structure can be 163// set at a time. Setting the discriminator helps clearing oher fields: 164// - If discriminator changed to non-nil, and a new field has been added 165// that doesn't match, an error is returned, 166// - If discriminator hasn't changed and two fields or more are set, an 167// error is returned, 168// - If discriminator changed to non-nil, all other fields but the 169// discriminated one will be cleared, 170// - Otherwise, If only one field is left, update discriminator to that value. 171type Union struct { 172 // Discriminator, if present, is the name of the field that 173 // discriminates fields in the union. The mapping between the value of 174 // the discriminator and the field is done by using the Fields list 175 // below. 176 Discriminator *string `yaml:"discriminator,omitempty"` 177 178 // DeduceInvalidDiscriminator indicates if the discriminator 179 // should be updated automatically based on the fields set. This 180 // typically defaults to false since we don't want to deduce by 181 // default (the behavior exists to maintain compatibility on 182 // existing types and shouldn't be used for new types). 183 DeduceInvalidDiscriminator bool `yaml:"deduceInvalidDiscriminator,omitempty"` 184 185 // This is the list of fields that belong to this union. All the 186 // fields present in here have to be part of the parent 187 // structure. Discriminator (if oneOf has one), is NOT included in 188 // this list. The value for field is how we map the name of the field 189 // to actual value for discriminator. 190 Fields []UnionField `yaml:"fields,omitempty"` 191} 192 193// StructField pairs a field name with a field type. 194type StructField struct { 195 // Name is the field name. 196 Name string `yaml:"name,omitempty"` 197 // Type is the field type. 198 Type TypeRef `yaml:"type,omitempty"` 199} 200 201// List represents a type which contains a zero or more elements, all of the 202// same subtype. Lists may be either associative: each element is more or less 203// independent and could be managed by separate entities in the system; or 204// atomic, where the elements are heavily dependent on each other: it is not 205// sensible to change one element without considering the ramifications on all 206// the other elements. 207type List struct { 208 // ElementType is the type of the list's elements. 209 ElementType TypeRef `yaml:"elementType,omitempty"` 210 211 // ElementRelationship states the relationship between the list's elements 212 // and must have one of these values: 213 // * `atomic`: the list is treated as a single entity, like a scalar. 214 // * `associative`: 215 // - If the list element is a scalar, the list is treated as a set. 216 // - If the list element is a map, the list is treated as a map. 217 // There is no default for this value for lists; all schemas must 218 // explicitly state the element relationship for all lists. 219 ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"` 220 221 // Iff ElementRelationship is `associative`, and the element type is 222 // map, then Keys must have non-zero length, and it lists the fields 223 // of the element's map type which are to be used as the keys of the 224 // list. 225 // 226 // TODO: change this to "non-atomic struct" above and make the code reflect this. 227 // 228 // Each key must refer to a single field name (no nesting, not JSONPath). 229 Keys []string `yaml:"keys,omitempty"` 230} 231 232// FindNamedType is a convenience function that returns the referenced TypeDef, 233// if it exists, or (nil, false) if it doesn't. 234func (s *Schema) FindNamedType(name string) (TypeDef, bool) { 235 s.once.Do(func() { 236 s.m = make(map[string]TypeDef, len(s.Types)) 237 for _, t := range s.Types { 238 s.m[t.Name] = t 239 } 240 }) 241 t, ok := s.m[name] 242 return t, ok 243} 244 245// Resolve is a convenience function which returns the atom referenced, whether 246// it is inline or named. Returns (Atom{}, false) if the type can't be resolved. 247// 248// This allows callers to not care about the difference between a (possibly 249// inlined) reference and a definition. 250func (s *Schema) Resolve(tr TypeRef) (Atom, bool) { 251 if tr.NamedType != nil { 252 t, ok := s.FindNamedType(*tr.NamedType) 253 if !ok { 254 return Atom{}, false 255 } 256 return t.Atom, true 257 } 258 return tr.Inlined, true 259} 260