Commit 806e36fb authored by Daniel Martí's avatar Daniel Martí

node/bindnode: start of a reflect-based Node implementation

Lots of TODOs and polishing to do, but it passes the schema/gen/go tests
with minimal changes.

Follow-up commits will continue filling in the gaps and adding
documentation, notably examples. We'll also make the tests run by
default, after a bit of refactoring.
parent 6d29b3c3
package bindnode_test
import (
"os"
ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/fluent/qp"
"github.com/ipld/go-ipld-prime/node/bindnode"
"github.com/ipld/go-ipld-prime/schema"
"github.com/polydawn/refmt/json"
)
func ExamplePrototypeOnlySchema() {
ts := schema.TypeSystem{}
ts.Init()
ts.Accumulate(schema.SpawnString("String"))
ts.Accumulate(schema.SpawnInt("Int"))
ts.Accumulate(schema.SpawnStruct("Person",
[]schema.StructField{
schema.SpawnStructField("Name", "String", false, false),
schema.SpawnStructField("Age", "Int", true, false),
schema.SpawnStructField("Friends", "List_String", false, false),
},
schema.SpawnStructRepresentationMap(nil),
))
ts.Accumulate(schema.SpawnList("List_String", "String", false))
schemaType := ts.TypeByName("Person")
proto := bindnode.PrototypeOnlySchema(schemaType)
n, err := qp.BuildMap(proto, -1, func(ma ipld.MapAssembler) {
qp.MapEntry(ma, "Name", qp.String("Michael"))
qp.MapEntry(ma, "Friends", qp.List(-1, func(la ipld.ListAssembler) {
qp.ListEntry(la, qp.String("Sarah"))
qp.ListEntry(la, qp.String("Alex"))
}))
})
if err != nil {
panic(err)
}
nr := n.(schema.TypedNode).Representation()
dagjson.Marshal(nr, json.NewEncoder(os.Stdout, json.EncodeOptions{}), true)
// Output:
// {"Name":"Michael","Friends":["Sarah","Alex"]}
}
package bindnode
import (
"fmt"
"reflect"
"strings"
ipld "github.com/ipld/go-ipld-prime"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
"github.com/ipld/go-ipld-prime/schema"
)
// WrapNoSchema implements an ipld.Node given a pointer to a Go value.
//
// Same rules as PrototypeNoSchema apply.
func WrapNoSchema(ptr interface{}) ipld.Node {
panic("TODO")
// ptrVal := reflect.ValueOf(ptr)
// if ptrVal.Kind() != reflect.Ptr {
// panic("must be a pointer")
// }
// return &_node{val: ptrVal.Elem()}
}
// Unwrap takes an ipld.Node implemented by one of the Wrap* or Prototype* APIs,
// and returns a pointer to the inner Go value.
//
// Unwrap returns the input node 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 node
}
if val.Kind() == reflect.Ptr {
panic("didn't expect val to be a pointer")
}
if !val.CanAddr() {
// Not addressable? Just return the interface as-is.
// TODO: This happens in some tests, figure out why.
return val.Interface()
}
return val.Addr().Interface()
}
// PrototypeNoSchema implements an ipld.NodePrototype given a Go pointer type.
//
// In this form, no IPLD schema is used; it is entirely inferred from the Go
// type.
//
// Go types map to schema types in simple ways: Go string to schema String, Go
// []byte to schema Bytes, Go struct to schema Map, and so on.
//
// A Go struct field is optional when its type is a pointer. Nullable fields are
// not supported in this mode.
func PrototypeNoSchema(ptrType interface{}) ipld.NodePrototype {
panic("TODO")
// typ := reflect.TypeOf(ptrType)
// if typ.Kind() != reflect.Ptr {
// panic("must be a pointer")
// }
// return &_prototype{goType: typ.Elem()}
}
// PrototypeOnlySchema implements an ipld.NodePrototype given an IPLD schema type.
//
// In this form, Go values are constructed with types inferred from the IPLD
// schema, like a reverse of PrototypeNoSchema.
func PrototypeOnlySchema(schemaType schema.Type) ipld.NodePrototype {
goType := inferGoType(schemaType)
return prototype(goType, schemaType)
}
// from IPLD Schema field names like "foo" to Go field names like "Foo".
func fieldNameFromSchema(name string) string {
return strings.Title(name)
}
func inferGoType(typ schema.Type) reflect.Type {
switch typ := typ.(type) {
case *schema.TypeInt:
return goTypeInt
case *schema.TypeString:
return goTypeString
case *schema.TypeStruct:
fields := typ.Fields()
goFields := make([]reflect.StructField, len(fields))
for i, field := range fields {
ftyp := inferGoType(field.Type())
if field.IsNullable() {
ftyp = reflect.PtrTo(ftyp)
}
if field.IsOptional() {
ftyp = reflect.PtrTo(ftyp)
}
goFields[i] = reflect.StructField{
Name: fieldNameFromSchema(field.Name()),
Type: ftyp,
}
}
return reflect.StructOf(goFields)
case *schema.TypeMap:
ktyp := inferGoType(typ.KeyType())
vtyp := inferGoType(typ.ValueType())
if typ.ValueIsNullable() {
vtyp = reflect.PtrTo(vtyp)
}
return reflect.MapOf(ktyp, vtyp)
case *schema.TypeList:
etyp := inferGoType(typ.ValueType())
if typ.ValueIsNullable() {
etyp = reflect.PtrTo(etyp)
}
return reflect.SliceOf(etyp)
case *schema.TypeUnion:
type goUnion struct {
Index int // 0..len(typ.Members)-1
Value interface{}
}
return reflect.TypeOf(goUnion{})
}
panic(fmt.Sprintf("%T\n", typ))
}
// Prototype implements an ipld.NodePrototype given a Go pointer type and an
// IPLD schema type.
//
// In this form, it is assumed that the Go type and IPLD schema type are
// compatible. TODO: check upfront and panic otherwise
func Prototype(ptrType interface{}, schemaType schema.Type) ipld.NodePrototype {
goPtrType := reflect.TypeOf(ptrType)
if goPtrType.Kind() != reflect.Ptr {
panic("ptrType must be a pointer")
}
return prototype(goPtrType.Elem(), schemaType)
}
func prototype(goType reflect.Type, schemaType schema.Type) ipld.NodePrototype {
if goType.Kind() == reflect.Invalid {
panic("goType must be valid")
}
if schemaType == nil {
panic("schemaType must not be nil")
}
return &_prototype{schemaType: schemaType, goType: goType}
}
var (
_ ipld.NodePrototype = (*_prototype)(nil)
_ ipld.Node = (*_node)(nil)
_ schema.TypedNode = (*_node)(nil)
_ ipld.NodeBuilder = (*_builder)(nil)
_ ipld.NodeAssembler = (*_assembler)(nil)
_ ipld.MapAssembler = (*_structAssembler)(nil)
_ ipld.MapIterator = (*_structIterator)(nil)
_ ipld.ListAssembler = (*_listAssembler)(nil)
_ ipld.ListIterator = (*_listIterator)(nil)
)
type _prototype struct {
schemaType schema.Type
goType reflect.Type // non-pointer
}
func (w *_prototype) NewBuilder() ipld.NodeBuilder {
return &_builder{_assembler{
schemaType: w.schemaType,
val: reflect.New(w.goType).Elem(),
}}
}
// TODO: consider these Typed interfaces for the schema package
type TypedPrototype interface {
ipld.NodePrototype
Representation() ipld.NodePrototype
}
type TypedAssembler interface {
ipld.NodeAssembler
Representation() ipld.NodeAssembler
}
func (w *_prototype) Representation() ipld.NodePrototype {
return (*_prototypeRepr)(w)
}
var (
goTypeString = reflect.TypeOf("")
goTypeInt = reflect.TypeOf(int(0))
goTypeBytes = reflect.TypeOf([]byte{})
goTypeLink = reflect.TypeOf((*ipld.Link)(nil)).Elem()
schemaTypeFieldName = schema.SpawnString("fieldNameString")
)
type _node struct {
schemaType schema.Type
val reflect.Value // non-pointer
}
// TODO: only expose TypedNode methods if the schema was explicit.
// type _typedNode struct {
// _node
// }
func (w *_node) Type() schema.Type {
return w.schemaType
}
func (w *_node) Representation() ipld.Node {
return (*_nodeRepr)(w)
}
func (w *_node) Kind() ipld.Kind {
return w.schemaType.TypeKind().ActsLike()
}
func (w *_node) LookupByString(key string) (ipld.Node, error) {
switch typ := w.schemaType.(type) {
case *schema.TypeStruct:
field := typ.Field(key)
if field == nil {
return nil, ipld.ErrInvalidKey{
TypeName: typ.Name().String(),
Key: basicnode.NewString(key),
}
}
fval := w.val.FieldByName(fieldNameFromSchema(key))
if !fval.IsValid() {
panic("TODO: go-schema mismatch")
}
if field.IsOptional() {
if fval.IsNil() {
return ipld.Absent, nil
}
fval = fval.Elem()
}
if field.IsNullable() {
if fval.IsNil() {
return ipld.Null, nil
}
fval = fval.Elem()
}
node := &_node{
schemaType: field.Type(),
val: fval,
}
return node, nil
case *schema.TypeMap:
var kval reflect.Value
switch ktyp := typ.KeyType().(type) {
case *schema.TypeString:
kval = reflect.ValueOf(key)
default:
asm := &_assembler{
schemaType: ktyp,
val: reflect.New(w.val.Type().Key()).Elem(),
}
if err := (*_assemblerRepr)(asm).AssignString(key); err != nil {
return nil, err
}
kval = asm.val
}
fval := w.val.MapIndex(kval)
if !fval.IsValid() { // not found
return nil, ipld.ErrNotExists{
// TODO
}
}
// 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 ipld.Null, nil
}
fval = fval.Elem()
}
node := &_node{
schemaType: typ.ValueType(),
val: fval,
}
return node, nil
case *schema.TypeUnion:
var idx int
var mtyp schema.Type
for i, member := range typ.Members() {
if member.Name().String() == key {
idx = i
mtyp = member
break
}
}
if mtyp == nil { // not found
return nil, ipld.ErrNotExists{
// TODO
}
}
haveIdx := int(w.val.FieldByName("Index").Int())
if haveIdx != idx { // mismatching type
return nil, ipld.ErrNotExists{
// TODO
}
}
mval := w.val.FieldByName("Value").Elem()
node := &_node{
schemaType: mtyp,
val: mval,
}
return node, nil
}
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "LookupByString",
// TODO
}
}
func (w *_node) LookupByNode(key ipld.Node) (ipld.Node, error) {
panic("TODO: LookupByNode")
}
func (w *_node) LookupByIndex(idx int64) (ipld.Node, error) {
switch typ := w.schemaType.(type) {
case *schema.TypeList:
if idx < 0 || int(idx) >= w.val.Len() {
return nil, ipld.ErrNotExists{
// TODO
}
}
// TODO: bounds check
val := w.val.Index(int(idx))
if typ.ValueIsNullable() {
if val.IsNil() {
return ipld.Null, nil
}
val = val.Elem()
}
return &_node{schemaType: typ.ValueType(), val: val}, nil
}
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "LookupByIndex",
// TODO
}
}
func (w *_node) LookupBySegment(seg ipld.PathSegment) (ipld.Node, error) {
panic("TODO: LookupBySegment")
}
func (w *_node) MapIterator() ipld.MapIterator {
switch typ := w.schemaType.(type) {
case *schema.TypeStruct:
return &_structIterator{
schemaType: typ,
fields: typ.Fields(),
val: w.val,
}
case *schema.TypeUnion:
return &_unionIterator{
schemaType: typ,
members: typ.Members(),
val: w.val,
}
case *schema.TypeMap:
panic("TODO: ")
}
return nil
}
func (w *_node) ListIterator() ipld.ListIterator {
val := w.val
if val.Type().Kind() == reflect.Ptr {
if !val.IsNil() {
val = val.Elem()
}
}
switch typ := w.schemaType.(type) {
case *schema.TypeList:
return &_listIterator{schemaType: typ, val: val}
}
return nil
}
func (w *_node) Length() int64 {
switch w.Kind() {
case ipld.Kind_Map:
switch typ := w.schemaType.(type) {
case *schema.TypeStruct:
return int64(len(typ.Fields()))
case *schema.TypeUnion:
return 1
}
fallthrough // map
case ipld.Kind_List:
return int64(w.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
}
func (w *_node) AsBool() (bool, error) {
panic("TODO: ")
}
func (w *_node) AsInt() (int64, error) {
if w.Kind() != ipld.Kind_Int {
return 0, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AsInt",
// TODO
}
}
return w.val.Int(), nil
}
func (w *_node) AsFloat() (float64, error) {
panic("TODO: ")
}
func (w *_node) AsString() (string, error) {
if w.Kind() != ipld.Kind_String {
return "", ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AsString",
// TODO
}
}
return w.val.String(), nil
}
func (w *_node) AsBytes() ([]byte, error) {
if w.Kind() != ipld.Kind_Bytes {
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AsBytes",
// TODO
}
}
return w.val.Bytes(), nil
}
func (w *_node) AsLink() (ipld.Link, error) {
if w.Kind() != ipld.Kind_Link {
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AsLink",
// TODO
}
}
link, _ := w.val.Interface().(ipld.Link)
return link, nil
}
func (w *_node) Prototype() ipld.NodePrototype {
panic("TODO: Prototype")
}
type _builder struct {
_assembler
}
func (w *_builder) Build() ipld.Node {
return &_node{schemaType: w.schemaType, val: w.val}
}
func (w *_builder) Reset() {
panic("TODO: Reset")
}
type _assembler struct {
schemaType schema.Type
val reflect.Value // non-pointer
finish func() error
// kinded bool // true if val is interface{} for a kinded union
nullable bool // true if field or map value is nullable
}
func (w *_assembler) nonPtrVal() reflect.Value {
val := w.val
if w.nullable {
val.Set(reflect.New(val.Type().Elem()))
val = val.Elem()
}
return val
}
func (w *_assembler) kind() ipld.Kind {
return w.schemaType.TypeKind().ActsLike()
}
func (w *_assembler) Representation() ipld.NodeAssembler {
return (*_assemblerRepr)(w)
}
func (w *_assembler) BeginMap(sizeHint int64) (ipld.MapAssembler, error) {
switch typ := w.schemaType.(type) {
case *schema.TypeStruct:
val := w.nonPtrVal()
doneFields := make([]bool, val.NumField())
return &_structAssembler{
schemaType: typ,
val: val,
doneFields: doneFields,
finish: w.finish,
}, nil
case *schema.TypeMap:
val := w.nonPtrVal()
if val.IsNil() {
val.Set(reflect.MakeMap(val.Type()))
}
return &_mapAssembler{
schemaType: typ,
val: val,
finish: w.finish,
}, nil
case *schema.TypeUnion:
val := w.nonPtrVal()
return &_unionAssembler{
schemaType: typ,
val: val,
finish: w.finish,
}, nil
}
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "BeginMap",
// TODO
}
}
func (w *_assembler) BeginList(sizeHint int64) (ipld.ListAssembler, error) {
switch typ := w.schemaType.(type) {
case *schema.TypeList:
val := w.nonPtrVal()
return &_listAssembler{
schemaType: typ,
val: val,
finish: w.finish,
}, nil
}
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "BeginList",
// TODO
}
}
func (w *_assembler) AssignNull() error {
if !w.nullable {
return ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AssignNull",
// TODO
}
}
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(bool) error {
panic("TODO: AssignBool")
}
func (w *_assembler) AssignInt(i int64) error {
if w.kind() != ipld.Kind_Int {
return ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AssignInt",
AppropriateKind: ipld.KindSet{ipld.Kind_Int},
ActualKind: w.kind(),
}
}
w.nonPtrVal().SetInt(i)
if w.finish != nil {
if err := w.finish(); err != nil {
return err
}
}
return nil
}
func (w *_assembler) AssignFloat(float64) error {
panic("TODO: AssignFloat")
}
func (w *_assembler) AssignString(s string) error {
if w.kind() != ipld.Kind_String {
return ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AssignString",
AppropriateKind: ipld.KindSet{ipld.Kind_String},
ActualKind: w.kind(),
}
}
w.nonPtrVal().SetString(s)
if w.finish != nil {
if err := w.finish(); err != nil {
return err
}
}
return nil
}
func (w *_assembler) AssignBytes(p []byte) error {
if w.kind() != ipld.Kind_Bytes {
return ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AssignBytes",
AppropriateKind: ipld.KindSet{ipld.Kind_Bytes},
ActualKind: w.kind(),
}
}
w.nonPtrVal().SetBytes(p)
return nil
}
func (w *_assembler) AssignLink(link ipld.Link) error {
newVal := reflect.ValueOf(link)
if !newVal.Type().AssignableTo(w.val.Type()) {
return ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AssignLink",
// TODO
}
}
w.nonPtrVal().Set(newVal)
if w.finish != nil {
if err := w.finish(); err != nil {
return err
}
}
return nil
}
func (w *_assembler) AssignNode(node ipld.Node) error {
// TODO: does this ever trigger?
// newVal := reflect.ValueOf(node)
// if newVal.Type().AssignableTo(w.val.Type()) {
// w.val.Set(newVal)
// return nil
// }
switch node.Kind() {
case ipld.Kind_Map:
itr := node.MapIterator()
// TODO: consider reusing this code from elsewhere,
// via something like ipld.BlindCopyMap.
am, err := w.BeginMap(-1) // TODO: length?
if err != nil {
return err
}
for !itr.Done() {
k, v, err := itr.Next()
if err != nil {
return err
}
if err := am.AssembleKey().AssignNode(k); err != nil {
return err
}
if err := am.AssembleValue().AssignNode(v); err != nil {
return err
}
}
return am.Finish()
case ipld.Kind_List:
itr := node.ListIterator()
am, err := w.BeginList(-1) // TODO: length?
if err != nil {
return err
}
for !itr.Done() {
_, v, err := itr.Next()
if err != nil {
return err
}
if err := am.AssembleValue().AssignNode(v); err != nil {
return err
}
}
return am.Finish()
case ipld.Kind_Int:
i, err := node.AsInt()
if err != nil {
return err
}
return w.AssignInt(i)
case ipld.Kind_String:
s, err := node.AsString()
if err != nil {
return err
}
return w.AssignString(s)
case ipld.Kind_Bytes:
p, err := node.AsBytes()
if err != nil {
return err
}
return w.AssignBytes(p)
case ipld.Kind_Link:
l, err := node.AsLink()
if err != nil {
return err
}
return w.AssignLink(l)
case ipld.Kind_Null:
return w.AssignNull()
}
// fmt.Println(w.val.Type(), reflect.TypeOf(node))
panic(fmt.Sprintf("TODO: %v %v", w.val.Type(), node.Kind()))
}
func (w *_assembler) Prototype() ipld.NodePrototype {
panic("TODO: Assembler.Prototype")
}
type _structAssembler struct {
// TODO: embed _assembler?
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() ipld.NodeAssembler {
w.curKey = _assembler{
schemaType: schemaTypeFieldName,
val: reflect.New(goTypeString).Elem(),
}
return &w.curKey
}
func (w *_structAssembler) AssembleValue() ipld.NodeAssembler {
// TODO: optimize this to do one lookup by name
name := w.curKey.val.String()
field := w.schemaType.Field(name)
if field == nil {
panic(name)
// return nil, ipld.ErrInvalidKey{
// TypeName: w.schemaType.Name().String(),
// Key: basicnode.NewString(name),
// }
}
ftyp, ok := w.val.Type().FieldByName(fieldNameFromSchema(name))
if !ok {
panic("TODO: go-schema mismatch")
}
if len(ftyp.Index) > 1 {
panic("TODO: embedded fields")
}
w.doneFields[ftyp.Index[0]] = true
fval := w.val.FieldByIndex(ftyp.Index)
if field.IsOptional() {
fval.Set(reflect.New(fval.Type().Elem()))
fval = fval.Elem()
}
// TODO: reuse same assembler for perf?
return &_assembler{
schemaType: field.Type(),
val: fval,
nullable: field.IsNullable(),
}
}
func (w *_structAssembler) AssembleEntry(k string) (ipld.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 ipld.ErrMissingRequiredField{Missing: missing}
}
if w.finish != nil {
if err := w.finish(); err != nil {
return err
}
}
return nil
}
func (w *_structAssembler) KeyPrototype() ipld.NodePrototype {
return &_prototype{schemaType: schemaTypeFieldName, goType: goTypeString}
}
func (w *_structAssembler) ValuePrototype(k string) ipld.NodePrototype {
panic("TODO: struct ValuePrototype")
}
type _mapAssembler struct {
schemaType *schema.TypeMap
val reflect.Value // non-pointer
finish func() error
// TODO: more state checks
curKey _assembler
nextIndex int // only used by repr.go
}
func (w *_mapAssembler) AssembleKey() ipld.NodeAssembler {
w.curKey = _assembler{
schemaType: w.schemaType.KeyType(),
val: reflect.New(w.val.Type().Key()).Elem(),
}
return &w.curKey
}
func (w *_mapAssembler) AssembleValue() ipld.NodeAssembler {
kval := w.curKey.val
val := reflect.New(w.val.Type().Elem()).Elem()
finish := func() error {
// fmt.Println(kval.Interface(), val.Interface())
w.val.SetMapIndex(kval, val)
return nil
}
return &_assembler{
schemaType: w.schemaType.ValueType(),
val: val,
nullable: w.schemaType.ValueIsNullable(),
finish: finish,
}
}
func (w *_mapAssembler) AssembleEntry(k string) (ipld.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() ipld.NodePrototype {
return &_prototype{schemaType: w.schemaType.KeyType(), goType: w.val.Type().Key()}
}
func (w *_mapAssembler) ValuePrototype(k string) ipld.NodePrototype {
panic("TODO: struct ValuePrototype")
}
type _listAssembler struct {
schemaType *schema.TypeList
val reflect.Value // non-pointer
finish func() error
}
func (w *_listAssembler) AssembleValue() ipld.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{
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) ipld.NodePrototype {
panic("TODO: list ValuePrototype")
}
type _unionAssembler struct {
schemaType *schema.TypeUnion
val reflect.Value // non-pointer
finish func() error
// TODO: more state checks
curKey _assembler
nextIndex int // only used by repr.go
}
func (w *_unionAssembler) AssembleKey() ipld.NodeAssembler {
w.curKey = _assembler{
schemaType: schemaTypeFieldName,
val: reflect.New(goTypeString).Elem(),
}
return &w.curKey
}
func (w *_unionAssembler) AssembleValue() ipld.NodeAssembler {
name := w.curKey.val.String()
var idx int
var mtyp schema.Type
for i, member := range w.schemaType.Members() {
if member.Name().String() == name {
idx = i
mtyp = member
break
}
}
if mtyp == nil {
panic("TODO: missing member")
// return nil, ipld.ErrInvalidKey{
// TypeName: w.schemaType.Name().String(),
// Key: basicnode.NewString(name),
// }
}
goType := inferGoType(mtyp) // TODO: do this upfront
val := reflect.New(goType).Elem()
finish := func() error {
// fmt.Println(kval.Interface(), val.Interface())
w.val.FieldByName("Index").SetInt(int64(idx))
w.val.FieldByName("Value").Set(val)
return nil
}
return &_assembler{
schemaType: mtyp,
val: val,
finish: finish,
}
}
func (w *_unionAssembler) AssembleEntry(k string) (ipld.NodeAssembler, error) {
if err := w.AssembleKey().AssignString(k); err != nil {
return nil, err
}
am := w.AssembleValue()
return am, nil
}
func (w *_unionAssembler) Finish() error {
if w.finish != nil {
if err := w.finish(); err != nil {
return err
}
}
return nil
}
func (w *_unionAssembler) KeyPrototype() ipld.NodePrototype {
return &_prototype{schemaType: schemaTypeFieldName, goType: goTypeString}
}
func (w *_unionAssembler) ValuePrototype(k string) ipld.NodePrototype {
panic("TODO: struct ValuePrototype")
}
type _structIterator struct {
// TODO: support embedded fields?
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 ipld.Node, _ error) {
if w.Done() {
return nil, nil, ipld.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, ipld.Absent, nil
}
val = val.Elem()
}
if field.IsNullable() {
if val.IsNil() {
return key, ipld.Null, nil
}
val = val.Elem()
}
node := &_node{
schemaType: field.Type(),
val: val,
}
return key, node, nil
}
func (w *_structIterator) Done() bool {
return w.nextIndex >= len(w.fields)
}
type _listIterator struct {
schemaType *schema.TypeList
val reflect.Value // non-pointer
nextIndex int
}
func (w *_listIterator) Next() (index int64, value ipld.Node, _ error) {
if w.Done() {
return 0, nil, ipld.ErrIteratorOverread{}
}
idx := int64(w.nextIndex)
val := w.val.Index(w.nextIndex)
w.nextIndex++
return idx, &_node{schemaType: w.schemaType.ValueType(), val: val}, nil
}
func (w *_listIterator) Done() bool {
return w.nextIndex >= w.val.Len()
}
type _unionIterator struct {
// TODO: support embedded fields?
schemaType *schema.TypeUnion
members []schema.Type
val reflect.Value // non-pointer
done bool
}
func (w *_unionIterator) Next() (key, value ipld.Node, _ error) {
if w.Done() {
return nil, nil, ipld.ErrIteratorOverread{}
}
w.done = true
haveIdx := int(w.val.FieldByName("Index").Int())
mtyp := w.members[haveIdx]
mval := w.val.FieldByName("Value").Elem()
node := &_node{
schemaType: mtyp,
val: mval,
}
key = basicnode.NewString(mtyp.Name().String())
return key, node, nil
}
func (w *_unionIterator) Done() bool {
return w.done
}
// TODO: consider making our own Node interface, like:
//
// type WrappedNode interface {
// ipld.Node
// Unwrap() (ptr interface)
// }
//
// Pros: API is easier to understand, harder to mix up with other ipld.Nodes.
// Cons: One usually only has an ipld.Node, and type assertions can be weird.
package bindnode
import (
"fmt"
"reflect"
"strings"
ipld "github.com/ipld/go-ipld-prime"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
"github.com/ipld/go-ipld-prime/schema"
)
func reprNode(node ipld.Node) ipld.Node {
if node, ok := node.(schema.TypedNode); ok {
return node.Representation()
}
// ipld.Absent and ipld.Null are not typed.
// TODO: is this a problem? surely a typed struct's fields are always
// typed, even when absent or null.
return node
}
func reprStrategy(typ schema.Type) interface{} {
switch typ := typ.(type) {
case *schema.TypeStruct:
return typ.RepresentationStrategy()
case *schema.TypeUnion:
return typ.RepresentationStrategy()
}
return nil
}
type _prototypeRepr _prototype
func (w *_prototypeRepr) NewBuilder() ipld.NodeBuilder {
return &_builderRepr{_assemblerRepr{
schemaType: w.schemaType,
val: reflect.New(w.goType).Elem(),
}}
}
type _nodeRepr _node
func (w *_nodeRepr) Kind() ipld.Kind {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Stringjoin:
return ipld.Kind_String
case schema.StructRepresentation_Map:
return ipld.Kind_Map
case schema.StructRepresentation_Tuple:
return ipld.Kind_List
case schema.UnionRepresentation_Keyed:
return ipld.Kind_Map
case schema.UnionRepresentation_Kinded:
haveIdx := int(w.val.FieldByName("Index").Int())
mtyp := w.schemaType.(*schema.TypeUnion).Members()[haveIdx]
return mtyp.TypeKind().ActsLike()
case schema.UnionRepresentation_Stringprefix:
return ipld.Kind_String
case nil:
return (*_node)(w).Kind()
default:
panic(fmt.Sprintf("TODO Kind: %T", stg))
}
}
func outboundMappedKey(stg schema.StructRepresentation_Map, key string) string {
// TODO: why doesn't stg just allow us to "get" by the key string?
field := schema.SpawnStructField(key, "", false, false)
mappedKey := stg.GetFieldKey(field)
return mappedKey
}
func inboundMappedKey(typ *schema.TypeStruct, stg schema.StructRepresentation_Map, key string) string {
// TODO: can't do a "reverse" lookup... needs better API probably.
fields := typ.Fields()
for _, field := range fields {
mappedKey := stg.GetFieldKey(field)
if key == mappedKey {
// println(key, "rev-mapped to", field.Name())
return field.Name()
}
}
// println(key, "had no mapping")
return key // fallback to the same key
}
func outboundMappedType(stg schema.UnionRepresentation_Keyed, key string) string {
// TODO: why doesn't stg just allow us to "get" by the key string?
typ := schema.SpawnBool(schema.TypeName(key))
mappedKey := stg.GetDiscriminant(typ)
return mappedKey
}
func inboundMappedType(typ *schema.TypeUnion, stg schema.UnionRepresentation_Keyed, key string) string {
// TODO: can't do a "reverse" lookup... needs better API probably.
for _, member := range typ.Members() {
mappedKey := stg.GetDiscriminant(member)
if key == mappedKey {
// println(key, "rev-mapped to", field.Name())
return member.Name().String()
}
}
// println(key, "had no mapping")
return key // fallback to the same key
}
func (w *_nodeRepr) asKinded(stg schema.UnionRepresentation_Kinded, kind ipld.Kind) *_nodeRepr {
name := stg.GetMember(kind)
members := w.schemaType.(*schema.TypeUnion).Members()
for _, member := range members {
if member.Name() != name {
continue
}
w2 := *w
w2.val = w.val.FieldByName("Value").Elem()
w2.schemaType = member
return &w2
}
return nil
}
func (w *_nodeRepr) LookupByString(key string) (ipld.Node, error) {
if stg, ok := reprStrategy(w.schemaType).(schema.UnionRepresentation_Kinded); ok {
w = w.asKinded(stg, ipld.Kind_Map)
}
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Map:
revKey := inboundMappedKey(w.schemaType.(*schema.TypeStruct), stg, key)
v, err := (*_node)(w).LookupByString(revKey)
if err != nil {
return nil, err
}
return reprNode(v), nil
case schema.UnionRepresentation_Keyed:
revKey := inboundMappedType(w.schemaType.(*schema.TypeUnion), stg, key)
v, err := (*_node)(w).LookupByString(revKey)
if err != nil {
return nil, err
}
return reprNode(v), nil
case nil:
v, err := (*_node)(w).LookupByString(key)
if err != nil {
return nil, err
}
return reprNode(v), nil
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_nodeRepr) LookupByNode(key ipld.Node) (ipld.Node, error) {
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "LookupByNode", AppropriateKind: ipld.KindSet_JustList, ActualKind: ipld.Kind_Map,
}
}
func (w *_nodeRepr) LookupByIndex(idx int64) (ipld.Node, error) {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Tuple:
fields := w.schemaType.(*schema.TypeStruct).Fields()
field := fields[idx]
v, err := (*_node)(w).LookupByString(field.Name())
if err != nil {
return nil, err
}
return reprNode(v), nil
case nil:
v, err := (*_node)(w).LookupByIndex(idx)
if err != nil {
return nil, err
}
return reprNode(v), nil
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_nodeRepr) LookupBySegment(seg ipld.PathSegment) (ipld.Node, error) {
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "LookupBySegment",
// TODO
}
}
func (w *_nodeRepr) MapIterator() ipld.MapIterator {
if stg, ok := reprStrategy(w.schemaType).(schema.UnionRepresentation_Kinded); ok {
w = w.asKinded(stg, ipld.Kind_Map)
}
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Map:
itr := (*_node)(w).MapIterator().(*_structIterator)
itr.reprEnd = int(w.lengthMinusTrailingAbsents())
return (*_structIteratorRepr)(itr)
case schema.UnionRepresentation_Keyed:
itr := (*_node)(w).MapIterator().(*_unionIterator)
return (*_unionIteratorRepr)(itr)
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_nodeRepr) ListIterator() ipld.ListIterator {
return nil
}
func (w *_nodeRepr) lengthMinusTrailingAbsents() int64 {
fields := w.schemaType.(*schema.TypeStruct).Fields()
for i := len(fields) - 1; i >= 0; i-- {
field := fields[i]
if !field.IsOptional() || !w.val.Field(i).IsNil() {
return int64(i + 1)
}
}
return 0
}
func (w *_nodeRepr) Length() int64 {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Stringjoin:
return -1
case schema.StructRepresentation_Map:
return w.lengthMinusTrailingAbsents()
case schema.StructRepresentation_Tuple:
return w.lengthMinusTrailingAbsents()
case schema.UnionRepresentation_Keyed:
return (*_node)(w).Length()
case schema.UnionRepresentation_Kinded:
w = w.asKinded(stg, w.Kind())
// continues below
case nil:
// continues below
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
return (*_node)(w).Length()
}
func (w *_nodeRepr) IsAbsent() bool {
return false
}
func (w *_nodeRepr) IsNull() bool {
return false
}
func (w *_nodeRepr) AsBool() (bool, error) {
return false, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(),
MethodName: "AsBool", AppropriateKind: ipld.KindSet_JustBool, ActualKind: ipld.Kind_Map,
}
}
func (w *_nodeRepr) AsInt() (int64, error) {
return 0, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(), MethodName: "AsInt",
AppropriateKind: ipld.KindSet_JustInt, ActualKind: ipld.Kind_Map,
}
}
func (w *_nodeRepr) AsFloat() (float64, error) {
return 0, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(), MethodName: "AsFloat",
AppropriateKind: ipld.KindSet_JustFloat, ActualKind: ipld.Kind_Map,
}
}
func (w *_nodeRepr) AsString() (string, error) {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Stringjoin:
var b strings.Builder
itr := (*_node)(w).MapIterator()
for !itr.Done() {
_, v, err := itr.Next()
if err != nil {
return "", err
}
s, err := reprNode(v).AsString()
if err != nil {
return "", err
}
if b.Len() > 0 {
b.WriteString(stg.GetDelim())
}
b.WriteString(s)
}
return b.String(), nil
case schema.UnionRepresentation_Stringprefix:
haveIdx := int(w.val.FieldByName("Index").Int())
mtyp := w.schemaType.(*schema.TypeUnion).Members()[haveIdx]
w2 := *w
w2.val = w.val.FieldByName("Value").Elem()
w2.schemaType = mtyp
s, err := w2.AsString()
if err != nil {
return "", err
}
name := stg.GetDiscriminant(mtyp)
return name + stg.GetDelim() + s, nil
case schema.UnionRepresentation_Kinded:
w = w.asKinded(stg, ipld.Kind_String)
// continues below
case nil:
// continues below
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
return (*_node)(w).AsString()
}
func (w *_nodeRepr) AsBytes() ([]byte, error) {
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(), MethodName: "AsBytes",
AppropriateKind: ipld.KindSet_JustBytes, ActualKind: ipld.Kind_Map,
}
}
func (w *_nodeRepr) AsLink() (ipld.Link, error) {
return nil, ipld.ErrWrongKind{
TypeName: w.schemaType.Name().String(), MethodName: "AsLink",
AppropriateKind: ipld.KindSet_JustLink, ActualKind: ipld.Kind_Map,
}
}
func (w *_nodeRepr) Prototype() ipld.NodePrototype {
panic("TODO: Prototype")
}
type _builderRepr struct {
_assemblerRepr
}
// TODO: returning a repr node here is probably good, but there's a gotcha: one
// can go from a typed node to a repr node via the Representation method, but
// not the other way. That's probably why codegen returns a typed node here.
// The solution might be to add a way to go from the repr node to its parent
// typed node.
func (w *_builderRepr) Build() ipld.Node {
// TODO: see the notes above.
// return &_nodeRepr{schemaType: w.schemaType, val: w.val}
return &_node{schemaType: w.schemaType, val: w.val}
}
func (w *_builderRepr) Reset() {
panic("TODO: Reset")
}
type _assemblerRepr struct {
schemaType schema.Type
val reflect.Value // non-pointer
finish func() error
nullable bool
}
func (w *_assemblerRepr) asKinded(stg schema.UnionRepresentation_Kinded, kind ipld.Kind) *_assemblerRepr {
name := stg.GetMember(kind)
members := w.schemaType.(*schema.TypeUnion).Members()
for idx, member := range members {
if member.Name() != name {
continue
}
w2 := *w
goType := inferGoType(member) // TODO: do this upfront
w2.val = reflect.New(goType).Elem()
w2.schemaType = member
// Layer a new finish func on top, to set Index/Value.
w2.finish = func() error {
if w.finish != nil {
if err := w.finish(); err != nil {
return err
}
}
w.val.FieldByName("Index").SetInt(int64(idx))
w.val.FieldByName("Value").Set(w2.val)
return nil
}
return &w2
}
return nil
}
func (w *_assemblerRepr) BeginMap(sizeHint int64) (ipld.MapAssembler, error) {
if stg, ok := reprStrategy(w.schemaType).(schema.UnionRepresentation_Kinded); ok {
w = w.asKinded(stg, ipld.Kind_Map)
}
asm, err := (*_assembler)(w).BeginMap(sizeHint)
if err != nil {
return nil, err
}
switch asm := asm.(type) {
case *_structAssembler:
return (*_structAssemblerRepr)(asm), nil
case *_mapAssembler:
return (*_mapAssemblerRepr)(asm), nil
case *_unionAssembler:
return (*_unionAssemblerRepr)(asm), nil
default:
panic(fmt.Sprintf("%T", asm))
}
}
func (w *_assemblerRepr) BeginList(sizeHint int64) (ipld.ListAssembler, error) {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Tuple:
asm, err := (*_assembler)(w).BeginMap(sizeHint)
if err != nil {
return nil, err
}
return (*_listStructAssemblerRepr)(asm.(*_structAssembler)), nil
case nil:
asm, err := (*_assembler)(w).BeginList(sizeHint)
if err != nil {
return nil, err
}
return (*_listAssemblerRepr)(asm.(*_listAssembler)), nil
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_assemblerRepr) AssignNull() error {
switch stg := reprStrategy(w.schemaType).(type) {
case nil:
return (*_assembler)(w).AssignNull()
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_assemblerRepr) AssignBool(bool) error {
panic("TODO: AssignBool")
}
func (w *_assemblerRepr) AssignInt(i int64) error {
panic("TODO")
}
func (w *_assemblerRepr) AssignFloat(float64) error {
panic("TODO: AssignFloat")
}
func (w *_assemblerRepr) AssignString(s string) error {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Stringjoin:
fields := w.schemaType.(*schema.TypeStruct).Fields()
parts := strings.Split(s, stg.GetDelim())
if len(parts) != len(fields) {
panic("TODO: len mismatch")
}
mapAsm, err := (*_assembler)(w).BeginMap(-1)
if err != nil {
return err
}
for i, field := range fields {
entryAsm, err := mapAsm.AssembleEntry(field.Name())
if err != nil {
return err
}
entryAsm = entryAsm.(TypedAssembler).Representation()
if err := entryAsm.AssignString(parts[i]); err != nil {
return err
}
}
return mapAsm.Finish()
case schema.UnionRepresentation_Kinded:
name := stg.GetMember(ipld.Kind_String)
members := w.schemaType.(*schema.TypeUnion).Members()
for idx, member := range members {
if member.Name() != name {
continue
}
w.val.FieldByName("Index").SetInt(int64(idx))
w.val.FieldByName("Value").Set(reflect.ValueOf(s))
return nil
}
panic("TODO: GetMember result is missing?")
case schema.UnionRepresentation_Stringprefix:
parts := strings.SplitN(s, stg.GetDelim(), 2)
if len(parts) != 2 {
panic("TODO: bad format")
}
name, value := parts[0], parts[1]
members := w.schemaType.(*schema.TypeUnion).Members()
for idx, member := range members {
if stg.GetDiscriminant(member) != name {
continue
}
w2 := *w
goType := inferGoType(member) // TODO: do this upfront
w2.val = reflect.New(goType).Elem()
w2.schemaType = member
w2.finish = func() error {
if w.finish != nil {
if err := w.finish(); err != nil {
return err
}
}
w.val.FieldByName("Index").SetInt(int64(idx))
w.val.FieldByName("Value").Set(w2.val)
return nil
}
return w2.AssignString(value)
}
panic("TODO: GetMember result is missing?")
case nil:
return (*_assembler)(w).AssignString(s)
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_assemblerRepr) AssignBytes(p []byte) error {
panic("TODO")
}
func (w *_assemblerRepr) AssignLink(link ipld.Link) error {
panic("TODO")
}
func (w *_assemblerRepr) AssignNode(node ipld.Node) error {
panic("TODO")
}
func (w *_assemblerRepr) Prototype() ipld.NodePrototype {
panic("TODO: Assembler.Prototype")
}
type _structAssemblerRepr _structAssembler
func (w *_structAssemblerRepr) AssembleKey() ipld.NodeAssembler {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Map:
return (*_structAssembler)(w).AssembleKey()
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_structAssemblerRepr) AssembleValue() ipld.NodeAssembler {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Map:
key := w.curKey.val.String()
revKey := inboundMappedKey(w.schemaType, stg, key)
w.curKey.val.SetString(revKey)
valAsm := (*_structAssembler)(w).AssembleValue()
valAsm = valAsm.(TypedAssembler).Representation()
return valAsm
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_structAssemblerRepr) AssembleEntry(k string) (ipld.NodeAssembler, error) {
if err := w.AssembleKey().AssignString(k); err != nil {
return nil, err
}
am := w.AssembleValue()
return am, nil
}
func (w *_structAssemblerRepr) Finish() error {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Map:
err := (*_structAssembler)(w).Finish()
if err, ok := err.(ipld.ErrMissingRequiredField); ok {
for i, name := range err.Missing {
serial := outboundMappedKey(stg, name)
if serial != name {
err.Missing[i] += fmt.Sprintf(" (serial:%q)", serial)
}
}
}
return err
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_structAssemblerRepr) KeyPrototype() ipld.NodePrototype {
panic("TODO")
}
func (w *_structAssemblerRepr) ValuePrototype(k string) ipld.NodePrototype {
panic("TODO: struct ValuePrototype")
}
type _mapAssemblerRepr _mapAssembler
func (w *_mapAssemblerRepr) AssembleKey() ipld.NodeAssembler {
switch stg := reprStrategy(w.schemaType).(type) {
case nil:
asm := (*_mapAssembler)(w).AssembleKey()
return (*_assemblerRepr)(asm.(*_assembler))
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_mapAssemblerRepr) AssembleValue() ipld.NodeAssembler {
switch stg := reprStrategy(w.schemaType).(type) {
case nil:
asm := (*_mapAssembler)(w).AssembleValue()
return (*_assemblerRepr)(asm.(*_assembler))
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_mapAssemblerRepr) AssembleEntry(k string) (ipld.NodeAssembler, error) {
if err := w.AssembleKey().AssignString(k); err != nil {
return nil, err
}
am := w.AssembleValue()
return am, nil
}
func (w *_mapAssemblerRepr) Finish() error {
switch stg := reprStrategy(w.schemaType).(type) {
case nil:
return (*_mapAssembler)(w).Finish()
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_mapAssemblerRepr) KeyPrototype() ipld.NodePrototype {
panic("TODO")
}
func (w *_mapAssemblerRepr) ValuePrototype(k string) ipld.NodePrototype {
panic("TODO: struct ValuePrototype")
}
type _listStructAssemblerRepr _structAssembler
func (w *_listStructAssemblerRepr) AssembleValue() ipld.NodeAssembler {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Tuple:
fields := w.schemaType.Fields()
field := fields[w.nextIndex]
w.nextIndex++
entryAsm, err := (*_structAssembler)(w).AssembleEntry(field.Name())
if err != nil {
panic(err) // TODO: probably return an assembler that always errors?
}
entryAsm = entryAsm.(TypedAssembler).Representation()
return entryAsm
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_listStructAssemblerRepr) Finish() error {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Tuple:
return (*_structAssembler)(w).Finish()
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_listStructAssemblerRepr) ValuePrototype(idx int64) ipld.NodePrototype {
panic("TODO: list ValuePrototype")
}
// Note that lists do not have any representation strategy right now.
type _listAssemblerRepr _listAssembler
func (w *_listAssemblerRepr) AssembleValue() ipld.NodeAssembler {
asm := (*_listAssembler)(w).AssembleValue()
return (*_assemblerRepr)(asm.(*_assembler))
}
func (w *_listAssemblerRepr) Finish() error {
return (*_listAssembler)(w).Finish()
}
func (w *_listAssemblerRepr) ValuePrototype(idx int64) ipld.NodePrototype {
panic("TODO: list ValuePrototype")
}
type _unionAssemblerRepr _unionAssembler
func (w *_unionAssemblerRepr) AssembleKey() ipld.NodeAssembler {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.UnionRepresentation_Keyed:
return (*_unionAssembler)(w).AssembleKey()
case nil:
asm := (*_unionAssembler)(w).AssembleKey()
return (*_assemblerRepr)(asm.(*_assembler))
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_unionAssemblerRepr) AssembleValue() ipld.NodeAssembler {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.UnionRepresentation_Keyed:
key := w.curKey.val.String()
revKey := inboundMappedType(w.schemaType, stg, key)
w.curKey.val.SetString(revKey)
valAsm := (*_unionAssembler)(w).AssembleValue()
valAsm = valAsm.(TypedAssembler).Representation()
return valAsm
case nil:
asm := (*_unionAssembler)(w).AssembleValue()
return (*_assemblerRepr)(asm.(*_assembler))
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_unionAssemblerRepr) AssembleEntry(k string) (ipld.NodeAssembler, error) {
if err := w.AssembleKey().AssignString(k); err != nil {
return nil, err
}
am := w.AssembleValue()
return am, nil
}
func (w *_unionAssemblerRepr) Finish() error {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.UnionRepresentation_Keyed:
return (*_unionAssembler)(w).Finish()
case nil:
return (*_unionAssembler)(w).Finish()
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_unionAssemblerRepr) KeyPrototype() ipld.NodePrototype {
panic("TODO")
}
func (w *_unionAssemblerRepr) ValuePrototype(k string) ipld.NodePrototype {
panic("TODO: struct ValuePrototype")
}
type _structIteratorRepr _structIterator
func (w *_structIteratorRepr) Next() (key, value ipld.Node, _ error) {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Map:
_skipAbsent:
key, value, err := (*_structIterator)(w).Next()
if err != nil {
return nil, nil, err
}
if value.IsAbsent() {
goto _skipAbsent
}
keyStr, _ := key.AsString()
mappedKey := outboundMappedKey(stg, keyStr)
if mappedKey != keyStr {
key = basicnode.NewString(mappedKey)
}
return key, reprNode(value), nil
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_structIteratorRepr) Done() bool {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.StructRepresentation_Map:
// TODO: the fact that repr map iterators skip absents should be
// documented somewhere
return w.nextIndex >= w.reprEnd
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
type _unionIteratorRepr _unionIterator
func (w *_unionIteratorRepr) Next() (key, value ipld.Node, _ error) {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.UnionRepresentation_Keyed:
key, value, err := (*_unionIterator)(w).Next()
if err != nil {
return nil, nil, err
}
keyStr, _ := key.AsString()
mappedKey := outboundMappedType(stg, keyStr)
if mappedKey != keyStr {
key = basicnode.NewString(mappedKey)
}
return key, reprNode(value), nil
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
func (w *_unionIteratorRepr) Done() bool {
switch stg := reprStrategy(w.schemaType).(type) {
case schema.UnionRepresentation_Keyed:
return (*_unionIterator)(w).Done()
default:
panic(fmt.Sprintf("TODO: %T", stg))
}
}
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