From a7237654a84508c6244e18952329a7c87951cb19 Mon Sep 17 00:00:00 2001 From: Eric Myhre Date: Sat, 3 Jul 2021 12:25:34 -0700 Subject: [PATCH] selectors: docs enhancements, new construction helpers. --- traversal/selector/helpers.go | 35 ++++++++++++++++++++++++++++++++ traversal/selector/selector.go | 37 +++++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 traversal/selector/helpers.go diff --git a/traversal/selector/helpers.go b/traversal/selector/helpers.go new file mode 100644 index 00000000..bb8be696 --- /dev/null +++ b/traversal/selector/helpers.go @@ -0,0 +1,35 @@ +package selector + +import ( + "strings" + + "github.com/ipld/go-ipld-prime/codec/json" + "github.com/ipld/go-ipld-prime/node/basic" +) + +// REVIEW: I feel sketchy about CompileJSONSelector because it brings in json as a transitive dependency. +// This has consequences as far as dragging go-cid in because our json shares code with dagjson... +// and then go-cid ends up in the transitives of the traversal package, since traversal depends on selector. +// That's... not great. +// On the other hand: trying to keep up that paper wall is creating a lot of strife. Maybe it's time to give up. + +func CompileJSONSelector(jsonStr string) (Selector, error) { + na := basicnode.Prototype.Any.NewBuilder() + if err := json.Decode(na, strings.NewReader(jsonStr)); err != nil { + return nil, err + } + if s, err := CompileSelector(na.Build()); err != nil { + return nil, err + } else { + return s, nil + } +} + +func must(s Selector, e error) Selector { + if e != nil { + panic(e) + } + return s +} + +var CommonSelector_MatchPoint = must(CompileJSONSelector(`{".":{}}`)) diff --git a/traversal/selector/selector.go b/traversal/selector/selector.go index 319c359e..2f7f4a13 100644 --- a/traversal/selector/selector.go +++ b/traversal/selector/selector.go @@ -6,14 +6,28 @@ import ( ipld "github.com/ipld/go-ipld-prime" ) -// Selector is the programmatic representation of an IPLD Selector Node -// and can be applied to traverse a given IPLD DAG +// Selector is a "compiled" and executable IPLD Selector. +// It can be put to work with functions like traversal.Walk, +// which will use the Selector's guidance to decide how to traverse an IPLD data graph. +// +// A Selector is created by parsing an IPLD Data Model document that declares a Selector +// (this is accomplished with functions like CompileSelector). +// Alternatively, there is a builder subpackage, +// which is useful if you would rather create the Selector declaration programmatically in golang. +// +// There is no way to go backwards from this "compiled" Selector type into the declarative IPLD data model information that produced it. +// That declaration information is discarded after compilation in order to limit the amount of memory held. +// Therefore, if you're building APIs about Selector composition, keep in mind that +// you'll probably want to approach this be composing the Data Model declaration documents, +// not be composing this type, which is only for the "compiled" result. type Selector interface { Interests() []ipld.PathSegment // returns the segments we're likely interested in **or nil** if we're a high-cardinality or expression based matcher and need all segments proposed to us. Explore(ipld.Node, ipld.PathSegment) Selector // explore one step -- iteration comes from outside (either whole node, or by following suggestions of Interests). returns nil if no interest. you have to traverse to the next node yourself (the selector doesn't do it for you because you might be considering multiple selection reasons at the same time). Decide(ipld.Node) bool } +// REVIEW: do ParsedParent and ParseContext need to be exported? They're mostly used during the compilation process. + // ParsedParent is created whenever you are parsing a selector node that may have // child selectors nodes that need to know it type ParsedParent interface { @@ -25,9 +39,22 @@ type ParseContext struct { parentStack []ParsedParent } -// ParseSelector creates a Selector that can be traversed from an IPLD Selector node -func ParseSelector(n ipld.Node) (Selector, error) { - return ParseContext{}.ParseSelector(n) +// CompileSelector accepts an ipld.Node which should contain data that declares a Selector. +// The data layout expected for this declaration is documented in https://ipld.io/specs/selectors/ . +// +// If the Selector is compiled successfully, it is returned. +// Otherwise, if the given data Node doesn't match the expected shape for a Selector declaration, +// or there are any other problems compiling the selector +// (such as a recursion edge with no enclosing recursion declaration, etc), +// then nil and an error will be returned. +func CompileSelector(dmt ipld.Node) (Selector, error) { + return ParseContext{}.ParseSelector(dmt) +} + +// ParseSelector is an alias for CompileSelector, and is deprecated. +// Prefer CompileSelector. +func ParseSelector(dmt ipld.Node) (Selector, error) { + return CompileSelector(dmt) } // ParseSelector creates a Selector from an IPLD Selector Node with the given context