Skip to content

Commit

Permalink
Improve json to edgeos conversions (#5)
Browse files Browse the repository at this point in the history
* Tidy go mod

* Fix type output in error message

* Force consistent map key ordering

* Test more cases

* Refactor kv writing

* Support multiple values for the same key

* Support numbers

* Support boolean values
  • Loading branch information
mraerino authored Aug 7, 2022
1 parent 3b74cdd commit e26c337
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 46 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ module github.com/ffddorf/confgen

go 1.19

require github.com/stretchr/testify v1.8.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/stretchr/testify v1.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 1 addition & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
121 changes: 94 additions & 27 deletions interop/edgeos/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package edgeos

import (
"fmt"
"reflect"
"sort"
"strings"
)

// ForceConsistentMapOrdering is used in tests to ensure consistent output
var ForceConsistentMapOrdering = false

type InvalidMapValueTypeError struct {
valueType string
}
Expand All @@ -22,57 +25,121 @@ type StringBuilder interface {
const indent = " "

func ConfigFromMap(out StringBuilder, in map[string]interface{}, depth int) error {
indentDepth := strings.Repeat(indent, depth)
for k, v := range in {
if _, err := out.WriteString(indentDepth); err != nil {
return err
}
if _, err := out.WriteString(k); err != nil {
return err
}
if err := out.WriteByte(' '); err != nil {
return err
}
keys := mapKeys(in)
if ForceConsistentMapOrdering {
sort.Strings(keys)
}

indentString := strings.Repeat(indent, depth)
for _, k := range keys {
v := in[k]
switch t := v.(type) {
case string:
if strings.Contains(t, " ") {
if err := out.WriteByte('"'); err != nil {
case []interface{}:
for _, item := range t {
s, err := primitiveToString(item)
if err != nil {
return err
}
if _, err := out.WriteString(t); err != nil {
if err := writeKV(out, k, s, indentString); err != nil {
return err
}
if err := out.WriteByte('"'); err != nil {
return err
}
} else {
if _, err := out.WriteString(t); err != nil {
}
case []string:
for _, item := range t {
if err := writeKV(out, k, item, indentString); err != nil {
return err
}
}
if err := out.WriteByte('\n'); err != nil {
case map[string]interface{}:
if _, err := out.WriteString(indentString); err != nil {
return err
}
case map[string]interface{}:
if _, err := out.WriteString("{\n"); err != nil {
if _, err := out.WriteString(k); err != nil {
return err
}
if _, err := out.WriteString(" {\n"); err != nil {
return err
}

if err := ConfigFromMap(out, t, depth+1); err != nil {
return err
}

if _, err := out.WriteString(indentDepth); err != nil {
if _, err := out.WriteString(indentString); err != nil {
return err
}
if _, err := out.WriteString("}\n"); err != nil {
return err
}
case bool:
if !t {
continue
}
if _, err := out.WriteString(indent); err != nil {
return err
}
if _, err := out.WriteString(k); err != nil {
return err
}
if err := out.WriteByte('\n'); err != nil {
return err
}
default:
return InvalidMapValueTypeError{
valueType: reflect.TypeOf(v).Name(),
s, err := primitiveToString(t)
if err != nil {
return err
}
if err := writeKV(out, k, s, indentString); err != nil {
return err
}
}
}
return nil
}

func writeKV(out StringBuilder, k, v string, indent string) error {
quoted := strings.Contains(v, " ")
if _, err := out.WriteString(indent); err != nil {
return err
}
if _, err := out.WriteString(k); err != nil {
return err
}
if err := out.WriteByte(' '); err != nil {
return err
}
if quoted {
if err := out.WriteByte('"'); err != nil {
return err
}
}
if _, err := out.WriteString(v); err != nil {
return err
}
if quoted {
if err := out.WriteByte('"'); err != nil {
return err
}
}
return out.WriteByte('\n')
}

func primitiveToString(in interface{}) (string, error) {
switch t := in.(type) {
case string:
return t, nil
case uint, int, uint32, int32, uint64, int64, float32, float64:
return fmt.Sprintf("%d", t), nil
}
return "", &InvalidMapValueTypeError{
valueType: fmt.Sprintf("%T", in),
}
}

func mapKeys[T any](in map[string]T) []string {
out := make([]string, 0, len(in))
for key := range in {
out = append(out, key)
}
return out
}
144 changes: 128 additions & 16 deletions interop/edgeos/convert_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package edgeos_test

import (
"fmt"
"strings"
"testing"

Expand All @@ -10,28 +9,141 @@ import (
"github.com/stretchr/testify/require"
)

func init() {
edgeos.ForceConsistentMapOrdering = true
}

type SM = map[string]interface{}

func TestMapConversion(t *testing.T) {
in := SM{
"smart-queue internal": SM{
"download": SM{
"rate": "39mbit",
testCases := map[string]struct {
in map[string]interface{}
out string
}{
"smart queue": {
in: SM{
"smart-queue internal": SM{
"download": SM{
"rate": "39mbit",
},
"wan-interface": "eth1.2",
},
},
"wan-interface": "eth1.2",
},
}

out := &strings.Builder{}
err := edgeos.ConfigFromMap(out, in, 0)
require.NoError(t, err)

fmt.Println(out.String())
assert.Equal(t, `smart-queue internal {
out: `smart-queue internal {
download {
rate 39mbit
}
wan-interface eth1.2
}
`, out.String())
`,
},
"quoted values": {
in: SM{
"ethernet eth0": SM{
"description": "Some interface doing something",
},
},
out: `ethernet eth0 {
description "Some interface doing something"
}
`,
},
"multivalue": {
in: SM{
"interfaces": SM{
"ethernet eth1": SM{
"address": []interface{}{"10.1.0.1/16", "fde4:4d90:9ebf::1/64"},
},
},
},
out: `interfaces {
ethernet eth1 {
address 10.1.0.1/16
address fde4:4d90:9ebf::1/64
}
}
`,
},
"numbers": {
in: SM{
"protocols": SM{
"bgp 207871": SM{
"maximum-paths": SM{
"ibgp": 2,
},
},
},
},
out: `protocols {
bgp 207871 {
maximum-paths {
ibgp 2
}
}
}
`,
},
"multivalue numbers": {
in: SM{
"snmp": SM{
"community public": SM{
"authorization": "ro",
},
"contact": "[email protected]",
"listen-address 2001:678:b7c::3": SM{
"port": []interface{}{161, "345"},
},
},
},
out: `snmp {
community public {
authorization ro
}
contact [email protected]
listen-address 2001:678:b7c::3 {
port 161
port 345
}
}
`,
},
"booleans": {
in: SM{
"ethernet eth3": SM{
"disable": true,
"duplex": "auto",
"speed": "auto",
},
},
out: `ethernet eth3 {
disable
duplex auto
speed auto
}
`,
},
"boolean off": {
in: SM{
"ethernet eth3": SM{
"disable": false,
"duplex": "auto",
"speed": "auto",
},
},
out: `ethernet eth3 {
duplex auto
speed auto
}
`,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
out := &strings.Builder{}
err := edgeos.ConfigFromMap(out, tc.in, 0)
require.NoError(t, err)
assert.Equal(t, tc.out, out.String())
})
}
}

0 comments on commit e26c337

Please sign in to comment.