diff --git a/lib/liveValidation/liveValidatorLoader.ts b/lib/liveValidation/liveValidatorLoader.ts index 3527aa70..b996cdb0 100644 --- a/lib/liveValidation/liveValidatorLoader.ts +++ b/lib/liveValidation/liveValidatorLoader.ts @@ -92,14 +92,18 @@ export class LiveValidatorLoader implements Loader { ); private constructor(private opts: LiveValidatorLoaderOptions) { setDefaultOpts(opts, { - transformToNewSchemaFormat: false, + transformToNewSchemaFormat: true, loadSuppression: Object.keys(allErrorConstants), }); this.jsonLoader = JsonLoader.create(opts); this.swaggerLoader = SwaggerLoader.create(opts); - this.schemaValidator = new AjvSchemaValidator(this.jsonLoader); + this.schemaValidator = new AjvSchemaValidator(this.jsonLoader, { + strictTypes: false, + strict: false, + strictTuples: false + }); this.transformContext = getTransformContext(this.jsonLoader, this.schemaValidator, [ xmsPathsTransformer, @@ -168,8 +172,7 @@ export class LiveValidatorLoader implements Loader { if (param.required) { copyInfo(param, schema); this.addRequiredToSchema(schema, "body"); - } - else { + } else { operation._bodyTransform = bodyTransformIfNotRequiredAndEmpty; } break; @@ -272,11 +275,10 @@ const parameterTransform = { const bodyTransformIfNotRequiredAndEmpty = (body: any) => { if (body && Object.keys(body).length === 0 && body.constructor === Object) { return undefined; - } - else { + } else { return body; } -} +}; const addParamTransform = (it: Operation | Response, param: Parameter) => { const transform = parameterTransform[param.type! as keyof typeof parameterTransform]; diff --git a/lib/models/requestResponse.ts b/lib/models/requestResponse.ts index 9a63227e..49581fff 100644 --- a/lib/models/requestResponse.ts +++ b/lib/models/requestResponse.ts @@ -79,8 +79,10 @@ export class RequestResponse { } export const requestResponseDefinition: Schema = { + type: "object", properties: { liveRequest: { + type: "object", required: ["url", "method"], properties: { headers: { @@ -90,6 +92,7 @@ export const requestResponseDefinition: Schema = { }, }, query: { + type: "object", nullable: true, additionalProperties: { oneOf: [ @@ -112,23 +115,45 @@ export const requestResponseDefinition: Schema = { type: "string", }, body: { - nullable: true, + oneOf: [ + { + type: "object", + }, + { + type: "array", + }, + { + type: "null", + }, + ], }, }, }, liveResponse: { + type: "object", required: ["statusCode"], properties: { statusCode: { type: "string", }, headers: { + type: "object", additionalProperties: { type: "string", }, }, body: { - nullable: true, + oneOf: [ + { + type: "object", + }, + { + type: "array", + }, + { + type: "null", + }, + ], }, }, }, diff --git a/lib/swagger/jsonLoader.ts b/lib/swagger/jsonLoader.ts index 43ec0405..0ef81b28 100644 --- a/lib/swagger/jsonLoader.ts +++ b/lib/swagger/jsonLoader.ts @@ -21,7 +21,7 @@ interface FileCache { mockName: string; } -export const $id = "id"; +export const $id = "$id"; export class JsonLoader implements Loader { private fileLoader: FileLoader; diff --git a/lib/swagger/swaggerTypes.ts b/lib/swagger/swaggerTypes.ts index 087dd1dd..25b3ddbf 100644 --- a/lib/swagger/swaggerTypes.ts +++ b/lib/swagger/swaggerTypes.ts @@ -195,13 +195,13 @@ interface BaseSchema { modelAsString?: boolean; values?: Array<{ value: any; description?: string; name?: string }>; }; - type?: string; + type?: string | string[]; items?: Schema | Schema[]; } export type SchemaType = "object" | "array" | "string" | "integer" | "number" | "boolean" | "null"; export interface Schema extends BaseSchema { - type?: SchemaType; + type?: SchemaType | SchemaType[]; allOf?: Schema[]; anyOf?: Schema[]; oneOf?: Schema[]; diff --git a/lib/swaggerValidator/ajv.ts b/lib/swaggerValidator/ajv.ts index 5b0601b3..f9415408 100644 --- a/lib/swaggerValidator/ajv.ts +++ b/lib/swaggerValidator/ajv.ts @@ -1,4 +1,5 @@ -import { CodeGen, default as Ajv, Format, _ } from "ajv"; +import { default as Ajv, Format, _ } from "ajv"; +import { default as addAjvFormats } from "ajv-formats"; import { JsonLoader } from "../swagger/jsonLoader"; import { Schema } from "../swagger/swaggerTypes"; import { xmsMutability, xmsSecret } from "../util/constants"; @@ -18,7 +19,7 @@ export const ajvEnableReadOnlyAndXmsMutability = (ajv: Ajv) => { const eq = _`===`; cxt.pass(_`this.isResponse || ${cxt.data} ${eq} null || ${cxt.data} ${eq} undefined`); } - } + }, }); ajv.addKeyword({ @@ -35,18 +36,22 @@ export const ajvEnableReadOnlyAndXmsMutability = (ajv: Ajv) => { throw new Error(`Invalid ${xmsMutability} value: ${JSON.stringify(mutability)}`); } const eq = _`===`; - cxt.pass(_`${validInRequest ? "!" : ""}this.isResponse || ${cxt.data} ${eq} null || ${cxt.data} ${eq} undefined`); + const cond = validInRequest ? _`!this.isResponse` : _`this.isResponse`; + cxt.pass(_`${cond} || ${cxt.data} ${eq} null || ${cxt.data} ${eq} undefined`); }, }); }; export const ajvEnableXmsSecret = (ajv: Ajv) => { ajv.addKeyword({ - keyword: xmsSecret + keyword: xmsSecret, metaSchema: { type: "boolean" } as Schema, - inline: (it: CompilationContext, _keyword: string, isSecret: boolean) => { - const data = `data${it.dataLevel || ""}`; - return isSecret ? `!this.isResponse || ${data} === null || ${data} === undefined` : "1"; + code: (ctx) => { + const isSecret = ctx.schema; + if (isSecret) { + const eq = _`===`; + ctx.pass(_`!this.isResponse || ${ctx.data} ${eq} null || ${ctx.data} ${eq} undefined`); + } }, }); }; @@ -102,36 +107,8 @@ export const ajvEnableDurationFormat = (ajv: Ajv) => { }); }; -// for (const keyword of [ -// "name", -// "in", -// "example", -// "parameters", -// "externalDocs", -// "x-nullable", -// "x-ms-enum", -// "x-ms-azure-resource", -// "x-ms-parameter-location", -// "x-ms-client-name", -// "x-ms-external", -// "x-ms-skip-url-encoding", -// "x-ms-client-flatten", -// "x-ms-api-version", -// "x-ms-parameter-grouping", -// "x-ms-discriminator-value", -// "x-ms-client-request-id", -// "x-apim-code-nillable", -// "x-new-pattern", -// "x-previous-pattern", -// "x-comment", -// "x-abstract", -// "allowEmptyValue", -// "collectionFormat", -// ]) { -// ajv.addKeyword(keyword, {}); -// } - export const ajvEnableAll = (ajv: Ajv, jsonLoader: JsonLoader) => { + addAjvFormats(ajv); ajvEnableDiscriminatorMap(ajv, jsonLoader); ajvEnableXmsSecret(ajv); ajvEnableReadOnlyAndXmsMutability(ajv); @@ -141,4 +118,33 @@ export const ajvEnableAll = (ajv: Ajv, jsonLoader: JsonLoader) => { ajvEnableDateTimeRfc1123Format(ajv); ajvAddFormatsDefaultValidation(ajv, "string", ["byte", "password", "file"]); ajvAddFormatsDefaultValidation(ajv, "number", ["double", "float", "decimal"]); + ajv.addVocabulary([ + "name", + "in", + "example", + "parameters", + "externalDocs", + "x-nullable", + "x-ms-enum", + "x-ms-azure-resource", + "x-ms-parameter-location", + "x-ms-client-name", + "x-ms-external", + "x-ms-examples", + "x-ms-skip-url-encoding", + "x-ms-client-flatten", + "x-ms-api-version", + "x-ms-parameter-grouping", + "x-ms-discriminator-value", + "x-ms-client-request-id", + "x-apim-code-nillable", + "x-new-pattern", + "x-previous-pattern", + "x-comment", + "x-abstract", + "allowEmptyValue", + "collectionFormat", + "_skipError", + "discriminator", + ]); }; diff --git a/lib/swaggerValidator/ajvDiscriminatorMap.ts b/lib/swaggerValidator/ajvDiscriminatorMap.ts index adf232cd..c51e0206 100644 --- a/lib/swaggerValidator/ajvDiscriminatorMap.ts +++ b/lib/swaggerValidator/ajvDiscriminatorMap.ts @@ -6,7 +6,10 @@ export const ajvEnableDiscriminatorMap = (ajv: Ajv, loader: JsonLoader) => { ajv.addKeyword({ keyword: "discriminatorMap", errors: "full", - metaSchema: { type: "object", additionalProperty: { type: "object,null" } }, + metaSchema: { + type: "object", + additionalProperties: { type: "object", nullable: true }, + }, compile(schemas: { [key: string]: Schema }, parentSch) { const parentSchema = parentSch as Schema; diff --git a/lib/swaggerValidator/ajvSchemaValidator.ts b/lib/swaggerValidator/ajvSchemaValidator.ts index 66a62c17..7c108e98 100644 --- a/lib/swaggerValidator/ajvSchemaValidator.ts +++ b/lib/swaggerValidator/ajvSchemaValidator.ts @@ -28,8 +28,6 @@ export class AjvSchemaValidator implements SchemaValidator { public constructor(loader: JsonLoader, options?: Options) { this.ajv = new Ajv({ - // tslint:disable-next-line: no-submodule-imports - meta: require("ajv/lib/refs/json-schema-draft-04.json"), strict: true, strictTypes: true, strictTuples: true, diff --git a/lib/transform/nullableTransformer.ts b/lib/transform/nullableTransformer.ts index 2052c72f..573bb008 100644 --- a/lib/transform/nullableTransformer.ts +++ b/lib/transform/nullableTransformer.ts @@ -49,6 +49,9 @@ const transformNullable = (s: Schema, jsonLoader: JsonLoader, defaultNullable?: // Originally it's not nullable if (nullable === false) { + if (sch.type === undefined) { + sch.type = "object"; + } return s; } @@ -66,6 +69,9 @@ const transformNullable = (s: Schema, jsonLoader: JsonLoader, defaultNullable?: } as Schema; } else { sch.nullable = true; + if (sch.type === undefined) { + sch.type = "object"; + } return sch; } }; diff --git a/lib/transform/pureObjectTransformer.ts b/lib/transform/pureObjectTransformer.ts index 4b80c732..b71f0786 100644 --- a/lib/transform/pureObjectTransformer.ts +++ b/lib/transform/pureObjectTransformer.ts @@ -9,7 +9,7 @@ export const pureObjectTransformer: GlobalTransformer = { (sch.properties === undefined || Object.keys(sch.properties).length === 0) && sch.additionalProperties === undefined ) { - delete sch.type; + sch.type = ["object", "array"] } } }, diff --git a/package-lock.json b/package-lock.json index 99ab7539..bf9a1932 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2543,6 +2543,14 @@ } } }, + "ajv-formats": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-0.6.1.tgz", + "integrity": "sha512-Zze3O7jGabuY4ospj2s8Jvjf5aGNaiwrFRqEdWdHs9oJd6IFXuWuS3ZHbJjyJvkjlDGncGQEBYnPRY9DoyaXgA==", + "requires": { + "ajv": "^7.0.0-beta.7" + } + }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", diff --git a/package.json b/package.json index 06f54eee..98b259d4 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@microsoft.azure/autorest-extension-base": "1.0.13", "@azure-tools/openapi-tools-common": "^1.2.2", "ajv": "7.0.0-beta.8", + "ajv-formats": "^0.6.1", "commonmark": "^0.29.0", "glob": "^7.1.2", "globby": "^9.2.0",