From 85f842eafa444191eb1c82f4ee02cfdab5a55de5 Mon Sep 17 00:00:00 2001 From: Christian Holzberger Date: Tue, 27 Dec 2016 12:01:22 +0100 Subject: [PATCH 1/7] add vscode to ignore file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 14b89da..505276d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ pids *.seed lib-cov .idea +.vscode build node_modules typings From 86a94641fd63096a83ace57ebc4e127f9c55045c Mon Sep 17 00:00:00 2001 From: Christian Holzberger Date: Tue, 27 Dec 2016 12:37:28 +0100 Subject: [PATCH 2/7] move deref out of compiler into deref.ts --- src/compiler.ts | 14 +++---- src/deref.ts | 92 +++++++++++++++++++++++++++++++++++++++++++++ src/swagger.spec.ts | 2 +- src/swagger.ts | 4 ++ 4 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 src/deref.ts diff --git a/src/compiler.ts b/src/compiler.ts index 94c187f..6cc4b31 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -29,7 +29,6 @@ */ import * as jsonValidator from 'is-my-json-valid'; -import * as deref from 'json-schema-deref-sync'; import {CollectionFormat, Definition, Document, Parameter, PathItem} from './schema'; @@ -142,12 +141,9 @@ function stringValidator(schema: any) { export function compile(document: Document): Compiled { - // get the de-referenced version of the swagger document - let swagger = deref(document); - // add a validator for every parameter in swagger document - Object.keys(swagger.paths).forEach(pathName => { - let path = swagger.paths[pathName]; + Object.keys(document.paths).forEach(pathName => { + let path = document.paths[pathName]; Object.keys(path).forEach(operationName => { let operation = path[operationName]; (operation.parameters || []).forEach((parameter: CompiledParameter) => { @@ -171,12 +167,12 @@ export function compile(document: Document): Compiled { }); }); - let matcher: CompiledPath[] = Object.keys(swagger.paths) + let matcher: CompiledPath[] = Object.keys(document.paths) .map(name => { return { name, - path: swagger.paths[name], - regex: new RegExp(swagger.basePath + name.replace(/\{[^}]*}/g, '[^/]+') + '$'), + path: document.paths[name], + regex: new RegExp(document.basePath + name.replace(/\{[^}]*}/g, '[^/]+') + '$'), expected: (name.match(/[^\/]+/g) || []).map(s => s.toString()) }; }); diff --git a/src/deref.ts b/src/deref.ts new file mode 100644 index 0000000..e5a7adc --- /dev/null +++ b/src/deref.ts @@ -0,0 +1,92 @@ +/* + The MIT License + + Copyright (c) 2014-2016 Carl Ansley, Christian Holzberger + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ +import js_deref = require('json-schema-deref'); +import js_deref_sync = require('json-schema-deref-sync'); +import * as YAML from 'yamljs'; +import path = require('path'); + +/** + * deref is not exporting their options interface + */ +export interface DerefOptions { + baseFolder?: string; + cache?: boolean; + cacheTTL?: number; + failOnMissing?: boolean; + loader?: any; //fn +} + +/* +* custom loader for yml references +*/ +async function ymlLoader( ref: string, option: DerefOptions, fn: any ) { + if ( ref.toLowerCase().endsWith('.yml') || ref.toLowerCase().endsWith('.yaml') ) { + let yamlRef = YAML.load(path.join(option.baseFolder, ref)); + + let derefOptions = { + failOnMissing: option.failOnMissing, + loader: ymlLoader, + baseFolder: path.dirname(path.join(option.baseFolder, ref)) + }; + + let resolved = await derefp(yamlRef, derefOptions); + return fn(null, resolved); + } + return fn(); +} + +/* + * A wrapper for async deref to provide a promise instead + */ +async function derefp(document: any, options?: any): Promise { + let p = new Promise ((resolve, reject) => { + js_deref(document, options, (err: any, derefedDocument: any) => { + if ( err ) { + reject(err); + } else { + resolve(derefedDocument); + } + }); + }); + return p; +} + +/** + * just an alias for sync dereffing of input document + */ +export function derefSync(document: any) { + return js_deref_sync(document); +} + +/** + * async dereffing of input document + */ +export async function deref(document: any, derefOptions?: DerefOptions ) { + let options: DerefOptions = { + loader: ymlLoader + }; + Object.assign( options, derefOptions ); + + return await derefp(document, options); +} diff --git a/src/swagger.spec.ts b/src/swagger.spec.ts index bc62dbe..70a30a8 100644 --- a/src/swagger.spec.ts +++ b/src/swagger.spec.ts @@ -38,7 +38,7 @@ describe('swagger2', () => { describe('petstore', () => { const raw = swagger.loadDocumentSync(__dirname + '/../test/yaml/petstore.yaml'); - const document: swagger.Document | undefined = swagger.validateDocument(raw); + const document: swagger.Document | undefined = swagger.derefSync( swagger.validateDocument(raw) ); let compiled: Compiled; diff --git a/src/swagger.ts b/src/swagger.ts index 427d575..6d5d7b5 100644 --- a/src/swagger.ts +++ b/src/swagger.ts @@ -32,6 +32,7 @@ */ import * as compiler from './compiler'; +import * as ref from './deref'; import * as document from './document'; import * as schema from './schema'; import * as validate from './validate'; @@ -42,5 +43,8 @@ export const validateRequest = validate.request; export const validateResponse = validate.response; export const compileDocument = compiler.compile; +export const deref = ref.deref; +export const derefSync = ref.derefSync; + export import Compiled = compiler.Compiled; export import Document = schema.Document; From 4883a92a23d913f4ab54af2b0bf9ce1030316614 Mon Sep 17 00:00:00 2001 From: Christian Holzberger Date: Tue, 27 Dec 2016 13:10:48 +0100 Subject: [PATCH 3/7] Fix for loading nested references from external yaml files --- src/deref.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/deref.ts b/src/deref.ts index e5a7adc..ebe528b 100644 --- a/src/deref.ts +++ b/src/deref.ts @@ -1,3 +1,5 @@ +// deref.ts + /* The MIT License @@ -41,7 +43,8 @@ export interface DerefOptions { * custom loader for yml references */ async function ymlLoader( ref: string, option: DerefOptions, fn: any ) { - if ( ref.toLowerCase().endsWith('.yml') || ref.toLowerCase().endsWith('.yaml') ) { + const isYamlRegex = /\.y(a|)ml($|#)/i; + if ( ref.match(isYamlRegex) ) { let yamlRef = YAML.load(path.join(option.baseFolder, ref)); let derefOptions = { From 6f89712a0304f023835126d3d5ea182ed3b1336a Mon Sep 17 00:00:00 2001 From: Christian Holzberger Date: Tue, 27 Dec 2016 14:32:10 +0100 Subject: [PATCH 4/7] Add deref tests --- src/deref.spec.ts | 100 ++++++++++++++++++++++++++++++ test/yaml/deref/base.nested.yaml | 3 + test/yaml/deref/base.sub.yaml | 3 + test/yaml/deref/base.yaml | 3 + test/yaml/deref/ext.nested.yaml | 2 + test/yaml/deref/ext.yaml | 1 + test/yaml/deref/internal.yaml | 5 ++ test/yaml/deref/sub/sub.yaml | 3 + test/yaml/deref/sub/sub/sub2.yaml | 1 + 9 files changed, 121 insertions(+) create mode 100644 src/deref.spec.ts create mode 100644 test/yaml/deref/base.nested.yaml create mode 100644 test/yaml/deref/base.sub.yaml create mode 100644 test/yaml/deref/base.yaml create mode 100644 test/yaml/deref/ext.nested.yaml create mode 100644 test/yaml/deref/ext.yaml create mode 100644 test/yaml/deref/internal.yaml create mode 100644 test/yaml/deref/sub/sub.yaml create mode 100644 test/yaml/deref/sub/sub/sub2.yaml diff --git a/src/deref.spec.ts b/src/deref.spec.ts new file mode 100644 index 0000000..783188b --- /dev/null +++ b/src/deref.spec.ts @@ -0,0 +1,100 @@ +// dref.spec.ts + +/* + The MIT License + + Copyright (c) 2016 Christian Holzberger + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the 'Software'), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + +import * as swagger from './swagger'; +import * as assert from 'assert'; + +const EXT_YML = { + name: 'base', + ref: { + name: 'ext' + } +}; +/* see nested dereference bug +const ext_nested_yml = { + name: 'base', + ref: { + name: 'nested', + } +}; +*/ + +const INT_YML = { + name: 'base', + intname: 'int', + ref: { + name: 'int' + } +}; + +const SUB_YML = { + name: 'base-sub', + sub: { + name: 'sub-level1', + sub: { + name: 'sub-level2' + } + } +}; + +const baseFolder = __dirname + '/../test/yaml/deref/'; + +describe('Reference resolution', () => { + + describe('in async mode', () => { + + it('does dereference external references in yaml files', () => { + const raw = swagger.loadDocumentSync(baseFolder + 'base.yaml'); + return swagger.deref(raw, {baseFolder}).then ((document) => { + assert.deepStrictEqual(document, EXT_YML); + }); + }); + /* + * this doesn't work and i can't figure out why... + * perhaps a bug in json-schema-deref? + + it('does dereference nested external references in yaml files', () => { + const raw = swagger.loadDocumentSync(baseFolder + 'base.nested.yaml'); + return swagger.deref(raw, {baseFolder: baseFolder}).then ((document) => { + assert.deepStrictEqual(document, ext_nested_yml); + }); + }); + */ + it('does dereference internal references in yaml files', () => { + const raw = swagger.loadDocumentSync(baseFolder + 'internal.yaml'); + return swagger.deref(raw, {baseFolder}).then ((document) => { + assert.deepStrictEqual(document, INT_YML); + }); + }); + + it('does dereference external references to yaml files in sub folders', () => { + const raw = swagger.loadDocumentSync(baseFolder + 'base.sub.yaml'); + return swagger.deref(raw, {baseFolder}).then ((document) => { + assert.deepStrictEqual(document, SUB_YML); + }); + }); + }); +}); diff --git a/test/yaml/deref/base.nested.yaml b/test/yaml/deref/base.nested.yaml new file mode 100644 index 0000000..d2adb1b --- /dev/null +++ b/test/yaml/deref/base.nested.yaml @@ -0,0 +1,3 @@ +name: "base" +ref: + $ref: './ext.nested.yaml#/name' diff --git a/test/yaml/deref/base.sub.yaml b/test/yaml/deref/base.sub.yaml new file mode 100644 index 0000000..f90a548 --- /dev/null +++ b/test/yaml/deref/base.sub.yaml @@ -0,0 +1,3 @@ +name: base-sub +sub: + $ref: "sub/sub.yaml" diff --git a/test/yaml/deref/base.yaml b/test/yaml/deref/base.yaml new file mode 100644 index 0000000..e4b4afc --- /dev/null +++ b/test/yaml/deref/base.yaml @@ -0,0 +1,3 @@ +name: "base" +ref: + $ref: "ext.yaml" diff --git a/test/yaml/deref/ext.nested.yaml b/test/yaml/deref/ext.nested.yaml new file mode 100644 index 0000000..b900e19 --- /dev/null +++ b/test/yaml/deref/ext.nested.yaml @@ -0,0 +1,2 @@ +nested: + name: "nested" \ No newline at end of file diff --git a/test/yaml/deref/ext.yaml b/test/yaml/deref/ext.yaml new file mode 100644 index 0000000..82ca5e0 --- /dev/null +++ b/test/yaml/deref/ext.yaml @@ -0,0 +1 @@ +name: 'ext' \ No newline at end of file diff --git a/test/yaml/deref/internal.yaml b/test/yaml/deref/internal.yaml new file mode 100644 index 0000000..363685d --- /dev/null +++ b/test/yaml/deref/internal.yaml @@ -0,0 +1,5 @@ +name: "base" +intname: "int" +ref: + name: + $ref: "#/intname" diff --git a/test/yaml/deref/sub/sub.yaml b/test/yaml/deref/sub/sub.yaml new file mode 100644 index 0000000..635230f --- /dev/null +++ b/test/yaml/deref/sub/sub.yaml @@ -0,0 +1,3 @@ +name: sub-level1 +sub: + $ref: sub/sub2.yaml \ No newline at end of file diff --git a/test/yaml/deref/sub/sub/sub2.yaml b/test/yaml/deref/sub/sub/sub2.yaml new file mode 100644 index 0000000..153b143 --- /dev/null +++ b/test/yaml/deref/sub/sub/sub2.yaml @@ -0,0 +1 @@ +name: sub-level2 \ No newline at end of file From a1b818e36aa79d5d8525a112c02c5c56c3efc9a4 Mon Sep 17 00:00:00 2001 From: Christian Holzberger Date: Tue, 27 Dec 2016 14:32:28 +0100 Subject: [PATCH 5/7] Add json-schema-deref to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 80801c6..864f99e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dependencies": { "is-my-json-valid": "^2.15.0", "json-refs": "^2.1.6", + "json-schema-deref": "^0.3.5", "json-schema-deref-sync": "^0.3.2", "yamljs": "^0.2.8" }, From 283f11931a25ac2208e8f1a5ff38e3544104a30a Mon Sep 17 00:00:00 2001 From: Christian Holzberger Date: Tue, 27 Dec 2016 14:32:46 +0100 Subject: [PATCH 6/7] Fix subfolder loading --- src/deref.ts | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/deref.ts b/src/deref.ts index ebe528b..c6ea7cc 100644 --- a/src/deref.ts +++ b/src/deref.ts @@ -36,27 +36,31 @@ export interface DerefOptions { cache?: boolean; cacheTTL?: number; failOnMissing?: boolean; - loader?: any; //fn + loader?: any; } -/* -* custom loader for yml references -*/ -async function ymlLoader( ref: string, option: DerefOptions, fn: any ) { - const isYamlRegex = /\.y(a|)ml($|#)/i; - if ( ref.match(isYamlRegex) ) { - let yamlRef = YAML.load(path.join(option.baseFolder, ref)); +/** + * custom loader for yml references + */ +async function ymlLoader( ref: string, derefOptions: DerefOptions, fn: any ) { + const isYamlRegex = /(.*\.y(a|)ml)($|#)/i; + const match = ref.match(isYamlRegex); - let derefOptions = { - failOnMissing: option.failOnMissing, + if ( match !== null ) { + const refFileName = match[1]; + let yamlRef = YAML.load(path.join(derefOptions.baseFolder, refFileName)); + + const optionsOverride = { loader: ymlLoader, - baseFolder: path.dirname(path.join(option.baseFolder, ref)) + baseFolder: path.dirname(path.join(derefOptions.baseFolder, refFileName)) }; - let resolved = await derefp(yamlRef, derefOptions); - return fn(null, resolved); + const options = Object.assign({}, derefOptions, optionsOverride); + let resolved = await derefp(yamlRef, options); + fn(null, resolved); + } else { + fn(); } - return fn(); } /* @@ -86,10 +90,9 @@ export function derefSync(document: any) { * async dereffing of input document */ export async function deref(document: any, derefOptions?: DerefOptions ) { - let options: DerefOptions = { + let optionsOverride: DerefOptions = { loader: ymlLoader }; - Object.assign( options, derefOptions ); - + const options = Object.assign({}, derefOptions , optionsOverride); return await derefp(document, options); } From 51bdc9c937b4fac12f7ffde62f179c0176638d93 Mon Sep 17 00:00:00 2001 From: Christian Holzberger Date: Tue, 27 Dec 2016 15:09:33 +0100 Subject: [PATCH 7/7] Fix order of imports --- src/deref.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/deref.spec.ts b/src/deref.spec.ts index 783188b..d5a69d1 100644 --- a/src/deref.spec.ts +++ b/src/deref.spec.ts @@ -23,9 +23,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -import * as swagger from './swagger'; import * as assert from 'assert'; +import * as swagger from './swagger'; const EXT_YML = { name: 'base',