From bc46506ecfa4196d74a90ed86b6ab2d49ff188f4 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Tue, 6 Jul 2021 10:56:48 -0700 Subject: [PATCH] Improve: OpenAPI 3 parameters validation error (#4198) * Improve OpenAPI 3 schema for parameter: Give better error message when it doesn't match * Chagnes * Wip * Wip * . --- ...pi3-schema-paramters_2021-07-02-19-11.json | 11 + ...pi3-schema-paramters_2021-07-02-16-40.json | 11 + .../openapi3-schema-validator.test.ts | 31 + .../autorest-schemas/openapi3-schema.json | 715 +++++------------- 4 files changed, 249 insertions(+), 519 deletions(-) create mode 100644 common/changes/@autorest/core/fix-openapi3-schema-paramters_2021-07-02-19-11.json create mode 100644 common/changes/@autorest/schemas/fix-openapi3-schema-paramters_2021-07-02-16-40.json diff --git a/common/changes/@autorest/core/fix-openapi3-schema-paramters_2021-07-02-19-11.json b/common/changes/@autorest/core/fix-openapi3-schema-paramters_2021-07-02-19-11.json new file mode 100644 index 000000000..895c2cfb9 --- /dev/null +++ b/common/changes/@autorest/core/fix-openapi3-schema-paramters_2021-07-02-19-11.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@autorest/core", + "comment": "", + "type": "none" + } + ], + "packageName": "@autorest/core", + "email": "tiguerin@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/@autorest/schemas/fix-openapi3-schema-paramters_2021-07-02-16-40.json b/common/changes/@autorest/schemas/fix-openapi3-schema-paramters_2021-07-02-16-40.json new file mode 100644 index 000000000..bf956a226 --- /dev/null +++ b/common/changes/@autorest/schemas/fix-openapi3-schema-paramters_2021-07-02-16-40.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@autorest/schemas", + "comment": "**Improve** OpenAPI 3 schema for parameter: Give better error message when it doesn't match", + "type": "minor" + } + ], + "packageName": "@autorest/schemas", + "email": "tiguerin@microsoft.com" +} \ No newline at end of file diff --git a/packages/extensions/core/src/lib/plugins/schema-validation/openapi3-schema-validator.test.ts b/packages/extensions/core/src/lib/plugins/schema-validation/openapi3-schema-validator.test.ts index 8e57f16d7..bf221cb42 100644 --- a/packages/extensions/core/src/lib/plugins/schema-validation/openapi3-schema-validator.test.ts +++ b/packages/extensions/core/src/lib/plugins/schema-validation/openapi3-schema-validator.test.ts @@ -63,6 +63,37 @@ describe("OpenAPI3 schema validator", () => { ]); }); + it("returns custom error if both example and examples are used in parameter", () => { + const errors = validator.validate({ + ...baseSwaggerSpec, + paths: { + "/test": { + get: { + parameters: [ + { + in: "query", + name: "foo", + example: {}, + examples: {}, + }, + ], + responses: { 200: { description: "ok" } }, + }, + }, + }, + }); + expect(errors).toEqual([ + { + instancePath: "/paths/~1test/get/parameters/0", + keyword: "errorMessage", + message: "must not have both `example` and `examples`, as they are mutually exclusive", + params: expect.anything(), + path: ["paths", "/test", "get", "parameters", "0"], + schemaPath: "#/definitions/ExampleXORExamples/errorMessage", + }, + ]); + }); + describe("when validating a file", () => { let file: DataHandle; beforeEach(() => { diff --git a/packages/libs/autorest-schemas/openapi3-schema.json b/packages/libs/autorest-schemas/openapi3-schema.json index 7cb1bedf0..d32746f4d 100644 --- a/packages/libs/autorest-schemas/openapi3-schema.json +++ b/packages/libs/autorest-schemas/openapi3-schema.json @@ -394,7 +394,7 @@ "if": { "type": "boolean" }, "then": { "type": "boolean", - "errorMessage": "should be a Reference Object, Schema Object, or boolean value" + "errorMessage": "must be a Reference Object, Schema Object, or boolean value" } } ], @@ -948,103 +948,81 @@ }, "additionalProperties": false }, + "ExampleXORExamples": { + "description": "Example and examples are mutually exclusive", + "errorMessage": "must not have both `example` and `examples`, as they are mutually exclusive", + "not": { + "required": ["example", "examples"] + } + }, + "SchemaXORContent": { + "description": "Schema and content are mutually exclusive, at least one is required", + "allOf": [ + { + "if": { + "required": ["schema", "content"] + }, + "then": false + }, + { + "if": { + "required": ["schema"] + }, + "then": true + }, + { + "if": { + "required": ["content"] + }, + "then": { + "description": "Some properties are not allowed if content is present", + "errorMessage": "must not have `style`, `explode`, `allowReserved`, `example`, or `examples` when `content` is present", + "allOf": [ + { + "not": { + "required": ["style"] + } + }, + { + "not": { + "required": ["explode"] + } + }, + { + "not": { + "required": ["allowReserved"] + } + }, + { + "not": { + "required": ["example"] + } + }, + { + "not": { + "required": ["examples"] + } + } + ] + } + }, + { + "then": { + "required": ["schema", "content"] + } + } + ], + "errorMessage": "must have either a `schema` or `content` property" + }, "Parameter": { - "oneOf": [ - { - "$ref": "#/definitions/ParameterWithSchema" - }, - { - "$ref": "#/definitions/ParameterWithContent" - } - ] - }, - "ParameterWithSchema": { - "oneOf": [ - { - "$ref": "#/definitions/ParameterWithSchemaWithExample" - }, - { - "$ref": "#/definitions/ParameterWithSchemaWithExamples" - } - ] - }, - "ParameterWithSchemaWithExample": { - "oneOf": [ - { - "$ref": "#/definitions/ParameterWithSchemaWithExampleInPath" - }, - { - "$ref": "#/definitions/ParameterWithSchemaWithExampleInQuery" - }, - { - "$ref": "#/definitions/ParameterWithSchemaWithExampleInHeader" - }, - { - "$ref": "#/definitions/ParameterWithSchemaWithExampleInCookie" - } - ] - }, - "ParameterWithSchemaWithExampleInPath": { "type": "object", - "required": ["name", "in", "schema", "required"], "properties": { "name": { "type": "string" }, "in": { - "type": "string", - "enum": ["path"] - }, - "description": { "type": "string" }, - "required": { - "type": "boolean", - "enum": [true] - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false - }, - "style": { - "type": "string", - "enum": ["matrix", "label", "simple"], - "default": "simple" - }, - "explode": { - "type": "boolean" - }, - "allowReserved": { - "type": "boolean", - "default": false - }, - "schema": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Schema" } - }, - "example": {} - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithSchemaWithExampleInQuery": { - "type": "object", - "required": ["name", "in", "schema"], - "properties": { - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["query"] - }, "description": { "type": "string" }, @@ -1061,9 +1039,7 @@ "default": false }, "style": { - "type": "string", - "enum": ["form", "spaceDelimited", "pipeDelimited", "deepObject"], - "default": "form" + "type": "string" }, "explode": { "type": "boolean" @@ -1073,396 +1049,15 @@ "default": false }, "schema": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Schema" } - }, - "example": {} - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithSchemaWithExampleInHeader": { - "type": "object", - "required": ["name", "in", "schema"], - "properties": { - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["header"] - }, - "description": { - "type": "string" - }, - "required": { - "type": "boolean", - "default": false - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false - }, - "style": { - "type": "string", - "enum": ["simple"], - "default": "simple" - }, - "explode": { - "type": "boolean" - }, - "allowReserved": { - "type": "boolean", - "default": false - }, - "schema": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Schema" } - }, - "example": {} - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithSchemaWithExampleInCookie": { - "type": "object", - "required": ["name", "in", "schema"], - "properties": { - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["cookie"] - }, - "description": { - "type": "string" - }, - "required": { - "type": "boolean", - "default": false - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false - }, - "style": { - "type": "string", - "enum": ["form"], - "default": "form" - }, - "explode": { - "type": "boolean" - }, - "allowReserved": { - "type": "boolean", - "default": false - }, - "schema": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Schema" } - }, - "example": {} - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithSchemaWithExamples": { - "oneOf": [ - { - "$ref": "#/definitions/ParameterWithSchemaWithExamplesInPath" - }, - { - "$ref": "#/definitions/ParameterWithSchemaWithExamplesInQuery" - }, - { - "$ref": "#/definitions/ParameterWithSchemaWithExamplesInHeader" - }, - { - "$ref": "#/definitions/ParameterWithSchemaWithExamplesInCookie" - } - ] - }, - "ParameterWithSchemaWithExamplesInPath": { - "type": "object", - "required": ["name", "in", "schema", "required", "examples"], - "properties": { - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["path"] - }, - "description": { - "type": "string" - }, - "required": { - "type": "boolean", - "enum": [true] - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false - }, - "style": { - "type": "string", - "enum": ["matrix", "label", "simple"], - "default": "simple" - }, - "explode": { - "type": "boolean" - }, - "allowReserved": { - "type": "boolean", - "default": false - }, - "schema": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Schema" } - }, - "examples": { - "type": "object", - "additionalProperties": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Example" } + "if": { + "required": ["$ref"] + }, + "then": { + "$ref": "#/definitions/Reference" + }, + "else": { + "$ref": "#/definitions/Schema" } - } - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithSchemaWithExamplesInQuery": { - "type": "object", - "required": ["name", "in", "schema", "examples"], - "properties": { - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["query"] - }, - "description": { - "type": "string" - }, - "required": { - "type": "boolean", - "default": false - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false - }, - "style": { - "type": "string", - "enum": ["form", "spaceDelimited", "pipeDelimited", "deepObject"], - "default": "form" - }, - "explode": { - "type": "boolean" - }, - "allowReserved": { - "type": "boolean", - "default": false - }, - "schema": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Schema" } - }, - "examples": { - "type": "object", - "additionalProperties": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Example" } - } - } - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithSchemaWithExamplesInHeader": { - "type": "object", - "required": ["name", "in", "schema", "examples"], - "properties": { - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["header"] - }, - "description": { - "type": "string" - }, - "required": { - "type": "boolean", - "default": false - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false - }, - "style": { - "type": "string", - "enum": ["simple"], - "default": "simple" - }, - "explode": { - "type": "boolean" - }, - "allowReserved": { - "type": "boolean", - "default": false - }, - "schema": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Schema" } - }, - "examples": { - "type": "object", - "additionalProperties": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Example" } - } - } - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithSchemaWithExamplesInCookie": { - "type": "object", - "required": ["name", "in", "schema", "examples"], - "properties": { - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["cookie"] - }, - "description": { - "type": "string" - }, - "required": { - "type": "boolean", - "default": false - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false - }, - "style": { - "type": "string", - "enum": ["form"], - "default": "form" - }, - "explode": { - "type": "boolean" - }, - "allowReserved": { - "type": "boolean", - "default": false - }, - "schema": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Schema" } - }, - "examples": { - "type": "object", - "additionalProperties": { - "if": { "required": ["$ref"] }, - "then": { "$ref": "#/definitions/Reference" }, - "else": { "$ref": "#/definitions/Example" } - } - } - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithContent": { - "oneOf": [ - { - "$ref": "#/definitions/ParameterWithContentInPath" - }, - { - "$ref": "#/definitions/ParameterWithContentNotInPath" - } - ] - }, - "ParameterWithContentInPath": { - "type": "object", - "required": ["name", "in", "content"], - "properties": { - "name": { - "type": "string" - }, - "in": { - "type": "string", - "enum": ["path"] - }, - "description": { - "type": "string" - }, - "required": { - "type": "boolean", - "enum": [true] - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false }, "content": { "type": "object", @@ -1471,52 +1066,134 @@ }, "minProperties": 1, "maxProperties": 1 - } - }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false - }, - "ParameterWithContentNotInPath": { - "type": "object", - "required": ["name", "in", "content"], - "properties": { - "name": { - "type": "string" }, - "in": { - "type": "string", - "enum": ["query", "header", "cookie"] - }, - "description": { - "type": "string" - }, - "required": { - "type": "boolean", - "default": false - }, - "deprecated": { - "type": "boolean", - "default": false - }, - "allowEmptyValue": { - "type": "boolean", - "default": false - }, - "content": { + "example": {}, + "examples": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/MediaType" - }, - "minProperties": 1, - "maxProperties": 1 + "if": { + "required": ["$ref"] + }, + "then": { + "$ref": "#/definitions/Reference" + }, + "else": { + "$ref": "#/definitions/Example" + } + } } }, "patternProperties": { "^x-": {} }, - "additionalProperties": false + "additionalProperties": false, + "required": ["name", "in"], + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + }, + { + "$ref": "#/definitions/ParameterLocation" + } + ] + }, + "ParameterLocation": { + "description": "Parameter location", + "allOf": [ + { + "if": { + "required": ["in"], + "properties": { + "in": { + "enum": ["path"] + } + } + }, + "then": { + "description": "Parameter in path", + "required": ["required"], + "properties": { + "style": { + "enum": ["matrix", "label", "simple"], + "default": "simple" + }, + "required": { + "enum": [true] + } + } + } + }, + { + "if": { + "required": ["in"], + "properties": { + "in": { + "enum": ["query"] + } + } + }, + "then": { + "description": "Parameter in query", + "properties": { + "style": { + "enum": ["form", "spaceDelimited", "pipeDelimited", "deepObject"], + "default": "form" + } + } + } + }, + { + "if": { + "required": ["in"], + "properties": { + "in": { + "enum": ["header"] + } + } + }, + "then": { + "description": "Parameter in header", + "properties": { + "style": { + "enum": ["simple"], + "default": "simple" + } + } + } + }, + { + "if": { + "required": ["in"], + "properties": { + "in": { + "enum": ["cookie"] + } + } + }, + "then": { + "description": "Parameter in cookie", + "properties": { + "style": { + "enum": ["form"], + "default": "form" + } + } + } + }, + { + "then": { + "required": ["in"], + "properties": { + "in": { + "enum": ["path", "query", "header", "cookie"] + } + } + } + } + ] }, "RequestBody": { "type": "object",