github.com/ipld/go-ipld-prime@v0.21.0/codecHelpers.go (about)

     1  package ipld
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"reflect"
     7  
     8  	"github.com/ipld/go-ipld-prime/node/basicnode"
     9  	"github.com/ipld/go-ipld-prime/node/bindnode"
    10  	"github.com/ipld/go-ipld-prime/schema"
    11  )
    12  
    13  // Encode serializes the given Node using the given Encoder function,
    14  // returning the serialized data or an error.
    15  //
    16  // The exact result data will depend the node content and on the encoder function,
    17  // but for example, using a json codec on a node with kind map will produce
    18  // a result starting in `{`, etc.
    19  //
    20  // Encode will automatically switch to encoding the representation form of the Node,
    21  // if it discovers the Node matches the schema.TypedNode interface.
    22  // This is probably what you want, in most cases;
    23  // if this is not desired, you can use the underlaying functions directly
    24  // (just look at the source of this function for an example of how!).
    25  //
    26  // If you would like this operation, but applied directly to a golang type instead of a Node,
    27  // look to the Marshal function.
    28  func Encode(n Node, encFn Encoder) ([]byte, error) {
    29  	var buf bytes.Buffer
    30  	err := EncodeStreaming(&buf, n, encFn)
    31  	return buf.Bytes(), err
    32  }
    33  
    34  // EncodeStreaming is like Encode, but emits output to an io.Writer.
    35  func EncodeStreaming(wr io.Writer, n Node, encFn Encoder) error {
    36  	if tn, ok := n.(schema.TypedNode); ok {
    37  		n = tn.Representation()
    38  	}
    39  	return encFn(n, wr)
    40  }
    41  
    42  // Decode parses the given bytes into a Node using the given Decoder function,
    43  // returning a new Node or an error.
    44  //
    45  // The new Node that is returned will be the implementation from the node/basicnode package.
    46  // This implementation of Node will work for storing any kind of data,
    47  // but note that because it is general, it is also not necessarily optimized.
    48  // If you want more control over what kind of Node implementation (and thus memory layout) is used,
    49  // or want to use features like IPLD Schemas (which can be engaged by using a schema.TypedPrototype),
    50  // then look to the DecodeUsingPrototype family of functions,
    51  // which accept more parameters in order to give you that kind of control.
    52  //
    53  // If you would like this operation, but applied directly to a golang type instead of a Node,
    54  // look to the Unmarshal function.
    55  func Decode(b []byte, decFn Decoder) (Node, error) {
    56  	return DecodeUsingPrototype(b, decFn, basicnode.Prototype.Any)
    57  }
    58  
    59  // DecodeStreaming is like Decode, but works on an io.Reader for input.
    60  func DecodeStreaming(r io.Reader, decFn Decoder) (Node, error) {
    61  	return DecodeStreamingUsingPrototype(r, decFn, basicnode.Prototype.Any)
    62  }
    63  
    64  // DecodeUsingPrototype is like Decode, but with a NodePrototype parameter,
    65  // which gives you control over the Node type you'll receive,
    66  // and thus control over the memory layout, and ability to use advanced features like schemas.
    67  // (Decode is simply this function, but hardcoded to use basicnode.Prototype.Any.)
    68  //
    69  // DecodeUsingPrototype internally creates a NodeBuilder, and thows it away when done.
    70  // If building a high performance system, and creating data of the same shape repeatedly,
    71  // you may wish to use NodeBuilder directly, so that you can control and avoid these allocations.
    72  //
    73  // For symmetry with the behavior of Encode, DecodeUsingPrototype will automatically
    74  // switch to using the representation form of the node for decoding
    75  // if it discovers the NodePrototype matches the schema.TypedPrototype interface.
    76  // This is probably what you want, in most cases;
    77  // if this is not desired, you can use the underlaying functions directly
    78  // (just look at the source of this function for an example of how!).
    79  func DecodeUsingPrototype(b []byte, decFn Decoder, np NodePrototype) (Node, error) {
    80  	return DecodeStreamingUsingPrototype(bytes.NewReader(b), decFn, np)
    81  }
    82  
    83  // DecodeStreamingUsingPrototype is like DecodeUsingPrototype, but works on an io.Reader for input.
    84  func DecodeStreamingUsingPrototype(r io.Reader, decFn Decoder, np NodePrototype) (Node, error) {
    85  	if tnp, ok := np.(schema.TypedPrototype); ok {
    86  		np = tnp.Representation()
    87  	}
    88  	nb := np.NewBuilder()
    89  	if err := decFn(nb, r); err != nil {
    90  		return nil, err
    91  	}
    92  	return nb.Build(), nil
    93  }
    94  
    95  // Marshal accepts a pointer to a Go value and an IPLD schema type,
    96  // and encodes the representation form of that data (which may be configured with the schema!)
    97  // using the given Encoder function.
    98  //
    99  // Marshal uses the node/bindnode subsystem.
   100  // See the documentation in that package for more details about its workings.
   101  // Please note that this subsystem is relatively experimental at this time.
   102  //
   103  // The schema.Type parameter is optional, and can be nil.
   104  // If given, it controls what kind of schema.Type (and what kind of representation strategy!)
   105  // to use when processing the data.
   106  // If absent, a default schema.Type will be inferred based on the golang type
   107  // (so, a struct in go will be inferred to have a schema with a similar struct, and the default representation strategy (e.g. map), etc).
   108  // Note that not all features of IPLD Schemas can be inferred from golang types alone.
   109  // For example, to use union types, the schema parameter will be required.
   110  // Similarly, to use most kinds of non-default representation strategy, the schema parameter is needed in order to convey that intention.
   111  func Marshal(encFn Encoder, bind interface{}, typ schema.Type) ([]byte, error) {
   112  	n := bindnode.Wrap(bind, typ)
   113  	return Encode(n.Representation(), encFn)
   114  }
   115  
   116  // MarshalStreaming is like Marshal, but emits output to an io.Writer.
   117  func MarshalStreaming(wr io.Writer, encFn Encoder, bind interface{}, typ schema.Type) error {
   118  	n := bindnode.Wrap(bind, typ)
   119  	return EncodeStreaming(wr, n.Representation(), encFn)
   120  }
   121  
   122  // Unmarshal accepts a pointer to a Go value and an IPLD schema type,
   123  // and fills the value with data by decoding into it with the given Decoder function.
   124  //
   125  // Unmarshal uses the node/bindnode subsystem.
   126  // See the documentation in that package for more details about its workings.
   127  // Please note that this subsystem is relatively experimental at this time.
   128  //
   129  // The schema.Type parameter is optional, and can be nil.
   130  // If given, it controls what kind of schema.Type (and what kind of representation strategy!)
   131  // to use when processing the data.
   132  // If absent, a default schema.Type will be inferred based on the golang type
   133  // (so, a struct in go will be inferred to have a schema with a similar struct, and the default representation strategy (e.g. map), etc).
   134  // Note that not all features of IPLD Schemas can be inferred from golang types alone.
   135  // For example, to use union types, the schema parameter will be required.
   136  // Similarly, to use most kinds of non-default representation strategy, the schema parameter is needed in order to convey that intention.
   137  //
   138  // In contrast to some other unmarshal conventions common in golang,
   139  // notice that we also return a Node value.
   140  // This Node points to the same data as the value you handed in as the bind parameter,
   141  // while making it available to read and iterate and handle as a ipld datamodel.Node.
   142  // If you don't need that interface, or intend to re-bind it later, you can discard that value.
   143  //
   144  // The 'bind' parameter may be nil.
   145  // In that case, the type of the nil is still used to infer what kind of value to return,
   146  // and a Node will still be returned based on that type.
   147  // bindnode.Unwrap can be used on that Node and will still return something
   148  // of the same golang type as the typed nil that was given as the 'bind' parameter.
   149  func Unmarshal(b []byte, decFn Decoder, bind interface{}, typ schema.Type) (Node, error) {
   150  	return UnmarshalStreaming(bytes.NewReader(b), decFn, bind, typ)
   151  }
   152  
   153  // UnmarshalStreaming is like Unmarshal, but works on an io.Reader for input.
   154  func UnmarshalStreaming(r io.Reader, decFn Decoder, bind interface{}, typ schema.Type) (Node, error) {
   155  	// Decode is fairly straightforward.
   156  	np := bindnode.Prototype(bind, typ)
   157  	n, err := DecodeStreamingUsingPrototype(r, decFn, np.Representation())
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	// ... but our approach above allocated new memory, and we have to copy it back out.
   162  	// In the future, the bindnode API could be improved to make this easier.
   163  	if !reflect.ValueOf(bind).IsNil() {
   164  		reflect.ValueOf(bind).Elem().Set(reflect.ValueOf(bindnode.Unwrap(n)).Elem())
   165  	}
   166  	// ... and we also have to re-bind a new node to the 'bind' value,
   167  	// because probably the user will be surprised if mutating 'bind' doesn't affect the Node later.
   168  	n = bindnode.Wrap(bind, typ)
   169  	return n, err
   170  }