|
| 1 | +# Tree |
| 2 | + |
| 3 | +Currently, the tree may contain up to 5 kinds of nodes. |
| 4 | + |
| 5 | +- RootNode - a single tree cannot have more than a single instance of RootNode |
| 6 | +- RegularNode and MirroredRegularNode - used for everything other than a schema fragment containing a `$ref` |
| 7 | +- ReferenceNode and MirroredReferenceNode - used only for fragments with a `$ref` included |
| 8 | + |
| 9 | +## Mirroring - mirrored nodes |
| 10 | + |
| 11 | +Although the name might sound a bit odd, it is exactly what these nodes are. They literally represent their standard |
| 12 | +variant. Mirrored nodes are used to represent the same portion of schema fragment. However, there are properties that do |
| 13 | +vary. These are `id` and `children`. Each node has always unique `id`, regardless of their type. Moreover, to avoid |
| 14 | +entering some infinite loop, `children` (if any) are processed shallowly. |
| 15 | + |
| 16 | +For a more end-to-end example, I encourage you to execute the sample provided below in [circular $refs](#circular-refs) |
| 17 | +section. |
| 18 | + |
| 19 | +### Caution |
| 20 | + |
| 21 | +**DO NOT** use `instanceof` checks for verifying whether a particular node is of a regular kind. Each regular node has |
| 22 | +`Symbol.hasInstance` trait implemented that will return true if you use a regular or mirrored sort of node on the RHS of |
| 23 | +condition. |
| 24 | + |
| 25 | +``` |
| 26 | +myNode instanceof RegularNode; // DO NOT DO IT |
| 27 | +
|
| 28 | +if (!isMirroredNode(myNode)) { |
| 29 | + // this is okay |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +Do note that the fact a mirrored node exists in tree does not necessarily mean the tree has no boundaries. Mirrored |
| 34 | +nodes are also used for fragments that were already processed previously. |
| 35 | + |
| 36 | +## $refs |
| 37 | + |
| 38 | +### Resolving |
| 39 | + |
| 40 | +By default, we attempt to resolve all local $refs leveraging `resolveInlineRef` util from `@stoplight/json`. If you wish |
| 41 | +to provide a custom resolver, you can supply a `resolver` option to a tree. |
| 42 | + |
| 43 | +```js |
| 44 | +import { SchemaTree } from '@stoplight/json-schema-tree'; |
| 45 | + |
| 46 | +const tree = new SchemaTree(schema, { |
| 47 | + refResolver(ref, propertyPath, fragment) { |
| 48 | + // ref has a pointer and a source |
| 49 | + // do something or throw if resolving cannot be performed |
| 50 | + }, |
| 51 | +}); |
| 52 | +``` |
| 53 | + |
| 54 | +Retaining all $refs in tree is possible as well. In such case, you should pass `null` as the value of `refResolver`. |
| 55 | + |
| 56 | +```js |
| 57 | +import { SchemaTree } from '@stoplight/json-schema-tree'; |
| 58 | + |
| 59 | +const tree = new SchemaTree(schema, { |
| 60 | + refResolver: null, // I will not try to resolve any $refs |
| 61 | +}); |
| 62 | +``` |
| 63 | + |
| 64 | +It is worth mentioning that we do not resolve the whole schema upfront. $refs are resolved only when they are actually |
| 65 | +seen (processed) during the traversal. |
| 66 | + |
| 67 | +### Circular $refs |
| 68 | + |
| 69 | +If your schema contains circular $refs, certain branches of tree will have mirrored nodes as some of their leaf nodes. |
| 70 | +That said, the size of the tree will be infinite. |
| 71 | + |
| 72 | +#### Example schema with indirect circular references |
| 73 | + |
| 74 | +```json |
| 75 | +{ |
| 76 | + "type": "object", |
| 77 | + "properties": { |
| 78 | + "foo": { |
| 79 | + "type": "array", |
| 80 | + "items": { |
| 81 | + "type": "object", |
| 82 | + "properties": { |
| 83 | + "user": { |
| 84 | + "$ref": "#/properties/bar" |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + }, |
| 89 | + "bar": { |
| 90 | + "$ref": "#/properties/baz" |
| 91 | + }, |
| 92 | + "baz": { |
| 93 | + "$ref": "#/properties/foo" |
| 94 | + } |
| 95 | + } |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +```js |
| 100 | +import { SchemaTree } from '@stoplight/json-schema-tree'; |
| 101 | + |
| 102 | +const tree = new SchemaTree(schema); |
| 103 | +tree.populate(); |
| 104 | + |
| 105 | +expect(tree.root.children[0].children[0].children[0].children[0].children[0].children[0].path).toEqual([ |
| 106 | + 'properties', |
| 107 | + 'foo', |
| 108 | + 'items', |
| 109 | + 'properties', |
| 110 | + 'user', |
| 111 | + 'items', |
| 112 | + 'properties', |
| 113 | + 'user', |
| 114 | +]); |
| 115 | +expect(tree.root.children[0].children[2].children[0].children[0].children[0].children[0].path).toEqual([ |
| 116 | + 'properties', |
| 117 | + 'baz', |
| 118 | + 'items', |
| 119 | + 'properties', |
| 120 | + 'user', |
| 121 | + 'items', |
| 122 | + 'properties', |
| 123 | + 'user', |
| 124 | +]); |
| 125 | + |
| 126 | +// etc. |
| 127 | +``` |
| 128 | + |
| 129 | +**CAUTION** |
| 130 | + |
| 131 | +Always use `isMirroredNode` guard when traversing the processed tree. If you forget to do it, you will enter recursive |
| 132 | +loops at times. |
| 133 | + |
| 134 | +```js |
| 135 | +function traverse(node) { |
| 136 | + if (isMirroredNode(node)) { |
| 137 | + // alright, it's a mirrored node, I can do something with it |
| 138 | + } else { |
| 139 | + // continue |
| 140 | + } |
| 141 | +} |
| 142 | +``` |
0 commit comments