...

Source file src/sigs.k8s.io/structured-merge-diff/v4/schema/elements.go

Documentation: sigs.k8s.io/structured-merge-diff/v4/schema

     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  

View as plain text