diff --git a/node/bindnode/api.go b/node/bindnode/api.go index a816cfce..278dd507 100644 --- a/node/bindnode/api.go +++ b/node/bindnode/api.go @@ -85,26 +85,38 @@ type converter struct { customToAny func(interface{}) (datamodel.Node, error) } -type config map[reflect.Type]*converter +type config struct { + namedConverters map[schema.TypeName]*converter + typeConverters map[reflect.Type]*converter +} // this mainly exists to short-circuit the nonPtrType() call; the `Type()` variant // exists for completeness -func (c config) converterFor(val reflect.Value) *converter { - if len(c) == 0 { +func (c *config) converterFor(typeName schema.TypeName, val reflect.Value) *converter { + if c == nil { return nil } - return c[nonPtrType(val)] + + if namedConverter, ok := c.namedConverters[typeName]; ok { + return namedConverter + } + + return c.typeConverters[nonPtrType(val)] } -func (c config) converterForType(typ reflect.Type) *converter { - if len(c) == 0 { +func (c *config) converterForType(typeName schema.TypeName, typ reflect.Type) *converter { + if c == nil { return nil } - return c[typ] + if namedConverter, ok := c.namedConverters[typeName]; ok { + return namedConverter + } + + return c.typeConverters[typ] } // Option is able to apply custom options to the bindnode API -type Option func(config) +type Option func(*config) // TypedBoolConverter adds custom converter functions for a particular // type as identified by a pointer in the first argument. @@ -121,8 +133,8 @@ func TypedBoolConverter(ptrVal interface{}, from func(bool) (interface{}, error) customFromBool: from, customToBool: to, } - return func(cfg config) { - cfg[customType] = converter + return func(cfg *config) { + cfg.typeConverters[customType] = converter } } @@ -141,8 +153,8 @@ func TypedIntConverter(ptrVal interface{}, from func(int64) (interface{}, error) customFromInt: from, customToInt: to, } - return func(cfg config) { - cfg[customType] = converter + return func(cfg *config) { + cfg.typeConverters[customType] = converter } } @@ -161,8 +173,8 @@ func TypedFloatConverter(ptrVal interface{}, from func(float64) (interface{}, er customFromFloat: from, customToFloat: to, } - return func(cfg config) { - cfg[customType] = converter + return func(cfg *config) { + cfg.typeConverters[customType] = converter } } @@ -181,8 +193,8 @@ func TypedStringConverter(ptrVal interface{}, from func(string) (interface{}, er customFromString: from, customToString: to, } - return func(cfg config) { - cfg[customType] = converter + return func(cfg *config) { + cfg.typeConverters[customType] = converter } } @@ -201,8 +213,8 @@ func TypedBytesConverter(ptrVal interface{}, from func([]byte) (interface{}, err customFromBytes: from, customToBytes: to, } - return func(cfg config) { - cfg[customType] = converter + return func(cfg *config) { + cfg.typeConverters[customType] = converter } } @@ -225,8 +237,8 @@ func TypedLinkConverter(ptrVal interface{}, from func(cid.Cid) (interface{}, err customFromLink: from, customToLink: to, } - return func(cfg config) { - cfg[customType] = converter + return func(cfg *config) { + cfg.typeConverters[customType] = converter } } @@ -248,18 +260,162 @@ func TypedAnyConverter(ptrVal interface{}, from func(datamodel.Node) (interface{ customFromAny: from, customToAny: to, } - return func(cfg config) { - cfg[customType] = converter + return func(cfg *config) { + cfg.typeConverters[customType] = converter + } +} + +// NamedBoolConverter adds custom converter functions for given +// named schema type. +// The fromFunc is of the form: func(bool) (interface{}, error) +// and toFunc is of the form: func(interface{}) (bool, error) +// where interface{} is a pointer form of the type we are converting. +// +// NamedBoolConverter is an EXPERIMENTAL API and may be removed or +// changed in a future release. +func NamedBoolConverter(typeName schema.TypeName, from func(bool) (interface{}, error), to func(interface{}) (bool, error)) Option { + converter := &converter{ + kind: schema.TypeKind_Bool, + customFromBool: from, + customToBool: to, + } + return func(cfg *config) { + cfg.namedConverters[typeName] = converter + } +} + +// NamedIntConverter adds custom converter functions for given +// named schema type. +// The fromFunc is of the form: func(int64) (interface{}, error) +// and toFunc is of the form: func(interface{}) (int64, error) +// where interface{} is a pointer form of the type we are converting. +// +// NamedIntConverter is an EXPERIMENTAL API and may be removed or +// changed in a future release. +func NamedIntConverter(typeName schema.TypeName, from func(int64) (interface{}, error), to func(interface{}) (int64, error)) Option { + converter := &converter{ + kind: schema.TypeKind_Int, + customFromInt: from, + customToInt: to, + } + return func(cfg *config) { + cfg.namedConverters[typeName] = converter + } +} + +// NamedFloatConverter adds custom converter functions for given +// named schema type. +// The fromFunc is of the form: func(float64) (interface{}, error) +// and toFunc is of the form: func(interface{}) (float64, error) +// where interface{} is a pointer form of the type we are converting. +// +// NamedFloatConverter is an EXPERIMENTAL API and may be removed or +// changed in a future release. +func NamedFloatConverter(typeName schema.TypeName, from func(float64) (interface{}, error), to func(interface{}) (float64, error)) Option { + converter := &converter{ + kind: schema.TypeKind_Float, + customFromFloat: from, + customToFloat: to, + } + return func(cfg *config) { + cfg.namedConverters[typeName] = converter + } +} + +// NamedStringConverter adds custom converter functions for given +// named schema type. +// The fromFunc is of the form: func(string) (interface{}, error) +// and toFunc is of the form: func(interface{}) (string, error) +// where interface{} is a pointer form of the type we are converting. +// +// NamedStringConverter is an EXPERIMENTAL API and may be removed or +// changed in a future release. +func NamedStringConverter(typeName schema.TypeName, from func(string) (interface{}, error), to func(interface{}) (string, error)) Option { + converter := &converter{ + kind: schema.TypeKind_String, + customFromString: from, + customToString: to, + } + return func(cfg *config) { + cfg.namedConverters[typeName] = converter + } +} + +// NamedBytesConverter adds custom converter functions for given +// named schema type. +// The fromFunc is of the form: func([]byte) (interface{}, error) +// and toFunc is of the form: func(interface{}) ([]byte, error) +// where interface{} is a pointer form of the type we are converting. +// +// NamedBytesConverter is an EXPERIMENTAL API and may be removed or +// changed in a future release. +func NamedBytesConverter(typeName schema.TypeName, from func([]byte) (interface{}, error), to func(interface{}) ([]byte, error)) Option { + converter := &converter{ + kind: schema.TypeKind_Bytes, + customFromBytes: from, + customToBytes: to, + } + return func(cfg *config) { + cfg.namedConverters[typeName] = converter + } +} + +// NamedLinkConverter adds custom converter functions for given +// named schema type. +// The fromFunc is of the form: func([]byte) (interface{}, error) +// and toFunc is of the form: func(interface{}) ([]byte, error) +// where interface{} is a pointer form of the type we are converting. +// +// Beware that this API is only compatible with cidlink.Link types in the data +// model and may result in errors if attempting to convert from other +// datamodel.Link types. +// +// NamedLinkConverter is an EXPERIMENTAL API and may be removed or +// changed in a future release. +func NamedLinkConverter(typeName schema.TypeName, from func(cid.Cid) (interface{}, error), to func(interface{}) (cid.Cid, error)) Option { + converter := &converter{ + kind: schema.TypeKind_Link, + customFromLink: from, + customToLink: to, + } + return func(cfg *config) { + cfg.namedConverters[typeName] = converter + } +} + +// NamedAnyConverter adds custom converter functions for given +// named schema type. +// The fromFunc is of the form: func(datamodel.Node) (interface{}, error) +// and toFunc is of the form: func(interface{}) (datamodel.Node, error) +// where interface{} is a pointer form of the type we are converting. +// +// This method should be able to deal with all forms of Any and return an error +// if the expected data forms don't match the expected. +// +// NamedAnyConverter is an EXPERIMENTAL API and may be removed or +// changed in a future release. +func NamedAnyConverter(typeName schema.TypeName, from func(datamodel.Node) (interface{}, error), to func(interface{}) (datamodel.Node, error)) Option { + converter := &converter{ + kind: schema.TypeKind_Any, + customFromAny: from, + customToAny: to, + } + return func(cfg *config) { + cfg.namedConverters[typeName] = converter } } -func applyOptions(opt ...Option) config { +func applyOptions(opt ...Option) *config { if len(opt) == 0 { // no need to allocate, we access it via converterFor and converterForType // which are safe for nil maps return nil } - cfg := make(map[reflect.Type]*converter) + cfg := &config{ + namedConverters: make(map[string]*converter), + typeConverters: make(map[reflect.Type]*converter), + } + for _, o := range opt { o(cfg) } diff --git a/node/bindnode/custom_test.go b/node/bindnode/custom_test.go index adfc84b1..88105478 100644 --- a/node/bindnode/custom_test.go +++ b/node/bindnode/custom_test.go @@ -3,6 +3,7 @@ package bindnode_test import ( "bytes" "encoding/hex" + "errors" "fmt" "math/big" "strings" @@ -25,6 +26,8 @@ import ( type BoolSubst int +var errorDefault = errors.New("something went wrong") + const ( BoolSubst_Yes = 100 BoolSubst_No = -100 @@ -37,6 +40,10 @@ func BoolSubstFromBool(b bool) (interface{}, error) { return BoolSubst_No, nil } +func BoolSubstFromBoolError(b bool) (interface{}, error) { + return BoolSubst_No, errorDefault +} + func BoolToBoolSubst(b interface{}) (bool, error) { bp, ok := b.(*BoolSubst) if !ok { @@ -51,6 +58,9 @@ func BoolToBoolSubst(b interface{}) (bool, error) { return true, fmt.Errorf("bad BoolSubst") } } +func BoolToBoolSubstError(b interface{}) (bool, error) { + return false, errorDefault +} type IntSubst string @@ -63,6 +73,10 @@ func IntSubstFromInt(i int64) (interface{}, error) { return nil, fmt.Errorf("unexpected value of IntSubst") } +func IntSubstFromIntError(i int64) (interface{}, error) { + return nil, errorDefault +} + func IntToIntSubst(i interface{}) (int64, error) { ip, ok := i.(*IntSubst) if !ok { @@ -78,6 +92,10 @@ func IntToIntSubst(i interface{}) (int64, error) { } } +func IntToIntSubstError(i interface{}) (int64, error) { + return 0, errorDefault +} + type BigFloat struct{ *big.Float } func BigFloatFromFloat(f float64) (interface{}, error) { @@ -85,6 +103,10 @@ func BigFloatFromFloat(f float64) (interface{}, error) { return &BigFloat{bf}, nil } +func BigFloatFromFloatError(f float64) (interface{}, error) { + return nil, errorDefault +} + func FloatFromBigFloat(f interface{}) (float64, error) { fp, ok := f.(*BigFloat) if !ok { @@ -94,6 +116,10 @@ func FloatFromBigFloat(f interface{}) (float64, error) { return f64, nil } +func FloatFromBigFloatError(f interface{}) (float64, error) { + return 0, errorDefault +} + type ByteArray [][]byte func ByteArrayFromString(s string) (interface{}, error) { @@ -105,6 +131,10 @@ func ByteArrayFromString(s string) (interface{}, error) { return ba, nil } +func ByteArrayFromStringError(s string) (interface{}, error) { + return nil, errorDefault +} + func StringFromByteArray(b interface{}) (string, error) { bap, ok := b.(*ByteArray) if !ok { @@ -120,6 +150,10 @@ func StringFromByteArray(b interface{}) (string, error) { return sb.String(), nil } +func StringFromByteArrayError(b interface{}) (string, error) { + return "", errorDefault +} + // similar to cid/Cid, go-address/Address, go-graphsync/RequestID type Boop struct{ str string } @@ -177,6 +211,10 @@ func BoopFromBytes(b []byte) (interface{}, error) { return NewBoop(b), nil } +func BoopFromBytesError(b []byte) (interface{}, error) { + return nil, errorDefault +} + func BoopToBytes(iface interface{}) ([]byte, error) { if boop, ok := iface.(*Boop); ok { return boop.Bytes(), nil @@ -184,10 +222,18 @@ func BoopToBytes(iface interface{}) ([]byte, error) { return nil, fmt.Errorf("did not get expected type") } +func BoopToBytesError(iface interface{}) ([]byte, error) { + return nil, errorDefault +} + func FropFromBytes(b []byte) (interface{}, error) { return NewFropFromBytes(b), nil } +func FropFromBytesError(b []byte) (interface{}, error) { + return nil, errorDefault +} + func FropToBytes(iface interface{}) ([]byte, error) { if frop, ok := iface.(*Frop); ok { return frop.Bytes(), nil @@ -195,6 +241,10 @@ func FropToBytes(iface interface{}) ([]byte, error) { return nil, fmt.Errorf("did not get expected type") } +func FropToBytesError(iface interface{}) ([]byte, error) { + return nil, errorDefault +} + // Bitcoin's version of "links" is a hex form of the dbl-sha2-256 digest reversed type BtcId string @@ -214,6 +264,10 @@ func FromCidToBtcId(c cid.Cid) (interface{}, error) { return BtcId(hex.EncodeToString(hid)), nil } +func FromCidToBtcIdError(c cid.Cid) (interface{}, error) { + return BtcId(""), errorDefault +} + func FromBtcIdToCid(iface interface{}) (cid.Cid, error) { bid, ok := iface.(*BtcId) if !ok { @@ -234,6 +288,10 @@ func FromBtcIdToCid(iface interface{}) (cid.Cid, error) { return cid.NewCidV1(cid.BitcoinBlock, mh), nil } +func FromBtcIdToCidError(iface interface{}) (cid.Cid, error) { + return cid.Undef, errorDefault +} + type Boom struct { S string St ByteArray @@ -248,17 +306,25 @@ type Boom struct { } const boomSchema = ` +type ByteArray string +type Boop bytes +type BoolSubst bool +type Frop bytes +type BigFloat float +type IntSubst int +type BtcId &Any + type Boom struct { S String - St String - B Bytes - Bo Bool - Bptr nullable Bytes - F Bytes - Fl Float - I Int - In Int - L &Any + St ByteArray + B Boop + Bo BoolSubst + Bptr nullable Boop + F Frop + Fl BigFloat + I Int + In IntSubst + L BtcId } representation map ` @@ -316,6 +382,53 @@ func TestCustom(t *testing.T) { qt.Assert(t, buf.String(), qt.Equals, boomFixtureDagJson) } +func TestCustomNamed(t *testing.T) { + opts := []bindnode.Option{ + bindnode.NamedBytesConverter("Boop", BoopFromBytes, BoopToBytes), + bindnode.NamedBytesConverter("Frop", FropFromBytes, FropToBytes), + bindnode.NamedBoolConverter("BoolSubst", BoolSubstFromBool, BoolToBoolSubst), + bindnode.NamedIntConverter("IntSubst", IntSubstFromInt, IntToIntSubst), + bindnode.NamedFloatConverter("BigFloat", BigFloatFromFloat, FloatFromBigFloat), + bindnode.NamedStringConverter("ByteArray", ByteArrayFromString, StringFromByteArray), + bindnode.NamedLinkConverter("BtcId", FromCidToBtcId, FromBtcIdToCid), + // these will error, but shouldn't get called cause the named converters take precedence + bindnode.TypedBytesConverter(&Boop{}, BoopFromBytesError, BoopToBytesError), + bindnode.TypedBytesConverter(&Frop{}, FropFromBytesError, FropToBytesError), + bindnode.TypedBoolConverter(BoolSubst(0), BoolSubstFromBoolError, BoolToBoolSubstError), + bindnode.TypedIntConverter(IntSubst(""), IntSubstFromIntError, IntToIntSubstError), + bindnode.TypedFloatConverter(&BigFloat{}, BigFloatFromFloatError, FloatFromBigFloatError), + bindnode.TypedStringConverter(&ByteArray{}, ByteArrayFromStringError, StringFromByteArrayError), + bindnode.TypedLinkConverter(BtcId(""), FromCidToBtcIdError, FromBtcIdToCidError), + } + + typeSystem, err := ipld.LoadSchemaBytes([]byte(boomSchema)) + qt.Assert(t, err, qt.IsNil) + schemaType := typeSystem.TypeByName("Boom") + proto := bindnode.Prototype(&Boom{}, schemaType, opts...) + + builder := proto.Representation().NewBuilder() + err = dagjson.Decode(builder, bytes.NewReader([]byte(boomFixtureDagJson))) + qt.Assert(t, err, qt.IsNil) + + typ := bindnode.Unwrap(builder.Build()) + inst, ok := typ.(*Boom) + qt.Assert(t, ok, qt.IsTrue) + + cmpr := qt.CmpEquals( + cmp.Comparer(func(x, y Boop) bool { return x.String() == y.String() }), + cmp.Comparer(func(x, y Frop) bool { return x.String() == y.String() }), + cmp.Comparer(func(x, y BigFloat) bool { return x.String() == y.String() }), + ) + qt.Assert(t, *inst, cmpr, boomFixtureInstance) + + tn := bindnode.Wrap(inst, schemaType, opts...) + var buf bytes.Buffer + err = dagjson.Encode(tn.Representation(), &buf) + qt.Assert(t, err, qt.IsNil) + + qt.Assert(t, buf.String(), qt.Equals, boomFixtureDagJson) +} + type AnyExtend struct { Name string Blob AnyExtendBlob @@ -533,3 +646,113 @@ func mustFromHex(hexStr string) []byte { } return byt } + +type ClosedUnion interface { + isClosedUnion() +} + +type intUnion uint64 + +func (intUnion) isClosedUnion() {} + +type stringUnion string + +func (stringUnion) isClosedUnion() {} + +func ClosedUnionFromNode(node datamodel.Node) (interface{}, error) { + asInt, err := node.AsInt() + if err == nil { + return intUnion(asInt), nil + } + asString, err := node.AsString() + if err == nil { + return stringUnion(asString), nil + } + return nil, errors.New("unrecognized type") +} + +func ClosedUnionToNode(val interface{}) (datamodel.Node, error) { + cu, ok := val.(*ClosedUnion) + if !ok { + return nil, errors.New("should be a ClosedUnion") + } + switch concrete := (*cu).(type) { + case intUnion: + return basicnode.NewInt(int64(concrete)), nil + case stringUnion: + return basicnode.NewString(string(concrete)), nil + default: + return nil, errors.New("unexpected union type") + } +} + +type StructWithUnion struct { + Cu ClosedUnion +} + +const closedUnionSchema = ` +type ClosedUnion any + +type StructWithUnion struct { + cu ClosedUnion +} +` + +const closedUnionFixtureIntDagJson = `{"cu":8}` +const closedUnionFixtureStringDagJson = `{"cu":"happy"}` + +var closedUnionIntInst = StructWithUnion{ + Cu: intUnion(8), +} +var closedUnionStringInst = StructWithUnion{ + Cu: stringUnion("happy"), +} + +func TestCustomAnyWithInterface(t *testing.T) { + opts := []bindnode.Option{ + bindnode.NamedAnyConverter("ClosedUnion", ClosedUnionFromNode, ClosedUnionToNode), + } + + typeSystem, err := ipld.LoadSchemaBytes([]byte(closedUnionSchema)) + qt.Assert(t, err, qt.IsNil) + schemaType := typeSystem.TypeByName("StructWithUnion") + proto := bindnode.Prototype(&StructWithUnion{}, schemaType, opts...) + + // test one union variant + builder := proto.Representation().NewBuilder() + err = dagjson.Decode(builder, bytes.NewReader([]byte(closedUnionFixtureIntDagJson))) + qt.Assert(t, err, qt.IsNil) + + typ := bindnode.Unwrap(builder.Build()) + inst, ok := typ.(*StructWithUnion) + qt.Assert(t, ok, qt.IsTrue) + + cmpr := qt.CmpEquals() + qt.Assert(t, *inst, cmpr, closedUnionIntInst) + + tn := bindnode.Wrap(inst, schemaType, opts...) + var buf bytes.Buffer + err = dagjson.Encode(tn.Representation(), &buf) + qt.Assert(t, err, qt.IsNil) + + qt.Assert(t, buf.String(), qt.Equals, closedUnionFixtureIntDagJson) + + // test other union variant + builder = proto.Representation().NewBuilder() + err = dagjson.Decode(builder, bytes.NewReader([]byte(closedUnionFixtureStringDagJson))) + qt.Assert(t, err, qt.IsNil) + + typ = bindnode.Unwrap(builder.Build()) + inst, ok = typ.(*StructWithUnion) + qt.Assert(t, ok, qt.IsTrue) + + cmpr = qt.CmpEquals() + qt.Assert(t, *inst, cmpr, closedUnionStringInst) + + tn = bindnode.Wrap(inst, schemaType, opts...) + buf = bytes.Buffer{} + err = dagjson.Encode(tn.Representation(), &buf) + qt.Assert(t, err, qt.IsNil) + + qt.Assert(t, buf.String(), qt.Equals, closedUnionFixtureStringDagJson) +} diff --git a/node/bindnode/infer.go b/node/bindnode/infer.go index 6b1c4849..9982397c 100644 --- a/node/bindnode/infer.go +++ b/node/bindnode/infer.go @@ -44,7 +44,7 @@ type seenEntry struct { // verifyCompatibility doesn't return an error, it panics—the errors here are // not runtime errors, they're programmer errors because your schema doesn't // match your Go type -func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Type, schemaType schema.Type) { +func verifyCompatibility(cfg *config, seen map[seenEntry]bool, goType reflect.Type, schemaType schema.Type) { // TODO(mvdan): support **T as well? if goType.Kind() == reflect.Ptr { goType = goType.Elem() @@ -71,7 +71,7 @@ func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Typ } switch schemaType := schemaType.(type) { case *schema.TypeBool: - if customConverter := cfg.converterForType(goType); customConverter != nil { + if customConverter := cfg.converterForType(schemaType.Name(), goType); customConverter != nil { if customConverter.kind != schema.TypeKind_Bool { doPanic("kind mismatch; custom converter for type is not for Bool") } @@ -79,7 +79,7 @@ func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Typ doPanic("kind mismatch; need boolean") } case *schema.TypeInt: - if customConverter := cfg.converterForType(goType); customConverter != nil { + if customConverter := cfg.converterForType(schemaType.Name(), goType); customConverter != nil { if customConverter.kind != schema.TypeKind_Int { doPanic("kind mismatch; custom converter for type is not for Int") } @@ -87,7 +87,7 @@ func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Typ doPanic("kind mismatch; need integer") } case *schema.TypeFloat: - if customConverter := cfg.converterForType(goType); customConverter != nil { + if customConverter := cfg.converterForType(schemaType.Name(), goType); customConverter != nil { if customConverter.kind != schema.TypeKind_Float { doPanic("kind mismatch; custom converter for type is not for Float") } @@ -100,7 +100,7 @@ func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Typ } case *schema.TypeString: // TODO: allow []byte? - if customConverter := cfg.converterForType(goType); customConverter != nil { + if customConverter := cfg.converterForType(schemaType.Name(), goType); customConverter != nil { if customConverter.kind != schema.TypeKind_String { doPanic("kind mismatch; custom converter for type is not for String") } @@ -109,7 +109,7 @@ func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Typ } case *schema.TypeBytes: // TODO: allow string? - if customConverter := cfg.converterForType(goType); customConverter != nil { + if customConverter := cfg.converterForType(schemaType.Name(), goType); customConverter != nil { if customConverter.kind != schema.TypeKind_Bytes { doPanic("kind mismatch; custom converter for type is not for Bytes") } @@ -209,7 +209,7 @@ func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Typ } case schemaField.IsNullable(): if ptr, nilable := ptrOrNilable(goType.Kind()); !nilable { - if customConverter := cfg.converterForType(goType); customConverter == nil { + if customConverter := cfg.converterForType(schemaType.Name(), goType); customConverter == nil { doPanic("nullable fields must be nilable") } } else if ptr { @@ -238,7 +238,7 @@ func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Typ verifyCompatibility(cfg, seen, goType, schemaType) } case *schema.TypeLink: - if customConverter := cfg.converterForType(goType); customConverter != nil { + if customConverter := cfg.converterForType(schemaType.Name(), goType); customConverter != nil { if customConverter.kind != schema.TypeKind_Link { doPanic("kind mismatch; custom converter for type is not for Link") } @@ -246,7 +246,7 @@ func verifyCompatibility(cfg config, seen map[seenEntry]bool, goType reflect.Typ doPanic("links in Go must be datamodel.Link, cidlink.Link, or cid.Cid") } case *schema.TypeAny: - if customConverter := cfg.converterForType(goType); customConverter != nil { + if customConverter := cfg.converterForType(schemaType.Name(), goType); customConverter != nil { if customConverter.kind != schema.TypeKind_Any { doPanic("kind mismatch; custom converter for type is not for Any") } diff --git a/node/bindnode/node.go b/node/bindnode/node.go index 1c0c3280..20a86550 100644 --- a/node/bindnode/node.go +++ b/node/bindnode/node.go @@ -59,7 +59,7 @@ var ( ) type _prototype struct { - cfg config + cfg *config schemaType schema.Type goType reflect.Type // non-pointer } @@ -81,7 +81,7 @@ func (w *_prototype) Representation() datamodel.NodePrototype { } type _node struct { - cfg config + cfg *config schemaType schema.Type val reflect.Value // non-pointer @@ -92,7 +92,7 @@ type _node struct { // _node // } -func newNode(cfg config, schemaType schema.Type, val reflect.Value) schema.TypedNode { +func newNode(cfg *config, schemaType schema.Type, val reflect.Value) schema.TypedNode { if schemaType.TypeKind() == schema.TypeKind_Int && nonPtrVal(val).Kind() == reflect.Uint64 { // special case for uint64 values so we can handle the >int64 range // we give this treatment to all uint64s, regardless of current value @@ -220,7 +220,7 @@ func (w *_node) LookupByString(key string) (datamodel.Node, error) { } } if _, ok := field.Type().(*schema.TypeAny); ok { - if customConverter := w.cfg.converterFor(fval); customConverter != nil { + if customConverter := w.cfg.converterFor(field.Type().Name(), fval); customConverter != nil { // field is an Any and we have a custom type converter for the type return customConverter.customToAny(ptrVal(fval).Interface()) } @@ -265,7 +265,7 @@ func (w *_node) LookupByString(key string) (datamodel.Node, error) { fval = fval.Elem() } if _, ok := typ.ValueType().(*schema.TypeAny); ok { - if customConverter := w.cfg.converterFor(fval); customConverter != nil { + if customConverter := w.cfg.converterFor(typ.ValueType().Name(), fval); customConverter != nil { // value is an Any and we have a custom type converter for the type return customConverter.customToAny(ptrVal(fval).Interface()) } @@ -340,7 +340,7 @@ func (w *_node) LookupByIndex(idx int64) (datamodel.Node, error) { val = val.Index(int(idx)) _, isAny := typ.ValueType().(*schema.TypeAny) if isAny { - if customConverter := w.cfg.converterFor(val); customConverter != nil { + if customConverter := w.cfg.converterFor(typ.ValueType().Name(), val); customConverter != nil { // values are Any and we have a converter for this type that will give us // a datamodel.Node return customConverter.customToAny(ptrVal(val).Interface()) @@ -486,7 +486,7 @@ func (w *_node) AsBool() (bool, error) { if err := compatibleKind(w.schemaType, datamodel.Kind_Bool); err != nil { return false, err } - if customConverter := w.cfg.converterFor(w.val); customConverter != nil { + if customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val); customConverter != nil { // user has registered a converter that takes the underlying type and returns a bool return customConverter.customToBool(ptrVal(w.val).Interface()) } @@ -497,7 +497,7 @@ func (w *_node) AsInt() (int64, error) { if err := compatibleKind(w.schemaType, datamodel.Kind_Int); err != nil { return 0, err } - if customConverter := w.cfg.converterFor(w.val); customConverter != nil { + if customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val); customConverter != nil { // user has registered a converter that takes the underlying type and returns an int return customConverter.customToInt(ptrVal(w.val).Interface()) } @@ -516,7 +516,7 @@ func (w *_node) AsFloat() (float64, error) { if err := compatibleKind(w.schemaType, datamodel.Kind_Float); err != nil { return 0, err } - if customConverter := w.cfg.converterFor(w.val); customConverter != nil { + if customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val); customConverter != nil { // user has registered a converter that takes the underlying type and returns a float return customConverter.customToFloat(ptrVal(w.val).Interface()) } @@ -527,7 +527,7 @@ func (w *_node) AsString() (string, error) { if err := compatibleKind(w.schemaType, datamodel.Kind_String); err != nil { return "", err } - if customConverter := w.cfg.converterFor(w.val); customConverter != nil { + if customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val); customConverter != nil { // user has registered a converter that takes the underlying type and returns a string return customConverter.customToString(ptrVal(w.val).Interface()) } @@ -538,7 +538,7 @@ func (w *_node) AsBytes() ([]byte, error) { if err := compatibleKind(w.schemaType, datamodel.Kind_Bytes); err != nil { return nil, err } - if customConverter := w.cfg.converterFor(w.val); customConverter != nil { + if customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val); customConverter != nil { // user has registered a converter that takes the underlying type and returns a []byte return customConverter.customToBytes(ptrVal(w.val).Interface()) } @@ -549,7 +549,7 @@ func (w *_node) AsLink() (datamodel.Link, error) { if err := compatibleKind(w.schemaType, datamodel.Kind_Link); err != nil { return nil, err } - if customConverter := w.cfg.converterFor(w.val); customConverter != nil { + if customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val); customConverter != nil { // user has registered a converter that takes the underlying type and returns a cid.Cid cid, err := customConverter.customToLink(ptrVal(w.val).Interface()) if err != nil { @@ -585,7 +585,7 @@ func (w *_builder) Reset() { } type _assembler struct { - cfg config + cfg *config schemaType schema.Type val reflect.Value // non-pointer @@ -664,7 +664,7 @@ func (w *_assembler) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) { if err != nil { return nil, err } - converter := w.cfg.converterFor(w.val) + converter := w.cfg.converterFor(w.schemaType.Name(), w.val) return &basicMapAssembler{MapAssembler: mapAsm, builder: basicBuilder, parent: w, converter: converter}, nil case *schema.TypeStruct: val := w.createNonPtrVal() @@ -758,7 +758,7 @@ func (w *_assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) if err != nil { return nil, err } - converter := w.cfg.converterFor(w.val) + converter := w.cfg.converterFor(w.schemaType.Name(), w.val) return &basicListAssembler{ListAssembler: listAsm, builder: basicBuilder, parent: w, converter: converter}, nil case *schema.TypeList: // we should be able to safely assume we're dealing with a Go slice here, @@ -781,7 +781,7 @@ func (w *_assembler) BeginList(sizeHint int64) (datamodel.ListAssembler, error) func (w *_assembler) AssignNull() error { _, isAny := w.schemaType.(*schema.TypeAny) - if customConverter := w.cfg.converterFor(w.val); customConverter != nil && isAny { + if customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val); customConverter != nil && isAny { // an Any field that is being assigned a Null, we pass the Null directly to // the converter, regardless of whether this field is nullable or not typ, err := customConverter.customFromAny(datamodel.Null) @@ -812,7 +812,7 @@ func (w *_assembler) AssignBool(b bool) error { if err := compatibleKind(w.schemaType, datamodel.Kind_Bool); err != nil { return err } - customConverter := w.cfg.converterFor(w.val) + customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val) _, isAny := w.schemaType.(*schema.TypeAny) if customConverter != nil { var typ interface{} @@ -881,7 +881,7 @@ func (w *_assembler) AssignInt(i int64) error { return err } // TODO: check for overflow - customConverter := w.cfg.converterFor(w.val) + customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val) _, isAny := w.schemaType.(*schema.TypeAny) if customConverter != nil { var typ interface{} @@ -926,7 +926,7 @@ func (w *_assembler) AssignFloat(f float64) error { if err := compatibleKind(w.schemaType, datamodel.Kind_Float); err != nil { return err } - customConverter := w.cfg.converterFor(w.val) + customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val) _, isAny := w.schemaType.(*schema.TypeAny) if customConverter != nil { var typ interface{} @@ -965,7 +965,7 @@ func (w *_assembler) AssignString(s string) error { if err := compatibleKind(w.schemaType, datamodel.Kind_String); err != nil { return err } - customConverter := w.cfg.converterFor(w.val) + customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val) _, isAny := w.schemaType.(*schema.TypeAny) if customConverter != nil { var typ interface{} @@ -1004,7 +1004,7 @@ func (w *_assembler) AssignBytes(p []byte) error { if err := compatibleKind(w.schemaType, datamodel.Kind_Bytes); err != nil { return err } - customConverter := w.cfg.converterFor(w.val) + customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val) _, isAny := w.schemaType.(*schema.TypeAny) if customConverter != nil { var typ interface{} @@ -1042,7 +1042,7 @@ func (w *_assembler) AssignBytes(p []byte) error { func (w *_assembler) AssignLink(link datamodel.Link) error { val := w.createNonPtrVal() // TODO: newVal.Type() panics if link==nil; add a test and fix. - customConverter := w.cfg.converterFor(w.val) + customConverter := w.cfg.converterFor(w.schemaType.Name(), w.val) if _, ok := w.schemaType.(*schema.TypeAny); ok { if customConverter != nil { // field is an Any, so the converter will be an Any converter that wants @@ -1118,7 +1118,7 @@ func (w *_assembler) Prototype() datamodel.NodePrototype { type _structAssembler struct { // TODO: embed _assembler? - cfg config + cfg *config schemaType *schema.TypeStruct val reflect.Value // non-pointer @@ -1245,7 +1245,7 @@ func (w _errorAssembler) Prototype() datamodel.NodePrototype { ret // used for Maps which we can assume are of type: struct{Keys []string, Values map[x]y}, // where we have Keys in keysVal and Values in valuesVal type _mapAssembler struct { - cfg config + cfg *config schemaType *schema.TypeMap keysVal reflect.Value // non-pointer valuesVal reflect.Value // non-pointer @@ -1311,7 +1311,7 @@ func (w *_mapAssembler) ValuePrototype(k string) datamodel.NodePrototype { // _listAssembler is for operating directly on slices, which we have in val type _listAssembler struct { - cfg config + cfg *config schemaType *schema.TypeList val reflect.Value // non-pointer finish func() error @@ -1345,7 +1345,7 @@ func (w *_listAssembler) ValuePrototype(idx int64) datamodel.NodePrototype { // when assembling as a Map but we anticipate a single value, which we need to // look up in the union members type _unionAssembler struct { - cfg config + cfg *config schemaType *schema.TypeUnion val reflect.Value // non-pointer finish func() error @@ -1434,7 +1434,7 @@ func (w *_unionAssembler) ValuePrototype(k string) datamodel.NodePrototype { // should match the schema for this type. type _structIterator struct { // TODO: support embedded fields? - cfg config + cfg *config schemaType *schema.TypeStruct fields []schema.StructField @@ -1463,7 +1463,7 @@ func (w *_structIterator) Next() (key, value datamodel.Node, _ error) { } _, isAny := field.Type().(*schema.TypeAny) if isAny { - if customConverter := w.cfg.converterFor(val); customConverter != nil { + if customConverter := w.cfg.converterFor(field.Type().Name(), val); customConverter != nil { // field is an Any and we have an Any converter which takes the underlying // struct field value and returns a datamodel.Node v, err := customConverter.customToAny(ptrVal(val).Interface()) @@ -1495,7 +1495,7 @@ func (w *_structIterator) Done() bool { // _mapIterator is for iterating over a struct{Keys []string, Values map[x]y}, // where we have the Keys in keysVal and Values in valuesVal type _mapIterator struct { - cfg config + cfg *config schemaType *schema.TypeMap keysVal reflect.Value // non-pointer valuesVal reflect.Value // non-pointer @@ -1513,7 +1513,7 @@ func (w *_mapIterator) Next() (key, value datamodel.Node, _ error) { key = newNode(w.cfg, w.schemaType.KeyType(), goKey) _, isAny := w.schemaType.ValueType().(*schema.TypeAny) if isAny { - if customConverter := w.cfg.converterFor(val); customConverter != nil { + if customConverter := w.cfg.converterFor(w.schemaType.ValueType().Name(), val); customConverter != nil { // values of this map are Any and we have an Any converter which takes the // underlying map value and returns a datamodel.Node @@ -1543,7 +1543,7 @@ func (w *_mapIterator) Done() bool { // _listIterator is for iterating over slices, which is held in val type _listIterator struct { - cfg config + cfg *config schemaType *schema.TypeList val reflect.Value // non-pointer nextIndex int @@ -1563,7 +1563,7 @@ func (w *_listIterator) Next() (index int64, value datamodel.Node, _ error) { val = val.Elem() // nullable values are pointers } if _, ok := w.schemaType.ValueType().(*schema.TypeAny); ok { - if customConverter := w.cfg.converterFor(val); customConverter != nil { + if customConverter := w.cfg.converterFor(w.schemaType.ValueType().Name(), val); customConverter != nil { // values are Any and we have an Any converter which can take whatever // the underlying Go type in this slice is and return a datamodel.Node val, err := customConverter.customToAny(ptrVal(val).Interface()) @@ -1581,7 +1581,7 @@ func (w *_listIterator) Done() bool { type _unionIterator struct { // TODO: support embedded fields? - cfg config + cfg *config schemaType *schema.TypeUnion members []schema.Type val reflect.Value // non-pointer @@ -1615,7 +1615,7 @@ func (w *_unionIterator) Done() bool { // --- uint64 special case handling type _uintNode struct { - cfg config + cfg *config schemaType schema.Type val reflect.Value // non-pointer @@ -1722,7 +1722,7 @@ func (tu *_uintNodeRepr) AsInt() (int64, error) { if err := compatibleKind(tu.schemaType, datamodel.Kind_Int); err != nil { return 0, err } - if customConverter := tu.cfg.converterFor(tu.val); customConverter != nil { + if customConverter := tu.cfg.converterFor(tu.schemaType.Name(), tu.val); customConverter != nil { // user has registered a converter that takes the underlying type and returns an int return customConverter.customToInt(ptrVal(tu.val).Interface()) } diff --git a/node/bindnode/repr.go b/node/bindnode/repr.go index 3c08a2b8..00805bf1 100644 --- a/node/bindnode/repr.go +++ b/node/bindnode/repr.go @@ -333,7 +333,7 @@ func (w *_nodeRepr) lengthMinusAbsents() int64 { type _tupleIteratorRepr struct { // TODO: support embedded fields? - cfg config + cfg *config schemaType *schema.TypeStruct fields []schema.StructField val reflect.Value // non-pointer @@ -361,7 +361,7 @@ func (w *_tupleIteratorRepr) Done() bool { } type _listpairsIteratorRepr struct { - cfg config + cfg *config schemaType *schema.TypeStruct fields []schema.StructField val reflect.Value // non-pointer @@ -627,7 +627,7 @@ func (w *_builderRepr) Reset() { } type _assemblerRepr struct { - cfg config + cfg *config schemaType schema.Type val reflect.Value // non-pointer finish func() error