Skip to content

Commit 663e420

Browse files
authored
Merge pull request #229 from postmanlabs/feature/validate-metadata
Validate metadata with option `validateMetadata`.
2 parents b195a27 + bd599a9 commit 663e420

File tree

11 files changed

+304
-83
lines changed

11 files changed

+304
-83
lines changed

assets/json-schema-faker.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* Date: 2018-04-09 17:23:23.954Z
1111
*/
1212

13+
var validateSchema = require('../lib/ajvValidation').validateSchema;
14+
1315
(function (global, factory) {
1416
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
1517
typeof define === 'function' && define.amd ? define(factory) :
@@ -24547,7 +24549,12 @@ function extend() {
2454724549
return;
2454824550
}
2454924551
if (optionAPI('useExamplesValue') && 'example' in schema) {
24550-
return schema.example;
24552+
var result = validateSchema(schema, schema.example);
24553+
24554+
// Use example only if valid
24555+
if (result && result.length === 0) {
24556+
return schema.example;
24557+
}
2455124558
}
2455224559
if (optionAPI('useDefaultValue') && 'default' in schema) {
2455324560
return schema.default;

lib/ajvValidation.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
var _ = require('lodash'),
2+
Ajv = require('ajv');
3+
4+
// Following keyword are supoorted for Ajv but not by OAS
5+
const IGNORED_KEYWORDS = ['propertyNames', 'const', 'additionalItems', 'dependencies'];
6+
7+
/**
8+
* Checks if value is postman variable or not
9+
*
10+
* @param {*} value - Value to check for
11+
* @returns {Boolean} postman variable or not
12+
*/
13+
function isPmVariable (value) {
14+
// collection/environment variables are in format - {{var}}
15+
return _.isString(value) && _.startsWith(value, '{{') && _.endsWith(value, '}}');
16+
}
17+
18+
/**
19+
* Used to validate schema against a value.
20+
* NOTE: Used in assets/json-schema-faker.js to validate schema example
21+
*
22+
* @param {*} schema - schema to validate
23+
* @param {*} valueToUse - value to validate schema against
24+
* @param {*} options - a standard list of options that's globally passed around. Check options.js for more.
25+
* @returns {*} - Found Validation Errors
26+
*/
27+
function validateSchema (schema, valueToUse, options = {}) {
28+
var ajv,
29+
validate,
30+
filteredValidationError;
31+
32+
try {
33+
// add Ajv options to support validation of OpenAPI schema.
34+
// For more details see https://ajv.js.org/#options
35+
ajv = new Ajv({
36+
// the unknown formats are ones that are allowed in OAS, but not JSON schema.
37+
unknownFormats: ['int32', 'int64'],
38+
39+
// check all rules collecting all errors. instead returning after the first error.
40+
allErrors: true,
41+
42+
// supports keyword "nullable" from Open API 3 specification.
43+
nullable: true
44+
});
45+
validate = ajv.compile(schema);
46+
validate(valueToUse);
47+
}
48+
catch (e) {
49+
// something went wrong validating the schema
50+
// input was invalid. Don't throw mismatch
51+
return filteredValidationError;
52+
}
53+
54+
// Filter validation errors for following cases
55+
filteredValidationError = _.filter(_.get(validate, 'errors', []), (validationError) => {
56+
let dataPath = _.get(validationError, 'dataPath', '');
57+
58+
// discard the leading '.' if it exists
59+
if (dataPath[0] === '.') {
60+
dataPath = dataPath.slice(1);
61+
}
62+
63+
// for invalid `propertyNames` two error are thrown by Ajv, which include error with `pattern` keyword
64+
if (validationError.keyword === 'pattern') {
65+
return !_.has(validationError, 'propertyName');
66+
}
67+
68+
// As OAS only supports some of Json Schema keywords, and Ajv is supporting all keywords from Draft 7
69+
// Remove keyword currently not supported in OAS to make both compatible with each other
70+
else if (_.includes(IGNORED_KEYWORDS, validationError.keyword)) {
71+
return false;
72+
}
73+
74+
// Ignore unresolved variables from mismatch if option is set
75+
else if (options.ignoreUnresolvedVariables && isPmVariable(_.get(valueToUse, dataPath))) {
76+
return false;
77+
}
78+
return true;
79+
});
80+
81+
// sort errors based on dataPath, as this will ensure no overriding later
82+
filteredValidationError = _.sortBy(filteredValidationError, ['dataPath']);
83+
84+
return filteredValidationError;
85+
}
86+
87+
module.exports = {
88+
validateSchema
89+
};

lib/deref.js

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ module.exports = {
5555
* @param {*} components components in openapi spec.
5656
* @param {object} schemaResolutionCache stores already resolved references
5757
* @param {*} resolveFor - resolve refs for validation/conversion (value to be one of VALIDATION/CONVERSION)
58+
* @param {string} resolveTo The desired JSON-generation mechanism (schema: prefer using the JSONschema to
59+
generate a fake object, example: use specified examples as-is). Default: schema
5860
* @param {*} stack counter which keeps a tab on nested schemas
5961
* @param {*} seenRef References that are repeated. Used to identify circular references.
6062
* @returns {*} schema - schema that adheres to all individual schemas in schemaArr
6163
*/
6264
resolveAllOf: function (schemaArr, parameterSourceOption, components, schemaResolutionCache,
63-
resolveFor = 'CONVERSION', stack = 0, seenRef) {
65+
resolveFor = 'CONVERSION', resolveTo = 'schema', stack = 0, seenRef) {
6466

6567
if (!(schemaArr instanceof Array)) {
6668
return null;
@@ -69,13 +71,13 @@ module.exports = {
6971
if (schemaArr.length === 1) {
7072
// for just one entry in allOf, don't need to enforce type: object restriction
7173
return this.resolveRefs(schemaArr[0], parameterSourceOption, components, schemaResolutionCache, resolveFor,
72-
stack, seenRef);
74+
resolveTo, stack, seenRef);
7375
}
7476

7577
// generate one object for each schema
7678
let indivObjects = schemaArr.map((schema) => {
7779
return this.resolveRefs(schema, parameterSourceOption, components, schemaResolutionCache, resolveFor,
78-
stack, seenRef);
80+
resolveTo, stack, seenRef);
7981
}).filter((schema) => {
8082
return schema.type === 'object';
8183
}),
@@ -113,13 +115,15 @@ module.exports = {
113115
* @param {*} components components in openapi spec.
114116
* @param {object} schemaResolutionCache stores already resolved references
115117
* @param {*} resolveFor - resolve refs for validation/conversion (value to be one of VALIDATION/CONVERSION)
118+
* @param {string} resolveTo The desired JSON-generation mechanism (schema: prefer using the JSONschema to
119+
generate a fake object, example: use specified examples as-is). Default: schema
116120
* @param {*} stack counter which keeps a tab on nested schemas
117121
* @param {*} seenRef - References that are repeated. Used to identify circular references.
118122
* @returns {*} schema satisfying JSON-schema-faker.
119123
*/
120124

121125
resolveRefs: function (schema, parameterSourceOption, components, schemaResolutionCache,
122-
resolveFor = 'CONVERSION', stack = 0, seenRef = {}) {
126+
resolveFor = 'CONVERSION', resolveTo = 'schema', stack = 0, seenRef = {}) {
123127
var resolvedSchema, prop, splitRef,
124128
ERR_TOO_MANY_LEVELS = '<Error: Too many levels of nesting to fake this schema>';
125129

@@ -136,15 +140,15 @@ module.exports = {
136140

137141
if (schema.anyOf) {
138142
return this.resolveRefs(schema.anyOf[0], parameterSourceOption, components, schemaResolutionCache, resolveFor,
139-
stack, _.cloneDeep(seenRef));
143+
resolveTo, stack, _.cloneDeep(seenRef));
140144
}
141145
if (schema.oneOf) {
142146
return this.resolveRefs(schema.oneOf[0], parameterSourceOption, components, schemaResolutionCache, resolveFor,
143-
stack, _.cloneDeep(seenRef));
147+
resolveTo, stack, _.cloneDeep(seenRef));
144148
}
145149
if (schema.allOf) {
146150
return this.resolveAllOf(schema.allOf, parameterSourceOption, components, schemaResolutionCache, resolveFor,
147-
stack, _.cloneDeep(seenRef));
151+
resolveTo, stack, _.cloneDeep(seenRef));
148152
}
149153
if (schema.$ref && _.isFunction(schema.$ref.split)) {
150154
let refKey = schema.$ref;
@@ -186,7 +190,7 @@ module.exports = {
186190

187191
if (resolvedSchema) {
188192
let refResolvedSchema = this.resolveRefs(resolvedSchema, parameterSourceOption,
189-
components, schemaResolutionCache, resolveFor, stack, _.cloneDeep(seenRef));
193+
components, schemaResolutionCache, resolveFor, resolveTo, stack, _.cloneDeep(seenRef));
190194

191195
if (refResolvedSchema && refResolvedSchema.value !== ERR_TOO_MANY_LEVELS) {
192196
schemaResolutionCache[refKey] = refResolvedSchema;
@@ -221,8 +225,8 @@ module.exports = {
221225
continue;
222226
}
223227
/* eslint-enable */
224-
tempSchema.properties[prop] = this.resolveRefs(property,
225-
parameterSourceOption, components, schemaResolutionCache, resolveFor, stack, _.cloneDeep(seenRef));
228+
tempSchema.properties[prop] = this.resolveRefs(property, parameterSourceOption, components,
229+
schemaResolutionCache, resolveFor, resolveTo, stack, _.cloneDeep(seenRef));
226230
}
227231
}
228232
return tempSchema;
@@ -260,13 +264,13 @@ module.exports = {
260264
// without this, schemas with circular references aren't faked correctly
261265
let tempSchema = _.omit(schema, 'items');
262266
tempSchema.items = this.resolveRefs(schema.items, parameterSourceOption,
263-
components, schemaResolutionCache, resolveFor, stack, _.cloneDeep(seenRef));
267+
components, schemaResolutionCache, resolveFor, resolveTo, stack, _.cloneDeep(seenRef));
264268
return tempSchema;
265269
}
266270
else if (!schema.hasOwnProperty('default')) {
267271
if (schema.hasOwnProperty('type')) {
268-
// Don't override default value to schema for validation
269-
if (resolveFor === 'CONVERSION') {
272+
// Override default value to schema for CONVERSION only for parmeter resolution set to schema
273+
if (resolveFor === 'CONVERSION' && resolveTo === 'schema') {
270274
if (!schema.hasOwnProperty('format')) {
271275
schema.default = '<' + schema.type + '>';
272276
}

lib/options.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ module.exports = {
3939
' If “Fallback” is selected, the request will be named after one of the following schema' +
4040
' values: `description`, `operationid`, `url`.',
4141
external: true,
42-
usage: ['CONVERSION']
42+
usage: ['CONVERSION', 'VALIDATION']
4343
},
4444
{
4545
name: 'Set indent character',
@@ -154,6 +154,24 @@ module.exports = {
154154
description: 'Whether to provide fixes for patching corresponding mismatches.',
155155
external: true,
156156
usage: ['VALIDATION']
157+
},
158+
{
159+
name: 'Show Metadata validation messages',
160+
id: 'validateMetadata',
161+
type: 'boolean',
162+
default: false,
163+
description: 'Whether to show mismatches for incorrect name and description of request',
164+
external: true,
165+
usage: ['VALIDATION']
166+
},
167+
{
168+
name: 'Ignore mismatch for unresolved postman variables',
169+
id: 'ignoreUnresolvedVariables',
170+
type: 'boolean',
171+
default: false,
172+
description: 'Whether to ignore mismatches resulting from unresolved variables in the Postman request',
173+
external: true,
174+
usage: ['VALIDATION']
157175
}
158176
];
159177

0 commit comments

Comments
 (0)