Skip to content

Commit

Permalink
Merge branch 'protobuf-fakeroot' into proto-sort-by-type
Browse files Browse the repository at this point in the history
  • Loading branch information
robshakir committed Feb 13, 2018
2 parents 2ef38fa + 06e4459 commit 207ecbe
Show file tree
Hide file tree
Showing 9 changed files with 43,671 additions and 26,122 deletions.
68,513 changes: 42,486 additions & 26,027 deletions exampleoc/oc.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions exampleoc/update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ go run ../generator/generator.go -path=public,deps -output_file=oc.go \
-package_name=exampleoc -generate_fakeroot -fakeroot_name=device -compress_paths=true \
-exclude_modules=ietf-interfaces \
-generate_rename \
-generate_append \
-generate_getters \
-annotations \
public/release/models/network-instance/openconfig-network-instance.yang \
public/release/models/optical-transport/openconfig-optical-amplifier.yang \
Expand Down
4 changes: 4 additions & 0 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ var (
addAnnotations = flag.Bool("annotations", false, "If set to true, metadata annotations are added within the generated structs.")
annotationPrefix = flag.String("annotation_prefix", ygen.DefaultAnnotationPrefix, "String to be appended to each metadata field within the generated structs if annoations is set to true.")
excludeState = flag.Bool("exclude_state", false, "If set to true, state (config false) fields in the YANG schema are not included in the generated Go code.")
generateAppend = flag.Bool("generate_append", false, "If set to true, append methods are generated for YANG lists (Go maps) within the Go code.")
generateGetters = flag.Bool("generate_getters", false, "If set to true, getter methdos that retrieve or create an element are generated for YANG container (Go struct pointer) or list (Go map) fields within the generated code.")
)

// writeGoCode takes a ygen.GeneratedGoCode struct and writes the Go code
Expand Down Expand Up @@ -159,6 +161,8 @@ func main() {
GenerateRenameMethod: *generateRename,
AddAnnotationFields: *addAnnotations,
AnnotationPrefix: *annotationPrefix,
GenerateGetters: *generateGetters,
GenerateAppendMethod: *generateAppend,
},
ExcludeState: *excludeState,
})
Expand Down
113 changes: 47 additions & 66 deletions ygen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package ygen

import (
"bytes"
"errors"
"fmt"
"sort"
Expand Down Expand Up @@ -116,6 +115,14 @@ type GoOpts struct {
// AnnotationPrefix specifies the string which is prefixed to the name of
// annotation fields. It defaults to Λ.
AnnotationPrefix string
// GenerateGetters specifies whether GetOrCreate* methods should be created
// for struct pointer (YANG container) and map (YANG list) fields of generated
// structs.
GenerateGetters bool
// GenerateAppendList specifies whether Append* methods should be created for
// list fields of a struct. These methods take an input list member type, extract
// the key and append the supplied value to the list.
GenerateAppendMethod bool
}

// ProtoOpts stores Protobuf specific options for the code generation library.
Expand Down Expand Up @@ -259,35 +266,6 @@ type Proto3Package struct {
Enums []string // Enums is a slice of string containing the generated set of enumerations within the package.
}

// YANGCodeGeneratorError is a type implementing error that is returned to the
// caller of the library when errors are encountered during code generation. It
// implements error such that idiomatic if err != nil testing can be used, and
// contains a list of errors associted with the code generation call.
type YANGCodeGeneratorError struct {
Errors []error
}

// NewYANGCodeGeneratorError returns a new instance of the YANGCodeGeneratorError
// struct with the relevant fields initialised.
func NewYANGCodeGeneratorError() *YANGCodeGeneratorError {
return &YANGCodeGeneratorError{}
}

// Error is a method of YANGCodeGeneratorError which returns a concatenated
// string of the errors that are stored within the YANGCodeGeneratorError
// struct. Its implementation allows a YANGCodeGeneratorError to implement the
// error interface.
func (yangErr *YANGCodeGeneratorError) Error() string {
var buf bytes.Buffer
buf.WriteString("errors encountered during code generation:\n")

// Concatenate the errors that are stored within the receiver YANGCodeGeneratorError struct.
for _, e := range yangErr.Errors {
buf.WriteString(fmt.Sprintf("%v\n", e))
}
return buf.String()
}

const (
// rootElementPath is the synthesised node name that is used for an
// element that represents the root. Such an element is generated only
Expand Down Expand Up @@ -321,26 +299,26 @@ const (
// of type enumeration, identities, typedefs that reference an enumeration)
// within the specified models.
// If errors are encountered during code generation, an error is returned.
func (cg *YANGCodeGenerator) GenerateGoCode(yangFiles, includePaths []string) (*GeneratedGoCode, *YANGCodeGeneratorError) {
func (cg *YANGCodeGenerator) GenerateGoCode(yangFiles, includePaths []string) (*GeneratedGoCode, util.Errors) {
// Extract the entities to be mapped into structs and enumerations in the output
// Go code. Extract the schematree from the modules provided such that it can be
// used to reference entities within the tree.
mdef, errs := mappedDefinitions(yangFiles, includePaths, &cg.Config)
if errs != nil {
return nil, &YANGCodeGeneratorError{Errors: errs}
return nil, errs
}

// Store the returned schematree within the state for this code generation.
cg.state.schematree = mdef.schemaTree

goStructs, errs := cg.state.buildDirectoryDefinitions(mdef.directoryEntries, cg.Config.CompressOCPaths, cg.Config.GenerateFakeRoot, golang, cg.Config.ExcludeState)
if errs != nil {
return nil, &YANGCodeGeneratorError{Errors: errs}
return nil, errs
}

codeHeader, err := writeGoHeader(yangFiles, includePaths, cg.Config)
if errs != nil {
return nil, &YANGCodeGeneratorError{Errors: []error{err}}
if err != nil {
return nil, util.AppendErr(util.Errors{}, err)
}

// orderedStructNames is used to store the structs that have been
Expand All @@ -359,13 +337,13 @@ func (cg *YANGCodeGenerator) GenerateGoCode(yangFiles, includePaths []string) (*

// enumTypeMap stores the map of the path to type.
enumTypeMap := map[string][]string{}
codegenErr := NewYANGCodeGeneratorError()
var codegenErr util.Errors
var structSnippets []string
for _, structName := range orderedStructNames {
structOut, errs := writeGoStruct(structNameMap[structName], goStructs, cg.state,
cg.Config.CompressOCPaths, cg.Config.GenerateJSONSchema, cg.Config.GoOptions)
if errs != nil {
codegenErr.Errors = append(codegenErr.Errors, errs...)
util.AppendErrs(codegenErr, errs)
continue
}
// Append the actual struct definitions that were returned.
Expand All @@ -383,7 +361,7 @@ func (cg *YANGCodeGenerator) GenerateGoCode(yangFiles, includePaths []string) (*

goEnums, errs := cg.state.findEnumSet(mdef.enumEntries, cg.Config.CompressOCPaths, false)
if errs != nil {
codegenErr.Errors = append(codegenErr.Errors, errs...)
util.AppendErrs(codegenErr, errs)
return nil, codegenErr
}

Expand Down Expand Up @@ -411,7 +389,7 @@ func (cg *YANGCodeGenerator) GenerateGoCode(yangFiles, includePaths []string) (*
for _, enumName := range orderedEnumNames {
enumOut, err := writeGoEnum(enumNameMap[enumName])
if err != nil {
codegenErr.Errors = append(codegenErr.Errors, err)
util.AppendErr(codegenErr, err)
continue
}
enumSnippets = append(enumSnippets, enumOut.constDef)
Expand All @@ -423,7 +401,7 @@ func (cg *YANGCodeGenerator) GenerateGoCode(yangFiles, includePaths []string) (*
// string values.
enumMap, err := generateEnumMap(enumValueMap)
if err != nil {
codegenErr.Errors = append(codegenErr.Errors, err)
util.AppendErr(codegenErr, err)
}

var rawSchema []byte
Expand All @@ -433,22 +411,22 @@ func (cg *YANGCodeGenerator) GenerateGoCode(yangFiles, includePaths []string) (*
var err error
rawSchema, err = buildJSONTree(mdef.modules, cg.state.uniqueDirectoryNames, mdef.directoryEntries["/"], cg.Config.CompressOCPaths)
if err != nil {
codegenErr.Errors = append(codegenErr.Errors, fmt.Errorf("error marshalling JSON schema: %v", err))
util.AppendErr(codegenErr, fmt.Errorf("error marshalling JSON schema: %v", err))
}

if rawSchema != nil {
if jsonSchema, err = writeGoSchema(rawSchema, cg.Config.GoOptions.SchemaVarName); err != nil {
codegenErr.Errors = append(codegenErr.Errors, fmt.Errorf("error storing schema variable: %v", err))
util.AppendErr(codegenErr, err)
}
}

if enumTypeMapCode, err = generateEnumTypeMap(enumTypeMap); err != nil {
codegenErr.Errors = append(codegenErr.Errors, err)
util.AppendErr(codegenErr, err)
}
}

// Return any errors that were encountered during code generation.
if len(codegenErr.Errors) != 0 {
if len(codegenErr) != 0 {
return nil, codegenErr
}

Expand All @@ -468,32 +446,34 @@ func (cg *YANGCodeGenerator) GenerateGoCode(yangFiles, includePaths []string) (*
// yangFiles argument, with included modules being searched for in includePaths.
// It returns a GeneratedProto3 struct containing the messages that are to be
// output, along with any associated values (e.g., enumerations).
func (cg *YANGCodeGenerator) GenerateProto3(yangFiles, includePaths []string) (*GeneratedProto3, *YANGCodeGeneratorError) {
func (cg *YANGCodeGenerator) GenerateProto3(yangFiles, includePaths []string) (*GeneratedProto3, util.Errors) {
mdef, errs := mappedDefinitions(yangFiles, includePaths, &cg.Config)
if errs != nil {
return nil, &YANGCodeGeneratorError{Errors: errs}
return nil, errs
}

cg.state.schematree = mdef.schemaTree

penums, errs := cg.state.findEnumSet(mdef.enumEntries, cg.Config.CompressOCPaths, true)
if errs != nil {
return nil, &YANGCodeGeneratorError{Errors: errs}
return nil, errs
}
protoEnums, errs := writeProtoEnums(penums, cg.Config.ProtoOptions.AnnotateEnumNames)
if errs != nil {
return nil, &YANGCodeGeneratorError{Errors: errs}
return nil, errs
}

protoMsgs, errs := cg.state.buildDirectoryDefinitions(mdef.directoryEntries, cg.Config.CompressOCPaths, cg.Config.GenerateFakeRoot, protobuf, cg.Config.ExcludeState)
if errs != nil {
return nil, &YANGCodeGeneratorError{Errors: errs}
return nil, errs
}

genProto := &GeneratedProto3{
Packages: map[string]Proto3Package{},
}
ye := NewYANGCodeGeneratorError()

// yerr stores errors encountered during code generation.
var yerr util.Errors

// pkgImports lists the imports that are required for the package that is being
// written out.
Expand Down Expand Up @@ -554,7 +534,7 @@ func (cg *YANGCodeGenerator) GenerateProto3(yangFiles, includePaths []string) (*
})

if errs != nil {
ye.Errors = append(ye.Errors, errs...)
util.AppendErrs(yerr, errs)
continue
}

Expand Down Expand Up @@ -603,14 +583,15 @@ func (cg *YANGCodeGenerator) GenerateProto3(yangFiles, includePaths []string) (*
YextPath: yextPath,
})
if err != nil {
ye.Errors = append(ye.Errors, errs...)
util.AppendErrs(yerr, errs)
continue
}
pkg.Header = h
genProto.Packages[n] = pkg
}

if ye.Errors != nil {
return nil, ye
if yerr != nil {
return nil, yerr
}

return genProto, nil
Expand All @@ -621,7 +602,7 @@ func (cg *YANGCodeGenerator) GenerateProto3(yangFiles, includePaths []string) (*
// and returns a processed set of yang.Entry pointers which correspond to the
// generated code for the modules. If errors are returned during the Goyang
// processing of the modules, these errors are returned.
func processModules(yangFiles, includePaths []string, options yang.Options) ([]*yang.Entry, []error) {
func processModules(yangFiles, includePaths []string, options yang.Options) ([]*yang.Entry, util.Errors) {
// Append the includePaths to the Goyang path variable, this ensures
// that where a YANG module uses an 'include' statement to reference
// another module, then Goyang can find this module to process.
Expand All @@ -637,15 +618,15 @@ func processModules(yangFiles, includePaths []string, options yang.Options) ([]*
// Initialise the set of YANG modules within the Goyang parsing package.
moduleSet := yang.NewModules()

var processErr []error
var errs util.Errors
for _, name := range yangFiles {
if err := moduleSet.Read(name); err != nil {
processErr = append(processErr, err)
util.AppendErr(errs, err)
}
}

if processErr != nil {
return nil, processErr
if errs != nil {
return nil, errs
}

if errs := moduleSet.Process(); errs != nil {
Expand Down Expand Up @@ -700,7 +681,7 @@ type mappedYANGDefinitions struct {
// - cfg: the current generator's configuration.
// It returns a mappedYANGDefinitions struct populated with the directory and enum
// entries in the input schemas, along with the calculated schema tree.
func mappedDefinitions(yangFiles, includePaths []string, cfg *GeneratorConfig) (*mappedYANGDefinitions, []error) {
func mappedDefinitions(yangFiles, includePaths []string, cfg *GeneratorConfig) (*mappedYANGDefinitions, util.Errors) {
modules, errs := processModules(yangFiles, includePaths, cfg.YANGParseOptions)
if errs != nil {
return nil, errs
Expand Down Expand Up @@ -825,7 +806,7 @@ func findMappableEntities(e *yang.Entry, dirs map[string]*yang.Entry, enums map[
}
}

var errs []error
var errs util.Errors
for _, ch := range children(e) {
switch {
case ch.IsLeaf(), ch.IsLeafList():
Expand All @@ -839,12 +820,12 @@ func findMappableEntities(e *yang.Entry, dirs map[string]*yang.Entry, enums map[
// If this is a config or state container and we are compressing paths
// then we do not want to map this container - but we do want to map its
// children.
errs = append(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, modules)...)
util.AppendErrs(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, modules))
case hasOnlyChild(ch) && children(ch)[0].IsList() && compressPaths:
// This is a surrounding container for a list, and we are compressing
// paths, so we don't want to map it but again we do want to map its
// children.
errs = append(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, modules)...)
util.AppendErrs(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, modules))
case isChoiceOrCase(ch):
// Don't map for a choice or case node itself, and rather skip over it.
// However, we must walk each branch to find the first container that
Expand All @@ -865,16 +846,16 @@ func findMappableEntities(e *yang.Entry, dirs map[string]*yang.Entry, enums map[
if gch.IsContainer() || gch.IsList() {
dirs[fmt.Sprintf("%s/%s", ch.Parent.Path(), gch.Name)] = gch
}
errs = append(errs, findMappableEntities(gch, dirs, enums, excludeModules, compressPaths, modules)...)
util.AppendErrs(errs, findMappableEntities(gch, dirs, enums, excludeModules, compressPaths, modules))
}
case ch.IsContainer(), ch.IsList():
dirs[ch.Path()] = ch
// Recurse down the tree.
errs = append(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, modules)...)
util.AppendErrs(errs, findMappableEntities(ch, dirs, enums, excludeModules, compressPaths, modules))
case ch.Kind == yang.AnyDataEntry:
continue
default:
errs = append(errs, fmt.Errorf("unknown type of entry %v in findMappableEntities for %s", e.Kind, e.Path()))
util.AppendErr(errs, fmt.Errorf("unknown type of entry %v in findMappableEntities for %s", e.Kind, e.Path()))
}
}
return errs
Expand Down
22 changes: 11 additions & 11 deletions ygen/codegen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,6 @@ const (
TestRoot string = ""
)

func TestNewYANGCodeGeneratorError(t *testing.T) {
e := NewYANGCodeGeneratorError()
e.Errors = append(e.Errors, fmt.Errorf("test string"))
e.Errors = append(e.Errors, []error{fmt.Errorf("test string two"), fmt.Errorf("string three")}...)
want := "errors encountered during code generation:\ntest string\ntest string two\nstring three\n"

if got := e.Error(); got != want {
t.Errorf("NewYANGCodeGenerator did not concatenate errors correctly, got: %s, want: %s", got, want)
}
}

// TestFindMappableEntities tests the extraction of elements that are to be mapped
// into Go code from a YANG schema.
func TestFindMappableEntities(t *testing.T) {
Expand Down Expand Up @@ -606,6 +595,17 @@ func TestSimpleStructs(t *testing.T) {
CompressOCPaths: true,
},
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "openconfig-config-false-compressed.formatted-txt"),
}, {
name: "module with getters and append methods",
inFiles: []string{filepath.Join(TestRoot, "testdata", "structs", "openconfig-list-enum-key.yang")},
inConfig: GeneratorConfig{
GenerateFakeRoot: true,
GoOptions: GoOpts{
GenerateAppendMethod: true,
GenerateGetters: true,
},
},
wantStructsCodeFile: filepath.Join(TestRoot, "testdata", "structs", "openconfig-list-enum-key.getters-append.formatted-txt"),
}}

for _, tt := range tests {
Expand Down

0 comments on commit 207ecbe

Please sign in to comment.