Skip to content

Commit 8c252eb

Browse files
JonasKsTerjeLafton
andcommitted
feat: support for diffing presence containers
Co-authored-by: Terje Lafton <terje@lafton.io>
1 parent 100bd44 commit 8c252eb

File tree

9 files changed

+75
-2
lines changed

9 files changed

+75
-2
lines changed

gogen/genir_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,7 @@ func TestGenerateIR(t *testing.T) {
13011301
DefiningModule: "openconfig-complex",
13021302
TelemetryAtomic: true,
13031303
CompressedTelemetryAtomic: false,
1304+
PresenceContainer: true,
13041305
},
13051306
"/openconfig-complex/model": {
13061307
Name: "Model",

gogen/gogen.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,12 @@ func (t *{{ .ParentReceiver }}) To_{{ .Name }}(i interface{}) ({{ .Name }}, erro
838838
{{- end -}}
839839
]", i, i)
840840
}
841+
`)
842+
// presenceMethodTemplate provides a template to output a method
843+
// indicating this is a presence container
844+
presenceMethodTemplate = mustMakeTemplate("presenceMethodTemplate", `
845+
// IsPresence returns nothing, but indicates that the receiver is a presence container.
846+
func (t *{{ .StructName }}) IsPresence() {}
841847
`)
842848
)
843849

@@ -1403,6 +1409,14 @@ func writeGoStruct(targetStruct *ygen.ParsedDirectory, goStructElements map[stri
14031409
errs = append(errs, err)
14041410
}
14051411

1412+
if goOpts.AddYangPresence {
1413+
if targetStruct.PresenceContainer {
1414+
if err := presenceMethodTemplate.Execute(&methodBuf, structDef); err != nil {
1415+
errs = append(errs, err)
1416+
}
1417+
}
1418+
}
1419+
14061420
return GoStructCodeSnippet{
14071421
StructName: structDef.StructName,
14081422
StructDef: structBuf.String(),

gogen/testdata/structs/presence-container-example.formatted-txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ func (*PresenceContainerExample_Parent_Child) ΛBelongingModule() string {
152152
return "presence-container-example"
153153
}
154154

155+
// IsPresence returns nothing, but indicates that the receiver is a presence container.
156+
func (t *PresenceContainerExample_Parent_Child) IsPresence() {}
157+
155158
// PresenceContainerExample_Parent_Child_Config represents the /presence-container-example/parent/child/config YANG schema element.
156159
type PresenceContainerExample_Parent_Child_Config struct {
157160
Four Binary `path:"four" module:"presence-container-example"`

protogen/genir_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func protoIR(nestedDirectories bool) *ygen.IR {
104104
DefiningModule: "openconfig-complex",
105105
TelemetryAtomic: true,
106106
CompressedTelemetryAtomic: false,
107+
PresenceContainer: true,
107108
},
108109
"/openconfig-complex/model": {
109110
Name: "Model",

ygen/directory.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,16 @@ func getOrderedDirDetails(langMapper LangMapper, directory map[string]*Directory
193193
}
194194
default:
195195
pd.Type = Container
196+
if len(dir.Entry.Extra["presence"]) > 0 {
197+
if v := dir.Entry.Extra["presence"][0].(*yang.Value); v != nil {
198+
pd.PresenceContainer = true
199+
} else {
200+
return nil, fmt.Errorf(
201+
"unable to retrieve presence statement, expected non-nil *yang.Value, got %v",
202+
dir.Entry.Extra["presence"][0],
203+
)
204+
}
205+
}
196206
}
197207

198208
for i, entry := 0, dir.Entry; ; i++ {

ygen/ir.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,8 @@ type ParsedDirectory struct {
468468
//
469469
// https://github.com/openconfig/public/blob/master/release/models/openconfig-extensions.yang#L154
470470
CompressedTelemetryAtomic bool
471+
// PresenceContainer indicates that this container is a YANG presence container
472+
PresenceContainer bool
471473
}
472474

473475
// OrderedFieldNames returns the YANG name of all fields belonging to the

ygot/diff.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ func findSetLeaves(s GoStruct, orderedMapAsLeaf bool, opts ...DiffOpt) (map[*pat
254254
return
255255
}
256256

257+
isYangPresence := hasRespectPresenceContainers(opts) != nil && util.IsYangPresence(ni.StructField)
258+
257259
var sp [][]string
258260
if pathOpt != nil && pathOpt.PreferShadowPath {
259261
// Try the shadow-path tag first to see if it exists.
@@ -313,9 +315,10 @@ func findSetLeaves(s GoStruct, orderedMapAsLeaf bool, opts ...DiffOpt) (map[*pat
313315
// Ignore structs unless it is an ordered map and we're
314316
// treating it as a leaf (since it is assumed to be
315317
// telemetry-atomic in order to preserve ordering of entries).
316-
if (!isOrderedMap || !orderedMapAsLeaf) && util.IsValueStructPtr(ni.FieldValue) {
318+
if util.IsValueStructPtr(ni.FieldValue) && (!isOrderedMap || !orderedMapAsLeaf) && !isYangPresence {
317319
return
318320
}
321+
319322
if isOrderedMap && orderedMap.Len() == 0 {
320323
return
321324
}
@@ -335,7 +338,15 @@ func findSetLeaves(s GoStruct, orderedMapAsLeaf bool, opts ...DiffOpt) (map[*pat
335338
}
336339

337340
outs := out.(map[*pathSpec]interface{})
338-
outs[vp] = ival
341+
if isYangPresence {
342+
// If the current field is tagged as a presence container,
343+
// we set it's value to `nil` instead of returning earlier.
344+
// This is because empty presence containers has a meaning,
345+
// unlike a normal container.
346+
outs[vp] = nil
347+
} else {
348+
outs[vp] = ival
349+
}
339350

340351
if isOrderedMap && orderedMapAsLeaf {
341352
// We treat the ordered map as a leaf, so don't
@@ -426,6 +437,24 @@ func hasIgnoreAdditions(opts []DiffOpt) *IgnoreAdditions {
426437
return nil
427438
}
428439

440+
// The RespectPresenceContainers DiffOpt indicates that presence containers
441+
// should be respected in the diff output.
442+
// This option was added to ensure we do not break backward compatibility.
443+
type WithRespectPresenceContainers struct{}
444+
445+
func (*WithRespectPresenceContainers) IsDiffOpt() {}
446+
447+
// hasIgnoreAdditions returns the first IgnoreAdditions from an opts slice, or
448+
// nil if there isn't one.
449+
func hasRespectPresenceContainers(opts []DiffOpt) *WithRespectPresenceContainers {
450+
for _, o := range opts {
451+
if rp, ok := o.(*WithRespectPresenceContainers); ok {
452+
return rp
453+
}
454+
}
455+
return nil
456+
}
457+
429458
// DiffPathOpt is a DiffOpt that allows control of the path behaviour of the
430459
// Diff function.
431460
type DiffPathOpt struct {

ygot/render.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,11 @@ func marshalStructOrOrderedList(s any, enc gnmipb.Encoding, cfg *RFC7951JSONConf
918918
if reflect.ValueOf(s).IsNil() {
919919
return nil, nil
920920
}
921+
// A presence container might not be empty, but we should still
922+
// treat it as such
923+
if _, ok := s.(PresenceContainer); ok {
924+
return nil, nil
925+
}
921926

922927
var (
923928
j any

ygot/types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ import (
1919
"reflect"
2020
)
2121

22+
// PresenceContainer is an interface which can be implemented by Go structs that are
23+
// generated to represent a YANG presence container.
24+
type PresenceContainer interface {
25+
// IsPresence is a marker method that indicates that the struct
26+
// implements the PresenceContainer interface.
27+
IsPresence()
28+
}
29+
2230
// GoStruct is an interface which can be implemented by Go structs that are
2331
// generated to represent a YANG container or list member. It simply allows
2432
// handling code to ensure that it is interacting with a struct that will meet

0 commit comments

Comments
 (0)