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.