Skip to content
This repository was archived by the owner on Jul 13, 2023. It is now read-only.

Commit cd9ccc9

Browse files
author
Marc MacLeod
authored
feat: refMap (#6)
* feat: add refMap to resolve result * test: cover and demonstrate all immutability rules * docs(readme): add more examples * docs(types): more comments * refactor: remove debug prop
1 parent cb25193 commit cd9ccc9

File tree

7 files changed

+409
-143
lines changed

7 files changed

+409
-143
lines changed

README.md

Lines changed: 158 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ Recursively resolves JSON pointers and remote authorities.
1111

1212
- **Performant**: Hot paths are memoized, remote authorities are resolved concurrently, and the minimum surface area is crawled and resolved.
1313
- **Caching**: Results from remote authorities are cached.
14-
- **Immutable**: The original object is not changed, and structural sharing is used to only change relevant bits.
15-
- **Reference equality:** Pointers to the same location will resolve to the same object in memory.
14+
- **Immutable**: The original object is not changed, and structural sharing is used to only change relevant bits. [example test](src/__tests__/resolver.spec.ts#L139-L143)
15+
- **Reference equality:** Pointers to the same location will resolve to the same object in memory. [example test](src/__tests__/resolver.spec.ts#L145)
1616
- **Flexible:** Bring your own readers for `http://`, `file://`, `mongo://`, `custom://`... etc.
1717
- **Reliable:** Well tested to handle all sorts of circular reference edge cases.
1818

@@ -29,7 +29,40 @@ yarn add @stoplight/json-ref-resolver
2929

3030
All relevant types and options can be found in [src/types.ts](src/types.ts) or in the TSDoc.
3131

32-
#### Basic Local Resolution
32+
```ts
33+
// Import the Resolver class.
34+
import { Resolver } from "@stoplight/json-ref-resolver";
35+
36+
/**
37+
* Create a Resolver instance. Resolve can be called on this instance multiple times to take advantage of caching.
38+
*
39+
* @param globalOpts {IResolverOpts} [{}]
40+
*
41+
* These options are used on every resolve call for this resolver instance.
42+
*
43+
* See `IResolverOpts` interface defined in [src/types.ts](src/types.ts) for available options.
44+
*
45+
* @return IResolver
46+
*/
47+
const resolver = new Resolver(globalOpts);
48+
49+
/**
50+
* Resolve the passed in object, replacing all references.
51+
52+
* @param resolveOpts {any} - The object to resolve.
53+
54+
* @param resolveOpts {IResolveOpts} [{}]
55+
*
56+
* These options override any globalOpts specified on the resolver instance, and only apply during this resolve call.
57+
*
58+
* See `IResolveOpts` interface defined in [src/types.ts](src/types.ts) for available options.
59+
*
60+
* @return IResolveResult - see [src/types.ts](src/types.ts) for interface definition.
61+
*/
62+
const resolved = await resolver.resolve(sourceObj, resolveOpts);
63+
```
64+
65+
#### Example: Basic Local Resolution
3366

3467
```ts
3568
import { Resolver } from "@stoplight/json-ref-resolver";
@@ -46,32 +79,81 @@ const resolved = await resolver.resolve({
4679
}
4780
});
4881

49-
console.log(resolved.result);
82+
// ==> result is the original object, with local refs resolved and replaced
83+
expect(resolved.result).toEqual({
84+
user: {
85+
name: "json"
86+
},
87+
models: {
88+
user: {
89+
name: "john"
90+
}
91+
}
92+
});
93+
```
5094

51-
// ==> outputs the original object, with local refs resolved and replaced
52-
//
53-
// {
54-
// user: {
55-
// name: 'json'
56-
// },
57-
// models: {
58-
// user: {
59-
// name: 'john'
60-
// }
61-
// }
62-
// }
95+
#### Example: Resolve a Subset of the Source
96+
97+
This will resolve the minimal number of references needed for the given target, and return the target.
98+
99+
In the example below, the address reference (`https://slow-website.com/definitions#/address`) will NOT be resolved, since
100+
it is not needed to resolve the `#/user` jsonPointer target we have specified. However, `#/models/user/card` IS resolved since
101+
it is needed in order to full resolve the `#/user` property.
102+
103+
```ts
104+
import { Resolver } from "@stoplight/json-ref-resolver";
105+
106+
const resolver = new Resolver();
107+
const resolved = await resolver.resolve(
108+
{
109+
user: {
110+
$ref: "#/models/user"
111+
},
112+
address: {
113+
$ref: "https://slow-website.com/definitions#/address"
114+
},
115+
models: {
116+
user: {
117+
name: "john",
118+
card: {
119+
$ref: "#/models/card"
120+
}
121+
},
122+
card: {
123+
type: "visa"
124+
}
125+
}
126+
},
127+
{
128+
jsonPointer: "#/user"
129+
}
130+
);
131+
132+
// ==> result is the target object, with refs resolved and replaced
133+
expect(resolved.result).toEqual({
134+
name: "json",
135+
card: {
136+
type: "visa"
137+
}
138+
});
63139
```
64140

65-
#### With Authority Readers
141+
#### Example: Resolving Remote References with Readers
142+
143+
By default only local references (those that point to values inside of the original source) are resolved.
144+
145+
In order to resolve remote authorities (file, http, etc) you must provide readers for each authority scheme.
146+
147+
Readers are keyed by scheme, receive the URI to fetch, and must return the fetched data.
66148

67149
```ts
68150
import { Resolver } from "@stoplight/json-ref-resolver";
69151

70152
// some example http library
71-
const request = require("request");
153+
import * as axios from "axios";
72154

73155
// if we're in node, we create a file reader with fs
74-
const fs = require("fs");
156+
import * as fs from "fs";
75157

76158
// create our resolver instance
77159
const resolver = new Resolver({
@@ -80,14 +162,17 @@ const resolver = new Resolver({
80162
// this reader will be invoked for refs with the https protocol
81163
https: {
82164
async read(ref: uri.URI) {
83-
return request(ref.toString());
165+
return axios({
166+
method: "get",
167+
url: String(ref)
168+
});
84169
}
85170
},
86171

87172
// this reader will be invoked for refs with the file protocol
88173
file: {
89174
async read(ref: uri.URI) {
90-
return fs.read(ref.toString());
175+
return fs.read(String(ref));
91176
}
92177
}
93178
}
@@ -104,22 +189,61 @@ const resolved = await resolver.resolve({
104189
}
105190
});
106191

107-
console.log(resolved.result);
108-
109-
// ==> outputs the original object, with refs resolved and replaced
110-
//
111-
// {
112-
// definitions: {
113-
// someOASFile: {
114-
// // ... the data located in the relative file `./main.oas2.yml` and inner json path `#/definitions/user`
115-
// },
116-
// someMarkdownFile: {
117-
// // ... the data located at the url `https://foo.com/intro.md`
118-
// }
119-
// },
192+
// ==> result is the original object, with refs resolved and replaced
193+
expect(resolved.result).toEqual({
194+
definitions: {
195+
someOASFile: {
196+
// ... the data located in the relative file `./main.oas2.yml` and inner json path `#/definitions/user`
197+
},
198+
someMarkdownFile: {
199+
// ... the data located at the url `https://foo.com/intro.md`
200+
}
201+
}
202+
});
203+
```
204+
205+
#### Example: Resolving Relative Remote References with the Authority Option
206+
207+
If there are relative remote references (for example, a relative file path `../model.json`), then the location of the source
208+
data must be specified via the `authority` resolve option. Relative references will be resolved against this authority.
209+
210+
```ts
211+
import { Resolver } from "@stoplight/json-ref-resolver";
212+
import * as fs from "fs";
213+
import * as URI from "urijs";
214+
215+
const resolver = new Resolver({
216+
readers: {
217+
file: {
218+
async read(ref: uri.URI) {
219+
return fs.read(String(ref));
220+
}
221+
}
222+
}
223+
});
224+
225+
const sourcePath = "/specs/api.json";
226+
const sourceData = fs.readSync(sourcePath);
227+
// sourceData === {
228+
// user: {
229+
// $ref: "../models/user.json"
230+
// }
120231
// }
232+
233+
const resolved = await resolver.resolve(sourceData, {
234+
// Indicate where the `sourceData` being resolved lives, so that relative remote references can be fetched and resolved.
235+
authority: new URI(sourcePath)
236+
});
237+
238+
expect(resolved.result).toEqual({
239+
user: {
240+
// ... the user object defined in `../models/user.json`
241+
}
242+
});
121243
```
122244

245+
In the above example, the user \$ref will resolve to `/models/user.json`, because `../models/user.json` is resolved against the authority of the current document (which was indicated at `/specs/api.json`). Relative references will not work if the source document has no authority set.
246+
123247
### Contributing
124248

125249
1. Clone repo.

0 commit comments

Comments
 (0)