diff --git a/src/api-version.ts b/src/api-version.ts new file mode 100644 index 0000000..404f9be --- /dev/null +++ b/src/api-version.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +// apiVersion may be date-based ("2025-07-01") or non-date-based ("7.0", "v7.0", etc). All recent versions should be +// date-based, so sort all date-based versions later than all non-date-based versions. +export const compareApiVersion = (a: string, b: string): number => { + const dateA = containsDate(a) + const dateB = containsDate(b) + if (dateA && !dateB) { + return 1 + } else if (!dateA && dateB) { + return -1 + } else { + // Both "a" and "b" are either date-based or non-date-based, so perform an invariant string comparison + return invariantCompare(a, b) + } +} + +// Returns true if a string contains a substring that looks like a date "YYYY-MM-DD" +const containsDate = (s: string): boolean => { + const dateRegex = /\d{4}-\d{2}-\d{2}/ + return dateRegex.test(s) +} + +const invariantCompare = (a: string, b: string): number => { + // JS doesn't provide a built-in invariantCompare(), and recommends this + return a.localeCompare(b, /* locales */ 'und', { sensitivity: 'variant' }) +} diff --git a/src/index.ts b/src/index.ts index 9b8139d..e187b8b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import * as commonmark from 'commonmark' import * as fs from 'fs' import { JSONPath } from 'jsonpath-plus' import * as path from 'path' +import { compareApiVersion } from './api-version' import * as childProcess from './child-process' import * as cli from './cli' import * as devOps from './dev-ops' @@ -469,7 +470,7 @@ export const validateRPMustContainAllLatestApiVersionSwagger = (dir: string): it export const mergePathTable = (pathTable: PathTable, newPathTable: PathTable): PathTable => { for (const [key, value] of newPathTable) { if (pathTable.has(key)) { - if (pathTable.get(key)!.apiVersion < value.apiVersion) { + if (compareApiVersion(pathTable.get(key)!.apiVersion, value.apiVersion) < 0) { pathTable.set(key, value) } } else { diff --git a/src/test/api-version-test.ts b/src/test/api-version-test.ts new file mode 100644 index 0000000..c80768b --- /dev/null +++ b/src/test/api-version-test.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +import * as assert from 'assert' +import { compareApiVersion } from '../api-version' + +describe('api-version', () => { + it.each([ + // date-based versions + ['2025-01-01', '2025-01-01', 0], + ['2025-01-01', '2025-01-02', -1], + ['2025-01-03', '2025-01-02', 1], + // non-date-based versions + ['7.0', '7.0', 0], + ['7.0', '7.1', -1], + ['7.2', '7.1', 1], + // mixed versions, date-based should always sort later + ['7.0', '2025-01-01', -1], + ['2025-01-01', '7.0', 1], + ])('compares API version strings(%s, %s, %d)', (a: string, b: string, result: number) => { + // Compare both ways, ensure results are inverted + assert.equal(compareApiVersion(a, b), result) + assert.equal(compareApiVersion(b, a), result * -1) + }) +}) diff --git a/src/test/avocado-test.ts b/src/test/avocado-test.ts index 512af9e..2724d4b 100644 --- a/src/test/avocado-test.ts +++ b/src/test/avocado-test.ts @@ -366,6 +366,17 @@ describe('avocado', () => { ) }) + it('avocado check date-based version is latest', async () => { + // Spec contains two versions "7.0" and "2025-07-01", and default version "2025-07-01". If the versions are only + // sorted alphabetically, "7.0" sorts later than "2025-07-01", which results in error + // "NOT_LATEST_API_VERSION_IN_DEFAULT_TAG". Thus, our runtime code must special-case this, and ensure date-based + // versions always sort "later" than non-date-based-versions. + + const r = await avocado.avocado({ cwd: 'src/test/latest_api_version', env: {} }).toArray() + const errorCodes = r.map((e) => e.code) + assert.deepStrictEqual(errorCodes, []) + }) + it('avocado check illegal file, like cadl', async () => { const r = await avocado.avocado({ cwd: 'src/test/contain_cadl_folder', env: {} }).toArray() assert.deepStrictEqual(r.length > 0, true) diff --git a/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/readme.md b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/readme.md new file mode 100644 index 0000000..3674c32 --- /dev/null +++ b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/readme.md @@ -0,0 +1,25 @@ +# latest-api-version + +> see https://aka.ms/autorest + +## Configuration + +### Basic Information + +```yaml +tag: package-2025-07-01 +``` + +### Tag: package-2025-07-01 + +```yaml $(tag) == 'package-2025-07-01' +input-file: + - stable/2025-07-01/spec.json +``` + +### Tag: package-7.0 + +```yaml $(tag) == 'package-7.0' +input-file: + - stable/7.0/spec.json +``` diff --git a/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/2025-07-01/spec.json b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/2025-07-01/spec.json new file mode 100644 index 0000000..c3ef1d6 --- /dev/null +++ b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/2025-07-01/spec.json @@ -0,0 +1,11 @@ +{ + "swagger": "2.0", + "info": { + "version": "2025-07-01" + }, + "paths": { + "/path1": {}, + "/path2": {}, + "/path3": {} + } +} diff --git a/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/7.0/spec.json b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/7.0/spec.json new file mode 100644 index 0000000..9f29f87 --- /dev/null +++ b/src/test/latest_api_version/specification/test/resource-manager/Microsoft.Test/Test/stable/7.0/spec.json @@ -0,0 +1,11 @@ +{ + "swagger": "2.0", + "info": { + "version": "7.0" + }, + "paths": { + "/path1": {}, + "/path2": {}, + "/path3": {} + } +}