boundNode.go 3.54 KB
Newer Older
1 2 3
package ipldbind

import (
4
	"fmt"
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
	"reflect"

	"github.com/ipld/go-ipld-prime"
	"github.com/polydawn/refmt/obj/atlas"
)

var (
	_ ipld.Node = &Node{}
)

/*
	Node binds to some Go object in memory, using the definitions provided
	by refmt's object atlasing tools.

	This binding does not provide a serialization valid for hashing; to
	compute a CID, you'll have to convert to another kind of node.
	If you're not sure which kind serializable node to use, try `ipldcbor.Node`.
*/
type Node struct {
	bound reflect.Value
	atlas atlas.Atlas
}
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108

/*
	Bind binds any go value into being interfacable as a Node, using the provided
	atlas to understand how to traverse it.
*/
func Bind(bindme interface{}, atl atlas.Atlas) ipld.Node {
	return &Node{
		bound: reflect.ValueOf(bindme),
		atlas: atl,
	}
}

func (n *Node) GetField(pth []string) (v interface{}, _ error) {
	return v, traverse(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())
}

// 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 {
		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:
			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
		}
	}
	// 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
	}
	// TODO all these cases
	switch atlent.Type.Kind() {
	case // primitives found when expecting to path deeper cause an error return.
		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:
	}
	return nil
}