Skip to content

Commit

Permalink
Merge branch 'master' into best_effort_unmarshal
Browse files Browse the repository at this point in the history
  • Loading branch information
lgomez9 authored Jun 26, 2023
2 parents 35fed0e + a2af562 commit 7cd70bf
Show file tree
Hide file tree
Showing 2 changed files with 241 additions and 31 deletions.
98 changes: 67 additions & 31 deletions gogen/unordered_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,60 @@ func generateGetListKey(buf *bytes.Buffer, s *ygen.ParsedDirectory, nameMap map[
return goKeyMapTemplate.Execute(buf, h)
}

// UnorderedMapTypeName returns the map and key type names of an
// unordered, keyed map given go-generated IR information, as well as whether
// it is a defined type rather than a Go built-in type.
//
// e.g. for a list to be represented as map[string]*Foo, it returns
// "map[string]*Foo", "string", false, nil
func UnorderedMapTypeName(listYANGPath, listFieldName, parentName string, goStructElements map[string]*ygen.ParsedDirectory) (string, string, bool, error) {
// The list itself, since it is a container, has a struct associated with it. Retrieve
// this from the set of Directory structs for which code (a Go struct) will be
// generated such that additional details can be used in the code generation.
listElem, ok := goStructElements[listYANGPath]
if !ok {
return "", "", false, fmt.Errorf("struct for %s did not exist", listYANGPath)
}

var listType, keyType string
var isDefinedType bool
switch len(listElem.ListKeys) {
case 0:
return "", "", false, fmt.Errorf("list does not contain any keys: %s:", listElem.Name)
case 1:
// This is a single keyed list, so we can represent it as a map with
// a simple Go type as the key. Note that a leaf-list can never be
// a key, so we do not need to handle the case whereby we would have to
// have a slice which keys the list.
for _, listKey := range listElem.ListKeys {
listType = fmt.Sprintf("map[%s]*%s", listKey.LangType.NativeType, listElem.Name)
keyType = listKey.LangType.NativeType
isDefinedType = ygen.IsYgenDefinedGoType(listKey.LangType)
}
default:
// This is a list with multiple keys, so we need to generate a new structure
// that represents the list key itself - this struct is described in a
// generatedGoMultiKeyListStruct struct, which is then expanded by a template to the struct
// definition.
listKeyStructName := fmt.Sprintf("%s_Key", listElem.Name)
names := make(map[string]bool, len(goStructElements))
for _, d := range goStructElements {
names[d.Name] = true
}
if names[listKeyStructName] {
listKeyStructName = fmt.Sprintf("%s_%s_YANGListKey", parentName, listFieldName)
if names[listKeyStructName] {
return "", "", false, fmt.Errorf("unexpected generated list key name conflict for %s", listYANGPath)
}
names[listKeyStructName] = true
}
listType = fmt.Sprintf("map[%s]*%s", listKeyStructName, listElem.Name)
keyType = listKeyStructName
isDefinedType = true
}
return listType, keyType, isDefinedType, nil
}

// yangListFieldToGoType takes a yang node description (listField) and returns
// a string corresponding to the Go type that should be used to represent it
// within its parent struct (the parent argument). If applicable, it also
Expand Down Expand Up @@ -555,11 +609,12 @@ func yangListFieldToGoType(listField *ygen.NodeDetails, listFieldName string, pa
return fmt.Sprintf("[]*%s", listElem.Name), nil, nil, nil, nil
}

var listType string
var keyType string
listType, keyType, _, err := UnorderedMapTypeName(listField.YANGDetails.Path, listFieldName, parent.Name, goStructElements)
if err != nil {
return "", nil, nil, nil, err
}
var multiListKey *generatedGoMultiKeyListStruct
var listKeys []goStructField
var listKeyStructName string

shortestPath := func(ss [][]string) [][]string {
var shortest []string
Expand Down Expand Up @@ -594,38 +649,17 @@ func yangListFieldToGoType(listField *ygen.NodeDetails, listFieldName string, pa
}

switch {
case len(listElem.ListKeys) == 1:
// This is a single keyed list, so we can represent it as a map with
// a simple Go type as the key. Note that a leaf-list can never be
// a key, so we do not need to handle the case whereby we would have to
// have a slice which keys the list.
listType = fmt.Sprintf("map[%s]*%s", listKeys[0].Type, listElem.Name)
keyType = listKeys[0].Type
default:
case len(listElem.ListKeys) != 1:
// This is a list with multiple keys, so we need to generate a new structure
// that represents the list key itself - this struct is described in a
// generatedGoMultiKeyListStruct struct, which is then expanded by a template to the struct
// definition.
listKeyStructName = fmt.Sprintf("%s_Key", listElem.Name)
names := make(map[string]bool, len(goStructElements))
for _, d := range goStructElements {
names[d.Name] = true
}
if names[listKeyStructName] {
listKeyStructName = fmt.Sprintf("%s_%s_YANGListKey", parent.Name, listFieldName)
if names[listKeyStructName] {
return "", nil, nil, nil, fmt.Errorf("unexpected generated list key name conflict for %s", listField.YANGDetails.Path)
}
names[listKeyStructName] = true
}
multiListKey = &generatedGoMultiKeyListStruct{
KeyStructName: listKeyStructName,
KeyStructName: keyType,
ParentPath: parent.Path,
ListName: listFieldName,
Keys: listKeys,
}
listType = fmt.Sprintf("map[%s]*%s", listKeyStructName, listElem.Name)
keyType = listKeyStructName
}

var listMethodSpec *generatedGoListMethod
Expand All @@ -648,11 +682,13 @@ func yangListFieldToGoType(listField *ygen.NodeDetails, listFieldName string, pa
// Generate the specification for the methods that should be generated for this
// list, such that this can be handed to the relevant templates to generate code.
listMethodSpec = &generatedGoListMethod{
ListName: listFieldName,
ListType: listElem.Name,
KeyStruct: listKeyStructName,
Keys: listKeys,
Receiver: parent.Name,
ListName: listFieldName,
ListType: listElem.Name,
Keys: listKeys,
Receiver: parent.Name,
}
if multiListKey != nil {
listMethodSpec.KeyStruct = keyType
}
}

Expand Down
174 changes: 174 additions & 0 deletions gogen/unordered_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2023 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gogen

import (
"testing"

"github.com/openconfig/gnmi/errdiff"
"github.com/openconfig/ygot/ygen"
)

func TestUnOrderedKeyedMapTypeName(t *testing.T) {
tests := []struct {
desc string
inListYANGPath string
inListFieldName string
inParentName string
inGoStructElements map[string]*ygen.ParsedDirectory
wantMapName string
wantKeyName string
wantIsDefined bool
wantErrSubstr string
}{{
desc: "single-key",
inListYANGPath: "/foo/bar",
inListFieldName: "Bar",
inParentName: "Foo",
inGoStructElements: map[string]*ygen.ParsedDirectory{
"/foo/bar": {
Name: "Foo_Bar",
ListKeys: map[string]*ygen.ListKey{
"name": {
LangType: &ygen.MappedType{
NativeType: "string",
},
},
},
},
},
wantMapName: "map[string]*Foo_Bar",
wantKeyName: "string",
wantIsDefined: false,
}, {
desc: "multi-key",
inListYANGPath: "/foo/bar",
inListFieldName: "Bar",
inParentName: "Foo",
inGoStructElements: map[string]*ygen.ParsedDirectory{
"/foo/bar": {
Name: "Foo_Bar",
ListKeys: map[string]*ygen.ListKey{
"name": {
LangType: &ygen.MappedType{
NativeType: "string",
},
},
"place": {
LangType: &ygen.MappedType{
NativeType: "string",
},
},
},
},
},
wantMapName: "map[Foo_Bar_Key]*Foo_Bar",
wantKeyName: "Foo_Bar_Key",
wantIsDefined: true,
}, {
desc: "multi-key-with-conflict",
inListYANGPath: "/foo/bar",
inListFieldName: "Bar",
inParentName: "Foo",
inGoStructElements: map[string]*ygen.ParsedDirectory{
"/foo/bar": {
Name: "Foo_Bar",
ListKeys: map[string]*ygen.ListKey{
"name": {
LangType: &ygen.MappedType{
NativeType: "string",
},
},
"place": {
LangType: &ygen.MappedType{
NativeType: "string",
},
},
},
},
"/foo/bar/key": {
Name: "Foo_Bar_Key",
},
},
wantMapName: "map[Foo_Bar_YANGListKey]*Foo_Bar",
wantKeyName: "Foo_Bar_YANGListKey",
wantIsDefined: true,
}, {
desc: "multi-key-with-unresolvable-conflict",
inListYANGPath: "/foo/bar",
inListFieldName: "Bar",
inParentName: "Foo",
inGoStructElements: map[string]*ygen.ParsedDirectory{
"/foo/bar": {
Name: "Foo_Bar",
ListKeys: map[string]*ygen.ListKey{
"name": {
LangType: &ygen.MappedType{
NativeType: "string",
},
},
"place": {
LangType: &ygen.MappedType{
NativeType: "string",
},
},
},
},
"/foo/bar/key": {
Name: "Foo_Bar_Key",
},
"/foo/bar/key/YANGListKey": {
Name: "Foo_Bar_YANGListKey",
},
},
wantErrSubstr: "unexpected generated list key name conflict",
}, {
desc: "error-list-not-found",
inListYANGPath: "/foo/bar",
inListFieldName: "Bar",
inParentName: "Foo",
inGoStructElements: map[string]*ygen.ParsedDirectory{},
wantErrSubstr: "did not exist",
}, {
desc: "error-unkeyed-list",
inListYANGPath: "/foo/bar",
inListFieldName: "Bar",
inParentName: "Foo",
inGoStructElements: map[string]*ygen.ParsedDirectory{
"/foo/bar": {
Name: "Foo_Bar",
},
},
wantErrSubstr: "list does not contain any keys",
}}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
gotMapName, gotKeyName, gotIsDefined, err := UnorderedMapTypeName(tt.inListYANGPath, tt.inListFieldName, tt.inParentName, tt.inGoStructElements)
if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" {
t.Fatalf("did not get expected error, %s", diff)
}
if gotMapName != tt.wantMapName {
t.Errorf("map name: got %q, want %q", gotMapName, tt.wantMapName)
}
if gotKeyName != tt.wantKeyName {
t.Errorf("key name: got %q, want %q", gotKeyName, tt.wantKeyName)
}
if gotIsDefined != tt.wantIsDefined {
t.Errorf("map name: got %v, want %v", gotIsDefined, tt.wantIsDefined)
}
})
}
}

0 comments on commit 7cd70bf

Please sign in to comment.