Skip to content

Commit faddc72

Browse files
Major refactoring of $Refs, $Ref, and Pointer
1 parent 1299df7 commit faddc72

14 files changed

Lines changed: 1028 additions & 671 deletions

File tree

dist/ref-parser.js

Lines changed: 465 additions & 335 deletions
Large diffs are not rendered by default.

dist/ref-parser.js.map

Lines changed: 15 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/ref-parser.min.js

Lines changed: 107 additions & 98 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/ref-parser.min.js.map

Lines changed: 15 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/bundle.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
*/
77
'use strict';
88

9+
var $Ref = require('./ref'),
10+
util = require('./util'),
11+
url = require('url');
12+
913
module.exports = bundle;
1014

1115
/**
@@ -17,5 +21,53 @@ module.exports = bundle;
1721
* @param {$RefParserOptions} options
1822
*/
1923
function bundle(parser, options) {
20-
throw new Error('The "bundle" method is not implemented yet. It will be implemented before the final alpha.');
24+
util.debug('Bundling $ref pointers in %s', parser._basePath);
25+
26+
var $refs = parser.$refs;
27+
var basePath = util.path.stripHash(parser._basePath);
28+
Object.keys($refs._$refs).forEach(function(key) {
29+
var $ref = $refs._$refs[key];
30+
31+
if (!$ref.pathFromRoot) {
32+
// This is the root of the JSON schema, so we don't need to do anything
33+
// since all of its $refs are already relative to the schema root
34+
return;
35+
}
36+
37+
// Crawl the value, re-mapping its pointer
38+
crawl($ref.value, $ref.path + '#', $ref.pathFromRoot, parser.$refs, options);
39+
40+
// Replace the original $ref with the resolved value
41+
//$refs.set(basePath + $ref.pathFromRoot, $ref.value, options);
42+
});
43+
}
44+
45+
/**
46+
*
47+
* @param obj
48+
* @param path
49+
* @param pointer
50+
* @param $refs
51+
* @param options
52+
*/
53+
function crawl(obj, path, pointer, $refs, options) {
54+
if (obj && typeof(obj) === 'object') {
55+
Object.keys(obj).forEach(function(key) {
56+
var keyPath = path + '/' + key;
57+
var value = obj[key];
58+
59+
if ($Ref.is$Ref(value)) {
60+
// We found a $ref, so resolve it
61+
util.debug('Bundling $ref pointer "%s" at %s', value.$ref, keyPath);
62+
var $refPath = url.resolve(path, value.$ref);
63+
var resolved$Ref = $refs._resolve($refPath, options);
64+
var $ref = resolved$Ref.$ref;
65+
66+
//value.$ref = $ref.pathFromRoot
67+
}
68+
else {
69+
crawl(value, keyPath, pointer, $refs, options);
70+
}
71+
});
72+
}
2173
}

lib/dereference.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function crawl(obj, path, parents, $refs, options) {
3535
var keyPath = path + '/' + key;
3636
var value = obj[key];
3737

38-
if ($Ref.isAllowed(value, options)) {
38+
if ($Ref.isAllowed$Ref(value, options)) {
3939
// We found a $ref, so resolve it
4040
util.debug('Dereferencing $ref pointer "%s" at %s', value.$ref, keyPath);
4141
var $refPath = url.resolve(path, value.$ref);

lib/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
var Promise = require('./promise'),
44
Options = require('./options'),
55
$Refs = require('./refs'),
6+
$Ref = require('./ref'),
67
read = require('./read'),
78
resolve = require('./resolve'),
89
bundle = require('./bundle'),
@@ -37,6 +38,9 @@ function $RefParser() {
3738
this.$refs = new $Refs();
3839

3940
/**
41+
* The file path or URL of the main JSON schema file.
42+
* This will be empty if the schema was passed as an object rather than a path.
43+
*
4044
* @type {string}
4145
* @protected
4246
*/
@@ -75,6 +79,8 @@ $RefParser.prototype.parse = function(schema, options, callback) {
7579
// The schema is an object, not a path/url
7680
this.schema = args.schema;
7781
this._basePath = '';
82+
var $ref = new $Ref(this.$refs, this._basePath);
83+
$ref.setValue(this.schema, args.options);
7884

7985
util.doCallback(args.callback, null, this.schema);
8086
return Promise.resolve(this.schema);
@@ -89,8 +95,8 @@ $RefParser.prototype.parse = function(schema, options, callback) {
8995
var me = this;
9096

9197
// Resolve the absolute path of the schema
92-
args.schema = url.resolve(util.cwd(), args.schema);
93-
this._basePath = util.stripHash(args.schema);
98+
args.schema = url.resolve(util.path.cwd(), args.schema);
99+
this._basePath = util.path.stripHash(args.schema);
94100

95101
// Read the schema file/url
96102
return read(args.schema, this.$refs, args.options)

lib/pointer.js

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
'use strict';
2+
3+
module.exports = Pointer;
4+
5+
var $Ref = require('./ref'),
6+
util = require('./util'),
7+
url = require('url'),
8+
ono = require('ono'),
9+
escapedSlash = /~1/g,
10+
escapedTilde = /~0/g;
11+
12+
/**
13+
* This class represents a single JSON pointer and its resolved value.
14+
*
15+
* @param {$Ref} $ref
16+
* @param {string} path
17+
* @constructor
18+
*/
19+
function Pointer($ref, path) {
20+
/**
21+
* The {@link $Ref} object that contains this {@link Pointer} object.
22+
* @type {$Ref}
23+
*/
24+
this.$ref = $ref;
25+
26+
/**
27+
* The file path or URL, containing the JSON pointer in the hash.
28+
* This path is relative to the path of the main JSON schema file.
29+
* @type {string}
30+
*/
31+
this.path = path;
32+
33+
/**
34+
* The value of the JSON pointer.
35+
* Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
36+
* @type {?*}
37+
*/
38+
this.value = undefined;
39+
}
40+
41+
/**
42+
* Resolves the value of a nested property within the given object.
43+
*
44+
* @param {*} obj - The object that will be crawled
45+
* @param {$RefParserOptions} [options]
46+
*
47+
* @returns {Pointer}
48+
* Returns a JSON pointer whose {@link Pointer#value} is the resolved value.
49+
* If resolving this value required resolving other JSON references, then
50+
* the {@link Pointer#$ref} and {@link Pointer#path} will reflect the resolution path
51+
* of the resolved value.
52+
*/
53+
Pointer.prototype.resolve = function(obj, options) {
54+
var tokens = Pointer.parse(this.path);
55+
this.value = obj;
56+
57+
// Crawl the value, one token at a time
58+
for (var i = 0; i < tokens.length; i++) {
59+
resolveIf$Ref(this, options);
60+
61+
var token = tokens[i];
62+
if (this.value[token] === undefined) {
63+
throw ono.syntax('Error resolving $ref pointer "%s". \nToken "%s" does not exist.', path, token);
64+
}
65+
else {
66+
this.value = this.value[token];
67+
}
68+
}
69+
70+
// Resolve the final value
71+
resolveIf$Ref(this, options);
72+
return this;
73+
};
74+
75+
/**
76+
* Sets the value of a nested property within the given object.
77+
*
78+
* @param {*} obj - The object that will be crawled
79+
* @param {*} value - the value to assign
80+
* @param {$RefParserOptions} [options]
81+
*
82+
* @returns {*}
83+
* Returns the modified object, or an entirely new object if the entire object is overwritten.
84+
*/
85+
Pointer.prototype.set = function(obj, value, options) {
86+
var tokens = Pointer.parse(this.path);
87+
88+
if (tokens.length === 0) {
89+
// There are no tokens, replace the entire object with the new value
90+
this.value = value;
91+
return value;
92+
}
93+
94+
// Crawl the object, one token at a time
95+
this.value = obj;
96+
for (var i = 0; i < tokens.length - 1; i++) {
97+
resolveIf$Ref(this, options);
98+
99+
var token = tokens[i];
100+
if (this.value && this.value[token] !== undefined) {
101+
// The token exists
102+
this.value = this.value[token];
103+
}
104+
else {
105+
// The token doesn't exist, so create it
106+
this.value = setValue(this, token, {});
107+
}
108+
}
109+
110+
// Set the value of the final token
111+
resolveIf$Ref(this, options);
112+
token = tokens[tokens.length - 1];
113+
setValue(this, token, value);
114+
115+
// Return the updated object
116+
return obj;
117+
};
118+
119+
/**
120+
* Parses a JSON pointer (or a path containing a JSON pointer in the hash)
121+
* and returns an array of the pointer's tokens.
122+
* (e.g. "schema.json#/definitions/person/name" => ["definitions", "person", "name"])
123+
*
124+
* The pointer is parsed according to RFC 6901
125+
* {@link https://tools.ietf.org/html/rfc6901#section-3}
126+
*
127+
* @param {string} path
128+
* @returns {string[]}
129+
*/
130+
Pointer.parse = function(path) {
131+
// Get the JSON pointer from the path's hash
132+
var pointer = util.path.getHash(path).substr(1);
133+
134+
// If there's no pointer, then there are no tokens,
135+
// so return an empty array
136+
if (!pointer) {
137+
return [];
138+
}
139+
140+
// Split into an array
141+
pointer = pointer.split('/');
142+
143+
// Decode each part, according to RFC 6901
144+
for (var i = 0; i < pointer.length; i++) {
145+
pointer[i] = pointer[i].replace(escapedSlash, '/').replace(escapedTilde, '~');
146+
}
147+
148+
if (pointer[0] !== '') {
149+
throw ono.syntax('Invalid $ref pointer "%s". Pointers must begin with "#/"', pointer);
150+
}
151+
152+
return pointer.slice(1);
153+
};
154+
155+
/**
156+
* If the given pointer's {@link Pointer#value} is a JSON reference,
157+
* then the reference is resolved and {@link Pointer#value} is replaced with the resolved value.
158+
* In addition, {@link Pointer#path} and {@link Pointer#$ref} are updated to reflect the
159+
* resolution path of the new value.
160+
*
161+
* @param {Pointer} pointer
162+
* @param {$RefParserOptions} [options]
163+
*/
164+
function resolveIf$Ref(pointer, options) {
165+
// Is the value a JSON reference? (and allowed?)
166+
if ($Ref.isAllowed$Ref(pointer.value, options)) {
167+
var $refPath = url.resolve(pointer.path, pointer.value.$ref);
168+
169+
// Is the value a reference to itself? If so, then there's nothing to do.
170+
if ($refPath !== pointer.path) {
171+
// Resolve the reference
172+
var resolved = pointer.$ref.$refs._resolve($refPath);
173+
pointer.$ref = resolved.$ref;
174+
pointer.path = resolved.path; // pointer.path = $refPath ???
175+
pointer.value = resolved.value;
176+
}
177+
}
178+
}
179+
180+
/**
181+
* Sets the specified token value of the {@link Pointer#value}.
182+
*
183+
* The token is evaluated according to RFC 6901.
184+
* {@link https://tools.ietf.org/html/rfc6901#section-4}
185+
*
186+
* @param {Pointer} pointer - The JSON Pointer whose value will be modified
187+
* @param {string} token - A JSON Pointer token that indicates how to modify `obj`
188+
* @param {*} value - The value to assign
189+
* @returns {*} - Returns the assigned value
190+
*/
191+
function setValue(pointer, token, value) {
192+
if (pointer.value && typeof(pointer.value) === 'object') {
193+
if (token === '-' && Array.isArray(pointer.value)) {
194+
pointer.value.push(value);
195+
}
196+
else {
197+
pointer.value[token] = value;
198+
}
199+
}
200+
else {
201+
throw ono.syntax('Error assigning $ref pointer "%s". \nCannot set "%s" of a non-object.', pointer.path, token);
202+
}
203+
return value;
204+
}

lib/read.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ module.exports = read;
2626
function read(path, $refs, options) {
2727
try {
2828
// Remove the URL fragment, if any
29-
path = util.stripHash(path);
29+
path = util.path.stripHash(path);
3030
util.debug('Reading %s', path);
3131

3232
// Return from cache, if possible
@@ -38,8 +38,7 @@ function read(path, $refs, options) {
3838
}
3939

4040
// Add a placeholder $ref to the cache, so we don't read this URL multiple times
41-
$ref = new $Ref(path);
42-
$refs._set$Ref($ref);
41+
$ref = new $Ref($refs, path);
4342

4443
// Read and return the $ref
4544
return read$Ref($ref, options);
@@ -94,11 +93,11 @@ function read$Ref($ref, options) {
9493
* The promise resolves with the raw file contents.
9594
*/
9695
function read$RefFile($ref, options) {
97-
if (process.browser || util.isUrl($ref.path)) {
96+
if (process.browser || util.path.isUrl($ref.path)) {
9897
return;
9998
}
10099

101-
$ref.type = 'fs';
100+
$ref.pathType = 'fs';
102101
return new Promise(function(resolve, reject) {
103102
var file;
104103
try {
@@ -141,11 +140,11 @@ function read$RefUrl($ref, options) {
141140
var u = url.parse($ref.path);
142141

143142
if (u.protocol === 'http:') {
144-
$ref.type = 'http';
143+
$ref.pathType = 'http';
145144
return download(http, u, options);
146145
}
147146
else if (u.protocol === 'https:') {
148-
$ref.type = 'https';
147+
$ref.pathType = 'https';
149148
return download(https, u, options);
150149
}
151150
}

0 commit comments

Comments
 (0)