From 6ea5f9da76053f12ba303ef349e2b67e4ab3af7a Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 11 Mar 2024 12:42:20 +0100 Subject: [PATCH 01/22] add EntryXml struct, render all missing functions for entry.go, extend functions used in templates --- pkg/generate/generator.go | 14 +++--- pkg/translate/funcs.go | 20 ++++++++ pkg/translate/funcs_test.go | 56 +++++++++++++++++++++ pkg/translate/structs.go | 29 +++++++++++ pkg/translate/structs_test.go | 68 +++++++++++++++++++++++++ templates/sdk/entry.tmpl | 94 ++++++++++++++++++++++++++++++++++- 6 files changed, 274 insertions(+), 7 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 62224c90..734f763a 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -60,7 +60,6 @@ func (c *Creator) RenderTemplate() error { return nil } - func (c *Creator) createFullFilePath(templateName string) string { fileBaseName := strings.TrimSuffix(templateName, filepath.Ext(templateName)) return filepath.Join(c.GoOutputDir, filepath.Join(c.Spec.GoSdkPath...), fileBaseName+".go") @@ -99,14 +98,17 @@ func (c *Creator) createFile(filePath string) (*os.File, error) { return outputFile, nil } - func (c *Creator) parseTemplate(templateName string) (*template.Template, error) { templatePath := filepath.Join(c.TemplatesDir, templateName) funcMap := template.FuncMap{ - "packageName": translate.PackageName, - "locationType": translate.LocationType, - "specParamType": translate.SpecParamType, - "omitEmpty": translate.OmitEmpty, + "packageName": translate.PackageName, + "locationType": translate.LocationType, + "specParamType": translate.SpecParamType, + "xmlParamType": translate.XmlParamType, + "xmlTag": translate.XmlTag, + "specifyEntryAssignment": translate.SpecifyEntryAssignment, + "specMatchesFunction": translate.SpecMatchesFunction, + "omitEmpty": translate.OmitEmpty, "contains": func(full, part string) bool { return strings.Contains(full, part) }, diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index ec03252c..fd8f6a20 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -3,6 +3,7 @@ package translate import ( "errors" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" + "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" "strings" ) @@ -14,3 +15,22 @@ func AsEntryXpath(location, xpath string) (string, error) { xpath = naming.CamelCase("", xpath, "", true) return "util.AsEntryXpath([]string{o." + location + "." + xpath + "}),", nil } + +func SpecifyEntryAssignment(param *properties.SpecParam) string { + calculatedAssignment := "" + if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { + calculatedAssignment = "util.StrToMem(o." + param.Name.CamelCase + ")" + } else { + calculatedAssignment = "o." + param.Name.CamelCase + } + + return calculatedAssignment +} + +func SpecMatchesFunction(param *properties.SpecParam) string { + calculatedFunction := "OptionalStringsMatch" + if param.Type == "list" { + calculatedFunction = "OrderedListsMatch" + } + return calculatedFunction +} diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index fdb891ee..88a5e255 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -1,6 +1,7 @@ package translate import ( + "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" "github.com/stretchr/testify/assert" "testing" ) @@ -14,3 +15,58 @@ func TestAsEntryXpath(t *testing.T) { // then assert.Equal(t, "util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),", asEntryXpath) } + +func TestSpecifyEntryAssignment(t *testing.T) { + // given + paramTypeString := properties.SpecParam{ + Name: &properties.NameVariant{ + CamelCase: "Description", + Underscore: "description", + }, + Profiles: []*properties.SpecParamProfile{ + { + Type: "string", + Xpath: []string{"description"}, + }, + }, + } + paramTypeListString := properties.SpecParam{ + Type: "list", + Name: &properties.NameVariant{ + CamelCase: "Tags", + Underscore: "tags", + }, + Profiles: []*properties.SpecParamProfile{ + { + Type: "member", + Xpath: []string{"tags"}, + }, + }, + } + + // when + calculatedAssignmentString := SpecifyEntryAssignment(¶mTypeString) + calculatedAssignmentListString := SpecifyEntryAssignment(¶mTypeListString) + + // then + assert.Equal(t, "o.Description", calculatedAssignmentString) + assert.Equal(t, "util.StrToMem(o.Tags)", calculatedAssignmentListString) +} + +func TestSpecMatchesFunction(t *testing.T) { + // given + paramTypeString := properties.SpecParam{ + Type: "string", + } + paramTypeListString := properties.SpecParam{ + Type: "list", + } + + // when + calculatedSpecMatchFunctionString := SpecMatchesFunction(¶mTypeString) + calculatedSpecMatchFunctionListString := SpecMatchesFunction(¶mTypeListString) + + // then + assert.Equal(t, "OptionalStringsMatch", calculatedSpecMatchFunctionString) + assert.Equal(t, "OrderedListsMatch", calculatedSpecMatchFunctionListString) +} diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 67c808ba..78b441ca 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -35,6 +35,35 @@ func SpecParamType(param *properties.SpecParam) string { return prefix + calculatedType } +func XmlParamType(param *properties.SpecParam) string { + prefix := "" + if !param.Required { + prefix = "*" + } + + calculatedType := "" + if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { + calculatedType = "util.MemberType" + } else { + calculatedType = param.Type + } + + return prefix + calculatedType +} + +func XmlTag(param *properties.SpecParam) string { + suffix := "" + if !param.Required { + suffix = ",omitempty" + } + + calculatedTag := "" + if param.Profiles != nil && len(param.Profiles) > 0 { + calculatedTag = "`xml:\"" + param.Profiles[0].Xpath[len(param.Profiles[0].Xpath)-1] + suffix + "\"`" + } + return calculatedTag +} + func OmitEmpty(location *properties.Location) string { if location.Vars != nil { return ",omitempty" diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 13e140d9..bf438e45 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -112,3 +112,71 @@ func TestOmitEmpty(t *testing.T) { assert.Contains(t, omitEmptyLocations, ",omitempty") assert.Contains(t, omitEmptyLocations, "") } + +func TestXmlParamType(t *testing.T) { + // given + paramTypeRequiredString := properties.SpecParam{ + Type: "string", + Required: true, + Profiles: []*properties.SpecParamProfile{ + { + Type: "string", + Xpath: []string{"description"}, + }, + }, + } + paramTypeListString := properties.SpecParam{ + Type: "list", + Items: &properties.SpecParamItems{ + Type: "string", + }, + Profiles: []*properties.SpecParamProfile{ + { + Type: "member", + Xpath: []string{"tag"}, + }, + }, + } + + // when + calculatedTypeRequiredString := XmlParamType(¶mTypeRequiredString) + calculatedTypeListString := XmlParamType(¶mTypeListString) + + // then + assert.Equal(t, "string", calculatedTypeRequiredString) + assert.Equal(t, "*util.MemberType", calculatedTypeListString) +} + +func TestXmlTag(t *testing.T) { + // given + paramTypeRequiredString := properties.SpecParam{ + Type: "string", + Required: false, + Profiles: []*properties.SpecParamProfile{ + { + Type: "string", + Xpath: []string{"description"}, + }, + }, + } + paramTypeListString := properties.SpecParam{ + Type: "list", + Items: &properties.SpecParamItems{ + Type: "string", + }, + Profiles: []*properties.SpecParamProfile{ + { + Type: "member", + Xpath: []string{"tag"}, + }, + }, + } + + // when + calculatedXmlTagRequiredString := XmlTag(¶mTypeRequiredString) + calculatedXmlTagListString := XmlTag(¶mTypeListString) + + // then + assert.Equal(t, "`xml:\"description,omitempty\"`", calculatedXmlTagRequiredString) + assert.Equal(t, "`xml:\"tag,omitempty\"`", calculatedXmlTagListString) +} diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index 281aa840..9bcd2b8c 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -36,6 +36,25 @@ type Entry struct { Misc map[string][]generic.Xml } +type EntryXmlContainer struct { + Answer []EntryXml `xml:"entry"` +} + +type EntryXml struct { + {{- if .Entry}} + XMLName xml.Name `xml:"entry"` + Name string `xml:"name,attr"` + {{- end}} + {{- range $_, $param := .Spec.Params}} + {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} + {{- end}} + {{- range $_, $param := .Spec.OneOf}} + {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} + {{- end}} + + Misc []generic.Xml `xml:",any"` +} + func (e *Entry) CopyMiscFrom(v *Entry) { if v == nil || len(v.Misc) == 0 { return @@ -58,6 +77,11 @@ func (e *Entry) Field(v string) (any, error) { if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { return e.{{$param.Name.CamelCase}}, nil } + {{- if eq $param.Type "list"}} + if v == "{{$param.Name.Underscore}}|LENGTH" || v == "{{$param.Name.CamelCase}}|LENGTH" { + return int64(len(e.{{$param.Name.CamelCase}})), nil + } + {{- end}} {{- end}} {{- range $_, $param := .Spec.OneOf}} if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { @@ -66,4 +90,72 @@ func (e *Entry) Field(v string) (any, error) { {{- end}} return nil, fmt.Errorf("unknown field") -} \ No newline at end of file +} + +func Versioning(vn version.Number) (Specifier, Normalizer, error) { + return SpecifyEntry, &EntryXmlContainer{}, nil +} + +func SpecifyEntry(o Entry) (any, error) { + ans := EntryXml{} + + {{- if .Entry}} + ans.Name = o.Name + {{- end}} + {{- range $_, $param := .Spec.Params}} + ans.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} + {{- end}} + {{- range $_, $param := .Spec.OneOf}} + ans.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} + {{- end}} + + ans.Misc = o.Misc[fmt.Sprintf("%s\n%s", "Entry", o.Name)] + + return ans, nil +} + +func (c *EntryXmlContainer) Normalize() ([]Entry, error) { + ans := make([]Entry, 0, len(c.Answer)) + for _, var0 := range c.Answer { + var1 := Entry{ + Misc: make(map[string][]generic.Xml), + } + {{- if .Entry}} + var1.Name = var0.Name + {{- end}} + {{- range $_, $param := .Spec.Params}} + var1.{{$param.Name.CamelCase}} = var0.{{$param.Name.CamelCase}} + {{- end}} + {{- range $_, $param := .Spec.OneOf}} + var1.{{$param.Name.CamelCase}} = var0.{{$param.Name.CamelCase}} + {{- end}} + + var1.Misc[fmt.Sprintf("%s\n%s", "Entry", var0.Name)] = var0.Misc + + ans = append(ans, var1) + } + + return ans, nil +} + +func SpecMatches(a, b *Entry) bool { + if a == nil && b != nil || a != nil && b == nil { + return false + } else if a == nil && b == nil { + return true + } + + // Don't compare Name. + {{- range $_, $param := .Spec.Params}} + if !util.{{specMatchesFunction $param}}(a.{{$param.Name.CamelCase}}, b.{{$param.Name.CamelCase}}) { + return false + } + {{- end}} + {{- range $_, $param := .Spec.OneOf}} + if !util.{{specMatchesFunction $param}}(a.{{$param.Name.CamelCase}}, b.{{$param.Name.CamelCase}}) { + return false + } + {{- end}} + + return true +} From 4d70add96112d0f5bb2a4adcdd3b054a668039bf Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 11 Mar 2024 13:47:01 +0100 Subject: [PATCH 02/22] start work on service.go --- templates/sdk/service.tmpl | 443 ++++++++++++++++++++++++++++++++++++- 1 file changed, 442 insertions(+), 1 deletion(-) diff --git a/templates/sdk/service.tmpl b/templates/sdk/service.tmpl index 3b3d1518..677fb936 100644 --- a/templates/sdk/service.tmpl +++ b/templates/sdk/service.tmpl @@ -1 +1,442 @@ -package {{packageName .GoSdkPath}} \ No newline at end of file +package {{packageName .GoSdkPath}} + +import ( + "context" + "fmt" + + "github.com/PaloAltoNetworks/pango/errors" + "github.com/PaloAltoNetworks/pango/filtering" + "github.com/PaloAltoNetworks/pango/util" + "github.com/PaloAltoNetworks/pango/xmlapi" +) + +type Service struct { + client util.PangoClient +} + +func NewService(client util.PangoClient) *Service { + return &Service{ + client: client, + } +} + +// Create creates the given config object. +func (s *Service) Create(ctx context.Context, loc Location, entry Entry) (*Entry, error) { + if entry.Name == "" { + return nil, errors.NameNotSpecifiedError + } + + vn := s.client.Versioning() + + specifier, _, err := Versioning(vn) + if err != nil { + return nil, err + } + + path, err := loc.Xpath(vn, entry.Name) + if err != nil { + return nil, err + } + + createSpec, err := specifier(entry) + if err != nil { + return nil, err + } + + cmd := &xmlapi.Config{ + Action: "set", + Xpath: util.AsXpath(path[:len(path)-1]), + Element: createSpec, + Target: s.client.GetTarget(), + } + + if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil { + return nil, err + } + + return s.Read(ctx, loc, entry.Name, "get") +} + +// Read returns the given config object, using the specified action. +// Param action should be either "get" or "show". +func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) { + if name == "" { + return nil, errors.NameNotSpecifiedError + } + + vn := s.client.Versioning() + _, normalizer, err := Versioning(vn) + if err != nil { + return nil, err + } + + path, err := loc.Xpath(vn, name) + if err != nil { + return nil, err + } + + cmd := &xmlapi.Config{ + Action: action, + Xpath: util.AsXpath(path), + Target: s.client.GetTarget(), + } + + if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil { + // action=show returns empty config like this + if err.Error() == "No such node" && action == "show" { + return nil, errors.ObjectNotFound() + } + return nil, err + } + + list, err := normalizer.Normalize() + if err != nil { + return nil, err + } else if len(list) != 1 { + return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list)) + } + + return &list[0], nil +} + +// ReadFromConfig returns the given config object from the loaded XML config. +// Requires that client.LoadPanosConfig() has been invoked. +func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) { + if name == "" { + return nil, errors.NameNotSpecifiedError + } + + vn := s.client.Versioning() + _, normalizer, err := Versioning(vn) + if err != nil { + return nil, err + } + + path, err := loc.Xpath(vn, name) + if err != nil { + return nil, err + } + + if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil { + return nil, err + } + + list, err := normalizer.Normalize() + if err != nil { + return nil, err + } else if len(list) != 1 { + return nil, fmt.Errorf("expected to find 1 entry, got %d", len(list)) + } + + return &list[0], nil +} + +// Update updates the given config object, then returns the result. +func (s *Service) Update(ctx context.Context, loc Location, entry Entry, oldName string) (*Entry, error) { + if entry.Name == "" { + return nil, errors.NameNotSpecifiedError + } + + vn := s.client.Versioning() + updates := xmlapi.NewMultiConfig(2) + specifier, _, err := Versioning(vn) + if err != nil { + return nil, err + } + + var old *Entry + if oldName != "" && oldName != entry.Name { + path, err := loc.Xpath(vn, oldName) + if err != nil { + return nil, err + } + + old, err = s.Read(ctx, loc, oldName, "get") + + updates.Add(&xmlapi.Config{ + Action: "rename", + Xpath: util.AsXpath(path), + NewName: entry.Name, + }) + } else { + old, err = s.Read(ctx, loc, entry.Name, "get") + } + if err != nil { + return nil, err + } + + if !SpecMatches(&entry, old) { + path, err := loc.Xpath(vn, entry.Name) + if err != nil { + return nil, err + } + + entry.CopyMiscFrom(old) + + updateSpec, err := specifier(entry) + if err != nil { + return nil, err + } + + updates.Add(&xmlapi.Config{ + Action: "edit", + Xpath: util.AsXpath(path), + Element: updateSpec, + }) + } + + if len(updates.Operations) != 0 { + if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil { + return nil, err + } + } + + return s.Read(ctx, loc, entry.Name, "get") +} + +// Delete deletes the given item. +func (s *Service) Delete(ctx context.Context, loc Location, name string) error { + if name == "" { + return errors.NameNotSpecifiedError + } + + vn := s.client.Versioning() + + path, err := loc.Xpath(vn, name) + if err != nil { + return err + } + + cmd := &xmlapi.Config{ + Action: "delete", + Xpath: util.AsXpath(path), + Target: s.client.GetTarget(), + } + + _, _, err = s.client.Communicate(ctx, cmd, false, nil) + + return err +} + +// List returns a list of service objects using the given action. +// Param action should be either "get" or "show". +// Params filter and quote are for client side filtering. +func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]Entry, error) { + var err error + + var logic *filtering.Group + if filter != "" { + logic, err = filtering.Parse(filter, quote) + if err != nil { + return nil, err + } + } + + vn := s.client.Versioning() + + _, normalizer, err := Versioning(vn) + if err != nil { + return nil, err + } + + path, err := loc.Xpath(vn, "") + if err != nil { + return nil, err + } + + cmd := &xmlapi.Config{ + Action: action, + Xpath: util.AsXpath(path), + Target: s.client.GetTarget(), + } + + if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil { + // action=show returns empty config like this, it is not an error. + if err.Error() == "No such node" && action == "show" { + return nil, nil + } + return nil, err + } + + listing, err := normalizer.Normalize() + if err != nil || logic == nil { + return listing, err + } + + filtered := make([]Entry, 0, len(listing)) + for _, x := range listing { + ok, err := logic.Matches(&x) + if err != nil { + return nil, err + } + if ok { + filtered = append(filtered, x) + } + } + + return filtered, nil +} + +// ListFromConfig returns a list of objects at the given location. +// Requires that client.LoadPanosConfig() has been invoked. +// Params filter and quote are for client side filtering. +func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]Entry, error) { + var err error + + var logic *filtering.Group + if filter != "" { + logic, err = filtering.Parse(filter, quote) + if err != nil { + return nil, err + } + } + + vn := s.client.Versioning() + + _, normalizer, err := Versioning(vn) + if err != nil { + return nil, err + } + + path, err := loc.Xpath(vn, "") + if err != nil { + return nil, err + } + path = path[:len(path)-1] + + if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil { + return nil, err + } + + listing, err := normalizer.Normalize() + if err != nil || logic == nil { + return listing, err + } + + filtered := make([]Entry, 0, len(listing)) + for _, x := range listing { + ok, err := logic.Matches(&x) + if err != nil { + return nil, err + } + if ok { + filtered = append(filtered, x) + } + } + + return filtered, nil +} + +// ConfigureGroup performs all necessary set / edit / delete commands to ensure +// that the objects are configured as specified. +func (s *Service) ConfigureGroup(ctx context.Context, loc Location, entries []Entry, prevNames []string) ([]Entry, error) { + var err error + + vn := s.client.Versioning() + updates := xmlapi.NewMultiConfig(len(prevNames) + len(entries)) + specifier, _, err := Versioning(vn) + if err != nil { + return nil, err + } + + curObjs, err := s.List(ctx, loc, "get", "", "") + if err != nil { + return nil, err + } + + // Determine set vs edit for desired objects. + for _, entry := range entries { + var found bool + for _, live := range curObjs { + if entry.Name == live.Name { + found = true + if !SpecMatches(&entry, &live) { + path, err := loc.Xpath(vn, entry.Name) + if err != nil { + return nil, err + } + + entry.CopyMiscFrom(&live) + + elm, err := specifier(entry) + if err != nil { + return nil, err + } + + updates.Add(&xmlapi.Config{ + Action: "edit", + Xpath: util.AsXpath(path), + Element: elm, + }) + } + break + } + } + + if !found { + path, err := loc.Xpath(vn, entry.Name) + if err != nil { + return nil, err + } + + elm, err := specifier(entry) + if err != nil { + return nil, err + } + + updates.Add(&xmlapi.Config{ + Action: "set", + Xpath: util.AsXpath(path), + Element: elm, + }) + } + } + + // Determine which old objects need to be removed. + if len(prevNames) != 0 { + for _, name := range prevNames { + var found bool + for _, entry := range entries { + if entry.Name == name { + found = true + break + } + } + + if !found { + path, err := loc.Xpath(vn, name) + if err != nil { + return nil, err + } + + updates.Add(&xmlapi.Config{ + Action: "delete", + Xpath: util.AsXpath(path), + }) + } + } + } + + if len(updates.Operations) != 0 { + if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil { + return nil, err + } + } + + curObjs, err = s.List(ctx, loc, "get", "", "") + if err != nil { + return nil, err + } + + ans := make([]Entry, 0, len(entries)) + for _, entry := range entries { + for _, live := range curObjs { + if entry.Name == live.Name { + ans = append(ans, live) + break + } + } + } + + return ans, nil +} From 401a4708f5450939d1ede38a5bb538c95251f2a3 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 11 Mar 2024 13:58:16 +0100 Subject: [PATCH 03/22] refactor names in template for entry.go --- templates/sdk/entry.tmpl | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index 9bcd2b8c..9941a7ad 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -97,45 +97,45 @@ func Versioning(vn version.Number) (Specifier, Normalizer, error) { } func SpecifyEntry(o Entry) (any, error) { - ans := EntryXml{} + entry := EntryXml{} {{- if .Entry}} - ans.Name = o.Name + entry.Name = o.Name {{- end}} {{- range $_, $param := .Spec.Params}} - ans.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} + entry.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} {{- end}} {{- range $_, $param := .Spec.OneOf}} - ans.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} + entry.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} {{- end}} - ans.Misc = o.Misc[fmt.Sprintf("%s\n%s", "Entry", o.Name)] + entry.Misc = o.Misc[fmt.Sprintf("%s\n%s", "Entry", o.Name)] - return ans, nil + return entry, nil } func (c *EntryXmlContainer) Normalize() ([]Entry, error) { - ans := make([]Entry, 0, len(c.Answer)) - for _, var0 := range c.Answer { - var1 := Entry{ + entryList := make([]Entry, 0, len(c.Answer)) + for _, entryXml := range c.Answer { + entry := Entry{ Misc: make(map[string][]generic.Xml), } {{- if .Entry}} - var1.Name = var0.Name + entry.Name = entryXml.Name {{- end}} {{- range $_, $param := .Spec.Params}} - var1.{{$param.Name.CamelCase}} = var0.{{$param.Name.CamelCase}} + entry.{{$param.Name.CamelCase}} = entryXml.{{$param.Name.CamelCase}} {{- end}} {{- range $_, $param := .Spec.OneOf}} - var1.{{$param.Name.CamelCase}} = var0.{{$param.Name.CamelCase}} + entry.{{$param.Name.CamelCase}} = entryXml.{{$param.Name.CamelCase}} {{- end}} - var1.Misc[fmt.Sprintf("%s\n%s", "Entry", var0.Name)] = var0.Misc + entry.Misc[fmt.Sprintf("%s\n%s", "Entry", entryXml.Name)] = entryXml.Misc - ans = append(ans, var1) + entryList = append(entryList, entry) } - return ans, nil + return entryList, nil } func SpecMatches(a, b *Entry) bool { From 53084e3f4a14bdb61773ce08a2f1ccf273092957 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 11 Mar 2024 15:05:53 +0100 Subject: [PATCH 04/22] add not_present and from_version to SpecParamProfile --- pkg/properties/normalized.go | 20 ++++++++++++++++++-- pkg/properties/normalized_test.go | 12 ++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 23290be1..77f1ce74 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -105,8 +105,10 @@ type SpecParamItemsLength struct { } type SpecParamProfile struct { - Xpath []string `json:"xpath" yaml:"xpath"` - Type string `json:"type" yaml:"type,omitempty"` + Xpath []string `json:"xpath" yaml:"xpath"` + Type string `json:"type" yaml:"type,omitempty"` + NotPresent bool `json:"not_present" yaml:"not_present"` + FromVersion string `json:"from_version" yaml:"from_version"` } func GetNormalizations() ([]string, error) { @@ -141,10 +143,24 @@ func ParseSpec(input []byte) (*Normalization, error) { var spec Normalization err := content.Unmarshal(input, &spec) + if err != nil { + return nil, err + } err = spec.AddNameVariantsForLocation() + if err != nil { + return nil, err + } + err = spec.AddNameVariantsForParams() + if err != nil { + return nil, err + } + err = spec.AddDefaultTypesForParams() + if err != nil { + return nil, err + } return &spec, err } diff --git a/pkg/properties/normalized_test.go b/pkg/properties/normalized_test.go index 37401379..767d3898 100644 --- a/pkg/properties/normalized_test.go +++ b/pkg/properties/normalized_test.go @@ -265,6 +265,8 @@ spec: profiles: - xpath: - description + not_present: false + from_version: "" spec: null tags: name: @@ -285,6 +287,8 @@ spec: - xpath: - tag type: member + not_present: false + from_version: "" spec: null one_of: fqdn: @@ -301,6 +305,8 @@ spec: profiles: - xpath: - fqdn + not_present: false + from_version: "" spec: null ip_netmask: name: @@ -312,6 +318,8 @@ spec: profiles: - xpath: - ip-netmask + not_present: false + from_version: "" spec: null ip_range: name: @@ -323,6 +331,8 @@ spec: profiles: - xpath: - ip-range + not_present: false + from_version: "" spec: null ip_wildcard: name: @@ -334,6 +344,8 @@ spec: profiles: - xpath: - ip-wildcard + not_present: false + from_version: "" spec: null ` From c5ff1b4571f7bd33db534432456aead85728eba7 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 11 Mar 2024 15:40:33 +0100 Subject: [PATCH 05/22] current service is only prepared for entry --- templates/sdk/service.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/sdk/service.tmpl b/templates/sdk/service.tmpl index 677fb936..b142799d 100644 --- a/templates/sdk/service.tmpl +++ b/templates/sdk/service.tmpl @@ -20,6 +20,7 @@ func NewService(client util.PangoClient) *Service { } } +{{- if .Entry}} // Create creates the given config object. func (s *Service) Create(ctx context.Context, loc Location, entry Entry) (*Entry, error) { if entry.Name == "" { @@ -440,3 +441,4 @@ func (s *Service) ConfigureGroup(ctx context.Context, loc Location, entries []En return ans, nil } +{{- end}} \ No newline at end of file From 5da312cdf7aeb659c44e5cccc3c70851684d2f0a Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 11 Mar 2024 15:41:12 +0100 Subject: [PATCH 06/22] clear service template --- templates/sdk/service.tmpl | 445 +------------------------------------ 1 file changed, 1 insertion(+), 444 deletions(-) diff --git a/templates/sdk/service.tmpl b/templates/sdk/service.tmpl index b142799d..3b3d1518 100644 --- a/templates/sdk/service.tmpl +++ b/templates/sdk/service.tmpl @@ -1,444 +1 @@ -package {{packageName .GoSdkPath}} - -import ( - "context" - "fmt" - - "github.com/PaloAltoNetworks/pango/errors" - "github.com/PaloAltoNetworks/pango/filtering" - "github.com/PaloAltoNetworks/pango/util" - "github.com/PaloAltoNetworks/pango/xmlapi" -) - -type Service struct { - client util.PangoClient -} - -func NewService(client util.PangoClient) *Service { - return &Service{ - client: client, - } -} - -{{- if .Entry}} -// Create creates the given config object. -func (s *Service) Create(ctx context.Context, loc Location, entry Entry) (*Entry, error) { - if entry.Name == "" { - return nil, errors.NameNotSpecifiedError - } - - vn := s.client.Versioning() - - specifier, _, err := Versioning(vn) - if err != nil { - return nil, err - } - - path, err := loc.Xpath(vn, entry.Name) - if err != nil { - return nil, err - } - - createSpec, err := specifier(entry) - if err != nil { - return nil, err - } - - cmd := &xmlapi.Config{ - Action: "set", - Xpath: util.AsXpath(path[:len(path)-1]), - Element: createSpec, - Target: s.client.GetTarget(), - } - - if _, _, err = s.client.Communicate(ctx, cmd, false, nil); err != nil { - return nil, err - } - - return s.Read(ctx, loc, entry.Name, "get") -} - -// Read returns the given config object, using the specified action. -// Param action should be either "get" or "show". -func (s *Service) Read(ctx context.Context, loc Location, name, action string) (*Entry, error) { - if name == "" { - return nil, errors.NameNotSpecifiedError - } - - vn := s.client.Versioning() - _, normalizer, err := Versioning(vn) - if err != nil { - return nil, err - } - - path, err := loc.Xpath(vn, name) - if err != nil { - return nil, err - } - - cmd := &xmlapi.Config{ - Action: action, - Xpath: util.AsXpath(path), - Target: s.client.GetTarget(), - } - - if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil { - // action=show returns empty config like this - if err.Error() == "No such node" && action == "show" { - return nil, errors.ObjectNotFound() - } - return nil, err - } - - list, err := normalizer.Normalize() - if err != nil { - return nil, err - } else if len(list) != 1 { - return nil, fmt.Errorf("expected to %q 1 entry, got %d", action, len(list)) - } - - return &list[0], nil -} - -// ReadFromConfig returns the given config object from the loaded XML config. -// Requires that client.LoadPanosConfig() has been invoked. -func (s *Service) ReadFromConfig(ctx context.Context, loc Location, name string) (*Entry, error) { - if name == "" { - return nil, errors.NameNotSpecifiedError - } - - vn := s.client.Versioning() - _, normalizer, err := Versioning(vn) - if err != nil { - return nil, err - } - - path, err := loc.Xpath(vn, name) - if err != nil { - return nil, err - } - - if _, err = s.client.ReadFromConfig(ctx, path, true, normalizer); err != nil { - return nil, err - } - - list, err := normalizer.Normalize() - if err != nil { - return nil, err - } else if len(list) != 1 { - return nil, fmt.Errorf("expected to find 1 entry, got %d", len(list)) - } - - return &list[0], nil -} - -// Update updates the given config object, then returns the result. -func (s *Service) Update(ctx context.Context, loc Location, entry Entry, oldName string) (*Entry, error) { - if entry.Name == "" { - return nil, errors.NameNotSpecifiedError - } - - vn := s.client.Versioning() - updates := xmlapi.NewMultiConfig(2) - specifier, _, err := Versioning(vn) - if err != nil { - return nil, err - } - - var old *Entry - if oldName != "" && oldName != entry.Name { - path, err := loc.Xpath(vn, oldName) - if err != nil { - return nil, err - } - - old, err = s.Read(ctx, loc, oldName, "get") - - updates.Add(&xmlapi.Config{ - Action: "rename", - Xpath: util.AsXpath(path), - NewName: entry.Name, - }) - } else { - old, err = s.Read(ctx, loc, entry.Name, "get") - } - if err != nil { - return nil, err - } - - if !SpecMatches(&entry, old) { - path, err := loc.Xpath(vn, entry.Name) - if err != nil { - return nil, err - } - - entry.CopyMiscFrom(old) - - updateSpec, err := specifier(entry) - if err != nil { - return nil, err - } - - updates.Add(&xmlapi.Config{ - Action: "edit", - Xpath: util.AsXpath(path), - Element: updateSpec, - }) - } - - if len(updates.Operations) != 0 { - if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil { - return nil, err - } - } - - return s.Read(ctx, loc, entry.Name, "get") -} - -// Delete deletes the given item. -func (s *Service) Delete(ctx context.Context, loc Location, name string) error { - if name == "" { - return errors.NameNotSpecifiedError - } - - vn := s.client.Versioning() - - path, err := loc.Xpath(vn, name) - if err != nil { - return err - } - - cmd := &xmlapi.Config{ - Action: "delete", - Xpath: util.AsXpath(path), - Target: s.client.GetTarget(), - } - - _, _, err = s.client.Communicate(ctx, cmd, false, nil) - - return err -} - -// List returns a list of service objects using the given action. -// Param action should be either "get" or "show". -// Params filter and quote are for client side filtering. -func (s *Service) List(ctx context.Context, loc Location, action, filter, quote string) ([]Entry, error) { - var err error - - var logic *filtering.Group - if filter != "" { - logic, err = filtering.Parse(filter, quote) - if err != nil { - return nil, err - } - } - - vn := s.client.Versioning() - - _, normalizer, err := Versioning(vn) - if err != nil { - return nil, err - } - - path, err := loc.Xpath(vn, "") - if err != nil { - return nil, err - } - - cmd := &xmlapi.Config{ - Action: action, - Xpath: util.AsXpath(path), - Target: s.client.GetTarget(), - } - - if _, _, err = s.client.Communicate(ctx, cmd, true, normalizer); err != nil { - // action=show returns empty config like this, it is not an error. - if err.Error() == "No such node" && action == "show" { - return nil, nil - } - return nil, err - } - - listing, err := normalizer.Normalize() - if err != nil || logic == nil { - return listing, err - } - - filtered := make([]Entry, 0, len(listing)) - for _, x := range listing { - ok, err := logic.Matches(&x) - if err != nil { - return nil, err - } - if ok { - filtered = append(filtered, x) - } - } - - return filtered, nil -} - -// ListFromConfig returns a list of objects at the given location. -// Requires that client.LoadPanosConfig() has been invoked. -// Params filter and quote are for client side filtering. -func (s *Service) ListFromConfig(ctx context.Context, loc Location, filter, quote string) ([]Entry, error) { - var err error - - var logic *filtering.Group - if filter != "" { - logic, err = filtering.Parse(filter, quote) - if err != nil { - return nil, err - } - } - - vn := s.client.Versioning() - - _, normalizer, err := Versioning(vn) - if err != nil { - return nil, err - } - - path, err := loc.Xpath(vn, "") - if err != nil { - return nil, err - } - path = path[:len(path)-1] - - if _, err = s.client.ReadFromConfig(ctx, path, false, normalizer); err != nil { - return nil, err - } - - listing, err := normalizer.Normalize() - if err != nil || logic == nil { - return listing, err - } - - filtered := make([]Entry, 0, len(listing)) - for _, x := range listing { - ok, err := logic.Matches(&x) - if err != nil { - return nil, err - } - if ok { - filtered = append(filtered, x) - } - } - - return filtered, nil -} - -// ConfigureGroup performs all necessary set / edit / delete commands to ensure -// that the objects are configured as specified. -func (s *Service) ConfigureGroup(ctx context.Context, loc Location, entries []Entry, prevNames []string) ([]Entry, error) { - var err error - - vn := s.client.Versioning() - updates := xmlapi.NewMultiConfig(len(prevNames) + len(entries)) - specifier, _, err := Versioning(vn) - if err != nil { - return nil, err - } - - curObjs, err := s.List(ctx, loc, "get", "", "") - if err != nil { - return nil, err - } - - // Determine set vs edit for desired objects. - for _, entry := range entries { - var found bool - for _, live := range curObjs { - if entry.Name == live.Name { - found = true - if !SpecMatches(&entry, &live) { - path, err := loc.Xpath(vn, entry.Name) - if err != nil { - return nil, err - } - - entry.CopyMiscFrom(&live) - - elm, err := specifier(entry) - if err != nil { - return nil, err - } - - updates.Add(&xmlapi.Config{ - Action: "edit", - Xpath: util.AsXpath(path), - Element: elm, - }) - } - break - } - } - - if !found { - path, err := loc.Xpath(vn, entry.Name) - if err != nil { - return nil, err - } - - elm, err := specifier(entry) - if err != nil { - return nil, err - } - - updates.Add(&xmlapi.Config{ - Action: "set", - Xpath: util.AsXpath(path), - Element: elm, - }) - } - } - - // Determine which old objects need to be removed. - if len(prevNames) != 0 { - for _, name := range prevNames { - var found bool - for _, entry := range entries { - if entry.Name == name { - found = true - break - } - } - - if !found { - path, err := loc.Xpath(vn, name) - if err != nil { - return nil, err - } - - updates.Add(&xmlapi.Config{ - Action: "delete", - Xpath: util.AsXpath(path), - }) - } - } - } - - if len(updates.Operations) != 0 { - if _, _, _, err = s.client.MultiConfig(ctx, updates, false, nil); err != nil { - return nil, err - } - } - - curObjs, err = s.List(ctx, loc, "get", "", "") - if err != nil { - return nil, err - } - - ans := make([]Entry, 0, len(entries)) - for _, entry := range entries { - for _, live := range curObjs { - if entry.Name == live.Name { - ans = append(ans, live) - break - } - } - } - - return ans, nil -} -{{- end}} \ No newline at end of file +package {{packageName .GoSdkPath}} \ No newline at end of file From 2e011a79b5e2a50fb619c3759cbfd1031f96998e Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 11 Mar 2024 16:24:19 +0100 Subject: [PATCH 07/22] render entry.go only for specs with entry --- pkg/generate/generator.go | 23 ++++++++++++++++------- templates/sdk/entry.tmpl | 12 ++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 734f763a..66737241 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -1,7 +1,9 @@ package generate import ( + "bytes" "fmt" + "io" "log" "os" "path/filepath" @@ -42,20 +44,27 @@ func (c *Creator) RenderTemplate() error { return fmt.Errorf("error creating directories for %s: %w", filePath, err) } - outputFile, err := os.Create(filePath) - if err != nil { - return fmt.Errorf("error creating file %s: %w", filePath, err) - } - defer outputFile.Close() - tmpl, err := c.parseTemplate(templateName) if err != nil { return fmt.Errorf("error parsing template %s: %w", templateName, err) } - if err := tmpl.Execute(outputFile, c.Spec); err != nil { + var data bytes.Buffer + if err := tmpl.Execute(&data, c.Spec); err != nil { return fmt.Errorf("error executing template %s: %w", templateName, err) } + if data.Len() > 0 { + outputFile, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("error creating file %s: %w", filePath, err) + } + defer outputFile.Close() + + _, err = io.Copy(outputFile, &data) + if err != nil { + return err + } + } } return nil } diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index 9941a7ad..5fe723cd 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -1,3 +1,4 @@ +{{- if .Entry}} package {{packageName .GoSdkPath}} import ( @@ -23,9 +24,7 @@ var ( ) type Entry struct { - {{- if .Entry}} Name string - {{- end}} {{- range $_, $param := .Spec.Params}} {{$param.Name.CamelCase}} {{specParamType $param}} {{- end}} @@ -41,10 +40,8 @@ type EntryXmlContainer struct { } type EntryXml struct { - {{- if .Entry}} XMLName xml.Name `xml:"entry"` Name string `xml:"name,attr"` - {{- end}} {{- range $_, $param := .Spec.Params}} {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} {{- end}} @@ -67,11 +64,9 @@ func (e *Entry) CopyMiscFrom(v *Entry) { } func (e *Entry) Field(v string) (any, error) { - {{- if .Entry}} if v == "name" || v == "Name" { return e.Name, nil } - {{- end}} {{- range $_, $param := .Spec.Params}} if v == "{{$param.Name.Underscore}}" || v == "{{$param.Name.CamelCase}}" { @@ -99,9 +94,7 @@ func Versioning(vn version.Number) (Specifier, Normalizer, error) { func SpecifyEntry(o Entry) (any, error) { entry := EntryXml{} - {{- if .Entry}} entry.Name = o.Name - {{- end}} {{- range $_, $param := .Spec.Params}} entry.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} {{- end}} @@ -120,9 +113,7 @@ func (c *EntryXmlContainer) Normalize() ([]Entry, error) { entry := Entry{ Misc: make(map[string][]generic.Xml), } - {{- if .Entry}} entry.Name = entryXml.Name - {{- end}} {{- range $_, $param := .Spec.Params}} entry.{{$param.Name.CamelCase}} = entryXml.{{$param.Name.CamelCase}} {{- end}} @@ -159,3 +150,4 @@ func SpecMatches(a, b *Entry) bool { return true } +{{- end}} \ No newline at end of file From 48db70c57e555607074894ed0e6558644a4e04a7 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Mon, 11 Mar 2024 23:02:05 +0100 Subject: [PATCH 08/22] create structs for nested specs --- pkg/generate/generator.go | 1 + pkg/properties/normalized.go | 46 +++++++++++++++++++++++++---------- pkg/translate/structs.go | 29 ++++++++++++++++++++++ pkg/translate/structs_test.go | 42 ++++++++++++++++++++++++++++++++ templates/sdk/entry.tmpl | 22 +++++++++++++++++ 5 files changed, 127 insertions(+), 13 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 66737241..23368199 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -125,6 +125,7 @@ func (c *Creator) parseTemplate(templateName string) (*template.Template, error) return a - b }, "asEntryXpath": translate.AsEntryXpath, + "nestedSpecs": translate.NestedSpecs, } return template.New(templateName).Funcs(funcMap).ParseFiles(templatePath) } diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 120254ab..b19999aa 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -219,24 +219,44 @@ func (spec *Normalization) AddNameVariantsForParams() error { return nil } -// AddDefaultTypesForParams ensures all SpecParams within Spec have a default type if not specified. -func (spec *Normalization) AddDefaultTypesForParams() error { - if spec.Spec == nil { - return nil +func AddDefaultTypesForParams(param *SpecParam) error { + if param.Type == "" { + param.Type = "string" } - setDefaultParamTypeForMap(spec.Spec.Params) - setDefaultParamTypeForMap(spec.Spec.OneOf) - - return nil + if param.Spec != nil { + for _, childParam := range param.Spec.Params { + if err := AddDefaultTypesForParams(childParam); err != nil { + return err + } + } + for _, childParam := range param.Spec.OneOf { + if err := AddDefaultTypesForParams(childParam); err != nil { + return err + } + } + return nil + } else { + return nil + } } -// setDefaultParamTypeForMap iterates over a map of SpecParam pointers, setting their Type to "string" if not specified. -func setDefaultParamTypeForMap(params map[string]*SpecParam) { - for _, param := range params { - if param.Type == "" { - param.Type = "string" +// AddDefaultTypesForParams ensures all SpecParams within Spec have a default type if not specified. +func (spec *Normalization) AddDefaultTypesForParams() error { + if spec.Spec != nil { + for _, childParam := range spec.Spec.Params { + if err := AddDefaultTypesForParams(childParam); err != nil { + return err + } + } + for _, childParam := range spec.Spec.OneOf { + if err := AddDefaultTypesForParams(childParam); err != nil { + return err + } } + return nil + } else { + return nil } } diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 78b441ca..0d418955 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -1,6 +1,7 @@ package translate import ( + "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) @@ -16,6 +17,30 @@ func LocationType(location *properties.Location, pointer bool) string { } } +func NestedSpecs(spec *properties.Spec) (map[string]*properties.Spec, error) { + nestedSpecs := make(map[string]*properties.Spec) + + checkNestedSpecs(spec, nestedSpecs) + + return nestedSpecs, nil +} + +func checkNestedSpecs(spec *properties.Spec, nestedSpecs map[string]*properties.Spec) { + for _, param := range spec.Params { + updateNestedSpecs(param, nestedSpecs) + } + for _, param := range spec.OneOf { + updateNestedSpecs(param, nestedSpecs) + } +} + +func updateNestedSpecs(param *properties.SpecParam, nestedSpecs map[string]*properties.Spec) { + if param.Spec != nil { + nestedSpecs[naming.CamelCase("", param.Name.CamelCase, "", true)] = param.Spec + checkNestedSpecs(param.Spec, nestedSpecs) + } +} + func SpecParamType(param *properties.SpecParam) string { prefix := "" if !param.Required { @@ -28,6 +53,8 @@ func SpecParamType(param *properties.SpecParam) string { calculatedType := "" if param.Type == "list" && param.Items != nil { calculatedType = param.Items.Type + } else if param.Spec != nil { + calculatedType = "Spec" + naming.CamelCase("", param.Name.CamelCase, "", true) } else { calculatedType = param.Type } @@ -44,6 +71,8 @@ func XmlParamType(param *properties.SpecParam) string { calculatedType := "" if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { calculatedType = "util.MemberType" + } else if param.Spec != nil { + calculatedType = "Spec" + naming.CamelCase("", param.Name.CamelCase, "", true) + "Xml" } else { calculatedType = param.Type } diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index bf438e45..468e358a 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -180,3 +180,45 @@ func TestXmlTag(t *testing.T) { assert.Equal(t, "`xml:\"description,omitempty\"`", calculatedXmlTagRequiredString) assert.Equal(t, "`xml:\"tag,omitempty\"`", calculatedXmlTagListString) } + +func TestNestedSpecs(t *testing.T) { + // given + spec := properties.Spec{ + Params: map[string]*properties.SpecParam{ + "a": { + Name: &properties.NameVariant{ + Underscore: "a", + CamelCase: "a", + }, + Spec: &properties.Spec{ + Params: map[string]*properties.SpecParam{ + "b": { + Name: &properties.NameVariant{ + Underscore: "b", + CamelCase: "b", + }, + Spec: &properties.Spec{ + Params: map[string]*properties.SpecParam{ + "c": { + Name: &properties.NameVariant{ + Underscore: "c", + CamelCase: "c", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + // when + nestedSpecs, _ := NestedSpecs(&spec) + + // then + assert.NotNil(t, nestedSpecs) + assert.Contains(t, nestedSpecs, "A") + assert.Contains(t, nestedSpecs, "B") +} diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index 5fe723cd..924a7902 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -35,6 +35,17 @@ type Entry struct { Misc map[string][]generic.Xml } +{{- range $name, $spec := nestedSpecs .Spec }} +type Spec{{$name}} struct { + {{- range $_, $param := $spec.Params}} + {{$param.Name.CamelCase}} {{specParamType $param}} + {{- end}} + {{- range $_, $param := $spec.OneOf}} + {{$param.Name.CamelCase}} {{specParamType $param}} + {{- end}} +} +{{- end}} + type EntryXmlContainer struct { Answer []EntryXml `xml:"entry"` } @@ -52,6 +63,17 @@ type EntryXml struct { Misc []generic.Xml `xml:",any"` } +{{- range $name, $spec := nestedSpecs .Spec }} +type Spec{{$name}}Xml struct { + {{- range $_, $param := $spec.Params}} + {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} + {{- end}} + {{- range $_, $param := $spec.OneOf}} + {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} + {{- end}} +} +{{- end}} + func (e *Entry) CopyMiscFrom(v *Entry) { if v == nil || len(v.Misc) == 0 { return From 07ccc142ce4242ac8f20557f7e216aab80f4ba97 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 12 Mar 2024 13:15:43 +0100 Subject: [PATCH 09/22] generate content of function SpecifyEntry for specification with nested specs --- pkg/translate/funcs.go | 84 +++++++++++++++++++++++++++++++++++-- pkg/translate/funcs_test.go | 52 +++++++++++++++++++++-- templates/sdk/entry.tmpl | 4 +- 3 files changed, 131 insertions(+), 9 deletions(-) diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index fd8f6a20..986d92af 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -17,14 +17,90 @@ func AsEntryXpath(location, xpath string) (string, error) { } func SpecifyEntryAssignment(param *properties.SpecParam) string { - calculatedAssignment := "" + var builder strings.Builder + + if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { + builder.WriteString("entry." + param.Name.CamelCase + " = util.StrToMem(o." + param.Name.CamelCase + ")") + } else if param.Spec != nil { + for _, subParam := range param.Spec.Params { + builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) + } + builder.WriteString("entry." + param.Name.CamelCase + " = &Spec" + param.Name.CamelCase + "Xml{\n") + for _, subParam := range param.Spec.Params { + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, subParam)) + } + for _, subParam := range param.Spec.OneOf { + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, subParam)) + } + builder.WriteString("}\n") + } else { + builder.WriteString("entry." + param.Name.CamelCase + " = o." + param.Name.CamelCase) + } + + return builder.String() +} + +func nestedObjectDeclaration(parent []string, param *properties.SpecParam) string { + var builder strings.Builder + + if param.Spec != nil { + for _, subParam := range param.Spec.Params { + builder.WriteString(declareVariableForNestedObject(parent, param, subParam)) + builder.WriteString(nestedObjectDeclaration(append(parent, param.Name.CamelCase), subParam)) + } + for _, subParam := range param.Spec.OneOf { + builder.WriteString(declareVariableForNestedObject(parent, param, subParam)) + builder.WriteString(nestedObjectDeclaration(append(parent, param.Name.CamelCase), subParam)) + } + } + + return builder.String() +} + +func declareVariableForNestedObject(parent []string, param *properties.SpecParam, subParam *properties.SpecParam) string { + if subParam.Spec == nil && parent != nil { + return "nested" + + strings.Join(parent, "") + + param.Name.CamelCase + + subParam.Name.CamelCase + + " := o." + + strings.Join(parent, ".") + + "." + param.Name.CamelCase + + "." + subParam.Name.CamelCase + + "\n" + } else { + return "" + } +} + +func nestedObjectAssignment(parent []string, param *properties.SpecParam) string { + var builder strings.Builder + if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { - calculatedAssignment = "util.StrToMem(o." + param.Name.CamelCase + ")" + builder.WriteString(param.Name.CamelCase + + " : util.StrToMem(o." + + param.Name.CamelCase + + "),\n") + } else if param.Spec != nil { + builder.WriteString(param.Name.CamelCase + + " : &Spec" + + param.Name.CamelCase + + "Xml{\n") + for _, subParam := range param.Spec.Params { + builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), subParam)) + } + for _, subParam := range param.Spec.OneOf { + builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), subParam)) + } + builder.WriteString("},\n") } else { - calculatedAssignment = "o." + param.Name.CamelCase + builder.WriteString(param.Name.CamelCase + + " : nested" + + strings.Join(parent, "") + + param.Name.CamelCase + ",\n") } - return calculatedAssignment + return builder.String() } func SpecMatchesFunction(param *properties.SpecParam) string { diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index 88a5e255..bbd2629d 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -16,7 +16,7 @@ func TestAsEntryXpath(t *testing.T) { assert.Equal(t, "util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),", asEntryXpath) } -func TestSpecifyEntryAssignment(t *testing.T) { +func TestSpecifyEntryAssignmentForFlatStructure(t *testing.T) { // given paramTypeString := properties.SpecParam{ Name: &properties.NameVariant{ @@ -49,8 +49,54 @@ func TestSpecifyEntryAssignment(t *testing.T) { calculatedAssignmentListString := SpecifyEntryAssignment(¶mTypeListString) // then - assert.Equal(t, "o.Description", calculatedAssignmentString) - assert.Equal(t, "util.StrToMem(o.Tags)", calculatedAssignmentListString) + assert.Equal(t, "entry.Description = o.Description", calculatedAssignmentString) + assert.Equal(t, "entry.Tags = util.StrToMem(o.Tags)", calculatedAssignmentListString) +} + +func TestSpecifyEntryAssignmentForNestedObject(t *testing.T) { + // given + spec := properties.Spec{ + Params: map[string]*properties.SpecParam{ + "a": { + Name: &properties.NameVariant{ + Underscore: "a", + CamelCase: "A", + }, + Spec: &properties.Spec{ + Params: map[string]*properties.SpecParam{ + "b": { + Name: &properties.NameVariant{ + Underscore: "b", + CamelCase: "B", + }, + Spec: &properties.Spec{ + Params: map[string]*properties.SpecParam{ + "c": { + Name: &properties.NameVariant{ + Underscore: "c", + CamelCase: "C", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + expectedAssignmentStreing := `nestedABC := o.A.B.C +entry.A = &SpecAXml{ +B : &SpecBXml{ +C : nestedABC, +}, +} +` + // when + calculatedAssignmentString := SpecifyEntryAssignment(spec.Params["a"]) + + // then + assert.Equal(t, expectedAssignmentStreing, calculatedAssignmentString) } func TestSpecMatchesFunction(t *testing.T) { diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index 924a7902..cca7f2dd 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -118,10 +118,10 @@ func SpecifyEntry(o Entry) (any, error) { entry.Name = o.Name {{- range $_, $param := .Spec.Params}} - entry.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} + {{specifyEntryAssignment $param}} {{- end}} {{- range $_, $param := .Spec.OneOf}} - entry.{{$param.Name.CamelCase}} = {{specifyEntryAssignment $param}} + {{specifyEntryAssignment $param}} {{- end}} entry.Misc = o.Misc[fmt.Sprintf("%s\n%s", "Entry", o.Name)] From 60af14eb441cd860f2b636f954cae37e10e879c9 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 12 Mar 2024 13:30:45 +0100 Subject: [PATCH 10/22] generate content of function Normalize for specification with nested specs --- pkg/generate/generator.go | 1 + pkg/translate/funcs.go | 36 ++++++++++++++++++++++----- pkg/translate/funcs_test.go | 46 +++++++++++++++++++++++++++++++++++ pkg/translate/structs_test.go | 6 ++--- templates/sdk/entry.tmpl | 10 ++++---- 5 files changed, 85 insertions(+), 14 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 23368199..cb27a643 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -116,6 +116,7 @@ func (c *Creator) parseTemplate(templateName string) (*template.Template, error) "xmlParamType": translate.XmlParamType, "xmlTag": translate.XmlTag, "specifyEntryAssignment": translate.SpecifyEntryAssignment, + "normalizeAssignment": translate.NormalizeAssignment, "specMatchesFunction": translate.SpecMatchesFunction, "omitEmpty": translate.OmitEmpty, "contains": func(full, part string) bool { diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 986d92af..0c69ac7e 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -27,10 +27,10 @@ func SpecifyEntryAssignment(param *properties.SpecParam) string { } builder.WriteString("entry." + param.Name.CamelCase + " = &Spec" + param.Name.CamelCase + "Xml{\n") for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, subParam)) + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "Xml", subParam)) } for _, subParam := range param.Spec.OneOf { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, subParam)) + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "Xml", subParam)) } builder.WriteString("}\n") } else { @@ -73,7 +73,7 @@ func declareVariableForNestedObject(parent []string, param *properties.SpecParam } } -func nestedObjectAssignment(parent []string, param *properties.SpecParam) string { +func nestedObjectAssignment(parent []string, suffix string, param *properties.SpecParam) string { var builder strings.Builder if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { @@ -85,12 +85,12 @@ func nestedObjectAssignment(parent []string, param *properties.SpecParam) string builder.WriteString(param.Name.CamelCase + " : &Spec" + param.Name.CamelCase + - "Xml{\n") + suffix + "{\n") for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), subParam)) + builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), suffix, subParam)) } for _, subParam := range param.Spec.OneOf { - builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), subParam)) + builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), suffix, subParam)) } builder.WriteString("},\n") } else { @@ -103,6 +103,30 @@ func nestedObjectAssignment(parent []string, param *properties.SpecParam) string return builder.String() } +func NormalizeAssignment(param *properties.SpecParam) string { + var builder strings.Builder + + if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { + builder.WriteString("entry." + param.Name.CamelCase + " = entryXml." + param.Name.CamelCase) + } else if param.Spec != nil { + for _, subParam := range param.Spec.Params { + builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) + } + builder.WriteString("entry." + param.Name.CamelCase + " = &Spec" + param.Name.CamelCase + "{\n") + for _, subParam := range param.Spec.Params { + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) + } + for _, subParam := range param.Spec.OneOf { + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) + } + builder.WriteString("}\n") + } else { + builder.WriteString("entry." + param.Name.CamelCase + " = entryXml." + param.Name.CamelCase) + } + + return builder.String() +} + func SpecMatchesFunction(param *properties.SpecParam) string { calculatedFunction := "OptionalStringsMatch" if param.Type == "list" { diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index bbd2629d..93a7edcc 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -99,6 +99,52 @@ C : nestedABC, assert.Equal(t, expectedAssignmentStreing, calculatedAssignmentString) } +func TestNormalizeAssignmentForNestedObject(t *testing.T) { + // given + spec := properties.Spec{ + Params: map[string]*properties.SpecParam{ + "a": { + Name: &properties.NameVariant{ + Underscore: "a", + CamelCase: "A", + }, + Spec: &properties.Spec{ + Params: map[string]*properties.SpecParam{ + "b": { + Name: &properties.NameVariant{ + Underscore: "b", + CamelCase: "B", + }, + Spec: &properties.Spec{ + Params: map[string]*properties.SpecParam{ + "c": { + Name: &properties.NameVariant{ + Underscore: "c", + CamelCase: "C", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + expectedAssignmentStreing := `nestedABC := o.A.B.C +entry.A = &SpecA{ +B : &SpecB{ +C : nestedABC, +}, +} +` + // when + calculatedAssignmentString := NormalizeAssignment(spec.Params["a"]) + + // then + assert.Equal(t, expectedAssignmentStreing, calculatedAssignmentString) +} + func TestSpecMatchesFunction(t *testing.T) { // given paramTypeString := properties.SpecParam{ diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 468e358a..27085549 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -188,21 +188,21 @@ func TestNestedSpecs(t *testing.T) { "a": { Name: &properties.NameVariant{ Underscore: "a", - CamelCase: "a", + CamelCase: "A", }, Spec: &properties.Spec{ Params: map[string]*properties.SpecParam{ "b": { Name: &properties.NameVariant{ Underscore: "b", - CamelCase: "b", + CamelCase: "B", }, Spec: &properties.Spec{ Params: map[string]*properties.SpecParam{ "c": { Name: &properties.NameVariant{ Underscore: "c", - CamelCase: "c", + CamelCase: "C", }, }, }, diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index cca7f2dd..a21b27d7 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -131,19 +131,19 @@ func SpecifyEntry(o Entry) (any, error) { func (c *EntryXmlContainer) Normalize() ([]Entry, error) { entryList := make([]Entry, 0, len(c.Answer)) - for _, entryXml := range c.Answer { + for _, o := range c.Answer { entry := Entry{ Misc: make(map[string][]generic.Xml), } - entry.Name = entryXml.Name + entry.Name = o.Name {{- range $_, $param := .Spec.Params}} - entry.{{$param.Name.CamelCase}} = entryXml.{{$param.Name.CamelCase}} + {{normalizeAssignment $param}} {{- end}} {{- range $_, $param := .Spec.OneOf}} - entry.{{$param.Name.CamelCase}} = entryXml.{{$param.Name.CamelCase}} + {{normalizeAssignment $param}} {{- end}} - entry.Misc[fmt.Sprintf("%s\n%s", "Entry", entryXml.Name)] = entryXml.Misc + entry.Misc[fmt.Sprintf("%s\n%s", "Entry", o.Name)] = o.Misc entryList = append(entryList, entry) } From 917a30191e22a864762da9fc6bd1df72df96b4b0 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 12 Mar 2024 14:03:46 +0100 Subject: [PATCH 11/22] add function comments --- cmd/codegen/main.go | 1 + pkg/generate/assets.go | 4 ++- pkg/generate/generator.go | 9 ++++++ pkg/properties/config.go | 1 + pkg/properties/normalized.go | 10 +++++- pkg/translate/funcs.go | 59 +++++++++++++++++++++--------------- pkg/translate/names.go | 2 +- pkg/translate/structs.go | 6 ++++ 8 files changed, 65 insertions(+), 27 deletions(-) diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go index 79e6fbfc..bf1dde56 100644 --- a/cmd/codegen/main.go +++ b/cmd/codegen/main.go @@ -24,6 +24,7 @@ func parseFlags() Config { return cfg } +// runCommand executed command to generate code for SDK or Terraform func runCommand(ctx context.Context, cmdType codegen.CommandType, cfg string) { cmd, err := codegen.NewCommand(ctx, cmdType, cfg) if err != nil { diff --git a/pkg/generate/assets.go b/pkg/generate/assets.go index 39078e78..7e20fa66 100644 --- a/pkg/generate/assets.go +++ b/pkg/generate/assets.go @@ -10,6 +10,7 @@ import ( "path/filepath" ) +// CopyAssets copy assets (static files) according to configuration func CopyAssets(config *properties.Config) error { for _, asset := range config.Assets { files, err := listAssets(asset) @@ -32,10 +33,10 @@ func CopyAssets(config *properties.Config) error { return nil } +// listAssets walk through directory and get list of all assets (static files) func listAssets(asset *properties.Asset) ([]string, error) { var files []string - // Walk through directory and get list of all files err := filepath.WalkDir(asset.Source, func(path string, entry fs.DirEntry, err error) error { if err != nil { return err @@ -52,6 +53,7 @@ func listAssets(asset *properties.Asset) ([]string, error) { return files, nil } +// copyAsset copy single asset, which may contain multiple files func copyAsset(target string, asset *properties.Asset, files []string) error { // Prepare destination path destinationDir := target + "/" + asset.Destination diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index cb27a643..d401ddc4 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -20,6 +20,7 @@ type Creator struct { Spec *properties.Normalization } +// NewCreator initialize Creator instance func NewCreator(goOutputDir, templatesDir string, spec *properties.Normalization) *Creator { return &Creator{ GoOutputDir: goOutputDir, @@ -28,6 +29,7 @@ func NewCreator(goOutputDir, templatesDir string, spec *properties.Normalization } } +// RenderTemplate loop through all templates, parse them and render content, which is saved to output file func (c *Creator) RenderTemplate() error { log.Println("Start rendering templates") @@ -53,6 +55,8 @@ func (c *Creator) RenderTemplate() error { if err := tmpl.Execute(&data, c.Spec); err != nil { return fmt.Errorf("error executing template %s: %w", templateName, err) } + // If from template no data was rendered (e.g. for DNS spec entry should not be created), + // then we don't need to create empty file (e.g. `entry.go`) with no content if data.Len() > 0 { outputFile, err := os.Create(filePath) if err != nil { @@ -69,11 +73,13 @@ func (c *Creator) RenderTemplate() error { return nil } +// createFullFilePath returns a full path for output file generated from template passed as argument to function func (c *Creator) createFullFilePath(templateName string) string { fileBaseName := strings.TrimSuffix(templateName, filepath.Ext(templateName)) return filepath.Join(c.GoOutputDir, filepath.Join(c.Spec.GoSdkPath...), fileBaseName+".go") } +// listOfTemplates return list of templates defined in TemplatesDir func (c *Creator) listOfTemplates() ([]string, error) { var files []string err := filepath.WalkDir(c.TemplatesDir, func(path string, entry os.DirEntry, err error) error { @@ -94,11 +100,13 @@ func (c *Creator) listOfTemplates() ([]string, error) { return files, nil } +// makeAllDirs creates all required directories, which are in the file path func (c *Creator) makeAllDirs(filePath string) error { dirPath := filepath.Dir(filePath) return os.MkdirAll(dirPath, os.ModePerm) } +// createFile just create a file and return it func (c *Creator) createFile(filePath string) (*os.File, error) { outputFile, err := os.Create(filePath) if err != nil { @@ -107,6 +115,7 @@ func (c *Creator) createFile(filePath string) (*os.File, error) { return outputFile, nil } +// parseTemplate parse template passed as argument and with function map defined below func (c *Creator) parseTemplate(templateName string) (*template.Template, error) { templatePath := filepath.Join(c.TemplatesDir, templateName) funcMap := template.FuncMap{ diff --git a/pkg/properties/config.go b/pkg/properties/config.go index 1e5e3648..6939d1bc 100644 --- a/pkg/properties/config.go +++ b/pkg/properties/config.go @@ -23,6 +23,7 @@ type Target struct { TerraformProvider bool `json:"terraform_provider" yaml:"terraform_provider"` } +// ParseConfig initialize Config instance using input data from YAML file func ParseConfig(input []byte) (*Config, error) { var ans Config err := content.Unmarshal(input, &ans) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index b19999aa..999f7019 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -111,6 +111,7 @@ type SpecParamProfile struct { FromVersion string `json:"from_version" yaml:"from_version"` } +// GetNormalizations get list of all specs (normalizations) func GetNormalizations() ([]string, error) { _, loc, _, ok := runtime.Caller(0) if !ok { @@ -139,6 +140,7 @@ func GetNormalizations() ([]string, error) { return files, nil } +// ParseSpec parse single spec (unmarshal file), add name variants for locations and params, add default types for params func ParseSpec(input []byte) (*Normalization, error) { var spec Normalization @@ -165,6 +167,7 @@ func ParseSpec(input []byte) (*Normalization, error) { return &spec, err } +// AddNameVariantsForLocation add name variants for location (under_score and CamelCase) func (spec *Normalization) AddNameVariantsForLocation() error { for key, location := range spec.Locations { location.Name = &NameVariant{ @@ -183,6 +186,7 @@ func (spec *Normalization) AddNameVariantsForLocation() error { return nil } +// AddNameVariantsForParams recursively add name variants for params for nested specs func AddNameVariantsForParams(name string, param *SpecParam) error { param.Name = &NameVariant{ Underscore: name, @@ -203,6 +207,7 @@ func AddNameVariantsForParams(name string, param *SpecParam) error { return nil } +// AddNameVariantsForParams add name variants for params (under_score and CamelCase) func (spec *Normalization) AddNameVariantsForParams() error { if spec.Spec != nil { for key, param := range spec.Spec.Params { @@ -219,6 +224,7 @@ func (spec *Normalization) AddNameVariantsForParams() error { return nil } +// AddDefaultTypesForParams recursively add default types for params for nested specs func AddDefaultTypesForParams(param *SpecParam) error { if param.Type == "" { param.Type = "string" @@ -241,7 +247,7 @@ func AddDefaultTypesForParams(param *SpecParam) error { } } -// AddDefaultTypesForParams ensures all SpecParams within Spec have a default type if not specified. +// AddDefaultTypesForParams ensures all params within Spec have a default type if not specified. func (spec *Normalization) AddDefaultTypesForParams() error { if spec.Spec != nil { for _, childParam := range spec.Spec.Params { @@ -260,6 +266,7 @@ func (spec *Normalization) AddDefaultTypesForParams() error { } } +// Sanity basic checks for specification (normalization) e.g. check if at least 1 location is defined func (spec *Normalization) Sanity() error { if spec.Name == "" { return errors.New("name is required") @@ -274,6 +281,7 @@ func (spec *Normalization) Sanity() error { return nil } +// Validate validations for specification (normalization) e.g. check if XPath contain / func (spec *Normalization) Validate() []error { var checks []error diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 0c69ac7e..7087af7d 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -7,6 +7,7 @@ import ( "strings" ) +// AsEntryXpath functions used in location.tmpl to generate XPath for location func AsEntryXpath(location, xpath string) (string, error) { if !strings.Contains(xpath, "$") || !strings.Contains(xpath, "}") { return "", errors.New("$ followed by } should exists in xpath'") @@ -16,6 +17,38 @@ func AsEntryXpath(location, xpath string) (string, error) { return "util.AsEntryXpath([]string{o." + location + "." + xpath + "}),", nil } +// NormalizeAssignment generates a string, which contains entry assignment in Normalize() function +// in entry.tmpl template. If param contains nested specs, then recursively are executed internal functions, +// which are declaring additional variables (function nestedObjectDeclaration()) and use them in +// entry assignment (function nestedObjectAssignment()) +func NormalizeAssignment(param *properties.SpecParam) string { + var builder strings.Builder + + if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { + builder.WriteString("entry." + param.Name.CamelCase + " = entryXml." + param.Name.CamelCase) + } else if param.Spec != nil { + for _, subParam := range param.Spec.Params { + builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) + } + builder.WriteString("entry." + param.Name.CamelCase + " = &Spec" + param.Name.CamelCase + "{\n") + for _, subParam := range param.Spec.Params { + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) + } + for _, subParam := range param.Spec.OneOf { + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) + } + builder.WriteString("}\n") + } else { + builder.WriteString("entry." + param.Name.CamelCase + " = entryXml." + param.Name.CamelCase) + } + + return builder.String() +} + +// SpecifyEntryAssignment generates a string, which contains entry assignment in SpecifyEntry() function +// in entry.tmpl template. If param contains nested specs, then recursively are executed internal functions, +// which are declaring additional variables (function nestedObjectDeclaration()) and use them in +// entry assignment (function nestedObjectAssignment()) func SpecifyEntryAssignment(param *properties.SpecParam) string { var builder strings.Builder @@ -103,30 +136,8 @@ func nestedObjectAssignment(parent []string, suffix string, param *properties.Sp return builder.String() } -func NormalizeAssignment(param *properties.SpecParam) string { - var builder strings.Builder - - if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { - builder.WriteString("entry." + param.Name.CamelCase + " = entryXml." + param.Name.CamelCase) - } else if param.Spec != nil { - for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) - } - builder.WriteString("entry." + param.Name.CamelCase + " = &Spec" + param.Name.CamelCase + "{\n") - for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) - } - for _, subParam := range param.Spec.OneOf { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) - } - builder.WriteString("}\n") - } else { - builder.WriteString("entry." + param.Name.CamelCase + " = entryXml." + param.Name.CamelCase) - } - - return builder.String() -} - +// SpecMatchesFunction return a string used in function SpecMatches() in entry.tmpl +// to compare all items of generated entry func SpecMatchesFunction(param *properties.SpecParam) string { calculatedFunction := "OptionalStringsMatch" if param.Type == "list" { diff --git a/pkg/translate/names.go b/pkg/translate/names.go index 3c62684a..d10a57dd 100644 --- a/pkg/translate/names.go +++ b/pkg/translate/names.go @@ -1,6 +1,6 @@ package translate -// PackageName Get package name from Go SDK path +// PackageName get package name from Go SDK path func PackageName(list []string) string { if len(list) == 0 { return "" diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 0d418955..14d2958b 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -5,6 +5,7 @@ import ( "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) +// LocationType function used in template location.tmpl to generate location type name func LocationType(location *properties.Location, pointer bool) string { prefix := "" if pointer { @@ -17,6 +18,7 @@ func LocationType(location *properties.Location, pointer bool) string { } } +// NestedSpecs go through all params and one of (recursively) and return map of all nested specs func NestedSpecs(spec *properties.Spec) (map[string]*properties.Spec, error) { nestedSpecs := make(map[string]*properties.Spec) @@ -41,6 +43,7 @@ func updateNestedSpecs(param *properties.SpecParam, nestedSpecs map[string]*prop } } +// SpecParamType return param type (it can be nested spec) (for struct based on spec from YAML files) func SpecParamType(param *properties.SpecParam) string { prefix := "" if !param.Required { @@ -62,6 +65,7 @@ func SpecParamType(param *properties.SpecParam) string { return prefix + calculatedType } +// XmlParamType return param type (it can be nested spec) (for struct based on spec from YAML files) func XmlParamType(param *properties.SpecParam) string { prefix := "" if !param.Required { @@ -80,6 +84,7 @@ func XmlParamType(param *properties.SpecParam) string { return prefix + calculatedType } +// XmlTag creates a string with xml tag (e.g. `xml:"description,omitempty"`) func XmlTag(param *properties.SpecParam) string { suffix := "" if !param.Required { @@ -93,6 +98,7 @@ func XmlTag(param *properties.SpecParam) string { return calculatedTag } +// OmitEmpty return omitempty in XML tag for location, if there are variables defined func OmitEmpty(location *properties.Location) string { if location.Vars != nil { return ",omitempty" From 2939d86c87d38737925d18f574c723eb2ac0dce9 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 13 Mar 2024 09:48:27 +0100 Subject: [PATCH 12/22] replace all string concatenation using "+" by function fmt.Sprintf() --- pkg/generate/assets.go | 3 ++- pkg/generate/generator.go | 2 +- pkg/translate/funcs.go | 50 +++++++++++++++++---------------------- pkg/translate/structs.go | 9 +++---- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/pkg/generate/assets.go b/pkg/generate/assets.go index 7e20fa66..9cabbb4b 100644 --- a/pkg/generate/assets.go +++ b/pkg/generate/assets.go @@ -2,6 +2,7 @@ package generate import ( "bytes" + "fmt" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" "io" "io/fs" @@ -56,7 +57,7 @@ func listAssets(asset *properties.Asset) ([]string, error) { // copyAsset copy single asset, which may contain multiple files func copyAsset(target string, asset *properties.Asset, files []string) error { // Prepare destination path - destinationDir := target + "/" + asset.Destination + destinationDir := fmt.Sprintf("%s/%s", target, asset.Destination) // Create the destination directory if it doesn't exist if err := os.MkdirAll(destinationDir, os.ModePerm); err != nil { diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index d401ddc4..ca3dbf8d 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -76,7 +76,7 @@ func (c *Creator) RenderTemplate() error { // createFullFilePath returns a full path for output file generated from template passed as argument to function func (c *Creator) createFullFilePath(templateName string) string { fileBaseName := strings.TrimSuffix(templateName, filepath.Ext(templateName)) - return filepath.Join(c.GoOutputDir, filepath.Join(c.Spec.GoSdkPath...), fileBaseName+".go") + return filepath.Join(c.GoOutputDir, filepath.Join(c.Spec.GoSdkPath...), fmt.Sprintf("%s.go", fileBaseName)) } // listOfTemplates return list of templates defined in TemplatesDir diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 7087af7d..1fa3e50f 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -2,6 +2,7 @@ package translate import ( "errors" + "fmt" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" "strings" @@ -14,7 +15,8 @@ func AsEntryXpath(location, xpath string) (string, error) { } xpath = strings.TrimSpace(strings.Split(strings.Split(xpath, "$")[1], "}")[0]) xpath = naming.CamelCase("", xpath, "", true) - return "util.AsEntryXpath([]string{o." + location + "." + xpath + "}),", nil + asEntryXpath := fmt.Sprintf("util.AsEntryXpath([]string{o.%s.%s}),", location, xpath) + return asEntryXpath, nil } // NormalizeAssignment generates a string, which contains entry assignment in Normalize() function @@ -25,12 +27,12 @@ func NormalizeAssignment(param *properties.SpecParam) string { var builder strings.Builder if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { - builder.WriteString("entry." + param.Name.CamelCase + " = entryXml." + param.Name.CamelCase) + builder.WriteString(fmt.Sprintf("entry.%s = entryXml.%s", param.Name.CamelCase, param.Name.CamelCase)) } else if param.Spec != nil { for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) } - builder.WriteString("entry." + param.Name.CamelCase + " = &Spec" + param.Name.CamelCase + "{\n") + builder.WriteString(fmt.Sprintf("entry.%s = &Spec%s{\n", param.Name.CamelCase, param.Name.CamelCase)) for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) } @@ -39,7 +41,7 @@ func NormalizeAssignment(param *properties.SpecParam) string { } builder.WriteString("}\n") } else { - builder.WriteString("entry." + param.Name.CamelCase + " = entryXml." + param.Name.CamelCase) + builder.WriteString(fmt.Sprintf("entry.%s = entryXml.%s", param.Name.CamelCase, param.Name.CamelCase)) } return builder.String() @@ -53,12 +55,12 @@ func SpecifyEntryAssignment(param *properties.SpecParam) string { var builder strings.Builder if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { - builder.WriteString("entry." + param.Name.CamelCase + " = util.StrToMem(o." + param.Name.CamelCase + ")") + builder.WriteString(fmt.Sprintf("entry.%s = util.StrToMem(o.%s)", param.Name.CamelCase, param.Name.CamelCase)) } else if param.Spec != nil { for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) } - builder.WriteString("entry." + param.Name.CamelCase + " = &Spec" + param.Name.CamelCase + "Xml{\n") + builder.WriteString(fmt.Sprintf("entry.%s = &Spec%sXml{\n", param.Name.CamelCase, param.Name.CamelCase)) for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "Xml", subParam)) } @@ -67,7 +69,7 @@ func SpecifyEntryAssignment(param *properties.SpecParam) string { } builder.WriteString("}\n") } else { - builder.WriteString("entry." + param.Name.CamelCase + " = o." + param.Name.CamelCase) + builder.WriteString(fmt.Sprintf("entry.%s = o.%s", param.Name.CamelCase, param.Name.CamelCase)) } return builder.String() @@ -92,15 +94,13 @@ func nestedObjectDeclaration(parent []string, param *properties.SpecParam) strin func declareVariableForNestedObject(parent []string, param *properties.SpecParam, subParam *properties.SpecParam) string { if subParam.Spec == nil && parent != nil { - return "nested" + - strings.Join(parent, "") + - param.Name.CamelCase + - subParam.Name.CamelCase + - " := o." + - strings.Join(parent, ".") + - "." + param.Name.CamelCase + - "." + subParam.Name.CamelCase + - "\n" + return fmt.Sprintf("nested%s%s%s := o.%s.%s.%s\n", + strings.Join(parent, ""), + param.Name.CamelCase, + subParam.Name.CamelCase, + strings.Join(parent, "."), + param.Name.CamelCase, + subParam.Name.CamelCase) } else { return "" } @@ -110,15 +110,11 @@ func nestedObjectAssignment(parent []string, suffix string, param *properties.Sp var builder strings.Builder if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { - builder.WriteString(param.Name.CamelCase + - " : util.StrToMem(o." + - param.Name.CamelCase + - "),\n") + builder.WriteString(fmt.Sprintf("%s : util.StrToMem(o.%s),\n", + param.Name.CamelCase, param.Name.CamelCase)) } else if param.Spec != nil { - builder.WriteString(param.Name.CamelCase + - " : &Spec" + - param.Name.CamelCase + - suffix + "{\n") + builder.WriteString(fmt.Sprintf("%s : &Spec%s%s{\n", + param.Name.CamelCase, param.Name.CamelCase, suffix)) for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), suffix, subParam)) } @@ -127,10 +123,8 @@ func nestedObjectAssignment(parent []string, suffix string, param *properties.Sp } builder.WriteString("},\n") } else { - builder.WriteString(param.Name.CamelCase + - " : nested" + - strings.Join(parent, "") + - param.Name.CamelCase + ",\n") + builder.WriteString(fmt.Sprintf("%s : nested%s%s,\n", + param.Name.CamelCase, strings.Join(parent, ""), param.Name.CamelCase)) } return builder.String() diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 14d2958b..bdf99de8 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -1,6 +1,7 @@ package translate import ( + "fmt" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) @@ -12,7 +13,7 @@ func LocationType(location *properties.Location, pointer bool) string { prefix = "*" } if location.Vars != nil { - return prefix + location.Name.CamelCase + "Location" + return fmt.Sprintf("%s%sLocation", prefix, location.Name.CamelCase) } else { return "bool" } @@ -57,7 +58,7 @@ func SpecParamType(param *properties.SpecParam) string { if param.Type == "list" && param.Items != nil { calculatedType = param.Items.Type } else if param.Spec != nil { - calculatedType = "Spec" + naming.CamelCase("", param.Name.CamelCase, "", true) + calculatedType = fmt.Sprintf("Spec%s", naming.CamelCase("", param.Name.CamelCase, "", true)) } else { calculatedType = param.Type } @@ -76,7 +77,7 @@ func XmlParamType(param *properties.SpecParam) string { if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { calculatedType = "util.MemberType" } else if param.Spec != nil { - calculatedType = "Spec" + naming.CamelCase("", param.Name.CamelCase, "", true) + "Xml" + calculatedType = fmt.Sprintf("Spec%sXml", naming.CamelCase("", param.Name.CamelCase, "", true)) } else { calculatedType = param.Type } @@ -93,7 +94,7 @@ func XmlTag(param *properties.SpecParam) string { calculatedTag := "" if param.Profiles != nil && len(param.Profiles) > 0 { - calculatedTag = "`xml:\"" + param.Profiles[0].Xpath[len(param.Profiles[0].Xpath)-1] + suffix + "\"`" + calculatedTag = fmt.Sprintf("`xml:\"%s%s\"`", param.Profiles[0].Xpath[len(param.Profiles[0].Xpath)-1], suffix) } return calculatedTag } From b51801b0c4c9861221b41aa79c3022c24a43ac5d Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 13 Mar 2024 11:21:38 +0100 Subject: [PATCH 13/22] for item with type object, render []string, which will contain all members --- pkg/properties/normalized.go | 1 + pkg/properties/normalized_test.go | 1 + pkg/translate/funcs.go | 4 ++-- pkg/translate/structs.go | 6 +++++- specs/objects/address-group.yaml | 1 + 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 999f7019..c3ccd6d6 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -97,6 +97,7 @@ type SpecParamCount struct { type SpecParamItems struct { Type string `json:"type" yaml:"type"` Length *SpecParamItemsLength `json:"length" yaml:"length"` + Ref []*string `json:"ref" yaml:"ref"` } type SpecParamItemsLength struct { diff --git a/pkg/properties/normalized_test.go b/pkg/properties/normalized_test.go index 767d3898..9fc3a67c 100644 --- a/pkg/properties/normalized_test.go +++ b/pkg/properties/normalized_test.go @@ -283,6 +283,7 @@ spec: length: min: null max: 127 + ref: [] profiles: - xpath: - tag diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 1fa3e50f..362d85c3 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -27,7 +27,7 @@ func NormalizeAssignment(param *properties.SpecParam) string { var builder strings.Builder if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { - builder.WriteString(fmt.Sprintf("entry.%s = entryXml.%s", param.Name.CamelCase, param.Name.CamelCase)) + builder.WriteString(fmt.Sprintf("entry.%s = o.%s", param.Name.CamelCase, param.Name.CamelCase)) } else if param.Spec != nil { for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) @@ -41,7 +41,7 @@ func NormalizeAssignment(param *properties.SpecParam) string { } builder.WriteString("}\n") } else { - builder.WriteString(fmt.Sprintf("entry.%s = entryXml.%s", param.Name.CamelCase, param.Name.CamelCase)) + builder.WriteString(fmt.Sprintf("entry.%s = o.%s", param.Name.CamelCase, param.Name.CamelCase)) } return builder.String() diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index bdf99de8..8f4a427f 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -56,7 +56,11 @@ func SpecParamType(param *properties.SpecParam) string { calculatedType := "" if param.Type == "list" && param.Items != nil { - calculatedType = param.Items.Type + if param.Items.Type == "object" && param.Items.Ref != nil { + calculatedType = "string" + } else { + calculatedType = param.Items.Type + } } else if param.Spec != nil { calculatedType = fmt.Sprintf("Spec%s", naming.CamelCase("", param.Name.CamelCase, "", true)) } else { diff --git a/specs/objects/address-group.yaml b/specs/objects/address-group.yaml index 90005d15..0cd7a476 100644 --- a/specs/objects/address-group.yaml +++ b/specs/objects/address-group.yaml @@ -100,6 +100,7 @@ spec: type: 'object' ref: - '#/specs/objects/address.yaml' + - '#/specs/objects/address-group.yaml' profiles: - type: 'member' From 6461db6a6536449b5ce0a22c5459f8c6bfba0de0 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 13 Mar 2024 11:53:15 +0100 Subject: [PATCH 14/22] refactor NormalizeAssignment(), SpecifyEntryAssignment(), fix some of the issues from `golangci-lint run --enable-all` --- cmd/codegen/main.go | 6 ++--- pkg/generate/assets.go | 6 ++--- pkg/generate/generator.go | 18 ++++++------- pkg/load/file.go | 1 + pkg/properties/config.go | 2 +- pkg/properties/config_test.go | 4 +-- pkg/properties/normalized.go | 16 ++++++------ pkg/properties/normalized_test.go | 2 +- pkg/translate/funcs.go | 42 ++++++++++--------------------- pkg/translate/names.go | 2 +- pkg/translate/structs.go | 12 ++++----- 11 files changed, 47 insertions(+), 64 deletions(-) diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go index bf1dde56..e2003efe 100644 --- a/cmd/codegen/main.go +++ b/cmd/codegen/main.go @@ -8,13 +8,13 @@ import ( "github.com/paloaltonetworks/pan-os-codegen/pkg/commands/codegen" ) -// Config holds the configuration values for the application +// Config holds the configuration values for the application. type Config struct { ConfigFile string OpType string } -// parseFlags parses the command line flags +// parseFlags parses the command line flags. func parseFlags() Config { var cfg Config flag.StringVar(&cfg.ConfigFile, "config", "./cmd/codegen/config.yaml", "Path to the configuration file") @@ -24,7 +24,7 @@ func parseFlags() Config { return cfg } -// runCommand executed command to generate code for SDK or Terraform +// runCommand executed command to generate code for SDK or Terraform. func runCommand(ctx context.Context, cmdType codegen.CommandType, cfg string) { cmd, err := codegen.NewCommand(ctx, cmdType, cfg) if err != nil { diff --git a/pkg/generate/assets.go b/pkg/generate/assets.go index 9cabbb4b..dabb50d9 100644 --- a/pkg/generate/assets.go +++ b/pkg/generate/assets.go @@ -11,7 +11,7 @@ import ( "path/filepath" ) -// CopyAssets copy assets (static files) according to configuration +// CopyAssets copy assets (static files) according to configuration. func CopyAssets(config *properties.Config) error { for _, asset := range config.Assets { files, err := listAssets(asset) @@ -34,7 +34,7 @@ func CopyAssets(config *properties.Config) error { return nil } -// listAssets walk through directory and get list of all assets (static files) +// listAssets walk through directory and get list of all assets (static files). func listAssets(asset *properties.Asset) ([]string, error) { var files []string @@ -54,7 +54,7 @@ func listAssets(asset *properties.Asset) ([]string, error) { return files, nil } -// copyAsset copy single asset, which may contain multiple files +// copyAsset copy single asset, which may contain multiple files. func copyAsset(target string, asset *properties.Asset, files []string) error { // Prepare destination path destinationDir := fmt.Sprintf("%s/%s", target, asset.Destination) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index ca3dbf8d..6e671302 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -20,7 +20,7 @@ type Creator struct { Spec *properties.Normalization } -// NewCreator initialize Creator instance +// NewCreator initialize Creator instance. func NewCreator(goOutputDir, templatesDir string, spec *properties.Normalization) *Creator { return &Creator{ GoOutputDir: goOutputDir, @@ -29,7 +29,7 @@ func NewCreator(goOutputDir, templatesDir string, spec *properties.Normalization } } -// RenderTemplate loop through all templates, parse them and render content, which is saved to output file +// RenderTemplate loop through all templates, parse them and render content, which is saved to output file. func (c *Creator) RenderTemplate() error { log.Println("Start rendering templates") @@ -73,13 +73,13 @@ func (c *Creator) RenderTemplate() error { return nil } -// createFullFilePath returns a full path for output file generated from template passed as argument to function +// createFullFilePath returns a full path for output file generated from template passed as argument to function. func (c *Creator) createFullFilePath(templateName string) string { fileBaseName := strings.TrimSuffix(templateName, filepath.Ext(templateName)) return filepath.Join(c.GoOutputDir, filepath.Join(c.Spec.GoSdkPath...), fmt.Sprintf("%s.go", fileBaseName)) } -// listOfTemplates return list of templates defined in TemplatesDir +// listOfTemplates return list of templates defined in TemplatesDir. func (c *Creator) listOfTemplates() ([]string, error) { var files []string err := filepath.WalkDir(c.TemplatesDir, func(path string, entry os.DirEntry, err error) error { @@ -100,13 +100,13 @@ func (c *Creator) listOfTemplates() ([]string, error) { return files, nil } -// makeAllDirs creates all required directories, which are in the file path +// makeAllDirs creates all required directories, which are in the file path. func (c *Creator) makeAllDirs(filePath string) error { dirPath := filepath.Dir(filePath) return os.MkdirAll(dirPath, os.ModePerm) } -// createFile just create a file and return it +// createFile just create a file and return it. func (c *Creator) createFile(filePath string) (*os.File, error) { outputFile, err := os.Create(filePath) if err != nil { @@ -115,7 +115,7 @@ func (c *Creator) createFile(filePath string) (*os.File, error) { return outputFile, nil } -// parseTemplate parse template passed as argument and with function map defined below +// parseTemplate parse template passed as argument and with function map defined below. func (c *Creator) parseTemplate(templateName string) (*template.Template, error) { templatePath := filepath.Join(c.TemplatesDir, templateName) funcMap := template.FuncMap{ @@ -128,9 +128,7 @@ func (c *Creator) parseTemplate(templateName string) (*template.Template, error) "normalizeAssignment": translate.NormalizeAssignment, "specMatchesFunction": translate.SpecMatchesFunction, "omitEmpty": translate.OmitEmpty, - "contains": func(full, part string) bool { - return strings.Contains(full, part) - }, + "contains": strings.Contains, "subtract": func(a, b int) int { return a - b }, diff --git a/pkg/load/file.go b/pkg/load/file.go index 7c89daf3..e774cc12 100644 --- a/pkg/load/file.go +++ b/pkg/load/file.go @@ -9,5 +9,6 @@ func File(path string) ([]byte, error) { if err != nil { return nil, err } + return content, err } diff --git a/pkg/properties/config.go b/pkg/properties/config.go index 6939d1bc..f40fe96e 100644 --- a/pkg/properties/config.go +++ b/pkg/properties/config.go @@ -23,7 +23,7 @@ type Target struct { TerraformProvider bool `json:"terraform_provider" yaml:"terraform_provider"` } -// ParseConfig initialize Config instance using input data from YAML file +// ParseConfig initialize Config instance using input data from YAML file. func ParseConfig(input []byte) (*Config, error) { var ans Config err := content.Unmarshal(input, &ans) diff --git a/pkg/properties/config_test.go b/pkg/properties/config_test.go index f08e45fe..8ecbbc7e 100644 --- a/pkg/properties/config_test.go +++ b/pkg/properties/config_test.go @@ -28,8 +28,8 @@ assets: assert.NotEmptyf(t, config.Output.GoSdk, "Config Go SDK path cannot be empty") assert.NotEmptyf(t, config.Output.TerraformProvider, "Config Terraform provider path cannot be empty") assert.NotEmpty(t, config.Assets) - assert.Equal(t, 1, len(config.Assets)) - assert.Equal(t, 1, len(config.Assets)) + assert.Len(t, config.Assets, 1) + assert.Len(t, config.Assets, 1) assert.Equal(t, "assets/util", config.Assets["util_package"].Source) assert.True(t, config.Assets["util_package"].Target.GoSdk) assert.False(t, config.Assets["util_package"].Target.TerraformProvider) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index c3ccd6d6..073466fc 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -112,7 +112,7 @@ type SpecParamProfile struct { FromVersion string `json:"from_version" yaml:"from_version"` } -// GetNormalizations get list of all specs (normalizations) +// GetNormalizations get list of all specs (normalizations). func GetNormalizations() ([]string, error) { _, loc, _, ok := runtime.Caller(0) if !ok { @@ -141,7 +141,7 @@ func GetNormalizations() ([]string, error) { return files, nil } -// ParseSpec parse single spec (unmarshal file), add name variants for locations and params, add default types for params +// ParseSpec parse single spec (unmarshal file), add name variants for locations and params, add default types for params. func ParseSpec(input []byte) (*Normalization, error) { var spec Normalization @@ -168,7 +168,7 @@ func ParseSpec(input []byte) (*Normalization, error) { return &spec, err } -// AddNameVariantsForLocation add name variants for location (under_score and CamelCase) +// AddNameVariantsForLocation add name variants for location (under_score and CamelCase). func (spec *Normalization) AddNameVariantsForLocation() error { for key, location := range spec.Locations { location.Name = &NameVariant{ @@ -187,7 +187,7 @@ func (spec *Normalization) AddNameVariantsForLocation() error { return nil } -// AddNameVariantsForParams recursively add name variants for params for nested specs +// AddNameVariantsForParams recursively add name variants for params for nested specs. func AddNameVariantsForParams(name string, param *SpecParam) error { param.Name = &NameVariant{ Underscore: name, @@ -208,7 +208,7 @@ func AddNameVariantsForParams(name string, param *SpecParam) error { return nil } -// AddNameVariantsForParams add name variants for params (under_score and CamelCase) +// AddNameVariantsForParams add name variants for params (under_score and CamelCase). func (spec *Normalization) AddNameVariantsForParams() error { if spec.Spec != nil { for key, param := range spec.Spec.Params { @@ -225,7 +225,7 @@ func (spec *Normalization) AddNameVariantsForParams() error { return nil } -// AddDefaultTypesForParams recursively add default types for params for nested specs +// AddDefaultTypesForParams recursively add default types for params for nested specs. func AddDefaultTypesForParams(param *SpecParam) error { if param.Type == "" { param.Type = "string" @@ -267,7 +267,7 @@ func (spec *Normalization) AddDefaultTypesForParams() error { } } -// Sanity basic checks for specification (normalization) e.g. check if at least 1 location is defined +// Sanity basic checks for specification (normalization) e.g. check if at least 1 location is defined. func (spec *Normalization) Sanity() error { if spec.Name == "" { return errors.New("name is required") @@ -282,7 +282,7 @@ func (spec *Normalization) Sanity() error { return nil } -// Validate validations for specification (normalization) e.g. check if XPath contain / +// Validate validations for specification (normalization) e.g. check if XPath contain /. func (spec *Normalization) Validate() []error { var checks []error diff --git a/pkg/properties/normalized_test.go b/pkg/properties/normalized_test.go index 9fc3a67c..98a49bd4 100644 --- a/pkg/properties/normalized_test.go +++ b/pkg/properties/normalized_test.go @@ -353,7 +353,7 @@ spec: // when yamlParsedData, _ := ParseSpec([]byte(sampleSpec)) yamlDump, _ := yaml.Marshal(&yamlParsedData) - //fmt.Printf("%s", string(yamlDump)) + // fmt.Printf("%s", string(yamlDump)) // then assert.NotNilf(t, yamlDump, "Marshalled data cannot be nil") diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 362d85c3..c946543c 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -8,7 +8,7 @@ import ( "strings" ) -// AsEntryXpath functions used in location.tmpl to generate XPath for location +// AsEntryXpath functions used in location.tmpl to generate XPath for location. func AsEntryXpath(location, xpath string) (string, error) { if !strings.Contains(xpath, "$") || !strings.Contains(xpath, "}") { return "", errors.New("$ followed by } should exists in xpath'") @@ -22,50 +22,34 @@ func AsEntryXpath(location, xpath string) (string, error) { // NormalizeAssignment generates a string, which contains entry assignment in Normalize() function // in entry.tmpl template. If param contains nested specs, then recursively are executed internal functions, // which are declaring additional variables (function nestedObjectDeclaration()) and use them in -// entry assignment (function nestedObjectAssignment()) +// entry assignment (function nestedObjectAssignment()). func NormalizeAssignment(param *properties.SpecParam) string { - var builder strings.Builder - - if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { - builder.WriteString(fmt.Sprintf("entry.%s = o.%s", param.Name.CamelCase, param.Name.CamelCase)) - } else if param.Spec != nil { - for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) - } - builder.WriteString(fmt.Sprintf("entry.%s = &Spec%s{\n", param.Name.CamelCase, param.Name.CamelCase)) - for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) - } - for _, subParam := range param.Spec.OneOf { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "", subParam)) - } - builder.WriteString("}\n") - } else { - builder.WriteString(fmt.Sprintf("entry.%s = o.%s", param.Name.CamelCase, param.Name.CamelCase)) - } - - return builder.String() + return prepareAssignment(param, "util.MemToStr", "") } // SpecifyEntryAssignment generates a string, which contains entry assignment in SpecifyEntry() function // in entry.tmpl template. If param contains nested specs, then recursively are executed internal functions, // which are declaring additional variables (function nestedObjectDeclaration()) and use them in -// entry assignment (function nestedObjectAssignment()) +// entry assignment (function nestedObjectAssignment()). func SpecifyEntryAssignment(param *properties.SpecParam) string { + return prepareAssignment(param, "util.StrToMem", "Xml") +} + +func prepareAssignment(param *properties.SpecParam, listFunction, specSuffix string) string { var builder strings.Builder if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { - builder.WriteString(fmt.Sprintf("entry.%s = util.StrToMem(o.%s)", param.Name.CamelCase, param.Name.CamelCase)) + builder.WriteString(fmt.Sprintf("entry.%s = %s(o.%s)", param.Name.CamelCase, listFunction, param.Name.CamelCase)) } else if param.Spec != nil { for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) } - builder.WriteString(fmt.Sprintf("entry.%s = &Spec%sXml{\n", param.Name.CamelCase, param.Name.CamelCase)) + builder.WriteString(fmt.Sprintf("entry.%s = &Spec%s%s{\n", param.Name.CamelCase, param.Name.CamelCase, specSuffix)) for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "Xml", subParam)) + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, specSuffix, subParam)) } for _, subParam := range param.Spec.OneOf { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, "Xml", subParam)) + builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, specSuffix, subParam)) } builder.WriteString("}\n") } else { @@ -131,7 +115,7 @@ func nestedObjectAssignment(parent []string, suffix string, param *properties.Sp } // SpecMatchesFunction return a string used in function SpecMatches() in entry.tmpl -// to compare all items of generated entry +// to compare all items of generated entry. func SpecMatchesFunction(param *properties.SpecParam) string { calculatedFunction := "OptionalStringsMatch" if param.Type == "list" { diff --git a/pkg/translate/names.go b/pkg/translate/names.go index d10a57dd..3b73be51 100644 --- a/pkg/translate/names.go +++ b/pkg/translate/names.go @@ -1,6 +1,6 @@ package translate -// PackageName get package name from Go SDK path +// PackageName get package name from Go SDK path. func PackageName(list []string) string { if len(list) == 0 { return "" diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 8f4a427f..2d180ad2 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -6,7 +6,7 @@ import ( "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" ) -// LocationType function used in template location.tmpl to generate location type name +// LocationType function used in template location.tmpl to generate location type name. func LocationType(location *properties.Location, pointer bool) string { prefix := "" if pointer { @@ -19,7 +19,7 @@ func LocationType(location *properties.Location, pointer bool) string { } } -// NestedSpecs go through all params and one of (recursively) and return map of all nested specs +// NestedSpecs go through all params and one of (recursively) and return map of all nested specs. func NestedSpecs(spec *properties.Spec) (map[string]*properties.Spec, error) { nestedSpecs := make(map[string]*properties.Spec) @@ -44,7 +44,7 @@ func updateNestedSpecs(param *properties.SpecParam, nestedSpecs map[string]*prop } } -// SpecParamType return param type (it can be nested spec) (for struct based on spec from YAML files) +// SpecParamType return param type (it can be nested spec) (for struct based on spec from YAML files). func SpecParamType(param *properties.SpecParam) string { prefix := "" if !param.Required { @@ -70,7 +70,7 @@ func SpecParamType(param *properties.SpecParam) string { return prefix + calculatedType } -// XmlParamType return param type (it can be nested spec) (for struct based on spec from YAML files) +// XmlParamType return param type (it can be nested spec) (for struct based on spec from YAML files). func XmlParamType(param *properties.SpecParam) string { prefix := "" if !param.Required { @@ -89,7 +89,7 @@ func XmlParamType(param *properties.SpecParam) string { return prefix + calculatedType } -// XmlTag creates a string with xml tag (e.g. `xml:"description,omitempty"`) +// XmlTag creates a string with xml tag (e.g. `xml:"description,omitempty"`). func XmlTag(param *properties.SpecParam) string { suffix := "" if !param.Required { @@ -103,7 +103,7 @@ func XmlTag(param *properties.SpecParam) string { return calculatedTag } -// OmitEmpty return omitempty in XML tag for location, if there are variables defined +// OmitEmpty return omitempty in XML tag for location, if there are variables defined. func OmitEmpty(location *properties.Location) string { if location.Vars != nil { return ",omitempty" From 30bcb0d5b3cce64c1c3dc156ed58382a45f7e7ad Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Wed, 13 Mar 2024 14:50:40 +0100 Subject: [PATCH 15/22] extend template functions after tests for service.yaml --- pkg/translate/funcs.go | 7 +++++-- pkg/translate/funcs_test.go | 4 ++-- pkg/translate/structs.go | 23 ++++++++++++----------- pkg/translate/structs_test.go | 12 ++++++------ specs/objects/service.yaml | 34 ++++++++++++++++++---------------- templates/sdk/entry.tmpl | 16 ++++++++-------- 6 files changed, 51 insertions(+), 45 deletions(-) diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index c946543c..cf2ecd9a 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -44,6 +44,9 @@ func prepareAssignment(param *properties.SpecParam, listFunction, specSuffix str for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) } + for _, subParam := range param.Spec.OneOf { + builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) + } builder.WriteString(fmt.Sprintf("entry.%s = &Spec%s%s{\n", param.Name.CamelCase, param.Name.CamelCase, specSuffix)) for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, specSuffix, subParam)) @@ -97,8 +100,8 @@ func nestedObjectAssignment(parent []string, suffix string, param *properties.Sp builder.WriteString(fmt.Sprintf("%s : util.StrToMem(o.%s),\n", param.Name.CamelCase, param.Name.CamelCase)) } else if param.Spec != nil { - builder.WriteString(fmt.Sprintf("%s : &Spec%s%s{\n", - param.Name.CamelCase, param.Name.CamelCase, suffix)) + builder.WriteString(fmt.Sprintf("%s : &Spec%s%s%s{\n", + param.Name.CamelCase, strings.Join(parent, ""), param.Name.CamelCase, suffix)) for _, subParam := range param.Spec.Params { builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), suffix, subParam)) } diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index 93a7edcc..e04fc087 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -87,7 +87,7 @@ func TestSpecifyEntryAssignmentForNestedObject(t *testing.T) { } expectedAssignmentStreing := `nestedABC := o.A.B.C entry.A = &SpecAXml{ -B : &SpecBXml{ +B : &SpecABXml{ C : nestedABC, }, } @@ -133,7 +133,7 @@ func TestNormalizeAssignmentForNestedObject(t *testing.T) { } expectedAssignmentStreing := `nestedABC := o.A.B.C entry.A = &SpecA{ -B : &SpecB{ +B : &SpecAB{ C : nestedABC, }, } diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 2d180ad2..cfc15670 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" + "strings" ) // LocationType function used in template location.tmpl to generate location type name. @@ -23,29 +24,29 @@ func LocationType(location *properties.Location, pointer bool) string { func NestedSpecs(spec *properties.Spec) (map[string]*properties.Spec, error) { nestedSpecs := make(map[string]*properties.Spec) - checkNestedSpecs(spec, nestedSpecs) + checkNestedSpecs([]string{}, spec, nestedSpecs) return nestedSpecs, nil } -func checkNestedSpecs(spec *properties.Spec, nestedSpecs map[string]*properties.Spec) { +func checkNestedSpecs(parent []string, spec *properties.Spec, nestedSpecs map[string]*properties.Spec) { for _, param := range spec.Params { - updateNestedSpecs(param, nestedSpecs) + updateNestedSpecs(append(parent, param.Name.CamelCase), param, nestedSpecs) } for _, param := range spec.OneOf { - updateNestedSpecs(param, nestedSpecs) + updateNestedSpecs(append(parent, param.Name.CamelCase), param, nestedSpecs) } } -func updateNestedSpecs(param *properties.SpecParam, nestedSpecs map[string]*properties.Spec) { +func updateNestedSpecs(parent []string, param *properties.SpecParam, nestedSpecs map[string]*properties.Spec) { if param.Spec != nil { - nestedSpecs[naming.CamelCase("", param.Name.CamelCase, "", true)] = param.Spec - checkNestedSpecs(param.Spec, nestedSpecs) + nestedSpecs[strings.Join(parent, "")] = param.Spec + checkNestedSpecs(parent, param.Spec, nestedSpecs) } } // SpecParamType return param type (it can be nested spec) (for struct based on spec from YAML files). -func SpecParamType(param *properties.SpecParam) string { +func SpecParamType(parent string, param *properties.SpecParam) string { prefix := "" if !param.Required { prefix = "*" @@ -62,7 +63,7 @@ func SpecParamType(param *properties.SpecParam) string { calculatedType = param.Items.Type } } else if param.Spec != nil { - calculatedType = fmt.Sprintf("Spec%s", naming.CamelCase("", param.Name.CamelCase, "", true)) + calculatedType = fmt.Sprintf("Spec%s%s", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) } else { calculatedType = param.Type } @@ -71,7 +72,7 @@ func SpecParamType(param *properties.SpecParam) string { } // XmlParamType return param type (it can be nested spec) (for struct based on spec from YAML files). -func XmlParamType(param *properties.SpecParam) string { +func XmlParamType(parent string, param *properties.SpecParam) string { prefix := "" if !param.Required { prefix = "*" @@ -81,7 +82,7 @@ func XmlParamType(param *properties.SpecParam) string { if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { calculatedType = "util.MemberType" } else if param.Spec != nil { - calculatedType = fmt.Sprintf("Spec%sXml", naming.CamelCase("", param.Name.CamelCase, "", true)) + calculatedType = fmt.Sprintf("Spec%s%sXml", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) } else { calculatedType = param.Type } diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 27085549..415be26d 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -85,9 +85,9 @@ func TestSpecParamType(t *testing.T) { } // when - calculatedTypeRequiredString := SpecParamType(¶mTypeRequiredString) - calculatedTypeListString := SpecParamType(¶mTypeListString) - calculatedTypeOptionalString := SpecParamType(¶mTypeOptionalString) + calculatedTypeRequiredString := SpecParamType("", ¶mTypeRequiredString) + calculatedTypeListString := SpecParamType("", ¶mTypeListString) + calculatedTypeOptionalString := SpecParamType("", ¶mTypeOptionalString) // then assert.Equal(t, "string", calculatedTypeRequiredString) @@ -139,8 +139,8 @@ func TestXmlParamType(t *testing.T) { } // when - calculatedTypeRequiredString := XmlParamType(¶mTypeRequiredString) - calculatedTypeListString := XmlParamType(¶mTypeListString) + calculatedTypeRequiredString := XmlParamType("", ¶mTypeRequiredString) + calculatedTypeListString := XmlParamType("", ¶mTypeListString) // then assert.Equal(t, "string", calculatedTypeRequiredString) @@ -220,5 +220,5 @@ func TestNestedSpecs(t *testing.T) { // then assert.NotNil(t, nestedSpecs) assert.Contains(t, nestedSpecs, "A") - assert.Contains(t, nestedSpecs, "B") + assert.Contains(t, nestedSpecs, "AB") } diff --git a/specs/objects/service.yaml b/specs/objects/service.yaml index cb9d969f..9dd7705d 100644 --- a/specs/objects/service.yaml +++ b/specs/objects/service.yaml @@ -131,18 +131,19 @@ spec: - xpath: [ "yes"] spec: - 'timeout': - description: 'TCP session timeout value (in second)' - profiles: - - xpath: [ "timeout" ] - 'halfclose-timeout': - description: 'TCP session half-close timeout value (in second)' - profiles: - - xpath: [ "halfclose-timeout" ] - 'timewait-timeout': - description: 'TCP session time-wait timeout value (in second)' - profiles: - - xpath: [ "timewait-timeout" ] + params: + 'timeout': + description: 'TCP session timeout value (in second)' + profiles: + - xpath: [ "timeout" ] + 'halfclose-timeout': + description: 'TCP session half-close timeout value (in second)' + profiles: + - xpath: [ "halfclose-timeout" ] + 'timewait-timeout': + description: 'TCP session time-wait timeout value (in second)' + profiles: + - xpath: [ "timewait-timeout" ] no: profiles: - xpath: [ "no" ] @@ -180,10 +181,11 @@ spec: - xpath: [ "yes"] spec: - 'timeout': - description: 'UDP session timeout value (in second)' - profiles: - - xpath: [ "timeout" ] + params: + 'timeout': + description: 'UDP session timeout value (in second)' + profiles: + - xpath: [ "timeout" ] no: profiles: - xpath: [ "no" ] diff --git a/templates/sdk/entry.tmpl b/templates/sdk/entry.tmpl index a21b27d7..fef8ef54 100644 --- a/templates/sdk/entry.tmpl +++ b/templates/sdk/entry.tmpl @@ -26,10 +26,10 @@ var ( type Entry struct { Name string {{- range $_, $param := .Spec.Params}} - {{$param.Name.CamelCase}} {{specParamType $param}} + {{$param.Name.CamelCase}} {{specParamType "" $param}} {{- end}} {{- range $_, $param := .Spec.OneOf}} - {{$param.Name.CamelCase}} {{specParamType $param}} + {{$param.Name.CamelCase}} {{specParamType "" $param}} {{- end}} Misc map[string][]generic.Xml @@ -38,10 +38,10 @@ type Entry struct { {{- range $name, $spec := nestedSpecs .Spec }} type Spec{{$name}} struct { {{- range $_, $param := $spec.Params}} - {{$param.Name.CamelCase}} {{specParamType $param}} + {{$param.Name.CamelCase}} {{specParamType $name $param}} {{- end}} {{- range $_, $param := $spec.OneOf}} - {{$param.Name.CamelCase}} {{specParamType $param}} + {{$param.Name.CamelCase}} {{specParamType $name $param}} {{- end}} } {{- end}} @@ -54,10 +54,10 @@ type EntryXml struct { XMLName xml.Name `xml:"entry"` Name string `xml:"name,attr"` {{- range $_, $param := .Spec.Params}} - {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}} {{xmlTag $param}} {{- end}} {{- range $_, $param := .Spec.OneOf}} - {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} + {{$param.Name.CamelCase}} {{xmlParamType "" $param}} {{xmlTag $param}} {{- end}} Misc []generic.Xml `xml:",any"` @@ -66,10 +66,10 @@ type EntryXml struct { {{- range $name, $spec := nestedSpecs .Spec }} type Spec{{$name}}Xml struct { {{- range $_, $param := $spec.Params}} - {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}} {{xmlTag $param}} {{- end}} {{- range $_, $param := $spec.OneOf}} - {{$param.Name.CamelCase}} {{xmlParamType $param}} {{xmlTag $param}} + {{$param.Name.CamelCase}} {{xmlParamType $name $param}} {{xmlTag $param}} {{- end}} } {{- end}} From 743ac40d68d3d742c7f7ad9f611601b7e6d1dea6 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 14 Mar 2024 13:28:07 +0100 Subject: [PATCH 16/22] apply changes after review (#1) --- pkg/properties/normalized.go | 39 +++++++++++++------------------ pkg/properties/normalized_test.go | 1 - pkg/translate/funcs.go | 15 +++++++++--- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 073466fc..8d599391 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -225,41 +225,34 @@ func (spec *Normalization) AddNameVariantsForParams() error { return nil } -// AddDefaultTypesForParams recursively add default types for params for nested specs. -func AddDefaultTypesForParams(param *SpecParam) error { - if param.Type == "" { - param.Type = "string" - } +// addDefaultTypesForParams recursively add default types for params for nested specs. +func addDefaultTypesForParams(params map[string]*SpecParam) error { + for _, param := range params { + if param.Type == "" { + param.Type = "string" + } - if param.Spec != nil { - for _, childParam := range param.Spec.Params { - if err := AddDefaultTypesForParams(childParam); err != nil { + if param.Spec != nil { + if err := addDefaultTypesForParams(param.Spec.Params); err != nil { return err } - } - for _, childParam := range param.Spec.OneOf { - if err := AddDefaultTypesForParams(childParam); err != nil { + if err := addDefaultTypesForParams(param.Spec.OneOf); err != nil { return err } } - return nil - } else { - return nil } + + return nil } -// AddDefaultTypesForParams ensures all params within Spec have a default type if not specified. +// addDefaultTypesForParams ensures all params within Spec have a default type if not specified. func (spec *Normalization) AddDefaultTypesForParams() error { if spec.Spec != nil { - for _, childParam := range spec.Spec.Params { - if err := AddDefaultTypesForParams(childParam); err != nil { - return err - } + if err := addDefaultTypesForParams(spec.Spec.Params); err != nil { + return err } - for _, childParam := range spec.Spec.OneOf { - if err := AddDefaultTypesForParams(childParam); err != nil { - return err - } + if err := addDefaultTypesForParams(spec.Spec.OneOf); err != nil { + return err } return nil } else { diff --git a/pkg/properties/normalized_test.go b/pkg/properties/normalized_test.go index 98a49bd4..e722344a 100644 --- a/pkg/properties/normalized_test.go +++ b/pkg/properties/normalized_test.go @@ -353,7 +353,6 @@ spec: // when yamlParsedData, _ := ParseSpec([]byte(sampleSpec)) yamlDump, _ := yaml.Marshal(&yamlParsedData) - // fmt.Printf("%s", string(yamlDump)) // then assert.NotNilf(t, yamlDump, "Marshalled data cannot be nil") diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index cf2ecd9a..8a5a1ec2 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -13,12 +13,21 @@ func AsEntryXpath(location, xpath string) (string, error) { if !strings.Contains(xpath, "$") || !strings.Contains(xpath, "}") { return "", errors.New("$ followed by } should exists in xpath'") } - xpath = strings.TrimSpace(strings.Split(strings.Split(xpath, "$")[1], "}")[0]) - xpath = naming.CamelCase("", xpath, "", true) - asEntryXpath := fmt.Sprintf("util.AsEntryXpath([]string{o.%s.%s}),", location, xpath) + asEntryXpath := generateXpathForLocation(location, xpath) return asEntryXpath, nil } +func generateXpathForLocation(location string, xpath string) string { + xpathPartWithoutDollar := strings.SplitAfter(xpath, "$") + xpathPartWithoutBrackets := strings.TrimSpace(strings.Trim(xpathPartWithoutDollar[1], "${}")) + xpathPartCamelCase := naming.CamelCase("", xpathPartWithoutBrackets, "", true) + asEntryXpath := fmt.Sprintf("util.AsEntryXpath([]string{o.%s.%s}),", location, xpathPartCamelCase) + //xpath = strings.TrimSpace(strings.Split(strings.Split(xpath, "$")[1], "}")[0]) + //xpath = naming.CamelCase("", xpath, "", true) + //asEntryXpath := fmt.Sprintf("util.AsEntryXpath([]string{o.%s.%s}),", location, xpath) + return asEntryXpath +} + // NormalizeAssignment generates a string, which contains entry assignment in Normalize() function // in entry.tmpl template. If param contains nested specs, then recursively are executed internal functions, // which are declaring additional variables (function nestedObjectDeclaration()) and use them in From f0e47db74643db254d9d20d18d024d6c7bf950c3 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 14 Mar 2024 13:28:32 +0100 Subject: [PATCH 17/22] remove comments --- pkg/translate/funcs.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 8a5a1ec2..7c7d8b83 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -22,9 +22,6 @@ func generateXpathForLocation(location string, xpath string) string { xpathPartWithoutBrackets := strings.TrimSpace(strings.Trim(xpathPartWithoutDollar[1], "${}")) xpathPartCamelCase := naming.CamelCase("", xpathPartWithoutBrackets, "", true) asEntryXpath := fmt.Sprintf("util.AsEntryXpath([]string{o.%s.%s}),", location, xpathPartCamelCase) - //xpath = strings.TrimSpace(strings.Split(strings.Split(xpath, "$")[1], "}")[0]) - //xpath = naming.CamelCase("", xpath, "", true) - //asEntryXpath := fmt.Sprintf("util.AsEntryXpath([]string{o.%s.%s}),", location, xpath) return asEntryXpath } From a0edfa2039baa8c0bc8d8f725619b3f5f00cac5d Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 14 Mar 2024 13:32:25 +0100 Subject: [PATCH 18/22] use fmt.Errorf to return errors --- pkg/properties/normalized.go | 15 +++++++-------- pkg/translate/funcs.go | 3 +-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index 8d599391..a665b277 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -1,7 +1,6 @@ package properties import ( - "errors" "fmt" "github.com/paloaltonetworks/pan-os-codegen/pkg/content" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" @@ -263,13 +262,13 @@ func (spec *Normalization) AddDefaultTypesForParams() error { // Sanity basic checks for specification (normalization) e.g. check if at least 1 location is defined. func (spec *Normalization) Sanity() error { if spec.Name == "" { - return errors.New("name is required") + return fmt.Errorf("name is required") } if spec.Locations == nil { - return errors.New("at least 1 location is required") + return fmt.Errorf("at least 1 location is required") } if spec.GoSdkPath == nil { - return errors.New("golang SDK path is required") + return fmt.Errorf("golang SDK path is required") } return nil @@ -280,18 +279,18 @@ func (spec *Normalization) Validate() []error { var checks []error if strings.Contains(spec.TerraformProviderSuffix, "panos_") { - checks = append(checks, errors.New("suffix for Terraform provider cannot contain `panos_`")) + checks = append(checks, fmt.Errorf("suffix for Terraform provider cannot contain `panos_`")) } for _, suffix := range spec.XpathSuffix { if strings.Contains(suffix, "/") { - checks = append(checks, errors.New("XPath cannot contain /")) + checks = append(checks, fmt.Errorf("XPath cannot contain /")) } } if len(spec.Locations) < 1 { - checks = append(checks, errors.New("at least 1 location is required")) + checks = append(checks, fmt.Errorf("at least 1 location is required")) } if len(spec.GoSdkPath) < 2 { - checks = append(checks, errors.New("golang SDK path should contain at least 2 elements of the path")) + checks = append(checks, fmt.Errorf("golang SDK path should contain at least 2 elements of the path")) } return checks diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 7c7d8b83..1328d8fe 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -1,7 +1,6 @@ package translate import ( - "errors" "fmt" "github.com/paloaltonetworks/pan-os-codegen/pkg/naming" "github.com/paloaltonetworks/pan-os-codegen/pkg/properties" @@ -11,7 +10,7 @@ import ( // AsEntryXpath functions used in location.tmpl to generate XPath for location. func AsEntryXpath(location, xpath string) (string, error) { if !strings.Contains(xpath, "$") || !strings.Contains(xpath, "}") { - return "", errors.New("$ followed by } should exists in xpath'") + return "", fmt.Errorf("xpath '%s' is missing '$' followed by '}'", xpath) } asEntryXpath := generateXpathForLocation(location, xpath) return asEntryXpath, nil From e55a5be5270c0dbce7c62c54f6deb417a5a2a2d7 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 14 Mar 2024 13:42:47 +0100 Subject: [PATCH 19/22] apply changes after review (#2) --- pkg/generate/generator.go | 4 ++-- pkg/translate/funcs.go | 16 ++++++++++------ pkg/translate/funcs_test.go | 4 ++-- pkg/translate/structs.go | 22 +++++++++++----------- templates/sdk/location.tmpl | 2 +- 5 files changed, 26 insertions(+), 22 deletions(-) diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 6e671302..d6c35bb1 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -132,8 +132,8 @@ func (c *Creator) parseTemplate(templateName string) (*template.Template, error) "subtract": func(a, b int) int { return a - b }, - "asEntryXpath": translate.AsEntryXpath, - "nestedSpecs": translate.NestedSpecs, + "generateEntryXpath": translate.GenerateEntryXpathForLocation, + "nestedSpecs": translate.NestedSpecs, } return template.New(templateName).Funcs(funcMap).ParseFiles(templatePath) } diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 1328d8fe..dc9cf49f 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -7,16 +7,16 @@ import ( "strings" ) -// AsEntryXpath functions used in location.tmpl to generate XPath for location. -func AsEntryXpath(location, xpath string) (string, error) { +// GenerateEntryXpathForLocation functions used in location.tmpl to generate XPath for location. +func GenerateEntryXpathForLocation(location, xpath string) (string, error) { if !strings.Contains(xpath, "$") || !strings.Contains(xpath, "}") { return "", fmt.Errorf("xpath '%s' is missing '$' followed by '}'", xpath) } - asEntryXpath := generateXpathForLocation(location, xpath) + asEntryXpath := generateEntryXpathForLocation(location, xpath) return asEntryXpath, nil } -func generateXpathForLocation(location string, xpath string) string { +func generateEntryXpathForLocation(location string, xpath string) string { xpathPartWithoutDollar := strings.SplitAfter(xpath, "$") xpathPartWithoutBrackets := strings.TrimSpace(strings.Trim(xpathPartWithoutDollar[1], "${}")) xpathPartCamelCase := naming.CamelCase("", xpathPartWithoutBrackets, "", true) @@ -43,7 +43,7 @@ func SpecifyEntryAssignment(param *properties.SpecParam) string { func prepareAssignment(param *properties.SpecParam, listFunction, specSuffix string) string { var builder strings.Builder - if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { + if isParamListAndProfileTypeIsMember(param) { builder.WriteString(fmt.Sprintf("entry.%s = %s(o.%s)", param.Name.CamelCase, listFunction, param.Name.CamelCase)) } else if param.Spec != nil { for _, subParam := range param.Spec.Params { @@ -67,6 +67,10 @@ func prepareAssignment(param *properties.SpecParam, listFunction, specSuffix str return builder.String() } +func isParamListAndProfileTypeIsMember(param *properties.SpecParam) bool { + return param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" +} + func nestedObjectDeclaration(parent []string, param *properties.SpecParam) string { var builder strings.Builder @@ -101,7 +105,7 @@ func declareVariableForNestedObject(parent []string, param *properties.SpecParam func nestedObjectAssignment(parent []string, suffix string, param *properties.SpecParam) string { var builder strings.Builder - if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { + if isParamListAndProfileTypeIsMember(param) { builder.WriteString(fmt.Sprintf("%s : util.StrToMem(o.%s),\n", param.Name.CamelCase, param.Name.CamelCase)) } else if param.Spec != nil { diff --git a/pkg/translate/funcs_test.go b/pkg/translate/funcs_test.go index e04fc087..0c048edf 100644 --- a/pkg/translate/funcs_test.go +++ b/pkg/translate/funcs_test.go @@ -6,11 +6,11 @@ import ( "testing" ) -func TestAsEntryXpath(t *testing.T) { +func TestGenerateEntryXpath(t *testing.T) { // given // when - asEntryXpath, _ := AsEntryXpath("DeviceGroup", "{{ Entry $panorama_device }}") + asEntryXpath, _ := GenerateEntryXpathForLocation("DeviceGroup", "{{ Entry $panorama_device }}") // then assert.Equal(t, "util.AsEntryXpath([]string{o.DeviceGroup.PanoramaDevice}),", asEntryXpath) diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index cfc15670..6bdbb13e 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -68,7 +68,7 @@ func SpecParamType(parent string, param *properties.SpecParam) string { calculatedType = param.Type } - return prefix + calculatedType + return fmt.Sprintf("%s%s", prefix, calculatedType) } // XmlParamType return param type (it can be nested spec) (for struct based on spec from YAML files). @@ -79,7 +79,7 @@ func XmlParamType(parent string, param *properties.SpecParam) string { } calculatedType := "" - if param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" { + if isParamListAndProfileTypeIsMember(param) { calculatedType = "util.MemberType" } else if param.Spec != nil { calculatedType = fmt.Sprintf("Spec%s%sXml", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) @@ -87,21 +87,21 @@ func XmlParamType(parent string, param *properties.SpecParam) string { calculatedType = param.Type } - return prefix + calculatedType + return fmt.Sprintf("%s%s", prefix, calculatedType) } // XmlTag creates a string with xml tag (e.g. `xml:"description,omitempty"`). func XmlTag(param *properties.SpecParam) string { - suffix := "" - if !param.Required { - suffix = ",omitempty" - } - - calculatedTag := "" if param.Profiles != nil && len(param.Profiles) > 0 { - calculatedTag = fmt.Sprintf("`xml:\"%s%s\"`", param.Profiles[0].Xpath[len(param.Profiles[0].Xpath)-1], suffix) + suffix := "" + if !param.Required { + suffix = ",omitempty" + } + + return fmt.Sprintf("`xml:\"%s%s\"`", param.Profiles[0].Xpath[len(param.Profiles[0].Xpath)-1], suffix) } - return calculatedTag + + return "" } // OmitEmpty return omitempty in XML tag for location, if there are variables defined. diff --git a/templates/sdk/location.tmpl b/templates/sdk/location.tmpl index cdc57075..121125cf 100644 --- a/templates/sdk/location.tmpl +++ b/templates/sdk/location.tmpl @@ -63,7 +63,7 @@ func (o Location) Xpath(vn version.Number, name string) ([]string, error) { ans = []string{ {{- range $name, $xpath := $location.Xpath}} {{- if contains $xpath "Entry"}} - {{asEntryXpath $location.Name.CamelCase $xpath}} + {{generateEntryXpath $location.Name.CamelCase $xpath}} {{- else}} "{{$xpath}}", {{- end}} From f6e4f92aba89c0fdcadc71dec693e198adcb91db Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Thu, 14 Mar 2024 13:53:22 +0100 Subject: [PATCH 20/22] refactor SpecMatchesFunction() --- pkg/translate/funcs.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index dc9cf49f..1ec81668 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -129,9 +129,8 @@ func nestedObjectAssignment(parent []string, suffix string, param *properties.Sp // SpecMatchesFunction return a string used in function SpecMatches() in entry.tmpl // to compare all items of generated entry. func SpecMatchesFunction(param *properties.SpecParam) string { - calculatedFunction := "OptionalStringsMatch" if param.Type == "list" { - calculatedFunction = "OrderedListsMatch" + return "OrderedListsMatch" } - return calculatedFunction + return "OptionalStringsMatch" } From 4df52fa817b3cd84b76b344ebe7910a6baf120b4 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Fri, 15 Mar 2024 09:12:38 +0100 Subject: [PATCH 21/22] refactor funcs.go --- pkg/properties/normalized.go | 2 +- pkg/translate/funcs.go | 103 ++++++++++++++++------------------- 2 files changed, 48 insertions(+), 57 deletions(-) diff --git a/pkg/properties/normalized.go b/pkg/properties/normalized.go index a665b277..a24dd4e8 100644 --- a/pkg/properties/normalized.go +++ b/pkg/properties/normalized.go @@ -244,7 +244,7 @@ func addDefaultTypesForParams(params map[string]*SpecParam) error { return nil } -// addDefaultTypesForParams ensures all params within Spec have a default type if not specified. +// AddDefaultTypesForParams ensures all params within Spec have a default type if not specified. func (spec *Normalization) AddDefaultTypesForParams() error { if spec.Spec != nil { if err := addDefaultTypesForParams(spec.Spec.Params); err != nil { diff --git a/pkg/translate/funcs.go b/pkg/translate/funcs.go index 1ec81668..251e4a14 100644 --- a/pkg/translate/funcs.go +++ b/pkg/translate/funcs.go @@ -43,25 +43,12 @@ func SpecifyEntryAssignment(param *properties.SpecParam) string { func prepareAssignment(param *properties.SpecParam, listFunction, specSuffix string) string { var builder strings.Builder - if isParamListAndProfileTypeIsMember(param) { - builder.WriteString(fmt.Sprintf("entry.%s = %s(o.%s)", param.Name.CamelCase, listFunction, param.Name.CamelCase)) - } else if param.Spec != nil { - for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) - } - for _, subParam := range param.Spec.OneOf { - builder.WriteString(nestedObjectDeclaration([]string{param.Name.CamelCase}, subParam)) - } - builder.WriteString(fmt.Sprintf("entry.%s = &Spec%s%s{\n", param.Name.CamelCase, param.Name.CamelCase, specSuffix)) - for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, specSuffix, subParam)) - } - for _, subParam := range param.Spec.OneOf { - builder.WriteString(nestedObjectAssignment([]string{param.Name.CamelCase}, specSuffix, subParam)) - } - builder.WriteString("}\n") + if param.Spec != nil { + appendSpecObjectAssignment(param, specSuffix, &builder) + } else if isParamListAndProfileTypeIsMember(param) { + appendListFunctionAssignment(param, listFunction, &builder) } else { - builder.WriteString(fmt.Sprintf("entry.%s = o.%s", param.Name.CamelCase, param.Name.CamelCase)) + appendSimpleAssignment(param, &builder) } return builder.String() @@ -71,59 +58,63 @@ func isParamListAndProfileTypeIsMember(param *properties.SpecParam) bool { return param.Type == "list" && param.Profiles != nil && len(param.Profiles) > 0 && param.Profiles[0].Type == "member" } -func nestedObjectDeclaration(parent []string, param *properties.SpecParam) string { - var builder strings.Builder +func appendSimpleAssignment(param *properties.SpecParam, builder *strings.Builder) { + builder.WriteString(fmt.Sprintf("entry.%s = o.%s", param.Name.CamelCase, param.Name.CamelCase)) +} - if param.Spec != nil { - for _, subParam := range param.Spec.Params { - builder.WriteString(declareVariableForNestedObject(parent, param, subParam)) - builder.WriteString(nestedObjectDeclaration(append(parent, param.Name.CamelCase), subParam)) - } - for _, subParam := range param.Spec.OneOf { - builder.WriteString(declareVariableForNestedObject(parent, param, subParam)) - builder.WriteString(nestedObjectDeclaration(append(parent, param.Name.CamelCase), subParam)) - } - } +func appendListFunctionAssignment(param *properties.SpecParam, listFunction string, builder *strings.Builder) { + builder.WriteString(fmt.Sprintf("entry.%s = %s(o.%s)", param.Name.CamelCase, listFunction, param.Name.CamelCase)) +} - return builder.String() +func appendSpecObjectAssignment(param *properties.SpecParam, suffix string, builder *strings.Builder) { + appendNestedObjectDeclaration([]string{param.Name.CamelCase}, param.Spec.Params, builder) + appendNestedObjectDeclaration([]string{param.Name.CamelCase}, param.Spec.OneOf, builder) + + builder.WriteString(fmt.Sprintf("entry.%s = &Spec%s%s{\n", param.Name.CamelCase, param.Name.CamelCase, suffix)) + + appendNestedObjectAssignment([]string{param.Name.CamelCase}, param.Spec.Params, suffix, builder) + appendNestedObjectAssignment([]string{param.Name.CamelCase}, param.Spec.OneOf, suffix, builder) + + builder.WriteString("}\n") +} + +func appendNestedObjectDeclaration(parent []string, params map[string]*properties.SpecParam, builder *strings.Builder) { + for _, subParam := range params { + appendDeclarationForNestedObject(parent, subParam, builder) + } } -func declareVariableForNestedObject(parent []string, param *properties.SpecParam, subParam *properties.SpecParam) string { - if subParam.Spec == nil && parent != nil { - return fmt.Sprintf("nested%s%s%s := o.%s.%s.%s\n", - strings.Join(parent, ""), - param.Name.CamelCase, - subParam.Name.CamelCase, - strings.Join(parent, "."), - param.Name.CamelCase, - subParam.Name.CamelCase) +func appendDeclarationForNestedObject(parent []string, param *properties.SpecParam, builder *strings.Builder) { + if param.Spec != nil { + appendNestedObjectDeclaration(append(parent, param.Name.CamelCase), param.Spec.Params, builder) + appendNestedObjectDeclaration(append(parent, param.Name.CamelCase), param.Spec.OneOf, builder) } else { - return "" + builder.WriteString(fmt.Sprintf("nested%s%s := o.%s.%s\n", + strings.Join(parent, ""), param.Name.CamelCase, + strings.Join(parent, "."), param.Name.CamelCase)) } } -func nestedObjectAssignment(parent []string, suffix string, param *properties.SpecParam) string { - var builder strings.Builder +func appendNestedObjectAssignment(parent []string, params map[string]*properties.SpecParam, suffix string, builder *strings.Builder) { + for _, subParam := range params { + appendAssignmentForNestedObject(parent, subParam, suffix, builder) + } +} - if isParamListAndProfileTypeIsMember(param) { +func appendAssignmentForNestedObject(parent []string, param *properties.SpecParam, suffix string, builder *strings.Builder) { + if param.Spec != nil { + builder.WriteString(fmt.Sprintf("%s : &Spec%s%s%s{\n", param.Name.CamelCase, + strings.Join(parent, ""), param.Name.CamelCase, suffix)) + appendNestedObjectAssignment(append(parent, param.Name.CamelCase), param.Spec.Params, suffix, builder) + appendNestedObjectAssignment(append(parent, param.Name.CamelCase), param.Spec.OneOf, suffix, builder) + builder.WriteString("},\n") + } else if isParamListAndProfileTypeIsMember(param) { builder.WriteString(fmt.Sprintf("%s : util.StrToMem(o.%s),\n", param.Name.CamelCase, param.Name.CamelCase)) - } else if param.Spec != nil { - builder.WriteString(fmt.Sprintf("%s : &Spec%s%s%s{\n", - param.Name.CamelCase, strings.Join(parent, ""), param.Name.CamelCase, suffix)) - for _, subParam := range param.Spec.Params { - builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), suffix, subParam)) - } - for _, subParam := range param.Spec.OneOf { - builder.WriteString(nestedObjectAssignment(append(parent, param.Name.CamelCase), suffix, subParam)) - } - builder.WriteString("},\n") } else { builder.WriteString(fmt.Sprintf("%s : nested%s%s,\n", param.Name.CamelCase, strings.Join(parent, ""), param.Name.CamelCase)) } - - return builder.String() } // SpecMatchesFunction return a string used in function SpecMatches() in entry.tmpl From 0ff2d5996e41747e533e3e1ae01e32eef13c6582 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Fri, 15 Mar 2024 09:24:04 +0100 Subject: [PATCH 22/22] refactor structs.go --- pkg/translate/structs.go | 49 ++++++++++++++++++++++------------- pkg/translate/structs_test.go | 4 +-- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/pkg/translate/structs.go b/pkg/translate/structs.go index 6bdbb13e..d96638e6 100644 --- a/pkg/translate/structs.go +++ b/pkg/translate/structs.go @@ -47,23 +47,13 @@ func updateNestedSpecs(parent []string, param *properties.SpecParam, nestedSpecs // SpecParamType return param type (it can be nested spec) (for struct based on spec from YAML files). func SpecParamType(parent string, param *properties.SpecParam) string { - prefix := "" - if !param.Required { - prefix = "*" - } - if param.Type == "list" { - prefix = "[]" - } + prefix := determinePrefix(param) calculatedType := "" if param.Type == "list" && param.Items != nil { - if param.Items.Type == "object" && param.Items.Ref != nil { - calculatedType = "string" - } else { - calculatedType = param.Items.Type - } + calculatedType = determineListType(param) } else if param.Spec != nil { - calculatedType = fmt.Sprintf("Spec%s%s", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) + calculatedType = calculateNestedSpecType(parent, param) } else { calculatedType = param.Type } @@ -73,16 +63,13 @@ func SpecParamType(parent string, param *properties.SpecParam) string { // XmlParamType return param type (it can be nested spec) (for struct based on spec from YAML files). func XmlParamType(parent string, param *properties.SpecParam) string { - prefix := "" - if !param.Required { - prefix = "*" - } + prefix := determinePrefix(param) calculatedType := "" if isParamListAndProfileTypeIsMember(param) { calculatedType = "util.MemberType" } else if param.Spec != nil { - calculatedType = fmt.Sprintf("Spec%s%sXml", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) + calculatedType = calculateNestedXmlSpecType(parent, param) } else { calculatedType = param.Type } @@ -90,6 +77,32 @@ func XmlParamType(parent string, param *properties.SpecParam) string { return fmt.Sprintf("%s%s", prefix, calculatedType) } +func determinePrefix(param *properties.SpecParam) string { + prefix := "" + if param.Type == "list" { + prefix = prefix + "[]" + } + if !param.Required { + prefix = prefix + "*" + } + return prefix +} + +func determineListType(param *properties.SpecParam) string { + if param.Items.Type == "object" && param.Items.Ref != nil { + return "string" + } + return param.Items.Type +} + +func calculateNestedSpecType(parent string, param *properties.SpecParam) string { + return fmt.Sprintf("Spec%s%s", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) +} + +func calculateNestedXmlSpecType(parent string, param *properties.SpecParam) string { + return fmt.Sprintf("Spec%s%sXml", parent, naming.CamelCase("", param.Name.CamelCase, "", true)) +} + // XmlTag creates a string with xml tag (e.g. `xml:"description,omitempty"`). func XmlTag(param *properties.SpecParam) string { if param.Profiles != nil && len(param.Profiles) > 0 { diff --git a/pkg/translate/structs_test.go b/pkg/translate/structs_test.go index 415be26d..4a3cb1bf 100644 --- a/pkg/translate/structs_test.go +++ b/pkg/translate/structs_test.go @@ -91,7 +91,7 @@ func TestSpecParamType(t *testing.T) { // then assert.Equal(t, "string", calculatedTypeRequiredString) - assert.Equal(t, "[]string", calculatedTypeListString) + assert.Equal(t, "[]*string", calculatedTypeListString) assert.Equal(t, "*string", calculatedTypeOptionalString) } @@ -144,7 +144,7 @@ func TestXmlParamType(t *testing.T) { // then assert.Equal(t, "string", calculatedTypeRequiredString) - assert.Equal(t, "*util.MemberType", calculatedTypeListString) + assert.Equal(t, "[]*util.MemberType", calculatedTypeListString) } func TestXmlTag(t *testing.T) {