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  }