Skip to content

Commit ab3023e

Browse files
apelisseAntoine Pelisse
authored andcommitted
Make TypedValue into an interface
This will make it easier to have another TypedValue for deduced type that don't have to share the exact same logic, for merging and building the set.
1 parent c96790a commit ab3023e

File tree

5 files changed

+120
-110
lines changed

5 files changed

+120
-110
lines changed

internal/fixture/state.go

Lines changed: 6 additions & 6 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
@@ -91,7 +91,7 @@ func (s *State) checkInit() error {
9191
if err != nil {
9292
return fmt.Errorf("failed to create new empty object: %v", err)
9393
}
94-
s.Live = &obj
94+
s.Live = obj
9595
}
9696
return nil
9797
}
@@ -103,11 +103,11 @@ func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, manag
103103
return err
104104
}
105105
tv, err := s.Parser.FromYAML(obj)
106-
managers, err := s.Updater.Update(*s.Live, tv, version, s.Managers, manager)
106+
managers, err := s.Updater.Update(s.Live, tv, version, s.Managers, manager)
107107
if err != nil {
108108
return err
109109
}
110-
s.Live = &tv
110+
s.Live = tv
111111
s.Managers = managers
112112

113113
return nil
@@ -123,11 +123,11 @@ func (s *State) Apply(obj typed.YAMLObject, version fieldpath.APIVersion, manage
123123
if err != nil {
124124
return err
125125
}
126-
new, managers, err := s.Updater.Apply(*s.Live, tv, version, s.Managers, manager, force)
126+
new, managers, err := s.Updater.Apply(s.Live, tv, version, s.Managers, manager, force)
127127
if err != nil {
128128
return err
129129
}
130-
s.Live = &new
130+
s.Live = new
131131
s.Managers = managers
132132

133133
return nil

merge/update.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,18 +127,18 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa
127127
func (s *Updater) Apply(liveObject, configObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (typed.TypedValue, fieldpath.ManagedFields, error) {
128128
newObject, err := liveObject.Merge(configObject)
129129
if err != nil {
130-
return typed.TypedValue{}, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
130+
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
131131
}
132132
managers, err = s.update(liveObject, newObject, version, managers, manager, force)
133133
if err != nil {
134-
return typed.TypedValue{}, fieldpath.ManagedFields{}, err
134+
return nil, fieldpath.ManagedFields{}, err
135135
}
136136

137137
// TODO: Remove unconflicting removed fields
138138

139139
set, err := configObject.ToFieldSet()
140140
if err != nil {
141-
return typed.TypedValue{}, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
141+
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
142142
}
143143
managers[manager] = &fieldpath.VersionedSet{
144144
Set: set,

typed/parser.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func (p *parseableType) IsValid() bool {
100100
func (p *parseableType) FromYAML(object YAMLObject) (TypedValue, error) {
101101
v, err := value.FromYAML([]byte(object))
102102
if err != nil {
103-
return TypedValue{}, err
103+
return nil, err
104104
}
105105
return AsTyped(v, &p.parser.Schema, p.typename)
106106
}
@@ -110,7 +110,7 @@ func (p *parseableType) FromYAML(object YAMLObject) (TypedValue, error) {
110110
func (p *parseableType) FromUnstructured(in interface{}) (TypedValue, error) {
111111
v, err := value.FromUnstructured(in)
112112
if err != nil {
113-
return TypedValue{}, err
113+
return nil, err
114114
}
115115
return AsTyped(v, &p.parser.Schema, p.typename)
116116
}
@@ -131,7 +131,7 @@ func (p DeducedParseableType) IsValid() bool {
131131
func (p DeducedParseableType) FromYAML(object YAMLObject) (TypedValue, error) {
132132
v, err := value.FromYAML([]byte(object))
133133
if err != nil {
134-
return TypedValue{}, err
134+
return nil, err
135135
}
136136
return AsTypedDeduced(v), nil
137137
}
@@ -141,7 +141,7 @@ func (p DeducedParseableType) FromYAML(object YAMLObject) (TypedValue, error) {
141141
func (p DeducedParseableType) FromUnstructured(in interface{}) (TypedValue, error) {
142142
v, err := value.FromUnstructured(in)
143143
if err != nil {
144-
return TypedValue{}, err
144+
return nil, err
145145
}
146146
return AsTypedDeduced(v), nil
147147
}

typed/typed.go

Lines changed: 106 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -25,55 +25,85 @@ import (
2525
"sigs.k8s.io/structured-merge-diff/value"
2626
)
2727

28-
// TypedValue is a value of some specific type.
29-
type TypedValue struct {
30-
value value.Value
31-
typeRef schema.TypeRef
32-
schema *schema.Schema
28+
// TypedValue is a value with an associated type.
29+
type TypedValue interface {
30+
// AsValue removes the type from the TypedValue and only keeps the value.
31+
AsValue() *value.Value
32+
// Validate returns an error with a list of every spec violation.
33+
Validate() error
34+
// ToFieldSet creates a set containing every leaf field mentioned, or
35+
// validation errors, if any were encountered.
36+
ToFieldSet() (*fieldpath.Set, error)
37+
// Merge returns the result of merging tv and pso ("partially specified
38+
// object") together. Of note:
39+
// * No fields can be removed by this operation.
40+
// * If both tv and pso specify a given leaf field, the result will keep pso's
41+
// value.
42+
// * Container typed elements will have their items ordered:
43+
// * like tv, if pso doesn't change anything in the container
44+
// * like pso, if pso does change something in the container.
45+
// tv and pso must both be of the same type (their Schema and TypeRef must
46+
// match), or an error will be returned. Validation errors will be returned if
47+
// the objects don't conform to the schema.
48+
Merge(pso TypedValue) (TypedValue, error)
49+
// Compare compares the two objects. See the comments on the `Comparison`
50+
// struct for details on the return value.
51+
//
52+
// tv and rhs must both be of the same type (their Schema and TypeRef must
53+
// match), or an error will be returned. Validation errors will be returned if
54+
// the objects don't conform to the schema.
55+
Compare(rhs TypedValue) (c *Comparison, err error)
3356
}
3457

3558
// AsTyped accepts a value and a type and returns a TypedValue. 'v' must have
3659
// type 'typeName' in the schema. An error is returned if the v doesn't conform
3760
// to the schema.
3861
func AsTyped(v value.Value, s *schema.Schema, typeName string) (TypedValue, error) {
39-
tv := TypedValue{
62+
tv := typedValue{
4063
value: v,
4164
typeRef: schema.TypeRef{NamedType: &typeName},
4265
schema: s,
4366
}
4467
if err := tv.Validate(); err != nil {
45-
return TypedValue{}, err
68+
return nil, err
4669
}
4770
return tv, nil
4871
}
4972

50-
// AsTypedDeduced is going to generate it's own type definition based on
51-
// the content of the object. This is useful for CRDs that don't have a
52-
// validation field.
53-
func AsTypedDeduced(v value.Value) TypedValue {
54-
return TypedValue{
55-
value: v,
56-
typeRef: schema.TypeRefFromValue(v),
57-
schema: nil,
73+
// AsTypeUnvalidated is just like AsTyped, but doesn't validate that the type
74+
// conforms to the schema, for cases where that has already been checked or
75+
// where you're going to call a method that validates as a side-effect (like
76+
// ToFieldSet).
77+
func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeName string) TypedValue {
78+
tv := typedValue{
79+
value: v,
80+
typeRef: schema.TypeRef{NamedType: &typeName},
81+
schema: s,
5882
}
83+
return tv
84+
}
85+
86+
// typedValue is a value of some specific type.
87+
type typedValue struct {
88+
value value.Value
89+
typeRef schema.TypeRef
90+
schema *schema.Schema
5991
}
6092

61-
// AsValue removes the type from the TypedValue and only keeps the value.
62-
func (tv TypedValue) AsValue() *value.Value {
93+
var _ TypedValue = typedValue{}
94+
95+
func (tv typedValue) AsValue() *value.Value {
6396
return &tv.value
6497
}
6598

66-
// Validate returns an error with a list of every spec violation.
67-
func (tv TypedValue) Validate() error {
99+
func (tv typedValue) Validate() error {
68100
if errs := tv.walker().validate(); len(errs) != 0 {
69101
return errs
70102
}
71103
return nil
72104
}
73105

74-
// ToFieldSet creates a set containing every leaf field mentioned in tv, or
75-
// validation errors, if any were encountered.
76-
func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) {
106+
func (tv typedValue) ToFieldSet() (*fieldpath.Set, error) {
77107
s := fieldpath.NewSet()
78108
w := tv.walker()
79109
w.leafFieldCallback = func(p fieldpath.Path) { s.Insert(p) }
@@ -83,73 +113,27 @@ func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) {
83113
return s, nil
84114
}
85115

86-
// Merge returns the result of merging tv and pso ("partially specified
87-
// object") together. Of note:
88-
// * No fields can be removed by this operation.
89-
// * If both tv and pso specify a given leaf field, the result will keep pso's
90-
// value.
91-
// * Container typed elements will have their items ordered:
92-
// * like tv, if pso doesn't change anything in the container
93-
// * like pso, if pso does change something in the container.
94-
// tv and pso must both be of the same type (their Schema and TypeRef must
95-
// match), or an error will be returned. Validation errors will be returned if
96-
// the objects don't conform to the schema.
97-
func (tv TypedValue) Merge(pso TypedValue) (TypedValue, error) {
98-
return merge(tv, pso, ruleKeepRHS, nil)
99-
}
100-
101-
// Comparison is the return value of a TypedValue.Compare() operation.
102-
//
103-
// No field will appear in more than one of the three fieldsets. If all of the
104-
// fieldsets are empty, then the objects must have been equal.
105-
type Comparison struct {
106-
// Merged is the result of merging the two objects, as explained in the
107-
// comments on TypedValue.Merge().
108-
Merged TypedValue
109-
110-
// Removed contains any fields removed by rhs (the right-hand-side
111-
// object in the comparison).
112-
Removed *fieldpath.Set
113-
// Modified contains fields present in both objects but different.
114-
Modified *fieldpath.Set
115-
// Added contains any fields added by rhs.
116-
Added *fieldpath.Set
117-
}
118-
119-
// IsSame returns true if the comparison returned no changes (the two
120-
// compared objects are similar).
121-
func (c *Comparison) IsSame() bool {
122-
return c.Removed.Empty() && c.Modified.Empty() && c.Added.Empty()
123-
}
124-
125-
// String returns a human readable version of the comparison.
126-
func (c *Comparison) String() string {
127-
str := fmt.Sprintf("- Merged Object:\n%v\n", c.Merged.AsValue())
128-
if !c.Modified.Empty() {
129-
str += fmt.Sprintf("- Modified Fields:\n%v\n", c.Modified)
130-
}
131-
if !c.Added.Empty() {
132-
str += fmt.Sprintf("- Added Fields:\n%v\n", c.Added)
116+
func (tv typedValue) Merge(pso TypedValue) (TypedValue, error) {
117+
tpso, ok := pso.(typedValue)
118+
if !ok {
119+
return nil, errorFormatter{}.
120+
errorf("can't merge typedValue with %T", pso)
133121
}
134-
if !c.Removed.Empty() {
135-
str += fmt.Sprintf("- Removed Fields:\n%v\n", c.Removed)
136-
}
137-
return str
122+
return merge(tv, tpso, ruleKeepRHS, nil)
138123
}
139124

140-
// Compare compares the two objects. See the comments on the `Comparison`
141-
// struct for details on the return value.
142-
//
143-
// tv and rhs must both be of the same type (their Schema and TypeRef must
144-
// match), or an error will be returned. Validation errors will be returned if
145-
// the objects don't conform to the schema.
146-
func (tv TypedValue) Compare(rhs TypedValue) (c *Comparison, err error) {
125+
func (tv typedValue) Compare(rhs TypedValue) (c *Comparison, err error) {
126+
trhs, ok := rhs.(typedValue)
127+
if !ok {
128+
return nil, errorFormatter{}.
129+
errorf("can't compare typedValue with %T", rhs)
130+
}
147131
c = &Comparison{
148132
Removed: fieldpath.NewSet(),
149133
Modified: fieldpath.NewSet(),
150134
Added: fieldpath.NewSet(),
151135
}
152-
c.Merged, err = merge(tv, rhs, func(w *mergingWalker) {
136+
c.Merged, err = merge(tv, trhs, func(w *mergingWalker) {
153137
if w.lhs == nil {
154138
c.Added.Insert(w.path)
155139
} else if w.rhs == nil {
@@ -175,13 +159,13 @@ func (tv TypedValue) Compare(rhs TypedValue) (c *Comparison, err error) {
175159
return c, nil
176160
}
177161

178-
func merge(lhs, rhs TypedValue, rule, postRule mergeRule) (TypedValue, error) {
162+
func merge(lhs, rhs typedValue, rule, postRule mergeRule) (TypedValue, error) {
179163
if lhs.schema != rhs.schema {
180-
return TypedValue{}, errorFormatter{}.
164+
return nil, errorFormatter{}.
181165
errorf("expected objects with types from the same schema")
182166
}
183167
if !reflect.DeepEqual(lhs.typeRef, rhs.typeRef) {
184-
return TypedValue{}, errorFormatter{}.
168+
return nil, errorFormatter{}.
185169
errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
186170
}
187171

@@ -195,10 +179,10 @@ func merge(lhs, rhs TypedValue, rule, postRule mergeRule) (TypedValue, error) {
195179
}
196180
errs := mw.merge()
197181
if len(errs) > 0 {
198-
return TypedValue{}, errs
182+
return nil, errs
199183
}
200184

201-
out := TypedValue{
185+
out := typedValue{
202186
schema: lhs.schema,
203187
typeRef: lhs.typeRef,
204188
}
@@ -210,15 +194,41 @@ func merge(lhs, rhs TypedValue, rule, postRule mergeRule) (TypedValue, error) {
210194
return out, nil
211195
}
212196

213-
// AsTypeUnvalidated is just like WithType, but doesn't validate that the type
214-
// conforms to the schema, for cases where that has already been checked or
215-
// where you're going to call a method that validates as a side-effect (like
216-
// ToFieldSet).
217-
func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeName string) TypedValue {
218-
tv := TypedValue{
219-
value: v,
220-
typeRef: schema.TypeRef{NamedType: &typeName},
221-
schema: s,
197+
// Comparison is the return value of a TypedValue.Compare() operation.
198+
//
199+
// No field will appear in more than one of the three fieldsets. If all of the
200+
// fieldsets are empty, then the objects must have been equal.
201+
type Comparison struct {
202+
// Merged is the result of merging the two objects, as explained in the
203+
// comments on TypedValue.Merge().
204+
Merged TypedValue
205+
206+
// Removed contains any fields removed by rhs (the right-hand-side
207+
// object in the comparison).
208+
Removed *fieldpath.Set
209+
// Modified contains fields present in both objects but different.
210+
Modified *fieldpath.Set
211+
// Added contains any fields added by rhs.
212+
Added *fieldpath.Set
213+
}
214+
215+
// IsSame returns true if the comparison returned no changes (the two
216+
// compared objects are similar).
217+
func (c *Comparison) IsSame() bool {
218+
return c.Removed.Empty() && c.Modified.Empty() && c.Added.Empty()
219+
}
220+
221+
// String returns a human readable version of the comparison.
222+
func (c *Comparison) String() string {
223+
str := fmt.Sprintf("- Merged Object:\n%v\n", c.Merged.AsValue())
224+
if !c.Modified.Empty() {
225+
str += fmt.Sprintf("- Modified Fields:\n%v\n", c.Modified)
222226
}
223-
return tv
227+
if !c.Added.Empty() {
228+
str += fmt.Sprintf("- Added Fields:\n%v\n", c.Added)
229+
}
230+
if !c.Removed.Empty() {
231+
str += fmt.Sprintf("- Removed Fields:\n%v\n", c.Removed)
232+
}
233+
return str
224234
}

typed/validate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"sigs.k8s.io/structured-merge-diff/value"
2323
)
2424

25-
func (tv TypedValue) walker() *validatingObjectWalker {
25+
func (tv typedValue) walker() *validatingObjectWalker {
2626
return &validatingObjectWalker{
2727
value: tv.value,
2828
schema: tv.schema,

0 commit comments

Comments
 (0)