1/*
2Copyright 2019 The Kubernetes Authors.
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6    http://www.apache.org/licenses/LICENSE-2.0
7Unless required by applicable law or agreed to in writing, software
8distributed under the License is distributed on an "AS IS" BASIS,
9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10See the License for the specific language governing permissions and
11limitations under the License.
12*/
13
14package typed
15
16import (
17	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
18	"sigs.k8s.io/structured-merge-diff/v4/schema"
19	"sigs.k8s.io/structured-merge-diff/v4/value"
20)
21
22type removingWalker struct {
23	value         value.Value
24	out           interface{}
25	schema        *schema.Schema
26	toRemove      *fieldpath.Set
27	allocator     value.Allocator
28	shouldExtract bool
29}
30
31// removeItemsWithSchema will walk the given value and look for items from the toRemove set.
32// Depending on whether shouldExtract is set true or false, it will return a modified version
33// of the input value with either:
34// 1. only the items in the toRemove set (when shouldExtract is true) or
35// 2. the items from the toRemove set removed from the value (when shouldExtract is false).
36func removeItemsWithSchema(val value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef, shouldExtract bool) value.Value {
37	w := &removingWalker{
38		value:         val,
39		schema:        schema,
40		toRemove:      toRemove,
41		allocator:     value.NewFreelistAllocator(),
42		shouldExtract: shouldExtract,
43	}
44	resolveSchema(schema, typeRef, val, w)
45	return value.NewValueInterface(w.out)
46}
47
48func (w *removingWalker) doScalar(t *schema.Scalar) ValidationErrors {
49	w.out = w.value.Unstructured()
50	return nil
51}
52
53func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
54	l := w.value.AsListUsing(w.allocator)
55	defer w.allocator.Free(l)
56	// If list is null, empty, or atomic just return
57	if l == nil || l.Length() == 0 || t.ElementRelationship == schema.Atomic {
58		return nil
59	}
60
61	var newItems []interface{}
62	iter := l.RangeUsing(w.allocator)
63	defer w.allocator.Free(iter)
64	for iter.Next() {
65		i, item := iter.Item()
66		// Ignore error because we have already validated this list
67		pe, _ := listItemToPathElement(w.allocator, w.schema, t, i, item)
68		path, _ := fieldpath.MakePath(pe)
69		// save items on the path when we shouldExtract
70		// but ignore them when we are removing (i.e. !w.shouldExtract)
71		if w.toRemove.Has(path) {
72			if w.shouldExtract {
73				newItems = append(newItems, item.Unstructured())
74			} else {
75				continue
76			}
77		}
78		if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
79			item = removeItemsWithSchema(item, subset, w.schema, t.ElementType, w.shouldExtract)
80		} else {
81			// don't save items not on the path when we shouldExtract.
82			if w.shouldExtract {
83				continue
84			}
85		}
86		newItems = append(newItems, item.Unstructured())
87	}
88	if len(newItems) > 0 {
89		w.out = newItems
90	}
91	return nil
92}
93
94func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
95	m := w.value.AsMapUsing(w.allocator)
96	if m != nil {
97		defer w.allocator.Free(m)
98	}
99	// If map is null, empty, or atomic just return
100	if m == nil || m.Empty() || t.ElementRelationship == schema.Atomic {
101		return nil
102	}
103
104	fieldTypes := map[string]schema.TypeRef{}
105	for _, structField := range t.Fields {
106		fieldTypes[structField.Name] = structField.Type
107	}
108
109	newMap := map[string]interface{}{}
110	m.Iterate(func(k string, val value.Value) bool {
111		pe := fieldpath.PathElement{FieldName: &k}
112		path, _ := fieldpath.MakePath(pe)
113		fieldType := t.ElementType
114		if ft, ok := fieldTypes[k]; ok {
115			fieldType = ft
116		}
117		// save values on the path when we shouldExtract
118		// but ignore them when we are removing (i.e. !w.shouldExtract)
119		if w.toRemove.Has(path) {
120			if w.shouldExtract {
121				newMap[k] = val.Unstructured()
122			}
123			return true
124		}
125		if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
126			val = removeItemsWithSchema(val, subset, w.schema, fieldType, w.shouldExtract)
127		} else {
128			// don't save values not on the path when we shouldExtract.
129			if w.shouldExtract {
130				return true
131			}
132		}
133		newMap[k] = val.Unstructured()
134		return true
135	})
136	if len(newMap) > 0 {
137		w.out = newMap
138	}
139	return nil
140}
141