Commit c35f4393 authored by Eric Myhre's avatar Eric Myhre

Switch to single-step deref'ering methods.

And add a bunch of type-specific ones for helpfulness.

But immediately re-reviewing this further.  What we've done here is
handle leafs very oddly, because it would seem to avoid unnecessary
wrapping on the leaf nodes.  But the result seems to be much uglier
code than it needs to be, and implies that we've got all sorts of
additional special cases required to either handle or reject binds
of primitive-kind fields.  And that seems... maybe not a great trade.
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent e16f4019
......@@ -4,6 +4,7 @@ import (
"fmt"
"reflect"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/polydawn/refmt/obj/atlas"
)
......@@ -36,19 +37,68 @@ func Bind(bindme interface{}, atl atlas.Atlas) ipld.Node {
}
}
func (n *Node) GetField(pth []string) (v interface{}, _ error) {
return v, traverse(n.bound, pth, n.atlas, reflect.ValueOf(v))
func (n *Node) GetField(pth string) (v interface{}, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(v))
}
func (n *Node) GetFieldString(pth []string) (v string, _ error) {
return v, traverse(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
func (n *Node) GetFieldBool(pth string) (v bool, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetFieldString(pth string) (v string, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetFieldInt(pth string) (v int, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetFieldLink(pth string) (v cid.Cid, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetIndex(pth int) (v interface{}, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(v))
}
func (n *Node) GetIndexBool(pth int) (v bool, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetIndexString(pth int) (v string, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetIndexInt(pth int) (v int, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetIndexLink(pth int) (v cid.Cid, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflect.Value) error {
// Unwrap any pointers.
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
// traverse is the internal impl behind GetField.
// It's preferable to have this function for recursing because the defn of
// the GetField function is more for caller-friendliness than performance.
func traverse(v reflect.Value, pth []string, atl atlas.Atlas, assignTo reflect.Value) error {
// Handle the terminating case of expected leaf nodes first.
if len(pth) == 0 {
// Traverse.
// Honor any atlent overrides if present;
// Use kind-based fallbacks if necessary.
atlent, exists := atl.Get(reflect.ValueOf(v.Type()).Pointer())
if exists {
switch {
case atlent.MarshalTransformFunc != nil:
panic(fmt.Errorf("invalid ipldbind.Node: type %q atlas specifies transform, but ipld doesn't support this power level", v.Type().Name()))
case atlent.StructMap != nil:
for _, fe := range atlent.StructMap.Fields {
if fe.SerialName == pth {
v = fe.ReflectRoute.TraverseToValue(v)
break
}
}
return fmt.Errorf("traverse failed: type %q has no field named %q", v.Type().Name(), pth)
case atlent.UnionKeyedMorphism != nil:
panic(fmt.Errorf("invalid ipldbind.Node: type %q atlas specifies union, but ipld doesn't know how to make sense of this", v.Type().Name()))
case atlent.MapMorphism != nil:
v = v.MapIndex(reflect.ValueOf(pth))
default:
panic("unreachable")
}
} else {
switch v.Type().Kind() {
case // primitives: set them
reflect.Bool,
......@@ -57,52 +107,65 @@ func traverse(v reflect.Value, pth []string, atl atlas.Atlas, assignTo reflect.V
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64,
reflect.Complex64, reflect.Complex128:
assignTo.Set(v)
return nil
case reflect.Array: // array: ... REVIEW, like map, is it acceptable to leak concrete types?
case reflect.Slice: // slice: same as array
case reflect.Map: // map: ... REVIEW: can we just return this? it might have more concrete types in it, and that's kind of unstandard.
case reflect.Struct: // struct: wrap in Node
// TODO
case reflect.Interface: // interface: ... REVIEW: i don't know what to do with this
case reflect.Chan: // chan: not acceptable in ipld objects
case reflect.Func: // func: not acceptable in ipld objects
case reflect.Ptr: // ptr: TODO needs an unwrap round
case reflect.UnsafePointer: // unsafe: not acceptable in ipld objects
panic(fmt.Errorf("invalid ipldbind.Node: atlas for type %q is union; ipld doesn't know how to make sense of this", v.Type().Name()))
case // recursives: wrap in node
reflect.Array,
reflect.Slice,
reflect.Map,
reflect.Struct,
reflect.Interface:
assignTo.Set(reflect.ValueOf(Node{v, atl}))
case // esotera: reject with panic
reflect.Chan,
reflect.Func,
reflect.UnsafePointer:
panic(fmt.Errorf("invalid ipldbind.Node: cannot atlas over type %q; it's a %v", v.Type().Name(), v.Kind()))
case // pointers: should've already been unwrapped
reflect.Ptr:
panic("unreachable")
}
}
// Handle traversal to deeper nodes.
// If we get a primitive here, it's an error, because we haven't handled all path segments yet.
atlent, exists := atl.Get(reflect.ValueOf(v.Type()).Pointer())
if !exists {
panic(fmt.Errorf("invalid ipldbind.Node: atlas missing entry for type %q", v.Type().Name()))
}
errIfPathNonEmpty := func() error {
if len(pth) > 1 {
return fmt.Errorf("getField reached leaf before following all path segements")
}
return nil
// Unwrap any pointers.
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
// TODO all these cases
switch atlent.Type.Kind() {
case // primitives found when expecting to path deeper cause an error return.
// Assign into the result.
// Either assign the result directly (for primitives)
// Or wrap with a Node and assign that (for recursives).
// TODO decide what to do with typedef'd primitives.
switch v.Type().Kind() {
case // primitives: set them
reflect.Bool,
reflect.String,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64,
reflect.Complex64, reflect.Complex128:
return errIfPathNonEmpty()
case reflect.Array:
case reflect.Slice:
case reflect.Map:
case reflect.Struct:
case reflect.Interface:
case reflect.Chan:
case reflect.Func:
case reflect.Ptr:
case reflect.UnsafePointer:
assignTo.Set(v)
return nil
case // recursives: wrap in node
reflect.Array,
reflect.Slice,
reflect.Map,
reflect.Struct,
reflect.Interface:
assignTo.Set(reflect.ValueOf(Node{v, atl}))
return nil
case // esotera: reject with panic
reflect.Chan,
reflect.Func,
reflect.UnsafePointer:
panic(fmt.Errorf("invalid ipldbind.Node: cannot atlas over type %q; it's a %v", v.Type().Name(), v.Kind()))
case // pointers: should've already been unwrapped
reflect.Ptr:
panic("unreachable")
default:
panic("unreachable")
}
return nil
}
func traverseIndex(v reflect.Value, pth int, atl atlas.Atlas, assignTo reflect.Value) error {
panic("NYI")
}
package ipldcbor
import (
"github.com/ipld/go-ipld-prime"
)
//import (
// "github.com/ipld/go-ipld-prime"
//)
var (
_ ipld.Node = &Node{}
_ ipld.SerializableNode = &Node{}
//_ ipld.Node = &Node{}
//_ ipld.SerializableNode = &Node{}
)
/*
......
package ipld
import "github.com/ipfs/go-cid"
type Node interface {
// GetField resolves a merklepath against the object and returns
// GetField resolves a path in the the object and returns
// either a primitive (e.g. string, int, etc), a link (type CID),
// or another Node.
//
......@@ -10,21 +12,42 @@ type Node interface {
// originally stored as a full node, you cannot immediately take
// a link to it for embedding in other objects (you'd have to make
// a new RootNode with the same content first, then store that).
GetField(path []string) (interface{}, error)
//
// A variety of GetField{Foo} methods exists to yield specific types,
// and will panic upon encountering unexpected types.
GetField(path string) (interface{}, error)
GetFieldBool(path string) (bool, error) // See GetField docs.
GetFieldString(path string) (string, error) // See GetField docs.
GetFieldInt(path string) (int, error) // See GetField docs.
GetFieldLink(path string) (cid.Cid, error) // See GetField docs.
// GetIndex is the equivalent of GetField but for indexing into an array
// (or a numerically-keyed map). Like GetField, it returns
// either a primitive (e.g. string, int, etc), a link (type CID),
// or another Node.
//
// A variety of GetIndex{Foo} methods exists to yield specific types,
// and will panic upon encountering unexpected types.
GetIndex(idx int) (interface{}, error)
GetIndexBool(idx int) (bool, error) // See GetIndex docs.
GetIndexString(idx int) (string, error) // See GetIndex docs.
GetIndexInt(idx int) (int, error) // See GetIndex docs.
GetIndexLink(idx int) (cid.Cid, error) // See GetIndex docs.
// Distinctly suspecting a GetField(string)(iface,err) API would be better.
// And a GetIndex(int)(iface,err) for arrays and int-keyed maps.
// Much easier to code.
// And traversals are apparently going to require a type schema parameter!
// Main counterargument: might be more efficient to let nodes take part,
// so they can do work without generating intermediate nodes.
// That logic applies to some but not all impls. e.g. freenode always
// already has the intermediates. ipldcbor.Node prob will too.
// Hrm. I guess ipldbind.Node is the only one that can get usefully fancy there.
// REVIEW this whole interface is still *very* unfriendly to chaining.
// Friendly would be returning `(Node)` at all times, and having final
// dereferencing options on leaf nodes, and treating it as a Maybe at all
// other times. Multireturn methods are antithetical to graceful fluent
// chaining in golang, syntactically.
// Panics might also be a viable option. I suspect we do quite usually
// want to do large amounts of these traversal operations, and bailing at
// any point is desired to be terse. We could provide a thunkwrapper.
}
type SerializableNode interface {
CID() cid.Cid
}
type MutableNode interface {
// FUTURE: setter methods go here
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment