Feature: Move schema validator to ajv and ajv-errors for custom messages (#3936)

This commit is contained in:
Timothee Guerin 2021-03-08 13:23:22 -08:00 коммит произвёл GitHub
Родитель 4246d1aba6
Коммит dcb1684cb2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
29 изменённых файлов: 1135 добавлений и 1067 удалений

Просмотреть файл

@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@autorest/common",
"comment": "Add `trackWarning` functionality to logger",
"type": "patch"
}
],
"packageName": "@autorest/common",
"email": "tiguerin@microsoft.com"
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@autorest/core",
"comment": "Update swagger schema validator to use new system(`ajv` & `ajv-errors`) providing more relevant information",
"type": "patch"
}
],
"packageName": "@autorest/core",
"email": "tiguerin@microsoft.com"
}

Просмотреть файл

@ -0,0 +1,16 @@
{
"changes": [
{
"packageName": "@autorest/schemas",
"comment": "Update schemas to jsonschema draft-07.",
"type": "minor"
},
{
"packageName": "@autorest/schemas",
"comment": "Update schemas to use if else instead of oneof to provide more helpful validation.",
"type": "minor"
}
],
"packageName": "@autorest/schemas",
"email": "tiguerin@microsoft.com"
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@autorest/test-utils",
"comment": "Add new util to create DataHandle",
"type": "minor"
}
],
"packageName": "@autorest/test-utils",
"email": "tiguerin@microsoft.com"
}

Просмотреть файл

@ -48,6 +48,9 @@ dependencies:
'@types/yargs': 15.0.13
'@typescript-eslint/eslint-plugin': 4.14.2_e5f964fa93e839b7a7927397f6cb9cb1
'@typescript-eslint/parser': 4.14.2_eslint@7.19.0+typescript@4.1.3
ajv: 7.1.1
ajv-errors: 2.0.0_ajv@7.1.1
ajv-formats: 1.5.1
body-parser: 1.19.0
chalk: 4.1.0
command-exists: 1.2.9
@ -107,7 +110,6 @@ dependencies:
yaml-ast-parser: 0.0.43
yargs: 16.2.0
yarn: 1.22.10
z-schema: 5.0.0
lockfileVersion: 5.2
packages:
/@azure-tools/async-io/3.0.253:
@ -1479,6 +1481,20 @@ packages:
node: '>= 4.0.0'
resolution:
integrity: sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==
/ajv-errors/2.0.0_ajv@7.1.1:
dependencies:
ajv: 7.1.1
dev: false
peerDependencies:
ajv: ^7.0.0
resolution:
integrity: sha512-Qi+I07e2Kc7Tgza7cZMvROyWuWmandN0BLbAiQUGLAMN/IfwIyg5kjg1qz/+q7p+uJ/x3THplnuGmihg/+WnXg==
/ajv-formats/1.5.1:
dependencies:
ajv: 7.1.1
dev: false
resolution:
integrity: sha512-s1RBVF4HZd2UjGkb6t6uWoXjf6o7j7dXPQIL7vprcIT/67bTD6+5ocsU0UKShS2qWxueGDWuGfKHfOxHWrlTQg==
/ajv-keywords/3.5.2_ajv@6.12.6:
dependencies:
ajv: 6.12.6
@ -1505,6 +1521,15 @@ packages:
dev: false
resolution:
integrity: sha512-xzzzaqgEQfmuhbhAoqjJ8T/1okb6gAzXn/eQRNpAN1AEUoHJTNF9xCDRTtf/s3SKldtZfa+RJeTs+BQq+eZ/sw==
/ajv/7.1.1:
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
uri-js: 4.4.1
dev: false
resolution:
integrity: sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==
/align-text/0.1.4:
dependencies:
kind-of: 3.2.2
@ -9958,7 +9983,7 @@ packages:
ts-node: '*'
webpack-cli: '*'
resolution:
integrity: sha512-zn1e426O60et7E7+UyeBq6Rey9550paWPTGAUk+pTr+YpjckVIoKRoSqSIfoFhwf9Y5PblAe9bKTj4d8SEDuEA==
integrity: sha512-QfELcsPUqDpOSGfMho2oSGzcGCt89Da8V5LpSXRqN2y8UZRg6Z4bjoT3DXPUAvbGDqppR6ydCJlrLW4WIWKtMg==
tarball: file:projects/autorest.tgz
version: 0.0.0
file:projects/codegen.tgz_prettier@2.2.1+ts-node@9.1.1:
@ -10072,7 +10097,7 @@ packages:
peerDependencies:
prettier: '*'
resolution:
integrity: sha512-ICQYsvvinkuAM42zp7jOoB+n+w3xqxTYbonvjNu3PoBGJuVTPNbAZ5I5kx65XvtVg3N4POEhAsS0iEuwabmV8w==
integrity: sha512-Jo2B0ovccrO54g8I6Slhpz/incztxEnm/r52st+81Fd8/C+02q5sDEUMO84uM9RHNDH79vq+BYZn1PsXz5k45Q==
tarball: file:projects/compare.tgz
version: 0.0.0
file:projects/configuration.tgz_prettier@2.2.1+ts-node@9.1.1:
@ -10100,7 +10125,7 @@ packages:
prettier: '*'
ts-node: '*'
resolution:
integrity: sha512-/618yDQpSPf7x6bJkMyiKhltfS8j/YBjBxXydeDAnwYF1nN7I1kKLTJteCBRrZKiK5tLJFDuWm16x9PB85r+oA==
integrity: sha512-LKUi6Mrflh5w0+U/PWjn1jtU7bE2jLqvND53aQf/hWMJmVmBcDFqXNS1vL2GUXNXjg/nWHsVG+Acu9agQwrPJg==
tarball: file:projects/configuration.tgz
version: 0.0.0
file:projects/core.tgz_ts-node@9.1.1:
@ -10120,6 +10145,9 @@ packages:
'@types/webpack': 4.41.26
'@typescript-eslint/eslint-plugin': 4.14.2_e5f964fa93e839b7a7927397f6cb9cb1
'@typescript-eslint/parser': 4.14.2_eslint@7.19.0+typescript@4.1.3
ajv: 7.1.1
ajv-errors: 2.0.0_ajv@7.1.1
ajv-formats: 1.5.1
commonmark: 0.27.0
compare-versions: 3.6.0
copy-webpack-plugin: 7.0.0_webpack@5.18.0
@ -10156,7 +10184,7 @@ packages:
peerDependencies:
ts-node: '*'
resolution:
integrity: sha512-QR7EBmyjKQCJfEkVZ/SgNyOPjgrtWDRW8nkJTexjXt6u++T17G2JSJ7l0v9+LDypazQ6Cebpq3lmQVUHcqrBMw==
integrity: sha512-UKacK+vMh/BPaGatzLaMSPbd0qt4LCnnoEScLtb7jpHBvnVRZMVhH4bj6pjsUOUcIx4Kuxsh0fmjJkw4Nmmbsg==
tarball: file:projects/core.tgz
version: 0.0.0
file:projects/datastore.tgz_prettier@2.2.1+ts-node@9.1.1:
@ -10440,11 +10468,12 @@ packages:
peerDependencies:
prettier: '*'
resolution:
integrity: sha512-Nr/rOhVjZc0golARSPIQKP6Po09CdqVmXJF9NEfypoN2dthsy31ki4ls1D1YB0aHFwonpZLLTnDuFhUQ5GBiVA==
integrity: sha512-dsmOlrJ7onRNMwAoVVwpacSEJbkLWYEEogGtNVi5aHh6VkhAFpyZKzJlrpOZ4UWasiCCFHr3w2tBzqVAMnox2A==
tarball: file:projects/test-public-packages.tgz
version: 0.0.0
file:projects/test-utils.tgz_prettier@2.2.1+ts-node@9.1.1:
dependencies:
'@azure-tools/tasks': 3.0.253
'@types/jest': 26.0.20
eslint: 7.19.0
eslint-plugin-prettier: 3.2.0_eslint@7.19.0+prettier@2.2.1
@ -10460,7 +10489,7 @@ packages:
prettier: '*'
ts-node: '*'
resolution:
integrity: sha512-8nMXR/fQrY7cWzKlnWAWLNAPsCqTy218Yq76aUySEg/K65B+nGDBNIZewWlUh3Dy4q/xEwC+EN8tG9/SSwQ9EA==
integrity: sha512-avGo/LW8WilQ37XPpPHqyRVtrS6FS4Oxm2NnkvMKV6v3UA8sQMMLH4QglVnyVpZDP8ULsCZwWrc7kVe+iYlZKA==
tarball: file:projects/test-utils.tgz
version: 0.0.0
registry: ''
@ -10514,6 +10543,9 @@ specifiers:
'@types/yargs': ~15.0.12
'@typescript-eslint/eslint-plugin': ^4.12.0
'@typescript-eslint/parser': ^4.12.0
ajv: ^7.1.1
ajv-errors: ^2.0.0
ajv-formats: ^1.5.1
body-parser: ^1.19.0
chalk: ^4.1.0
command-exists: ~1.2.9
@ -10573,4 +10605,3 @@ specifiers:
yaml-ast-parser: 0.0.43
yargs: ~16.2.0
yarn: 1.22.10
z-schema: ^5.0.0

Просмотреть файл

@ -63,6 +63,9 @@
"@types/webpack": "~4.41.26",
"@typescript-eslint/eslint-plugin": "^4.12.0",
"@typescript-eslint/parser": "^4.12.0",
"ajv": "^7.1.1",
"ajv-errors": "^2.0.0",
"ajv-formats": "^1.5.1",
"commonmark": "^0.27.0",
"compare-versions": "^3.4.0",
"copy-webpack-plugin": "^7.0.0",
@ -89,7 +92,6 @@
"webpack-cli": "~4.4.0",
"webpack-node-externals": "~2.5.2",
"webpack": "~5.18.0",
"yaml-ast-parser": "0.0.43",
"z-schema": "^5.0.0"
"yaml-ast-parser": "0.0.43"
}
}

Просмотреть файл

@ -18,7 +18,7 @@ import {
arrayOf,
extendAutorestConfiguration,
} from "@autorest/configuration";
import { AutorestError, AutorestLogger } from "@autorest/common";
import { AutorestError, AutorestLogger, AutorestWarning } from "@autorest/common";
import { Message } from "../message";
import { AutorestCoreLogger } from "./logger";
@ -60,6 +60,10 @@ export class AutorestContext implements AutorestLogger {
this.logger.trackError(error);
}
public trackWarning(error: AutorestWarning) {
this.logger.trackWarning(error);
}
public Message(m: Message) {
void this.logger.message(m);
}

Просмотреть файл

@ -1,6 +1,6 @@
import { Channel, Message, Range, SourceLocation } from "../message";
import { BlameTree, stringify, Stringify, TryDecodeEnhancedPositionFromName } from "@azure-tools/datastore";
import { AutorestError } from "@autorest/common";
import { AutorestError, AutorestWarning } from "@autorest/common";
import { AutorestConfiguration } from "@autorest/configuration";
import { From } from "linq-es2015";
import { Suppressor } from "../pipeline/suppression";
@ -34,11 +34,21 @@ export class AutorestCoreLogger {
});
}
public trackWarning(error: AutorestWarning) {
void this.message({
Channel: Channel.Warning,
Text: error.message,
Source: error.source?.map((x) => ({ document: x.document, Position: x.position })),
Details: error.details,
});
}
public trackError(error: AutorestError) {
void this.message({
Channel: Channel.Error,
Text: error.message,
Source: error.source?.map((x) => ({ document: x.document, Position: x.position })),
Details: error.details,
});
}

Просмотреть файл

@ -51,7 +51,7 @@ import {
import { createTreeShakerPlugin } from "./plugins/tree-shaker/tree-shaker";
import { createApiVersionParameterHandlerPlugin } from "./plugins/version-param-handler";
import { createJsonToYamlPlugin, createYamlToJsonPlugin } from "./plugins/yaml-and-json";
import { createOpenApiSchemaValidatorPlugin, createSwaggerSchemaValidatorPlugin } from "./schema-validation";
import { createOpenApiSchemaValidatorPlugin, createSwaggerSchemaValidatorPlugin } from "./plugins/schema-validation";
import { createHash } from "crypto";
import { isCached, readCache, writeCache } from "./pipeline-cache";
import { values } from "@azure-tools/linq";

Просмотреть файл

@ -0,0 +1 @@
export * from "./schema-validation-plugins";

Просмотреть файл

@ -0,0 +1,146 @@
import { CompilePosition, DataHandle, JsonPath, parseJsonPointer } from "@azure-tools/datastore";
import Ajv, { AnySchemaObject, ErrorObject } from "ajv";
import ajvErrors from "ajv-errors";
import { Position } from "source-map";
export interface ValidationError extends ErrorObject {
path: JsonPath;
}
export interface PositionedValidationError extends ValidationError {
position: Position;
}
export abstract class JsonSchemaValidator {
protected ajv: Ajv;
public constructor() {
this.ajv = new Ajv({ allErrors: true, strict: false });
ajvErrors(this.ajv, {});
}
public abstract get schema(): AnySchemaObject;
public validate(spec: unknown): ValidationError[] {
const validate = this.ajv.compile(this.schema);
const valid = validate(spec);
if (valid || !validate.errors) {
return [];
} else {
return condenseErrors(validate.errors).map((x) => ({ ...x, path: parseJsonPointer(x.dataPath) }));
}
}
public async validateFile(file: DataHandle): Promise<PositionedValidationError[]> {
const spec = await file.ReadObject();
const errors = this.validate(spec);
const mappedErrors = errors.map((x) => extendWithPosition(x, file));
return Promise.all(mappedErrors);
}
}
async function extendWithPosition(error: ValidationError, file: DataHandle): Promise<PositionedValidationError> {
return {
...error,
position: await CompilePosition({ path: error.path }, file),
};
}
const IGNORE_ERROR = new Set(["if"]);
export function condenseErrors(errors: ErrorObject[]): ErrorObject[] {
const tree: any = {};
function countFor(dataPath: string, message: string) {
return tree[dataPath][message].length;
}
for (const err of errors.filter((x) => !IGNORE_ERROR.has(x.keyword))) {
const { dataPath, message } = err;
if (!message) {
continue;
}
if (tree[dataPath] && tree[dataPath][message]) {
tree[dataPath][message].push(err);
} else if (tree[dataPath]) {
tree[dataPath][message] = [err];
} else {
tree[dataPath] = {
[message]: [err],
};
}
}
const dataPaths = Object.keys(tree);
return dataPaths.reduce((res: ValidationError[], path) => {
const messages = Object.keys(tree[path]);
const mostFrequentMessageNames = messages.reduce(
(obj, msg) => {
const count = countFor(path, msg);
if (count > obj.max) {
return {
messages: [msg],
max: count,
};
} else if (count === obj.max) {
obj.messages.push(msg);
return obj;
} else {
return obj;
}
},
{ max: 0, messages: [] as string[] },
).messages;
const mostFrequentMessages = mostFrequentMessageNames.map((name) => tree[path][name]);
const condensedErrors = mostFrequentMessages.map((messages) => {
return messages.reduce((prev: any, err: ValidationError) => {
const obj = Object.assign({}, prev, {
params: mergeParams(prev.params, err.params),
});
if (!prev.params && !err.params) {
delete obj.params;
}
return obj;
});
});
return res.concat(condensedErrors);
}, []);
}
function mergeParams(objA: any = {}, objB: any = {}) {
if (!objA && !objB) {
return undefined;
}
const res: any = {};
for (const k of Object.keys(objA)) {
res[k] = arrayify(objA[k]);
}
for (const k of Object.keys(objB)) {
if (res[k]) {
res[k] = res[k].concat(arrayify(objB[k]));
} else {
res[k] = arrayify(objB[k]);
}
}
return res;
}
function arrayify<T>(thing: T | T[] | undefined): T[] | undefined {
if (thing === undefined || thing === null) {
return undefined;
}
if (Array.isArray(thing)) {
return thing;
} else {
return [thing];
}
}

Просмотреть файл

@ -0,0 +1,91 @@
import { DataHandle } from "@azure-tools/datastore";
import { omit } from "lodash";
import { createDataHandle } from "@autorest/test-utils";
import { OpenApi3SchemaValidator } from "./openapi3-schema-validator";
const baseSwaggerSpec = {
openapi: "3.0.0",
info: {
title: "Base spec",
version: "1.0",
},
paths: {},
};
describe("OpenAPI3 schema validator", () => {
const validator = new OpenApi3SchemaValidator();
it("returns no errors if the spec is valid", () => {
expect(validator.validate(baseSwaggerSpec)).toHaveLength(0);
});
it("returns error if the info field is missing", () => {
const errors = validator.validate(omit(baseSwaggerSpec, "info"));
expect(errors).toEqual([
{
dataPath: "",
keyword: "required",
message: "should have required property 'info'",
params: { missingProperty: "info" },
path: [],
schemaPath: "#/required",
},
]);
});
it("combines erros", () => {
const errors = validator.validate({
...baseSwaggerSpec,
info: { ...baseSwaggerSpec.info, invalidProp: "foo", otherProp: "bar" },
});
expect(errors).toEqual([
{
dataPath: "/info",
keyword: "additionalProperties",
message: "should NOT have additional properties",
params: { additionalProperty: ["invalidProp", "otherProp"] },
path: ["info"],
schemaPath: "#/additionalProperties",
},
]);
});
it("returns custom error if path is not starting with /", () => {
const errors = validator.validate({ ...baseSwaggerSpec, paths: { "foo/bar": {} } });
expect(errors).toEqual([
{
dataPath: "/paths/foo~1bar",
keyword: "errorMessage",
message: 'should only have path names that start with `/` but found "foo/bar"',
params: expect.anything(),
path: ["paths", "foo/bar"],
schemaPath: "#/additionalProperties/errorMessage",
},
]);
});
describe("when validating a file", () => {
let file: DataHandle;
beforeEach(() => {
const spec = {
...baseSwaggerSpec,
info: {
...baseSwaggerSpec.info,
unexpectedProperty: "value",
},
};
file = createDataHandle(JSON.stringify(spec, null, 2));
});
it("returns the line number where the error is", async () => {
const errors = await validator.validateFile(file);
expect(errors).toHaveLength(1);
expect(errors[0].position).toEqual(
expect.objectContaining({
column: 2,
length: 6,
}),
);
});
});
});

Просмотреть файл

@ -0,0 +1,13 @@
import addFormats from "ajv-formats";
import { JsonSchemaValidator } from "./json-schema-validator";
export class OpenApi3SchemaValidator extends JsonSchemaValidator {
public constructor() {
super();
addFormats(this.ajv);
}
public get schema() {
return require(`@autorest/schemas/openapi3-schema.json`);
}
}

Просмотреть файл

@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DataHandle } from "@azure-tools/datastore";
import { OperationAbortedException } from "@autorest/common";
import { Channel } from "../../../message";
import { createPerFilePlugin, PipelinePlugin } from "../../common";
import { AutorestContext } from "../../../configuration";
import { SwaggerSchemaValidator } from "./swagger-schema-validator";
import { PositionedValidationError } from "./json-schema-validator";
import { OpenApi3SchemaValidator } from "./openapi3-schema-validator";
export const SCHEMA_VIOLATION_ERROR_CODE = "schema_violation";
export function createSwaggerSchemaValidatorPlugin(): PipelinePlugin {
const swaggerValidator = new SwaggerSchemaValidator();
return createPerFilePlugin(async (config) => async (fileIn, sink) => {
const obj = await fileIn.ReadObject<any>();
const isSecondary = !!obj["x-ms-secondary-file"];
const errors = await swaggerValidator.validateFile(fileIn);
if (errors.length > 0) {
for (const error of errors) {
// secondary files have reduced schema compliancy, so we're gonna just warn them for now.
logValidationError(config, fileIn, error, isSecondary ? "warning" : "error");
}
if (!isSecondary) {
throw new OperationAbortedException();
}
}
return sink.Forward(fileIn.Description, fileIn);
});
}
export function createOpenApiSchemaValidatorPlugin(): PipelinePlugin {
const validator = new OpenApi3SchemaValidator();
return createPerFilePlugin(async (context) => async (fileIn, sink) => {
const obj = await fileIn.ReadObject<any>();
const isSecondary = !!obj["x-ms-secondary-file"];
const markErrorAsWarnings = context.config["mark-oai3-errors-as-warnings"];
const errors = await validator.validateFile(fileIn);
if (errors.length > 0) {
for (const error of errors) {
const level = markErrorAsWarnings || isSecondary ? "warning" : "error";
logValidationError(context, fileIn, error as any, level);
}
if (!isSecondary) {
context.Message({
Channel: Channel.Error,
Plugin: "schema-validator-openapi",
Text: [
`Unrecoverable schema validation errors were encountered in OpenAPI 3 input files.`,
`You can use --markOpenAPI3ErrorsAsWarning to keep mark as warning and let autorest keep going.`,
`If you believe this the validation error is incorrect, please open an issue at https://github.com/Azure/autorest`,
`NOTE: in the future this flag will be removed and validation error will fail the pipeline.`,
].join("\n"),
});
throw new OperationAbortedException();
}
}
return sink.Forward(fileIn.Description, fileIn);
});
}
const IGNORED_AJV_PARAMS = new Set(["type", "errors"]);
const logValidationError = (
config: AutorestContext,
fileIn: DataHandle,
error: PositionedValidationError,
type: "error" | "warning",
) => {
const messageLines = [`Schema violation: ${error.message} (${error.path.join(" > ")})`];
for (const [name, value] of Object.entries(error.params).filter(([name]) => !IGNORED_AJV_PARAMS.has(name))) {
const formattedValue = Array.isArray(value) ? [...new Set(value)].join(", ") : value;
messageLines.push(` ${name}: ${formattedValue}`);
}
const msg = {
code: SCHEMA_VIOLATION_ERROR_CODE,
message: messageLines.join("\n"),
source: [{ document: fileIn.key, position: error.position }],
details: error,
};
if (type == "warning") {
config.trackWarning(msg);
} else {
config.trackError(msg);
}
};

Просмотреть файл

@ -0,0 +1,91 @@
import { DataHandle } from "@azure-tools/datastore";
import { omit } from "lodash";
import { SwaggerSchemaValidator } from "./swagger-schema-validator";
import { createDataHandle } from "@autorest/test-utils";
const baseSwaggerSpec = {
swagger: "2.0",
info: {
title: "Base spec",
version: "1.0",
},
paths: {},
};
describe("Swagger schema validator", () => {
const validator = new SwaggerSchemaValidator();
it("returns no errors if the spec is valid", () => {
expect(validator.validate(baseSwaggerSpec)).toHaveLength(0);
});
it("returns error if the info field is missing", () => {
const errors = validator.validate(omit(baseSwaggerSpec, "info"));
expect(errors).toEqual([
{
dataPath: "",
keyword: "required",
message: "should have required property 'info'",
params: { missingProperty: "info" },
path: [],
schemaPath: "#/required",
},
]);
});
it("combines erros", () => {
const errors = validator.validate({
...baseSwaggerSpec,
info: { ...baseSwaggerSpec.info, invalidProp: "foo", otherProp: "bar" },
});
expect(errors).toEqual([
{
dataPath: "/info",
keyword: "additionalProperties",
message: "should NOT have additional properties",
params: { additionalProperty: ["invalidProp", "otherProp"] },
path: ["info"],
schemaPath: "#/additionalProperties",
},
]);
});
it("returns custom error if path is not starting with /", () => {
const errors = validator.validate({ ...baseSwaggerSpec, paths: { "foo/bar": {} } });
expect(errors).toEqual([
{
dataPath: "/paths/foo~1bar",
keyword: "errorMessage",
message: 'should only have path names that start with `/` but found "foo/bar"',
params: expect.anything(),
path: ["paths", "foo/bar"],
schemaPath: "#/additionalProperties/errorMessage",
},
]);
});
describe("when validating a file", () => {
let file: DataHandle;
beforeEach(() => {
const spec = {
...baseSwaggerSpec,
info: {
...baseSwaggerSpec.info,
unexpectedProperty: "value",
},
};
file = createDataHandle(JSON.stringify(spec, null, 2));
});
it("returns the line number where the error is", async () => {
const errors = await validator.validateFile(file);
expect(errors).toHaveLength(1);
expect(errors[0].position).toEqual(
expect.objectContaining({
column: 2,
length: 6,
}),
);
});
});
});

Просмотреть файл

@ -0,0 +1,19 @@
import addFormats from "ajv-formats";
import { JsonSchemaValidator } from "./json-schema-validator";
export class SwaggerSchemaValidator extends JsonSchemaValidator {
public constructor() {
super();
addFormats(this.ajv);
this.ajv.addSchema(require("@autorest/schemas/swagger.json"), "http://json.schemastore.org/swagger-2.0");
this.ajv.addSchema(
require("@autorest/schemas/example-schema.json"),
"https://raw.githubusercontent.com/Azure/autorest/master/schema/example-schema.json",
);
}
public get schema() {
return require(`@autorest/schemas/swagger-extensions.json`);
}
}

Просмотреть файл

@ -1,114 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* eslint-disable @typescript-eslint/no-var-requires */
import { DataHandle, parseJsonPointer } from "@azure-tools/datastore";
import SchemaValidator from "z-schema";
import { OperationAbortedException } from "@autorest/common";
import { Channel } from "../message";
import { createPerFilePlugin, PipelinePlugin } from "./common";
import { AutorestContext } from "../configuration";
export function createSwaggerSchemaValidatorPlugin(): PipelinePlugin {
const validator = new SchemaValidator({ breakOnFirstError: false });
const extendedSwaggerSchema = require(`@autorest/schemas/swagger-extensions.json`);
(<any>validator).setRemoteReference(
"http://json.schemastore.org/swagger-2.0",
require(`@autorest/schemas/swagger.json`),
);
(<any>validator).setRemoteReference(
"https://raw.githubusercontent.com/Azure/autorest/master/schema/example-schema.json",
require(`@autorest/schemas/example-schema.json`),
);
return createPerFilePlugin(async (config) => async (fileIn, sink) => {
const obj = await fileIn.ReadObject<any>();
const isSecondary = !!obj["x-ms-secondary-file"];
const errors = await validateSchema(obj, extendedSwaggerSchema, validator);
if (errors !== null) {
for (const error of errors) {
// secondary files have reduced schema compliancy, so we're gonna just warn them for now.
logValidationError(config, fileIn, error, "schema-validator-swagger", isSecondary ? "warning" : "error");
}
if (!isSecondary) {
throw new OperationAbortedException();
}
}
return sink.Forward(fileIn.Description, fileIn);
});
}
/* @internal */
export function createOpenApiSchemaValidatorPlugin(): PipelinePlugin {
const validator = new SchemaValidator({ breakOnFirstError: false });
const extendedOpenApiSchema = require(`@autorest/schemas/openapi3-schema.json`);
return createPerFilePlugin(async (context) => async (fileIn, sink) => {
const obj = await fileIn.ReadObject<any>();
const isSecondary = !!obj["x-ms-secondary-file"];
const markErrorAsWarnings = context.config["mark-oai3-errors-as-warnings"];
const errors = await validateSchema(obj, extendedOpenApiSchema, validator);
if (errors !== null) {
for (const error of errors) {
const level = markErrorAsWarnings || isSecondary ? "warning" : "error";
logValidationError(context, fileIn, error, "schema-validator-openapi", level);
}
if (!isSecondary) {
context.Message({
Channel: Channel.Error,
Plugin: "schema-validator-openapi",
Text: [
`Unrecoverable schema validation errors were encountered in OpenAPI 3 input files.`,
`You can use --markOpenAPI3ErrorsAsWarning to keep mark as warning and let autorest keep going.`,
`If you believe this the validation error is incorrect, please open an issue at https://github.com/Azure/autorest`,
`NOTE: in the future this flag will be removed and validation error will fail the pipeline.`,
].join("\n"),
});
throw new OperationAbortedException();
}
}
return sink.Forward(fileIn.Description, fileIn);
});
}
interface ValidationError {
code: "OBJECT_ADDITIONAL_PROPERTIES" | "ONE_OF_MISSING" | string;
params: string[];
message: string;
path: string;
inner?: ValidationError[];
}
/**
* @param value Value to validate.
* @param schema Schema as a javascript object.
* @param validator Schema validator.
*/
const validateSchema = (
value: unknown,
schema: unknown,
validator: SchemaValidator,
): Promise<ValidationError[] | null> => {
return new Promise<ValidationError[] | null>((res) =>
validator.validate(value, schema, (err, valid) => res(valid ? null : err)),
);
};
const logValidationError = (
config: AutorestContext,
fileIn: DataHandle,
error: ValidationError,
pluginName: string,
type: "error" | "warning",
) => {
const path = parseJsonPointer(error.path);
config.Message({
Channel: type == "warning" ? Channel.Warning : Channel.Error,
Details: error,
Plugin: pluginName,
Source: [{ document: fileIn.key, Position: <any>{ path } }],
Text: `Schema violation: ${error.message} (${path.join(" > ")})`,
});
};

Просмотреть файл

@ -0,0 +1,9 @@
{
"swagger": "2.0",
"info": {
"title": "Base spec",
"version": "1.0",
"unexpectedProperty": "value"
},
"paths": {}
}

Просмотреть файл

@ -38,10 +38,33 @@ describe("EndToEnd", () => {
},
});
const messages: Message[] = [];
const channels = new Set([
Channel.Information,
Channel.Warning,
Channel.Error,
Channel.Fatal,
Channel.Debug,
Channel.Verbose,
]);
autoRest.Message.Subscribe((_, message) => {
if (channels.has(message.Channel)) {
messages.push(message);
}
});
// TODO: generate for all, probe results
const success = await autoRest.Process().finish;
assert.strictEqual(success, true);
if (!success) {
// eslint-disable-next-line no-console
console.log("Messages", messages);
fail("Autorest didn't complete with success.");
}
expect(success).toBe(true);
});
it("other configuration scenario", async () => {

Просмотреть файл

@ -1,12 +1,9 @@
{
"title": "A JSON Schema for Composite Swagger in Swagger 2.0 API.",
"id": "https://raw.githubusercontent.com/Azure/autorest/master/schema/composite-swagger.json#",
"$schema": "http://json-schema.org/draft-04/schema#",
"$id": "https://raw.githubusercontent.com/Azure/autorest/master/schema/composite-swagger.json#",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"info",
"documents"
],
"required": ["info", "documents"],
"description": "Describes the title, description of the Composite Swagger. It provides a mechanism to bind/package clients generated from multiple swaggers together into one composite client.",
"additionalProperties": false,
"properties": {
@ -17,7 +14,7 @@
"type": "array",
"minItems": 1,
"description": "Describes the list of relative or absolute swagger document urls or file paths.",
"items": {
"items": {
"type": "string",
"description": "A relative or absolute url or file path to a swagger document to be included in the list."
}
@ -26,10 +23,7 @@
"info": {
"type": "object",
"description": "General information about the API.",
"required": [
"title",
"description"
],
"required": ["title", "description"],
"additionalProperties": false,
"patternProperties": {
"^x-(?!ms-).*$": {
@ -91,9 +85,7 @@
},
"license": {
"type": "object",
"required": [
"name"
],
"required": ["name"],
"additionalProperties": false,
"properties": {
"name": {

Просмотреть файл

@ -1,9 +1,9 @@
{
"title": "A JSON Schema for x-ms-examples extension in Swagger 2.0 API.",
"id": "https://raw.githubusercontent.com/Azure/autorest/master/packages/libs/autorest-schemas/example-schema.json#",
"$schema": "http://json-schema.org/draft-04/schema#",
"$id": "https://raw.githubusercontent.com/Azure/autorest/master/packages/libs/autorest-schemas/example-schema.json#",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [ "parameters", "responses" ],
"required": ["parameters", "responses"],
"description": "Describes the format of an example defined using the x-ms-examples extension.",
"additionalProperties": false,
"properties": {
@ -90,7 +90,7 @@
"$ref": "#/xmsResponseHeaderExample"
},
"body": {
"type": [ "object", "number", "array", "integer", "string", "boolean", "null" ],
"type": ["object", "number", "array", "integer", "string", "boolean", "null"],
"description": "Example of the response body if applicable for the operation. Don't specify the body if the server is not sending a response body. The outcome from `JSON.parse()`, needs to be described over here."
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,7 +1,7 @@
{
"title": "A JSON Schema for Swagger 2.0 API.",
"id": "https://raw.githubusercontent.com/Azure/autorest/master/schema/swagger-extensions.json#",
"$schema": "http://json-schema.org/draft-04/schema#",
"$id": "https://raw.githubusercontent.com/Azure/autorest/master/schema/swagger-extensions.json#",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["swagger", "info", "paths"],
"additionalProperties": false,
@ -192,7 +192,10 @@
"$ref": "#/definitions/pathItem"
}
},
"additionalProperties": false
"additionalProperties": {
"not": true,
"errorMessage": "should only have path names that start with `/` but found ${0#}"
}
},
"definitions": {
"type": "object",
@ -400,14 +403,15 @@
}
},
"responseValue": {
"oneOf": [
{
"$ref": "#/definitions/response"
},
{
"$ref": "#/definitions/jsonReference"
}
]
"if": {
"required": ["$ref"]
},
"then": {
"$ref": "#/definitions/jsonReference"
},
"else": {
"$ref": "#/definitions/response"
}
},
"response": {
"type": "object",
@ -1063,30 +1067,138 @@
}
},
"nonBodyParameter": {
"type": "object",
"required": ["name", "in", "type"],
"oneOf": [
"allOf": [
{
"$ref": "#/definitions/headerParameterSubSchema"
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["header"]
}
}
},
"then": {
"$ref": "#/definitions/headerParameterSubSchema"
}
},
{
"$ref": "#/definitions/formDataParameterSubSchema"
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["formData"]
}
}
},
"then": {
"$ref": "#/definitions/formDataParameterSubSchema"
}
},
{
"$ref": "#/definitions/queryParameterSubSchema"
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["query"]
}
}
},
"then": {
"$ref": "#/definitions/queryParameterSubSchema"
}
},
{
"$ref": "#/definitions/pathParameterSubSchema"
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["path"]
}
}
},
"then": {
"$ref": "#/definitions/pathParameterSubSchema"
}
}
]
},
"parameter": {
"oneOf": [
"allOf": [
{
"$ref": "#/definitions/bodyParameter"
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["body"]
}
}
},
"then": {
"$ref": "#/definitions/bodyParameter"
}
},
{
"$ref": "#/definitions/nonBodyParameter"
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["header"]
}
}
},
"then": {
"$ref": "#/definitions/headerParameterSubSchema"
}
},
{
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["formData"]
}
}
},
"then": {
"$ref": "#/definitions/formDataParameterSubSchema"
}
},
{
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["query"]
}
}
},
"then": {
"$ref": "#/definitions/queryParameterSubSchema"
}
},
{
"if": {
"required": ["in"],
"properties": {
"in": {
"enum": ["path"]
}
}
},
"then": {
"$ref": "#/definitions/pathParameterSubSchema"
}
},
{
"then": {
"type": "object",
"required": ["in"],
"properties": {
"in": {
"enum": ["body", "header", "formData", "query", "path"]
}
}
}
}
],
"properties": {
@ -1114,58 +1226,58 @@
"type": "string"
},
"title": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/title"
"$ref": "http://json-schema.org/draft-07/schema#/properties/title"
},
"description": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/description"
"$ref": "http://json-schema.org/draft-07/schema#/properties/description"
},
"default": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/default"
"$ref": "http://json-schema.org/draft-07/schema#/properties/default"
},
"multipleOf": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf"
"$ref": "http://json-schema.org/draft-07/schema#/properties/multipleOf"
},
"maximum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/maximum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/maximum"
},
"exclusiveMaximum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum"
"$ref": "#/definitions/exclusiveMaximum"
},
"minimum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/minimum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/minimum"
},
"exclusiveMinimum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum"
"$ref": "#/definitions/exclusiveMinimum"
},
"maxLength": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minLength": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"pattern": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/pattern"
"$ref": "http://json-schema.org/draft-07/schema#/properties/pattern"
},
"maxItems": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minItems": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"uniqueItems": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems"
"$ref": "http://json-schema.org/draft-07/schema#/properties/uniqueItems"
},
"maxProperties": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minProperties": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"required": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/stringArray"
},
"enum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/enum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/enum"
},
"x-ms-enum": {
"$ref": "#/definitions/xmsEnum"
@ -1188,7 +1300,7 @@
"default": {}
},
"type": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/type"
"$ref": "http://json-schema.org/draft-07/schema#/properties/type"
},
"items": {
"anyOf": [
@ -1274,16 +1386,16 @@
"type": "string"
},
"title": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/title"
"$ref": "http://json-schema.org/draft-07/schema#/properties/title"
},
"description": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/description"
"$ref": "http://json-schema.org/draft-07/schema#/properties/description"
},
"default": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/default"
"$ref": "http://json-schema.org/draft-07/schema#/properties/default"
},
"required": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/stringArray"
},
"type": {
"type": "string",
@ -1464,14 +1576,15 @@
"description": "The parameters needed to send a valid API call.",
"additionalItems": false,
"items": {
"oneOf": [
{
"$ref": "#/definitions/parameter"
},
{
"$ref": "#/definitions/jsonReference"
}
]
"if": {
"required": ["$ref"]
},
"then": {
"$ref": "#/definitions/jsonReference"
},
"else": {
"$ref": "#/definitions/parameter"
}
},
"uniqueItems": true
},
@ -1495,49 +1608,51 @@
"default": "csv"
},
"title": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/title"
"$ref": "http://json-schema.org/draft-07/schema#/properties/title"
},
"description": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/description"
"$ref": "http://json-schema.org/draft-07/schema#/properties/description"
},
"default": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/default"
"$ref": "http://json-schema.org/draft-07/schema#/properties/default"
},
"multipleOf": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf"
"$ref": "http://json-schema.org/draft-07/schema#/properties/multipleOf"
},
"maximum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/maximum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/maximum"
},
"exclusiveMaximum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum"
"type": "boolean",
"default": false
},
"minimum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/minimum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/minimum"
},
"exclusiveMinimum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum"
"type": "boolean",
"default": false
},
"maxLength": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minLength": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"pattern": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/pattern"
"$ref": "http://json-schema.org/draft-07/schema#/properties/pattern"
},
"maxItems": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minItems": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"uniqueItems": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems"
"$ref": "http://json-schema.org/draft-07/schema#/properties/uniqueItems"
},
"enum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/enum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/enum"
},
"jsonReference": {
"type": "object",
@ -1692,14 +1807,15 @@
"description": "The parameters used in conjunction with x-ms-parameterized-host.",
"additionalItems": false,
"items": {
"oneOf": [
{
"$ref": "#/definitions/nonBodyParameter"
},
{
"$ref": "#/definitions/jsonReference"
}
]
"if": {
"required": ["$ref"]
},
"then": {
"$ref": "#/definitions/jsonReference"
},
"else": {
"$ref": "#/definitions/nonBodyParameter"
}
},
"uniqueItems": true
},
@ -1766,14 +1882,15 @@
"type": "object",
"description": "Describes the 'x-ms-examples' extension.",
"additionalProperties": {
"oneOf": [
{
"$ref": "https://raw.githubusercontent.com/Azure/autorest/master/schema/example-schema.json#"
},
{
"$ref": "#/definitions/jsonReference"
}
]
"if": {
"required": ["$ref"]
},
"then": {
"$ref": "#/definitions/jsonReference"
},
"else": {
"$ref": "https://raw.githubusercontent.com/Azure/autorest/master/schema/example-schema.json#"
}
}
}
}

Просмотреть файл

@ -1,13 +1,9 @@
{
"title": "A JSON Schema for Swagger 2.0 API.",
"id": "http://swagger.io/v2/schema.json#",
"$schema": "http://json-schema.org/draft-04/schema#",
"$id": "http://swagger.io/v2/schema.json#",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"swagger",
"info",
"paths"
],
"required": ["swagger", "info", "paths"],
"additionalProperties": false,
"patternProperties": {
"^x-": {
@ -17,9 +13,7 @@
"properties": {
"swagger": {
"type": "string",
"enum": [
"2.0"
],
"enum": ["2.0"],
"description": "The Swagger version of this document."
},
"info": {
@ -87,10 +81,7 @@
"info": {
"type": "object",
"description": "General information about the API.",
"required": [
"version",
"title"
],
"required": ["version", "title"],
"additionalProperties": false,
"patternProperties": {
"^x-": {
@ -152,9 +143,7 @@
},
"license": {
"type": "object",
"required": [
"name"
],
"required": ["name"],
"additionalProperties": false,
"properties": {
"name": {
@ -211,9 +200,7 @@
"type": "object",
"additionalProperties": false,
"description": "information about external documentation",
"required": [
"url"
],
"required": ["url"],
"properties": {
"description": {
"type": "string"
@ -239,9 +226,7 @@
},
"operation": {
"type": "object",
"required": [
"responses"
],
"required": ["responses"],
"additionalProperties": false,
"patternProperties": {
"^x-": {
@ -378,9 +363,7 @@
},
"response": {
"type": "object",
"required": [
"description"
],
"required": ["description"],
"properties": {
"description": {
"type": "string"
@ -418,19 +401,11 @@
"header": {
"type": "object",
"additionalProperties": false,
"required": [
"type"
],
"required": ["type"],
"properties": {
"type": {
"type": "string",
"enum": [
"string",
"number",
"integer",
"boolean",
"array"
]
"enum": ["string", "number", "integer", "boolean", "array"]
},
"format": {
"type": "string"
@ -497,11 +472,7 @@
},
"bodyParameter": {
"type": "object",
"required": [
"name",
"in",
"schema"
],
"required": ["name", "in", "schema"],
"patternProperties": {
"^x-": {
"$ref": "#/definitions/vendorExtension"
@ -519,9 +490,7 @@
"in": {
"type": "string",
"description": "Determines the location of the parameter.",
"enum": [
"body"
]
"enum": ["body"]
},
"required": {
"type": "boolean",
@ -550,9 +519,7 @@
"in": {
"type": "string",
"description": "Determines the location of the parameter.",
"enum": [
"header"
]
"enum": ["header"]
},
"description": {
"type": "string",
@ -564,13 +531,7 @@
},
"type": {
"type": "string",
"enum": [
"string",
"number",
"boolean",
"integer",
"array"
]
"enum": ["string", "number", "boolean", "integer", "array"]
},
"format": {
"type": "string"
@ -638,9 +599,7 @@
"in": {
"type": "string",
"description": "Determines the location of the parameter.",
"enum": [
"query"
]
"enum": ["query"]
},
"description": {
"type": "string",
@ -657,13 +616,7 @@
},
"type": {
"type": "string",
"enum": [
"string",
"number",
"boolean",
"integer",
"array"
]
"enum": ["string", "number", "boolean", "integer", "array"]
},
"format": {
"type": "string"
@ -731,9 +684,7 @@
"in": {
"type": "string",
"description": "Determines the location of the parameter.",
"enum": [
"formData"
]
"enum": ["formData"]
},
"description": {
"type": "string",
@ -750,14 +701,7 @@
},
"type": {
"type": "string",
"enum": [
"string",
"number",
"boolean",
"integer",
"array",
"file"
]
"enum": ["string", "number", "boolean", "integer", "array", "file"]
},
"format": {
"type": "string"
@ -816,23 +760,17 @@
"$ref": "#/definitions/vendorExtension"
}
},
"required": [
"required"
],
"required": ["required"],
"properties": {
"required": {
"type": "boolean",
"enum": [
true
],
"enum": [true],
"description": "Determines whether or not this parameter is required or optional."
},
"in": {
"type": "string",
"description": "Determines the location of the parameter.",
"enum": [
"path"
]
"enum": ["path"]
},
"description": {
"type": "string",
@ -844,13 +782,7 @@
},
"type": {
"type": "string",
"enum": [
"string",
"number",
"boolean",
"integer",
"array"
]
"enum": ["string", "number", "boolean", "integer", "array"]
},
"format": {
"type": "string"
@ -904,11 +836,7 @@
},
"nonBodyParameter": {
"type": "object",
"required": [
"name",
"in",
"type"
],
"required": ["name", "in", "type"],
"oneOf": [
{
"$ref": "#/definitions/headerParameterSubSchema"
@ -950,58 +878,58 @@
"type": "string"
},
"title": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/title"
"$ref": "http://json-schema.org/draft-07/schema#/properties/title"
},
"description": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/description"
"$ref": "http://json-schema.org/draft-07/schema#/properties/description"
},
"default": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/default"
"$ref": "http://json-schema.org/draft-07/schema#/properties/default"
},
"multipleOf": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf"
"$ref": "http://json-schema.org/draft-07/schema#/properties/multipleOf"
},
"maximum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/maximum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/maximum"
},
"exclusiveMaximum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/exclusiveMaximum"
},
"minimum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/minimum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/minimum"
},
"exclusiveMinimum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/exclusiveMinimum"
},
"maxLength": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minLength": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"pattern": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/pattern"
"$ref": "http://json-schema.org/draft-07/schema#/properties/pattern"
},
"maxItems": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minItems": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"uniqueItems": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems"
"$ref": "http://json-schema.org/draft-07/schema#/properties/uniqueItems"
},
"maxProperties": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minProperties": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"required": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/stringArray"
},
"enum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/enum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/enum"
},
"additionalProperties": {
"anyOf": [
@ -1015,7 +943,7 @@
"default": {}
},
"type": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/type"
"$ref": "http://json-schema.org/draft-07/schema#/properties/type"
},
"items": {
"anyOf": [
@ -1071,30 +999,26 @@
"$ref": "#/definitions/vendorExtension"
}
},
"required": [
"type"
],
"required": ["type"],
"properties": {
"format": {
"type": "string"
},
"title": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/title"
"$ref": "http://json-schema.org/draft-07/schema#/properties/title"
},
"description": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/description"
"$ref": "http://json-schema.org/draft-07/schema#/properties/description"
},
"default": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/default"
"$ref": "http://json-schema.org/draft-07/schema#/properties/default"
},
"required": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/stringArray"
},
"type": {
"type": "string",
"enum": [
"file"
]
"enum": ["file"]
},
"readOnly": {
"type": "boolean",
@ -1113,13 +1037,7 @@
"properties": {
"type": {
"type": "string",
"enum": [
"string",
"number",
"integer",
"boolean",
"array"
]
"enum": ["string", "number", "integer", "boolean", "array"]
},
"format": {
"type": "string"
@ -1224,9 +1142,7 @@
"tag": {
"type": "object",
"additionalProperties": false,
"required": [
"name"
],
"required": ["name"],
"properties": {
"name": {
"type": "string"
@ -1272,15 +1188,11 @@
"basicAuthenticationSecurity": {
"type": "object",
"additionalProperties": false,
"required": [
"type"
],
"required": ["type"],
"properties": {
"type": {
"type": "string",
"enum": [
"basic"
]
"enum": ["basic"]
},
"description": {
"type": "string"
@ -1295,27 +1207,18 @@
"apiKeySecurity": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"name",
"in"
],
"required": ["type", "name", "in"],
"properties": {
"type": {
"type": "string",
"enum": [
"apiKey"
]
"enum": ["apiKey"]
},
"name": {
"type": "string"
},
"in": {
"type": "string",
"enum": [
"header",
"query"
]
"enum": ["header", "query"]
},
"description": {
"type": "string"
@ -1330,23 +1233,15 @@
"oauth2ImplicitSecurity": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"flow",
"authorizationUrl"
],
"required": ["type", "flow", "authorizationUrl"],
"properties": {
"type": {
"type": "string",
"enum": [
"oauth2"
]
"enum": ["oauth2"]
},
"flow": {
"type": "string",
"enum": [
"implicit"
]
"enum": ["implicit"]
},
"scopes": {
"$ref": "#/definitions/oauth2Scopes"
@ -1368,23 +1263,15 @@
"oauth2PasswordSecurity": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"flow",
"tokenUrl"
],
"required": ["type", "flow", "tokenUrl"],
"properties": {
"type": {
"type": "string",
"enum": [
"oauth2"
]
"enum": ["oauth2"]
},
"flow": {
"type": "string",
"enum": [
"password"
]
"enum": ["password"]
},
"scopes": {
"$ref": "#/definitions/oauth2Scopes"
@ -1406,23 +1293,15 @@
"oauth2ApplicationSecurity": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"flow",
"tokenUrl"
],
"required": ["type", "flow", "tokenUrl"],
"properties": {
"type": {
"type": "string",
"enum": [
"oauth2"
]
"enum": ["oauth2"]
},
"flow": {
"type": "string",
"enum": [
"application"
]
"enum": ["application"]
},
"scopes": {
"$ref": "#/definitions/oauth2Scopes"
@ -1444,24 +1323,15 @@
"oauth2AccessCodeSecurity": {
"type": "object",
"additionalProperties": false,
"required": [
"type",
"flow",
"authorizationUrl",
"tokenUrl"
],
"required": ["type", "flow", "authorizationUrl", "tokenUrl"],
"properties": {
"type": {
"type": "string",
"enum": [
"oauth2"
]
"enum": ["oauth2"]
},
"flow": {
"type": "string",
"enum": [
"accessCode"
]
"enum": ["accessCode"]
},
"scopes": {
"$ref": "#/definitions/oauth2Scopes"
@ -1518,86 +1388,68 @@
"description": "The transfer protocol of the API.",
"items": {
"type": "string",
"enum": [
"http",
"https",
"ws",
"wss"
]
"enum": ["http", "https", "ws", "wss"]
},
"uniqueItems": true
},
"collectionFormat": {
"type": "string",
"enum": [
"csv",
"ssv",
"tsv",
"pipes"
],
"enum": ["csv", "ssv", "tsv", "pipes"],
"default": "csv"
},
"collectionFormatWithMulti": {
"type": "string",
"enum": [
"csv",
"ssv",
"tsv",
"pipes",
"multi"
],
"enum": ["csv", "ssv", "tsv", "pipes", "multi"],
"default": "csv"
},
"title": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/title"
"$ref": "http://json-schema.org/draft-07/schema#/properties/title"
},
"description": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/description"
"$ref": "http://json-schema.org/draft-07/schema#/properties/description"
},
"default": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/default"
"$ref": "http://json-schema.org/draft-07/schema#/properties/default"
},
"multipleOf": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf"
"$ref": "http://json-schema.org/draft-07/schema#/properties/multipleOf"
},
"maximum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/maximum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/maximum"
},
"exclusiveMaximum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/exclusiveMaximum"
},
"minimum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/minimum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/minimum"
},
"exclusiveMinimum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/exclusiveMinimum"
},
"maxLength": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minLength": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"pattern": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/pattern"
"$ref": "http://json-schema.org/draft-07/schema#/properties/pattern"
},
"maxItems": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeInteger"
},
"minItems": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-07/schema#/definitions/nonNegativeIntegerDefault0"
},
"uniqueItems": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems"
"$ref": "http://json-schema.org/draft-07/schema#/properties/uniqueItems"
},
"enum": {
"$ref": "http://json-schema.org/draft-04/schema#/properties/enum"
"$ref": "http://json-schema.org/draft-07/schema#/properties/enum"
},
"jsonReference": {
"type": "object",
"required": [
"$ref"
],
"required": ["$ref"],
"additionalProperties": false,
"properties": {
"$ref": {
@ -1606,4 +1458,4 @@
}
}
}
}
}

Просмотреть файл

@ -2,16 +2,16 @@ import { CancellationToken, DataStore } from "@azure-tools/datastore";
import { parse } from "./literate-yaml";
import { AutorestError } from "../logging";
import { OperationAbortedException } from "../exceptions";
describe("SyntaxValidation", () => {
let errors: AutorestError[];
const logger = {
verbose: jest.fn((x) => errors.push(x)),
info: jest.fn((x) => errors.push(x)),
verbose: jest.fn(),
info: jest.fn(),
fatal: jest.fn((x) => errors.push(x)),
trackError: jest.fn((x) => errors.push(x)),
trackWarning: jest.fn((x) => errors.push(x)),
};
beforeEach(() => {

Просмотреть файл

@ -24,6 +24,11 @@ export interface AutorestDiagnostic {
* location where the problem was found.
*/
readonly source?: SourceLocation[];
/**
* Additional details.
*/
readonly details?: Error | unknown;
}
export interface AutorestError extends AutorestDiagnostic {}
@ -52,4 +57,9 @@ export interface AutorestLogger {
* Track an error that occurred.
*/
trackError(error: AutorestError): void;
/**
* Track an warning that occurred.
*/
trackWarning(error: AutorestWarning): void;
}

Просмотреть файл

@ -30,6 +30,8 @@
},
"homepage": "https://github.com/Azure/autorest#readme",
"dependencies": {
"@azure-tools/datastore": "~4.1.271",
"@azure-tools/tasks": "~3.0.0",
"expect": "~26.6.2",
"jest-snapshot": "~26.6.2",
"jest": "^26.6.3"

Просмотреть файл

@ -0,0 +1,20 @@
import { DataHandle, LineIndices } from "@azure-tools/datastore";
import { Lazy } from "@azure-tools/tasks";
/**
* Create a data handle from some string content.
* @param content Content of the file
* @returns DataHandle.
*/
export function createDataHandle(content: string): DataHandle {
const name = "test-generated";
return new DataHandle(`mem://${name}`, {
name,
identity: [name],
artifactType: "",
cached: content,
metadata: {
lineIndices: new Lazy<number[]>(() => LineIndices(content)),
},
});
}

Просмотреть файл

@ -0,0 +1 @@
export * from "./data-store-test-utils";