github.com/ipld/go-ipld-prime@v0.21.0/traversal/selector/exploreRecursive_test.go (about)

     1  package selector
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	qt "github.com/frankban/quicktest"
     8  
     9  	"github.com/ipld/go-ipld-prime/codec/dagjson"
    10  	"github.com/ipld/go-ipld-prime/datamodel"
    11  	"github.com/ipld/go-ipld-prime/fluent"
    12  	"github.com/ipld/go-ipld-prime/node/basicnode"
    13  )
    14  
    15  func TestParseExploreRecursive(t *testing.T) {
    16  	t.Run("parsing non map node should error", func(t *testing.T) {
    17  		sn := basicnode.NewInt(0)
    18  		_, err := ParseContext{}.ParseExploreRecursive(sn)
    19  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: selector body must be a map")
    20  	})
    21  	t.Run("parsing map node without sequence field should error", func(t *testing.T) {
    22  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 1, func(na fluent.MapAssembler) {
    23  			na.AssembleEntry(SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) {
    24  				na.AssembleEntry(SelectorKey_LimitDepth).AssignInt(2)
    25  			})
    26  		})
    27  		_, err := ParseContext{}.ParseExploreRecursive(sn)
    28  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: sequence field must be present in ExploreRecursive selector")
    29  	})
    30  	t.Run("parsing map node without limit field should error", func(t *testing.T) {
    31  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 1, func(na fluent.MapAssembler) {
    32  			na.AssembleEntry(SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) {
    33  				na.AssembleEntry(SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {})
    34  			})
    35  		})
    36  		_, err := ParseContext{}.ParseExploreRecursive(sn)
    37  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: limit field must be present in ExploreRecursive selector")
    38  	})
    39  	t.Run("parsing map node with limit field that is not a map should fail", func(t *testing.T) {
    40  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 2, func(na fluent.MapAssembler) {
    41  			na.AssembleEntry(SelectorKey_Limit).AssignString("cheese")
    42  			na.AssembleEntry(SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) {
    43  				na.AssembleEntry(SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {})
    44  			})
    45  		})
    46  		_, err := ParseContext{}.ParseExploreRecursive(sn)
    47  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: limit in ExploreRecursive is a keyed union and thus must be a map")
    48  	})
    49  	t.Run("parsing map node with limit field that is not a single entry map should fail", func(t *testing.T) {
    50  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 2, func(na fluent.MapAssembler) {
    51  			na.AssembleEntry(SelectorKey_Limit).CreateMap(2, func(na fluent.MapAssembler) {
    52  				na.AssembleEntry(SelectorKey_LimitDepth).AssignInt(2)
    53  				na.AssembleEntry(SelectorKey_LimitNone).CreateMap(0, func(na fluent.MapAssembler) {})
    54  			})
    55  			na.AssembleEntry(SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) {
    56  				na.AssembleEntry(SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {})
    57  			})
    58  		})
    59  		_, err := ParseContext{}.ParseExploreRecursive(sn)
    60  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: limit in ExploreRecursive is a keyed union and thus must be a single-entry map")
    61  	})
    62  	t.Run("parsing map node with limit field that does not have a known key should fail", func(t *testing.T) {
    63  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 2, func(na fluent.MapAssembler) {
    64  			na.AssembleEntry(SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) {
    65  				na.AssembleEntry("applesauce").AssignInt(2)
    66  			})
    67  			na.AssembleEntry(SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) {
    68  				na.AssembleEntry(SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {})
    69  			})
    70  		})
    71  		_, err := ParseContext{}.ParseExploreRecursive(sn)
    72  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: \"applesauce\" is not a known member of the limit union in ExploreRecursive")
    73  	})
    74  	t.Run("parsing map node with limit field of type depth that is not an int should error", func(t *testing.T) {
    75  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 2, func(na fluent.MapAssembler) {
    76  			na.AssembleEntry(SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) {
    77  				na.AssembleEntry(SelectorKey_LimitDepth).AssignString("cheese")
    78  			})
    79  			na.AssembleEntry(SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) {
    80  				na.AssembleEntry(SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {})
    81  			})
    82  		})
    83  		_, err := ParseContext{}.ParseExploreRecursive(sn)
    84  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: limit field of type depth must be a number in ExploreRecursive selector")
    85  	})
    86  	t.Run("parsing map node with sequence field with invalid selector node should return child's error", func(t *testing.T) {
    87  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 2, func(na fluent.MapAssembler) {
    88  			na.AssembleEntry(SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) {
    89  				na.AssembleEntry(SelectorKey_LimitDepth).AssignInt(2)
    90  			})
    91  			na.AssembleEntry(SelectorKey_Sequence).AssignInt(0)
    92  		})
    93  		_, err := ParseContext{}.ParseExploreRecursive(sn)
    94  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: selector is a keyed union and thus must be a map")
    95  	})
    96  	t.Run("parsing map node with sequence field with valid selector w/o ExploreRecursiveEdge should not parse", func(t *testing.T) {
    97  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 2, func(na fluent.MapAssembler) {
    98  			na.AssembleEntry(SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) {
    99  				na.AssembleEntry(SelectorKey_LimitDepth).AssignInt(2)
   100  			})
   101  			na.AssembleEntry(SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) {
   102  				na.AssembleEntry(SelectorKey_ExploreAll).CreateMap(1, func(na fluent.MapAssembler) {
   103  					na.AssembleEntry(SelectorKey_Next).CreateMap(1, func(na fluent.MapAssembler) {
   104  						na.AssembleEntry(SelectorKey_Matcher).CreateMap(0, func(na fluent.MapAssembler) {})
   105  					})
   106  				})
   107  			})
   108  		})
   109  		_, err := ParseContext{}.ParseExploreRecursive(sn)
   110  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: ExploreRecursive must have at least one ExploreRecursiveEdge")
   111  	})
   112  	t.Run("parsing map node that is ExploreRecursiveEdge without ExploreRecursive parent should not parse", func(t *testing.T) {
   113  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 0, func(na fluent.MapAssembler) {})
   114  		_, err := ParseContext{}.ParseExploreRecursiveEdge(sn)
   115  		qt.Check(t, err, qt.ErrorMatches, "selector spec parse rejected: ExploreRecursiveEdge must be beneath ExploreRecursive")
   116  	})
   117  	t.Run("parsing map node with sequence field with valid selector node should parse", func(t *testing.T) {
   118  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 2, func(na fluent.MapAssembler) {
   119  			na.AssembleEntry(SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) {
   120  				na.AssembleEntry(SelectorKey_LimitDepth).AssignInt(2)
   121  			})
   122  			na.AssembleEntry(SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) {
   123  				na.AssembleEntry(SelectorKey_ExploreAll).CreateMap(1, func(na fluent.MapAssembler) {
   124  					na.AssembleEntry(SelectorKey_Next).CreateMap(1, func(na fluent.MapAssembler) {
   125  						na.AssembleEntry(SelectorKey_ExploreRecursiveEdge).CreateMap(0, func(na fluent.MapAssembler) {})
   126  					})
   127  				})
   128  			})
   129  		})
   130  		s, err := ParseContext{}.ParseExploreRecursive(sn)
   131  		qt.Check(t, err, qt.IsNil)
   132  		qt.Check(t, s, qt.Equals, ExploreRecursive{ExploreAll{ExploreRecursiveEdge{}}, ExploreAll{ExploreRecursiveEdge{}}, RecursionLimit{RecursionLimit_Depth, 2}, nil})
   133  	})
   134  
   135  	t.Run("parsing map node with sequence field with valid selector node and limit type none should parse", func(t *testing.T) {
   136  		sn := fluent.MustBuildMap(basicnode.Prototype__Map{}, 2, func(na fluent.MapAssembler) {
   137  			na.AssembleEntry(SelectorKey_Limit).CreateMap(1, func(na fluent.MapAssembler) {
   138  				na.AssembleEntry(SelectorKey_LimitNone).CreateMap(0, func(na fluent.MapAssembler) {})
   139  			})
   140  			na.AssembleEntry(SelectorKey_Sequence).CreateMap(1, func(na fluent.MapAssembler) {
   141  				na.AssembleEntry(SelectorKey_ExploreAll).CreateMap(1, func(na fluent.MapAssembler) {
   142  					na.AssembleEntry(SelectorKey_Next).CreateMap(1, func(na fluent.MapAssembler) {
   143  						na.AssembleEntry(SelectorKey_ExploreRecursiveEdge).CreateMap(0, func(na fluent.MapAssembler) {})
   144  					})
   145  				})
   146  			})
   147  		})
   148  		s, err := ParseContext{}.ParseExploreRecursive(sn)
   149  		qt.Check(t, err, qt.IsNil)
   150  		qt.Check(t, s, qt.Equals, ExploreRecursive{ExploreAll{ExploreRecursiveEdge{}}, ExploreAll{ExploreRecursiveEdge{}}, RecursionLimit{RecursionLimit_None, 0}, nil})
   151  	})
   152  
   153  }
   154  
   155  /*
   156  
   157  {
   158  	exploreRecursive: {
   159  		maxDepth: 3
   160  		sequence: {
   161  			exploreFields: {
   162  				fields: {
   163  					Parents: {
   164  						exploreAll: {
   165  							exploreRecursiveEdge: {}
   166  						}
   167  					}
   168  				}
   169  			}
   170  		}
   171  	}
   172   }
   173  
   174  */
   175  
   176  func TestExploreRecursiveExplore(t *testing.T) {
   177  	recursiveEdge := ExploreRecursiveEdge{}
   178  	maxDepth := int64(3)
   179  	var rs Selector
   180  	t.Run("exploring should traverse until we get to maxDepth", func(t *testing.T) {
   181  		parentsSelector := ExploreAll{recursiveEdge}
   182  		subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []datamodel.PathSegment{datamodel.PathSegmentOfString("Parents")}}
   183  		rs = ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}
   184  		nodeString := `{
   185  			"Parents": [
   186  				{
   187  					"Parents": [
   188  						{
   189  							"Parents": [
   190  								{
   191  									"Parents": []
   192  								}
   193  							]
   194  						}
   195  					]
   196  				}
   197  			]
   198  		}
   199  		`
   200  		nb := basicnode.Prototype__Any{}.NewBuilder()
   201  		err := dagjson.Decode(nb, strings.NewReader(nodeString))
   202  		qt.Check(t, err, qt.IsNil)
   203  		rn := nb.Build()
   204  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   205  		rn, err = rn.LookupByString("Parents")
   206  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil})
   207  		qt.Check(t, err, qt.IsNil)
   208  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   209  		rn, err = rn.LookupByIndex(0)
   210  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   211  		qt.Check(t, err, qt.IsNil)
   212  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   213  
   214  		rn, err = rn.LookupByString("Parents")
   215  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   216  		qt.Check(t, err, qt.IsNil)
   217  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   218  		rn, err = rn.LookupByIndex(0)
   219  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil})
   220  		qt.Check(t, err, qt.IsNil)
   221  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   222  		rn, err = rn.LookupByString("Parents")
   223  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil})
   224  		qt.Check(t, err, qt.IsNil)
   225  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   226  		_, err = rn.LookupByIndex(0)
   227  		qt.Check(t, rs, qt.IsNil)
   228  		qt.Check(t, err, qt.IsNil)
   229  	})
   230  
   231  	t.Run("exploring should traverse indefinitely if no depth specified", func(t *testing.T) {
   232  		parentsSelector := ExploreAll{recursiveEdge}
   233  		subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []datamodel.PathSegment{datamodel.PathSegmentOfString("Parents")}}
   234  		rs = ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil}
   235  		nodeString := `{
   236  			"Parents": [
   237  				{
   238  					"Parents": [
   239  						{
   240  							"Parents": [
   241  								{
   242  									"Parents": []
   243  								}
   244  							]
   245  						}
   246  					]
   247  				}
   248  			]
   249  		}
   250  		`
   251  		nb := basicnode.Prototype__Any{}.NewBuilder()
   252  		err := dagjson.Decode(nb, strings.NewReader(nodeString))
   253  		qt.Check(t, err, qt.IsNil)
   254  		rn := nb.Build()
   255  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   256  		rn, err = rn.LookupByString("Parents")
   257  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil})
   258  		qt.Check(t, err, qt.IsNil)
   259  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   260  		rn, err = rn.LookupByIndex(0)
   261  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil})
   262  		qt.Check(t, err, qt.IsNil)
   263  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   264  		rn, err = rn.LookupByString("Parents")
   265  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil})
   266  		qt.Check(t, err, qt.IsNil)
   267  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   268  		rn, err = rn.LookupByIndex(0)
   269  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil})
   270  		qt.Check(t, err, qt.IsNil)
   271  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   272  		rn, err = rn.LookupByString("Parents")
   273  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil})
   274  		qt.Check(t, err, qt.IsNil)
   275  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   276  		rn, err = rn.LookupByIndex(0)
   277  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_None, 0}, nil})
   278  		qt.Check(t, err, qt.IsNil)
   279  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   280  		_, err = rn.LookupByString("Parents")
   281  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_None, 0}, nil})
   282  		qt.Check(t, err, qt.IsNil)
   283  	})
   284  
   285  	t.Run("exploring should continue till we get to selector that returns nil on explore", func(t *testing.T) {
   286  		parentsSelector := ExploreIndex{recursiveEdge, [1]datamodel.PathSegment{datamodel.PathSegmentOfInt(1)}}
   287  		subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []datamodel.PathSegment{datamodel.PathSegmentOfString("Parents")}}
   288  		rs = ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}
   289  		nodeString := `{
   290  			"Parents": {
   291  			}
   292  		}
   293  		`
   294  		nb := basicnode.Prototype__Any{}.NewBuilder()
   295  		err := dagjson.Decode(nb, strings.NewReader(nodeString))
   296  		qt.Check(t, err, qt.IsNil)
   297  		rn := nb.Build()
   298  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   299  		rn, err = rn.LookupByString("Parents")
   300  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil})
   301  		qt.Check(t, err, qt.IsNil)
   302  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   303  		qt.Check(t, rs, qt.IsNil)
   304  	})
   305  	t.Run("exploring should work when there is nested recursion", func(t *testing.T) {
   306  		parentsSelector := ExploreAll{recursiveEdge}
   307  		sideSelector := ExploreAll{recursiveEdge}
   308  		subTree := ExploreFields{map[string]Selector{
   309  			"Parents": parentsSelector,
   310  			"Side":    ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil},
   311  		}, []datamodel.PathSegment{
   312  			datamodel.PathSegmentOfString("Parents"),
   313  			datamodel.PathSegmentOfString("Side"),
   314  		},
   315  		}
   316  		s := ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}
   317  		nodeString := `{
   318  			"Parents": [
   319  				{
   320  					"Parents": [],
   321  					"Side": {
   322  						"cheese": {
   323  							"whiz": {
   324  							}
   325  						}
   326  					}
   327  				}
   328  			],
   329  			"Side": {
   330  				"real": {
   331  					"apple": {
   332  						"sauce": {
   333  						}
   334  					}
   335  				}
   336  			}
   337  		}
   338  		`
   339  		nb := basicnode.Prototype__Any{}.NewBuilder()
   340  		err := dagjson.Decode(nb, strings.NewReader(nodeString))
   341  		qt.Check(t, err, qt.IsNil)
   342  		n := nb.Build()
   343  
   344  		// traverse down Parent nodes
   345  		rn := n
   346  		rs = s
   347  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   348  		rn, err = rn.LookupByString("Parents")
   349  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil})
   350  		qt.Check(t, err, qt.IsNil)
   351  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   352  		rn, err = rn.LookupByIndex(0)
   353  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   354  		qt.Check(t, err, qt.IsNil)
   355  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   356  		_, err = rn.LookupByString("Parents")
   357  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   358  		qt.Check(t, err, qt.IsNil)
   359  
   360  		// traverse down top level Side tree (nested recursion)
   361  		rn = n
   362  		rs = s
   363  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Side"))
   364  		rn, err = rn.LookupByString("Side")
   365  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil})
   366  		qt.Check(t, err, qt.IsNil)
   367  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("real"))
   368  		rn, err = rn.LookupByString("real")
   369  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil})
   370  		qt.Check(t, err, qt.IsNil)
   371  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("apple"))
   372  		rn, err = rn.LookupByString("apple")
   373  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil})
   374  		qt.Check(t, err, qt.IsNil)
   375  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("sauce"))
   376  		_, err = rn.LookupByString("sauce")
   377  		qt.Check(t, rs, qt.IsNil)
   378  		qt.Check(t, err, qt.IsNil)
   379  
   380  		// traverse once down Parent (top level recursion) then down Side tree (nested recursion)
   381  		rn = n
   382  		rs = s
   383  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   384  		rn, err = rn.LookupByString("Parents")
   385  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil})
   386  		qt.Check(t, err, qt.IsNil)
   387  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   388  		rn, err = rn.LookupByIndex(0)
   389  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   390  		qt.Check(t, err, qt.IsNil)
   391  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Side"))
   392  		rn, err = rn.LookupByString("Side")
   393  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   394  		qt.Check(t, err, qt.IsNil)
   395  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("cheese"))
   396  		rn, err = rn.LookupByString("cheese")
   397  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   398  		qt.Check(t, err, qt.IsNil)
   399  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("whiz"))
   400  		_, err = rn.LookupByString("whiz")
   401  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, ExploreRecursive{sideSelector, sideSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   402  		qt.Check(t, err, qt.IsNil)
   403  	})
   404  	t.Run("exploring should work with explore union and recursion", func(t *testing.T) {
   405  		parentsSelector := ExploreUnion{[]Selector{ExploreAll{Matcher{}}, ExploreIndex{recursiveEdge, [1]datamodel.PathSegment{datamodel.PathSegmentOfInt(0)}}}}
   406  		subTree := ExploreFields{map[string]Selector{"Parents": parentsSelector}, []datamodel.PathSegment{datamodel.PathSegmentOfString("Parents")}}
   407  		rs = ExploreRecursive{subTree, subTree, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil}
   408  		nodeString := `{
   409  			"Parents": [
   410  				{
   411  					"Parents": [
   412  						{
   413  							"Parents": [
   414  								{
   415  									"Parents": []
   416  								}
   417  							]
   418  						}
   419  					]
   420  				}
   421  			]
   422  		}
   423  		`
   424  		nb := basicnode.Prototype__Any{}.NewBuilder()
   425  		err := dagjson.Decode(nb, strings.NewReader(nodeString))
   426  		qt.Check(t, err, qt.IsNil)
   427  		rn := nb.Build()
   428  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   429  		rn, err = rn.LookupByString("Parents")
   430  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth}, nil})
   431  		qt.Check(t, err, qt.IsNil)
   432  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   433  		rn, err = rn.LookupByIndex(0)
   434  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, ExploreUnion{[]Selector{Matcher{}, subTree}}, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   435  		qt.Check(t, err, qt.IsNil)
   436  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfString("Parents"))
   437  
   438  		rn, err = rn.LookupByString("Parents")
   439  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, parentsSelector, RecursionLimit{RecursionLimit_Depth, maxDepth - 1}, nil})
   440  		qt.Check(t, err, qt.IsNil)
   441  		rs, _ = rs.Explore(rn, datamodel.PathSegmentOfInt(0))
   442  		_, err = rn.LookupByIndex(0)
   443  		qt.Check(t, rs, deepEqualsAllowAllUnexported, ExploreRecursive{subTree, ExploreUnion{[]Selector{Matcher{}, subTree}}, RecursionLimit{RecursionLimit_Depth, maxDepth - 2}, nil})
   444  		qt.Check(t, err, qt.IsNil)
   445  	})
   446  }