github.com/ipld/go-ipld-prime@v0.21.0/datamodel/nodeBuilder.go (about) 1 package datamodel 2 3 // NodeAssembler is the interface that describes all the ways we can set values 4 // in a node that's under construction. 5 // 6 // A NodeAssembler is about filling in data. 7 // To create a new Node, you should start with a NodeBuilder (which contains a 8 // superset of the NodeAssembler methods, and can return the finished Node 9 // from its `Build` method). 10 // While continuing to build a recursive structure from there, 11 // you'll see NodeAssembler for all the child values. 12 // 13 // For filling scalar data, there's a `Assign{Kind}` method for each kind; 14 // after calling one of these methods, the data is filled in, and the assembler is done. 15 // For recursives, there are `BeginMap` and `BeginList` methods, 16 // which return an object that needs further manipulation to fill in the contents. 17 // 18 // There is also one special method: `AssignNode`. 19 // `AssignNode` takes another `Node` as a parameter, 20 // and should should internally call one of the other `Assign*` or `Begin*` (and subsequent) functions 21 // as appropriate for the kind of the `Node` it is given. 22 // This is roughly equivalent to using the `Copy` function (and is often implemented using it!), but 23 // `AssignNode` may also try to take faster shortcuts in some implementations, when it detects they're possible. 24 // (For example, for typed nodes, if they're the same type, lots of checking can be skipped. 25 // For nodes implemented with pointers, lots of copying can be skipped. 26 // For nodes that can detect the argument has the same memory layout, faster copy mechanisms can be used; etc.) 27 // 28 // Why do both this and the NodeBuilder interface exist? 29 // In short: NodeBuilder is when you want to cause an allocation; 30 // NodeAssembler can be used to just "fill in" memory. 31 // (In the internal gritty details: separate interfaces, one of which lacks a 32 // `Build` method, helps us write efficient library internals: avoiding the 33 // requirement to be able to return a Node at any random point in the process 34 // relieves internals from needing to implement 'freeze' features. 35 // This is useful in turn because implementing those 'freeze' features in a 36 // language without first-class/compile-time support for them (as golang is) 37 // would tend to push complexity and costs to execution time; we'd rather not.) 38 type NodeAssembler interface { 39 BeginMap(sizeHint int64) (MapAssembler, error) 40 BeginList(sizeHint int64) (ListAssembler, error) 41 AssignNull() error 42 AssignBool(bool) error 43 AssignInt(int64) error 44 AssignFloat(float64) error 45 AssignString(string) error 46 AssignBytes([]byte) error 47 AssignLink(Link) error 48 49 AssignNode(Node) error // if you already have a completely constructed subtree, this method puts the whole thing in place at once. 50 51 // Prototype returns a NodePrototype describing what kind of value we're assembling. 52 // 53 // You often don't need this (because you should be able to 54 // just feed data and check errors), but it's here. 55 // 56 // Using `this.Prototype().NewBuilder()` to produce a new `Node`, 57 // then giving that node to `this.AssignNode(n)` should always work. 58 // (Note that this is not necessarily an _exclusive_ statement on what 59 // sort of values will be accepted by `this.AssignNode(n)`.) 60 Prototype() NodePrototype 61 } 62 63 // MapAssembler assembles a map node! (You guessed it.) 64 // 65 // Methods on MapAssembler must be called in a valid order: 66 // assemble a key, then assemble a value, then loop as long as desired; 67 // when finished, call 'Finish'. 68 // 69 // Incorrect order invocations will panic. 70 // Calling AssembleKey twice in a row will panic; 71 // calling AssembleValue before finishing using the NodeAssembler from AssembleKey will panic; 72 // calling AssembleValue twice in a row will panic; 73 // etc. 74 // 75 // Note that the NodeAssembler yielded from AssembleKey has additional behavior: 76 // if the node assembled there matches a key already present in the map, 77 // that assembler will emit the error! 78 type MapAssembler interface { 79 AssembleKey() NodeAssembler // must be followed by call to AssembleValue. 80 AssembleValue() NodeAssembler // must be called immediately after AssembleKey. 81 82 AssembleEntry(k string) (NodeAssembler, error) // shortcut combining AssembleKey and AssembleValue into one step; valid when the key is a string kind. 83 84 Finish() error 85 86 // KeyPrototype returns a NodePrototype that knows how to build keys of a type this map uses. 87 // 88 // You often don't need this (because you should be able to 89 // just feed data and check errors), but it's here. 90 // 91 // For all Data Model maps, this will answer with a basic concept of "string". 92 // For Schema typed maps, this may answer with a more complex type 93 // (potentially even a struct type or union type -- anything that can have a string representation). 94 KeyPrototype() NodePrototype 95 96 // ValuePrototype returns a NodePrototype that knows how to build values this map can contain. 97 // 98 // You often don't need this (because you should be able to 99 // just feed data and check errors), but it's here. 100 // 101 // ValuePrototype requires a parameter describing the key in order to say what 102 // NodePrototype will be acceptable as a value for that key, because when using 103 // struct types (or union types) from the Schemas system, they behave as maps 104 // but have different acceptable types for each field (or member, for unions). 105 // For plain maps (that is, not structs or unions masquerading as maps), 106 // the empty string can be used as a parameter, and the returned NodePrototype 107 // can be assumed applicable for all values. 108 // Using an empty string for a struct or union will return nil, 109 // as will using any string which isn't a field or member of those types. 110 // 111 // (Design note: a string is sufficient for the parameter here rather than 112 // a full Node, because the only cases where the value types vary are also 113 // cases where the keys may not be complex.) 114 ValuePrototype(k string) NodePrototype 115 } 116 117 type ListAssembler interface { 118 AssembleValue() NodeAssembler 119 120 Finish() error 121 122 // ValuePrototype returns a NodePrototype that knows how to build values this map can contain. 123 // 124 // You often don't need this (because you should be able to 125 // just feed data and check errors), but it's here. 126 // 127 // ValuePrototype, much like the matching method on the MapAssembler interface, 128 // requires a parameter specifying the index in the list in order to say 129 // what NodePrototype will be acceptable as a value at that position. 130 // For many lists (and *all* lists which operate exclusively at the Data Model level), 131 // this will return the same NodePrototype regardless of the value of 'idx'; 132 // the only time this value will vary is when operating with a Schema, 133 // and handling the representation NodeAssembler for a struct type with 134 // a representation of a list kind. 135 // If you know you are operating in a situation that won't have varying 136 // NodePrototypes, it is acceptable to call `ValuePrototype(0)` and use the 137 // resulting NodePrototype for all reasoning. 138 ValuePrototype(idx int64) NodePrototype 139 } 140 141 type NodeBuilder interface { 142 NodeAssembler 143 144 // Build returns the new value after all other assembly has been completed. 145 // 146 // A method on the NodeAssembler that finishes assembly of the data must 147 // be called first (e.g., any of the "Assign*" methods, or "Finish" if 148 // the assembly was for a map or a list); that finishing method still has 149 // all responsibility for validating the assembled data and returning 150 // any errors from that process. 151 // (Correspondingly, there is no error return from this method.) 152 // 153 // Note that building via a representation-level NodePrototype or NodeBuilder 154 // returns a node at the type level which implements schema.TypedNode. 155 // To obtain the representation-level node, you can do: 156 // 157 // // builder is at the representation level, so it returns typed nodes 158 // node := builder.Build().(schema.TypedNode) 159 // reprNode := node.Representation() 160 Build() Node 161 162 // Resets the builder. It can hereafter be used again. 163 // Reusing a NodeBuilder can reduce allocations and improve performance. 164 // 165 // Only call this if you're going to reuse the builder. 166 // (Otherwise, it's unnecessary, and may cause an unwanted allocation). 167 Reset() 168 }