Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1755 lines
51 KiB
Go
1755 lines
51 KiB
Go
package bindnode
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/ipld/go-ipld-prime/datamodel"
|
|
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
|
|
"github.com/ipld/go-ipld-prime/node/basicnode"
|
|
"github.com/ipld/go-ipld-prime/node/mixins"
|
|
"github.com/ipld/go-ipld-prime/schema"
|
|
)
|
|
|
|
// Assert that we implement all the interfaces as expected.
|
|
// Grouped by the interfaces to implement, roughly.
|
|
var (
|
|
_ datamodel.NodePrototype = (*_prototype)(nil)
|
|
_ schema.TypedPrototype = (*_prototype)(nil)
|
|
_ datamodel.NodePrototype = (*_prototypeRepr)(nil)
|
|
|
|
_ datamodel.Node = (*_node)(nil)
|
|
_ schema.TypedNode = (*_node)(nil)
|
|
_ datamodel.Node = (*_nodeRepr)(nil)
|
|
|
|
_ datamodel.Node = (*_uintNode)(nil)
|
|
_ schema.TypedNode = (*_uintNode)(nil)
|
|
_ datamodel.UintNode = (*_uintNode)(nil)
|
|
_ datamodel.Node = (*_uintNodeRepr)(nil)
|
|
_ datamodel.UintNode = (*_uintNodeRepr)(nil)
|
|
|
|
_ datamodel.NodeBuilder = (*_builder)(nil)
|
|
_ datamodel.NodeBuilder = (*_builderRepr)(nil)
|
|
_ datamodel.NodeAssembler = (*_assembler)(nil)
|
|
_ datamodel.NodeAssembler = (*_assemblerRepr)(nil)
|
|
|
|
_ datamodel.MapAssembler = (*_structAssembler)(nil)
|
|
_ datamodel.MapAssembler = (*_structAssemblerRepr)(nil)
|
|
_ datamodel.MapIterator = (*_structIterator)(nil)
|
|
_ datamodel.MapIterator = (*_structIteratorRepr)(nil)
|
|
|
|
_ datamodel.ListAssembler = (*_listAssembler)(nil)
|
|
_ datamodel.ListAssembler = (*_listAssemblerRepr)(nil)
|
|
_ datamodel.ListIterator = (*_listIterator)(nil)
|
|
_ datamodel.ListIterator = (*_tupleIteratorRepr)(nil)
|
|
|
|
_ datamodel.MapAssembler = (*_unionAssembler)(nil)
|
|
_ datamodel.MapAssembler = (*_unionAssemblerRepr)(nil)
|
|
_ datamodel.MapIterator = (*_unionIterator)(nil)
|
|
_ datamodel.MapIterator = (*_unionIteratorRepr)(nil)
|
|
)
|
|
|
|
type _prototype struct {
|
|
cfg config
|
|
schemaType schema.Type
|
|
goType reflect.Type // non-pointer
|
|
}
|
|
|
|
func (w *_prototype) NewBuilder() datamodel.NodeBuilder {
|
|
return &_builder{_assembler{
|
|
cfg: w.cfg,
|
|
schemaType: w.schemaType,
|
|
val: reflect.New(w.goType).Elem(),
|
|
}}
|
|
}
|
|
|
|
func (w *_prototype) Type() schema.Type {
|
|
return w.schemaType
|
|
}
|
|
|
|
func (w *_prototype) Representation() datamodel.NodePrototype {
|
|
return (*_prototypeRepr)(w)
|
|
}
|
|
|
|
type _node struct {
|
|
cfg config
|
|
schemaType schema.Type
|
|
|
|
val reflect.Value // non-pointer
|
|
}
|
|
|
|
// TODO: only expose TypedNode methods if the schema was explicit.
|
|
// type _typedNode struct {
|
|
// _node
|
|
// }
|
|
|
|
func newNode(cfg config, schemaType schema.Type, val reflect.Value) schema.TypedNode {
|
|
if schemaType.TypeKind() == schema.TypeKind_Int && nonPtrVal(val).Kind() == reflect.Uint64 {
|
|
// special case for uint64 values so we can handle the >int64 range
|
|
// we give this treatment to all uint64s, regardless of current value
|
|
// because we have no guarantees the value won't change underneath us
|
|
return &_uintNode{
|
|
cfg: cfg,
|
|
schemaType: schemaType,
|
|
val: val,
|
|
}
|
|
}
|
|
return &_node{cfg, schemaType, val}
|
|
}
|
|
|
|
func (w *_node) Type() schema.Type {
|
|
return w.schemaType
|
|
}
|
|
|
|
func (w *_node) Representation() datamodel.Node {
|
|
return (*_nodeRepr)(w)
|
|
}
|
|
|
|
func (w *_node) Kind() datamodel.Kind {
|
|
return actualKind(w.schemaType)
|
|
}
|
|
|
|
// matching schema level types to data model kinds, since our Node and Builder
|
|
// interfaces operate on kinds
|
|
func compatibleKind(schemaType schema.Type, kind datamodel.Kind) error {
|
|
switch sch := schemaType.(type) {
|
|
case *schema.TypeAny:
|
|
return nil
|
|
default:
|
|
actual := actualKind(sch) // ActsLike data model
|
|
if actual == kind {
|
|
return nil
|
|
}
|
|
|
|
// Error
|
|
methodName := ""
|
|
if pc, _, _, ok := runtime.Caller(1); ok {
|
|
if fn := runtime.FuncForPC(pc); fn != nil {
|
|
methodName = fn.Name()
|
|
// Go from "pkg/path.Type.Method" to just "Method".
|
|
methodName = methodName[strings.LastIndexByte(methodName, '.')+1:]
|
|
}
|
|
}
|
|
return datamodel.ErrWrongKind{
|
|
TypeName: schemaType.Name(),
|
|
MethodName: methodName,
|
|
AppropriateKind: datamodel.KindSet{kind},
|
|
ActualKind: actual,
|
|
}
|
|
}
|
|
}
|
|
|
|
func actualKind(schemaType schema.Type) datamodel.Kind {
|
|
return schemaType.TypeKind().ActsLike()
|
|
}
|
|
|
|
func nonPtrVal(val reflect.Value) reflect.Value {
|
|
// TODO: support **T as well as *T?
|
|
if val.Kind() == reflect.Ptr {
|
|
if val.IsNil() {
|
|
// TODO: error in this case?
|
|
return reflect.Value{}
|
|
}
|
|
val = val.Elem()
|
|
}
|
|
return val
|
|
}
|
|
|
|
func ptrVal(val reflect.Value) reflect.Value {
|
|
if val.Kind() == reflect.Ptr {
|
|
return val
|
|
}
|
|
return val.Addr()
|
|
}
|
|
|
|
func nonPtrType(val reflect.Value) reflect.Type {
|
|
typ := val.Type()
|
|
if typ.Kind() == reflect.Ptr {
|
|
return typ.Elem()
|
|
}
|
|
return typ
|
|
}
|
|
|
|
// where we need to cal Set(), ensure the Value we're setting is a pointer or
|
|
// not, depending on the field we're setting into.
|
|
func matchSettable(val interface{}, to reflect.Value) reflect.Value {
|
|
setVal := nonPtrVal(reflect.ValueOf(val))
|
|
if !setVal.Type().AssignableTo(to.Type()) && setVal.Type().ConvertibleTo(to.Type()) {
|
|
setVal = setVal.Convert(to.Type())
|
|
}
|
|
return setVal
|
|
}
|
|
|
|
func (w *_node) LookupByString(key string) (datamodel.Node, error) {
|
|
switch typ := w.schemaType.(type) {
|
|
case *schema.TypeStruct:
|
|
field := typ.Field(key)
|
|
if field == nil {
|
|
return nil, schema.ErrInvalidKey{
|
|
TypeName: typ.Name(),
|
|
Key: basicnode.NewString(key),
|
|
}
|
|
}
|
|
fval := nonPtrVal(w.val).FieldByName(fieldNameFromSchema(key))
|
|
if !fval.IsValid() {
|
|
return nil, fmt.Errorf("bindnode TODO: go-schema mismatch")
|
|
}
|
|
if field.IsOptional() {
|
|
if fval.IsNil() {
|
|
return datamodel.Absent, nil
|
|
}
|
|
if fval.Kind() == reflect.Ptr {
|
|
fval = fval.Elem()
|
|
}
|
|
}
|
|
if field.IsNullable() {
|
|
if fval.IsNil() {
|
|
return datamodel.Null, nil
|
|
}
|
|
if fval.Kind() == reflect.Ptr {
|
|
fval = fval.Elem()
|
|
}
|
|
}
|
|
if _, ok := field.Type().(*schema.TypeAny); ok {
|
|
if customConverter := w.cfg.converterFor(fval); customConverter != nil {
|
|
// field is an Any and we have a custom type converter for the type
|
|
return customConverter.customToAny(ptrVal(fval).Interface())
|
|
}
|
|
// field is an Any, safely assume a Node in fval
|
|
return nonPtrVal(fval).Interface().(datamodel.Node), nil
|
|
}
|
|
return newNode(w.cfg, field.Type(), fval), nil
|
|
case *schema.TypeMap:
|
|
// maps can only be structs with a Values map
|
|
var kval reflect.Value
|
|
valuesVal := nonPtrVal(w.val).FieldByName("Values")
|
|
switch ktyp := typ.KeyType().(type) {
|
|
case *schema.TypeString:
|
|
// plain String keys, so safely use the map key as is
|
|
kval = reflect.ValueOf(key)
|
|
default:
|
|
// key is something other than a string that we need to assemble via
|
|
// the string representation form, use _assemblerRepr to reverse from
|
|
// string to the type that indexes the map
|
|
asm := &_assembler{
|
|
cfg: w.cfg,
|
|
schemaType: ktyp,
|
|
val: reflect.New(valuesVal.Type().Key()).Elem(),
|
|
}
|
|
if err := (*_assemblerRepr)(asm).AssignString(key); err != nil {
|
|
return nil, err
|
|
}
|
|
kval = asm.val
|
|
}
|
|
fval := valuesVal.MapIndex(kval)
|
|
if !fval.IsValid() { // not found
|
|
return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)}
|
|
}
|
|
// TODO: Error/panic if fval.IsNil() && !typ.ValueIsNullable()?
|
|
// Otherwise we could have two non-equal Go values (nil map,
|
|
// non-nil-but-empty map) which represent the exact same IPLD
|
|
// node when the field is not nullable.
|
|
if typ.ValueIsNullable() {
|
|
if fval.IsNil() {
|
|
return datamodel.Null, nil
|
|
}
|
|
fval = fval.Elem()
|
|
}
|
|
if _, ok := typ.ValueType().(*schema.TypeAny); ok {
|
|
if customConverter := w.cfg.converterFor(fval); customConverter != nil {
|
|
// value is an Any and we have a custom type converter for the type
|
|
return customConverter.customToAny(ptrVal(fval).Interface())
|
|
}
|
|
// value is an Any, safely assume a Node in fval
|
|
return nonPtrVal(fval).Interface().(datamodel.Node), nil
|
|
}
|
|
return newNode(w.cfg, typ.ValueType(), fval), nil
|
|
case *schema.TypeUnion:
|
|
// treat a union similar to a struct, but we have the member names more
|
|
// easily accessible to match to 'key'
|
|
var idx int
|
|
var mtyp schema.Type
|
|
for i, member := range typ.Members() {
|
|
if member.Name() == key {
|
|
idx = i
|
|
mtyp = member
|
|
break
|
|
}
|
|
}
|
|
if mtyp == nil { // not found
|
|
return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)}
|
|
}
|
|
// TODO: we could look up the right Go field straight away via idx.
|
|
haveIdx, mval := unionMember(nonPtrVal(w.val))
|
|
if haveIdx != idx { // mismatching type
|
|
return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfString(key)}
|
|
}
|
|
return newNode(w.cfg, mtyp, mval), nil
|
|
}
|
|
return nil, datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "LookupByString",
|
|
AppropriateKind: datamodel.KindSet_JustMap,
|
|
ActualKind: w.Kind(),
|
|
}
|
|
}
|
|
|
|
var invalidValue reflect.Value
|
|
|
|
// unionMember finds which union member is set in the corresponding Go struct.
|
|
func unionMember(val reflect.Value) (int, reflect.Value) {
|
|
// The first non-nil field is a match.
|
|
for i := 0; i < val.NumField(); i++ {
|
|
elemVal := val.Field(i)
|
|
if elemVal.Kind() != reflect.Ptr {
|
|
panic("bindnode bug: found unexpected non-pointer in a union field")
|
|
}
|
|
if elemVal.IsNil() {
|
|
continue
|
|
}
|
|
return i, elemVal.Elem()
|
|
}
|
|
return -1, invalidValue
|
|
}
|
|
|
|
func unionSetMember(val reflect.Value, memberIdx int, memberPtr reflect.Value) {
|
|
// Reset the entire union struct to zero, to clear any non-nil pointers.
|
|
val.Set(reflect.Zero(val.Type()))
|
|
|
|
// Set the index pointer to the given value.
|
|
val.Field(memberIdx).Set(memberPtr)
|
|
}
|
|
|
|
func (w *_node) LookupByIndex(idx int64) (datamodel.Node, error) {
|
|
switch typ := w.schemaType.(type) {
|
|
case *schema.TypeList:
|
|
val := nonPtrVal(w.val)
|
|
// we should be able assume that val is something we can Len() and Index()
|
|
if idx < 0 || int(idx) >= val.Len() {
|
|
return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)}
|
|
}
|
|
val = val.Index(int(idx))
|
|
_, isAny := typ.ValueType().(*schema.TypeAny)
|
|
if isAny {
|
|
if customConverter := w.cfg.converterFor(val); customConverter != nil {
|
|
// values are Any and we have a converter for this type that will give us
|
|
// a datamodel.Node
|
|
return customConverter.customToAny(ptrVal(val).Interface())
|
|
}
|
|
}
|
|
if typ.ValueIsNullable() {
|
|
if val.IsNil() {
|
|
return datamodel.Null, nil
|
|
}
|
|
// nullable elements are assumed to be pointers
|
|
val = val.Elem()
|
|
}
|
|
if isAny {
|
|
// Any always yields a plain datamodel.Node
|
|
return nonPtrVal(val).Interface().(datamodel.Node), nil
|
|
}
|
|
return newNode(w.cfg, typ.ValueType(), val), nil
|
|
}
|
|
return nil, datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "LookupByIndex",
|
|
AppropriateKind: datamodel.KindSet_JustList,
|
|
ActualKind: w.Kind(),
|
|
}
|
|
}
|
|
|
|
func (w *_node) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
|
|
switch w.Kind() {
|
|
case datamodel.Kind_Map:
|
|
return w.LookupByString(seg.String())
|
|
case datamodel.Kind_List:
|
|
idx, err := seg.Index()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.LookupByIndex(idx)
|
|
}
|
|
return nil, datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "LookupBySegment",
|
|
AppropriateKind: datamodel.KindSet_Recursive,
|
|
ActualKind: w.Kind(),
|
|
}
|
|
}
|
|
|
|
func (w *_node) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
|
|
switch w.Kind() {
|
|
case datamodel.Kind_Map:
|
|
s, err := key.AsString()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.LookupByString(s)
|
|
case datamodel.Kind_List:
|
|
i, err := key.AsInt()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return w.LookupByIndex(i)
|
|
}
|
|
return nil, datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "LookupByNode",
|
|
AppropriateKind: datamodel.KindSet_Recursive,
|
|
ActualKind: w.Kind(),
|
|
}
|
|
}
|
|
|
|
func (w *_node) MapIterator() datamodel.MapIterator {
|
|
val := nonPtrVal(w.val)
|
|
// structs, unions and maps can all iterate but they each have different
|
|
// access semantics for the underlying type, so we need a different iterator
|
|
// for each
|
|
switch typ := w.schemaType.(type) {
|
|
case *schema.TypeStruct:
|
|
return &_structIterator{
|
|
cfg: w.cfg,
|
|
schemaType: typ,
|
|
fields: typ.Fields(),
|
|
val: val,
|
|
}
|
|
case *schema.TypeUnion:
|
|
return &_unionIterator{
|
|
cfg: w.cfg,
|
|
schemaType: typ,
|
|
members: typ.Members(),
|
|
val: val,
|
|
}
|
|
case *schema.TypeMap:
|
|
// we can assume a: struct{Keys []string, Values map[x]y}
|
|
return &_mapIterator{
|
|
cfg: w.cfg,
|
|
schemaType: typ,
|
|
keysVal: val.FieldByName("Keys"),
|
|
valuesVal: val.FieldByName("Values"),
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_node) ListIterator() datamodel.ListIterator {
|
|
val := nonPtrVal(w.val)
|
|
switch typ := w.schemaType.(type) {
|
|
case *schema.TypeList:
|
|
return &_listIterator{cfg: w.cfg, schemaType: typ, val: val}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_node) Length() int64 {
|
|
val := nonPtrVal(w.val)
|
|
switch w.Kind() {
|
|
case datamodel.Kind_Map:
|
|
switch typ := w.schemaType.(type) {
|
|
case *schema.TypeStruct:
|
|
return int64(len(typ.Fields()))
|
|
case *schema.TypeUnion:
|
|
return 1
|
|
}
|
|
return int64(val.FieldByName("Keys").Len())
|
|
case datamodel.Kind_List:
|
|
return int64(val.Len())
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// TODO: better story around pointers and absent/null
|
|
|
|
func (w *_node) IsAbsent() bool {
|
|
return false
|
|
}
|
|
|
|
func (w *_node) IsNull() bool {
|
|
return false
|
|
}
|
|
|
|
// The AsX methods are matter of fetching the non-pointer form of the underlying
|
|
// value and returning the appropriate Go type. The user may have registered
|
|
// custom converters for the kind being converted, in which case the underlying
|
|
// type may not be the type we need, but the converter will supply it for us.
|
|
|
|
func (w *_node) AsBool() (bool, error) {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Bool); err != nil {
|
|
return false, err
|
|
}
|
|
if customConverter := w.cfg.converterFor(w.val); customConverter != nil {
|
|
// user has registered a converter that takes the underlying type and returns a bool
|
|
return customConverter.customToBool(ptrVal(w.val).Interface())
|
|
}
|
|
return nonPtrVal(w.val).Bool(), nil
|
|
}
|
|
|
|
func (w *_node) AsInt() (int64, error) {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Int); err != nil {
|
|
return 0, err
|
|
}
|
|
if customConverter := w.cfg.converterFor(w.val); customConverter != nil {
|
|
// user has registered a converter that takes the underlying type and returns an int
|
|
return customConverter.customToInt(ptrVal(w.val).Interface())
|
|
}
|
|
val := nonPtrVal(w.val)
|
|
if kindUint[val.Kind()] {
|
|
u := val.Uint()
|
|
if u > math.MaxInt64 {
|
|
return 0, fmt.Errorf("bindnode: integer overflow, %d is too large for an int64", u)
|
|
}
|
|
return int64(u), nil
|
|
}
|
|
return val.Int(), nil
|
|
}
|
|
|
|
func (w *_node) AsFloat() (float64, error) {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Float); err != nil {
|
|
return 0, err
|
|
}
|
|
if customConverter := w.cfg.converterFor(w.val); customConverter != nil {
|
|
// user has registered a converter that takes the underlying type and returns a float
|
|
return customConverter.customToFloat(ptrVal(w.val).Interface())
|
|
}
|
|
return nonPtrVal(w.val).Float(), nil
|
|
}
|
|
|
|
func (w *_node) AsString() (string, error) {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_String); err != nil {
|
|
return "", err
|
|
}
|
|
if customConverter := w.cfg.converterFor(w.val); customConverter != nil {
|
|
// user has registered a converter that takes the underlying type and returns a string
|
|
return customConverter.customToString(ptrVal(w.val).Interface())
|
|
}
|
|
return nonPtrVal(w.val).String(), nil
|
|
}
|
|
|
|
func (w *_node) AsBytes() ([]byte, error) {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
if customConverter := w.cfg.converterFor(w.val); customConverter != nil {
|
|
// user has registered a converter that takes the underlying type and returns a []byte
|
|
return customConverter.customToBytes(ptrVal(w.val).Interface())
|
|
}
|
|
return nonPtrVal(w.val).Bytes(), nil
|
|
}
|
|
|
|
func (w *_node) AsLink() (datamodel.Link, error) {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Link); err != nil {
|
|
return nil, err
|
|
}
|
|
if customConverter := w.cfg.converterFor(w.val); customConverter != nil {
|
|
// user has registered a converter that takes the underlying type and returns a cid.Cid
|
|
cid, err := customConverter.customToLink(ptrVal(w.val).Interface())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cidlink.Link{Cid: cid}, nil
|
|
}
|
|
switch val := nonPtrVal(w.val).Interface().(type) {
|
|
case datamodel.Link:
|
|
return val, nil
|
|
case cid.Cid:
|
|
return cidlink.Link{Cid: val}, nil
|
|
default:
|
|
return nil, fmt.Errorf("bindnode: unexpected link type %T", val)
|
|
}
|
|
}
|
|
|
|
func (w *_node) Prototype() datamodel.NodePrototype {
|
|
return &_prototype{cfg: w.cfg, schemaType: w.schemaType, goType: w.val.Type()}
|
|
}
|
|
|
|
type _builder struct {
|
|
_assembler
|
|
}
|
|
|
|
func (w *_builder) Build() datamodel.Node {
|
|
// TODO: should we panic if no Assign call was made, just like codegen?
|
|
return newNode(w.cfg, w.schemaType, w.val)
|
|
}
|
|
|
|
func (w *_builder) Reset() {
|
|
panic("bindnode TODO: Reset")
|
|
}
|
|
|
|
type _assembler struct {
|
|
cfg config
|
|
schemaType schema.Type
|
|
val reflect.Value // non-pointer
|
|
|
|
// finish is used as an optional post-assemble step.
|
|
// For example, assigning to a kinded union uses a finish func
|
|
// to set the right union member in the Go union struct,
|
|
// which isn't known before the assemble has finished.
|
|
finish func() error
|
|
|
|
nullable bool // true if field or map value is nullable
|
|
}
|
|
|
|
// createNonPtrVal is used for Set() operations on the underlying value
|
|
func (w *_assembler) createNonPtrVal() reflect.Value {
|
|
val := w.val
|
|
// TODO: if val is not a pointer, we reuse its value.
|
|
// If it is a pointer, we allocate a new one and replace it.
|
|
// We should probably never reuse the existing value.
|
|
|
|
// TODO: support **T as well as *T?
|
|
if val.Kind() == reflect.Ptr {
|
|
// TODO: Sometimes we call createNonPtrVal before an assignment actually
|
|
// happens. Does that matter?
|
|
// If it matters and we only want to modify the destination value on
|
|
// success, then we should make use of the "finish" func.
|
|
val.Set(reflect.New(val.Type().Elem()))
|
|
val = val.Elem()
|
|
}
|
|
return val
|
|
}
|
|
|
|
func (w *_assembler) Representation() datamodel.NodeAssembler {
|
|
return (*_assemblerRepr)(w)
|
|
}
|
|
|
|
// basicMapAssembler is for assembling basicnode values, it's only use is for
|
|
// Any fields that end up needing a BeginMap()
|
|
type basicMapAssembler struct {
|
|
datamodel.MapAssembler
|
|
|
|
builder datamodel.NodeBuilder
|
|
parent *_assembler
|
|
converter *converter
|
|
}
|
|
|
|
func (w *basicMapAssembler) Finish() error {
|
|
if err := w.MapAssembler.Finish(); err != nil {
|
|
return err
|
|
}
|
|
basicNode := w.builder.Build()
|
|
if w.converter != nil {
|
|
// we can assume an Any converter because basicMapAssembler is only for Any
|
|
// the user has registered the ability to convert a datamodel.Node to the
|
|
// underlying Go type which may not be a datamodel.Node
|
|
typ, err := w.converter.customFromAny(basicNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.parent.createNonPtrVal().Set(matchSettable(typ, reflect.ValueOf(basicNode)))
|
|
} else {
|
|
w.parent.createNonPtrVal().Set(reflect.ValueOf(basicNode))
|
|
}
|
|
if w.parent.finish != nil {
|
|
if err := w.parent.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
|
|
switch typ := w.schemaType.(type) {
|
|
case *schema.TypeAny:
|
|
basicBuilder := basicnode.Prototype.Any.NewBuilder()
|
|
mapAsm, err := basicBuilder.BeginMap(sizeHint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
converter := w.cfg.converterFor(w.val)
|
|
return &basicMapAssembler{MapAssembler: mapAsm, builder: basicBuilder, parent: w, converter: converter}, nil
|
|
case *schema.TypeStruct:
|
|
val := w.createNonPtrVal()
|
|
// _structAssembler walks through the fields in order as the entries are
|
|
// assembled, verifyCompatibility() should mean it's safe to assume that
|
|
// they match the schema, but we need to keep track of the fields that are
|
|
// set in case of premature Finish()
|
|
doneFields := make([]bool, val.NumField())
|
|
return &_structAssembler{
|
|
cfg: w.cfg,
|
|
schemaType: typ,
|
|
val: val,
|
|
doneFields: doneFields,
|
|
finish: w.finish,
|
|
}, nil
|
|
case *schema.TypeMap:
|
|
// assume a struct{Keys []string, Values map[x]y} that we can fill with
|
|
// _mapAssembler
|
|
val := w.createNonPtrVal()
|
|
keysVal := val.FieldByName("Keys")
|
|
valuesVal := val.FieldByName("Values")
|
|
if valuesVal.IsNil() {
|
|
valuesVal.Set(reflect.MakeMap(valuesVal.Type()))
|
|
}
|
|
return &_mapAssembler{
|
|
cfg: w.cfg,
|
|
schemaType: typ,
|
|
keysVal: keysVal,
|
|
valuesVal: valuesVal,
|
|
finish: w.finish,
|
|
}, nil
|
|
case *schema.TypeUnion:
|
|
// we can use _unionAssembler to assemble a union as if it were a map with
|
|
// a single entry
|
|
val := w.createNonPtrVal()
|
|
return &_unionAssembler{
|
|
cfg: w.cfg,
|
|
schemaType: typ,
|
|
val: val,
|
|
finish: w.finish,
|
|
}, nil
|
|
}
|
|
return nil, datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "BeginMap",
|
|
AppropriateKind: datamodel.KindSet_JustMap,
|
|
ActualKind: actualKind(w.schemaType),
|
|
}
|
|
}
|
|
|
|
// basicListAssembler is for assembling basicnode values, it's only use is for
|
|
// Any fields that end up needing a BeginList()
|
|
type basicListAssembler struct {
|
|
datamodel.ListAssembler
|
|
|
|
builder datamodel.NodeBuilder
|
|
parent *_assembler
|
|
converter *converter
|
|
}
|
|
|
|
func (w *basicListAssembler) Finish() error {
|
|
if err := w.ListAssembler.Finish(); err != nil {
|
|
return err
|
|
}
|
|
basicNode := w.builder.Build()
|
|
if w.converter != nil {
|
|
// we can assume an Any converter because basicListAssembler is only for Any
|
|
// the user has registered the ability to convert a datamodel.Node to the
|
|
// underlying Go type which may not be a datamodel.Node
|
|
typ, err := w.converter.customFromAny(basicNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.parent.createNonPtrVal().Set(matchSettable(typ, reflect.ValueOf(basicNode)))
|
|
} else {
|
|
w.parent.createNonPtrVal().Set(reflect.ValueOf(basicNode))
|
|
}
|
|
if w.parent.finish != nil {
|
|
if err := w.parent.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
|
|
switch typ := w.schemaType.(type) {
|
|
case *schema.TypeAny:
|
|
basicBuilder := basicnode.Prototype.Any.NewBuilder()
|
|
listAsm, err := basicBuilder.BeginList(sizeHint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
converter := w.cfg.converterFor(w.val)
|
|
return &basicListAssembler{ListAssembler: listAsm, builder: basicBuilder, parent: w, converter: converter}, nil
|
|
case *schema.TypeList:
|
|
// we should be able to safely assume we're dealing with a Go slice here,
|
|
// so _listAssembler can append to that
|
|
val := w.createNonPtrVal()
|
|
return &_listAssembler{
|
|
cfg: w.cfg,
|
|
schemaType: typ,
|
|
val: val,
|
|
finish: w.finish,
|
|
}, nil
|
|
}
|
|
return nil, datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "BeginList",
|
|
AppropriateKind: datamodel.KindSet_JustList,
|
|
ActualKind: actualKind(w.schemaType),
|
|
}
|
|
}
|
|
|
|
func (w *_assembler) AssignNull() error {
|
|
_, isAny := w.schemaType.(*schema.TypeAny)
|
|
if customConverter := w.cfg.converterFor(w.val); customConverter != nil && isAny {
|
|
// an Any field that is being assigned a Null, we pass the Null directly to
|
|
// the converter, regardless of whether this field is nullable or not
|
|
typ, err := customConverter.customFromAny(datamodel.Null)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.createNonPtrVal().Set(matchSettable(typ, w.val))
|
|
} else {
|
|
if !w.nullable {
|
|
return datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "AssignNull",
|
|
// TODO
|
|
}
|
|
}
|
|
// set the zero value for the underlying type as a stand-in for Null
|
|
w.val.Set(reflect.Zero(w.val.Type()))
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) AssignBool(b bool) error {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Bool); err != nil {
|
|
return err
|
|
}
|
|
customConverter := w.cfg.converterFor(w.val)
|
|
_, isAny := w.schemaType.(*schema.TypeAny)
|
|
if customConverter != nil {
|
|
var typ interface{}
|
|
var err error
|
|
if isAny {
|
|
// field is an Any, so the converter will be an Any converter that wants
|
|
// a datamodel.Node to convert to whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromAny(basicnode.NewBool(b)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// field is a Bool, but the user has registered a converter from a bool to
|
|
// whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromBool(b); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.createNonPtrVal().Set(matchSettable(typ, w.val))
|
|
} else {
|
|
if isAny {
|
|
// Any means the Go type must receive a datamodel.Node
|
|
w.createNonPtrVal().Set(reflect.ValueOf(basicnode.NewBool(b)))
|
|
} else {
|
|
w.createNonPtrVal().SetBool(b)
|
|
}
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) assignUInt(uin datamodel.UintNode) error {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Int); err != nil {
|
|
return err
|
|
}
|
|
_, isAny := w.schemaType.(*schema.TypeAny)
|
|
// TODO: customConverter for uint??
|
|
if isAny {
|
|
// Any means the Go type must receive a datamodel.Node
|
|
w.createNonPtrVal().Set(reflect.ValueOf(uin))
|
|
} else {
|
|
i, err := uin.AsUint()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if kindUint[w.val.Kind()] {
|
|
w.createNonPtrVal().SetUint(i)
|
|
} else {
|
|
// TODO: check for overflow
|
|
w.createNonPtrVal().SetInt(int64(i))
|
|
}
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) AssignInt(i int64) error {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Int); err != nil {
|
|
return err
|
|
}
|
|
// TODO: check for overflow
|
|
customConverter := w.cfg.converterFor(w.val)
|
|
_, isAny := w.schemaType.(*schema.TypeAny)
|
|
if customConverter != nil {
|
|
var typ interface{}
|
|
var err error
|
|
if isAny {
|
|
// field is an Any, so the converter will be an Any converter that wants
|
|
// a datamodel.Node to convert to whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromAny(basicnode.NewInt(i)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// field is an Int, but the user has registered a converter from an int to
|
|
// whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromInt(i); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.createNonPtrVal().Set(matchSettable(typ, w.val))
|
|
} else {
|
|
if isAny {
|
|
// Any means the Go type must receive a datamodel.Node
|
|
w.createNonPtrVal().Set(reflect.ValueOf(basicnode.NewInt(i)))
|
|
} else if kindUint[w.val.Kind()] {
|
|
if i < 0 {
|
|
// TODO: write a test
|
|
return fmt.Errorf("bindnode: cannot assign negative integer to %s", w.val.Type())
|
|
}
|
|
w.createNonPtrVal().SetUint(uint64(i))
|
|
} else {
|
|
w.createNonPtrVal().SetInt(i)
|
|
}
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) AssignFloat(f float64) error {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Float); err != nil {
|
|
return err
|
|
}
|
|
customConverter := w.cfg.converterFor(w.val)
|
|
_, isAny := w.schemaType.(*schema.TypeAny)
|
|
if customConverter != nil {
|
|
var typ interface{}
|
|
var err error
|
|
if isAny {
|
|
// field is an Any, so the converter will be an Any converter that wants
|
|
// a datamodel.Node to convert to whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromAny(basicnode.NewFloat(f)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// field is a Float, but the user has registered a converter from a float
|
|
// to whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromFloat(f); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.createNonPtrVal().Set(matchSettable(typ, w.val))
|
|
} else {
|
|
if isAny {
|
|
// Any means the Go type must receive a datamodel.Node
|
|
w.createNonPtrVal().Set(reflect.ValueOf(basicnode.NewFloat(f)))
|
|
} else {
|
|
w.createNonPtrVal().SetFloat(f)
|
|
}
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) AssignString(s string) error {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_String); err != nil {
|
|
return err
|
|
}
|
|
customConverter := w.cfg.converterFor(w.val)
|
|
_, isAny := w.schemaType.(*schema.TypeAny)
|
|
if customConverter != nil {
|
|
var typ interface{}
|
|
var err error
|
|
if isAny {
|
|
// field is an Any, so the converter will be an Any converter that wants
|
|
// a datamodel.Node to convert to whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromAny(basicnode.NewString(s)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// field is a String, but the user has registered a converter from a
|
|
// string to whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromString(s); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.createNonPtrVal().Set(matchSettable(typ, w.val))
|
|
} else {
|
|
if isAny {
|
|
// Any means the Go type must receive a datamodel.Node
|
|
w.createNonPtrVal().Set(reflect.ValueOf(basicnode.NewString(s)))
|
|
} else {
|
|
w.createNonPtrVal().SetString(s)
|
|
}
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) AssignBytes(p []byte) error {
|
|
if err := compatibleKind(w.schemaType, datamodel.Kind_Bytes); err != nil {
|
|
return err
|
|
}
|
|
customConverter := w.cfg.converterFor(w.val)
|
|
_, isAny := w.schemaType.(*schema.TypeAny)
|
|
if customConverter != nil {
|
|
var typ interface{}
|
|
var err error
|
|
if isAny {
|
|
// field is an Any, so the converter will be an Any converter that wants
|
|
// a datamodel.Node to convert to whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromAny(basicnode.NewBytes(p)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// field is a Bytes, but the user has registered a converter from a []byte
|
|
// to whatever the underlying Go type is
|
|
if typ, err = customConverter.customFromBytes(p); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.createNonPtrVal().Set(matchSettable(typ, w.val))
|
|
} else {
|
|
if isAny {
|
|
// Any means the Go type must receive a datamodel.Node
|
|
w.createNonPtrVal().Set(reflect.ValueOf(basicnode.NewBytes(p)))
|
|
} else {
|
|
w.createNonPtrVal().SetBytes(p)
|
|
}
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) AssignLink(link datamodel.Link) error {
|
|
val := w.createNonPtrVal()
|
|
// TODO: newVal.Type() panics if link==nil; add a test and fix.
|
|
customConverter := w.cfg.converterFor(w.val)
|
|
if _, ok := w.schemaType.(*schema.TypeAny); ok {
|
|
if customConverter != nil {
|
|
// field is an Any, so the converter will be an Any converter that wants
|
|
// a datamodel.Node to convert to whatever the underlying Go type is
|
|
typ, err := customConverter.customFromAny(basicnode.NewLink(link))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.createNonPtrVal().Set(matchSettable(typ, w.val))
|
|
} else {
|
|
// Any means the Go type must receive a datamodel.Node
|
|
val.Set(reflect.ValueOf(basicnode.NewLink(link)))
|
|
}
|
|
} else if customConverter != nil {
|
|
if cl, ok := link.(cidlink.Link); ok {
|
|
// field is a Link, but the user has registered a converter from a cid.Cid
|
|
// to whatever the underlying Go type is
|
|
typ, err := customConverter.customFromLink(cl.Cid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.createNonPtrVal().Set(matchSettable(typ, w.val))
|
|
} else {
|
|
return fmt.Errorf("bindnode: custom converter can only receive a cidlink.Link through AssignLink")
|
|
}
|
|
} else if newVal := reflect.ValueOf(link); newVal.Type().AssignableTo(val.Type()) {
|
|
// Directly assignable.
|
|
val.Set(newVal)
|
|
} else if newVal.Type() == goTypeCidLink && goTypeCid.AssignableTo(val.Type()) {
|
|
// Unbox a cidlink.Link to assign to a go-cid.Cid value.
|
|
newVal = newVal.FieldByName("Cid")
|
|
val.Set(newVal)
|
|
} else if actual := actualKind(w.schemaType); actual != datamodel.Kind_Link {
|
|
// We're assigning a Link to a schema type that isn't a Link.
|
|
return datamodel.ErrWrongKind{
|
|
TypeName: w.schemaType.Name(),
|
|
MethodName: "AssignLink",
|
|
AppropriateKind: datamodel.KindSet_JustLink,
|
|
ActualKind: actualKind(w.schemaType),
|
|
}
|
|
} else {
|
|
// The schema type is a Link, but we somehow can't assign to the Go value.
|
|
// Almost certainly a bug; we should have verified for compatibility upfront.
|
|
return fmt.Errorf("bindnode bug: AssignLink with %s argument can't be used on Go type %s",
|
|
newVal.Type(), val.Type())
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_assembler) AssignNode(node datamodel.Node) error {
|
|
// TODO: does this ever trigger?
|
|
// newVal := reflect.ValueOf(node)
|
|
// if newVal.Type().AssignableTo(w.val.Type()) {
|
|
// w.val.Set(newVal)
|
|
// return nil
|
|
// }
|
|
if uintNode, ok := node.(datamodel.UintNode); ok {
|
|
return w.assignUInt(uintNode)
|
|
}
|
|
return datamodel.Copy(node, w)
|
|
}
|
|
|
|
func (w *_assembler) Prototype() datamodel.NodePrototype {
|
|
return &_prototype{cfg: w.cfg, schemaType: w.schemaType, goType: w.val.Type()}
|
|
}
|
|
|
|
// _structAssembler is used for Struct assembling via BeginMap()
|
|
type _structAssembler struct {
|
|
// TODO: embed _assembler?
|
|
|
|
cfg config
|
|
|
|
schemaType *schema.TypeStruct
|
|
val reflect.Value // non-pointer
|
|
finish func() error
|
|
|
|
// TODO: more state checks
|
|
|
|
// TODO: Consider if we could do this in a cheaper way,
|
|
// such as looking at the reflect.Value directly.
|
|
// If not, at least avoid an extra alloc.
|
|
doneFields []bool
|
|
|
|
// TODO: optimize for structs
|
|
|
|
curKey _assembler
|
|
|
|
nextIndex int // only used by repr.go
|
|
}
|
|
|
|
func (w *_structAssembler) AssembleKey() datamodel.NodeAssembler {
|
|
w.curKey = _assembler{
|
|
cfg: w.cfg,
|
|
schemaType: schemaTypeString,
|
|
val: reflect.New(goTypeString).Elem(),
|
|
}
|
|
return &w.curKey
|
|
}
|
|
|
|
func (w *_structAssembler) AssembleValue() datamodel.NodeAssembler {
|
|
// TODO: optimize this to do one lookup by name
|
|
name := w.curKey.val.String()
|
|
field := w.schemaType.Field(name)
|
|
if field == nil {
|
|
// TODO: should've been raised when the key was submitted instead.
|
|
// TODO: should make well-typed errors for this.
|
|
return _errorAssembler{fmt.Errorf("bindnode TODO: invalid key: %q is not a field in type %s", name, w.schemaType.Name())}
|
|
// panic(schema.ErrInvalidKey{
|
|
// TypeName: w.schemaType.Name(),
|
|
// Key: basicnode.NewString(name),
|
|
// })
|
|
}
|
|
ftyp, ok := w.val.Type().FieldByName(fieldNameFromSchema(name))
|
|
if !ok {
|
|
// It is unfortunate this is not detected proactively earlier during bind.
|
|
return _errorAssembler{fmt.Errorf("schema type %q has field %q, we expect go struct to have field %q", w.schemaType.Name(), field.Name(), fieldNameFromSchema(name))}
|
|
}
|
|
if len(ftyp.Index) > 1 {
|
|
return _errorAssembler{fmt.Errorf("bindnode TODO: embedded fields")}
|
|
}
|
|
w.doneFields[ftyp.Index[0]] = true
|
|
fval := w.val.FieldByIndex(ftyp.Index)
|
|
if field.IsOptional() {
|
|
if fval.Kind() == reflect.Ptr {
|
|
// ptrVal = new(T); val = *ptrVal
|
|
fval.Set(reflect.New(fval.Type().Elem()))
|
|
fval = fval.Elem()
|
|
} else {
|
|
// val = *new(T)
|
|
fval.Set(reflect.New(fval.Type()).Elem())
|
|
}
|
|
}
|
|
// TODO: reuse same assembler for perf?
|
|
return &_assembler{
|
|
cfg: w.cfg,
|
|
schemaType: field.Type(),
|
|
val: fval,
|
|
nullable: field.IsNullable(),
|
|
}
|
|
}
|
|
|
|
func (w *_structAssembler) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
|
|
if err := w.AssembleKey().AssignString(k); err != nil {
|
|
return nil, err
|
|
}
|
|
am := w.AssembleValue()
|
|
return am, nil
|
|
}
|
|
|
|
func (w *_structAssembler) Finish() error {
|
|
fields := w.schemaType.Fields()
|
|
var missing []string
|
|
for i, field := range fields {
|
|
if !field.IsOptional() && !w.doneFields[i] {
|
|
missing = append(missing, field.Name())
|
|
}
|
|
}
|
|
if len(missing) > 0 {
|
|
return schema.ErrMissingRequiredField{Missing: missing}
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_structAssembler) KeyPrototype() datamodel.NodePrototype {
|
|
// TODO: if the user provided their own schema with their own typesystem,
|
|
// the schemaTypeString here may be using the wrong typesystem.
|
|
return &_prototype{cfg: w.cfg, schemaType: schemaTypeString, goType: goTypeString}
|
|
}
|
|
|
|
func (w *_structAssembler) ValuePrototype(k string) datamodel.NodePrototype {
|
|
panic("bindnode TODO: struct ValuePrototype")
|
|
}
|
|
|
|
type _errorAssembler struct {
|
|
err error
|
|
}
|
|
|
|
func (w _errorAssembler) BeginMap(int64) (datamodel.MapAssembler, error) { return nil, w.err }
|
|
func (w _errorAssembler) BeginList(int64) (datamodel.ListAssembler, error) { return nil, w.err }
|
|
func (w _errorAssembler) AssignNull() error { return w.err }
|
|
func (w _errorAssembler) AssignBool(bool) error { return w.err }
|
|
func (w _errorAssembler) AssignInt(int64) error { return w.err }
|
|
func (w _errorAssembler) AssignFloat(float64) error { return w.err }
|
|
func (w _errorAssembler) AssignString(string) error { return w.err }
|
|
func (w _errorAssembler) AssignBytes([]byte) error { return w.err }
|
|
func (w _errorAssembler) AssignLink(datamodel.Link) error { return w.err }
|
|
func (w _errorAssembler) AssignNode(datamodel.Node) error { return w.err }
|
|
func (w _errorAssembler) Prototype() datamodel.NodePrototype { return nil }
|
|
|
|
// used for Maps which we can assume are of type: struct{Keys []string, Values map[x]y},
|
|
// where we have Keys in keysVal and Values in valuesVal
|
|
type _mapAssembler struct {
|
|
cfg config
|
|
schemaType *schema.TypeMap
|
|
keysVal reflect.Value // non-pointer
|
|
valuesVal reflect.Value // non-pointer
|
|
finish func() error
|
|
|
|
// TODO: more state checks
|
|
|
|
curKey _assembler
|
|
}
|
|
|
|
func (w *_mapAssembler) AssembleKey() datamodel.NodeAssembler {
|
|
w.curKey = _assembler{
|
|
cfg: w.cfg,
|
|
schemaType: w.schemaType.KeyType(),
|
|
val: reflect.New(w.valuesVal.Type().Key()).Elem(),
|
|
}
|
|
return &w.curKey
|
|
}
|
|
|
|
func (w *_mapAssembler) AssembleValue() datamodel.NodeAssembler {
|
|
kval := w.curKey.val
|
|
val := reflect.New(w.valuesVal.Type().Elem()).Elem()
|
|
finish := func() error {
|
|
// TODO: check for duplicates in keysVal
|
|
w.keysVal.Set(reflect.Append(w.keysVal, kval))
|
|
|
|
w.valuesVal.SetMapIndex(kval, val)
|
|
return nil
|
|
}
|
|
return &_assembler{
|
|
cfg: w.cfg,
|
|
schemaType: w.schemaType.ValueType(),
|
|
val: val,
|
|
nullable: w.schemaType.ValueIsNullable(),
|
|
finish: finish,
|
|
}
|
|
}
|
|
|
|
func (w *_mapAssembler) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
|
|
if err := w.AssembleKey().AssignString(k); err != nil {
|
|
return nil, err
|
|
}
|
|
am := w.AssembleValue()
|
|
return am, nil
|
|
}
|
|
|
|
func (w *_mapAssembler) Finish() error {
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_mapAssembler) KeyPrototype() datamodel.NodePrototype {
|
|
return &_prototype{cfg: w.cfg, schemaType: w.schemaType.KeyType(), goType: w.valuesVal.Type().Key()}
|
|
}
|
|
|
|
func (w *_mapAssembler) ValuePrototype(k string) datamodel.NodePrototype {
|
|
return &_prototype{cfg: w.cfg, schemaType: w.schemaType.ValueType(), goType: w.valuesVal.Type().Elem()}
|
|
}
|
|
|
|
// _listAssembler is for operating directly on slices, which we have in val
|
|
type _listAssembler struct {
|
|
cfg config
|
|
schemaType *schema.TypeList
|
|
val reflect.Value // non-pointer
|
|
finish func() error
|
|
}
|
|
|
|
func (w *_listAssembler) AssembleValue() datamodel.NodeAssembler {
|
|
goType := w.val.Type().Elem()
|
|
// TODO: use a finish func to append
|
|
w.val.Set(reflect.Append(w.val, reflect.New(goType).Elem()))
|
|
return &_assembler{
|
|
cfg: w.cfg,
|
|
schemaType: w.schemaType.ValueType(),
|
|
val: w.val.Index(w.val.Len() - 1),
|
|
nullable: w.schemaType.ValueIsNullable(),
|
|
}
|
|
}
|
|
|
|
func (w *_listAssembler) Finish() error {
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_listAssembler) ValuePrototype(idx int64) datamodel.NodePrototype {
|
|
return &_prototype{cfg: w.cfg, schemaType: w.schemaType.ValueType(), goType: w.val.Type().Elem()}
|
|
}
|
|
|
|
// when assembling as a Map but we anticipate a single value, which we need to
|
|
// look up in the union members
|
|
type _unionAssembler struct {
|
|
cfg config
|
|
schemaType *schema.TypeUnion
|
|
val reflect.Value // non-pointer
|
|
finish func() error
|
|
|
|
// TODO: more state checks
|
|
|
|
curKey _assembler
|
|
}
|
|
|
|
func (w *_unionAssembler) AssembleKey() datamodel.NodeAssembler {
|
|
w.curKey = _assembler{
|
|
cfg: w.cfg,
|
|
schemaType: schemaTypeString,
|
|
val: reflect.New(goTypeString).Elem(),
|
|
}
|
|
return &w.curKey
|
|
}
|
|
|
|
func (w *_unionAssembler) AssembleValue() datamodel.NodeAssembler {
|
|
name := w.curKey.val.String()
|
|
var idx int
|
|
var mtyp schema.Type
|
|
for i, member := range w.schemaType.Members() {
|
|
if member.Name() == name {
|
|
idx = i
|
|
mtyp = member
|
|
break
|
|
}
|
|
}
|
|
if mtyp == nil {
|
|
return _errorAssembler{fmt.Errorf("bindnode TODO: missing member %s in %s", name, w.schemaType.Name())}
|
|
// return nil, datamodel.ErrInvalidKey{
|
|
// TypeName: w.schemaType.Name(),
|
|
// Key: basicnode.NewString(name),
|
|
// }
|
|
}
|
|
|
|
goType := w.val.Field(idx).Type().Elem()
|
|
valPtr := reflect.New(goType)
|
|
finish := func() error {
|
|
unionSetMember(w.val, idx, valPtr)
|
|
return nil
|
|
}
|
|
return &_assembler{
|
|
cfg: w.cfg,
|
|
schemaType: mtyp,
|
|
val: valPtr.Elem(),
|
|
finish: finish,
|
|
}
|
|
}
|
|
|
|
func (w *_unionAssembler) AssembleEntry(k string) (datamodel.NodeAssembler, error) {
|
|
if err := w.AssembleKey().AssignString(k); err != nil {
|
|
return nil, err
|
|
}
|
|
am := w.AssembleValue()
|
|
return am, nil
|
|
}
|
|
|
|
func (w *_unionAssembler) Finish() error {
|
|
// TODO(rvagg): I think this might allow setting multiple members of the union
|
|
// we need a test for this.
|
|
haveIdx, _ := unionMember(w.val)
|
|
if haveIdx < 0 {
|
|
return schema.ErrNotUnionStructure{TypeName: w.schemaType.Name(), Detail: "a union must have exactly one entry"}
|
|
}
|
|
if w.finish != nil {
|
|
if err := w.finish(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *_unionAssembler) KeyPrototype() datamodel.NodePrototype {
|
|
return &_prototype{cfg: w.cfg, schemaType: schemaTypeString, goType: goTypeString}
|
|
}
|
|
|
|
func (w *_unionAssembler) ValuePrototype(k string) datamodel.NodePrototype {
|
|
panic("bindnode TODO: union ValuePrototype")
|
|
}
|
|
|
|
// _structIterator is for iterating over Struct types which operate over Go
|
|
// structs. The iteration order is dictated by Go field declaration order which
|
|
// should match the schema for this type.
|
|
type _structIterator struct {
|
|
// TODO: support embedded fields?
|
|
cfg config
|
|
|
|
schemaType *schema.TypeStruct
|
|
fields []schema.StructField
|
|
val reflect.Value // non-pointer
|
|
nextIndex int
|
|
|
|
// these are only used in repr.go
|
|
reprEnd int
|
|
}
|
|
|
|
func (w *_structIterator) Next() (key, value datamodel.Node, _ error) {
|
|
if w.Done() {
|
|
return nil, nil, datamodel.ErrIteratorOverread{}
|
|
}
|
|
field := w.fields[w.nextIndex]
|
|
val := w.val.Field(w.nextIndex)
|
|
w.nextIndex++
|
|
key = basicnode.NewString(field.Name())
|
|
if field.IsOptional() {
|
|
if val.IsNil() {
|
|
return key, datamodel.Absent, nil
|
|
}
|
|
if val.Kind() == reflect.Ptr {
|
|
val = val.Elem()
|
|
}
|
|
}
|
|
_, isAny := field.Type().(*schema.TypeAny)
|
|
if isAny {
|
|
if customConverter := w.cfg.converterFor(val); customConverter != nil {
|
|
// field is an Any and we have an Any converter which takes the underlying
|
|
// struct field value and returns a datamodel.Node
|
|
v, err := customConverter.customToAny(ptrVal(val).Interface())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return key, v, nil
|
|
}
|
|
}
|
|
if field.IsNullable() {
|
|
if val.IsNil() {
|
|
return key, datamodel.Null, nil
|
|
}
|
|
if val.Kind() == reflect.Ptr {
|
|
val = val.Elem()
|
|
}
|
|
}
|
|
if isAny {
|
|
// field holds a datamodel.Node
|
|
return key, nonPtrVal(val).Interface().(datamodel.Node), nil
|
|
}
|
|
return key, newNode(w.cfg, field.Type(), val), nil
|
|
}
|
|
|
|
func (w *_structIterator) Done() bool {
|
|
return w.nextIndex >= len(w.fields)
|
|
}
|
|
|
|
// _mapIterator is for iterating over a struct{Keys []string, Values map[x]y},
|
|
// where we have the Keys in keysVal and Values in valuesVal
|
|
type _mapIterator struct {
|
|
cfg config
|
|
schemaType *schema.TypeMap
|
|
keysVal reflect.Value // non-pointer
|
|
valuesVal reflect.Value // non-pointer
|
|
nextIndex int
|
|
}
|
|
|
|
func (w *_mapIterator) Next() (key, value datamodel.Node, _ error) {
|
|
if w.Done() {
|
|
return nil, nil, datamodel.ErrIteratorOverread{}
|
|
}
|
|
goKey := w.keysVal.Index(w.nextIndex)
|
|
val := w.valuesVal.MapIndex(goKey)
|
|
w.nextIndex++
|
|
|
|
key = newNode(w.cfg, w.schemaType.KeyType(), goKey)
|
|
_, isAny := w.schemaType.ValueType().(*schema.TypeAny)
|
|
if isAny {
|
|
if customConverter := w.cfg.converterFor(val); customConverter != nil {
|
|
// values of this map are Any and we have an Any converter which takes the
|
|
// underlying map value and returns a datamodel.Node
|
|
|
|
// TODO(rvagg): can't call ptrVal on a map value that's not a pointer
|
|
// so only map[string]*foo will work for the Values map and an Any
|
|
// converter. Should we check in infer.go?
|
|
val, err := customConverter.customToAny(ptrVal(val).Interface())
|
|
return key, val, err
|
|
}
|
|
}
|
|
if w.schemaType.ValueIsNullable() {
|
|
if val.IsNil() {
|
|
return key, datamodel.Null, nil
|
|
}
|
|
val = val.Elem() // nullable entries are pointers
|
|
}
|
|
if isAny {
|
|
// Values holds datamodel.Nodes
|
|
return key, nonPtrVal(val).Interface().(datamodel.Node), nil
|
|
}
|
|
return key, newNode(w.cfg, w.schemaType.ValueType(), val), nil
|
|
}
|
|
|
|
func (w *_mapIterator) Done() bool {
|
|
return w.nextIndex >= w.keysVal.Len()
|
|
}
|
|
|
|
// _listIterator is for iterating over slices, which is held in val
|
|
type _listIterator struct {
|
|
cfg config
|
|
schemaType *schema.TypeList
|
|
val reflect.Value // non-pointer
|
|
nextIndex int
|
|
}
|
|
|
|
func (w *_listIterator) Next() (index int64, value datamodel.Node, _ error) {
|
|
if w.Done() {
|
|
return 0, nil, datamodel.ErrIteratorOverread{}
|
|
}
|
|
idx := int64(w.nextIndex)
|
|
val := w.val.Index(w.nextIndex)
|
|
w.nextIndex++
|
|
if w.schemaType.ValueIsNullable() {
|
|
if val.IsNil() {
|
|
return idx, datamodel.Null, nil
|
|
}
|
|
val = val.Elem() // nullable values are pointers
|
|
}
|
|
if _, ok := w.schemaType.ValueType().(*schema.TypeAny); ok {
|
|
if customConverter := w.cfg.converterFor(val); customConverter != nil {
|
|
// values are Any and we have an Any converter which can take whatever
|
|
// the underlying Go type in this slice is and return a datamodel.Node
|
|
val, err := customConverter.customToAny(ptrVal(val).Interface())
|
|
return idx, val, err
|
|
}
|
|
// values are Any, assume that they are datamodel.Nodes
|
|
return idx, nonPtrVal(val).Interface().(datamodel.Node), nil
|
|
}
|
|
return idx, newNode(w.cfg, w.schemaType.ValueType(), val), nil
|
|
}
|
|
|
|
func (w *_listIterator) Done() bool {
|
|
return w.nextIndex >= w.val.Len()
|
|
}
|
|
|
|
type _unionIterator struct {
|
|
// TODO: support embedded fields?
|
|
cfg config
|
|
schemaType *schema.TypeUnion
|
|
members []schema.Type
|
|
val reflect.Value // non-pointer
|
|
|
|
done bool
|
|
}
|
|
|
|
func (w *_unionIterator) Next() (key, value datamodel.Node, _ error) {
|
|
// we can only call this once for a union since a union can only have one
|
|
// entry even though it behaves like a Map
|
|
if w.Done() {
|
|
return nil, nil, datamodel.ErrIteratorOverread{}
|
|
}
|
|
w.done = true
|
|
|
|
haveIdx, mval := unionMember(w.val)
|
|
if haveIdx < 0 {
|
|
return nil, nil, fmt.Errorf("bindnode: union %s has no member", w.val.Type())
|
|
}
|
|
mtyp := w.members[haveIdx]
|
|
|
|
node := newNode(w.cfg, mtyp, mval)
|
|
key = basicnode.NewString(mtyp.Name())
|
|
return key, node, nil
|
|
}
|
|
|
|
func (w *_unionIterator) Done() bool {
|
|
return w.done
|
|
}
|
|
|
|
// --- uint64 special case handling
|
|
|
|
type _uintNode struct {
|
|
cfg config
|
|
schemaType schema.Type
|
|
|
|
val reflect.Value // non-pointer
|
|
}
|
|
|
|
func (tu *_uintNode) Type() schema.Type {
|
|
return tu.schemaType
|
|
}
|
|
func (tu *_uintNode) Representation() datamodel.Node {
|
|
return (*_uintNodeRepr)(tu)
|
|
}
|
|
func (_uintNode) Kind() datamodel.Kind {
|
|
return datamodel.Kind_Int
|
|
}
|
|
func (_uintNode) LookupByString(string) (datamodel.Node, error) {
|
|
return mixins.Int{TypeName: "int"}.LookupByString("")
|
|
}
|
|
func (_uintNode) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
|
|
return mixins.Int{TypeName: "int"}.LookupByNode(nil)
|
|
}
|
|
func (_uintNode) LookupByIndex(idx int64) (datamodel.Node, error) {
|
|
return mixins.Int{TypeName: "int"}.LookupByIndex(0)
|
|
}
|
|
func (_uintNode) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
|
|
return mixins.Int{TypeName: "int"}.LookupBySegment(seg)
|
|
}
|
|
func (_uintNode) MapIterator() datamodel.MapIterator {
|
|
return nil
|
|
}
|
|
func (_uintNode) ListIterator() datamodel.ListIterator {
|
|
return nil
|
|
}
|
|
func (_uintNode) Length() int64 {
|
|
return -1
|
|
}
|
|
func (_uintNode) IsAbsent() bool {
|
|
return false
|
|
}
|
|
func (_uintNode) IsNull() bool {
|
|
return false
|
|
}
|
|
func (_uintNode) AsBool() (bool, error) {
|
|
return mixins.Int{TypeName: "int"}.AsBool()
|
|
}
|
|
func (tu *_uintNode) AsInt() (int64, error) {
|
|
return (*_uintNodeRepr)(tu).AsInt()
|
|
}
|
|
func (tu *_uintNode) AsUint() (uint64, error) {
|
|
return (*_uintNodeRepr)(tu).AsUint()
|
|
}
|
|
func (_uintNode) AsFloat() (float64, error) {
|
|
return mixins.Int{TypeName: "int"}.AsFloat()
|
|
}
|
|
func (_uintNode) AsString() (string, error) {
|
|
return mixins.Int{TypeName: "int"}.AsString()
|
|
}
|
|
func (_uintNode) AsBytes() ([]byte, error) {
|
|
return mixins.Int{TypeName: "int"}.AsBytes()
|
|
}
|
|
func (_uintNode) AsLink() (datamodel.Link, error) {
|
|
return mixins.Int{TypeName: "int"}.AsLink()
|
|
}
|
|
func (_uintNode) Prototype() datamodel.NodePrototype {
|
|
return basicnode.Prototype__Int{}
|
|
}
|
|
|
|
// we need this for _uintNode#Representation() so we don't return a TypeNode
|
|
type _uintNodeRepr _uintNode
|
|
|
|
func (_uintNodeRepr) Kind() datamodel.Kind {
|
|
return datamodel.Kind_Int
|
|
}
|
|
func (_uintNodeRepr) LookupByString(string) (datamodel.Node, error) {
|
|
return mixins.Int{TypeName: "int"}.LookupByString("")
|
|
}
|
|
func (_uintNodeRepr) LookupByNode(key datamodel.Node) (datamodel.Node, error) {
|
|
return mixins.Int{TypeName: "int"}.LookupByNode(nil)
|
|
}
|
|
func (_uintNodeRepr) LookupByIndex(idx int64) (datamodel.Node, error) {
|
|
return mixins.Int{TypeName: "int"}.LookupByIndex(0)
|
|
}
|
|
func (_uintNodeRepr) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
|
|
return mixins.Int{TypeName: "int"}.LookupBySegment(seg)
|
|
}
|
|
func (_uintNodeRepr) MapIterator() datamodel.MapIterator {
|
|
return nil
|
|
}
|
|
func (_uintNodeRepr) ListIterator() datamodel.ListIterator {
|
|
return nil
|
|
}
|
|
func (_uintNodeRepr) Length() int64 {
|
|
return -1
|
|
}
|
|
func (_uintNodeRepr) IsAbsent() bool {
|
|
return false
|
|
}
|
|
func (_uintNodeRepr) IsNull() bool {
|
|
return false
|
|
}
|
|
func (_uintNodeRepr) AsBool() (bool, error) {
|
|
return mixins.Int{TypeName: "int"}.AsBool()
|
|
}
|
|
func (tu *_uintNodeRepr) AsInt() (int64, error) {
|
|
if err := compatibleKind(tu.schemaType, datamodel.Kind_Int); err != nil {
|
|
return 0, err
|
|
}
|
|
if customConverter := tu.cfg.converterFor(tu.val); customConverter != nil {
|
|
// user has registered a converter that takes the underlying type and returns an int
|
|
return customConverter.customToInt(ptrVal(tu.val).Interface())
|
|
}
|
|
val := nonPtrVal(tu.val)
|
|
// we can assume it's a uint64 at this point
|
|
u := val.Uint()
|
|
if u > math.MaxInt64 {
|
|
return 0, fmt.Errorf("bindnode: integer overflow, %d is too large for an int64", u)
|
|
}
|
|
return int64(u), nil
|
|
}
|
|
func (tu *_uintNodeRepr) AsUint() (uint64, error) {
|
|
if err := compatibleKind(tu.schemaType, datamodel.Kind_Int); err != nil {
|
|
return 0, err
|
|
}
|
|
// TODO(rvagg): do we want a converter option for uint values? do we combine it
|
|
// with int converters?
|
|
// we can assume it's a uint64 at this point
|
|
return nonPtrVal(tu.val).Uint(), nil
|
|
}
|
|
func (_uintNodeRepr) AsFloat() (float64, error) {
|
|
return mixins.Int{TypeName: "int"}.AsFloat()
|
|
}
|
|
func (_uintNodeRepr) AsString() (string, error) {
|
|
return mixins.Int{TypeName: "int"}.AsString()
|
|
}
|
|
func (_uintNodeRepr) AsBytes() ([]byte, error) {
|
|
return mixins.Int{TypeName: "int"}.AsBytes()
|
|
}
|
|
func (_uintNodeRepr) AsLink() (datamodel.Link, error) {
|
|
return mixins.Int{TypeName: "int"}.AsLink()
|
|
}
|
|
func (_uintNodeRepr) Prototype() datamodel.NodePrototype {
|
|
return basicnode.Prototype__Int{}
|
|
}
|