diff --git a/ChangeLog.md b/ChangeLog.md index 25d377de..94229cb0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,10 @@ +### 03/15/2018 0.4.35 +- Validate default responses. [#228](https://github.com/Azure/oav/issues/228) +- Add option to model an implicit default cloud error response. [#224](https://github.com/Azure/oav/issues/224) + ### 03/08/2018 0.4.34 - Updating default output level to info. -- Fixing issue #225, where output verbosity wasn't respected in one case. +- Fixing issue #225, where output verbosity wasn't respected in one case. ### 02/28/2018 0.4.33 - Don't validate pure objects. diff --git a/lib/util/utils.js b/lib/util/utils.js index 9bcf8593..7373424a 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -758,3 +758,43 @@ function isPropertyRequired(propName, model) { * Contains the reverse mapping of http.STATUS_CODES */ exports.statusCodeStringToStatusCode = lodash.invert(lodash.mapValues(http.STATUS_CODES, value => { return value.replace(/ |-/g, "").toLowerCase(); })); + + +/** + * Models an ARM cloud error. + */ +exports.CloudError = { + description: "Error response describing why the operation failed.", + schema: { + properties: { + error: { + properties: { + code: { + type: "string", + description: "An identifier for the error. Codes are invariant and are intended to be consumed programmatically." + }, + message: { + type: "string", + description: "A message describing the error, intended to be suitable for display in a user interface." + }, + target: { + type: "string", + description: "The target of the particular error. For example, the name of the property in error." + }, + details: { + type: "array", + items: { type: "object" }, + description: "A list of additional details about the error." + }, + additionalInfo: { + type: "array", + items: { type: "object" }, + description: "A list of additional info about an error." + } + }, + additionalProperties: false + } + }, + additionalProperties: false + } +}; \ No newline at end of file diff --git a/lib/validators/liveValidator.js b/lib/validators/liveValidator.js index 1a72b5cc..724972a3 100644 --- a/lib/validators/liveValidator.js +++ b/lib/validators/liveValidator.js @@ -41,6 +41,8 @@ class LiveValidator { * * @param {string} [options.directory] The directory where to clone github repository or from where to find swaggers. Defaults to "repo" under user directory. * + * @param {string} [options.shouldModelImplicitDefaultResponse] Specifies if to model a default response for operations even if it is not specified in the specs. + * * @returns {object} CacheBuilder Returns the configured CacheBuilder object. */ constructor(options) { @@ -141,7 +143,14 @@ class LiveValidator { return () => { log.info(`Building cache from: "${swaggerPath}"`); - let validator = new SpecValidator(swaggerPath, null, { isPathCaseSensitive: this.options.isPathCaseSensitive }); + let validator = new SpecValidator( + swaggerPath, + null, + { + shouldModelImplicitDefaultResponse: this.options.shouldModelImplicitDefaultResponse, + isPathCaseSensitive: this.options.isPathCaseSensitive + }); + return validator.initialize().then((api) => { let operations = api.getOperations(); let apiVersion = api.info.version.toLowerCase(); diff --git a/lib/validators/specResolver.js b/lib/validators/specResolver.js index 886545ce..90a5cd78 100644 --- a/lib/validators/specResolver.js +++ b/lib/validators/specResolver.js @@ -47,6 +47,8 @@ class SpecResolver { * * @param {object} [options.shouldResolveNullableTypes] Should we allow null values to match any type? Default: true * + * @param {object} [options.shouldModelImplicitDefaultResponse] Should we model a default response even if it is not defined? Default: false + * * @return {object} An instance of the SpecResolver class. */ constructor(specPath, specInJson, options) { @@ -93,6 +95,9 @@ class SpecResolver { if (options.shouldResolveNullableTypes === null || options.shouldResolveNullableTypes === undefined) { options.shouldResolveNullableTypes = options.shouldResolveAllOf; } + if (options.shouldModelImplicitDefaultResponse === null || options.shouldModelImplicitDefaultResponse === undefined) { + options.shouldModelImplicitDefaultResponse = false; + } this.options = options; } @@ -169,7 +174,13 @@ class SpecResolver { } else { return Promise.resolve(self); } - }).catch(function (err) { + }).then(() => { + if (self.options.shouldModelImplicitDefaultResponse) { + return self.modelImplicitDefaultResponse(); + } else { + return Promise.resolve(self); + } + }).catch((err) => { let e = { message: `An Error occurred while resolving relative references and allOf in model definitions in the swagger spec: "${self.specPath}".`, code: ErrorCodes.ResolveSpecError.name, @@ -216,9 +227,9 @@ class SpecResolver { } let allRefsRemoteRelative = JsonRefs.findRefs(doc, options); - let promiseFactories = utils.getKeys(allRefsRemoteRelative).map(function (refName) { + let promiseFactories = utils.getKeys(allRefsRemoteRelative).map((refName) => { let refDetails = allRefsRemoteRelative[refName]; - return function () { return self.resolveRelativeReference(refName, refDetails, doc, docPath); }; + return () => { return self.resolveRelativeReference(refName, refDetails, doc, docPath); }; }); if (promiseFactories.length) { return utils.executePromisesSequentially(promiseFactories); @@ -271,7 +282,7 @@ class SpecResolver { docPath = utils.joinPath(docDir, parsedReference.filePath); } - return utils.parseJson(docPath).then(function (result) { + return utils.parseJson(docPath).then((result) => { if (!parsedReference.localReference) { //Since there is no local reference we will replace the key in the object with the parsed //json (relative) file it is refering to. @@ -344,7 +355,7 @@ class SpecResolver { let spec = self.specInJson; let definitions = spec.definitions; let modelNames = utils.getKeys(definitions); - modelNames.map(function (modelName) { + modelNames.map((modelName) => { let model = definitions[modelName]; let modelRef = '/definitions/' + modelName; return self.resolveAllOfInModel(model, modelRef); @@ -370,7 +381,7 @@ class SpecResolver { if (!self.resolvedAllOfModels[modelRef]) { if (model && model.allOf) { - model.allOf.map(function (item) { + model.allOf.map((item) => { let referencedModel = item; let ref = item['$ref']; let slicedRef = ref ? ref.slice(1) : undefined; @@ -435,7 +446,7 @@ class SpecResolver { let spec = self.specInJson; let definitions = spec.definitions; let modelNames = utils.getKeys(definitions); - modelNames.map(function (modelName) { + modelNames.map((modelName) => { if (definitions[modelName].allOf) { delete definitions[modelName].allOf; } @@ -460,7 +471,7 @@ class SpecResolver { if (!modelNames) { modelNames = utils.getKeys(definitions); } - modelNames.forEach(function iterator(modelName) { + modelNames.forEach(modelName => { let model = definitions[modelName]; if (model) { if (force || (!model.additionalProperties && (!(!model.properties || (model.properties && utils.getKeys(model.properties).length === 0))))) { @@ -590,6 +601,23 @@ class SpecResolver { return Promise.resolve(self); } + /** + * Models a default response as a Cloud Error if none is specified in the api spec. + */ + modelImplicitDefaultResponse() { + let self = this; + let spec = self.specInJson; + for (let pathObj of utils.getValues(spec.paths)) { + for (let operation of utils.getValues(pathObj)) { + + // going through responses + if (operation.responses && !operation.responses.default) { + operation.responses.default = utils.CloudError; + } + } + } + } + /** * Resolves the discriminator by replacing all the references to the parent model with a oneOf array containing * references to the parent model and all its child models. It also modifies the discriminator property in diff --git a/package-lock.json b/package-lock.json index 8eec934e..014ca209 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "oav", - "version": "0.4.33", + "version": "0.4.35", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -197,7 +197,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "requires": { "ms": "2.0.0" } @@ -307,7 +307,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -421,7 +421,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" }, "cookiejar": { "version": "2.1.1", @@ -444,7 +444,7 @@ "boom": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=", "requires": { "hoek": "4.2.1" } @@ -478,7 +478,7 @@ "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "requires": { "ms": "2.0.0" } @@ -696,7 +696,7 @@ "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" + "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=" }, "estraverse": { "version": "0.0.4", @@ -861,7 +861,7 @@ "hawk": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "integrity": "sha1-r02RTrBl+bXOTZ0RwcshJu7MMDg=", "requires": { "boom": "4.3.1", "cryptiles": "3.1.2", @@ -955,7 +955,7 @@ "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=" }, "inflight": { "version": "1.0.6", @@ -1178,7 +1178,7 @@ "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", "requires": { "ms": "2.0.0" } @@ -1276,7 +1276,7 @@ "linq": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/linq/-/linq-3.0.9.tgz", - "integrity": "sha512-9kDSW2s4sjajLcGN4f+pRiwYZrbqmwDibQyysEgRSvA8dgHmqEEruYQX4GNBQAiOYcMv8IdUbCWgDq6eUidkKQ==" + "integrity": "sha1-litNVrP0uE6iq9J3TPFxta23A+8=" }, "load-json-file": { "version": "1.1.0", @@ -1560,7 +1560,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "1.1.11" } @@ -1706,7 +1706,7 @@ "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "requires": { "hosted-git-info": "2.6.0", "is-builtin-module": "1.0.0", @@ -1790,7 +1790,7 @@ "path-loader": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/path-loader/-/path-loader-1.0.4.tgz", - "integrity": "sha512-k/IPo9OWyofATP5gwIehHHQoFShS37zsSIsejKe6fjI+tqK+FnRpiSg4ZfWUpxb0g2PfCreWPqBD4ayjqjqkdQ==", + "integrity": "sha1-EH3IsbfA9qihjndJva9GomwtmLQ=", "requires": { "native-promise-only": "0.8.1", "superagent": "3.8.2" @@ -1847,7 +1847,7 @@ "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=" }, "randexp": { "version": "0.4.9", @@ -1918,7 +1918,7 @@ "request": { "version": "2.83.0", "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=", "requires": { "aws-sign2": "0.7.0", "aws4": "1.6.0", @@ -1962,7 +1962,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" }, "semver": { "version": "5.5.0", @@ -2216,7 +2216,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "requires": { "safe-buffer": "5.1.1" } @@ -2290,7 +2290,7 @@ "swagger-parser": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-3.4.2.tgz", - "integrity": "sha512-himpIkA50AjTvrgtz0PPbzwWoTjj3F3ye/y1PcW/514YEp1A3IhAcJFkkEu7b1zHnSIthnzxC8aTy+XZG0D+iA==", + "integrity": "sha1-JE1n1u7tCMAKy12VlQ1a771hhaM=", "requires": { "call-me-maybe": "1.0.1", "debug": "3.1.0", @@ -2390,7 +2390,7 @@ "tunnel": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.5.tgz", - "integrity": "sha512-gj5sdqherx4VZKMcBA4vewER7zdK25Td+z1npBqpbDys4eJrLx+SlYjJvq1bDXs2irkuJM5pf8ktaEQVipkrbA==" + "integrity": "sha1-0VMiVHSe02Yg/NEBCGVJWh+p0K4=" }, "tunnel-agent": { "version": "0.6.0", diff --git a/package.json b/package.json index 2b550621..eb068331 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oav", - "version": "0.4.34", + "version": "0.4.35", "author": { "name": "Microsoft Corporation", "email": "azsdkteam@microsoft.com",