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 }