diff --git a/node/bindnode/example_test.go b/node/bindnode/example_test.go new file mode 100644 index 0000000000000000000000000000000000000000..17db56a111b3b2312af5480ac86d3cb90ea48657 --- /dev/null +++ b/node/bindnode/example_test.go @@ -0,0 +1,47 @@ +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"]} +} diff --git a/node/bindnode/node.go b/node/bindnode/node.go new file mode 100644 index 0000000000000000000000000000000000000000..b25b0eb95f4e692500c6d2fa28f71ee2c1e49570 --- /dev/null +++ b/node/bindnode/node.go @@ -0,0 +1,1112 @@ +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. diff --git a/node/bindnode/repr.go b/node/bindnode/repr.go new file mode 100644 index 0000000000000000000000000000000000000000..b26aacc4321e2ecd9d4df62ff474fa3f6e12cfe0 --- /dev/null +++ b/node/bindnode/repr.go @@ -0,0 +1,815 @@ +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)) + } +}