Skip to content

Commit e753b30

Browse files
authored
Merge pull request #25 from ythadhani/merge_overlapping_slices
allow optional merge of overlapping slices
2 parents 16f8b37 + b3e92ac commit e753b30

File tree

2 files changed

+104
-29
lines changed

2 files changed

+104
-29
lines changed

ygot/struct_validation_map.go

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
// Package ygot contains helper methods for dealing with structs that represent
1616
// a YANG schema. Particularly, it takes structs that represent a YANG schema -
1717
// generated by ygen:
18-
// - Provides helper functions which simplify their usage such as functions
19-
// to return pointers to a type.
20-
// - Renders structs to other output formats such as JSON, or gNMI
21-
// notifications.
18+
// - Provides helper functions which simplify their usage such as functions
19+
// to return pointers to a type.
20+
// - Renders structs to other output formats such as JSON, or gNMI
21+
// notifications.
2222
package ygot
2323

2424
import (
@@ -554,6 +554,17 @@ type MergeOverwriteExistingFields struct{}
554554
// IsMergeOpt marks MergeStructOpt as a MergeOpt.
555555
func (*MergeOverwriteExistingFields) IsMergeOpt() {}
556556

557+
// MergeOverlappingSlices is a MergeOpt that allows control of the merge behaviour
558+
// of MergeStructs and MergeStructInto functions.
559+
//
560+
// When used, slices (YANG leaf-lists) with overlapping elements will be merged
561+
// such that only those elements of the source are appended to the destination
562+
// that are not already present.
563+
type MergeOverlappingSlices struct{}
564+
565+
// IsMergeOpt marks MergeStructOpt as a MergeOpt.
566+
func (*MergeOverlappingSlices) IsMergeOpt() {}
567+
557568
// MergeStructs takes two input ValidatedGoStructs and merges their contents,
558569
// returning a new ValidatedGoStruct. If the input structs a and b are of
559570
// different types, an error is returned.
@@ -620,6 +631,18 @@ func fieldOverwriteEnabled(opts []MergeOpt) bool {
620631
return false
621632
}
622633

634+
// mergeOverlappigSlicesEnabled returns true if MergeOverlappingSlices
635+
// is present in the slice of MergeOpt.
636+
func mergeOverlappigSlicesEnabled(opts []MergeOpt) bool {
637+
for _, o := range opts {
638+
switch o.(type) {
639+
case *MergeOverlappingSlices:
640+
return true
641+
}
642+
}
643+
return false
644+
}
645+
623646
// copyStruct copies the fields of srcVal into the dstVal struct in-place.
624647
func copyStruct(dstVal, srcVal reflect.Value, opts ...MergeOpt) error {
625648
if srcVal.Type() != dstVal.Type() {
@@ -870,32 +893,51 @@ func copySliceField(dstField, srcField reflect.Value, opts ...MergeOpt) error {
870893
return nil
871894
}
872895

896+
dstFieldUnqiue := map[interface{}]struct{}{}
873897
if _, ok := srcField.Interface().([]Annotation); !ok {
874898
if reflect.DeepEqual(srcField.Interface(), dstField.Interface()) {
875899
return nil
876900
}
877-
878-
unique, err := uniqueSlices(dstField, srcField)
901+
_, err := validateSlices(dstField, srcField)
879902
if err != nil {
880-
return fmt.Errorf("error checking src and dst for uniqueness, got: %v", err)
903+
return fmt.Errorf("error validating src and dst, got: %v", err)
881904
}
882-
883-
if !unique {
884-
// YANG lists and leaf-lists must be unique.
885-
return fmt.Errorf("source and destination lists must be unique, got src: %v, dst: %v", srcField, dstField)
905+
if !mergeOverlappigSlicesEnabled(opts) {
906+
unique, err := uniqueSlices(dstField, srcField)
907+
if err != nil {
908+
return fmt.Errorf("error checking src and dst for uniqueness, got: %v", err)
909+
}
910+
if !unique {
911+
// YANG lists and leaf-lists must be unique.
912+
return fmt.Errorf("source and destination lists must be unique, got src: %v, dst: %v", srcField, dstField)
913+
}
914+
} else {
915+
for i := 0; i < dstField.Len(); i++ {
916+
dstFieldUnqiue[dstField.Index(i).Interface()] = struct{}{}
917+
}
886918
}
887919
}
888920

889921
if !util.IsTypeStructPtr(srcField.Type().Elem()) {
890922
for i := 0; i < srcField.Len(); i++ {
891923
v := srcField.Index(i)
924+
if mergeOverlappigSlicesEnabled(opts) {
925+
if _, present := dstFieldUnqiue[v.Interface()]; present {
926+
continue
927+
}
928+
}
892929
dstField.Set(reflect.Append(dstField, v))
893930
}
894931
return nil
895932
}
896933

897934
for i := 0; i < srcField.Len(); i++ {
898935
v := srcField.Index(i)
936+
if mergeOverlappigSlicesEnabled(opts) {
937+
if _, present := dstFieldUnqiue[v.Interface()]; present {
938+
continue
939+
}
940+
}
899941
d := reflect.New(v.Type().Elem())
900942
if err := copyStruct(d.Elem(), v.Elem(), opts...); err != nil {
901943
return err
@@ -909,14 +951,6 @@ func copySliceField(dstField, srcField reflect.Value, opts ...MergeOpt) error {
909951
// whether a and b are disjoint. It returns true if the slices have unique
910952
// members, and false if not.
911953
func uniqueSlices(a, b reflect.Value) (bool, error) {
912-
if !util.IsValueSlice(a) || !util.IsValueSlice(b) {
913-
return false, fmt.Errorf("a and b must both be slices, got a: %v, b: %v", a.Type().Kind(), b.Type().Kind())
914-
}
915-
916-
if a.Type().Elem() != b.Type().Elem() {
917-
return false, fmt.Errorf("a and b do not contain the same type, got a: %v, b: %v", a.Type().Elem().Kind(), b.Type().Elem().Kind())
918-
}
919-
920954
for i := 0; i < a.Len(); i++ {
921955
for j := 0; j < b.Len(); j++ {
922956
if reflect.DeepEqual(a.Index(i).Interface(), b.Index(j).Interface()) {
@@ -926,3 +960,13 @@ func uniqueSlices(a, b reflect.Value) (bool, error) {
926960
}
927961
return true, nil
928962
}
963+
964+
func validateSlices(a, b reflect.Value) (bool, error) {
965+
if !util.IsValueSlice(a) || !util.IsValueSlice(b) {
966+
return false, fmt.Errorf("a and b must both be slices, got a: %v, b: %v", a.Type().Kind(), b.Type().Kind())
967+
}
968+
if a.Type().Elem() != b.Type().Elem() {
969+
return false, fmt.Errorf("a and b do not contain the same type, got a: %v, b: %v", a.Type().Elem().Kind(), b.Type().Elem().Kind())
970+
}
971+
return true, nil
972+
}

ygot/struct_validation_map_test.go

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,16 +2756,6 @@ func TestUniqueSlices(t *testing.T) {
27562756
inA: reflect.ValueOf([]int{1, 2, 3}),
27572757
inB: reflect.ValueOf([]int{4, 5, 6}),
27582758
wantUnique: true,
2759-
}, {
2760-
name: "error: mismatched types",
2761-
inA: reflect.ValueOf([]string{"american-dream"}),
2762-
inB: reflect.ValueOf([]int{42}),
2763-
wantErrSubstring: "a and b do not contain the same type",
2764-
}, {
2765-
name: "error: not slices",
2766-
inA: reflect.ValueOf("beer-geek-breakfast"),
2767-
inB: reflect.ValueOf([]string{"beer-mile"}),
2768-
wantErrSubstring: "a and b must both be slices",
27692759
}, {
27702760
name: "not unique, strings",
27712761
inA: reflect.ValueOf([]string{"beobrew-ipa", "berliner-weisse"}),
@@ -2811,3 +2801,44 @@ func TestUniqueSlices(t *testing.T) {
28112801
})
28122802
}
28132803
}
2804+
2805+
func TestValidateSlices(t *testing.T) {
2806+
type stringPtrStruct struct {
2807+
Foo *string
2808+
}
2809+
2810+
type sliceStruct struct {
2811+
Bar []string
2812+
}
2813+
2814+
tests := []struct {
2815+
name string
2816+
inA reflect.Value
2817+
inB reflect.Value
2818+
wantValid bool
2819+
wantErrSubstring string
2820+
}{{
2821+
name: "error: mismatched types",
2822+
inA: reflect.ValueOf([]string{"american-dream"}),
2823+
inB: reflect.ValueOf([]int{42}),
2824+
wantErrSubstring: "a and b do not contain the same type",
2825+
}, {
2826+
name: "error: not slices",
2827+
inA: reflect.ValueOf("beer-geek-breakfast"),
2828+
inB: reflect.ValueOf([]string{"beer-mile"}),
2829+
wantErrSubstring: "a and b must both be slices",
2830+
}}
2831+
2832+
for _, tt := range tests {
2833+
t.Run(tt.name, func(t *testing.T) {
2834+
got, err := validateSlices(tt.inA, tt.inB)
2835+
if diff := errdiff.Substring(err, tt.wantErrSubstring); diff != "" {
2836+
t.Fatalf("%s: validateSlices(%v, %v): did not get expected error, %s", tt.name, tt.inA, tt.inB, diff)
2837+
}
2838+
2839+
if want := tt.wantValid; got != want {
2840+
t.Fatalf("%s: validateSlices(%v, %v): did not get expected status, got: %v, want: %v", tt.name, tt.inA, tt.inB, got, want)
2841+
}
2842+
})
2843+
}
2844+
}

0 commit comments

Comments
 (0)