github.com/ipld/go-ipld-prime@v0.21.0/datamodel/node.go (about) 1 package datamodel 2 3 import "io" 4 5 // Node represents a value in IPLD. Any point in a tree of data is a node: 6 // scalar values (like int64, string, etc) are nodes, and 7 // so are recursive values (like map and list). 8 // 9 // Nodes and kinds are described in the IPLD specs at 10 // https://github.com/ipld/specs/blob/master/data-model-layer/data-model.md . 11 // 12 // Methods on the Node interface cover the superset of all possible methods for 13 // all possible kinds -- but some methods only make sense for particular kinds, 14 // and thus will only make sense to call on values of the appropriate kind. 15 // (For example, 'Length' on an integer doesn't make sense, 16 // and 'AsInt' on a map certainly doesn't work either!) 17 // Use the Kind method to find out the kind of value before 18 // calling kind-specific methods. 19 // Individual method documentation state which kinds the method is valid for. 20 // (If you're familiar with the stdlib reflect package, you'll find 21 // the design of the Node interface very comparable to 'reflect.Value'.) 22 // 23 // The Node interface is read-only. All of the methods on the interface are 24 // for examining values, and implementations should be immutable. 25 // The companion interface, NodeBuilder, provides the matching writable 26 // methods, and should be use to create a (thence immutable) Node. 27 // 28 // Keeping Node immutable and separating mutation into NodeBuilder makes 29 // it possible to perform caching (or rather, memoization, since there's no 30 // such thing as cache invalidation for immutable systems) of computed 31 // properties of Node; use copy-on-write algorithms for memory efficiency; 32 // and to generally build pleasant APIs. 33 // Many library functions will rely on the immutability of Node (e.g., 34 // assuming that pointer-equal nodes do not change in value over time), 35 // so any user-defined Node implementations should be careful to uphold 36 // the immutability contract.) 37 // 38 // There are many different concrete types which implement Node. 39 // The primary purpose of various node implementations is to organize 40 // memory in the program in different ways -- some in-memory layouts may 41 // be more optimal for some programs than others, and changing the Node 42 // (and NodeBuilder) implementations lets the programmer choose. 43 // 44 // For concrete implementations of Node, check out the "./node/" folder, 45 // and the packages within it. 46 // "node/basicnode" should probably be your first start; the Node and NodeBuilder 47 // implementations in that package work for any data. 48 // Other packages are optimized for specific use-cases. 49 // Codegen tools can also be used to produce concrete implementations of Node; 50 // these may be specific to certain data, but still conform to the Node 51 // interface for interoperability and to support higher-level functions. 52 // 53 // Nodes may also be *typed* -- see the 'schema' package and `schema.TypedNode` 54 // interface, which extends the Node interface with additional methods. 55 // Typed nodes have additional constraints and behaviors: 56 // for example, they may be a "struct" and have a specific type/structure 57 // to what data you can put inside them, but still behave as a regular Node 58 // in all ways this interface specifies (so you can traverse typed nodes, etc, 59 // without any additional special effort). 60 type Node interface { 61 // Kind returns a value from the Kind enum describing what the 62 // essential serializable kind of this node is (map, list, integer, etc). 63 // Most other handling of a node requires first switching upon the kind. 64 Kind() Kind 65 66 // LookupByString looks up a child object in this node and returns it. 67 // The returned Node may be any of the Kind: 68 // a primitive (string, int64, etc), a map, a list, or a link. 69 // 70 // If the Kind of this Node is not Kind_Map, a nil node and an error 71 // will be returned. 72 // 73 // If the key does not exist, a nil node and an error will be returned. 74 LookupByString(key string) (Node, error) 75 76 // LookupByNode is the equivalent of LookupByString, but takes a reified Node 77 // as a parameter instead of a plain string. 78 // This mechanism is useful if working with typed maps (if the key types 79 // have constraints, and you already have a reified `schema.TypedNode` value, 80 // using that value can save parsing and validation costs); 81 // and may simply be convenient if you already have a Node value in hand. 82 // 83 // (When writing generic functions over Node, a good rule of thumb is: 84 // when handling a map, check for `schema.TypedNode`, and in this case prefer 85 // the LookupByNode(Node) method; otherwise, favor LookupByString; typically 86 // implementations will have their fastest paths thusly.) 87 LookupByNode(key Node) (Node, error) 88 89 // LookupByIndex is the equivalent of LookupByString but for indexing into a list. 90 // As with LookupByString, the returned Node may be any of the Kind: 91 // a primitive (string, int64, etc), a map, a list, or a link. 92 // 93 // If the Kind of this Node is not Kind_List, a nil node and an error 94 // will be returned. 95 // 96 // If idx is out of range, a nil node and an error will be returned. 97 LookupByIndex(idx int64) (Node, error) 98 99 // LookupBySegment is will act as either LookupByString or LookupByIndex, 100 // whichever is contextually appropriate. 101 // 102 // Using LookupBySegment may imply an "atoi" conversion if used on a list node, 103 // or an "itoa" conversion if used on a map node. If an "itoa" conversion 104 // takes place, it may error, and this method may return that error. 105 LookupBySegment(seg PathSegment) (Node, error) 106 107 // Note that when using codegenerated types, there may be a fifth variant 108 // of lookup method on maps: `Get($GeneratedTypeKey) $GeneratedTypeValue`! 109 110 // MapIterator returns an iterator which yields key-value pairs 111 // traversing the node. 112 // If the node kind is anything other than a map, nil will be returned. 113 // 114 // The iterator will yield every entry in the map; that is, it 115 // can be expected that itr.Next will be called node.Length times 116 // before itr.Done becomes true. 117 MapIterator() MapIterator 118 119 // ListIterator returns an iterator which traverses the node and yields indicies and list entries. 120 // If the node kind is anything other than a list, nil will be returned. 121 // 122 // The iterator will yield every entry in the list; that is, it 123 // can be expected that itr.Next will be called node.Length times 124 // before itr.Done becomes true. 125 // 126 // List iteration is ordered, and indices yielded during iteration will range from 0 to Node.Length-1. 127 // (The IPLD Data Model definition of lists only defines that it is an ordered list of elements; 128 // the definition does not include a concept of sparseness, so the indices are always sequential.) 129 ListIterator() ListIterator 130 131 // Length returns the length of a list, or the number of entries in a map, 132 // or -1 if the node is not of list nor map kind. 133 Length() int64 134 135 // Absent nodes are returned when traversing a struct field that is 136 // defined by a schema but unset in the data. (Absent nodes are not 137 // possible otherwise; you'll only see them from `schema.TypedNode`.) 138 // The absent flag is necessary so iterating over structs can 139 // unambiguously make the distinction between values that are 140 // present-and-null versus values that are absent. 141 // 142 // Absent nodes respond to `Kind()` as `ipld.Kind_Null`, 143 // for lack of any better descriptive value; you should therefore 144 // always check IsAbsent rather than just a switch on kind 145 // when it may be important to handle absent values distinctly. 146 IsAbsent() bool 147 148 IsNull() bool 149 AsBool() (bool, error) 150 AsInt() (int64, error) 151 AsFloat() (float64, error) 152 AsString() (string, error) 153 AsBytes() ([]byte, error) 154 AsLink() (Link, error) 155 156 // Prototype returns a NodePrototype which can describe some properties of this node's implementation, 157 // and also be used to get a NodeBuilder, 158 // which can be use to create new nodes with the same implementation as this one. 159 // 160 // For typed nodes, the NodePrototype will also implement schema.Type. 161 // 162 // For Advanced Data Layouts, the NodePrototype will encapsulate any additional 163 // parameters and configuration of the ADL, and will also (usually) 164 // implement NodePrototypeSupportingAmend. 165 // 166 // Calling this method should not cause an allocation. 167 Prototype() NodePrototype 168 } 169 170 // UintNode is an optional interface that can be used to represent an Int node 171 // that provides access to the full uint64 range. 172 // 173 // EXPERIMENTAL: this API is experimental and may be changed or removed in a 174 // future use. A future iteration may replace this with a BigInt interface to 175 // access a larger range of integers that may be enabled by alternative codecs. 176 type UintNode interface { 177 Node 178 179 // AsUint returns a uint64 representing the underlying integer if possible. 180 // This may return an error if the Node represents a negative integer that 181 // cannot be represented as a uint64. 182 AsUint() (uint64, error) 183 } 184 185 // LargeBytesNode is an optional interface extending a Bytes node that allows its 186 // contents to be accessed through an io.ReadSeeker instead of a []byte slice. Use of 187 // an io.Reader is encouraged, as it allows for streaming large byte slices 188 // without allocating a large slice in memory. 189 type LargeBytesNode interface { 190 Node 191 // AsLargeBytes returns an io.ReadSeeker that can be used to read the contents of the node. 192 // Note that the presence of this method / interface does not imply that the node 193 // can always return a valid io.ReadSeeker, and the error value must also be checked 194 // for support. 195 // It is not guaranteed that all implementations will implement the full semantics of 196 // Seek, in particular, they may refuse to seek to the end of a large bytes node if 197 // it is not possible to do so efficiently. 198 // The io.ReadSeeker returned by AsLargeBytes must be a seperate instance from subsequent 199 // calls to AsLargeBytes. Calls to read or seek on one returned instance should NOT 200 // affect the read position of other returned instances. 201 AsLargeBytes() (io.ReadSeeker, error) 202 } 203 204 // NodePrototype describes a node implementation (all Node have a NodePrototype), 205 // and a NodePrototype can always be used to get a NodeBuilder. 206 // 207 // A NodePrototype may also provide other information about implementation; 208 // such information is specific to this library ("prototype" isn't a concept 209 // you'll find in the IPLD Specifications), and is usually provided through 210 // feature-detection interfaces (for example, see NodePrototypeSupportingAmend). 211 // 212 // Generic algorithms for working with IPLD Nodes make use of NodePrototype 213 // to get builders for new nodes when creating data, and can also use the 214 // feature-detection interfaces to help decide what kind of operations 215 // will be optimal to use on a given node implementation. 216 // 217 // Note that NodePrototype is not the same as schema.Type. 218 // NodePrototype is a (golang-specific!) way to reflect upon the implementation 219 // and in-memory layout of some IPLD data. 220 // schema.Type is information about how a group of nodes is related in a schema 221 // (if they have one!) and the rules that the type mandates the node must follow. 222 // (Every node must have a prototype; but schema types are an optional feature.) 223 type NodePrototype interface { 224 // NewBuilder returns a NodeBuilder that can be used to create a new Node. 225 // 226 // Note that calling NewBuilder often performs an allocation 227 // (while in contrast, getting a NodePrototype typically does not!) -- 228 // this may be consequential when writing high performance code. 229 NewBuilder() NodeBuilder 230 } 231 232 // NodePrototypeSupportingAmend is a feature-detection interface that can be 233 // used on a NodePrototype to see if it's possible to build new nodes of this style 234 // while sharing some internal data in a copy-on-write way. 235 // 236 // For example, Nodes using an Advanced Data Layout will typically 237 // support this behavior, and since ADLs are often used for handling large 238 // volumes of data, detecting and using this feature can result in significant 239 // performance savings. 240 type NodePrototypeSupportingAmend interface { 241 AmendingBuilder(base Node) NodeBuilder 242 // FUTURE: probably also needs a `AmendingWithout(base Node, filter func(k,v) bool) NodeBuilder`, or similar. 243 // ("deletion" based APIs are also possible but both more complicated in interfaces added, and prone to accidentally quadratic usage.) 244 // FUTURE: there should be some stdlib `Copy` (?) methods that automatically look for this feature, and fallback if absent. 245 // Might include a wide range of point `Transform`, etc, methods. 246 // FUTURE: consider putting this (and others like it) in a `feature` package, if there begin to be enough of them and docs get crowded. 247 } 248 249 // MapIterator is an interface for traversing map nodes. 250 // Sequential calls to Next() will yield key-value pairs; 251 // Done() describes whether iteration should continue. 252 // 253 // Iteration order is defined to be stable: two separate MapIterator 254 // created to iterate the same Node will yield the same key-value pairs 255 // in the same order. 256 // The order itself may be defined by the Node implementation: some 257 // Nodes may retain insertion order, and some may return iterators which 258 // always yield data in sorted order, for example. 259 type MapIterator interface { 260 // Next returns the next key-value pair. 261 // 262 // An error value can also be returned at any step: in the case of advanced 263 // data structures with incremental loading, it's possible to encounter 264 // cancellation or I/O errors at any point in iteration. 265 // If an error will be returned by the next call to Next, 266 // then the boolean returned by the Done method will be false 267 // (meaning it's acceptable to check Done first and move on if it's true, 268 // since that both means the iterator is complete and that there is no error). 269 // If an error is returned, the key and value may be nil. 270 Next() (key Node, value Node, err error) 271 272 // Done returns false as long as there's at least one more entry to iterate. 273 // When Done returns true, iteration can stop. 274 // 275 // Note when implementing iterators for advanced data layouts (e.g. more than 276 // one chunk of backing data, which is loaded incrementally): if your 277 // implementation does any I/O during the Done method, and it encounters 278 // an error, it must return 'false', so that the following Next call 279 // has an opportunity to return the error. 280 Done() bool 281 } 282 283 // ListIterator is an interface for traversing list nodes. 284 // Sequential calls to Next() will yield index-value pairs; 285 // Done() describes whether iteration should continue. 286 // 287 // ListIterator's Next method returns an index for convenience, 288 // but this number will always start at 0 and increment by 1 monotonically. 289 // A loop which iterates from 0 to Node.Length while calling Node.LookupByIndex 290 // is equivalent to using a ListIterator. 291 type ListIterator interface { 292 // Next returns the next index and value. 293 // 294 // An error value can also be returned at any step: in the case of advanced 295 // data structures with incremental loading, it's possible to encounter 296 // cancellation or I/O errors at any point in iteration. 297 // If an error will be returned by the next call to Next, 298 // then the boolean returned by the Done method will be false 299 // (meaning it's acceptable to check Done first and move on if it's true, 300 // since that both means the iterator is complete and that there is no error). 301 // If an error is returned, the key and value may be nil. 302 Next() (idx int64, value Node, err error) 303 304 // Done returns false as long as there's at least one more entry to iterate. 305 // When Done returns false, iteration can stop. 306 // 307 // Note when implementing iterators for advanced data layouts (e.g. more than 308 // one chunk of backing data, which is loaded incrementally): if your 309 // implementation does any I/O during the Done method, and it encounters 310 // an error, it must return 'false', so that the following Next call 311 // has an opportunity to return the error. 312 Done() bool 313 } 314 315 // REVIEW: immediate-mode AsBytes() method (as opposed to e.g. returning 316 // an io.Reader instance) might be problematic, esp. if we introduce 317 // AdvancedLayouts which support large bytes natively. 318 // 319 // Probable solution is having both immediate and iterator return methods. 320 // Returning a reader for bytes when you know you want a slice already 321 // is going to be high friction without purpose in many common uses. 322 // 323 // Unclear what SetByteStream() would look like for advanced layouts. 324 // One could try to encapsulate the chunking entirely within the advlay 325 // node impl... but would it be graceful? Not sure. Maybe. Hopefully! 326 // Yes? The advlay impl would still tend to use SetBytes for the raw 327 // data model layer nodes its composing, so overall, it shakes out nicely.