Node/Reflect
Use cases:
- CRUD on Go structs
- CRUD on Go maps or slices
Special notes
- You don’t need perfect alignment with Go field names and YANG to use
Reflect
. LetReflect
do the heavy lifting for you and capture small variations by combining with Extend. To that end, do not expect magical powers fromReflect
to coerse your custom field types to YANG types. - Currently
Reflect
doesn’t attempt to use reflection to implementnotifications
oractions/rpcs
but again, you can combineReflect
withExtend
. - Names in YANG can be
camelCase
,kabob-case
orsnake_case
interchangablely. Your Go public field are obviously inCamelCase
.
Simple example
When you happen to have perfect alignment of field names to names in YANG.
If you have application code like this…
package demo
type Contacts struct {
Me *User
Users []*User
Size int
HarmlessValue string
}
type User struct {
FullName string
}
…and YANG like this…
module contacts {
namespace "freeconf.org";
prefix "a";
container me {
// full-name, full_name or fullName all work
leaf full-name {
type string;
}
}
list users {
key full-name;
// could be grouping/uses here, doesn't matter
leaf full-name {
type string;
}
}
leaf size {
type int32;
}
}
…then your node code can be this.
package demo
import (
"github.com/freeconf/yang/node"
"github.com/freeconf/yang/nodeutil"
)
func manage(app *Contacts) node.Node {
return &nodeutil.Node{Object: app}
}
…and you test like this.
package demo
import (
"testing"
"github.com/freeconf/yang/fc"
"github.com/freeconf/yang/node"
"github.com/freeconf/yang/nodeutil"
"github.com/freeconf/yang/parser"
"github.com/freeconf/yang/source"
)
func TestManageContact(t *testing.T) {
app := &Contacts{}
ypath := source.Dir(".")
m := parser.RequireModule(ypath, "contacts")
b := node.NewBrowser(m, manage(app))
actual, err := nodeutil.WriteJSON(b.Root())
fc.AssertEqual(t, nil, err)
fc.AssertEqual(t, `{"size":0}`, actual)
}
Map example
Reflect also supports Go’s map
interface. While this Go code’s lack of data structures that might make this difficult to use in Go, if you don’t need to handle this data in Go, then this is completely acceptable. Remember, you can add constraints to yang to ensure the data is validated properly.
If you have application code like this…
package demo
type JunkDrawer struct {
Info map[string]interface{}
}
|| …and YANG like this…
module junk-drawer {
container info {
leaf name {
type string;
}
container more {
leaf size {
type decimal64;
}
}
list stuff {
key anything;
leaf anything {
type enumeration {
enum one;
enum two;
}
}
}
}
}
…then your node code can be this.
package demo
import (
"github.com/freeconf/yang/node"
"github.com/freeconf/yang/nodeutil"
)
func manageJunkDrawer(app *JunkDrawer) node.Node {
return &nodeutil.Node{Object: app}
}
…and you test like this.
package demo
import (
"testing"
"github.com/freeconf/yang/fc"
"github.com/freeconf/yang/node"
"github.com/freeconf/yang/nodeutil"
"github.com/freeconf/yang/parser"
"github.com/freeconf/yang/source"
)
func TestManageJunk(t *testing.T) {
app := &JunkDrawer{Info: make(map[string]interface{})}
ypath := source.Dir(".")
m := parser.RequireModule(ypath, "junk-drawer")
b := node.NewBrowser(m, manageJunkDrawer(app))
actual, err := nodeutil.WriteJSON(b.Root())
fc.AssertEqual(t, nil, err)
fc.AssertEqual(t, `{"info":{}}`, actual)
}
tips:
- useful for building validated RESTCONF APIs quickly to be filled in later with structs
- good for handling a bulk set of configs
Field coersion
Reflect can do a decent job converting Go primatives to and from YANG leaf types: strings to numbers, numbers to enums, enums to strings, number types to other number types, etc.. If a conversion represents a loss of data or a type can cannot be convert safely, then an error is returned. To handle the conversion of these values yourself, you can use Extend
or Reflect.OnField
. Reflect.OnField
is more suited over Extend
when have a lot of fields of the same type that you want to reuse in a lot of places and not one-offs.
If you have application code like this…
package demo
import "time"
type Timely struct {
LastModified time.Time
}
…and YANG like this…
module timely {
namespace "freeconf.org";
prefix "t";
leaf lastModified {
type int64; // unix seconds
}
}
…then your node code can be this.
package demo
import (
"reflect"
"time"
"github.com/freeconf/yang/meta"
"github.com/freeconf/yang/node"
"github.com/freeconf/yang/nodeutil"
)
func manageTimely(t *Timely) node.Node {
timeType := reflect.TypeOf(&time.Time{})
return &nodeutil.Node{
Object: t,
OnRead: func(ref *nodeutil.Node, m meta.Definition, t reflect.Type, v reflect.Value) (reflect.Value, error) {
switch t {
case timeType:
at := v.Interface().(*time.Time)
return reflect.ValueOf(at.Unix()), nil
}
return v, nil
},
OnWrite: func(ref *nodeutil.Node, m meta.Definition, t reflect.Type, v reflect.Value) (reflect.Value, error) {
switch t {
case timeType:
return reflect.ValueOf(time.Unix(v.Int(), 0)), nil
}
return v, nil
},
}
}
Adhoc structs
Create an anonymous struct or a just a map. Useful for handing RPC requests or responses. Here we use it to create whole app.
package demo
import (
"testing"
"github.com/freeconf/yang/fc"
"github.com/freeconf/yang/node"
"github.com/freeconf/yang/nodeutil"
"github.com/freeconf/yang/parser"
)
// Load a YANG module from a string!
var mstr = `
module mystery {
namespace "freeconf.org";
prefix "m";
leaf name {
type string;
}
}
`
func TestMystery(t *testing.T) {
m, err := parser.LoadModuleFromString(nil, mstr)
fc.AssertEqual(t, nil, err)
t.Run("struct", func(t *testing.T) {
// create a node from an adhoc struct
app := struct {
Name string
}{
"john",
}
manage := &nodeutil.Node{Object: &app}
// verify works
b := node.NewBrowser(m, manage)
actual, err := nodeutil.WriteJSON(b.Root())
fc.AssertEqual(t, nil, err)
fc.AssertEqual(t, `{"name":"john"}`, actual)
})
t.Run("map", func(t *testing.T) {
// create a node from map
app := map[string]interface{}{"name": "mary"}
manage := &nodeutil.Node{Object: app}
// verify works
b := node.NewBrowser(m, manage)
actual, err := nodeutil.WriteJSON(b.Root())
fc.AssertEqual(t, nil, err)
fc.AssertEqual(t, `{"name":"mary"}`, actual)
})
}