1/*
2Copyright 2017 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 apply
18
19import (
20	"fmt"
21)
22
23// Element contains the record, local, and remote value for a field in an object
24// and metadata about the field read from openapi.
25// Calling Merge on an element will apply the passed in strategy to Element -
26// e.g. either replacing the whole element with the local copy or merging each
27// of the recorded, local and remote fields of the element.
28type Element interface {
29	// FieldMeta specifies which merge strategy to use for this element
30	FieldMeta
31
32	// Merge merges the recorded, local and remote values in the element using the Strategy
33	// provided as an argument.  Calls the type specific method on the Strategy - following the
34	// "Accept" method from the "Visitor" pattern.
35	// e.g. Merge on a ListElement will call Strategy.MergeList(self)
36	// Returns the Result of the merged elements
37	Merge(Strategy) (Result, error)
38
39	// HasRecorded returns true if the field was explicitly
40	// present in the recorded source.  This is to differentiate between
41	// undefined and set to null
42	HasRecorded() bool
43
44	// GetRecorded returns the field value from the recorded source of the object
45	GetRecorded() interface{}
46
47	// HasLocal returns true if the field was explicitly
48	// present in the local source.  This is to differentiate between
49	// undefined and set to null
50	HasLocal() bool
51
52	// GetLocal returns the field value from the local source of the object
53	GetLocal() interface{}
54
55	// HasRemote returns true if the field was explicitly
56	// present in the remote source.  This is to differentiate between
57	// undefined and set to null
58	HasRemote() bool
59
60	// GetRemote returns the field value from the remote source of the object
61	GetRemote() interface{}
62}
63
64// FieldMeta defines the strategy used to apply a Patch for an element
65type FieldMeta interface {
66	// GetFieldMergeType returns the type of merge strategy to use for this field
67	// maybe "merge", "replace" or "retainkeys"
68	// TODO: There maybe multiple strategies, so this may need to be a slice, map, or struct
69	// Address this in a follow up in the PR to introduce retainkeys strategy
70	GetFieldMergeType() string
71
72	// GetFieldMergeKeys returns the merge key to use when the MergeType is "merge" and underlying type is a list
73	GetFieldMergeKeys() MergeKeys
74
75	// GetFieldType returns the openapi field type - e.g. primitive, array, map, type, reference
76	GetFieldType() string
77}
78
79// FieldMetaImpl implements FieldMeta
80type FieldMetaImpl struct {
81	// MergeType is the type of merge strategy to use for this field
82	// maybe "merge", "replace" or "retainkeys"
83	MergeType string
84
85	// MergeKeys are the merge keys to use when the MergeType is "merge" and underlying type is a list
86	MergeKeys MergeKeys
87
88	// Type is the openapi type of the field - "list", "primitive", "map"
89	Type string
90
91	// Name contains name of the field
92	Name string
93}
94
95// GetFieldMergeType implements FieldMeta.GetFieldMergeType
96func (s FieldMetaImpl) GetFieldMergeType() string {
97	return s.MergeType
98}
99
100// GetFieldMergeKeys implements FieldMeta.GetFieldMergeKeys
101func (s FieldMetaImpl) GetFieldMergeKeys() MergeKeys {
102	return s.MergeKeys
103}
104
105// GetFieldType implements FieldMeta.GetFieldType
106func (s FieldMetaImpl) GetFieldType() string {
107	return s.Type
108}
109
110// MergeKeyValue records the value of the mergekey for an item in a list
111type MergeKeyValue map[string]string
112
113// Equal returns true if the MergeKeyValues share the same value,
114// representing the same item in a list
115func (v MergeKeyValue) Equal(o MergeKeyValue) bool {
116	if len(v) != len(o) {
117		return false
118	}
119
120	for key, v1 := range v {
121		if v2, found := o[key]; !found || v1 != v2 {
122			return false
123		}
124	}
125
126	return true
127}
128
129// MergeKeys is the set of fields on an object that uniquely identify
130// and is used when merging lists to identify the "same" object
131// independent of the ordering of the objects
132type MergeKeys []string
133
134// GetMergeKeyValue parses the MergeKeyValue from an item in a list
135func (mk MergeKeys) GetMergeKeyValue(i interface{}) (MergeKeyValue, error) {
136	result := MergeKeyValue{}
137	if len(mk) <= 0 {
138		return result, fmt.Errorf("merge key must have at least 1 value to merge")
139	}
140	m, ok := i.(map[string]interface{})
141	if !ok {
142		return result, fmt.Errorf("cannot use mergekey %v for primitive item in list %v", mk, i)
143	}
144	for _, field := range mk {
145		if value, found := m[field]; !found {
146			result[field] = ""
147		} else {
148			result[field] = fmt.Sprintf("%v", value)
149		}
150	}
151	return result, nil
152}
153
154// CombinedPrimitiveSlice implements a slice of primitives
155type CombinedPrimitiveSlice struct {
156	Items []*PrimitiveListItem
157}
158
159// PrimitiveListItem represents a single value in a slice of primitives
160type PrimitiveListItem struct {
161	// Value is the value of the primitive, should match recorded, local and remote
162	Value interface{}
163
164	RawElementData
165}
166
167// Contains returns true if the slice contains the l
168func (s *CombinedPrimitiveSlice) lookup(l interface{}) *PrimitiveListItem {
169	val := fmt.Sprintf("%v", l)
170	for _, i := range s.Items {
171		if fmt.Sprintf("%v", i.Value) == val {
172			return i
173		}
174	}
175	return nil
176}
177
178func (s *CombinedPrimitiveSlice) upsert(l interface{}) *PrimitiveListItem {
179	// Return the item if it exists
180	if item := s.lookup(l); item != nil {
181		return item
182	}
183
184	// Otherwise create a new item and append to the list
185	item := &PrimitiveListItem{
186		Value: l,
187	}
188	s.Items = append(s.Items, item)
189	return item
190}
191
192// UpsertRecorded adds l to the slice.  If there is already a value of l in the
193// slice for either the local or remote, set on that value as the recorded value
194// Otherwise append a new item to the list with the recorded value.
195func (s *CombinedPrimitiveSlice) UpsertRecorded(l interface{}) {
196	v := s.upsert(l)
197	v.recorded = l
198	v.recordedSet = true
199}
200
201// UpsertLocal adds l to the slice.  If there is already a value of l in the
202// slice for either the recorded or remote, set on that value as the local value
203// Otherwise append a new item to the list with the local value.
204func (s *CombinedPrimitiveSlice) UpsertLocal(l interface{}) {
205	v := s.upsert(l)
206	v.local = l
207	v.localSet = true
208}
209
210// UpsertRemote adds l to the slice.  If there is already a value of l in the
211// slice for either the local or recorded, set on that value as the remote value
212// Otherwise append a new item to the list with the remote value.
213func (s *CombinedPrimitiveSlice) UpsertRemote(l interface{}) {
214	v := s.upsert(l)
215	v.remote = l
216	v.remoteSet = true
217}
218
219// ListItem represents a single value in a slice of maps or types
220type ListItem struct {
221	// KeyValue is the merge key value of the item
222	KeyValue MergeKeyValue
223
224	// RawElementData contains the field values
225	RawElementData
226}
227
228// CombinedMapSlice is a slice of maps or types with merge keys
229type CombinedMapSlice struct {
230	Items []*ListItem
231}
232
233// Lookup returns the ListItem matching the merge key, or nil if not found.
234func (s *CombinedMapSlice) lookup(v MergeKeyValue) *ListItem {
235	for _, i := range s.Items {
236		if i.KeyValue.Equal(v) {
237			return i
238		}
239	}
240	return nil
241}
242
243func (s *CombinedMapSlice) upsert(key MergeKeys, l interface{}) (*ListItem, error) {
244	// Get the identity of the item
245	val, err := key.GetMergeKeyValue(l)
246	if err != nil {
247		return nil, err
248	}
249
250	// Return the item if it exists
251	if item := s.lookup(val); item != nil {
252		return item, nil
253	}
254
255	// Otherwise create a new item and append to the list
256	item := &ListItem{
257		KeyValue: val,
258	}
259	s.Items = append(s.Items, item)
260	return item, nil
261}
262
263// UpsertRecorded adds l to the slice.  If there is already a value of l sharing
264// l's merge key in the slice for either the local or remote, set l the recorded value
265// Otherwise append a new item to the list with the recorded value.
266func (s *CombinedMapSlice) UpsertRecorded(key MergeKeys, l interface{}) error {
267	item, err := s.upsert(key, l)
268	if err != nil {
269		return err
270	}
271	item.SetRecorded(l)
272	return nil
273}
274
275// UpsertLocal adds l to the slice.  If there is already a value of l sharing
276// l's merge key in the slice for either the recorded or remote, set l the local value
277// Otherwise append a new item to the list with the local value.
278func (s *CombinedMapSlice) UpsertLocal(key MergeKeys, l interface{}) error {
279	item, err := s.upsert(key, l)
280	if err != nil {
281		return err
282	}
283	item.SetLocal(l)
284	return nil
285}
286
287// UpsertRemote adds l to the slice.  If there is already a value of l sharing
288// l's merge key in the slice for either the recorded or local, set l the remote value
289// Otherwise append a new item to the list with the remote value.
290func (s *CombinedMapSlice) UpsertRemote(key MergeKeys, l interface{}) error {
291	item, err := s.upsert(key, l)
292	if err != nil {
293		return err
294	}
295	item.SetRemote(l)
296	return nil
297}
298
299// IsDrop returns true if the field represented by e should be dropped from the merged object
300func IsDrop(e Element) bool {
301	// Specified in the last value recorded value and since deleted from the local
302	removed := e.HasRecorded() && !e.HasLocal()
303
304	// Specified locally and explicitly set to null
305	setToNil := e.HasLocal() && e.GetLocal() == nil
306
307	return removed || setToNil
308}
309
310// IsAdd returns true if the field represented by e should have the local value directly
311// added to the merged object instead of merging the recorded, local and remote values
312func IsAdd(e Element) bool {
313	// If it isn't already present in the remote value and is present in the local value
314	return e.HasLocal() && !e.HasRemote()
315}
316
317// NewRawElementData returns a new RawElementData, setting IsSet to true for
318// non-nil values, and leaving IsSet false for nil values.
319// Note: use this only when you want a nil-value to be considered "unspecified"
320// (ignore) and not "unset" (deleted).
321func NewRawElementData(recorded, local, remote interface{}) RawElementData {
322	data := RawElementData{}
323	if recorded != nil {
324		data.SetRecorded(recorded)
325	}
326	if local != nil {
327		data.SetLocal(local)
328	}
329	if remote != nil {
330		data.SetRemote(remote)
331	}
332	return data
333}
334
335// RawElementData contains the raw recorded, local and remote data
336// and metadata about whethere or not each was set
337type RawElementData struct {
338	HasElementData
339
340	recorded interface{}
341	local    interface{}
342	remote   interface{}
343}
344
345// SetRecorded sets the recorded value
346func (b *RawElementData) SetRecorded(value interface{}) {
347	b.recorded = value
348	b.recordedSet = true
349}
350
351// SetLocal sets the local value
352func (b *RawElementData) SetLocal(value interface{}) {
353	b.local = value
354	b.localSet = true
355}
356
357// SetRemote sets the remote value
358func (b *RawElementData) SetRemote(value interface{}) {
359	b.remote = value
360	b.remoteSet = true
361}
362
363// GetRecorded implements Element.GetRecorded
364func (b RawElementData) GetRecorded() interface{} {
365	// https://golang.org/doc/faq#nil_error
366	if b.recorded == nil {
367		return nil
368	}
369	return b.recorded
370}
371
372// GetLocal implements Element.GetLocal
373func (b RawElementData) GetLocal() interface{} {
374	// https://golang.org/doc/faq#nil_error
375	if b.local == nil {
376		return nil
377	}
378	return b.local
379}
380
381// GetRemote implements Element.GetRemote
382func (b RawElementData) GetRemote() interface{} {
383	// https://golang.org/doc/faq#nil_error
384	if b.remote == nil {
385		return nil
386	}
387	return b.remote
388}
389
390// HasElementData contains whether a field was set in the recorded, local and remote sources
391type HasElementData struct {
392	recordedSet bool
393	localSet    bool
394	remoteSet   bool
395}
396
397// HasRecorded implements Element.HasRecorded
398func (e HasElementData) HasRecorded() bool {
399	return e.recordedSet
400}
401
402// HasLocal implements Element.HasLocal
403func (e HasElementData) HasLocal() bool {
404	return e.localSet
405}
406
407// HasRemote implements Element.HasRemote
408func (e HasElementData) HasRemote() bool {
409	return e.remoteSet
410}
411
412// ConflictDetector defines the capability to detect conflict. An element can examine remote/recorded value to detect conflict.
413type ConflictDetector interface {
414	HasConflict() error
415}
416