diff --git a/common/changes/@microsoft.azure/openapi-validator-rulesets/dinwa-arm-apiversion-validation_2024-08-21-18-07.json b/common/changes/@microsoft.azure/openapi-validator-rulesets/dinwa-arm-apiversion-validation_2024-08-21-18-07.json new file mode 100644 index 000000000..af537b4ad --- /dev/null +++ b/common/changes/@microsoft.azure/openapi-validator-rulesets/dinwa-arm-apiversion-validation_2024-08-21-18-07.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft.azure/openapi-validator-rulesets", + "comment": "update APIVersionPattern rule", + "type": "patch" + } + ], + "packageName": "@microsoft.azure/openapi-validator-rulesets" +} \ No newline at end of file diff --git a/docs/api-version-pattern.md b/docs/api-version-pattern.md index 072118641..5c9c50830 100644 --- a/docs/api-version-pattern.md +++ b/docs/api-version-pattern.md @@ -10,11 +10,11 @@ ARM OpenAPI(swagger) specs ## Output Message -API Version must be in the format: yyyy-MM-dd, optionally followed by -preview. +API Version must be in the format: yyyy-MM-dd, optionally followed by -preview, or 'canonical' for canonical swagger. ## Description -The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.) NOTE that this is the en-US ordering of month and date. +The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.) or 'canonical' for canonical swagger. NOTE that this is the en-US ordering of month and date. The date MAY optionally be followed by one of: * -preview - Indicates the API version is in (public) preview @@ -36,6 +36,7 @@ The API version specified wil be used by the generated client. Examples of valid version patterns include: * 2016-07-04 * 2016-07-04-preview +* canonical (for canonical swagger) ## Bad Examples diff --git a/docs/rules.md b/docs/rules.md index b57c9937b..a396bb641 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -62,7 +62,7 @@ Please refer to [api-version-parameter-required.md](./api-version-parameter-requ ### APIVersionPattern -The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.) NOTE that this is the en-US ordering of month and date. +The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.) or 'canonical' for canonical swagger. NOTE that this is the en-US ordering of month and date. The date MAY optionally be followed by one of: * -preview - Indicates the API version is in (public) preview diff --git a/packages/rulesets/generated/spectral/az-arm.js b/packages/rulesets/generated/spectral/az-arm.js index 0d0d88609..cca320be5 100644 --- a/packages/rulesets/generated/spectral/az-arm.js +++ b/packages/rulesets/generated/spectral/az-arm.js @@ -1278,6 +1278,36 @@ const ruleset$1 = { }, }; +const apiVersionPatternValidation = (info, opts, paths) => { + if (info === null || typeof info !== "object") { + return []; + } + const path = paths.path || []; + const canonicalEmitter = "@azure-tools/typespec-autorest-canonical"; + if (info["x-typespec-generated"] && info["x-typespec-generated"][0].emitter == canonicalEmitter) { + if (info.version != "canonical") { + return [ + { + message: `Canonical swagger version should be 'canonical.`, + path: [...path, "version"], + }, + ]; + } + } + else { + const apiVersionPattern = /^(20\d{2})-(0[1-9]|1[0-2])-((0[1-9])|[12][0-9]|3[01])(-preview)?$/gi; + if (!apiVersionPattern.test(info.version)) { + return [ + { + message: `The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.). NOTE that this is the en-US ordering of month and date.`, + path: [...path, "version"], + }, + ]; + } + } + return []; +}; + function matchAnyPatterns$2(patterns, path) { return patterns.some((p) => p.test(path)); } @@ -4001,17 +4031,14 @@ const ruleset = { }, }, APIVersionPattern: { - description: "The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.) NOTE that this is the en-US ordering of month and date.", + description: "The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.) or 'canonical'. NOTE that this is the en-US ordering of month and date.", severity: "error", message: "{{description}}", resolved: true, formats: [oas2], - given: "$.info.version", + given: "$.info", then: { - function: pattern, - functionOptions: { - match: "^(20\\d{2})-(0[1-9]|1[0-2])-((0[1-9])|[12][0-9]|3[01])(-(preview))?$", - }, + function: apiVersionPatternValidation, }, }, ParameterNotDefinedInGlobalParameters: { diff --git a/packages/rulesets/src/spectral/az-arm.ts b/packages/rulesets/src/spectral/az-arm.ts index bf41e6a2e..8e28e088a 100644 --- a/packages/rulesets/src/spectral/az-arm.ts +++ b/packages/rulesets/src/spectral/az-arm.ts @@ -1,6 +1,7 @@ import { oas2 } from "@stoplight/spectral-formats" import { falsy, pattern, truthy } from "@stoplight/spectral-functions" import common from "./az-common" +import apiVersionPatternValidation from "./functions/api-version-pattern-validation" import verifyArmPath from "./functions/arm-path-validation" import bodyParamRepeatedInfo from "./functions/body-param-repeated-info" import { camelCase } from "./functions/camel-case" @@ -1104,17 +1105,14 @@ const ruleset: any = { }, APIVersionPattern: { description: - "The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.) NOTE that this is the en-US ordering of month and date.", + "The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.) or 'canonical'. NOTE that this is the en-US ordering of month and date.", severity: "error", message: "{{description}}", resolved: true, formats: [oas2], - given: "$.info.version", + given: "$.info", then: { - function: pattern, - functionOptions: { - match: "^(20\\d{2})-(0[1-9]|1[0-2])-((0[1-9])|[12][0-9]|3[01])(-(preview))?$", - }, + function: apiVersionPatternValidation, }, }, ParameterNotDefinedInGlobalParameters: { diff --git a/packages/rulesets/src/spectral/functions/api-version-pattern-validation.ts b/packages/rulesets/src/spectral/functions/api-version-pattern-validation.ts new file mode 100644 index 000000000..64308783a --- /dev/null +++ b/packages/rulesets/src/spectral/functions/api-version-pattern-validation.ts @@ -0,0 +1,34 @@ +const apiVersionPatternValidation = (info: any, opts: any, paths: any) => { + if (info === null || typeof info !== "object") { + return [] + } + + const path = paths.path || [] + const canonicalEmitter = "@azure-tools/typespec-autorest-canonical" + + if (info["x-typespec-generated"] && info["x-typespec-generated"][0].emitter == canonicalEmitter) { + if (info.version != "canonical") { + return [ + { + message: `Canonical swagger version should be 'canonical.`, + path: [...path, "version"], + }, + ] + } + } else { + const apiVersionPattern = /^(20\d{2})-(0[1-9]|1[0-2])-((0[1-9])|[12][0-9]|3[01])(-preview)?$/gi + + if (!apiVersionPattern.test(info.version)) { + return [ + { + message: `The API Version parameter MUST be in the Year-Month-Date format (i.e. 2016-07-04.). NOTE that this is the en-US ordering of month and date.`, + path: [...path, "version"], + }, + ] + } + } + + return [] +} + +export default apiVersionPatternValidation diff --git a/packages/rulesets/src/spectral/test/api-version-pattern.test.ts b/packages/rulesets/src/spectral/test/api-version-pattern.test.ts new file mode 100644 index 000000000..f0f57ae81 --- /dev/null +++ b/packages/rulesets/src/spectral/test/api-version-pattern.test.ts @@ -0,0 +1,182 @@ +import { Spectral } from "@stoplight/spectral-core" +import linterForRule from "./utils" + +let linter: Spectral + +beforeAll(async () => { + linter = await linterForRule("APIVersionPattern") + return linter +}) + +test("APIVersionPattern should find no errors", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "2021-07-01", + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(0) + }) +}) + +test("APIVersionPattern should find errors", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "3.0.1", + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(1) + expect(results[0].path.join(".")).toBe("info.version") + }) +}) + +test("APIVersionPattern should find errors 2", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "canonical", + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(1) + expect(results[0].path.join(".")).toBe("info.version") + }) +}) + +test("APIVersionPattern should find no errors for autorest generated swagger", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "2021-07-01", + "x-typespec-generated": [ + { + emitter: "@azure-tools/typespec-autorest", + }, + ], + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(0) + }) +}) + +test("APIVersionPattern should find errors for autorest generated swagger", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "3.0.1", + "x-typespec-generated": [ + { + emitter: "@azure-tools/typespec-autorest", + }, + ], + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(1) + expect(results[0].path.join(".")).toBe("info.version") + }) +}) + +test("APIVersionPattern should find errors for autorest-canonical generated swagger", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "3.0.1", + "x-typespec-generated": [ + { + emitter: "@azure-tools/typespec-autorest-canonical", + }, + ], + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(1) + expect(results[0].path.join(".")).toBe("info.version") + }) +}) + +test("APIVersionPattern should find errors for autorest-canonical generated swagger 2", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "2021-07-01", + "x-typespec-generated": [ + { + emitter: "@azure-tools/typespec-autorest-canonical", + }, + ], + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(1) + expect(results[0].path.join(".")).toBe("info.version") + }) +}) + +test("APIVersionPattern should find no errors for autorest-canonical generated swagger", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "canonical", + "x-typespec-generated": [ + { + emitter: "@azure-tools/typespec-autorest-canonical", + }, + ], + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(0) + }) +}) + +test("APIVersionPattern should find no errors with -preview", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "2021-07-01-preview", + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(0) + }) +}) + +test("APIVersionPattern should find no errors for autorest generated swagger with -preview", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "2021-07-01-preview", + "x-typespec-generated": [ + { + emitter: "@azure-tools/typespec-autorest", + }, + ], + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(0) + }) +}) + +test("APIVersionPattern should find errors for autorest-canonical generated swagger with -preview 2", () => { + const myOpenApiDocument = { + swagger: "2.0", + info: { + version: "2021-07-01-preview", + "x-typespec-generated": [ + { + emitter: "@azure-tools/typespec-autorest-canonical", + }, + ], + }, + } + return linter.run(myOpenApiDocument).then((results) => { + expect(results.length).toBe(1) + expect(results[0].path.join(".")).toBe("info.version") + }) +})