github.com/ipld/go-ipld-prime@v0.21.0/node/bindnode/registry/registry.go (about) 1 package registry 2 3 import ( 4 "fmt" 5 "io" 6 "reflect" 7 8 "github.com/ipld/go-ipld-prime" 9 "github.com/ipld/go-ipld-prime/codec" 10 "github.com/ipld/go-ipld-prime/datamodel" 11 "github.com/ipld/go-ipld-prime/node/bindnode" 12 "github.com/ipld/go-ipld-prime/schema" 13 ) 14 15 type prototypeData struct { 16 proto schema.TypedPrototype 17 options []bindnode.Option 18 } 19 20 // BindnodeRegistry holds TypedPrototype and bindnode options for Go types and 21 // will use that data for conversion operations. 22 type BindnodeRegistry map[reflect.Type]prototypeData 23 24 // NewRegistry creates a new BindnodeRegistry 25 func NewRegistry() BindnodeRegistry { 26 return make(BindnodeRegistry) 27 } 28 29 func typeOf(ptrValue interface{}) reflect.Type { 30 val := reflect.ValueOf(ptrValue).Type() 31 for val.Kind() == reflect.Ptr { 32 val = val.Elem() 33 } 34 return val 35 } 36 37 func (br BindnodeRegistry) prototypeDataFor(ptrType interface{}) prototypeData { 38 typ := typeOf(ptrType) 39 proto, ok := br[typ] 40 if !ok { 41 panic(fmt.Sprintf("bindnode utils: type has not been registered: %s", typ.Name())) 42 } 43 return proto 44 } 45 46 // RegisterType registers ptrType with schema such that it can be wrapped and 47 // unwrapped without needing the schema, Type, or TypedPrototype. 48 // Typically the typeName will match the Go type name, but it can be whatever 49 // is defined in the schema for the type being registered. 50 // Registering the same type twice on this registry will cause an error. 51 // This call may also error if the schema is invalid or the type doesn't match 52 // the schema. Additionally, panics from within bindnode's initial prototype 53 // checks will be captured and returned as errors from this function. 54 func (br BindnodeRegistry) RegisterType(ptrType interface{}, schema string, typeName string, options ...bindnode.Option) (err error) { 55 typ := typeOf(ptrType) 56 if _, ok := br[typ]; ok { 57 return fmt.Errorf("bindnode utils: type already registered: %s", typ.Name()) 58 } 59 typeSystem, err := ipld.LoadSchemaBytes([]byte(schema)) 60 if err != nil { 61 return fmt.Errorf("bindnode utils: failed to load schema: %s", err.Error()) 62 } 63 schemaType := typeSystem.TypeByName(typeName) 64 if schemaType == nil { 65 return fmt.Errorf("bindnode utils: schema for [%T] does not contain that named type [%s]", ptrType, typ.Name()) 66 } 67 68 // focusing on bindnode setup panics 69 defer func() { 70 if rec := recover(); rec != nil { 71 switch v := rec.(type) { 72 case string: 73 err = fmt.Errorf(v) 74 case error: 75 err = v 76 default: 77 panic(rec) 78 } 79 } 80 }() 81 82 proto := bindnode.Prototype(ptrType, schemaType, options...) 83 br[typ] = prototypeData{ 84 proto, 85 options, 86 } 87 88 return err 89 } 90 91 // IsRegistered can be used to determine if the type has already been registered 92 // within this registry. 93 // Using RegisterType on an already registered type will cause a panic, so where 94 // this may be the case, IsRegistered can be used to check. 95 func (br BindnodeRegistry) IsRegistered(ptrType interface{}) bool { 96 _, ok := br[typeOf(ptrType)] 97 return ok 98 } 99 100 // TypeFromReader deserializes bytes using the given codec from a Reader and 101 // instantiates the Go type that's provided as a pointer via the ptrValue 102 // argument. 103 func (br BindnodeRegistry) TypeFromReader(r io.Reader, ptrValue interface{}, decoder codec.Decoder) (interface{}, error) { 104 protoData := br.prototypeDataFor(ptrValue) 105 node, err := ipld.DecodeStreamingUsingPrototype(r, decoder, protoData.proto) 106 if err != nil { 107 return nil, err 108 } 109 typ := bindnode.Unwrap(node) 110 return typ, nil 111 } 112 113 // TypeFromBytes deserializes bytes using the given codec from its bytes and 114 // instantiates the Go type that's provided as a pointer via the ptrValue 115 // argument. 116 func (br BindnodeRegistry) TypeFromBytes(byts []byte, ptrValue interface{}, decoder codec.Decoder) (interface{}, error) { 117 protoData := br.prototypeDataFor(ptrValue) 118 node, err := ipld.DecodeUsingPrototype(byts, decoder, protoData.proto) 119 if err != nil { 120 return nil, err 121 } 122 typ := bindnode.Unwrap(node) 123 return typ, nil 124 } 125 126 // TypeFromNode converts an datamodel.Node into an appropriate Go type that's 127 // provided as a pointer via the ptrValue argument. 128 func (br BindnodeRegistry) TypeFromNode(node datamodel.Node, ptrValue interface{}) (interface{}, error) { 129 protoData := br.prototypeDataFor(ptrValue) 130 if tn, ok := node.(schema.TypedNode); ok { 131 node = tn.Representation() 132 } 133 builder := protoData.proto.Representation().NewBuilder() 134 err := builder.AssignNode(node) 135 if err != nil { 136 return nil, err 137 } 138 typ := bindnode.Unwrap(builder.Build()) 139 return typ, nil 140 } 141 142 // TypeToNode converts a Go type that's provided as a pointer via the ptrValue 143 // argument to an schema.TypedNode. 144 func (br BindnodeRegistry) TypeToNode(ptrValue interface{}) schema.TypedNode { 145 protoData := br.prototypeDataFor(ptrValue) 146 return bindnode.Wrap(ptrValue, protoData.proto.Type(), protoData.options...) 147 } 148 149 // TypeToWriter is a utility method that serializes a Go type that's provided as 150 // a pointer via the ptrValue argument through the given codec to a Writer. 151 func (br BindnodeRegistry) TypeToWriter(ptrValue interface{}, w io.Writer, encoder codec.Encoder) error { 152 return ipld.EncodeStreaming(w, br.TypeToNode(ptrValue), encoder) 153 } 154 155 // TypeToBytes is a utility method that serializes a Go type that's provided as 156 // a pointer via the ptrValue argument through the given codec and returns the 157 // bytes. 158 func (br BindnodeRegistry) TypeToBytes(ptrValue interface{}, encoder codec.Encoder) ([]byte, error) { 159 return ipld.Encode(br.TypeToNode(ptrValue), encoder) 160 }