Skip to content

Commit 18dcdd8

Browse files
authored
Merge pull request #71 from jennybuckley/untyped-separable
Make deduced type use type lookup instead of duplicating logic
2 parents e85c7b2 + e3c6ed6 commit 18dcdd8

File tree

14 files changed

+167
-351
lines changed

14 files changed

+167
-351
lines changed

internal/cli/operation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type operationBase struct {
3333
typeName string
3434
}
3535

36-
func (b operationBase) parseFile(path string) (tv typed.TypedValue, err error) {
36+
func (b operationBase) parseFile(path string) (tv *typed.TypedValue, err error) {
3737
bytes, err := ioutil.ReadFile(path)
3838
if err != nil {
3939
return tv, fmt.Errorf("unable to read file %q: %v", path, err)

internal/fixture/state.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
// State of the current test in terms of live object. One can check at
3030
// any time that Live and Managers match the expectations.
3131
type State struct {
32-
Live typed.TypedValue
32+
Live *typed.TypedValue
3333
Parser typed.ParseableType
3434
Managers fieldpath.ManagedFields
3535
Updater *merge.Updater
@@ -146,7 +146,7 @@ type dummyConverter struct{}
146146
var _ merge.Converter = dummyConverter{}
147147

148148
// Convert returns the object given in input, not doing any conversion.
149-
func (dummyConverter) Convert(v typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error) {
149+
func (dummyConverter) Convert(v *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
150150
if len(version) == 0 {
151151
return nil, fmt.Errorf("cannot convert to invalid version: %q", version)
152152
}

merge/deduced_test.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func TestDeduced(t *testing.T) {
168168
},
169169
},
170170
},
171-
"leaf_apply_twice_dangling": {
171+
"leaf_apply_twice_remove": {
172172
Ops: []Operation{
173173
Apply{
174174
Manager: "default",
@@ -188,9 +188,7 @@ func TestDeduced(t *testing.T) {
188188
},
189189
},
190190
Object: `
191-
numeric: 1
192191
string: "new string"
193-
bool: false
194192
`,
195193
Managed: fieldpath.ManagedFields{
196194
"default": &fieldpath.VersionedSet{
@@ -328,9 +326,7 @@ func TestDeduced(t *testing.T) {
328326
Object: ``,
329327
},
330328
},
331-
Object: `
332-
string: "string"
333-
`,
329+
Object: ``,
334330
Managed: fieldpath.ManagedFields{},
335331
},
336332
"apply_update_apply_nested": {
@@ -523,7 +519,7 @@ func TestDeduced(t *testing.T) {
523519

524520
for name, test := range tests {
525521
t.Run(name, func(t *testing.T) {
526-
if err := test.Test(typed.DeducedParseableType{}); err != nil {
522+
if err := test.Test(typed.DeducedParseableType); err != nil {
527523
t.Fatal(err)
528524
}
529525
})

merge/multiple_appliers_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,7 @@ var _ merge.Converter = repeatingConverter{}
834834
var missingVersionError error = fmt.Errorf("cannot convert to invalid version")
835835

836836
// Convert implements merge.Converter
837-
func (r repeatingConverter) Convert(v typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error) {
837+
func (r repeatingConverter) Convert(v *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
838838
if len(version) < 2 || string(version)[0] != 'v' {
839839
return nil, missingVersionError
840840
}

merge/obsolete_versions_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type specificVersionConverter struct {
3232
AcceptedVersions []fieldpath.APIVersion
3333
}
3434

35-
func (d *specificVersionConverter) Convert(object typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error) {
35+
func (d *specificVersionConverter) Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
3636
for _, v := range d.AcceptedVersions {
3737
if v == version {
3838
return object, nil
@@ -53,7 +53,7 @@ func TestObsoleteVersions(t *testing.T) {
5353
}
5454
state := fixture.State{
5555
Updater: &merge.Updater{Converter: converter},
56-
Parser: typed.DeducedParseableType{},
56+
Parser: typed.DeducedParseableType,
5757
}
5858

5959
if err := state.Update(typed.YAMLObject(`{"v1": 0}`), fieldpath.APIVersion("v1"), "v1"); err != nil {

merge/update.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
// Converter is an interface to the conversion logic. The converter
2424
// needs to be able to convert objects from one version to another.
2525
type Converter interface {
26-
Convert(object typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error)
26+
Convert(object *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error)
2727
IsMissingVersionError(error) bool
2828
}
2929

@@ -33,12 +33,12 @@ type Updater struct {
3333
Converter Converter
3434
}
3535

36-
func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, error) {
36+
func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, error) {
3737
conflicts := fieldpath.ManagedFields{}
3838
removed := fieldpath.ManagedFields{}
3939
type Versioned struct {
40-
oldObject typed.TypedValue
41-
newObject typed.TypedValue
40+
oldObject *typed.TypedValue
41+
newObject *typed.TypedValue
4242
}
4343
versions := map[fieldpath.APIVersion]Versioned{
4444
version: Versioned{
@@ -119,7 +119,7 @@ func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpat
119119
// that you intend to persist (after applying the patch if this is for a
120120
// PATCH call), and liveObject must be the original object (empty if
121121
// this is a CREATE call).
122-
func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (fieldpath.ManagedFields, error) {
122+
func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (fieldpath.ManagedFields, error) {
123123
var err error
124124
managers = shallowCopyManagers(managers)
125125
managers, err = s.update(liveObject, newObject, version, managers, manager, true)
@@ -146,7 +146,7 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa
146146
// Apply should be called when Apply is run, given the current object as
147147
// well as the configuration that is applied. This will merge the object
148148
// and return it.
149-
func (s *Updater) Apply(liveObject, configObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (typed.TypedValue, fieldpath.ManagedFields, error) {
149+
func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (*typed.TypedValue, fieldpath.ManagedFields, error) {
150150
managers = shallowCopyManagers(managers)
151151
newObject, err := liveObject.Merge(configObject)
152152
if err != nil {
@@ -185,7 +185,7 @@ func shallowCopyManagers(managers fieldpath.ManagedFields) fieldpath.ManagedFiel
185185
// * applyingManager applied it last time
186186
// * applyingManager didn't apply it this time
187187
// * no other applier claims to manage it
188-
func (s *Updater) prune(merged typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
188+
func (s *Updater) prune(merged *typed.TypedValue, managers fieldpath.ManagedFields, applyingManager string, lastSet *fieldpath.VersionedSet) (*typed.TypedValue, error) {
189189
if lastSet == nil || lastSet.Set.Empty() {
190190
return merged, nil
191191
}
@@ -210,7 +210,7 @@ func (s *Updater) prune(merged typed.TypedValue, managers fieldpath.ManagedField
210210

211211
// addBackOwnedItems adds back any list and map items that were removed by prune,
212212
// but other appliers (or the current applier's new config) claim to own.
213-
func (s *Updater) addBackOwnedItems(merged, pruned typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (typed.TypedValue, error) {
213+
func (s *Updater) addBackOwnedItems(merged, pruned *typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (*typed.TypedValue, error) {
214214
var err error
215215
managedAtVersion := map[fieldpath.APIVersion]*fieldpath.Set{}
216216
for _, managerSet := range managedFields {
@@ -253,7 +253,7 @@ func (s *Updater) addBackOwnedItems(merged, pruned typed.TypedValue, managedFiel
253253
// addBackDanglingItems makes sure that the only items removed by prune are items that were
254254
// previously owned by the currently applying manager. This will add back unowned items and items
255255
// which are owned by Updaters that shouldn't be removed.
256-
func (s *Updater) addBackDanglingItems(merged, pruned typed.TypedValue, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) {
256+
func (s *Updater) addBackDanglingItems(merged, pruned *typed.TypedValue, lastSet *fieldpath.VersionedSet) (*typed.TypedValue, error) {
257257
convertedPruned, err := s.Converter.Convert(pruned, lastSet.APIVersion)
258258
if err != nil {
259259
if s.Converter.IsMissingVersionError(err) {

schema/elements.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ limitations under the License.
1616

1717
package schema
1818

19+
import "sigs.k8s.io/structured-merge-diff/value"
20+
1921
// Schema is a list of named types.
2022
type Schema struct {
2123
Types []TypeDef `yaml:"types,omitempty"`
@@ -77,6 +79,8 @@ const (
7779
// Separable means the items of the container type have no particular
7880
// relationship (default behavior for maps and structs).
7981
Separable = ElementRelationship("separable")
82+
// Deduced only applies to untyped (see the documentation there).
83+
Deduced = ElementRelationship("deduced")
8084
)
8185

8286
// Struct represents a type which is composed of a number of different fields.
@@ -180,17 +184,45 @@ type Map struct {
180184
type Untyped struct {
181185
// ElementRelationship states the relationship between the items, if
182186
// container-typed data happens to be present here.
187+
// * `deduced` implies that the behavior is based on the type of data.
188+
// Structs and maps are both treated as a `separable` Map with `deduced` Untyped elements.
189+
// Lists and Scalars are both treated as an `atomic` Untyped.
183190
// * `atomic` implies that all elements depend on each other, and this
184191
// is effectively a scalar / leaf field; it doesn't make sense for
185192
// separate actors to set the elements.
186193
// TODO: support "guess" (guesses at associative list keys)
187-
// TODO: support "lookup" (calls a lookup function to figure out the
188-
// schema based on the data)
189194
// The default behavior for untyped data is `atomic`; it's permitted to
190195
// leave this unset to get the default behavior.
191196
ElementRelationship ElementRelationship `yaml:"elementRelationship,omitempty"`
192197
}
193198

199+
// DeduceType determines the behavior based on a value.
200+
func DeduceType(v *value.Value) TypeRef {
201+
if v != nil && v.MapValue != nil {
202+
return TypeRef{
203+
Inlined: Atom{
204+
Map: &Map{
205+
ElementType: TypeRef{
206+
Inlined: Atom{
207+
Untyped: &Untyped{
208+
ElementRelationship: Deduced,
209+
},
210+
},
211+
},
212+
ElementRelationship: Separable,
213+
},
214+
},
215+
}
216+
}
217+
return TypeRef{
218+
Inlined: Atom{
219+
Untyped: &Untyped{
220+
ElementRelationship: Atomic,
221+
},
222+
},
223+
}
224+
}
225+
194226
// FindNamedType is a convenience function that returns the referenced TypeDef,
195227
// if it exists, or (nil, false) if it doesn't.
196228
func (s Schema) FindNamedType(name string) (TypeDef, bool) {

0 commit comments

Comments
 (0)