...

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

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

     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 typed
    18  
    19  import (
    20  	"sync"
    21  
    22  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    23  	"sigs.k8s.io/structured-merge-diff/v4/schema"
    24  	"sigs.k8s.io/structured-merge-diff/v4/value"
    25  )
    26  
    27  // ValidationOptions is the list of all the options available when running the validation.
    28  type ValidationOptions int
    29  
    30  const (
    31  	// AllowDuplicates means that sets and associative lists can have duplicate similar items.
    32  	AllowDuplicates ValidationOptions = iota
    33  )
    34  
    35  // AsTyped accepts a value and a type and returns a TypedValue. 'v' must have
    36  // type 'typeName' in the schema. An error is returned if the v doesn't conform
    37  // to the schema.
    38  func AsTyped(v value.Value, s *schema.Schema, typeRef schema.TypeRef, opts ...ValidationOptions) (*TypedValue, error) {
    39  	tv := &TypedValue{
    40  		value:   v,
    41  		typeRef: typeRef,
    42  		schema:  s,
    43  	}
    44  	if err := tv.Validate(opts...); err != nil {
    45  		return nil, err
    46  	}
    47  	return tv, nil
    48  }
    49  
    50  // AsTypeUnvalidated is just like AsTyped, but doesn't validate that the type
    51  // conforms to the schema, for cases where that has already been checked or
    52  // where you're going to call a method that validates as a side-effect (like
    53  // ToFieldSet).
    54  //
    55  // Deprecated: This function was initially created because validation
    56  // was expensive. Now that this has been solved, objects should always
    57  // be created as validated, using `AsTyped`.
    58  func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeRef schema.TypeRef) *TypedValue {
    59  	tv := &TypedValue{
    60  		value:   v,
    61  		typeRef: typeRef,
    62  		schema:  s,
    63  	}
    64  	return tv
    65  }
    66  
    67  // TypedValue is a value of some specific type.
    68  type TypedValue struct {
    69  	value   value.Value
    70  	typeRef schema.TypeRef
    71  	schema  *schema.Schema
    72  }
    73  
    74  // TypeRef is the type of the value.
    75  func (tv TypedValue) TypeRef() schema.TypeRef {
    76  	return tv.typeRef
    77  }
    78  
    79  // AsValue removes the type from the TypedValue and only keeps the value.
    80  func (tv TypedValue) AsValue() value.Value {
    81  	return tv.value
    82  }
    83  
    84  // Schema gets the schema from the TypedValue.
    85  func (tv TypedValue) Schema() *schema.Schema {
    86  	return tv.schema
    87  }
    88  
    89  // Validate returns an error with a list of every spec violation.
    90  func (tv TypedValue) Validate(opts ...ValidationOptions) error {
    91  	w := tv.walker()
    92  	for _, opt := range opts {
    93  		switch opt {
    94  		case AllowDuplicates:
    95  			w.allowDuplicates = true
    96  		}
    97  	}
    98  	defer w.finished()
    99  	if errs := w.validate(nil); len(errs) != 0 {
   100  		return errs
   101  	}
   102  	return nil
   103  }
   104  
   105  // ToFieldSet creates a set containing every leaf field and item mentioned, or
   106  // validation errors, if any were encountered.
   107  func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) {
   108  	w := tv.toFieldSetWalker()
   109  	defer w.finished()
   110  	if errs := w.toFieldSet(); len(errs) != 0 {
   111  		return nil, errs
   112  	}
   113  	return w.set, nil
   114  }
   115  
   116  // Merge returns the result of merging tv and pso ("partially specified
   117  // object") together. Of note:
   118  //   - No fields can be removed by this operation.
   119  //   - If both tv and pso specify a given leaf field, the result will keep pso's
   120  //     value.
   121  //   - Container typed elements will have their items ordered:
   122  //     1. like tv, if pso doesn't change anything in the container
   123  //     2. like pso, if pso does change something in the container.
   124  //
   125  // tv and pso must both be of the same type (their Schema and TypeRef must
   126  // match), or an error will be returned. Validation errors will be returned if
   127  // the objects don't conform to the schema.
   128  func (tv TypedValue) Merge(pso *TypedValue) (*TypedValue, error) {
   129  	return merge(&tv, pso, ruleKeepRHS, nil)
   130  }
   131  
   132  var cmpwPool = sync.Pool{
   133  	New: func() interface{} { return &compareWalker{} },
   134  }
   135  
   136  // Compare compares the two objects. See the comments on the `Comparison`
   137  // struct for details on the return value.
   138  //
   139  // tv and rhs must both be of the same type (their Schema and TypeRef must
   140  // match), or an error will be returned. Validation errors will be returned if
   141  // the objects don't conform to the schema.
   142  func (tv TypedValue) Compare(rhs *TypedValue) (c *Comparison, err error) {
   143  	lhs := tv
   144  	if lhs.schema != rhs.schema {
   145  		return nil, errorf("expected objects with types from the same schema")
   146  	}
   147  	if !lhs.typeRef.Equals(&rhs.typeRef) {
   148  		return nil, errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
   149  	}
   150  
   151  	cmpw := cmpwPool.Get().(*compareWalker)
   152  	defer func() {
   153  		cmpw.lhs = nil
   154  		cmpw.rhs = nil
   155  		cmpw.schema = nil
   156  		cmpw.typeRef = schema.TypeRef{}
   157  		cmpw.comparison = nil
   158  		cmpw.inLeaf = false
   159  
   160  		cmpwPool.Put(cmpw)
   161  	}()
   162  
   163  	cmpw.lhs = lhs.value
   164  	cmpw.rhs = rhs.value
   165  	cmpw.schema = lhs.schema
   166  	cmpw.typeRef = lhs.typeRef
   167  	cmpw.comparison = &Comparison{
   168  		Removed:  fieldpath.NewSet(),
   169  		Modified: fieldpath.NewSet(),
   170  		Added:    fieldpath.NewSet(),
   171  	}
   172  	if cmpw.allocator == nil {
   173  		cmpw.allocator = value.NewFreelistAllocator()
   174  	}
   175  
   176  	errs := cmpw.compare(nil)
   177  	if len(errs) > 0 {
   178  		return nil, errs
   179  	}
   180  	return cmpw.comparison, nil
   181  }
   182  
   183  // RemoveItems removes each provided list or map item from the value.
   184  func (tv TypedValue) RemoveItems(items *fieldpath.Set) *TypedValue {
   185  	tv.value = removeItemsWithSchema(tv.value, items, tv.schema, tv.typeRef, false)
   186  	return &tv
   187  }
   188  
   189  // ExtractItems returns a value with only the provided list or map items extracted from the value.
   190  func (tv TypedValue) ExtractItems(items *fieldpath.Set) *TypedValue {
   191  	tv.value = removeItemsWithSchema(tv.value, items, tv.schema, tv.typeRef, true)
   192  	return &tv
   193  }
   194  
   195  func (tv TypedValue) Empty() *TypedValue {
   196  	tv.value = value.NewValueInterface(nil)
   197  	return &tv
   198  }
   199  
   200  var mwPool = sync.Pool{
   201  	New: func() interface{} { return &mergingWalker{} },
   202  }
   203  
   204  func merge(lhs, rhs *TypedValue, rule, postRule mergeRule) (*TypedValue, error) {
   205  	if lhs.schema != rhs.schema {
   206  		return nil, errorf("expected objects with types from the same schema")
   207  	}
   208  	if !lhs.typeRef.Equals(&rhs.typeRef) {
   209  		return nil, errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
   210  	}
   211  
   212  	mw := mwPool.Get().(*mergingWalker)
   213  	defer func() {
   214  		mw.lhs = nil
   215  		mw.rhs = nil
   216  		mw.schema = nil
   217  		mw.typeRef = schema.TypeRef{}
   218  		mw.rule = nil
   219  		mw.postItemHook = nil
   220  		mw.out = nil
   221  		mw.inLeaf = false
   222  
   223  		mwPool.Put(mw)
   224  	}()
   225  
   226  	mw.lhs = lhs.value
   227  	mw.rhs = rhs.value
   228  	mw.schema = lhs.schema
   229  	mw.typeRef = lhs.typeRef
   230  	mw.rule = rule
   231  	mw.postItemHook = postRule
   232  	if mw.allocator == nil {
   233  		mw.allocator = value.NewFreelistAllocator()
   234  	}
   235  
   236  	errs := mw.merge(nil)
   237  	if len(errs) > 0 {
   238  		return nil, errs
   239  	}
   240  
   241  	out := &TypedValue{
   242  		schema:  lhs.schema,
   243  		typeRef: lhs.typeRef,
   244  	}
   245  	if mw.out != nil {
   246  		out.value = value.NewValueInterface(*mw.out)
   247  	}
   248  	return out, nil
   249  }
   250  

View as plain text