From a2af562646b2a9a597c362c20d21eef57a4b0114 Mon Sep 17 00:00:00 2001 From: Wen Bo Li <50884368+wenovus@users.noreply.github.com> Date: Fri, 23 Jun 2023 14:30:56 -0700 Subject: [PATCH] Export function for determining name of a keyed, unordered map. (#882) * Export function for determining name of keyed, unordered map. * simplify name --- gogen/unordered_list.go | 98 +++++++++++++------- gogen/unordered_list_test.go | 174 +++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+), 31 deletions(-) create mode 100644 gogen/unordered_list_test.go diff --git a/gogen/unordered_list.go b/gogen/unordered_list.go index d722c90d..0c1b4c59 100644 --- a/gogen/unordered_list.go +++ b/gogen/unordered_list.go @@ -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 @@ -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 @@ -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 @@ -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 } } diff --git a/gogen/unordered_list_test.go b/gogen/unordered_list_test.go new file mode 100644 index 00000000..62c6d79f --- /dev/null +++ b/gogen/unordered_list_test.go @@ -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) + } + }) + } +}