Skip to content

Commit

Permalink
fix: oas3-valid-(content-)schema-example has problem with nullable (#914
Browse files Browse the repository at this point in the history
)

* First attempt to fix #894: oas3-valid-(content-)schema-example has problem with nullable

* Use OpenAPI schemas

* Don't use OpenAPI schemas

* Set default oasVersion

* Use optional instead of 0.

* Fix annoying lint rule

* Update src/functions/schema.ts

Co-Authored-By: Jakub Rożek <[email protected]>

Co-authored-by: Jakub Rożek <[email protected]>
  • Loading branch information
m-mohr and P0lip committed Jan 20, 2020
1 parent d8c3aba commit 4725569
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 35 deletions.
5 changes: 3 additions & 2 deletions src/functions/__tests__/schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Optional } from '@stoplight/types';
import { JSONSchema4, JSONSchema6 } from 'json-schema';
import { schema } from '../schema';

function runSchema(target: any, schemaObj: object) {
return schema(target, { schema: schemaObj }, { given: [] }, { given: null, original: null } as any);
function runSchema(target: any, schemaObj: object, oasVersion?: Optional<number>) {
return schema(target, { schema: schemaObj, oasVersion }, { given: [] }, { given: null, original: null } as any);
}

describe('schema', () => {
Expand Down
9 changes: 8 additions & 1 deletion src/functions/schema-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
*/
import { JSONPath } from 'jsonpath-plus';

import { Optional } from '@stoplight/types';

import { IFunction, IFunctionResult, IRule, RuleFunction } from '../types';
import { getLintTargets } from '../utils';
import { schema } from './schema';
Expand All @@ -17,6 +19,8 @@ export interface ISchemaPathOptions {
schemaPath: string;
// the `path.to.prop` to field, or special `@key` value to target keys for matched `given` object
field?: string;
// The oasVersion, either 2 or 3 for OpenAPI Spec versions, could also be 3.1 or a larger number if there's a need for it, otherwise JSON Schema
oasVersion?: Optional<number>;
}

export type SchemaPathRule = IRule<RuleFunction.SCHEMAPATH, ISchemaPathOptions>;
Expand All @@ -33,7 +37,10 @@ export const schemaPath: IFunction<ISchemaPathOptions> = (targetVal, opts, paths
for (const relevantItem of relevantItems) {
const result = schema(
relevantItem.value,
{ schema: schemaObject },
{
schema: schemaObject,
oasVersion: opts.oasVersion,
},
{
given: paths.given,
target: [...(paths.target || paths.given), ...relevantItem.path],
Expand Down
63 changes: 40 additions & 23 deletions src/functions/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as AJV from 'ajv';
import { ValidateFunction } from 'ajv';
import * as jsonSpecv4 from 'ajv/lib/refs/json-schema-draft-04.json';
import * as jsonSpecv6 from 'ajv/lib/refs/json-schema-draft-06.json';
import * as jsonSpecv7 from 'ajv/lib/refs/json-schema-draft-07.json';
import { IOutputError } from 'better-ajv-errors';
import { escapeRegExp } from 'lodash';
import { IFunction, IFunctionResult, IRule, JSONSchema, RuleFunction } from '../types';
Expand All @@ -13,6 +12,8 @@ const betterAjvErrors = require('better-ajv-errors/lib/modern');

export interface ISchemaOptions {
schema: object;
// The oasVersion, either 2 or 3 for OpenAPI Spec versions, could also be 3.1 or a larger number if there's a need for it, otherwise JSON Schema
oasVersion?: Optional<number>;
}

export type SchemaRule = IRule<RuleFunction.SCHEMA, ISchemaOptions>;
Expand All @@ -33,26 +34,41 @@ const logger = {
error: console.error,
};

const ajv = new AJV({
meta: false,
schemaId: 'auto',
jsonPointers: true,
unknownFormats: 'ignore',
logger,
});
ajv.addMetaSchema(jsonSpecv4);
ajv.addMetaSchema(jsonSpecv6);
ajv.addMetaSchema(jsonSpecv7);
// @ts-ignore
ajv._opts.defaultMeta = jsonSpecv4.id;
// @ts-ignore
ajv._refs['http://json-schema.org/schema'] = 'http://json-schema.org/draft-04/schema';

ajv.addFormat('int32', { type: 'number', validate: oasFormatValidator.int32 });
ajv.addFormat('int64', { type: 'number', validate: oasFormatValidator.int64 });
ajv.addFormat('float', { type: 'number', validate: oasFormatValidator.float });
ajv.addFormat('double', { type: 'number', validate: oasFormatValidator.double });
ajv.addFormat('byte', { type: 'string', validate: oasFormatValidator.byte });
const ajvInstances = {};

function getAjv(oasVersion?: Optional<number>): AJV.Ajv {
const type: string = oasVersion && oasVersion >= 2 ? 'oas' + oasVersion : 'jsonschema';
if (typeof ajvInstances[type] !== 'undefined') {
return ajvInstances[type];
}

const ajvOpts: AJV.Options = {
meta: true, // Add default meta schemas (draft 7 at the moment)
schemaId: 'auto',
jsonPointers: true,
unknownFormats: 'ignore',
nullable: oasVersion === 3, // Support nullable for OAS3
logger,
};
const ajv = new AJV(ajvOpts);
// We need v4 for OpenAPI and it doesn't hurt to have v6 as well.
ajv.addMetaSchema(jsonSpecv4);
ajv.addMetaSchema(jsonSpecv6);

// @ts-ignore
ajv._opts.defaultMeta = jsonSpecv4.id;
// @ts-ignore
ajv._refs['http://json-schema.org/schema'] = 'http://json-schema.org/draft-04/schema';

ajv.addFormat('int32', { type: 'number', validate: oasFormatValidator.int32 });
ajv.addFormat('int64', { type: 'number', validate: oasFormatValidator.int64 });
ajv.addFormat('float', { type: 'number', validate: oasFormatValidator.float });
ajv.addFormat('double', { type: 'number', validate: oasFormatValidator.double });
ajv.addFormat('byte', { type: 'string', validate: oasFormatValidator.byte });

ajvInstances[type] = ajv;
return ajv;
}

function getSchemaId(schemaObj: JSONSchema): void | string {
if ('$id' in schemaObj) {
Expand All @@ -65,7 +81,8 @@ function getSchemaId(schemaObj: JSONSchema): void | string {
}

const validators = new (class extends WeakMap<JSONSchema, ValidateFunction> {
public get(schemaObj: JSONSchema) {
public get(schemaObj: JSONSchema, oasVersion?: Optional<number>) {
const ajv = getAjv(oasVersion);
const schemaId = getSchemaId(schemaObj);
let validator = schemaId !== void 0 ? ajv.getSchema(schemaId) : void 0;
if (validator !== void 0) {
Expand Down Expand Up @@ -128,7 +145,7 @@ export const schema: IFunction<ISchemaOptions> = (targetVal, opts, paths) => {

try {
// we used the compiled validation now, hence this lookup here (see the logic above for more info)
const validator = validators.get(schemaObj);
const validator = validators.get(schemaObj, opts.oasVersion);
if (!validator(targetVal) && validator.errors) {
try {
results.push(
Expand Down
27 changes: 18 additions & 9 deletions src/rulesets/oas/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$"
"schemaPath": "$",
"oasVersion": 2
}
}
},
Expand All @@ -547,7 +548,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$"
"schemaPath": "$",
"oasVersion": 2
}
}
},
Expand Down Expand Up @@ -708,7 +710,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$.schema"
"schemaPath": "$.schema",
"oasVersion": 3
}
}
},
Expand All @@ -724,7 +727,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$.schema"
"schemaPath": "$.schema",
"oasVersion": 3
}
}
},
Expand All @@ -740,7 +744,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$.schema"
"schemaPath": "$.schema",
"oasVersion": 3
}
}
},
Expand All @@ -756,7 +761,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$"
"schemaPath": "$",
"oasVersion": 3
}
}
},
Expand All @@ -772,7 +778,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$"
"schemaPath": "$",
"oasVersion": 3
}
}
},
Expand All @@ -788,7 +795,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$"
"schemaPath": "$",
"oasVersion": 3
}
}
},
Expand All @@ -804,7 +812,8 @@
"function": "schemaPath",
"functionOptions": {
"field": "example",
"schemaPath": "$"
"schemaPath": "$",
"oasVersion": 3
}
}
},
Expand Down

0 comments on commit 4725569

Please sign in to comment.