Integrated resolve-references into the preprocessor.

This commit is contained in:
Ryan Leonard 2017-03-17 10:48:32 -07:00
parent f41c5d5440
commit edccddb1c9
6 changed files with 68 additions and 33 deletions

View File

@ -1,12 +1,18 @@
var path = require('path'),
_ = require('lodash');
var httpMethods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch'];
var httpMethods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', '$ref'];
// Preprocessor for the Swagger JSON so that some of the logic can be taken
// out of the template.
module.exports = function(options, specData) {
if(!options.specFile) {
console.warn("[WARNING] preprocessor must be given 'options.specFile'. Defaulting to 'cwd'.");
options.specFile = process.cwd();
}
specData["x-spec-path"] = options.specFile;
var copy = _.cloneDeep(specData);
var tagsByName = _.keyBy(copy.tags, 'name');
@ -66,19 +72,8 @@ module.exports = function(options, specData) {
copy.showTagSummary = copy.tags.length > 1
}
// Resolve references to external files.
function resolveReferences(obj) {
for (var k in obj) {
var val = obj[k];
if (typeof val === "object" && val !== null) {
resolveReferences(val);
}
else {
// #TODO: resolve references
}
}
}
resolveReferences(copy);
var replaceRefs = require("./resolve-references").replaceRefs;
replaceRefs(path.dirname(copy["x-spec-path"]), copy, copy, "");
return copy;
}

View File

@ -17,7 +17,8 @@ var TreeWalkError = require("./errors").TreeWalkError;
* @return {boolean} `true` if the reference points to the current file.
*/
function localReference(ref) {
return typeof ref.trim === "function" && ref.trim().indexOf("#") === 0;
return (typeof ref.trim === "function" && ref.trim().indexOf("#") === 0) ||
(typeof ref.indexOf === "function" && ref.indexOf("#") === 0);
}
/**
@ -76,7 +77,9 @@ function replaceReference(cwd, top, obj, context) {
var external = pathUtils.relative(path.dirname(top["x-spec-path"]), ref);
var referenced = module.exports.fetchReference(ref);
referenced["x-external"] = external;
module.exports.replaceRefs(path.dirname(ref), top, referenced, context);
if(typeof referenced === "object") {
module.exports.replaceRefs(path.dirname(ref), top, referenced, context);
}
//TODO use other merge mechanisms besides `Object.assign(obj, ...)` depending on the path.
Object.assign(obj, referenced);
delete obj.$ref;
@ -97,7 +100,12 @@ function replaceReference(cwd, top, obj, context) {
*/
function replaceRefs(cwd, top, obj, context) {
if(typeof obj !== "object") { return; }
if(typeof cwd !== "string" || cwd.length < 1) {
throw new Error("replaceRefs must be given a 'cwd'. Given '"+cwd+"'");
}
if(typeof obj !== "object") {
console.warn("[WARN] replaceRefs() must be given an object for 'obj'. Given "+typeof obj+" ("+obj+")");
return; }
if(obj.$ref) {
throw new TreeWalkError("Walked too deep in the tree looking for references. Can't resolve reference " +
@ -111,11 +119,18 @@ function replaceRefs(cwd, top, obj, context) {
if(val.$ref) {
if(localReference(val.$ref)) {
if(cwd === top["x-spec-path"]) { continue; }
throw new Error("Can't deal with internal references in external files yet.");
if((cwd === top["x-spec-path"]) || (cwd === path.dirname(top["x-spec-path"]))) { continue; }
throw new Error(
"Can't deal with internal references in external files yet. Got: '"+val.$ref+"'.");
}
module.exports.replaceReference(cwd, top, val, context);
try {
module.exports.replaceReference(cwd, top, val, context);
}
catch (e) {
console.error("Couldn't replace reference to '"+val.$ref+"' from '"+cwd+"'. Reference path: #/"+context);
throw e;
}
continue;
}

View File

@ -16,6 +16,8 @@
<button class="close-button" aria-label="Close menu" type="button" data-drawer-close>
<span aria-hidden="true">×</span>
</button>
<div id="logo">
<img src="images/cheese.png" title="Cheese Store" /> </div>
<nav id="nav" role="navigation">
<h5>API Reference</h5>
<a href="#introduction">Introduction</a>

16
test/fixtures/User.yml vendored Normal file
View File

@ -0,0 +1,16 @@
type: object
required:
- id
- name
properties:
id:
type: string
description: The user's unique identifer.
name:
type: string
description: The user's full name.
email:
type: string
description: |
The user's email address.
Only included in documents for the current user.

View File

@ -26,26 +26,28 @@ describe("preprocessor referencing", function() {
},
},
});
processed = preprocessor({}, spec);
processed = preprocessor({
specFile: __dirname + "/spec.json",
}, spec);
});
it("should include the path section", function() {
processed.paths.should.have.key("/");
processed.paths.should.have.property("/");
processed.paths["/"].should.be.an.object;
});
it.skip("should include the imported paths", function() {
processed.paths["/"].should.have.key("get");
processed.paths["/"].should.have.key("post");
it("should include the imported paths", function() {
processed.paths["/"].should.have.property("get");
processed.paths["/"].should.have.property("post");
});
it.skip("should include the reference path ('/' path)", function() {
processed.paths["/"].should.have.property("x-external", "./fixtures/basic-path.yaml");
it("should include the reference path ('/' path)", function() {
processed.paths["/"].should.have.property("x-external", "fixtures/basic-path.yaml");
});
it.skip("should include the reference path ('get', 'post')", function() {
processed.paths["/"].get.should.have.property("x-external", "./fixtures/basic-path.yaml#get");
processed.paths["/"].post.should.have.property("x-external", "./fixtures/basic-path.yaml#post");
processed.paths["/"].get.should.have.property("x-external", "fixtures/basic-path.yaml#get");
processed.paths["/"].post.should.have.property("x-external", "fixtures/basic-path.yaml#post");
});
});
@ -61,11 +63,13 @@ describe("preprocessor referencing", function() {
responses: { "200": {
description: "Current user",
schema: {
"$ref": "fixtures/User.yaml"
"$ref": "fixtures/User.yml"
}
}
}}}}});
processed = preprocessor({}, spec);
processed = preprocessor({
specFile: __dirname + "/spec.json",
}, spec);
response = processed.paths["/"].get.responses["200"];
});
@ -78,10 +82,11 @@ describe("preprocessor referencing", function() {
});
it.skip("should update '$ref'", function() {
response.schema.should.have.key("$ref", "#/definitions/.%2Ffixtures%2FUser.yaml");
response.schema.should.have.property("$ref", "#/definitions/.%2Ffixtures%2FUser.yaml");
});
it.skip("should include the definition globally", function() {
processed.should.have.property("definitions");
processed.definitions.should.be.an.object;
processed.definitions.should.have.property("./fixtures/User.yaml");
var schema = processed.definitions["./fixtures/User.yaml"];

View File

@ -12,7 +12,9 @@ describe("preprocessor", function() {
beforeEach(function() {
spec = Object.assign({}, minimal);
processed = preprocessor({}, spec);
processed = preprocessor({
specFile: __dirname + "/spec.json"
}, spec);
});
describe("with minimal spec", function() {