Skip to content

Commit 2b68a64

Browse files
authored
Add MergeOpt MergeEmptyMaps to allow an empty map to be merged. (#685)
Some users may want to use this as a different meaning. Added the following note in the doc comments: ``` // NOTE: Since YANG doesn't distinguish between a nil map and an empty map, // please consider another approach before using this option. ```
1 parent 7f2d6dc commit 2b68a64

File tree

2 files changed

+116
-11
lines changed

2 files changed

+116
-11
lines changed

ygot/struct_validation_map.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,20 @@ type MergeOverwriteExistingFields struct{}
559559
// IsMergeOpt marks MergeStructOpt as a MergeOpt.
560560
func (*MergeOverwriteExistingFields) IsMergeOpt() {}
561561

562+
// MergeEmptyMaps is a MergeOpt that allows control of the merge behaviour
563+
// of MergeStructs and MergeStructInto functions.
564+
//
565+
// When used, if both the destination struct and the source struct has an empty
566+
// map field, but it is non-nil in either one, then that map field in the
567+
// destination will always be populated with an empty, non-nil map value.
568+
//
569+
// NOTE: Since YANG doesn't distinguish between a nil map and an empty map,
570+
// please consider another approach before using this option.
571+
type MergeEmptyMaps struct{}
572+
573+
// IsMergeOpt marks MergeEmptyMaps as a MergeOpt.
574+
func (*MergeEmptyMaps) IsMergeOpt() {}
575+
562576
// MergeStructs takes two input GoStruct and merges their contents,
563577
// returning a new GoStruct. If the input structs a and b are of
564578
// different types, an error is returned.
@@ -622,6 +636,18 @@ func fieldOverwriteEnabled(opts []MergeOpt) bool {
622636
return false
623637
}
624638

639+
// mergeEmptyMapsEnabled returns true if MergeEmptyMaps
640+
// is present in the slice of MergeOpt.
641+
func mergeEmptyMapsEnabled(opts []MergeOpt) bool {
642+
for _, o := range opts {
643+
switch o.(type) {
644+
case *MergeEmptyMaps:
645+
return true
646+
}
647+
}
648+
return false
649+
}
650+
625651
// copyStruct copies the fields of srcVal into the dstVal struct in-place.
626652
func copyStruct(dstVal, srcVal reflect.Value, opts ...MergeOpt) error {
627653
if srcVal.Type() != dstVal.Type() {
@@ -795,8 +821,11 @@ func copyMapField(dstField, srcField reflect.Value, opts ...MergeOpt) error {
795821
}
796822

797823
// Skip cases where there are empty maps in both src and dst.
824+
// Exception: user wants an empty map to be merged as well.
798825
if srcField.Len() == 0 && dstField.Len() == 0 {
799-
return nil
826+
if !mergeEmptyMapsEnabled(opts) || srcField.IsNil() {
827+
return nil
828+
}
800829
}
801830

802831
m, err := validateMap(srcField, dstField)

ygot/struct_validation_map_test.go

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,17 +1839,93 @@ func TestCopyStruct(t *testing.T) {
18391839
inSrc: &copyTest{StringSlice: []string{"mikkeler-draft-bear"}},
18401840
inDst: &copyTest{StringSlice: []string{"mikkeler-draft-bear"}},
18411841
wantDst: &copyTest{StringSlice: []string{"mikkeler-draft-bear"}},
1842-
},
1843-
{
1844-
name: "overwrite, slice fields not unique",
1845-
inSrc: &copyTest{StringSlice: []string{"mikkeler-draft-bear"}},
1846-
inDst: &copyTest{StringSlice: []string{"kingfisher"}},
1847-
inOpts: []MergeOpt{
1848-
&MergeOverwriteExistingFields{},
1842+
}, {
1843+
name: "overwrite, slice fields not unique",
1844+
inSrc: &copyTest{StringSlice: []string{"mikkeler-draft-bear"}},
1845+
inDst: &copyTest{StringSlice: []string{"kingfisher"}},
1846+
inOpts: []MergeOpt{
1847+
&MergeOverwriteExistingFields{},
1848+
},
1849+
wantDst: &copyTest{StringSlice: []string{"kingfisher", "mikkeler-draft-bear"}},
1850+
}, {
1851+
name: "dst struct pointer with no populated field",
1852+
inSrc: &copyTest{
1853+
StringField: String("lagunitas-aunt-sally"),
1854+
StructPointer: &copyTest{
1855+
StructPointer: &copyTest{},
18491856
},
1850-
wantDst: &copyTest{StringSlice: []string{"kingfisher", "mikkeler-draft-bear"}},
18511857
},
1852-
}
1858+
inDst: &copyTest{},
1859+
wantDst: &copyTest{
1860+
StringField: String("lagunitas-aunt-sally"),
1861+
StructPointer: &copyTest{
1862+
StructPointer: &copyTest{},
1863+
},
1864+
},
1865+
}, {
1866+
name: "src struct pointer with no populated field",
1867+
inSrc: &copyTest{},
1868+
inDst: &copyTest{
1869+
StringField: String("lagunitas-aunt-sally"),
1870+
StructPointer: &copyTest{
1871+
StructPointer: &copyTest{},
1872+
},
1873+
},
1874+
wantDst: &copyTest{
1875+
StringField: String("lagunitas-aunt-sally"),
1876+
StructPointer: &copyTest{
1877+
StructPointer: &copyTest{},
1878+
},
1879+
},
1880+
}, {
1881+
name: "dst single-key map with no elements",
1882+
inSrc: &copyTest{
1883+
StringMap: map[string]*copyTest{},
1884+
},
1885+
inDst: &copyTest{},
1886+
inOpts: []MergeOpt{
1887+
&MergeEmptyMaps{},
1888+
},
1889+
wantDst: &copyTest{
1890+
StringMap: map[string]*copyTest{},
1891+
},
1892+
}, {
1893+
name: "dst single-key map with no elements",
1894+
inSrc: &copyTest{},
1895+
inDst: &copyTest{
1896+
StringMap: map[string]*copyTest{},
1897+
},
1898+
inOpts: []MergeOpt{
1899+
&MergeEmptyMaps{},
1900+
},
1901+
wantDst: &copyTest{
1902+
StringMap: map[string]*copyTest{},
1903+
},
1904+
}, {
1905+
name: "dst struct map with no elements",
1906+
inSrc: &copyTest{
1907+
StructMap: map[copyMapKey]*copyTest{},
1908+
},
1909+
inDst: &copyTest{},
1910+
inOpts: []MergeOpt{
1911+
&MergeEmptyMaps{},
1912+
},
1913+
wantDst: &copyTest{
1914+
StructMap: map[copyMapKey]*copyTest{},
1915+
},
1916+
}, {
1917+
name: "src struct map with no elements",
1918+
inSrc: &copyTest{},
1919+
inDst: &copyTest{
1920+
StructMap: map[copyMapKey]*copyTest{},
1921+
},
1922+
inOpts: []MergeOpt{
1923+
&MergeEmptyMaps{},
1924+
},
1925+
wantDst: &copyTest{
1926+
StructMap: map[copyMapKey]*copyTest{},
1927+
},
1928+
}}
18531929

18541930
for _, tt := range tests {
18551931
dst, src := reflect.ValueOf(tt.inDst).Elem(), reflect.ValueOf(tt.inSrc).Elem()
@@ -1867,7 +1943,7 @@ func TestCopyStruct(t *testing.T) {
18671943
continue
18681944
}
18691945

1870-
if diff := pretty.Compare(dst.Interface(), wantDst.Interface()); diff != "" {
1946+
if diff := cmp.Diff(dst.Interface(), wantDst.Interface()); diff != "" {
18711947
t.Errorf("%s: copyStruct(%v, %v): did not get expected copied struct, diff(-got,+want):\n%s", tt.name, tt.inSrc, tt.inDst, diff)
18721948
}
18731949
}

0 commit comments

Comments
 (0)