api.go 2.89 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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
// Package bindnode provides an ipld.Node implementation via Go reflection.
package bindnode

import (
	"reflect"

	ipld "github.com/ipld/go-ipld-prime"
	"github.com/ipld/go-ipld-prime/schema"
)

// Prototype implements a TypedPrototype given a Go pointer type and an IPLD
// schema type. Note that the result is also an ipld.NodePrototype.
//
// If both the Go type and schema type are supplied, it is assumed that they are
// compatible with one another.
//
// If either the Go type or schema type are nil, we infer the missing type from
// the other provided type. For example, we can infer an unnamed Go struct type
// for a schema struct tyep, and we can infer a schema Int type for a Go int64
// type. The inferring logic is still a work in progress and subject to change.
//
// When supplying a non-nil ptrType, Prototype only obtains the Go pointer type
// from it, so its underlying value will typically be nil. For example:
//
//     proto := bindnode.Prototype((*goType)(nil), schemaType)
func Prototype(ptrType interface{}, schemaType schema.Type) TypedPrototype {
	if ptrType == nil && schemaType == nil {
		panic("either ptrType or schemaType must not be nil")
	}

	// TODO: if both are supplied, verify that they are compatible

	var goType reflect.Type
	if ptrType == nil {
		goType = inferGoType(schemaType)
	} else {
		goPtrType := reflect.TypeOf(ptrType)
		if goPtrType.Kind() != reflect.Ptr {
			panic("ptrType must be a pointer")
		}
		goType = goPtrType.Elem()
	}

	if schemaType == nil {
		schemaType = inferSchema(goType)
	}

	return &_prototype{schemaType: schemaType, goType: goType}
}

// Wrap implements a schema.TypedNode given a non-nil pointer to a Go value and an
// IPLD schema type. Note that the result is also an ipld.Node.
//
// Wrap is meant to be used when one already has a Go value with data.
// As such, ptrVal must not be nil.
//
// Similar to Prototype, if schemaType is non-nil it is assumed to be compatible
// with the Go type, and otherwise it's inferred from the Go type.
func Wrap(ptrVal interface{}, schemaType schema.Type) schema.TypedNode {
	if ptrVal == nil {
		panic("ptrVal must not be nil")
	}
	goPtrVal := reflect.ValueOf(ptrVal)
	if goPtrVal.Kind() != reflect.Ptr {
		panic("ptrVal must be a pointer")
	}
	if goPtrVal.IsNil() {
		panic("ptrVal must not be nil")
	}
	goVal := goPtrVal.Elem()
	if schemaType == nil {
		schemaType = inferSchema(goVal.Type())
	}
	return &_node{val: goVal, schemaType: schemaType}
}

// Unwrap takes an ipld.Node implemented by Prototype or Wrap,
// and returns a pointer to the inner Go value.
//
// Unwrap returns nil if the node isn't implemented by this package.
func Unwrap(node ipld.Node) (ptr interface{}) {
	var val reflect.Value
	switch node := node.(type) {
	case *_node:
		val = node.val
	case *_nodeRepr:
		val = node.val
	default:
		return nil
	}
	if val.Kind() == reflect.Ptr {
		panic("didn't expect val to be a pointer")
	}
	return val.Addr().Interface()
}