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  }