From dffa3cc9189ded10b0dcd7ea2f6179137927d5b8 Mon Sep 17 00:00:00 2001 From: Chenjie Shi Date: Tue, 15 Oct 2024 04:01:11 +0800 Subject: [PATCH 01/14] back port https://github.com/Azure/cadl-ranch/pull/748 (#4714) sync cadl-ranch fix of https://github.com/Azure/cadl-ranch/pull/748 --- packages/http-specs/specs/encode/bytes/mockapi.ts | 4 ++-- .../specs/parameters/body-optionality/mockapi.ts | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/http-specs/specs/encode/bytes/mockapi.ts b/packages/http-specs/specs/encode/bytes/mockapi.ts index 3840b5ba17..f1cf120097 100644 --- a/packages/http-specs/specs/encode/bytes/mockapi.ts +++ b/packages/http-specs/specs/encode/bytes/mockapi.ts @@ -208,11 +208,11 @@ Scenarios.Encode_Bytes_RequestBody_base64 = createRequestBodyServerTests( ); Scenarios.Encode_Bytes_RequestBody_base64url = createRequestBodyServerTests( "/encode/bytes/body/request/base64url", - '"dGVzdA=="', + '"dGVzdA"', { "Content-Type": "application/json", }, - '"dGVzdA=="', + '"dGVzdA"', ); Scenarios.Encode_Bytes_RequestBody_customContentType = createRequestBodyServerTests( diff --git a/packages/http-specs/specs/parameters/body-optionality/mockapi.ts b/packages/http-specs/specs/parameters/body-optionality/mockapi.ts index 0e7e2a31ac..dec097235b 100644 --- a/packages/http-specs/specs/parameters/body-optionality/mockapi.ts +++ b/packages/http-specs/specs/parameters/body-optionality/mockapi.ts @@ -47,16 +47,12 @@ Scenarios.Parameters_BodyOptionality_OptionalExplicit = passOnSuccess([ { uri: "/parameters/body-optionality/optional-explicit/omit", method: "post", - request: { - body: { - name: "foo", - }, - }, + request: {}, response: { status: 204, }, handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); + req.expect.rawBodyEquals(undefined); return { status: 204 }; }, kind: "MockApiDefinition", From 224d71a565e51db3b9e9afef930a987865026cdd Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Tue, 15 Oct 2024 04:01:21 +0800 Subject: [PATCH 02/14] specs, back port cadl-ranch fix PR 750 (#4713) back port fix in cadl-ranch (about "explode" query parameters in route (uri template) https://github.com/Azure/cadl-ranch/pull/750 --- packages/http-specs/specs/routes/mockapi.ts | 35 +++++++++------------ 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/http-specs/specs/routes/mockapi.ts b/packages/http-specs/specs/routes/mockapi.ts index ec8eb6a7af..b1a8013c4e 100644 --- a/packages/http-specs/specs/routes/mockapi.ts +++ b/packages/http-specs/specs/routes/mockapi.ts @@ -4,34 +4,29 @@ export const Scenarios: Record = {}; function createTests(uri: string) { const url = new URL("http://example.com" + uri); - const searchParams = url.searchParams; - const params: Record = {}; - for (const [key, value] of searchParams) { - params[key] = value; + const queryMap = new Map(); + for (const [key, value] of url.searchParams.entries()) { + if (queryMap.has(key)) { + const existing = queryMap.get(key)!; + if (Array.isArray(existing)) { + existing.push(value); + } else { + queryMap.set(key, [existing, value]); + } + } else { + queryMap.set(key, value); + } } return passOnSuccess({ uri: url.pathname, method: "get", request: { - params, + params: Object.fromEntries(queryMap), }, response: { status: 204, }, handler: (req: MockRequest) => { - const queryMap = new Map(); - for (const [key, value] of url.searchParams.entries()) { - if (queryMap.has(key)) { - const existing = queryMap.get(key)!; - if (Array.isArray(existing)) { - existing.push(value); - } else { - queryMap.set(key, [existing, value]); - } - } else { - queryMap.set(key, value); - } - } for (const [key, value] of queryMap.entries()) { if (Array.isArray(value)) { req.expect.containsQueryParam(key, value, "multi"); @@ -155,7 +150,7 @@ Scenarios.Routes_QueryParameters_QueryExpansion_Explode_primitive = createTests( "/routes/query/query-expansion/explode/primitive?param=a", ); Scenarios.Routes_QueryParameters_QueryExpansion_Explode_array = createTests( - "/routes/query/query-expansion/explode/array?param=a,b", + "/routes/query/query-expansion/explode/array?param=a¶m=b", ); Scenarios.Routes_QueryParameters_QueryExpansion_Explode_record = createTests( "/routes/query/query-expansion/explode/record?a=1&b=2", @@ -173,7 +168,7 @@ Scenarios.Routes_QueryParameters_QueryContinuation_Explode_primitive = createTes "/routes/query/query-continuation/explode/primitive?fixed=true¶m=a", ); Scenarios.Routes_QueryParameters_QueryContinuation_Explode_array = createTests( - "/routes/query/query-continuation/explode/array?fixed=true¶m=a,b", + "/routes/query/query-continuation/explode/array?fixed=true¶m=a¶m=b", ); Scenarios.Routes_QueryParameters_QueryContinuation_Explode_record = createTests( "/routes/query/query-continuation/explode/record?fixed=true&a=1&b=2", From ab6d9512bdb86ec08eb1ab82b3dd73cd9e9abb3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:40:11 +0000 Subject: [PATCH 03/14] Bump astro from 4.16.0 to 4.16.1 (#4726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) from 4.16.0 to 4.16.1.
Release notes

Sourced from astro's releases.

astro@4.16.1

Patch Changes

  • #12177 a4ffbfa Thanks @​matthewp! - Ensure we target scripts for execution in the router

    Using document.scripts is unsafe because if the application has a name="scripts" this will shadow the built-in document.scripts. Fix is to use getElementsByTagName to ensure we're only grabbing real scripts.

  • #12173 2d10de5 Thanks @​ematipico! - Fixes a bug where Astro Actions couldn't redirect to the correct pathname when there was a rewrite involved.

Changelog

Sourced from astro's changelog.

4.16.1

Patch Changes

  • #12177 a4ffbfa Thanks @​matthewp! - Ensure we target scripts for execution in the router

    Using document.scripts is unsafe because if the application has a name="scripts" this will shadow the built-in document.scripts. Fix is to use getElementsByTagName to ensure we're only grabbing real scripts.

  • #12173 2d10de5 Thanks @​ematipico! - Fixes a bug where Astro Actions couldn't redirect to the correct pathname when there was a rewrite involved.

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=astro&package-manager=npm_and_yarn&previous-version=4.16.0&new-version=4.16.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/microsoft/typespec/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/website-astro/package.json | 2 +- pnpm-lock.yaml | 171 +++++++++++++--------------- 2 files changed, 80 insertions(+), 93 deletions(-) diff --git a/packages/website-astro/package.json b/packages/website-astro/package.json index 573177e6e5..decc253800 100644 --- a/packages/website-astro/package.json +++ b/packages/website-astro/package.json @@ -27,7 +27,7 @@ "@fluentui/react-icons": "^2.0.260", "@typespec/compiler": "workspace:~", "@typespec/playground": "workspace:~", - "astro": "^4.16.0", + "astro": "^4.16.1", "clsx": "^2.1.1", "es-module-shims": "~1.10.0", "prism-react-renderer": "^2.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26fb838754..2fa4c09525 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -617,12 +617,12 @@ importers: '@typespec/spec-api': specifier: workspace:~ version: link:../spec-api - '@typespec/spector': - specifier: workspace:~ - version: link:../spector '@typespec/spec-lib': specifier: workspace:~ version: link:../spec-lib + '@typespec/spector': + specifier: workspace:~ + version: link:../spector '@typespec/versioning': specifier: workspace:~ version: link:../versioning @@ -1484,6 +1484,53 @@ importers: specifier: ^2.1.2 version: 2.1.2(@types/node@22.7.5)(@vitest/ui@2.1.2)(happy-dom@15.7.4)(jsdom@25.0.1)(terser@5.34.1) + packages/spec-coverage-sdk: + dependencies: + '@azure/identity': + specifier: ~4.4.1 + version: 4.4.1 + '@azure/storage-blob': + specifier: ~12.25.0 + version: 12.25.0 + '@types/node': + specifier: ~22.7.5 + version: 22.7.5 + devDependencies: + rimraf: + specifier: ~6.0.1 + version: 6.0.1 + typescript: + specifier: ~5.6.3 + version: 5.6.3 + + packages/spec-lib: + dependencies: + '@typespec/compiler': + specifier: workspace:~ + version: link:../compiler + '@typespec/http': + specifier: workspace:~ + version: link:../http + '@typespec/rest': + specifier: workspace:~ + version: link:../rest + '@typespec/versioning': + specifier: workspace:~ + version: link:../versioning + devDependencies: + '@types/node': + specifier: ~22.7.5 + version: 22.7.5 + '@typespec/tspd': + specifier: workspace:~ + version: link:../tspd + rimraf: + specifier: ~6.0.1 + version: 6.0.1 + typescript: + specifier: ~5.6.3 + version: 5.6.3 + packages/spector: dependencies: '@azure/identity': @@ -1599,53 +1646,6 @@ importers: specifier: ~5.6.3 version: 5.6.3 - packages/spec-coverage-sdk: - dependencies: - '@azure/identity': - specifier: ~4.4.1 - version: 4.4.1 - '@azure/storage-blob': - specifier: ~12.25.0 - version: 12.25.0 - '@types/node': - specifier: ~22.7.5 - version: 22.7.5 - devDependencies: - rimraf: - specifier: ~6.0.1 - version: 6.0.1 - typescript: - specifier: ~5.6.3 - version: 5.6.3 - - packages/spec-lib: - dependencies: - '@typespec/compiler': - specifier: workspace:~ - version: link:../compiler - '@typespec/http': - specifier: workspace:~ - version: link:../http - '@typespec/rest': - specifier: workspace:~ - version: link:../rest - '@typespec/versioning': - specifier: workspace:~ - version: link:../versioning - devDependencies: - '@types/node': - specifier: ~22.7.5 - version: 22.7.5 - '@typespec/tspd': - specifier: workspace:~ - version: link:../tspd - rimraf: - specifier: ~6.0.1 - version: 6.0.1 - typescript: - specifier: ~5.6.3 - version: 5.6.3 - packages/sse: devDependencies: '@types/node': @@ -2058,7 +2058,7 @@ importers: version: 3.6.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vite@5.4.8(@types/node@22.7.5)(terser@5.34.1)) '@astrojs/starlight': specifier: ^0.28.3 - version: 0.28.3(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + version: 0.28.3(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) '@docsearch/css': specifier: ^3.6.2 version: 3.6.2 @@ -2081,8 +2081,8 @@ importers: specifier: workspace:~ version: link:../playground astro: - specifier: ^4.16.0 - version: 4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + specifier: ^4.16.1 + version: 4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -6705,8 +6705,8 @@ packages: peerDependencies: astro: ^4.0.0-beta || ^3.3.0 - astro@4.16.0: - resolution: {integrity: sha512-R5voBFy0yOg57uFnW24WV+RvqPerp9eOoDQoT0pQYqECuGuyV1PsZaSb9Nm0ec+KMLrfO9jvvESFw9LIN6XiUw==} + astro@4.16.1: + resolution: {integrity: sha512-ZeZd+L147HHgHmvoSkve7KM3EutV+hY0mOCa4PwARHEFAAh+omo4MUNoTWsFkfq7ozTgR0PCXQwslrZduoWHNg==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true @@ -8176,9 +8176,6 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-module-lexer@1.5.0: - resolution: {integrity: sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==} - es-module-lexer@1.5.4: resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} @@ -13755,10 +13752,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - yocto-queue@1.1.1: resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} @@ -13988,12 +13981,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@3.1.8(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': + '@astrojs/mdx@3.1.8(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': dependencies: '@astrojs/markdown-remark': 5.3.0 '@mdx-js/mdx': 3.0.1 acorn: 8.12.1 - astro: 4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro: 4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) es-module-lexer: 1.5.4 estree-util-visit: 2.0.0 gray-matter: 4.0.3 @@ -14030,15 +14023,15 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.23.8 - '@astrojs/starlight@0.28.3(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': + '@astrojs/starlight@0.28.3(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': dependencies: - '@astrojs/mdx': 3.1.8(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + '@astrojs/mdx': 3.1.8(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) '@astrojs/sitemap': 3.2.0 '@pagefind/default-ui': 1.1.1 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - astro: 4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) - astro-expressive-code: 0.35.6(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + astro: 4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro-expressive-code: 0.35.6(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.2 @@ -14318,7 +14311,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.24.5': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@babel/helper-module-imports@7.24.7': dependencies: @@ -14346,7 +14339,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.22.5': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@babel/helper-plugin-utils@7.24.7': {} @@ -14375,7 +14368,7 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.22.5': dependencies: - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@babel/helper-split-export-declaration@7.24.7': dependencies: @@ -14492,7 +14485,7 @@ snapshots: '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.25.8)': dependencies: '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-jsx@7.25.7(@babel/core@7.25.8)': dependencies: @@ -14542,7 +14535,7 @@ snapshots: '@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.25.8)': dependencies: '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.25.8)': dependencies: @@ -14805,7 +14798,7 @@ snapshots: '@babel/plugin-transform-react-display-name@7.24.1(@babel/core@7.25.8)': dependencies: '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-react-jsx-development@7.22.5(@babel/core@7.25.8)': dependencies: @@ -14839,7 +14832,7 @@ snapshots: dependencies: '@babel/core': 7.25.8 '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.25.8)': dependencies: @@ -14895,7 +14888,7 @@ snapshots: '@babel/core': 7.25.8 '@babel/helper-annotate-as-pure': 7.25.7 '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.25.8) '@babel/plugin-transform-unicode-escapes@7.24.1(@babel/core@7.25.8)': @@ -18647,7 +18640,7 @@ snapshots: '@shikijs/engine-javascript': 1.21.0 '@shikijs/engine-oniguruma': 1.21.0 '@shikijs/types': 1.21.0 - '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 @@ -18663,7 +18656,7 @@ snapshots: '@shikijs/engine-javascript@1.21.0': dependencies: '@shikijs/types': 1.21.0 - '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/vscode-textmate': 9.3.0 oniguruma-to-js: 0.4.3 '@shikijs/engine-javascript@1.22.0': @@ -18675,7 +18668,7 @@ snapshots: '@shikijs/engine-oniguruma@1.21.0': dependencies: '@shikijs/types': 1.21.0 - '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/vscode-textmate': 9.3.0 '@shikijs/engine-oniguruma@1.22.0': dependencies: @@ -18684,7 +18677,7 @@ snapshots: '@shikijs/types@1.21.0': dependencies: - '@shikijs/vscode-textmate': 9.2.2 + '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 '@shikijs/types@1.22.0': @@ -18809,7 +18802,7 @@ snapshots: dependencies: '@babel/core': 7.25.8 '@babel/preset-env': 7.24.5(@babel/core@7.25.8) - '@babel/types': 7.25.7 + '@babel/types': 7.25.8 '@storybook/core': 8.3.5 '@storybook/csf': 0.1.11 '@types/cross-spawn': 6.0.6 @@ -20286,12 +20279,12 @@ snapshots: astring@1.8.6: {} - astro-expressive-code@0.35.6(astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)): + astro-expressive-code@0.35.6(astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)): dependencies: - astro: 4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro: 4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) rehype-expressive-code: 0.35.6 - astro@4.16.0(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3): + astro@4.16.1(@types/node@22.7.5)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3): dependencies: '@astrojs/compiler': 2.10.3 '@astrojs/internal-helpers': 0.4.1 @@ -20323,7 +20316,6 @@ snapshots: esbuild: 0.21.5 estree-walker: 3.0.3 fast-glob: 3.3.2 - fastq: 1.17.1 flattie: 1.1.1 github-slugger: 2.0.0 gray-matter: 4.0.3 @@ -20344,7 +20336,6 @@ snapshots: rehype: 13.0.2 semver: 7.6.3 shiki: 1.22.0 - string-width: 7.2.0 tinyexec: 0.3.0 tsconfck: 3.1.4(typescript@5.6.3) unist-util-visit: 5.0.0 @@ -22149,8 +22140,6 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.0.0 - es-module-lexer@1.5.0: {} - es-module-lexer@1.5.4: {} es-module-shims@1.10.0: {} @@ -25680,7 +25669,7 @@ snapshots: p-limit@4.0.0: dependencies: - yocto-queue: 1.0.0 + yocto-queue: 1.1.1 p-limit@6.1.0: dependencies: @@ -28760,7 +28749,7 @@ snapshots: browserslist: 4.23.2 chrome-trace-event: 1.0.3 enhanced-resolve: 5.16.0 - es-module-lexer: 1.5.0 + es-module-lexer: 1.5.4 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -29071,8 +29060,6 @@ snapshots: yocto-queue@0.1.0: {} - yocto-queue@1.0.0: {} - yocto-queue@1.1.1: {} zod-to-json-schema@3.23.3(zod@3.23.8): From 3dfdd79c9009b02b75a76227a60f8b09e843f99e Mon Sep 17 00:00:00 2001 From: Sarangan Rajamanickam Date: Mon, 14 Oct 2024 14:09:20 -0700 Subject: [PATCH 04/14] [@typespec/http-specs] - Remove handler for majority of the Scenarios (#4725) This PR handles the removal of `handler` property in majority of the Scenarios. All Scenarios are validated and tested. Please review and approve the PR. Thanks --- .../specs/authentication/api-key/mockapi.ts | 14 +-- .../authentication/http/custom/mockapi.ts | 14 +-- .../specs/authentication/oauth2/mockapi.ts | 14 +-- .../specs/authentication/union/mockapi.ts | 10 +-- .../http-specs/specs/encode/bytes/mockapi.ts | 21 +---- .../specs/encode/duration/mockapi.ts | 26 +----- .../specs/encode/numeric/mockapi.ts | 9 +- .../specs/parameters/basic/mockapi.ts | 6 +- .../parameters/body-optionality/mockapi.ts | 8 -- .../parameters/collection-format/mockapi.ts | 30 ------- .../specs/parameters/spread/mockapi.ts | 53 +---------- .../specs/payload/json-merge-patch/mockapi.ts | 51 +---------- .../specs/payload/media-type/mockapi.ts | 27 +----- .../http-specs/specs/payload/xml/mockapi.ts | 15 +--- .../encoded-name/json/mockapi.ts | 14 +-- .../server/endpoint/not-defined/mockapi.ts | 5 +- .../specs/server/path/multiple/mockapi.ts | 8 +- .../specs/server/path/single/mockapi.ts | 5 +- .../server/versions/not-versioned/mockapi.ts | 4 - .../server/versions/versioned/mockapi.ts | 4 - .../conditional-request/mockapi.ts | 26 +----- .../http-specs/specs/special-words/mockapi.ts | 25 +----- .../http-specs/specs/type/array/mockapi.ts | 14 +-- .../specs/type/dictionary/mockapi.ts | 14 +-- .../specs/type/enum/extensible/mockapi.ts | 9 +- .../specs/type/enum/fixed/mockapi.ts | 13 +-- .../specs/type/model/empty/mockapi.ts | 13 +-- .../inheritance/enum-discriminator/mockapi.ts | 12 +-- .../nested-discriminator/mockapi.ts | 22 +---- .../inheritance/not-discriminated/mockapi.ts | 12 +-- .../model/inheritance/recursive/mockapi.ts | 9 +- .../single-discriminator/mockapi.ts | 25 +----- .../specs/type/model/templated/mockapi.ts | 38 +------- .../specs/type/model/usage/mockapi.ts | 13 +-- .../specs/type/model/visibility/mockapi.ts | 33 +------ .../property/additional-properties/mockapi.ts | 15 +--- .../specs/type/property/nullable/mockapi.ts | 17 +--- .../type/property/optionality/mockapi.ts | 14 +-- .../type/property/value-types/mockapi.ts | 49 ++++++----- .../http-specs/specs/type/scalar/mockapi.ts | 87 +------------------ .../http-specs/specs/type/union/mockapi.ts | 9 +- .../specs/versioning/added/mockapi.ts | 24 +---- .../specs/versioning/madeOptional/mockapi.ts | 9 +- .../specs/versioning/removed/mockapi.ts | 9 +- .../specs/versioning/renamedFrom/mockapi.ts | 17 +--- .../returnTypeChangedFrom/mockapi.ts | 9 +- .../versioning/typeChangedFrom/mockapi.ts | 10 +-- packages/spector/src/app/app.ts | 43 ++++++++- 48 files changed, 113 insertions(+), 815 deletions(-) diff --git a/packages/http-specs/specs/authentication/api-key/mockapi.ts b/packages/http-specs/specs/authentication/api-key/mockapi.ts index ac41373a27..2d8bfd8073 100644 --- a/packages/http-specs/specs/authentication/api-key/mockapi.ts +++ b/packages/http-specs/specs/authentication/api-key/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -17,14 +17,6 @@ Scenarios.Authentication_ApiKey_invalid = passOnSuccess({ error: "invalid-api-key", }), }, - handler: (req: MockRequest) => { - return { - status: 403, - body: json({ - error: "invalid-api-key", - }), - }; - }, kind: "MockApiDefinition", }); @@ -39,9 +31,5 @@ Scenarios.Authentication_ApiKey_valid = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-api-key", "valid-key"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/authentication/http/custom/mockapi.ts b/packages/http-specs/specs/authentication/http/custom/mockapi.ts index 882f64ee20..9ebb14b972 100644 --- a/packages/http-specs/specs/authentication/http/custom/mockapi.ts +++ b/packages/http-specs/specs/authentication/http/custom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Authentication_Http_Custom_valid = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("authorization", "SharedAccessKey valid-key"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -35,13 +31,5 @@ Scenarios.Authentication_Http_Custom_invalid = passOnSuccess({ error: "invalid-api-key", }), }, - handler: (req: MockRequest) => { - return { - status: 403, - body: json({ - error: "invalid-api-key", - }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/authentication/oauth2/mockapi.ts b/packages/http-specs/specs/authentication/oauth2/mockapi.ts index acc244e29d..85fcaf576c 100644 --- a/packages/http-specs/specs/authentication/oauth2/mockapi.ts +++ b/packages/http-specs/specs/authentication/oauth2/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Authentication_OAuth2_valid = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("authorization", "Bearer https://security.microsoft.com/.default"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -32,13 +28,5 @@ Scenarios.Authentication_OAuth2_invalid = passOnSuccess({ error: "invalid-grant", }), }, - handler: (req: MockRequest) => { - return { - status: 403, - body: json({ - error: "invalid-grant", - }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/authentication/union/mockapi.ts b/packages/http-specs/specs/authentication/union/mockapi.ts index 1efed8a672..e52e327ec3 100644 --- a/packages/http-specs/specs/authentication/union/mockapi.ts +++ b/packages/http-specs/specs/authentication/union/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Authentication_Union_validKey = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-api-key", "valid-key"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -31,9 +27,5 @@ Scenarios.Authentication_Union_validToken = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("authorization", "Bearer https://security.microsoft.com/.default"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/encode/bytes/mockapi.ts b/packages/http-specs/specs/encode/bytes/mockapi.ts index f1cf120097..f267462203 100644 --- a/packages/http-specs/specs/encode/bytes/mockapi.ts +++ b/packages/http-specs/specs/encode/bytes/mockapi.ts @@ -1,11 +1,5 @@ import { resolvePath } from "@typespec/compiler"; -import { - CollectionFormat, - json, - MockRequest, - passOnSuccess, - ScenarioMockApi, -} from "@typespec/spec-api"; +import { CollectionFormat, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; import { readFileSync } from "fs"; import { fileURLToPath } from "url"; @@ -78,13 +72,6 @@ function createPropertyServerTests(uri: string, data: any, value: any) { response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals({ value: value }); - return { - status: 200, - body: json({ value: value }), - }; - }, kind: "MockApiDefinition", }); } @@ -126,12 +113,6 @@ function createHeaderServerTests(uri: string, data: any, value: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("value", value); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/encode/duration/mockapi.ts b/packages/http-specs/specs/encode/duration/mockapi.ts index 6008f9ad0a..c6c5413a0c 100644 --- a/packages/http-specs/specs/encode/duration/mockapi.ts +++ b/packages/http-specs/specs/encode/duration/mockapi.ts @@ -19,13 +19,6 @@ function createBodyServerTests(uri: string, data: any, value: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals({ value: value }); - return { - status: 200, - body: json({ value: value }), - }; - }, kind: "MockApiDefinition", }); } @@ -150,12 +143,6 @@ function createHeaderServerTests(uri: string, headersData: any, value: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("duration", value); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } @@ -177,28 +164,21 @@ Scenarios.Encode_Duration_Header_iso8601 = createHeaderServerTests( Scenarios.Encode_Duration_Header_int32Seconds = createHeaderServerTests( "/encode/duration/header/int32-seconds", { - duration: 36, + duration: "36", }, "36", ); Scenarios.Encode_Duration_Header_floatSeconds = createHeaderServerTests( "/encode/duration/header/float-seconds", { - duration: 35.625, - }, - "35.625", -); -Scenarios.Encode_Duration_Header_floatSeconds = createHeaderServerTests( - "/encode/duration/header/float64-seconds", - { - duration: 35.625, + duration: "35.625", }, "35.625", ); Scenarios.Encode_Duration_Header_float64Seconds = createHeaderServerTests( "/encode/duration/header/float64-seconds", { - duration: 35.625, + duration: "35.625", }, "35.625", ); diff --git a/packages/http-specs/specs/encode/numeric/mockapi.ts b/packages/http-specs/specs/encode/numeric/mockapi.ts index 80292c88a4..42814aa5a0 100644 --- a/packages/http-specs/specs/encode/numeric/mockapi.ts +++ b/packages/http-specs/specs/encode/numeric/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -15,13 +15,6 @@ function createTests(uri: string, value: any) { status: 200, body: json({ value }), }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals({ value }); - return { - status: 200, - body: json({ value }), - }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/parameters/basic/mockapi.ts b/packages/http-specs/specs/parameters/basic/mockapi.ts index 509cce0aa4..8cd105bca0 100644 --- a/packages/http-specs/specs/parameters/basic/mockapi.ts +++ b/packages/http-specs/specs/parameters/basic/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; function createServerTests(uri: string) { @@ -13,10 +13,6 @@ function createServerTests(uri: string) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/parameters/body-optionality/mockapi.ts b/packages/http-specs/specs/parameters/body-optionality/mockapi.ts index dec097235b..8cefc38e52 100644 --- a/packages/http-specs/specs/parameters/body-optionality/mockapi.ts +++ b/packages/http-specs/specs/parameters/body-optionality/mockapi.ts @@ -11,10 +11,6 @@ function createServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); } @@ -38,10 +34,6 @@ Scenarios.Parameters_BodyOptionality_OptionalExplicit = passOnSuccess([ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }, { diff --git a/packages/http-specs/specs/parameters/collection-format/mockapi.ts b/packages/http-specs/specs/parameters/collection-format/mockapi.ts index 60f97a1362..5b5bdc1b23 100644 --- a/packages/http-specs/specs/parameters/collection-format/mockapi.ts +++ b/packages/http-specs/specs/parameters/collection-format/mockapi.ts @@ -31,12 +31,6 @@ Scenarios.Parameters_CollectionFormat_Query_csv = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("colors", ["blue", "red", "green"], "csv"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -49,12 +43,6 @@ Scenarios.Parameters_CollectionFormat_Query_ssv = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("colors", ["blue", "red", "green"], "ssv"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -67,12 +55,6 @@ Scenarios.Parameters_CollectionFormat_Query_tsv = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("colors", ["blue", "red", "green"], "tsv"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -85,12 +67,6 @@ Scenarios.Parameters_CollectionFormat_Query_pipes = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("colors", ["blue", "red", "green"], "pipes"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -103,11 +79,5 @@ Scenarios.Parameters_CollectionFormat_Header_csv = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("colors", "blue,red,green"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/parameters/spread/mockapi.ts b/packages/http-specs/specs/parameters/spread/mockapi.ts index cf675e67a3..171a52cd97 100644 --- a/packages/http-specs/specs/parameters/spread/mockapi.ts +++ b/packages/http-specs/specs/parameters/spread/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Parameters_Spread_Model_spreadAsRequestBody = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -31,10 +27,6 @@ Scenarios.Parameters_Spread_Model_spreadCompositeRequestOnlyWithBody = passOnSuc response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -49,10 +41,6 @@ Scenarios.Parameters_Spread_Model_spreadCompositeRequestWithoutBody = passOnSucc response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("test-header", "bar"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -70,11 +58,6 @@ Scenarios.Parameters_Spread_Model_spreadCompositeRequest = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("test-header", "bar"); - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -92,11 +75,6 @@ Scenarios.Parameters_Spread_Model_spreadCompositeRequestMix = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("test-header", "bar"); - req.expect.bodyEquals({ prop: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -111,10 +89,6 @@ Scenarios.Parameters_Spread_Alias_spreadAsRequestBody = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -132,11 +106,6 @@ Scenarios.Parameters_Spread_Alias_spreadAsRequestParameter = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-test-header", "bar"); - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -157,16 +126,6 @@ Scenarios.Parameters_Spread_Alias_spreadWithMultipleParameters = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-test-header", "bar"); - req.expect.bodyEquals({ - requiredString: "foo", - optionalInt: 1, - requiredIntList: [1, 2], - optionalStringList: ["foo", "bar"], - }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -184,11 +143,6 @@ Scenarios.Parameters_Spread_Alias_spreadParameterWithInnerModel = passOnSuccess( response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-test-header", "bar"); - req.expect.bodyEquals({ name: "foo" }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -207,10 +161,5 @@ Scenarios.Parameters_Spread_Alias_spreadParameterWithInnerAlias = passOnSuccess( response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("x-ms-test-header", "bar"); - req.expect.bodyEquals({ name: "foo", age: 1 }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/payload/json-merge-patch/mockapi.ts b/packages/http-specs/specs/payload/json-merge-patch/mockapi.ts index 79390e0473..6fa60ba349 100644 --- a/packages/http-specs/specs/payload/json-merge-patch/mockapi.ts +++ b/packages/http-specs/specs/payload/json-merge-patch/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -53,13 +53,6 @@ Scenarios.Payload_JsonMergePatch_createResource = passOnSuccess({ status: 200, body: json(expectedCreateBody), }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals(expectedCreateBody); - return { - status: 200, - body: json(expectedCreateBody), - }; - }, kind: "MockApiDefinition", }); @@ -80,27 +73,6 @@ Scenarios.Payload_JsonMergePatch_updateResource = passOnSuccess({ }, }), }, - handler: (req: MockRequest) => { - req.expect.deepEqual(req.body.description, expectedUpdateBody.description); - req.expect.deepEqual(req.body.map.key.description, expectedUpdateBody.map.key.description); - req.expect.deepEqual(req.body.map.key2, expectedUpdateBody.map.key2); - req.expect.deepEqual(req.body.array, expectedUpdateBody.array); - req.expect.deepEqual(req.body.intValue, expectedUpdateBody.intValue); - req.expect.deepEqual(req.body.floatValue, expectedUpdateBody.floatValue); - req.expect.deepEqual(req.body.innerModel, expectedUpdateBody.innerModel); - req.expect.deepEqual(req.body.intArray, expectedUpdateBody.intArray); - return { - status: 200, - body: json({ - name: "Madge", - map: { - key: { - name: "InnerMadge", - }, - }, - }), - }; - }, kind: "MockApiDefinition", }); @@ -121,26 +93,5 @@ Scenarios.Payload_JsonMergePatch_updateOptionalResource = passOnSuccess({ }, }), }, - handler: (req: MockRequest) => { - req.expect.deepEqual(req.body.description, expectedUpdateBody.description); - req.expect.deepEqual(req.body.map.key.description, expectedUpdateBody.map.key.description); - req.expect.deepEqual(req.body.map.key2, expectedUpdateBody.map.key2); - req.expect.deepEqual(req.body.array, expectedUpdateBody.array); - req.expect.deepEqual(req.body.intValue, expectedUpdateBody.intValue); - req.expect.deepEqual(req.body.floatValue, expectedUpdateBody.floatValue); - req.expect.deepEqual(req.body.innerModel, expectedUpdateBody.innerModel); - req.expect.deepEqual(req.body.intArray, expectedUpdateBody.intArray); - return { - status: 200, - body: json({ - name: "Madge", - map: { - key: { - name: "InnerMadge", - }, - }, - }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/payload/media-type/mockapi.ts b/packages/http-specs/specs/payload/media-type/mockapi.ts index 0e9d04174d..71775f9f7e 100644 --- a/packages/http-specs/specs/payload/media-type/mockapi.ts +++ b/packages/http-specs/specs/payload/media-type/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -14,11 +14,6 @@ Scenarios.Payload_MediaType_StringBody_sendAsText = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("content-type", "text/plain"); - req.expect.bodyEquals("{cat}"); - return { status: 200 }; - }, kind: "MockApiDefinition", }); @@ -34,13 +29,6 @@ Scenarios.Payload_MediaType_StringBody_getAsText = passOnSuccess({ status: 200, body: { rawContent: "{cat}", contentType: "text/plain" }, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("accept", "text/plain"); - return { - status: 200, - body: { rawContent: "{cat}", contentType: "text/plain" }, - }; - }, kind: "MockApiDefinition", }); @@ -56,11 +44,6 @@ Scenarios.Payload_MediaType_StringBody_sendAsJson = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("content-type", "application/json"); - req.expect.bodyEquals("foo"); - return { status: 200 }; - }, kind: "MockApiDefinition", }); @@ -76,13 +59,5 @@ Scenarios.Payload_MediaType_StringBody_getAsJson = passOnSuccess({ status: 200, body: json("foo"), }, - handler: (req: MockRequest) => { - req.expect.containsHeader("accept", "application/json"); - return { - status: 200, - body: json("foo"), - contentType: "application/json", - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/payload/xml/mockapi.ts b/packages/http-specs/specs/payload/xml/mockapi.ts index 23d7369d82..d6273feec8 100644 --- a/packages/http-specs/specs/payload/xml/mockapi.ts +++ b/packages/http-specs/specs/payload/xml/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi, xml } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi, xml } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -133,12 +133,6 @@ function createServerTests(uri: string, data?: any) { status: 200, body: xml(data), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: xml(data), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -153,13 +147,6 @@ function createServerTests(uri: string, data?: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("content-type", "application/xml"); - req.expect.xmlBodyEquals(data); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/serialization/encoded-name/json/mockapi.ts b/packages/http-specs/specs/serialization/encoded-name/json/mockapi.ts index b20c74b5f6..d4cc9bf62b 100644 --- a/packages/http-specs/specs/serialization/encoded-name/json/mockapi.ts +++ b/packages/http-specs/specs/serialization/encoded-name/json/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -7,12 +7,6 @@ Scenarios.Serialization_EncodedName_Json_Property_send = passOnSuccess({ method: "post", request: { body: { wireName: true } }, response: { status: 204 }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ wireName: true }); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -24,11 +18,5 @@ Scenarios.Serialization_EncodedName_Json_Property_get = passOnSuccess({ status: 200, body: json({ wireName: true }), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json({ wireName: true }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/endpoint/not-defined/mockapi.ts b/packages/http-specs/specs/server/endpoint/not-defined/mockapi.ts index 4d87c11583..c3a58c0882 100644 --- a/packages/http-specs/specs/server/endpoint/not-defined/mockapi.ts +++ b/packages/http-specs/specs/server/endpoint/not-defined/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -9,8 +9,5 @@ Scenarios.Server_Endpoint_NotDefined_valid = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - return { status: 200 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/path/multiple/mockapi.ts b/packages/http-specs/specs/server/path/multiple/mockapi.ts index 6d2fa3bd1c..8c5fef06e1 100644 --- a/packages/http-specs/specs/server/path/multiple/mockapi.ts +++ b/packages/http-specs/specs/server/path/multiple/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -9,9 +9,6 @@ Scenarios.Server_Path_Multiple_noOperationParams = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -22,8 +19,5 @@ Scenarios.Server_Path_Multiple_withOperationPathParam = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/path/single/mockapi.ts b/packages/http-specs/specs/server/path/single/mockapi.ts index cd2f2615c2..93619e7357 100644 --- a/packages/http-specs/specs/server/path/single/mockapi.ts +++ b/packages/http-specs/specs/server/path/single/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -9,8 +9,5 @@ Scenarios.Server_Path_Single_myOp = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - return { status: 200 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/versions/not-versioned/mockapi.ts b/packages/http-specs/specs/server/versions/not-versioned/mockapi.ts index 9e4b511d60..e8737bd640 100644 --- a/packages/http-specs/specs/server/versions/not-versioned/mockapi.ts +++ b/packages/http-specs/specs/server/versions/not-versioned/mockapi.ts @@ -47,9 +47,5 @@ Scenarios.Server_Versions_NotVersioned_withQueryApiVersion = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("api-version", "v1.0"); - return { status: 200 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/server/versions/versioned/mockapi.ts b/packages/http-specs/specs/server/versions/versioned/mockapi.ts index f1e6e1b11a..7bae7e0a6b 100644 --- a/packages/http-specs/specs/server/versions/versioned/mockapi.ts +++ b/packages/http-specs/specs/server/versions/versioned/mockapi.ts @@ -39,10 +39,6 @@ function createAPIVersionTests(uri: string, requestData: any, serverData: string response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("api-version", serverData); - return { status: 200 }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/special-headers/conditional-request/mockapi.ts b/packages/http-specs/specs/special-headers/conditional-request/mockapi.ts index 74dd4d40e6..ddad3105af 100644 --- a/packages/http-specs/specs/special-headers/conditional-request/mockapi.ts +++ b/packages/http-specs/specs/special-headers/conditional-request/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,12 +13,6 @@ Scenarios.SpecialHeaders_ConditionalRequest_postIfUnmodifiedSince = passOnSucces response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("if-unmodified-since", "Fri, 26 Aug 2022 14:38:00 GMT"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -33,12 +27,6 @@ Scenarios.SpecialHeaders_ConditionalRequest_headIfModifiedSince = passOnSuccess( response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("if-modified-since", "Fri, 26 Aug 2022 14:38:00 GMT"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -53,12 +41,6 @@ Scenarios.SpecialHeaders_ConditionalRequest_postIfMatch = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("if-match", '"valid"'); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -73,11 +55,5 @@ Scenarios.SpecialHeaders_ConditionalRequest_postIfNoneMatch = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsHeader("if-none-match", '"invalid"'); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/special-words/mockapi.ts b/packages/http-specs/specs/special-words/mockapi.ts index 9ade4e38b7..5dee179e4e 100644 --- a/packages/http-specs/specs/special-words/mockapi.ts +++ b/packages/http-specs/specs/special-words/mockapi.ts @@ -1,4 +1,4 @@ -import { MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,12 +13,6 @@ Scenarios.SpecialWords_ModelProperties_sameAsModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ ["SameAsModel"]: "ok" }); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); @@ -34,12 +28,6 @@ function createModelsTests(uri: string) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ name: "ok" }); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } @@ -85,11 +73,6 @@ function createOperationsTests(uri: string) { response: { status: 204, }, - handler: (req: MockRequest) => { - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } @@ -156,12 +139,6 @@ function createParametersTests(uri: string, data: any, paramName: string) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam(paramName, "ok"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/type/array/mockapi.ts b/packages/http-specs/specs/type/array/mockapi.ts index 469b8d3ea1..719a2e4d0f 100644 --- a/packages/http-specs/specs/type/array/mockapi.ts +++ b/packages/http-specs/specs/type/array/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,12 +12,6 @@ function createServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(data), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -29,12 +23,6 @@ function createServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals(data); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/dictionary/mockapi.ts b/packages/http-specs/specs/type/dictionary/mockapi.ts index a7b28a2587..159ed922e7 100644 --- a/packages/http-specs/specs/type/dictionary/mockapi.ts +++ b/packages/http-specs/specs/type/dictionary/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,12 +12,6 @@ function createServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(data), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -29,12 +23,6 @@ function createServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.coercedBodyEquals(data); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/enum/extensible/mockapi.ts b/packages/http-specs/specs/type/enum/extensible/mockapi.ts index 71bd51f067..9c51d6570b 100644 --- a/packages/http-specs/specs/type/enum/extensible/mockapi.ts +++ b/packages/http-specs/specs/type/enum/extensible/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,9 +12,6 @@ function createMockServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(data) }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -29,10 +26,6 @@ function createMockServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(data); - return { status: 204 }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/enum/fixed/mockapi.ts b/packages/http-specs/specs/type/enum/fixed/mockapi.ts index e06624e958..41f0343175 100644 --- a/packages/http-specs/specs/type/enum/fixed/mockapi.ts +++ b/packages/http-specs/specs/type/enum/fixed/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -10,9 +10,6 @@ Scenarios.Type_Enum_Fixed_String_getKnownValue = passOnSuccess({ status: 200, body: json("Monday"), }, - handler: (req: MockRequest) => { - return { status: 200, body: json("Monday") }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Enum_Fixed_String_putKnownValue = passOnSuccess({ @@ -27,10 +24,6 @@ Scenarios.Type_Enum_Fixed_String_putKnownValue = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("Monday"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -47,9 +40,5 @@ Scenarios.Type_Enum_Fixed_String_putUnknownValue = passOnSuccess({ response: { status: 500, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("Weekend"); - return { status: 500 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/empty/mockapi.ts b/packages/http-specs/specs/type/model/empty/mockapi.ts index cb5858ea63..a4ab1731ef 100644 --- a/packages/http-specs/specs/type/model/empty/mockapi.ts +++ b/packages/http-specs/specs/type/model/empty/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Type_Model_Empty_putEmpty = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Empty_getEmpty = passOnSuccess({ @@ -27,9 +23,6 @@ Scenarios.Type_Model_Empty_getEmpty = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); @@ -43,9 +36,5 @@ Scenarios.Type_Model_Empty_postRoundTripEmpty = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/inheritance/enum-discriminator/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/enum-discriminator/mockapi.ts index b288187344..eb06680737 100644 --- a/packages/http-specs/specs/type/model/inheritance/enum-discriminator/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/enum-discriminator/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -19,9 +19,6 @@ function createGetServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(data) }; - }, kind: "MockApiDefinition", }); } @@ -36,9 +33,6 @@ function createGetPutServerTests(uri: string, data: any) { status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(data) }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -50,10 +44,6 @@ function createGetPutServerTests(uri: string, data: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(data); - return { status: 204 }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/model/inheritance/nested-discriminator/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/nested-discriminator/mockapi.ts index a35af68f7d..9b657fec1a 100644 --- a/packages/http-specs/specs/type/model/inheritance/nested-discriminator/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/nested-discriminator/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -72,9 +72,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_getModel = passOnSuccess({ status: 200, body: json(validPolymorphicBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(validPolymorphicBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NestedDiscriminator_putModel = passOnSuccess({ @@ -86,10 +83,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_putModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(validPolymorphicBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -101,9 +94,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_getRecursiveModel = passOnS status: 200, body: json(validRecursiveBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(validRecursiveBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NestedDiscriminator_putRecursiveModel = passOnSuccess({ @@ -115,10 +105,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_putRecursiveModel = passOnS response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(validRecursiveBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -130,9 +116,6 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_getMissingDiscriminator = p status: 200, body: json({ age: 1 }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ age: 1 }) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NestedDiscriminator_getWrongDiscriminator = passOnSuccess({ @@ -143,8 +126,5 @@ Scenarios.Type_Model_Inheritance_NestedDiscriminator_getWrongDiscriminator = pas status: 200, body: json({ age: 1, kind: "wrongKind" }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ age: 1, kind: "wrongKind" }) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/inheritance/not-discriminated/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/not-discriminated/mockapi.ts index 3e1bbdb511..f8e3a83188 100644 --- a/packages/http-specs/specs/type/model/inheritance/not-discriminated/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/not-discriminated/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -13,10 +13,6 @@ Scenarios.Type_Model_Inheritance_NotDiscriminated_postValid = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(inheritanceValidBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NotDiscriminated_getValid = passOnSuccess({ @@ -27,9 +23,6 @@ Scenarios.Type_Model_Inheritance_NotDiscriminated_getValid = passOnSuccess({ status: 200, body: json(inheritanceValidBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(inheritanceValidBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_NotDiscriminated_putValid = passOnSuccess({ @@ -42,8 +35,5 @@ Scenarios.Type_Model_Inheritance_NotDiscriminated_putValid = passOnSuccess({ status: 200, body: json(inheritanceValidBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(req.body) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/inheritance/recursive/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/recursive/mockapi.ts index f5cac6e012..30db7228d2 100644 --- a/packages/http-specs/specs/type/model/inheritance/recursive/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/recursive/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -27,10 +27,6 @@ Scenarios.Type_Model_Inheritance_Recursive_put = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_Recursive_get = passOnSuccess({ @@ -41,8 +37,5 @@ Scenarios.Type_Model_Inheritance_Recursive_get = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts index 9d86e79c21..ba6958ac18 100644 --- a/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -34,9 +34,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getModel = passOnSuccess({ status: 200, body: json(validPolymorphicBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(validPolymorphicBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_SingleDiscriminator_putModel = passOnSuccess({ @@ -48,10 +45,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_putModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(validPolymorphicBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -63,9 +56,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getRecursiveModel = passOnS status: 200, body: json(validRecursiveBody), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(validRecursiveBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_SingleDiscriminator_putRecursiveModel = passOnSuccess({ @@ -77,10 +67,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_putRecursiveModel = passOnS response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(validRecursiveBody); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -92,9 +78,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getMissingDiscriminator = p status: 200, body: json({ wingspan: 1 }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ wingspan: 1 }) }; - }, kind: "MockApiDefinition", }); @@ -106,9 +89,6 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getWrongDiscriminator = pas status: 200, body: json({ wingspan: 1, kind: "wrongKind" }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ wingspan: 1, kind: "wrongKind" }) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Inheritance_SingleDiscriminator_getLegacyModel = passOnSuccess({ @@ -119,8 +99,5 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getLegacyModel = passOnSucc status: 200, body: json({ size: 20, kind: "t-rex" }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ size: 20, kind: "t-rex" }) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/templated/mockapi.ts b/packages/http-specs/specs/type/model/templated/mockapi.ts index 474f76920b..cd69b9bfa8 100644 --- a/packages/http-specs/specs/type/model/templated/mockapi.ts +++ b/packages/http-specs/specs/type/model/templated/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -20,18 +20,6 @@ Scenarios.Type_Model_Templated_numericType = passOnSuccess({ value: 1234, }), }, - handler: (req: MockRequest) => { - const body = { - kind: "Int32Values", - values: [1234], - value: 1234, - }; - req.expect.bodyEquals(body); - return { - status: 200, - body: json(body), - }; - }, kind: "MockApiDefinition", }); @@ -53,18 +41,6 @@ Scenarios.Type_Model_Templated_float32Type = passOnSuccess({ value: 0.5, }), }, - handler: (req: MockRequest) => { - const body = { - kind: "Float32Values", - values: [0.5], - value: 0.5, - }; - req.expect.bodyEquals(body); - return { - status: 200, - body: json(body), - }; - }, kind: "MockApiDefinition", }); @@ -86,17 +62,5 @@ Scenarios.Type_Model_Templated_int32Type = passOnSuccess({ value: 1234, }), }, - handler: (req: MockRequest) => { - const body = { - kind: "Int32Values", - values: [1234], - value: 1234, - }; - req.expect.bodyEquals(body); - return { - status: 200, - body: json(body), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/usage/mockapi.ts b/packages/http-specs/specs/type/model/usage/mockapi.ts index caa1c77e5e..f705391f97 100644 --- a/packages/http-specs/specs/type/model/usage/mockapi.ts +++ b/packages/http-specs/specs/type/model/usage/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -15,10 +15,6 @@ Scenarios.Type_Model_Usage_input = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -30,9 +26,6 @@ Scenarios.Type_Model_Usage_output = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); @@ -48,9 +41,5 @@ Scenarios.Type_Model_Usage_inputAndOutput = passOnSuccess({ status: 200, body: json(body), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(body); - return { status: 200, body: json(body) }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/model/visibility/mockapi.ts b/packages/http-specs/specs/type/model/visibility/mockapi.ts index 74a4a45c17..22ac0dfe64 100644 --- a/packages/http-specs/specs/type/model/visibility/mockapi.ts +++ b/packages/http-specs/specs/type/model/visibility/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -30,10 +30,6 @@ Scenarios.Type_Model_Visibility_putReadOnlyModel = passOnSuccess({ status: 200, body: json(expectBody), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({}); - return { status: 200, body: json(expectBody) }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_headModel = passOnSuccess({ @@ -45,10 +41,6 @@ Scenarios.Type_Model_Visibility_headModel = passOnSuccess({ response: { status: 200, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["queryProp"])); - return { status: 200 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_getModel = passOnSuccess({ @@ -61,13 +53,6 @@ Scenarios.Type_Model_Visibility_getModel = passOnSuccess({ status: 200, body: json(genData(["readProp"])), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["queryProp"])); - return { - status: 200, - body: json(genData(["readProp"])), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_putModel = passOnSuccess({ @@ -82,10 +67,6 @@ Scenarios.Type_Model_Visibility_putModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["createProp", "updateProp"])); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_patchModel = passOnSuccess({ @@ -99,10 +80,6 @@ Scenarios.Type_Model_Visibility_patchModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["updateProp"])); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_postModel = passOnSuccess({ @@ -116,10 +93,6 @@ Scenarios.Type_Model_Visibility_postModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["createProp"])); - return { status: 204 }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Model_Visibility_deleteModel = passOnSuccess({ @@ -131,9 +104,5 @@ Scenarios.Type_Model_Visibility_deleteModel = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(genData(["deleteProp"])); - return { status: 204 }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/property/additional-properties/mockapi.ts b/packages/http-specs/specs/type/property/additional-properties/mockapi.ts index 672b1692e0..f029442b99 100644 --- a/packages/http-specs/specs/type/property/additional-properties/mockapi.ts +++ b/packages/http-specs/specs/type/property/additional-properties/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -108,12 +108,6 @@ function createServerTests(url: string, value: any) { status: 200, body: json(value), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(value), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -125,13 +119,6 @@ function createServerTests(url: string, value: any) { response: { status: 204, }, - handler: (req: MockRequest) => { - const expectedBody = JSON.parse(JSON.stringify(value)); - req.expect.coercedBodyEquals(expectedBody); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/property/nullable/mockapi.ts b/packages/http-specs/specs/type/property/nullable/mockapi.ts index 625ed5d388..5c54b8608c 100644 --- a/packages/http-specs/specs/type/property/nullable/mockapi.ts +++ b/packages/http-specs/specs/type/property/nullable/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,12 +12,6 @@ function createServerTests(url: string, value: unknown, patchNullableProperty?: status: 200, body: json(value), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(value), - }; - }, kind: "MockApiDefinition", }), patch: passOnSuccess({ @@ -35,15 +29,6 @@ function createServerTests(url: string, value: unknown, patchNullableProperty?: response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ - requiredProperty: "foo", - nullableProperty: patchNullableProperty || null, - }); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/property/optionality/mockapi.ts b/packages/http-specs/specs/type/property/optionality/mockapi.ts index 874f1fb841..bb8234c1b8 100644 --- a/packages/http-specs/specs/type/property/optionality/mockapi.ts +++ b/packages/http-specs/specs/type/property/optionality/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -12,12 +12,6 @@ function createServerTests(url: string, value: unknown) { status: 200, body: json(value), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(value), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ @@ -29,12 +23,6 @@ function createServerTests(url: string, value: unknown) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(value); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; diff --git a/packages/http-specs/specs/type/property/value-types/mockapi.ts b/packages/http-specs/specs/type/property/value-types/mockapi.ts index 7bc9bc68bd..45a9574223 100644 --- a/packages/http-specs/specs/type/property/value-types/mockapi.ts +++ b/packages/http-specs/specs/type/property/value-types/mockapi.ts @@ -2,14 +2,7 @@ import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spe export const Scenarios: Record = {}; -function createServerTests(url: string, data: unknown, convertedToFn?: (_: any) => any) { - let property; - if (convertedToFn) { - property = convertedToFn(data); - } else { - property = data; - } - +function createServerTests(url: string, data: unknown) { return { get: passOnSuccess({ uri: url, @@ -19,30 +12,17 @@ function createServerTests(url: string, data: unknown, convertedToFn?: (_: any) status: 200, body: json(data), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(data), - }; - }, kind: "MockApiDefinition", }), put: passOnSuccess({ uri: url, method: `put`, request: { - body: property, + body: data, }, response: { status: 204, }, - handler: (req: MockRequest) => { - const expectedBody = JSON.parse(JSON.stringify(property)); - req.expect.coercedBodyEquals(expectedBody); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }), }; @@ -176,7 +156,30 @@ const Type_Property_ValueTypes_Never = createServerTests(`/type/property/value-t property: undefined, }); Scenarios.Type_Property_ValueTypes_Never_get = Type_Property_ValueTypes_Never.get; -Scenarios.Type_Property_ValueTypes_Never_put = Type_Property_ValueTypes_Never.put; +Scenarios.Type_Property_ValueTypes_Never_put = passOnSuccess({ + uri: `/type/property/value-types/never`, + method: `put`, + request: { + body: { + property: undefined, + }, + }, + response: { + status: 204, + }, + handler: (req: MockRequest) => { + const expectedBody = JSON.parse( + JSON.stringify({ + property: undefined, + }), + ); + req.expect.coercedBodyEquals(expectedBody); + return { + status: 204, + }; + }, + kind: "MockApiDefinition", +}); const Type_Property_ValueTypes_Unknown_String = createServerTests( `/type/property/value-types/unknown/string`, diff --git a/packages/http-specs/specs/type/scalar/mockapi.ts b/packages/http-specs/specs/type/scalar/mockapi.ts index 77ac2df9b2..97c5e5eeeb 100644 --- a/packages/http-specs/specs/type/scalar/mockapi.ts +++ b/packages/http-specs/specs/type/scalar/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -10,9 +10,6 @@ Scenarios.Type_Scalar_String_get = passOnSuccess({ status: 200, body: json("test"), }, - handler: (req: MockRequest) => { - return { status: 200, body: json("test") }; - }, kind: "MockApiDefinition", }); @@ -28,10 +25,6 @@ Scenarios.Type_Scalar_String_put = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("test"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -43,9 +36,6 @@ Scenarios.Type_Scalar_Boolean_get = passOnSuccess({ status: 200, body: json(true), }, - handler: (req: MockRequest) => { - return { status: 200, body: json(true) }; - }, kind: "MockApiDefinition", }); @@ -61,10 +51,6 @@ Scenarios.Type_Scalar_Boolean_put = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(true); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -76,9 +62,6 @@ Scenarios.Type_Scalar_Unknown_get = passOnSuccess({ status: 200, body: json("test"), }, - handler: (req: MockRequest) => { - return { status: 200, body: json("test") }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Unknown_put = passOnSuccess({ @@ -93,10 +76,6 @@ Scenarios.Type_Scalar_Unknown_put = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("test"); - return { status: 204 }; - }, kind: "MockApiDefinition", }); @@ -108,12 +87,6 @@ Scenarios.Type_Scalar_DecimalType_responseBody = passOnSuccess({ status: 200, body: json(0.33333), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(0.33333), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Type_responseBody = passOnSuccess({ @@ -124,12 +97,6 @@ Scenarios.Type_Scalar_Decimal128Type_responseBody = passOnSuccess({ status: 200, body: json(0.33333), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json(0.33333), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_DecimalType_requestBody = passOnSuccess({ @@ -144,12 +111,6 @@ Scenarios.Type_Scalar_DecimalType_requestBody = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(0.33333); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Type_requestBody = passOnSuccess({ @@ -164,46 +125,28 @@ Scenarios.Type_Scalar_Decimal128Type_requestBody = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(0.33333); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_DecimalType_requestParameter = passOnSuccess({ uri: "/type/scalar/decimal/request_parameter", method: `get`, request: { - params: { value: 0.33333 }, + params: { value: "0.33333" }, }, response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("value", "0.33333"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Type_requestParameter = passOnSuccess({ uri: "/type/scalar/decimal128/request_parameter", method: `get`, request: { - params: { value: 0.33333 }, + params: { value: "0.33333" }, }, response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.containsQueryParam("value", "0.33333"); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_DecimalVerify_prepareVerify = passOnSuccess({ @@ -214,12 +157,6 @@ Scenarios.Type_Scalar_DecimalVerify_prepareVerify = passOnSuccess({ status: 200, body: json([0.1, 0.1, 0.1]), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json([0.1, 0.1, 0.1]), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Verify_prepareVerify = passOnSuccess({ @@ -230,12 +167,6 @@ Scenarios.Type_Scalar_Decimal128Verify_prepareVerify = passOnSuccess({ status: 200, body: json([0.1, 0.1, 0.1]), }, - handler: (req: MockRequest) => { - return { - status: 200, - body: json([0.1, 0.1, 0.1]), - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_DecimalVerify_verify = passOnSuccess({ @@ -250,12 +181,6 @@ Scenarios.Type_Scalar_DecimalVerify_verify = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(0.3); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); Scenarios.Type_Scalar_Decimal128Verify_verify = passOnSuccess({ @@ -270,11 +195,5 @@ Scenarios.Type_Scalar_Decimal128Verify_verify = passOnSuccess({ response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals(0.3); - return { - status: 204, - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/type/union/mockapi.ts b/packages/http-specs/specs/type/union/mockapi.ts index da973f602e..16f5db19b1 100644 --- a/packages/http-specs/specs/type/union/mockapi.ts +++ b/packages/http-specs/specs/type/union/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -11,9 +11,6 @@ function createGetServerTests(url: string, value: unknown) { status: 200, body: json({ prop: value }), }, - handler: (req: MockRequest) => { - return { status: 200, body: json({ prop: value }) }; - }, kind: "MockApiDefinition", }); } @@ -30,10 +27,6 @@ function createPostServerTests(url: string, value: unknown) { response: { status: 204, }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: value }); - return { status: 204 }; - }, kind: "MockApiDefinition", }); } diff --git a/packages/http-specs/specs/versioning/added/mockapi.ts b/packages/http-specs/specs/versioning/added/mockapi.ts index 04bbf8aa67..b8cfec5253 100644 --- a/packages/http-specs/specs/versioning/added/mockapi.ts +++ b/packages/http-specs/specs/versioning/added/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -19,14 +19,6 @@ Scenarios.Versioning_Added_v1 = passOnSuccess({ status: 200, body: json({ prop: "foo", enumProp: "enumMemberV2", unionProp: 10 }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", enumProp: "enumMemberV2", unionProp: 10 }); - req.expect.containsHeader("header-v2", "bar"); - return { - status: 200, - body: json({ prop: "foo", enumProp: "enumMemberV2", unionProp: 10 }), - }; - }, kind: "MockApiDefinition", }); @@ -44,13 +36,6 @@ Scenarios.Versioning_Added_v2 = passOnSuccess({ status: 200, body: json({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }); - return { - status: 200, - body: json({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }), - }; - }, kind: "MockApiDefinition", }); @@ -68,12 +53,5 @@ Scenarios.Versioning_Added_InterfaceV2 = passOnSuccess({ status: 200, body: json({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }); - return { - status: 200, - body: json({ prop: "foo", enumProp: "enumMember", unionProp: "bar" }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/madeOptional/mockapi.ts b/packages/http-specs/specs/versioning/madeOptional/mockapi.ts index 145931af66..c13da829fa 100644 --- a/packages/http-specs/specs/versioning/madeOptional/mockapi.ts +++ b/packages/http-specs/specs/versioning/madeOptional/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -14,12 +14,5 @@ Scenarios.Versioning_MadeOptional_test = passOnSuccess({ status: 200, body: json({ prop: "foo" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo" }); - return { - status: 200, - body: json({ prop: "foo" }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/removed/mockapi.ts b/packages/http-specs/specs/versioning/removed/mockapi.ts index 284abb8be3..7f4e2534cf 100644 --- a/packages/http-specs/specs/versioning/removed/mockapi.ts +++ b/packages/http-specs/specs/versioning/removed/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -16,12 +16,5 @@ Scenarios.Versioning_Removed_v2 = passOnSuccess({ status: 200, body: json({ prop: "foo", enumProp: "enumMemberV2", unionProp: "bar" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", enumProp: "enumMemberV2", unionProp: "bar" }); - return { - status: 200, - body: json({ prop: "foo", enumProp: "enumMemberV2", unionProp: "bar" }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/renamedFrom/mockapi.ts b/packages/http-specs/specs/versioning/renamedFrom/mockapi.ts index aa938dc57f..5069a74a5a 100644 --- a/packages/http-specs/specs/versioning/renamedFrom/mockapi.ts +++ b/packages/http-specs/specs/versioning/renamedFrom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -19,14 +19,6 @@ Scenarios.Versioning_RenamedFrom_newOp = passOnSuccess({ status: 200, body: json({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }); - req.expect.containsQueryParam("newQuery", "bar"); - return { - status: 200, - body: json({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }), - }; - }, kind: "MockApiDefinition", }); @@ -44,12 +36,5 @@ Scenarios.Versioning_RenamedFrom_NewInterface = passOnSuccess({ status: 200, body: json({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }); - return { - status: 200, - body: json({ newProp: "foo", enumProp: "newEnumMember", unionProp: 10 }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/returnTypeChangedFrom/mockapi.ts b/packages/http-specs/specs/versioning/returnTypeChangedFrom/mockapi.ts index 2fff5bcec0..a34afd223b 100644 --- a/packages/http-specs/specs/versioning/returnTypeChangedFrom/mockapi.ts +++ b/packages/http-specs/specs/versioning/returnTypeChangedFrom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -15,12 +15,5 @@ Scenarios.Versioning_ReturnTypeChangedFrom_test = passOnSuccess({ status: 200, body: json("test"), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals("test"); - return { - status: 200, - body: json("test"), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/http-specs/specs/versioning/typeChangedFrom/mockapi.ts b/packages/http-specs/specs/versioning/typeChangedFrom/mockapi.ts index 35425324f5..9e3405f2cb 100644 --- a/packages/http-specs/specs/versioning/typeChangedFrom/mockapi.ts +++ b/packages/http-specs/specs/versioning/typeChangedFrom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, MockRequest, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -18,13 +18,5 @@ Scenarios.Versioning_TypeChangedFrom_test = passOnSuccess({ status: 200, body: json({ prop: "foo", changedProp: "bar" }), }, - handler: (req: MockRequest) => { - req.expect.bodyEquals({ prop: "foo", changedProp: "bar" }); - req.expect.containsQueryParam("param", "baz"); - return { - status: 200, - body: json({ prop: "foo", changedProp: "bar" }), - }; - }, kind: "MockApiDefinition", }); diff --git a/packages/spector/src/app/app.ts b/packages/spector/src/app/app.ts index 49b9f03ec7..1ae839d6ae 100644 --- a/packages/spector/src/app/app.ts +++ b/packages/spector/src/app/app.ts @@ -1,4 +1,4 @@ -import { RequestExt, ScenarioMockApi } from "@typespec/spec-api"; +import { MockApiDefinition, MockRequest, RequestExt, ScenarioMockApi } from "@typespec/spec-api"; import { Response, Router } from "express"; import { getScenarioMetadata } from "../coverage/common.js"; import { CoverageTracker } from "../coverage/coverage-tracker.js"; @@ -56,7 +56,7 @@ export class MockApiApp { }); } else { if (!endpoint.handler) { - continue; + endpoint.handler = createHandler(endpoint); } this.router.route(endpoint.uri)[endpoint.method]((req: RequestExt, res: Response) => { processRequest( @@ -75,3 +75,42 @@ export class MockApiApp { } } } + +function createHandler(apiDefinition: MockApiDefinition) { + return (req: MockRequest) => { + // Validate body if present in the request + if (apiDefinition.request.body) { + if ( + apiDefinition.request.headers && + apiDefinition.request.headers["Content-Type"] === "application/xml" + ) { + req.expect.xmlBodyEquals(apiDefinition.request.body); + } else { + req.expect.coercedBodyEquals(apiDefinition.request.body); + } + } + + // Validate headers if present in the request + if (apiDefinition.request.headers) { + Object.entries(apiDefinition.request.headers).forEach(([key, value]) => { + if (key !== "Content-Type") { + req.expect.containsHeader(key, value as string); + } + }); + } + + // Validate query params if present in the request + if (apiDefinition.request.params) { + Object.entries(apiDefinition.request.params).forEach(([key, value]) => { + req.expect.containsQueryParam(key, value as string); + }); + } + + // Validations are done. Now return the response + return { + status: apiDefinition.response.status, + body: apiDefinition.response.body, + headers: apiDefinition.response.headers, + }; + }; +} From de763ceb0f69c994006fef74273b1bdea55e05f9 Mon Sep 17 00:00:00 2001 From: Alitzel Mendez <6895254+AlitzelMendez@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:12:29 -0700 Subject: [PATCH 05/14] Mark discriminator properties consistently as requiered (#4663) Co-authored-by: Timothee Guerin --- ...r-should-be-requiered-2024-9-9-17-24-19.md | 7 ++++++ packages/openapi3/src/schema-emitter.ts | 7 ++++++ packages/openapi3/test/discriminator.test.ts | 22 +++++++++++++++---- .../@typespec/openapi3/openapi.yaml | 1 + .../@typespec/openapi3/openapi.v1.yaml | 1 + .../@typespec/openapi3/openapi.v2.yaml | 1 + 6 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .chronus/changes/fix-discriminator-should-be-requiered-2024-9-9-17-24-19.md diff --git a/.chronus/changes/fix-discriminator-should-be-requiered-2024-9-9-17-24-19.md b/.chronus/changes/fix-discriminator-should-be-requiered-2024-9-9-17-24-19.md new file mode 100644 index 0000000000..d854fd90d3 --- /dev/null +++ b/.chronus/changes/fix-discriminator-should-be-requiered-2024-9-9-17-24-19.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/openapi3" +--- + +Discriminator properties are marked as required regardless if they are in TypeSpec to match OpenAPI3 spec. diff --git a/packages/openapi3/src/schema-emitter.ts b/packages/openapi3/src/schema-emitter.ts index 71c34c3fe9..90446bf0df 100644 --- a/packages/openapi3/src/schema-emitter.ts +++ b/packages/openapi3/src/schema-emitter.ts @@ -295,6 +295,13 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter< } } + const discriminator = getDiscriminator(this.emitter.getProgram(), model); + if (discriminator) { + if (!requiredProps.includes(discriminator.propertyName)) { + requiredProps.push(discriminator.propertyName); + } + } + return requiredProps.length > 0 ? requiredProps : undefined; } diff --git a/packages/openapi3/test/discriminator.test.ts b/packages/openapi3/test/discriminator.test.ts index 0d2bb8f4aa..88192cd26f 100644 --- a/packages/openapi3/test/discriminator.test.ts +++ b/packages/openapi3/test/discriminator.test.ts @@ -103,7 +103,7 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { name: { type: "string" }, weight: { type: "number", format: "float" }, }, - required: ["name"], + required: ["name", "kind"], discriminator: { propertyName: "kind", mapping: { @@ -154,7 +154,7 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { }, weight: { type: "number", format: "float" }, }, - required: ["name"], + required: ["name", "kind"], discriminator: { propertyName: "kind", mapping: { @@ -215,7 +215,7 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { name: { type: "string" }, weight: { type: "number", format: "float" }, }, - required: ["name"], + required: ["name", "kind"], discriminator: { propertyName: "kind", mapping: { @@ -234,7 +234,7 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { }, bark: { type: "string" }, }, - required: ["kind", "bark"], + required: ["kind", "bark", "breed"], allOf: [{ $ref: "#/components/schemas/Pet" }], discriminator: { propertyName: "breed", @@ -344,4 +344,18 @@ describe("openapi3: polymorphic model inheritance with discriminator", () => { }, ]); }); + + it("discriminator always needs to be marked as required", async () => { + const openApi = await openApiFor(` + @discriminator("kind") + model Animal { + id: string; + kind?: string; + }`); + + deepStrictEqual(openApi.components.schemas.Animal.required, ["id", "kind"]); + deepStrictEqual(openApi.components.schemas.Animal.discriminator, { + propertyName: "kind", + }); + }); }); diff --git a/packages/samples/test/output/polymorphism/@typespec/openapi3/openapi.yaml b/packages/samples/test/output/polymorphism/@typespec/openapi3/openapi.yaml index 6e9172b3a2..b8a536d4cc 100644 --- a/packages/samples/test/output/polymorphism/@typespec/openapi3/openapi.yaml +++ b/packages/samples/test/output/polymorphism/@typespec/openapi3/openapi.yaml @@ -50,6 +50,7 @@ components: type: object required: - name + - kind properties: name: type: string diff --git a/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v1.yaml b/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v1.yaml index 8befefdcfb..48b989550b 100644 --- a/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v1.yaml +++ b/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v1.yaml @@ -63,6 +63,7 @@ components: required: - name - favoriteToys + - type properties: name: type: string diff --git a/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v2.yaml b/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v2.yaml index 10b519e5f0..01e3552978 100644 --- a/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v2.yaml +++ b/packages/samples/test/output/versioning/@typespec/openapi3/openapi.v2.yaml @@ -85,6 +85,7 @@ components: required: - name - favoriteToys + - type properties: name: type: string From b002ca480b969beef9d2ffdde4ec7b10bc002e29 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 14 Oct 2024 14:26:59 -0700 Subject: [PATCH 06/14] Migrate spec lib to spector (#4686) Combine the spec-lib package into spector as this is a bit too much extra packages --- packages/http-specs/package.json | 1 - .../specs/authentication/api-key/main.tsp | 4 +- .../specs/authentication/http/custom/main.tsp | 4 +- .../specs/authentication/oauth2/main.tsp | 4 +- .../specs/authentication/union/main.tsp | 4 +- .../http-specs/specs/encode/bytes/main.tsp | 4 +- .../http-specs/specs/encode/datetime/main.tsp | 4 +- .../http-specs/specs/encode/duration/main.tsp | 4 +- .../http-specs/specs/encode/numeric/main.tsp | 4 +- .../specs/parameters/basic/main.tsp | 4 +- .../parameters/body-optionality/main.tsp | 4 +- .../parameters/collection-format/main.tsp | 4 +- .../specs/parameters/spread/main.tsp | 4 +- .../payload/content-negotiation/main.tsp | 4 +- .../specs/payload/json-merge-patch/main.tsp | 4 +- .../specs/payload/media-type/main.tsp | 4 +- .../specs/payload/multipart/main.tsp | 4 +- .../http-specs/specs/payload/xml/main.tsp | 4 +- packages/http-specs/specs/routes/main.tsp | 4 +- .../serialization/encoded-name/json/main.tsp | 4 +- .../server/endpoint/not-defined/main.tsp | 4 +- .../specs/server/path/multiple/main.tsp | 4 +- .../specs/server/path/single/main.tsp | 4 +- .../server/versions/not-versioned/main.tsp | 4 +- .../specs/server/versions/versioned/main.tsp | 4 +- .../conditional-request/main.tsp | 4 +- .../special-headers/repeatability/main.tsp | 4 +- .../http-specs/specs/special-words/dec.js | 2 +- .../http-specs/specs/special-words/main.tsp | 4 +- packages/http-specs/specs/type/array/main.tsp | 4 +- .../http-specs/specs/type/dictionary/main.tsp | 4 +- .../specs/type/enum/extensible/main.tsp | 4 +- .../http-specs/specs/type/enum/fixed/main.tsp | 4 +- .../specs/type/model/empty/main.tsp | 4 +- .../inheritance/enum-discriminator/main.tsp | 4 +- .../inheritance/nested-discriminator/main.tsp | 4 +- .../inheritance/not-discriminated/main.tsp | 4 +- .../type/model/inheritance/recursive/main.tsp | 4 +- .../inheritance/single-discriminator/main.tsp | 4 +- .../specs/type/model/templated/main.tsp | 4 +- .../specs/type/model/usage/main.tsp | 4 +- .../specs/type/model/visibility/main.tsp | 4 +- .../property/additional-properties/main.tsp | 4 +- .../specs/type/property/nullable/main.tsp | 4 +- .../specs/type/property/optionality/main.tsp | 4 +- .../specs/type/property/value-types/main.tsp | 4 +- .../http-specs/specs/type/scalar/main.tsp | 4 +- packages/http-specs/specs/type/union/main.tsp | 4 +- .../specs/versioning/added/main.tsp | 4 +- .../specs/versioning/madeOptional/main.tsp | 4 +- .../specs/versioning/removed/main.tsp | 4 +- .../specs/versioning/renamedFrom/main.tsp | 4 +- .../versioning/returnTypeChangedFrom/main.tsp | 4 +- .../specs/versioning/typeChangedFrom/main.tsp | 4 +- packages/http-specs/tsconfig.build.json | 1 - packages/spec-api/tsconfig.build.json | 2 +- .../spec-coverage-sdk/tsconfig.build.json | 2 +- packages/spec-lib/CHANGELOG.md | 1 - packages/spec-lib/package.json | 47 ----- packages/spec-lib/tsconfig.build.json | 4 - packages/spec-lib/tsconfig.config.json | 4 - packages/spec-lib/tsconfig.json | 10 -- packages/spector/cmd/cli.mjs | 2 +- .../generated-defs/TypeSpec.Spector.ts} | 2 +- .../TypeSpec.Spector.ts-test.ts} | 6 +- .../lib/lib.tsp => spector/lib/main.tsp} | 4 +- packages/spector/package.json | 13 +- .../src/actions/generate-scenario-summary.ts | 2 +- .../spector/src/coverage/scenario-manifest.ts | 2 +- packages/spector/src/index.ts | 1 + .../src => spector/src/lib}/decorators.ts | 16 +- .../src => spector/src/lib}/index.ts | 0 .../{spec-lib/src => spector/src/lib}/lib.ts | 4 +- .../src => spector/src/lib}/tsp-index.ts | 6 +- .../src => spector/src/lib}/validate.ts | 0 packages/spector/src/scenarios-resolver.ts | 4 +- .../spector/src/spec-utils/import-spec.ts | 11 +- .../spector/src/spec-utils/module-resolver.ts | 165 ------------------ packages/spector/tsconfig.build.json | 7 +- packages/spector/tsconfig.json | 6 +- pnpm-lock.yaml | 38 +--- tsconfig.ws.json | 1 - 82 files changed, 159 insertions(+), 413 deletions(-) delete mode 100644 packages/spec-lib/CHANGELOG.md delete mode 100644 packages/spec-lib/package.json delete mode 100644 packages/spec-lib/tsconfig.build.json delete mode 100644 packages/spec-lib/tsconfig.config.json delete mode 100644 packages/spec-lib/tsconfig.json rename packages/{spec-lib/generated-defs/TypeSpec.SpecLib.ts => spector/generated-defs/TypeSpec.Spector.ts} (96%) rename packages/{spec-lib/generated-defs/TypeSpec.SpecLib.ts-test.ts => spector/generated-defs/TypeSpec.Spector.ts-test.ts} (57%) rename packages/{spec-lib/lib/lib.tsp => spector/lib/main.tsp} (92%) create mode 100644 packages/spector/src/index.ts rename packages/{spec-lib/src => spector/src/lib}/decorators.ts (93%) rename packages/{spec-lib/src => spector/src/lib}/index.ts (100%) rename packages/{spec-lib/src => spector/src/lib}/lib.ts (95%) rename packages/{spec-lib/src => spector/src/lib}/tsp-index.ts (62%) rename packages/{spec-lib/src => spector/src/lib}/validate.ts (100%) delete mode 100644 packages/spector/src/spec-utils/module-resolver.ts diff --git a/packages/http-specs/package.json b/packages/http-specs/package.json index 37e847caa0..d41d60a276 100644 --- a/packages/http-specs/package.json +++ b/packages/http-specs/package.json @@ -49,7 +49,6 @@ "@typespec/compiler": "workspace:~", "@typespec/http": "workspace:~", "@typespec/rest": "workspace:~", - "@typespec/spec-lib": "workspace:~", "@typespec/versioning": "workspace:~", "@typespec/xml": "workspace:~" } diff --git a/packages/http-specs/specs/authentication/api-key/main.tsp b/packages/http-specs/specs/authentication/api-key/main.tsp index 8cade48fd3..e7ee63add6 100644 --- a/packages/http-specs/specs/authentication/api-key/main.tsp +++ b/packages/http-specs/specs/authentication/api-key/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/authentication/api-key") @doc("Illustrates clients generated with ApiKey authentication.") diff --git a/packages/http-specs/specs/authentication/http/custom/main.tsp b/packages/http-specs/specs/authentication/http/custom/main.tsp index 5d666cecba..6165727803 100644 --- a/packages/http-specs/specs/authentication/http/custom/main.tsp +++ b/packages/http-specs/specs/authentication/http/custom/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using TypeSpec.Http; -using SpecLib; +using Spector; @scenarioService("/authentication/http/custom") @doc("Illustrates clients generated with generic HTTP auth.") diff --git a/packages/http-specs/specs/authentication/oauth2/main.tsp b/packages/http-specs/specs/authentication/oauth2/main.tsp index ef6fa1ec65..ced1738a65 100644 --- a/packages/http-specs/specs/authentication/oauth2/main.tsp +++ b/packages/http-specs/specs/authentication/oauth2/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/authentication/oauth2") @doc("Illustrates clients generated with OAuth2 authentication.") diff --git a/packages/http-specs/specs/authentication/union/main.tsp b/packages/http-specs/specs/authentication/union/main.tsp index 17873d352b..9d6afef5c8 100644 --- a/packages/http-specs/specs/authentication/union/main.tsp +++ b/packages/http-specs/specs/authentication/union/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/authentication/union") @doc("Illustrates clients generated with ApiKey and OAuth2 authentication.") diff --git a/packages/http-specs/specs/encode/bytes/main.tsp b/packages/http-specs/specs/encode/bytes/main.tsp index 69b6cc1fd5..e69ed6e194 100644 --- a/packages/http-specs/specs/encode/bytes/main.tsp +++ b/packages/http-specs/specs/encode/bytes/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for encode decorator on bytes.") @scenarioService("/encode/bytes") diff --git a/packages/http-specs/specs/encode/datetime/main.tsp b/packages/http-specs/specs/encode/datetime/main.tsp index 2a4abf5433..52ba484f23 100644 --- a/packages/http-specs/specs/encode/datetime/main.tsp +++ b/packages/http-specs/specs/encode/datetime/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for encode decorator on datetime.") @scenarioService("/encode/datetime") diff --git a/packages/http-specs/specs/encode/duration/main.tsp b/packages/http-specs/specs/encode/duration/main.tsp index 3f06ad10b5..9509745119 100644 --- a/packages/http-specs/specs/encode/duration/main.tsp +++ b/packages/http-specs/specs/encode/duration/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for encode decorator on duration.") @scenarioService("/encode/duration") diff --git a/packages/http-specs/specs/encode/numeric/main.tsp b/packages/http-specs/specs/encode/numeric/main.tsp index ee2099e572..5677d2c673 100644 --- a/packages/http-specs/specs/encode/numeric/main.tsp +++ b/packages/http-specs/specs/encode/numeric/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for encode decorator on integer.") @scenarioService("/encode/numeric") diff --git a/packages/http-specs/specs/parameters/basic/main.tsp b/packages/http-specs/specs/parameters/basic/main.tsp index 65f766f1a9..d797a05cec 100644 --- a/packages/http-specs/specs/parameters/basic/main.tsp +++ b/packages/http-specs/specs/parameters/basic/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for basic parameters cases.") @scenarioService("/parameters/basic") diff --git a/packages/http-specs/specs/parameters/body-optionality/main.tsp b/packages/http-specs/specs/parameters/body-optionality/main.tsp index 32d9acf9d1..1019638fc7 100644 --- a/packages/http-specs/specs/parameters/body-optionality/main.tsp +++ b/packages/http-specs/specs/parameters/body-optionality/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test describing optionality of the request body.") @scenarioService("/parameters/body-optionality") diff --git a/packages/http-specs/specs/parameters/collection-format/main.tsp b/packages/http-specs/specs/parameters/collection-format/main.tsp index 355b295901..dc420a49f9 100644 --- a/packages/http-specs/specs/parameters/collection-format/main.tsp +++ b/packages/http-specs/specs/parameters/collection-format/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for collectionFormat.") @scenarioService("/parameters/collection-format") diff --git a/packages/http-specs/specs/parameters/spread/main.tsp b/packages/http-specs/specs/parameters/spread/main.tsp index 5c2f6ff379..5d8d348844 100644 --- a/packages/http-specs/specs/parameters/spread/main.tsp +++ b/packages/http-specs/specs/parameters/spread/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for the spread operator.") @scenarioService("/parameters/spread") diff --git a/packages/http-specs/specs/payload/content-negotiation/main.tsp b/packages/http-specs/specs/payload/content-negotiation/main.tsp index 3a1055af95..7dc2ad3c3c 100644 --- a/packages/http-specs/specs/payload/content-negotiation/main.tsp +++ b/packages/http-specs/specs/payload/content-negotiation/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test describing optionality of the request body.") @scenarioService("/content-negotiation") diff --git a/packages/http-specs/specs/payload/json-merge-patch/main.tsp b/packages/http-specs/specs/payload/json-merge-patch/main.tsp index a4f502e387..00686cd844 100644 --- a/packages/http-specs/specs/payload/json-merge-patch/main.tsp +++ b/packages/http-specs/specs/payload/json-merge-patch/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for merge-patch+json content-type") @scenarioService("/json-merge-patch") diff --git a/packages/http-specs/specs/payload/media-type/main.tsp b/packages/http-specs/specs/payload/media-type/main.tsp index 6567355f6e..3ced443cbb 100644 --- a/packages/http-specs/specs/payload/media-type/main.tsp +++ b/packages/http-specs/specs/payload/media-type/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Test the payload with different media types and different types of the payload itself. diff --git a/packages/http-specs/specs/payload/multipart/main.tsp b/packages/http-specs/specs/payload/multipart/main.tsp index aff1c2f860..f94de86ae9 100644 --- a/packages/http-specs/specs/payload/multipart/main.tsp +++ b/packages/http-specs/specs/payload/multipart/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Test for multipart") @scenarioService("/multipart") diff --git a/packages/http-specs/specs/payload/xml/main.tsp b/packages/http-specs/specs/payload/xml/main.tsp index b445b29676..68bf621eb8 100644 --- a/packages/http-specs/specs/payload/xml/main.tsp +++ b/packages/http-specs/specs/payload/xml/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; import "@typespec/xml"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; using TypeSpec.Xml; @doc("Sends and receives bodies in XML format.") diff --git a/packages/http-specs/specs/routes/main.tsp b/packages/http-specs/specs/routes/main.tsp index 679ca79b03..9364b1aa81 100644 --- a/packages/http-specs/specs/routes/main.tsp +++ b/packages/http-specs/specs/routes/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Define scenario in building the http route/uri diff --git a/packages/http-specs/specs/serialization/encoded-name/json/main.tsp b/packages/http-specs/specs/serialization/encoded-name/json/main.tsp index 8082612cd3..de32ec6a02 100644 --- a/packages/http-specs/specs/serialization/encoded-name/json/main.tsp +++ b/packages/http-specs/specs/serialization/encoded-name/json/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Projection") @scenarioService("/serialization/encoded-name/json") diff --git a/packages/http-specs/specs/server/endpoint/not-defined/main.tsp b/packages/http-specs/specs/server/endpoint/not-defined/main.tsp index eb53790012..04962911dc 100644 --- a/packages/http-specs/specs/server/endpoint/not-defined/main.tsp +++ b/packages/http-specs/specs/server/endpoint/not-defined/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Illustrates server doesn't define endpoint. Client should automatically add an endpoint to let user pass in. diff --git a/packages/http-specs/specs/server/path/multiple/main.tsp b/packages/http-specs/specs/server/path/multiple/main.tsp index d785fd5563..868684aeb3 100644 --- a/packages/http-specs/specs/server/path/multiple/main.tsp +++ b/packages/http-specs/specs/server/path/multiple/main.tsp @@ -1,9 +1,9 @@ import "@typespec/rest"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; using TypeSpec.Rest; diff --git a/packages/http-specs/specs/server/path/single/main.tsp b/packages/http-specs/specs/server/path/single/main.tsp index 381b58d86a..fd8713c47a 100644 --- a/packages/http-specs/specs/server/path/single/main.tsp +++ b/packages/http-specs/specs/server/path/single/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates server with a single path parameter @server") @service diff --git a/packages/http-specs/specs/server/versions/not-versioned/main.tsp b/packages/http-specs/specs/server/versions/not-versioned/main.tsp index 749ee8934a..0f8bafb36c 100644 --- a/packages/http-specs/specs/server/versions/not-versioned/main.tsp +++ b/packages/http-specs/specs/server/versions/not-versioned/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Illustrates not-versioned server. diff --git a/packages/http-specs/specs/server/versions/versioned/main.tsp b/packages/http-specs/specs/server/versions/versioned/main.tsp index 9c3744556f..4a98569d15 100644 --- a/packages/http-specs/specs/server/versions/versioned/main.tsp +++ b/packages/http-specs/specs/server/versions/versioned/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/special-headers/conditional-request/main.tsp b/packages/http-specs/specs/special-headers/conditional-request/main.tsp index 2dea074f85..3f6b6d8979 100644 --- a/packages/http-specs/specs/special-headers/conditional-request/main.tsp +++ b/packages/http-specs/specs/special-headers/conditional-request/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; import "@typespec/versioning"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; @doc("Illustrates conditional request headers") diff --git a/packages/http-specs/specs/special-headers/repeatability/main.tsp b/packages/http-specs/specs/special-headers/repeatability/main.tsp index af5ed18094..e9bb0067ef 100644 --- a/packages/http-specs/specs/special-headers/repeatability/main.tsp +++ b/packages/http-specs/specs/special-headers/repeatability/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; import "@typespec/versioning"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; @doc("Illustrates OASIS repeatability headers") diff --git a/packages/http-specs/specs/special-words/dec.js b/packages/http-specs/specs/special-words/dec.js index 5486579b4e..b3188c2b22 100644 --- a/packages/http-specs/specs/special-words/dec.js +++ b/packages/http-specs/specs/special-words/dec.js @@ -1,7 +1,7 @@ // @ts-check import { $route } from "@typespec/http"; -import { $scenario, $scenarioDoc } from "@typespec/spec-lib"; +import { $scenario, $scenarioDoc } from "@typespec/spector"; /** * diff --git a/packages/http-specs/specs/special-words/main.tsp b/packages/http-specs/specs/special-words/main.tsp index 244deaebe8..d802c373fd 100644 --- a/packages/http-specs/specs/special-words/main.tsp +++ b/packages/http-specs/specs/special-words/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "./dec.js"; using Http; -using SpecLib; +using Spector; /** * Scenarios to verify that reserved words can be used in service and generators will handle it appropriately. diff --git a/packages/http-specs/specs/type/array/main.tsp b/packages/http-specs/specs/type/array/main.tsp index 4276140fcf..b91b2fe684 100644 --- a/packages/http-specs/specs/type/array/main.tsp +++ b/packages/http-specs/specs/type/array/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates various types of arrays.") @scenarioService("/type/array") diff --git a/packages/http-specs/specs/type/dictionary/main.tsp b/packages/http-specs/specs/type/dictionary/main.tsp index ff97282ea0..bf8f558652 100644 --- a/packages/http-specs/specs/type/dictionary/main.tsp +++ b/packages/http-specs/specs/type/dictionary/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates various of dictionaries.") @scenarioService("/type/dictionary") diff --git a/packages/http-specs/specs/type/enum/extensible/main.tsp b/packages/http-specs/specs/type/enum/extensible/main.tsp index ba7899108b..405dd0c9be 100644 --- a/packages/http-specs/specs/type/enum/extensible/main.tsp +++ b/packages/http-specs/specs/type/enum/extensible/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/type/enum/extensible") namespace Type.Enum.Extensible; diff --git a/packages/http-specs/specs/type/enum/fixed/main.tsp b/packages/http-specs/specs/type/enum/fixed/main.tsp index 846f0c3498..327a5afcd1 100644 --- a/packages/http-specs/specs/type/enum/fixed/main.tsp +++ b/packages/http-specs/specs/type/enum/fixed/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/type/enum/fixed") namespace Type.Enum.Fixed; diff --git a/packages/http-specs/specs/type/model/empty/main.tsp b/packages/http-specs/specs/type/model/empty/main.tsp index b8f3b917bd..bbfb6194c6 100644 --- a/packages/http-specs/specs/type/model/empty/main.tsp +++ b/packages/http-specs/specs/type/model/empty/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates usage of empty model used in operation's parameters and responses.") @scenarioService("/type/model/empty") diff --git a/packages/http-specs/specs/type/model/inheritance/enum-discriminator/main.tsp b/packages/http-specs/specs/type/model/inheritance/enum-discriminator/main.tsp index 870699717f..e7893d7310 100644 --- a/packages/http-specs/specs/type/model/inheritance/enum-discriminator/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/enum-discriminator/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates inheritance with enum discriminator.") @scenarioService("/type/model/inheritance/enum-discriminator") diff --git a/packages/http-specs/specs/type/model/inheritance/nested-discriminator/main.tsp b/packages/http-specs/specs/type/model/inheritance/nested-discriminator/main.tsp index 3ef6b307ec..fdf750bf53 100644 --- a/packages/http-specs/specs/type/model/inheritance/nested-discriminator/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/nested-discriminator/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates multiple level inheritance with multiple discriminators.") @scenarioService("/type/model/inheritance/nested-discriminator") diff --git a/packages/http-specs/specs/type/model/inheritance/not-discriminated/main.tsp b/packages/http-specs/specs/type/model/inheritance/not-discriminated/main.tsp index e8e893703d..69c186445b 100644 --- a/packages/http-specs/specs/type/model/inheritance/not-discriminated/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/not-discriminated/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates not-discriminated inheritance model.") @scenarioService("/type/model/inheritance/not-discriminated") diff --git a/packages/http-specs/specs/type/model/inheritance/recursive/main.tsp b/packages/http-specs/specs/type/model/inheritance/recursive/main.tsp index 0e813d9919..a5656b9491 100644 --- a/packages/http-specs/specs/type/model/inheritance/recursive/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/recursive/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates inheritance recursion") @scenarioService("/type/model/inheritance/recursive") diff --git a/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp b/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp index 189c7d21da..0a396d731a 100644 --- a/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates inheritance with single discriminator.") @scenarioService("/type/model/inheritance/single-discriminator") diff --git a/packages/http-specs/specs/type/model/templated/main.tsp b/packages/http-specs/specs/type/model/templated/main.tsp index 3905fa4dd2..3d9bc1d92e 100644 --- a/packages/http-specs/specs/type/model/templated/main.tsp +++ b/packages/http-specs/specs/type/model/templated/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Illustrates the model templated cases. There is a base templated type and an instantiated type extending from it. diff --git a/packages/http-specs/specs/type/model/usage/main.tsp b/packages/http-specs/specs/type/model/usage/main.tsp index 60fe11f9d3..5197007a0f 100644 --- a/packages/http-specs/specs/type/model/usage/main.tsp +++ b/packages/http-specs/specs/type/model/usage/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Those tests are meant to test different behavior of records if they are used as input, output or both. diff --git a/packages/http-specs/specs/type/model/visibility/main.tsp b/packages/http-specs/specs/type/model/visibility/main.tsp index 14098f633a..37bec3992e 100644 --- a/packages/http-specs/specs/type/model/visibility/main.tsp +++ b/packages/http-specs/specs/type/model/visibility/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates models with visibility properties.") @scenarioService("/type/model/visibility") diff --git a/packages/http-specs/specs/type/property/additional-properties/main.tsp b/packages/http-specs/specs/type/property/additional-properties/main.tsp index 3a4d59432c..10bc60616a 100644 --- a/packages/http-specs/specs/type/property/additional-properties/main.tsp +++ b/packages/http-specs/specs/type/property/additional-properties/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Tests for additional properties of models") @scenarioService("/type/property/additionalProperties") diff --git a/packages/http-specs/specs/type/property/nullable/main.tsp b/packages/http-specs/specs/type/property/nullable/main.tsp index 78ae48b8b3..aa9c5b0f29 100644 --- a/packages/http-specs/specs/type/property/nullable/main.tsp +++ b/packages/http-specs/specs/type/property/nullable/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates models with nullable properties.") @scenarioService("/type/property/nullable") diff --git a/packages/http-specs/specs/type/property/optionality/main.tsp b/packages/http-specs/specs/type/property/optionality/main.tsp index 83f884934b..7c7cd06000 100644 --- a/packages/http-specs/specs/type/property/optionality/main.tsp +++ b/packages/http-specs/specs/type/property/optionality/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates models with optional properties.") @scenarioService("/type/property/optional") diff --git a/packages/http-specs/specs/type/property/value-types/main.tsp b/packages/http-specs/specs/type/property/value-types/main.tsp index a77373f482..3f661a4baa 100644 --- a/packages/http-specs/specs/type/property/value-types/main.tsp +++ b/packages/http-specs/specs/type/property/value-types/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @doc("Illustrates various property types for models") @scenarioService("/type/property/value-types") diff --git a/packages/http-specs/specs/type/scalar/main.tsp b/packages/http-specs/specs/type/scalar/main.tsp index 47cce3f1f6..b083d5c7b0 100644 --- a/packages/http-specs/specs/type/scalar/main.tsp +++ b/packages/http-specs/specs/type/scalar/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; @scenarioService("/type/scalar") namespace Type.Scalar; diff --git a/packages/http-specs/specs/type/union/main.tsp b/packages/http-specs/specs/type/union/main.tsp index c31191d616..7df2d3387a 100644 --- a/packages/http-specs/specs/type/union/main.tsp +++ b/packages/http-specs/specs/type/union/main.tsp @@ -1,8 +1,8 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; using Http; -using SpecLib; +using Spector; /** * Describe scenarios for various combinations of unions. diff --git a/packages/http-specs/specs/versioning/added/main.tsp b/packages/http-specs/specs/versioning/added/main.tsp index 3bc5f7085b..a81594a47a 100644 --- a/packages/http-specs/specs/versioning/added/main.tsp +++ b/packages/http-specs/specs/versioning/added/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/madeOptional/main.tsp b/packages/http-specs/specs/versioning/madeOptional/main.tsp index 98f052c66f..df1d25c018 100644 --- a/packages/http-specs/specs/versioning/madeOptional/main.tsp +++ b/packages/http-specs/specs/versioning/madeOptional/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/removed/main.tsp b/packages/http-specs/specs/versioning/removed/main.tsp index 2ef7641aac..609df7c6d3 100644 --- a/packages/http-specs/specs/versioning/removed/main.tsp +++ b/packages/http-specs/specs/versioning/removed/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/renamedFrom/main.tsp b/packages/http-specs/specs/versioning/renamedFrom/main.tsp index 9252a83173..c6e6e81fe2 100644 --- a/packages/http-specs/specs/versioning/renamedFrom/main.tsp +++ b/packages/http-specs/specs/versioning/renamedFrom/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/returnTypeChangedFrom/main.tsp b/packages/http-specs/specs/versioning/returnTypeChangedFrom/main.tsp index c809b2ebf9..64944a191f 100644 --- a/packages/http-specs/specs/versioning/returnTypeChangedFrom/main.tsp +++ b/packages/http-specs/specs/versioning/returnTypeChangedFrom/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/specs/versioning/typeChangedFrom/main.tsp b/packages/http-specs/specs/versioning/typeChangedFrom/main.tsp index f62b45d413..acc8bd99e5 100644 --- a/packages/http-specs/specs/versioning/typeChangedFrom/main.tsp +++ b/packages/http-specs/specs/versioning/typeChangedFrom/main.tsp @@ -1,9 +1,9 @@ import "@typespec/http"; -import "@typespec/spec-lib"; +import "@typespec/spector"; import "@typespec/versioning"; using Http; -using SpecLib; +using Spector; using TypeSpec.Versioning; /** diff --git a/packages/http-specs/tsconfig.build.json b/packages/http-specs/tsconfig.build.json index 1b4589b0cc..39fcac3378 100644 --- a/packages/http-specs/tsconfig.build.json +++ b/packages/http-specs/tsconfig.build.json @@ -2,7 +2,6 @@ "extends": "./tsconfig.json", "references": [ { "path": "../spec-api/tsconfig.build.json" }, - { "path": "../spec-lib/tsconfig.build.json" }, { "path": "../spector/tsconfig.build.json" } ], "exclude": ["**/*.test.*", "test/**/*"] diff --git a/packages/spec-api/tsconfig.build.json b/packages/spec-api/tsconfig.build.json index 3498722d1d..4877190ccc 100644 --- a/packages/spec-api/tsconfig.build.json +++ b/packages/spec-api/tsconfig.build.json @@ -3,6 +3,6 @@ "compilerOptions": { "rootDir": "./src" }, - "references": [{ "path": "../spec-lib/tsconfig.build.json" }], + "references": [], "exclude": ["**/*.test.*", "test/**/*"] } diff --git a/packages/spec-coverage-sdk/tsconfig.build.json b/packages/spec-coverage-sdk/tsconfig.build.json index 3498722d1d..4877190ccc 100644 --- a/packages/spec-coverage-sdk/tsconfig.build.json +++ b/packages/spec-coverage-sdk/tsconfig.build.json @@ -3,6 +3,6 @@ "compilerOptions": { "rootDir": "./src" }, - "references": [{ "path": "../spec-lib/tsconfig.build.json" }], + "references": [], "exclude": ["**/*.test.*", "test/**/*"] } diff --git a/packages/spec-lib/CHANGELOG.md b/packages/spec-lib/CHANGELOG.md deleted file mode 100644 index 8754b76b9a..0000000000 --- a/packages/spec-lib/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# @typespec/spec-lib diff --git a/packages/spec-lib/package.json b/packages/spec-lib/package.json deleted file mode 100644 index c0da8a477a..0000000000 --- a/packages/spec-lib/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@typespec/spec-lib", - "version": "0.1.0", - "author": "Microsoft", - "description": "Spec Library providing decorator and validation for specs", - "homepage": "https://github.com/microsoft/typespec#readme", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/microsoft/typespec.git" - }, - "bugs": { - "url": "https://github.com/microsoft/typespec/issues" - }, - "type": "module", - "private": true, - "main": "dist/index.js", - "tspMain": "lib/lib.tsp", - "exports": { - ".": { - "typespec": "./lib/lib.tsp", - "default": "./dist/src/index.js" - } - }, - "engines": { - "node": ">=16.0.0" - }, - "scripts": { - "watch": "tsc -p . --watch", - "build": "npm run gen-extern-signature && tsc -p .", - "clean": "rimraf dist/ temp/", - "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "devDependencies": { - "@types/node": "~22.7.5", - "@typespec/tspd": "workspace:~", - "rimraf": "~6.0.1", - "typescript": "~5.6.3" - }, - "peerDependencies": { - "@typespec/compiler": "workspace:~", - "@typespec/http": "workspace:~", - "@typespec/rest": "workspace:~", - "@typespec/versioning": "workspace:~" - } -} diff --git a/packages/spec-lib/tsconfig.build.json b/packages/spec-lib/tsconfig.build.json deleted file mode 100644 index b4079ce75d..0000000000 --- a/packages/spec-lib/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["**/*.test.*", "test/**/*"] -} diff --git a/packages/spec-lib/tsconfig.config.json b/packages/spec-lib/tsconfig.config.json deleted file mode 100644 index 79fb341f39..0000000000 --- a/packages/spec-lib/tsconfig.config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": {} -} diff --git a/packages/spec-lib/tsconfig.json b/packages/spec-lib/tsconfig.json deleted file mode 100644 index 5745bb2c1c..0000000000 --- a/packages/spec-lib/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "references": [{ "path": "../compiler/tsconfig.json" }], - "compilerOptions": { - "outDir": "dist", - "rootDir": ".", - "tsBuildInfoFile": "temp/.tsbuildinfo" - }, - "include": ["src/**/*.ts", "generated-defs/**/*.ts", "test/**/*.ts"] -} diff --git a/packages/spector/cmd/cli.mjs b/packages/spector/cmd/cli.mjs index e2f595f637..73f921a55f 100755 --- a/packages/spector/cmd/cli.mjs +++ b/packages/spector/cmd/cli.mjs @@ -1,3 +1,3 @@ #!/usr/bin/env node -import "../dist/cli/cli.js"; +import "../dist/src/cli/cli.js"; diff --git a/packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts b/packages/spector/generated-defs/TypeSpec.Spector.ts similarity index 96% rename from packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts rename to packages/spector/generated-defs/TypeSpec.Spector.ts index 97dc5a2009..6003f5b786 100644 --- a/packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts +++ b/packages/spector/generated-defs/TypeSpec.Spector.ts @@ -39,7 +39,7 @@ export type ScenarioDocDecorator = ( formatArgs?: Model, ) => void; -export type TypeSpecSpecLibDecorators = { +export type TypeSpecSpectorDecorators = { scenarioService: ScenarioServiceDecorator; scenario: ScenarioDecorator; scenarioDoc: ScenarioDocDecorator; diff --git a/packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts-test.ts b/packages/spector/generated-defs/TypeSpec.Spector.ts-test.ts similarity index 57% rename from packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts-test.ts rename to packages/spector/generated-defs/TypeSpec.Spector.ts-test.ts index e9f03a6013..08dc02b6da 100644 --- a/packages/spec-lib/generated-defs/TypeSpec.SpecLib.ts-test.ts +++ b/packages/spector/generated-defs/TypeSpec.Spector.ts-test.ts @@ -1,5 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { $decorators } from "@typespec/spec-lib"; -import type { TypeSpecSpecLibDecorators } from "./TypeSpec.SpecLib.js"; +import { $decorators } from "@typespec/spector"; +import type { TypeSpecSpectorDecorators } from "./TypeSpec.Spector.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: TypeSpecSpecLibDecorators = $decorators["TypeSpec.SpecLib"]; +const _: TypeSpecSpectorDecorators = $decorators["TypeSpec.Spector"]; diff --git a/packages/spec-lib/lib/lib.tsp b/packages/spector/lib/main.tsp similarity index 92% rename from packages/spec-lib/lib/lib.tsp rename to packages/spector/lib/main.tsp index 7c07d9df91..8749ec88fa 100644 --- a/packages/spec-lib/lib/lib.tsp +++ b/packages/spector/lib/main.tsp @@ -1,8 +1,8 @@ -import "../dist/src/tsp-index.js"; +import "../dist/src/lib/tsp-index.js"; using TypeSpec.Reflection; -namespace TypeSpec.SpecLib; +namespace TypeSpec.Spector; alias ScenarioServiceOptions = { /** diff --git a/packages/spector/package.json b/packages/spector/package.json index ab854ffb77..a4671eb970 100644 --- a/packages/spector/package.json +++ b/packages/spector/package.json @@ -2,7 +2,12 @@ "name": "@typespec/spector", "version": "0.1.0", "description": "Typespec Core Tool to validate, run mock api, collect coverage.", - "main": "dist/index.js", + "exports": { + ".": { + "import": "./dist/src/index.js", + "typespec": "./lib/main.tsp" + } + }, "private": true, "type": "module", "bin": { @@ -10,8 +15,9 @@ }, "scripts": { "watch": "tsc -p ./tsconfig.build.json --watch", - "build": "tsc -p ./tsconfig.build.json", + "build": "npm run gen-extern-signature && tsc -p tsconfig.build.json", "clean": "rimraf dist/ temp/", + "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .", "test": "vitest run", "test:watch": "vitest -w", "test:ui": "vitest --ui" @@ -33,11 +39,11 @@ "@azure/identity": "~4.4.1", "@types/js-yaml": "^4.0.5", "@typespec/compiler": "workspace:~", + "@typespec/versioning": "workspace:~", "@typespec/http": "workspace:~", "@typespec/rest": "workspace:~", "@typespec/spec-api": "workspace:~", "@typespec/spec-coverage-sdk": "workspace:~", - "@typespec/spec-lib": "workspace:~", "ajv": "~8.17.1", "axios": "^1.7.5", "body-parser": "^1.20.3", @@ -67,6 +73,7 @@ "@types/node-fetch": "^2.6.3", "@types/xml2js": "^0.4.11", "@types/yargs": "~17.0.33", + "@typespec/tspd": "workspace:~", "rimraf": "~6.0.1", "typescript": "~5.6.3" } diff --git a/packages/spector/src/actions/generate-scenario-summary.ts b/packages/spector/src/actions/generate-scenario-summary.ts index da1891dc19..7817056f5d 100644 --- a/packages/spector/src/actions/generate-scenario-summary.ts +++ b/packages/spector/src/actions/generate-scenario-summary.ts @@ -1,7 +1,7 @@ -import { Scenario, ScenarioEndpoint } from "@typespec/spec-lib"; import { writeFile } from "fs/promises"; import pc from "picocolors"; import prettier from "prettier"; +import type { Scenario, ScenarioEndpoint } from "../lib/decorators.js"; import { logger } from "../logger.js"; import { loadScenarios } from "../scenarios-resolver.js"; diff --git a/packages/spector/src/coverage/scenario-manifest.ts b/packages/spector/src/coverage/scenario-manifest.ts index 91a8364c6a..681fa4490b 100644 --- a/packages/spector/src/coverage/scenario-manifest.ts +++ b/packages/spector/src/coverage/scenario-manifest.ts @@ -1,10 +1,10 @@ -import { Scenario } from "@typespec/spec-lib"; import { loadScenarios } from "../scenarios-resolver.js"; import { Diagnostic } from "../utils/diagnostic-reporter.js"; import { getCommit, getPackageJson } from "../utils/misc-utils.js"; import { ScenarioLocation, ScenarioManifest, GeneratorMode } from "@typespec/spec-coverage-sdk"; import { getSourceLocation, normalizePath } from "@typespec/compiler"; import { relative } from "path"; +import type { Scenario } from "../lib/decorators.js"; export async function computeScenarioManifest( scenariosPath: string, diff --git a/packages/spector/src/index.ts b/packages/spector/src/index.ts new file mode 100644 index 0000000000..735d00691a --- /dev/null +++ b/packages/spector/src/index.ts @@ -0,0 +1 @@ +export * from "./lib/index.js"; diff --git a/packages/spec-lib/src/decorators.ts b/packages/spector/src/lib/decorators.ts similarity index 93% rename from packages/spec-lib/src/decorators.ts rename to packages/spector/src/lib/decorators.ts index 5f0aac830a..3fc1f8c1f0 100644 --- a/packages/spec-lib/src/decorators.ts +++ b/packages/spector/src/lib/decorators.ts @@ -23,16 +23,16 @@ import { ScenarioDecorator, ScenarioDocDecorator, ScenarioServiceDecorator, -} from "../generated-defs/TypeSpec.SpecLib.js"; -import { SpecLibStateKeys } from "./lib.js"; +} from "../../generated-defs/TypeSpec.Spector.js"; +import { SpectorStateKeys } from "./lib.js"; export const $scenario: ScenarioDecorator = (context, target, name?) => { - context.program.stateMap(SpecLibStateKeys.Scenario).set(target, name ?? target.name); + context.program.stateMap(SpectorStateKeys.Scenario).set(target, name ?? target.name); }; export const $scenarioDoc: ScenarioDocDecorator = (context, target, doc, formatArgs?) => { const formattedDoc = formatArgs ? replaceTemplatedStringFromProperties(doc, formatArgs) : doc; - context.program.stateMap(SpecLibStateKeys.ScenarioDoc).set(target, formattedDoc); + context.program.stateMap(SpectorStateKeys.ScenarioDoc).set(target, formattedDoc); }; export const $scenarioService: ScenarioServiceDecorator = (context, target, route, options?) => { @@ -40,7 +40,7 @@ export const $scenarioService: ScenarioServiceDecorator = (context, target, rout type: { kind: "String", value: getNamespaceFullName(target).replace(/\./g, "") }, }); - context.program.stateSet(SpecLibStateKeys.ScenarioService).add(target); + context.program.stateSet(SpectorStateKeys.ScenarioService).add(target); const versions = options ? (options as Model).properties.get("versioned")?.type : null; if (versions) { @@ -63,7 +63,7 @@ export function getScenarioDoc( program: Program, target: Operation | Interface | Namespace, ): string | undefined { - return program.stateMap(SpecLibStateKeys.ScenarioDoc).get(target); + return program.stateMap(SpectorStateKeys.ScenarioDoc).get(target); } function replaceTemplatedStringFromProperties(formatString: string, formatArgs: Model) { @@ -210,14 +210,14 @@ function resolveScenarioName(target: Operation | Interface | Namespace, name: st } export function isScenario(program: Program, target: Operation | Interface | Namespace): boolean { - return program.stateMap(SpecLibStateKeys.Scenario).has(target); + return program.stateMap(SpectorStateKeys.Scenario).has(target); } export function getScenarioName( program: Program, target: Operation | Interface | Namespace, ): string | undefined { - const name = program.stateMap(SpecLibStateKeys.Scenario).get(target); + const name = program.stateMap(SpectorStateKeys.Scenario).get(target); if (name === undefined) { return undefined; } diff --git a/packages/spec-lib/src/index.ts b/packages/spector/src/lib/index.ts similarity index 100% rename from packages/spec-lib/src/index.ts rename to packages/spector/src/lib/index.ts diff --git a/packages/spec-lib/src/lib.ts b/packages/spector/src/lib/lib.ts similarity index 95% rename from packages/spec-lib/src/lib.ts rename to packages/spector/src/lib/lib.ts index 812d9823a2..b4450552ec 100644 --- a/packages/spec-lib/src/lib.ts +++ b/packages/spector/src/lib/lib.ts @@ -1,7 +1,7 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler"; export const $lib = createTypeSpecLibrary({ - name: "@typespec/spec-lib", + name: "@typespec/spector", diagnostics: { "category-invalid": { severity: "error", @@ -30,4 +30,4 @@ export const $lib = createTypeSpecLibrary({ }, }); -export const { reportDiagnostic, createStateSymbol, stateKeys: SpecLibStateKeys } = $lib; +export const { reportDiagnostic, createStateSymbol, stateKeys: SpectorStateKeys } = $lib; diff --git a/packages/spec-lib/src/tsp-index.ts b/packages/spector/src/lib/tsp-index.ts similarity index 62% rename from packages/spec-lib/src/tsp-index.ts rename to packages/spector/src/lib/tsp-index.ts index bba193fbfe..bf29ae1369 100644 --- a/packages/spec-lib/src/tsp-index.ts +++ b/packages/spector/src/lib/tsp-index.ts @@ -1,12 +1,12 @@ -import type { TypeSpecSpecLibDecorators } from "../generated-defs/TypeSpec.SpecLib.js"; +import type { TypeSpecSpectorDecorators } from "../../generated-defs/TypeSpec.Spector.js"; import { $scenario, $scenarioDoc, $scenarioService } from "./decorators.js"; export { $lib } from "./lib.js"; /** @internal */ export const $decorators = { - "TypeSpec.SpecLib": { + "TypeSpec.Spector": { scenario: $scenario, scenarioDoc: $scenarioDoc, scenarioService: $scenarioService, - } satisfies TypeSpecSpecLibDecorators, + } satisfies TypeSpecSpectorDecorators, }; diff --git a/packages/spec-lib/src/validate.ts b/packages/spector/src/lib/validate.ts similarity index 100% rename from packages/spec-lib/src/validate.ts rename to packages/spector/src/lib/validate.ts diff --git a/packages/spector/src/scenarios-resolver.ts b/packages/spector/src/scenarios-resolver.ts index 32f20211f2..6836b623f4 100644 --- a/packages/spector/src/scenarios-resolver.ts +++ b/packages/spector/src/scenarios-resolver.ts @@ -1,10 +1,10 @@ import { Operation } from "@typespec/compiler"; import { isSharedRoute } from "@typespec/http"; import { ScenarioMockApi } from "@typespec/spec-api"; -import { Scenario } from "@typespec/spec-lib"; import { dirname, join, relative, resolve } from "path"; import pc from "picocolors"; import { pathToFileURL } from "url"; +import type { Scenario } from "./lib/decorators.js"; import { logger } from "./logger.js"; import { importSpecExpect, importTypeSpec, importTypeSpecHttp } from "./spec-utils/index.js"; import { findFilesFromPattern } from "./utils/file-utils.js"; @@ -70,7 +70,7 @@ export async function loadScenarios( for (const { name, specFilePath } of scenarioFiles) { logger.debug(`Found scenario "${specFilePath}"`); const program = await typespecCompiler.compile(typespecCompiler.NodeHost, specFilePath, { - additionalImports: ["@typespec/spec-lib"], + additionalImports: ["@typespec/spector"], noEmit: true, warningAsError: true, }); diff --git a/packages/spector/src/spec-utils/import-spec.ts b/packages/spector/src/spec-utils/import-spec.ts index 9175d0b265..82ce92e201 100644 --- a/packages/spector/src/spec-utils/import-spec.ts +++ b/packages/spector/src/spec-utils/import-spec.ts @@ -1,6 +1,6 @@ +import { type ResolveModuleHost, resolveModule } from "@typespec/compiler/module-resolver"; import { readFile, realpath, stat } from "fs/promises"; import { pathToFileURL } from "url"; -import { ResolveModuleHost, resolveModule } from "./module-resolver.js"; export async function importTypeSpec( baseDir: string, @@ -9,8 +9,8 @@ export async function importTypeSpec( } export async function importSpecExpect( baseDir: string, -): Promise { - return importTypeSpecLibrary("@typespec/spec-lib", baseDir); +): Promise { + return importTypeSpecLibrary("@typespec/spector", baseDir); } export async function importTypeSpecRest( baseDir: string, @@ -33,8 +33,11 @@ export async function importTypeSpecLibrary(name: string, baseDir: string): Prom }; const resolved = await resolveModule(host, name, { baseDir, + conditions: ["import"], }); - return import(pathToFileURL(resolved).toString()); + return import( + pathToFileURL(resolved.type === "module" ? resolved.mainFile : resolved.path).toString() + ); } catch (err: any) { if (err.code === "MODULE_NOT_FOUND") { // Resolution from cwd failed: use current package. diff --git a/packages/spector/src/spec-utils/module-resolver.ts b/packages/spector/src/spec-utils/module-resolver.ts deleted file mode 100644 index 9f32637c1e..0000000000 --- a/packages/spector/src/spec-utils/module-resolver.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { getDirectoryPath, joinPaths, resolvePath } from "@typespec/compiler"; - -export interface ResolveModuleOptions { - baseDir: string; - resolveMain?: (pkg: any) => string; -} - -export interface ResolveModuleHost { - /** - * Resolve the real path for the current host. - */ - realpath(path: string): Promise; - - /** - * Get information about the given path - */ - stat(path: string): Promise<{ isDirectory(): boolean; isFile(): boolean }>; - - /** - * Read a utf-8 encoded file. - */ - readFile(path: string): Promise; -} - -type ResolveModuleErrorCode = "MODULE_NOT_FOUND"; -export class ResolveModuleError extends Error { - public constructor( - public code: ResolveModuleErrorCode, - message: string, - ) { - super(message); - } -} - -/** - * Resolve a module - * @param host - * @param name - * @param options - * @returns - */ -export async function resolveModule( - host: ResolveModuleHost, - name: string, - options: ResolveModuleOptions, -) { - const { baseDir } = options; - const absoluteStart = baseDir === "" ? "." : await host.realpath(resolvePath(baseDir)); - - if (!(await isDirectory(host, absoluteStart))) { - throw new TypeError(`Provided basedir '${baseDir}'is not a directory.`); - } - - // Check if the module name is referencing a path(./foo, /foo, file:/foo) - if (/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/.test(name)) { - const res = resolvePath(absoluteStart, name); - const m = (await loadAsFile(res)) || (await loadAsDirectory(res)); - if (m) return host.realpath(m); - } - - const module = await findAsNodeModule(name, absoluteStart); - if (module) return host.realpath(module); - - throw new ResolveModuleError( - "MODULE_NOT_FOUND", - `Cannot find module '${name} ' from '${baseDir}'`, - ); - - /** - * Returns a list of all the parent directory and the given one. - */ - function listAllParentDirs(baseDir: string): string[] { - const paths = [baseDir]; - let current = getDirectoryPath(baseDir); - while (current !== paths[paths.length - 1]) { - paths.push(current); - current = getDirectoryPath(current); - } - - return paths; - } - - function getPackageCandidates(name: string, baseDir: string) { - const dirs = listAllParentDirs(baseDir); - return dirs.map((x) => joinPaths(x, "node_modules", name)); - } - - async function findAsNodeModule(name: string, baseDir: string): Promise { - const dirs = getPackageCandidates(name, baseDir); - for (const dir of dirs) { - if (await isDirectory(host, dir)) { - const n = await loadAsDirectory(dir); - if (n) return n; - } - } - return undefined; - } - - async function loadAsDirectory(directory: string): Promise { - const pkgFile = resolvePath(directory, "package.json"); - if (await isFile(host, pkgFile)) { - const pkg = await readPackage(host, pkgFile); - const mainFile = options.resolveMain ? options.resolveMain(pkg) : pkg.main; - if (typeof mainFile !== "string") { - throw new TypeError(`package "${pkg.name}" main must be a string but was '${mainFile}'`); - } - - const mainFullPath = resolvePath(directory, mainFile); - try { - return loadAsFile(mainFullPath) ?? loadAsDirectory(mainFullPath); - } catch (e) { - throw new Error( - `Cannot find module '${mainFullPath}'. Please verify that the package.json has a valid "main" entry`, - ); - } - } - - // Try to load index file - return loadAsFile(joinPaths(directory, "index")); - } - - async function loadAsFile(file: string): Promise { - if (await isFile(host, file)) { - return file; - } - - const extensions = [".js"]; - for (const ext of extensions) { - const fileWithExtension = file + ext; - if (await isFile(host, fileWithExtension)) { - return fileWithExtension; - } - } - return undefined; - } -} - -async function readPackage(host: ResolveModuleHost, pkgfile: string) { - const content = await host.readFile(pkgfile); - return JSON.parse(content); -} - -async function isDirectory(host: ResolveModuleHost, path: string) { - try { - const stats = await host.stat(path); - return stats.isDirectory(); - } catch (e: any) { - if (e.code === "ENOENT" || e.code === "ENOTDIR") { - return false; - } - throw e; - } -} - -async function isFile(host: ResolveModuleHost, path: string) { - try { - const stats = await host.stat(path); - return stats.isFile(); - } catch (e: any) { - if (e.code === "ENOENT" || e.code === "ENOTDIR") { - return false; - } - throw e; - } -} diff --git a/packages/spector/tsconfig.build.json b/packages/spector/tsconfig.build.json index edde292acf..0085c31c29 100644 --- a/packages/spector/tsconfig.build.json +++ b/packages/spector/tsconfig.build.json @@ -1,12 +1,13 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "rootDir": "./src" + "outDir": "dist", + "tsBuildInfoFile": "temp/.tsbuildinfo" }, "references": [ - { "path": "../spec-lib/tsconfig.build.json" }, { "path": "../spec-api/tsconfig.build.json" }, { "path": "../spec-coverage-sdk/tsconfig.build.json" } ], - "exclude": ["**/*.test.*", "test/**/*"] + "include": ["src/**/*.ts", "generated-defs/**/*.ts"], + "exclude": ["**/*.test.*"] } diff --git a/packages/spector/tsconfig.json b/packages/spector/tsconfig.json index 4c0d25c1d4..79fb341f39 100644 --- a/packages/spector/tsconfig.json +++ b/packages/spector/tsconfig.json @@ -1,8 +1,4 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "dist", - "tsBuildInfoFile": "temp/.tsbuildinfo" - }, - "include": ["src/**/*.ts", "test/**/*.ts", "config/config.ts"] + "compilerOptions": {} } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fa4c09525..d7248a9be7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -617,9 +617,6 @@ importers: '@typespec/spec-api': specifier: workspace:~ version: link:../spec-api - '@typespec/spec-lib': - specifier: workspace:~ - version: link:../spec-lib '@typespec/spector': specifier: workspace:~ version: link:../spector @@ -1503,34 +1500,6 @@ importers: specifier: ~5.6.3 version: 5.6.3 - packages/spec-lib: - dependencies: - '@typespec/compiler': - specifier: workspace:~ - version: link:../compiler - '@typespec/http': - specifier: workspace:~ - version: link:../http - '@typespec/rest': - specifier: workspace:~ - version: link:../rest - '@typespec/versioning': - specifier: workspace:~ - version: link:../versioning - devDependencies: - '@types/node': - specifier: ~22.7.5 - version: 22.7.5 - '@typespec/tspd': - specifier: workspace:~ - version: link:../tspd - rimraf: - specifier: ~6.0.1 - version: 6.0.1 - typescript: - specifier: ~5.6.3 - version: 5.6.3 - packages/spector: dependencies: '@azure/identity': @@ -1554,9 +1523,9 @@ importers: '@typespec/spec-coverage-sdk': specifier: workspace:~ version: link:../spec-coverage-sdk - '@typespec/spec-lib': + '@typespec/versioning': specifier: workspace:~ - version: link:../spec-lib + version: link:../versioning ajv: specifier: ~8.17.1 version: 8.17.1 @@ -1639,6 +1608,9 @@ importers: '@types/yargs': specifier: ~17.0.33 version: 17.0.33 + '@typespec/tspd': + specifier: workspace:~ + version: link:../tspd rimraf: specifier: ~6.0.1 version: 6.0.1 diff --git a/tsconfig.ws.json b/tsconfig.ws.json index 3767a3d6ff..68c7f0f897 100644 --- a/tsconfig.ws.json +++ b/tsconfig.ws.json @@ -15,7 +15,6 @@ { "path": "packages/openapi3/tsconfig.json" }, { "path": "packages/spec-api/tsconfig.build.json" }, { "path": "packages/spector/tsconfig.build.json" }, - { "path": "packages/spec-lib/tsconfig.build.json" }, { "path": "packages/http-specs/tsconfig.build.json" }, { "path": "packages/monarch/tsconfig.json" }, { "path": "packages/bundler/tsconfig.json" }, From 45787924a80d148db91dbf4883334445ac4c03ea Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:36:29 -0700 Subject: [PATCH 07/14] Add canonical view and fix customized struct serialization (#4712) Fixes https://github.com/microsoft/typespec/issues/4624 and https://github.com/microsoft/typespec/issues/3796 --- .../MrwSerializationTypeDefinition.cs | 2 +- .../ClientProviderCustomizationTests.cs | 9 +- .../CanReplaceStructMethod.cs | 0 .../CanReplaceStructMethod.cs | 23 +++ .../ModelCustomizationTests.cs | 1 - .../SerializationCustomizationTests.cs | 30 ++++ .../CanCustomizeExtensibleEnum(int32).cs | 145 ++++++++++++++++ .../MockInputModel.cs | 17 ++ .../CanCustomizeExtensibleEnum(string).cs | 145 ++++++++++++++++ .../MockInputModel.cs | 17 ++ .../src/Expressions/MemberExpression.cs | 4 +- .../src/Providers/CanonicalTypeProvider.cs | 160 ++++++++++++++++++ .../src/Providers/ModelProvider.cs | 20 +-- .../src/Providers/NamedTypeSymbolProvider.cs | 10 +- .../src/Providers/PropertyProvider.cs | 4 +- .../src/Providers/TypeProvider.cs | 138 ++++++--------- .../src/TypeFactory.cs | 48 +++--- .../src/Utilities/TypeSymbolExtensions.cs | 10 +- .../Providers/CanonicalTypeProviderTests.cs | 99 +++++++++++ .../NamedTypeSymbolProviderTests.cs | 111 +----------- .../test/TestHelpers/TestNamedSymbol.cs | 97 +++++++++++ .../test/TestHelpers/TestPropertyType.cs | 20 +++ 22 files changed, 865 insertions(+), 245 deletions(-) rename packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/{CanReplaceStructMethod => CanReplaceStructMethod(False)}/CanReplaceStructMethod.cs (100%) create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(True)/CanReplaceStructMethod.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32)/MockInputModel.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string)/MockInputModel.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/CanonicalTypeProviderTests.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestNamedSymbol.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestPropertyType.cs diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs index 4d33051b0f..53dd9ba593 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs @@ -1311,7 +1311,7 @@ private static MethodBodyStatement ThrowValidationFailException(ValueExpression /// private MethodBodyStatement[] CreateWritePropertiesStatements() { - var properties = _model.Properties.Concat(_model.CustomCodeView?.Properties.Where(p => p.WireInfo != null) ?? []); + var properties = _model.CanonicalView.Properties; List propertyStatements = new(); foreach (var property in properties) { diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs index 46e74868ac..ac529cee6f 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs @@ -153,7 +153,9 @@ public async Task CanReplaceOpMethod() // Validates that a method with a struct parameter can be replaced [Test] - public async Task CanReplaceStructMethod() + [TestCase(true)] + [TestCase(false)] + public async Task CanReplaceStructMethod(bool isStructCustomized) { var inputOperation = InputFactory.Operation("HelloAgain", parameters: [ @@ -162,7 +164,7 @@ public async Task CanReplaceStructMethod() var inputClient = InputFactory.Client("TestClient", operations: [inputOperation]); var plugin = await MockHelpers.LoadMockPluginAsync( clients: () => [inputClient], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(isStructCustomized.ToString())); // Find the client provider var clientProvider = plugin.Object.OutputLibrary.TypeProviders.SingleOrDefault(t => t is ClientProvider); @@ -188,7 +190,8 @@ public async Task CanReplaceStructMethod() var customMethodParams = customMethods[0].Signature.Parameters; Assert.AreEqual(1, customMethodParams.Count); Assert.AreEqual("p1", customMethodParams[0].Name); - Assert.AreEqual("MyStruct", customMethodParams[0].Type.Name); + Assert.AreEqual(isStructCustomized ? "Sample.TestClient.MyStruct" : "MyStruct", customMethodParams[0].Type.Name); + Assert.IsTrue(customMethodParams[0].Type.IsStruct); Assert.IsTrue(customMethodParams[0].Type.IsNullable); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod/CanReplaceStructMethod.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(False)/CanReplaceStructMethod.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod/CanReplaceStructMethod.cs rename to packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(False)/CanReplaceStructMethod.cs diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(True)/CanReplaceStructMethod.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(True)/CanReplaceStructMethod.cs new file mode 100644 index 0000000000..cc6387df2e --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/CanReplaceStructMethod(True)/CanReplaceStructMethod.cs @@ -0,0 +1,23 @@ + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Sample +{ + /// + public partial class TestClient + { + public virtual ClientResult HelloAgain(MyStruct? p1) + { + + } + + public readonly partial struct MyStruct + { + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs index bcc736508d..b48d273ef3 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/ModelCustomizationTests.cs @@ -35,7 +35,6 @@ public async Task CanChangePropertyName() // validate the methods use the custom member name var writer = new TypeProviderWriter(modelProvider); var file = writer.Write(); - var expected = Helpers.GetExpectedFromFile(); Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs index 62566bf7d1..6dfbe6c893 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Generator.CSharp.ClientModel.Providers; @@ -212,6 +213,35 @@ public async Task CanChangeDictionaryToBinaryData() Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } + private static IEnumerable ExtensibleEnumCases => + [ + new TestCaseData(InputPrimitiveType.String), + new TestCaseData(InputPrimitiveType.Int32), + ]; + + [TestCaseSource(nameof(ExtensibleEnumCases))] + public async Task CanCustomizeExtensibleEnum(InputPrimitiveType enumType) + { + var props = new[] + { + InputFactory.Property("Prop1", InputFactory.Enum("EnumType", enumType, isExtensible: true)) + }; + + var inputModel = InputFactory.Model("mockInputModel", properties: props, usage: InputModelTypeUsage.Json); + var plugin = await MockHelpers.LoadMockPluginAsync( + inputModels: () => [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(enumType.Name)); + + var modelProvider = plugin.Object.OutputLibrary.TypeProviders.Single(t => t is ModelProvider); + var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); + Assert.IsNotNull(serializationProvider); + Assert.AreEqual(0, serializationProvider.Fields.Count); + + var writer = new TypeProviderWriter(serializationProvider); + var file = writer.Write(); + Assert.AreEqual(Helpers.GetExpectedFromFile(enumType.Name), file.Content); + } + [Test] public async Task CanReplaceSerializationMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32).cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32).cs new file mode 100644 index 0000000000..4032f62c2b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32).cs @@ -0,0 +1,145 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using Sample; + +namespace Sample.Models +{ + /// + public partial class MockInputModel : global::System.ClientModel.Primitives.IJsonModel + { + void global::System.ClientModel.Primitives.IJsonModel.Write(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + this.JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + /// The JSON writer. + /// The client options for reading and writing models. + protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{format}' format."); + } + writer.WritePropertyName("prop1"u8); + writer.WriteNumberValue(Prop1.ToSerialInt32()); + if (((options.Format != "W") && (_additionalBinaryDataProperties != null))) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(item.Value)) + { + global::System.Text.Json.JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IJsonModel.Create(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.JsonModelCreateCore(ref reader, options)); + + /// The JSON reader. + /// The client options for reading and writing models. + protected virtual global::Sample.Models.MockInputModel JsonModelCreateCore(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{format}' format."); + } + using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.ParseValue(ref reader); + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options); + } + + internal static global::Sample.Models.MockInputModel DeserializeMockInputModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + return null; + } + global::Sample.Models.EnumType prop1 = default; + global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("prop1"u8)) + { + if ((prop.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + prop1 = null; + continue; + } + prop1 = new global::Sample.Models.EnumType(prop.Value.GetInt32()); + continue; + } + if ((options.Format != "W")) + { + additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText())); + } + } + return new global::Sample.Models.MockInputModel(prop1, additionalBinaryDataProperties); + } + + global::System.BinaryData global::System.ClientModel.Primitives.IPersistableModel.Write(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => this.PersistableModelWriteCore(options); + + /// The client options for reading and writing models. + protected virtual global::System.BinaryData PersistableModelWriteCore(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + return global::System.ClientModel.Primitives.ModelReaderWriter.Write(this, options); + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{options.Format}' format."); + } + } + + global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IPersistableModel.Create(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.PersistableModelCreateCore(data, options)); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual global::Sample.Models.MockInputModel PersistableModelCreateCore(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(data)) + { + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options); + } + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{options.Format}' format."); + } + } + + string global::System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => "J"; + + /// The to serialize into . + public static implicit operator BinaryContent(global::Sample.Models.MockInputModel mockInputModel) + { + return global::System.ClientModel.BinaryContent.Create(mockInputModel, global::Sample.ModelSerializationExtensions.WireOptions); + } + + /// The to deserialize the from. + public static explicit operator MockInputModel(global::System.ClientModel.ClientResult result) + { + using global::System.ClientModel.Primitives.PipelineResponse response = result.GetRawResponse(); + using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(response.Content); + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, global::Sample.ModelSerializationExtensions.WireOptions); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32)/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32)/MockInputModel.cs new file mode 100644 index 0000000000..a27e763526 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(int32)/MockInputModel.cs @@ -0,0 +1,17 @@ +#nullable disable + +using Microsoft.Generator.CSharp.Customization; +using Microsoft.Generator.CSharp.Primitives; + +namespace Sample.Models +{ + public partial class MockInputModel + { + public EnumType Prop1 { get; set; } + } + + public readonly partial struct EnumType + { + public static EnumType Foo = new EnumType(1); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string).cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string).cs new file mode 100644 index 0000000000..68d078fc24 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string).cs @@ -0,0 +1,145 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using Sample; + +namespace Sample.Models +{ + /// + public partial class MockInputModel : global::System.ClientModel.Primitives.IJsonModel + { + void global::System.ClientModel.Primitives.IJsonModel.Write(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + writer.WriteStartObject(); + this.JsonModelWriteCore(writer, options); + writer.WriteEndObject(); + } + + /// The JSON writer. + /// The client options for reading and writing models. + protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{format}' format."); + } + writer.WritePropertyName("prop1"u8); + writer.WriteStringValue(Prop1.ToString()); + if (((options.Format != "W") && (_additionalBinaryDataProperties != null))) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(item.Value)) + { + global::System.Text.Json.JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + + global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IJsonModel.Create(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.JsonModelCreateCore(ref reader, options)); + + /// The JSON reader. + /// The client options for reading and writing models. + protected virtual global::Sample.Models.MockInputModel JsonModelCreateCore(ref global::System.Text.Json.Utf8JsonReader reader, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{format}' format."); + } + using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.ParseValue(ref reader); + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options); + } + + internal static global::Sample.Models.MockInputModel DeserializeMockInputModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + return null; + } + global::Sample.Models.EnumType prop1 = default; + global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("prop1"u8)) + { + if ((prop.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + prop1 = null; + continue; + } + prop1 = new global::Sample.Models.EnumType(prop.Value.GetString()); + continue; + } + if ((options.Format != "W")) + { + additionalBinaryDataProperties.Add(prop.Name, global::System.BinaryData.FromString(prop.Value.GetRawText())); + } + } + return new global::Sample.Models.MockInputModel(prop1, additionalBinaryDataProperties); + } + + global::System.BinaryData global::System.ClientModel.Primitives.IPersistableModel.Write(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => this.PersistableModelWriteCore(options); + + /// The client options for reading and writing models. + protected virtual global::System.BinaryData PersistableModelWriteCore(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + return global::System.ClientModel.Primitives.ModelReaderWriter.Write(this, options); + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{options.Format}' format."); + } + } + + global::Sample.Models.MockInputModel global::System.ClientModel.Primitives.IPersistableModel.Create(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => ((global::Sample.Models.MockInputModel)this.PersistableModelCreateCore(data, options)); + + /// The data to parse. + /// The client options for reading and writing models. + protected virtual global::Sample.Models.MockInputModel PersistableModelCreateCore(global::System.BinaryData data, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + switch (format) + { + case "J": + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(data)) + { + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, options); + } + default: + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support reading '{options.Format}' format."); + } + } + + string global::System.ClientModel.Primitives.IPersistableModel.GetFormatFromOptions(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => "J"; + + /// The to serialize into . + public static implicit operator BinaryContent(global::Sample.Models.MockInputModel mockInputModel) + { + return global::System.ClientModel.BinaryContent.Create(mockInputModel, global::Sample.ModelSerializationExtensions.WireOptions); + } + + /// The to deserialize the from. + public static explicit operator MockInputModel(global::System.ClientModel.ClientResult result) + { + using global::System.ClientModel.Primitives.PipelineResponse response = result.GetRawResponse(); + using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(response.Content); + return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, global::Sample.ModelSerializationExtensions.WireOptions); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string)/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string)/MockInputModel.cs new file mode 100644 index 0000000000..93fccae2ce --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/CanCustomizeExtensibleEnum(string)/MockInputModel.cs @@ -0,0 +1,17 @@ +#nullable disable + +using Microsoft.Generator.CSharp.Customization; +using Microsoft.Generator.CSharp.Primitives; + +namespace Sample.Models +{ + public partial class MockInputModel + { + public EnumType Prop1 { get; set; } + } + + public readonly partial struct EnumType + { + public static EnumType Foo = new EnumType("Foo"); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/MemberExpression.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/MemberExpression.cs index fb36ee9e55..b02f4e7c4c 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/MemberExpression.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Expressions/MemberExpression.cs @@ -12,7 +12,9 @@ internal override void Write(CodeWriter writer) Inner.Write(writer); writer.AppendRaw("."); } - writer.AppendRaw(MemberName); + // workaround to avoid Roslyn reducing properties named Object to object + // Should come up with a better approach - https://github.com/microsoft/typespec/issues/4724 + writer.AppendRaw(MemberName == "Object" && Inner == null ? $"this.{MemberName}" : MemberName); } } } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs new file mode 100644 index 0000000000..ade5f63349 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Generator.CSharp.Input; +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.SourceInput; + +namespace Microsoft.Generator.CSharp.Providers +{ + internal class CanonicalTypeProvider : TypeProvider + { + private readonly InputModelType? _inputModel; + private readonly TypeProvider _generatedTypeProvider; + + public CanonicalTypeProvider(TypeProvider generatedTypeProvider, InputType? inputType) + { + _generatedTypeProvider = generatedTypeProvider; + _inputModel = inputType as InputModelType; + } + protected override string BuildRelativeFilePath() => throw new InvalidOperationException("This type should not be writing in generation"); + + protected override string BuildName() => _generatedTypeProvider.Name; + + protected override string GetNamespace() => _generatedTypeProvider.Namespace; + + protected override TypeSignatureModifiers GetDeclarationModifiers() => _generatedTypeProvider.DeclarationModifiers; + + private protected override PropertyProvider[] FilterCustomizedProperties(PropertyProvider[] canonicalProperties) => canonicalProperties; + + private protected override CanonicalTypeProvider GetCanonicalView() => this; + + // TODO - Implement BuildMethods, BuildConstructors, etc as needed + + protected override PropertyProvider[] BuildProperties() + { + var specProperties = _inputModel?.Properties ?? []; + var specPropertiesMap = specProperties.ToDictionary(p => p.Name, p => p); + var generatedProperties = _generatedTypeProvider.Properties; + var customProperties = _generatedTypeProvider.CustomCodeView?.Properties ?? []; + + Dictionary serializedNameMapping = BuildSerializationNameMap(); + + // Update the serializedName of generated properties if necessary + foreach (var generatedProperty in generatedProperties) + { + if (serializedNameMapping.TryGetValue(generatedProperty.Name, out var serializedName) && serializedName != null) + { + generatedProperty.WireInfo!.SerializedName = serializedName; + } + } + + Dictionary specToCustomPropertiesMap = BuildSpecToCustomPropertyMap(customProperties, specPropertiesMap); + + foreach (var customProperty in customProperties) + { + InputModelProperty? specProperty = null; + + if (((customProperty.OriginalName != null && specPropertiesMap.TryGetValue(customProperty.OriginalName, out var candidateSpecProperty)) + || specPropertiesMap.TryGetValue(customProperty.Name, out candidateSpecProperty)) + // Ensure that the spec property is mapped to this custom property + && specToCustomPropertiesMap[candidateSpecProperty] == customProperty) + { + specProperty = candidateSpecProperty; + customProperty.WireInfo = new PropertyWireInformation(specProperty); + } + + string? serializedName = specProperty?.SerializedName; + bool hasCustomSerialization = false; + // Update the serializedName of custom properties if necessary + if (serializedNameMapping.TryGetValue(customProperty.Name, out var customSerializedName) || + (customProperty.OriginalName != null && serializedNameMapping.TryGetValue(customProperty.OriginalName, out customSerializedName))) + { + hasCustomSerialization = true; + if (customSerializedName != null) + { + serializedName = customSerializedName; + } + } + + if (serializedName != null || hasCustomSerialization) + { + if (specProperty == null) + { + customProperty.WireInfo = new( + SerializationFormat.Default, + false, + !customProperty.Body.HasSetter, + customProperty.Type.IsNullable, + false, + serializedName ?? customProperty.Name.ToVariableName());; + } + else + { + customProperty.WireInfo!.SerializedName = serializedName!; + } + } + + // handle customized extensible enums, since the custom type would not be an enum, but the spec type would be an enum + if (specProperty?.Type is InputEnumType { IsExtensible: true } inputEnumType) + { + customProperty.Type = new CSharpType( + customProperty.Type.Name, + customProperty.Type.Namespace, + customProperty.Type.IsValueType, + customProperty.Type.IsNullable, + customProperty.Type.DeclaringType, + customProperty.Type.Arguments, + customProperty.Type.IsPublic, + customProperty.Type.IsStruct, + customProperty.Type.BaseType, + TypeFactory.CreatePrimitiveCSharpTypeCore(inputEnumType.ValueType)); + } + } + + return [..generatedProperties, ..customProperties]; + } + + private static Dictionary BuildSpecToCustomPropertyMap( + IReadOnlyList customProperties, + Dictionary specPropertiesMap) + { + var specToCustomPropertiesMap = new Dictionary(); + // Create a map from spec properties to custom properties so that we know which custom properties are replacing spec properties + foreach (var customProperty in customProperties) + { + if ((customProperty.OriginalName != null && specPropertiesMap.TryGetValue(customProperty.OriginalName, out var specProperty)) + || specPropertiesMap.TryGetValue(customProperty.Name, out specProperty)) + { + // If the spec property is not already mapped to a custom property, map it to this custom property + specToCustomPropertiesMap.TryAdd(specProperty, customProperty); + } + } + return specToCustomPropertiesMap; + } + + private Dictionary BuildSerializationNameMap() + { + var serializationAttributes = _generatedTypeProvider.CustomCodeView?.GetAttributes(). + Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSerializationAttributeName) ?? []; + var serializedNameMapping = new Dictionary(); + foreach (var serializationAttribute in serializationAttributes) + { + if (CodeGenAttributes.TryGetCodeGenSerializationAttributeValue( + serializationAttribute, + out var propertyName, + out string? serializationName, + out _, + out _, + out _)) + { + serializedNameMapping[propertyName] = serializationName; + } + } + return serializedNameMapping; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs index 5db762628e..2e68330ec3 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs @@ -30,7 +30,7 @@ public sealed class ModelProvider : TypeProvider private ModelProvider? _baseModelProvider; private ConstructorProvider? _fullConstructor; - public ModelProvider(InputModelType inputModel) + public ModelProvider(InputModelType inputModel) : base(inputModel) { _inputModel = inputModel; Description = inputModel.Description != null ? FormattableStringHelpers.FromString(inputModel.Description) : $"The {Name}."; @@ -85,8 +85,6 @@ public ModelProvider? BaseModelProvider internal bool SupportsBinaryDataAdditionalProperties => AdditionalPropertyProperties.Any(p => p.Type.ElementType.Equals(_additionalPropsUnknownType)); public ConstructorProvider FullConstructor => _fullConstructor ??= BuildFullConstructor(); - internal IReadOnlyList AllSpecProperties => Properties.Concat(CustomCodeView?.Properties.Where(p => p.WireInfo != null) ?? []).ToList(); - protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; protected override CSharpType? GetBaseType() @@ -459,14 +457,14 @@ private ConstructorProvider BuildFullConstructor() if (isPrimaryConstructor) { // the primary ctor should only include the properties of the direct base model - baseProperties = BaseModelProvider?.Properties ?? []; + baseProperties = BaseModelProvider?.CanonicalView.Properties ?? []; } else if (BaseModelProvider?.FullConstructor.Signature != null) { baseParameters.AddRange(BaseModelProvider.FullConstructor.Signature.Parameters); } - HashSet overriddenProperties = AllSpecProperties.Where(p => p.BaseProperty is not null).Select(p => p.BaseProperty!).ToHashSet(); + HashSet overriddenProperties = CanonicalView.Properties.Where(p => p.BaseProperty is not null).Select(p => p.BaseProperty!).ToHashSet(); // add the base parameters, if any foreach (var property in baseProperties) @@ -477,7 +475,7 @@ private ConstructorProvider BuildFullConstructor() // construct the initializer using the parameters from base signature var constructorInitializer = new ConstructorInitializer(true, [.. baseParameters.Select(p => GetExpressionForCtor(p, overriddenProperties, isPrimaryConstructor))]); - foreach (var property in AllSpecProperties) + foreach (var property in CanonicalView.Properties) { AddInitializationParameterForCtor(constructorParameters, property, Type.IsStruct, isPrimaryConstructor); } @@ -508,7 +506,7 @@ p.Property is null { if (_inputModel.BaseModel is not null && _inputModel.DiscriminatorValue is not null) { - var discriminator = BaseModelProvider?.Properties.Where(p => p.IsDiscriminator).FirstOrDefault(); + var discriminator = BaseModelProvider?.CanonicalView.Properties.Where(p => p.IsDiscriminator).FirstOrDefault(); if (discriminator != null) { var type = discriminator.Type; @@ -518,12 +516,12 @@ p.Property is null } else { - if (!type.IsFrameworkType && type.IsEnum) + if (!type.IsFrameworkType && type.IsEnum && _inputModel.BaseModel.DiscriminatorProperty!.Type is InputEnumType inputEnumType) { /* TODO: when customize the discriminator type to a enum, then we may not be able to get the correct TypeProvider in this way. * We will handle this when issue https://github.com/microsoft/typespec/issues/4313 is resolved. * */ - var discriminatorProvider = CodeModelPlugin.Instance.TypeFactory.CreateEnum(enumType: (InputEnumType)_inputModel.BaseModel.DiscriminatorProperty!.Type); + var discriminatorProvider = CodeModelPlugin.Instance.TypeFactory.CreateEnum(enumType: inputEnumType); var enumMember = discriminatorProvider!.EnumValues.FirstOrDefault(e => e.Value.ToString() == _inputModel.DiscriminatorValue) ?? throw new InvalidOperationException($"invalid discriminator value {_inputModel.DiscriminatorValue}"); /* {KindType}.{enumMember} */ return TypeReferenceExpression.FromType(type).Property(enumMember.Name); @@ -619,10 +617,10 @@ private MethodBodyStatement GetPropertyInitializers( bool isPrimaryConstructor, IReadOnlyList? parameters = null) { - List methodBodyStatements = new(Properties.Count + 1); + List methodBodyStatements = new(CanonicalView.Properties.Count + 1); Dictionary parameterMap = parameters?.ToDictionary(p => p.Name) ?? []; - foreach (var property in AllSpecProperties) + foreach (var property in CanonicalView.Properties) { // skip those non-spec properties if (property.WireInfo == null) diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/NamedTypeSymbolProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/NamedTypeSymbolProvider.cs index dc5a15b153..0fb6f041aa 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/NamedTypeSymbolProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/NamedTypeSymbolProvider.cs @@ -8,6 +8,7 @@ using System.Xml.Linq; using Microsoft.CodeAnalysis; using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.SourceInput; using Microsoft.Generator.CSharp.Statements; namespace Microsoft.Generator.CSharp.Providers @@ -108,6 +109,13 @@ protected override PropertyProvider[] BuildProperties() List properties = new List(); foreach (var propertySymbol in _namedTypeSymbol.GetMembers().OfType()) { + var codeGenAttribute = propertySymbol.GetAttributes().SingleOrDefault( + a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenMemberAttributeName); + string? originalName = null; + if (codeGenAttribute != null) + { + CodeGenAttributes.TryGetCodeGenMemberAttributeValue(codeGenAttribute, out originalName); + } var propertyProvider = new PropertyProvider( GetSymbolXmlDoc(propertySymbol, "summary"), GetAccessModifier(propertySymbol.DeclaredAccessibility), @@ -116,7 +124,7 @@ protected override PropertyProvider[] BuildProperties() new AutoPropertyBody(propertySymbol.SetMethod is not null), this) { - Attributes = propertySymbol.GetAttributes() + OriginalName = originalName }; properties.Add(propertyProvider); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/PropertyProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/PropertyProvider.cs index d56284fbce..8b2ab9480f 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/PropertyProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/PropertyProvider.cs @@ -23,7 +23,7 @@ public class PropertyProvider public FormattableString Description { get; } public XmlDocSummaryStatement XmlDocSummary { get; } public MethodSignatureModifiers Modifiers { get; internal set; } - public CSharpType Type { get; } + public CSharpType Type { get; internal set; } public string Name { get; internal set; } public PropertyBody Body { get; internal set; } public CSharpType? ExplicitInterface { get; } @@ -42,7 +42,7 @@ public class PropertyProvider public TypeProvider EnclosingType { get; } - internal IEnumerable? Attributes { get; init; } + internal string? OriginalName { get; init; } // for mocking #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs index c16966262c..bc472969e9 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs @@ -17,10 +17,21 @@ namespace Microsoft.Generator.CSharp.Providers public abstract class TypeProvider { private Lazy _customCodeView; + private Lazy _canonicalView; + private readonly InputType? _inputType; - protected TypeProvider() + protected TypeProvider(InputType? inputType = default) { _customCodeView = new(GetCustomCodeView); + _canonicalView = new(GetCanonicalView); + _inputType = inputType; + } + + // for mocking +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + protected TypeProvider() : this(null) +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { } private protected virtual NamedTypeSymbolProvider? GetCustomCodeView() @@ -28,6 +39,26 @@ protected TypeProvider() public NamedTypeSymbolProvider? CustomCodeView => _customCodeView.Value; + internal IReadOnlyList GetAllCustomProperties() + { + var allCustomProperties = CustomCodeView?.Properties != null + ? new List(CustomCodeView.Properties) + : []; + var baseTypeCustomCodeView = BaseTypeProvider?.CustomCodeView; + + // add all custom properties from base types + while (baseTypeCustomCodeView != null) + { + allCustomProperties.AddRange(baseTypeCustomCodeView.Properties); + baseTypeCustomCodeView = baseTypeCustomCodeView.BaseTypeProvider?.CustomCodeView; + } + + return allCustomProperties; + } + + private protected virtual CanonicalTypeProvider GetCanonicalView() => new CanonicalTypeProvider(this, _inputType); + public TypeProvider CanonicalView => _canonicalView.Value; + protected string? _deprecated; /// @@ -133,7 +164,8 @@ private TypeSignatureModifiers GetDeclarationModifiersInternal() public IReadOnlyList Implements => _implements ??= BuildImplements(); private IReadOnlyList? _properties; - public IReadOnlyList Properties => _properties ??= BuildPropertiesInternal(); + + public IReadOnlyList Properties => _properties ??= FilterCustomizedProperties(BuildProperties()); private IReadOnlyList? _methods; public IReadOnlyList Methods => _methods ??= BuildMethodsInternal(); @@ -157,64 +189,22 @@ private TypeSignatureModifiers GetDeclarationModifiersInternal() protected virtual CSharpType[] GetTypeArguments() => []; - private PropertyProvider[] BuildPropertiesInternal() + private protected virtual PropertyProvider[] FilterCustomizedProperties(PropertyProvider[] specProperties) { var properties = new List(); var customProperties = new Dictionary(); var renamedProperties = new Dictionary(); - var allCustomProperties = CustomCodeView?.Properties != null - ? new List(CustomCodeView.Properties) - : []; - var baseTypeCustomCodeView = BaseTypeProvider?.CustomCodeView; - - // add all custom properties from base types - while (baseTypeCustomCodeView != null) - { - allCustomProperties.AddRange(baseTypeCustomCodeView.Properties); - baseTypeCustomCodeView = baseTypeCustomCodeView.BaseTypeProvider?.CustomCodeView; - } - foreach (var customProperty in allCustomProperties) + foreach (var customProperty in GetAllCustomProperties()) { customProperties.Add(customProperty.Name, customProperty); - bool isRenamedProperty = false; - - foreach (var attribute in customProperty.Attributes ?? []) + if (customProperty.OriginalName != null) { - if (CodeGenAttributes.TryGetCodeGenMemberAttributeValue(attribute, out var originalName)) - { - renamedProperties.Add(originalName, customProperty); - isRenamedProperty = true; - } - } - - // Handle custom serializable properties - if (!isRenamedProperty && customProperty.WireInfo == null) - { - foreach (var attribute in GetCodeGenSerializationAttributes()) - { - if (CodeGenAttributes.TryGetCodeGenSerializationAttributeValue( - attribute, - out var propertyName, - out string? serializationName, - out _, - out _, - out _) && propertyName == customProperty.Name) - { - customProperty.WireInfo = new( - SerializationFormat.Default, - false, - !customProperty.Body.HasSetter, - customProperty.Type.IsNullable, - false, - serializationName ?? customProperty.Name.ToVariableName()); - break; - } - } + renamedProperties.Add(customProperty.OriginalName, customProperty); } } - foreach (var property in BuildProperties()) + foreach (var property in specProperties) { if (ShouldGenerate(property, customProperties, renamedProperties)) { @@ -222,7 +212,7 @@ private PropertyProvider[] BuildPropertiesInternal() } } - return properties.ToArray(); + return [..properties]; } private MethodProvider[] BuildMethodsInternal() @@ -236,7 +226,7 @@ private MethodProvider[] BuildMethodsInternal() } } - return methods.ToArray(); + return [..methods]; } private ConstructorProvider[] BuildConstructorsInternal() @@ -405,43 +395,9 @@ private bool ShouldGenerate(PropertyProvider property, IDictionary /// The to convert. /// An instance of . - private CSharpType CreatePrimitiveCSharpTypeCore(InputType inputType) => inputType switch + internal static Type CreatePrimitiveCSharpTypeCore(InputType inputType) => inputType switch { InputPrimitiveType primitiveType => primitiveType.Kind switch { - InputPrimitiveTypeKind.Boolean => new CSharpType(typeof(bool)), - InputPrimitiveTypeKind.Bytes => new CSharpType(typeof(BinaryData)), - InputPrimitiveTypeKind.PlainDate => new CSharpType(typeof(DateTimeOffset)), - InputPrimitiveTypeKind.Decimal => new CSharpType(typeof(decimal)), - InputPrimitiveTypeKind.Decimal128 => new CSharpType(typeof(decimal)), - InputPrimitiveTypeKind.PlainTime => new CSharpType(typeof(TimeSpan)), - InputPrimitiveTypeKind.Float32 => new CSharpType(typeof(float)), - InputPrimitiveTypeKind.Float64 => new CSharpType(typeof(double)), - InputPrimitiveTypeKind.Int8 => new CSharpType(typeof(sbyte)), - InputPrimitiveTypeKind.UInt8 => new CSharpType(typeof(byte)), - InputPrimitiveTypeKind.Int32 => new CSharpType(typeof(int)), - InputPrimitiveTypeKind.Int64 => new CSharpType(typeof(long)), - InputPrimitiveTypeKind.SafeInt => new CSharpType(typeof(long)), - InputPrimitiveTypeKind.Integer => new CSharpType(typeof(long)), // in typespec, integer is the base type of int related types, see type relation: https://typespec.io/docs/language-basics/type-relations - InputPrimitiveTypeKind.Float => new CSharpType(typeof(double)), // in typespec, float is the base type of float32 and float64, see type relation: https://typespec.io/docs/language-basics/type-relations - InputPrimitiveTypeKind.Numeric => new CSharpType(typeof(double)), // in typespec, numeric is the base type of number types, see type relation: https://typespec.io/docs/language-basics/type-relations - InputPrimitiveTypeKind.Stream => new CSharpType(typeof(Stream)), - InputPrimitiveTypeKind.String => new CSharpType(typeof(string)), - InputPrimitiveTypeKind.Url => new CSharpType(typeof(Uri)), - InputPrimitiveTypeKind.Unknown => new CSharpType(typeof(BinaryData)), - _ => new CSharpType(typeof(object)), + InputPrimitiveTypeKind.Boolean => typeof(bool), + InputPrimitiveTypeKind.Bytes => typeof(BinaryData), + InputPrimitiveTypeKind.PlainDate => typeof(DateTimeOffset), + InputPrimitiveTypeKind.Decimal => typeof(decimal), + InputPrimitiveTypeKind.Decimal128 => typeof(decimal), + InputPrimitiveTypeKind.PlainTime => typeof(TimeSpan), + InputPrimitiveTypeKind.Float32 => typeof(float), + InputPrimitiveTypeKind.Float64 => typeof(double), + InputPrimitiveTypeKind.Int8 => typeof(sbyte), + InputPrimitiveTypeKind.UInt8 => typeof(byte), + InputPrimitiveTypeKind.Int32 => typeof(int), + InputPrimitiveTypeKind.Int64 => typeof(long), + InputPrimitiveTypeKind.SafeInt => typeof(long), + InputPrimitiveTypeKind.Integer => typeof(long), // in typespec, integer is the base type of int related types, see type relation: https://typespec.io/docs/language-basics/type-relations + InputPrimitiveTypeKind.Float => typeof(double), // in typespec, float is the base type of float32 and float64, see type relation: https://typespec.io/docs/language-basics/type-relations + InputPrimitiveTypeKind.Numeric => typeof(double), // in typespec, numeric is the base type of number types, see type relation: https://typespec.io/docs/language-basics/type-relations + InputPrimitiveTypeKind.Stream => typeof(Stream), + InputPrimitiveTypeKind.String => typeof(string), + InputPrimitiveTypeKind.Url => typeof(Uri), + InputPrimitiveTypeKind.Unknown => typeof(BinaryData), + _ => typeof(object), }, - InputDateTimeType dateTimeType => new CSharpType(typeof(DateTimeOffset)), - InputDurationType durationType => new CSharpType(typeof(TimeSpan)), + InputDateTimeType dateTimeType => typeof(DateTimeOffset), + InputDurationType durationType => typeof(TimeSpan), _ => throw new InvalidOperationException($"Unknown type: {inputType}") }; diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs index 1efa2c8704..a171c04939 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs @@ -11,6 +11,7 @@ namespace Microsoft.Generator.CSharp internal static class TypeSymbolExtensions { private const string GlobalPrefix = "global::"; + private const string NullableTypeName = "System.Nullable"; public static bool IsSameType(this INamedTypeSymbol symbol, CSharpType type) { @@ -110,18 +111,19 @@ public static string GetFullyQualifiedName(this ITypeSymbol typeSymbol) // Handle nullable types if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated && !IsCollectionType(namedTypeSymbol)) { - const string nullableTypeName = "System.Nullable"; var argTypeSymbol = namedTypeSymbol.TypeArguments.FirstOrDefault(); if (argTypeSymbol != null) { + // If the argument type is an error type, then fall back to using the ToString of the arg type symbol. This means that the + // arg may not be fully qualified, but it is better than not having any type information at all. if (argTypeSymbol.TypeKind == TypeKind.Error) { - return GetFullyQualifiedName(argTypeSymbol); + return $"{NullableTypeName}`1[{argTypeSymbol}]"; } string[] typeArguments = [.. namedTypeSymbol.TypeArguments.Select(arg => "[" + GetFullyQualifiedName(arg) + "]")]; - return $"{nullableTypeName}`{namedTypeSymbol.TypeArguments.Length}[{string.Join(", ", typeArguments)}]"; + return $"{NullableTypeName}`{namedTypeSymbol.TypeArguments.Length}[{string.Join(", ", typeArguments)}]"; } } else if (namedTypeSymbol.TypeArguments.Length > 0 && !IsCollectionType(namedTypeSymbol)) @@ -164,7 +166,7 @@ private static CSharpType ConstructCSharpTypeFromSymbol( var typeArg = namedTypeSymbol?.TypeArguments.FirstOrDefault(); bool isValueType = typeSymbol.IsValueType; bool isEnum = typeSymbol.TypeKind == TypeKind.Enum; - bool isNullable = typeSymbol.NullableAnnotation == NullableAnnotation.Annotated; + bool isNullable = fullyQualifiedName.StartsWith(NullableTypeName); bool isNullableUnknownType = isNullable && typeArg?.TypeKind == TypeKind.Error; string name = isNullableUnknownType ? fullyQualifiedName : typeSymbol.Name; string[] pieces = fullyQualifiedName.Split('.'); diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/CanonicalTypeProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/CanonicalTypeProviderTests.cs new file mode 100644 index 0000000000..da9b9301f8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/CanonicalTypeProviderTests.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Generator.CSharp.Input; +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; +using Microsoft.Generator.CSharp.Tests.Providers.NamedTypeSymbolProviders; +using NUnit.Framework; + +namespace Microsoft.Generator.CSharp.Tests.Providers +{ + public class CanonicalTypeProviderTests + { + private NamedTypeSymbolProvider _namedTypeSymbolProvider; + private NamedSymbol _namedSymbol; + private readonly TestTypeProvider _typeProvider; + private readonly Compilation _compilation; + + public CanonicalTypeProviderTests() + { + _namedSymbol = new NamedSymbol(name: "TestName"); + _compilation = CompilationHelper.LoadCompilation([_namedSymbol, new PropertyType()]); + var iNamedSymbol = CompilationHelper.GetSymbol(_compilation.Assembly.Modules.First().GlobalNamespace, "TestName"); + + _namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!); + _typeProvider = new TestTypeProvider(); + } + + [SetUp] + public async Task Setup() + { + await MockHelpers.LoadMockPluginAsync(compilation: () => Task.FromResult(_compilation)); + } + + [Test] + public void ValidateModifiers() + { + var modifiers = _typeProvider.CanonicalView.DeclarationModifiers; + Assert.IsTrue(modifiers.HasFlag(TypeSignatureModifiers.Internal | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Class)); + } + + [Test] + public void ValidateName() + { + Assert.AreEqual(_typeProvider.Name, _typeProvider.CanonicalView.Name); + } + + [Test] + public void ValidateNamespace() + { + Assert.AreEqual(_typeProvider.Namespace, _typeProvider.CanonicalView.Namespace); + } + + [Test] + public void ValidateProperties() + { + Dictionary properties = _typeProvider.CanonicalView.Properties.ToDictionary(p => p.Name); + Assert.AreEqual(5, properties.Count); + Assert.AreEqual(1, _typeProvider.Properties.Count); + Assert.AreEqual(4, _typeProvider.CustomCodeView!.Properties.Count); + foreach (var expected in _namedSymbol.Properties) + { + var actual = properties[expected.Name]; + + Assert.IsTrue(properties.ContainsKey(expected.Name)); + Assert.AreEqual(expected.Name, actual.Name); + Assert.AreEqual($"{expected.Description}.", actual.Description.ToString()); // the writer adds a period + Assert.AreEqual(expected.Modifiers, actual.Modifiers); + Assert.AreEqual(expected.Type, actual.Type); + Assert.AreEqual(expected.Body.GetType(), actual.Body.GetType()); + Assert.AreEqual(expected.Body.HasSetter, actual.Body.HasSetter); + } + } + + + private class TestTypeProvider : TypeProvider + { + protected override string BuildRelativeFilePath() => "NamedSymbol"; + + protected override string BuildName() => "TestName"; + + protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; + + protected override TypeSignatureModifiers GetDeclarationModifiers() => TypeSignatureModifiers.Internal | TypeSignatureModifiers.Partial |TypeSignatureModifiers.Class; + + protected override PropertyProvider[] BuildProperties() + { + return + [ + // customized by the NamedSymbol + new PropertyProvider($"Foo property", MethodSignatureModifiers.Public, typeof(int), "IntProperty", new AutoPropertyBody(true), this, wireInfo: new PropertyWireInformation(SerializationFormat.Default, true, true, true, false, "intProperty")), + // not customized by the NamedSymbol + new PropertyProvider($"Bar property", MethodSignatureModifiers.Public, typeof(string), "SpecProperty", new AutoPropertyBody(false), this, wireInfo: new PropertyWireInformation(SerializationFormat.Default, true, true, true, false, "stringProperty")), + ]; + } + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs index 2d996ebf02..381059663a 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs @@ -4,12 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.Generator.CSharp.Primitives; using Microsoft.Generator.CSharp.Providers; using NUnit.Framework; -using static Microsoft.Generator.CSharp.Snippets.Snippet; namespace Microsoft.Generator.CSharp.Tests.Providers.NamedTypeSymbolProviders { @@ -22,7 +19,7 @@ public NamedTypeSymbolProviderTests() { _namedSymbol = new NamedSymbol(); var compilation = CompilationHelper.LoadCompilation([_namedSymbol, new PropertyType()]); - var iNamedSymbol = GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol"); + var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol"); _namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!); } @@ -89,7 +86,7 @@ public void ValidatePropertyTypes(Type propertyType) var namedSymbol = new NamedSymbol(propertyType); _namedSymbol = namedSymbol; var compilation = CompilationHelper.LoadCompilation([namedSymbol, new PropertyType()]); - var iNamedSymbol = GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol"); + var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol"); _namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!); @@ -191,109 +188,5 @@ public void ValidateFields() Assert.AreEqual(expected.InitializationValue, actual.InitializationValue); } } - - private class NamedSymbol : TypeProvider - { - private readonly Type? _propertyType; - protected override string BuildRelativeFilePath() => "."; - - protected override string BuildName() => "NamedSymbol"; - - protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; - - public NamedSymbol(Type? propertyType = null) : base() - { - _propertyType = propertyType; - } - - protected override FieldProvider[] BuildFields() - { - return - [ - new FieldProvider(FieldModifiers.Public, typeof(int), "IntField", new TestTypeProvider(), $"PublicIntField field"), - new FieldProvider(FieldModifiers.Private, typeof(string), "StringField", new TestTypeProvider(), $"PrivateStringField field no setter"), - new FieldProvider(FieldModifiers.Internal, typeof(double), "DoubleField", new TestTypeProvider(), $"InternalDoubleField field"), - new FieldProvider(FieldModifiers.Public | FieldModifiers.Static, typeof(float), "FloatField", new TestTypeProvider(), $"PublicStaticFloatField field"), - ]; - } - - protected override PropertyProvider[] BuildProperties() - { - if (_propertyType == null) - { - return - [ - new PropertyProvider($"IntProperty property", MethodSignatureModifiers.Public, typeof(int), "IntProperty", new AutoPropertyBody(true), this), - new PropertyProvider($"StringProperty property no setter", MethodSignatureModifiers.Public, typeof(string), "StringProperty", new AutoPropertyBody(false), this), - new PropertyProvider($"InternalStringProperty property no setter", MethodSignatureModifiers.Public, typeof(string), "InternalStringProperty", new AutoPropertyBody(false), this), - new PropertyProvider($"PropertyTypeProperty property", MethodSignatureModifiers.Public, new PropertyType().Type, "PropertyTypeProperty", new AutoPropertyBody(true), this), - ]; - } - - return - [ - new PropertyProvider($"p1", MethodSignatureModifiers.Public, _propertyType, "P1", new AutoPropertyBody(true), this) - ]; - } - - protected override ConstructorProvider[] BuildConstructors() - { - var intParam = new ParameterProvider("intParam", $"intParam", new CSharpType(typeof(int))); - - return - [ - new ConstructorProvider( - new ConstructorSignature(Type, $"Initializes a new instance of {Type}", MethodSignatureModifiers.Public, [intParam]), - Throw(New.Instance(typeof(NotImplementedException))), - this) - ]; - } - - protected override MethodProvider[] BuildMethods() - { - var intParam = new ParameterProvider("intParam", $"intParam", new CSharpType(typeof(int))); - - return - [ - new MethodProvider( - new MethodSignature("Method1", $"Description of method1", MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, typeof(Task), null, [intParam]), - Throw(New.Instance(typeof(NotImplementedException))), - this) - ]; - } - } - - private class PropertyType : TypeProvider - { - protected override PropertyProvider[] BuildProperties() - { - return - [ - new PropertyProvider($"Foo property", MethodSignatureModifiers.Public, typeof(int), "Foo", new AutoPropertyBody(true), this), - ]; - } - - protected override string BuildRelativeFilePath() => "."; - - protected override string BuildName() => "PropertyType"; - } - - internal static INamedTypeSymbol? GetSymbol(INamespaceSymbol namespaceSymbol, string name) - { - foreach (var childNamespaceSymbol in namespaceSymbol.GetNamespaceMembers()) - { - return GetSymbol(childNamespaceSymbol, name); - } - - foreach (INamedTypeSymbol symbol in namespaceSymbol.GetTypeMembers()) - { - if (symbol.MetadataName == name) - { - return symbol; - } - } - - return null; - } } } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestNamedSymbol.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestNamedSymbol.cs new file mode 100644 index 0000000000..4a065909e5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestNamedSymbol.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; +using Microsoft.Generator.CSharp.Snippets; +using static Microsoft.Generator.CSharp.Snippets.Snippet; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class NamedSymbol : TypeProvider + { + private readonly Type? _propertyType; + private readonly string _typeName; + protected override string BuildRelativeFilePath() => "."; + + protected override string BuildName() => _typeName; + + protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; + + public NamedSymbol(Type? propertyType = null, string name = "NamedSymbol") + { + _propertyType = propertyType; + _typeName = name; + } + + protected override FieldProvider[] BuildFields() + { + return + [ + new FieldProvider(FieldModifiers.Public, typeof(int), "IntField", new TestTypeProvider(), + $"PublicIntField field"), + new FieldProvider(FieldModifiers.Private, typeof(string), "StringField", new TestTypeProvider(), + $"PrivateStringField field no setter"), + new FieldProvider(FieldModifiers.Internal, typeof(double), "DoubleField", new TestTypeProvider(), + $"InternalDoubleField field"), + new FieldProvider(FieldModifiers.Public | FieldModifiers.Static, typeof(float), "FloatField", + new TestTypeProvider(), $"PublicStaticFloatField field"), + ]; + } + + protected override PropertyProvider[] BuildProperties() + { + if (_propertyType == null) + { + return + [ + new PropertyProvider($"IntProperty property", MethodSignatureModifiers.Public, typeof(int), + "IntProperty", new AutoPropertyBody(true), this), + new PropertyProvider($"StringProperty property no setter", MethodSignatureModifiers.Public, + typeof(string), "StringProperty", new AutoPropertyBody(false), this), + new PropertyProvider($"InternalStringProperty property no setter", MethodSignatureModifiers.Public, + typeof(string), "InternalStringProperty", new AutoPropertyBody(false), this), + new PropertyProvider($"PropertyTypeProperty property", MethodSignatureModifiers.Public, + new PropertyType().Type, "PropertyTypeProperty", new AutoPropertyBody(true), this), + ]; + } + + return + [ + new PropertyProvider($"p1", MethodSignatureModifiers.Public, _propertyType, "P1", + new AutoPropertyBody(true), this) + ]; + } + + protected override ConstructorProvider[] BuildConstructors() + { + var intParam = new ParameterProvider("intParam", $"intParam", new CSharpType(typeof(int))); + + return + [ + new ConstructorProvider( + new ConstructorSignature(Type, $"Initializes a new instance of {Type}", + MethodSignatureModifiers.Public, [intParam]), + Throw(Snippet.New.Instance(typeof(NotImplementedException))), + this) + ]; + } + + protected override MethodProvider[] BuildMethods() + { + var intParam = new ParameterProvider("intParam", $"intParam", new CSharpType(typeof(int))); + + return + [ + new MethodProvider( + new MethodSignature("Method1", $"Description of method1", + MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual, typeof(Task), null, + [intParam]), + Throw(Snippet.New.Instance(typeof(NotImplementedException))), + this) + ]; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestPropertyType.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestPropertyType.cs new file mode 100644 index 0000000000..52199b9bfe --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/TestHelpers/TestPropertyType.cs @@ -0,0 +1,20 @@ +using Microsoft.Generator.CSharp.Primitives; +using Microsoft.Generator.CSharp.Providers; + +namespace Microsoft.Generator.CSharp.Tests +{ + public class PropertyType : TypeProvider + { + protected override PropertyProvider[] BuildProperties() + { + return + [ + new PropertyProvider($"Foo property", MethodSignatureModifiers.Public, typeof(int), "Foo", new AutoPropertyBody(true), this), + ]; + } + + protected override string BuildRelativeFilePath() => "."; + + protected override string BuildName() => "PropertyType"; + } +} From cec2b389c8189897c143f0408ea280a42ef5dc04 Mon Sep 17 00:00:00 2001 From: Christopher Radek <14189820+chrisradek@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:59:00 -0700 Subject: [PATCH 08/14] adds packages to chronus versioning config (#4730) Co-authored-by: Christopher Radek --- .chronus/config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.chronus/config.yaml b/.chronus/config.yaml index 1aa7cecd08..0db0cc476a 100644 --- a/.chronus/config.yaml +++ b/.chronus/config.yaml @@ -51,6 +51,10 @@ versionPolicies: - "typespec-vs" - "typespec-vscode" - "@typespec/library-linter" + - "@typespec/events" + - "@typespec/sse" + - "@typespec/streams" + - "@typespec/xml" changelog: ["@chronus/github/changelog", { repo: "microsoft/typespec" }] From c215c5a35a00b3109532c78a3583489338a7c2cb Mon Sep 17 00:00:00 2001 From: Jorge Rangel <102122018+jorgerangel-msft@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:03:15 -0500 Subject: [PATCH 09/14] Additional fixes for custom nullable types (#4722) This PR adds some more fixes around namespace and name construction for custom types. Fixes: https://github.com/microsoft/typespec/issues/4635 --- .../ClientProviderCustomizationTests.cs | 4 +-- .../src/Providers/TypeProvider.cs | 2 -- .../src/Utilities/TypeSymbolExtensions.cs | 27 +++++++++++++------ .../ModelProviders/ModelCustomizationTests.cs | 24 +++++++++++++++++ .../MockInputModel.cs | 19 +++++++++++++ .../NamedTypeSymbolProviderTests.cs | 23 +++++++++++++--- 6 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangePropertyTypeToEnum/MockInputModel.cs diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs index ac529cee6f..fe58495d83 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs @@ -152,7 +152,6 @@ public async Task CanReplaceOpMethod() } // Validates that a method with a struct parameter can be replaced - [Test] [TestCase(true)] [TestCase(false)] public async Task CanReplaceStructMethod(bool isStructCustomized) @@ -190,7 +189,8 @@ public async Task CanReplaceStructMethod(bool isStructCustomized) var customMethodParams = customMethods[0].Signature.Parameters; Assert.AreEqual(1, customMethodParams.Count); Assert.AreEqual("p1", customMethodParams[0].Name); - Assert.AreEqual(isStructCustomized ? "Sample.TestClient.MyStruct" : "MyStruct", customMethodParams[0].Type.Name); + Assert.AreEqual("MyStruct", customMethodParams[0].Type.Name); + Assert.AreEqual(isStructCustomized ? "Sample.TestClient" : string.Empty, customMethodParams[0].Type.Namespace); Assert.IsTrue(customMethodParams[0].Type.IsStruct); Assert.IsTrue(customMethodParams[0].Type.IsNullable); diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs index bc472969e9..0f3aa2cc64 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/TypeProvider.cs @@ -532,7 +532,5 @@ private static FileLinePositionSpan GetFileLinePosition(SyntaxReference? syntaxR private IEnumerable GetMemberSuppressionAttributes() => CustomCodeView?.GetAttributes()?.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSuppressAttributeName) ?? []; - private IEnumerable GetCodeGenSerializationAttributes() - => CustomCodeView?.GetAttributes()?.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSerializationAttributeName) ?? []; } } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs index a171c04939..d6c3613e9f 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Utilities/TypeSymbolExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.Generator.CSharp.Primitives; @@ -154,7 +155,7 @@ public static string GetFullyQualifiedNameFromDisplayString(this ISymbol typeSym { // Special case for types that would not be defined in corlib, but should still be considered framework types. "System.BinaryData" => typeof(BinaryData), - _ => System.Type.GetType(fullyQualifiedName) + _ => Type.GetType(fullyQualifiedName) }; } @@ -165,18 +166,28 @@ private static CSharpType ConstructCSharpTypeFromSymbol( { var typeArg = namedTypeSymbol?.TypeArguments.FirstOrDefault(); bool isValueType = typeSymbol.IsValueType; - bool isEnum = typeSymbol.TypeKind == TypeKind.Enum; bool isNullable = fullyQualifiedName.StartsWith(NullableTypeName); + bool isEnum = typeSymbol.TypeKind == TypeKind.Enum || (isNullable && typeArg?.TypeKind == TypeKind.Enum); bool isNullableUnknownType = isNullable && typeArg?.TypeKind == TypeKind.Error; string name = isNullableUnknownType ? fullyQualifiedName : typeSymbol.Name; string[] pieces = fullyQualifiedName.Split('.'); + List arguments = []; + INamedTypeSymbol? namedTypeArg = typeArg as INamedTypeSymbol; + INamedTypeSymbol? enumUnderlyingType = !isNullable ? namedTypeSymbol?.EnumUnderlyingType : namedTypeArg?.EnumUnderlyingType; + + // For nullable types, we need to get the type arguments from the underlying type. + if (namedTypeSymbol?.IsGenericType == true && + (!isNullable || (namedTypeArg?.IsGenericType == true))) + { + arguments.AddRange(namedTypeSymbol.TypeArguments.Select(GetCSharpType)); + } // handle nullables - if (isNullable) + if (isNullable && typeArg != null) { // System.Nullable`1[T] -> T - name = typeArg != null ? GetFullyQualifiedName(typeArg) : fullyQualifiedName; - pieces = name.Split('.'); + name = typeArg.Name; + pieces = GetFullyQualifiedName(typeArg).Split('.'); } return new CSharpType( @@ -185,14 +196,14 @@ private static CSharpType ConstructCSharpTypeFromSymbol( isValueType, isNullable, typeSymbol.ContainingType is not null ? GetCSharpType(typeSymbol.ContainingType) : null, - namedTypeSymbol is not null && !isNullableUnknownType ? [.. namedTypeSymbol.TypeArguments.Select(GetCSharpType)] : [], + arguments, typeSymbol.DeclaredAccessibility == Accessibility.Public, isValueType && !isEnum, baseType: typeSymbol.BaseType is not null && typeSymbol.BaseType.TypeKind != TypeKind.Error && !isNullableUnknownType ? GetCSharpType(typeSymbol.BaseType) : null, - underlyingEnumType: namedTypeSymbol is not null && namedTypeSymbol.EnumUnderlyingType is not null - ? GetCSharpType(namedTypeSymbol.EnumUnderlyingType).FrameworkType + underlyingEnumType: enumUnderlyingType != null + ? GetCSharpType(enumUnderlyingType).FrameworkType : null); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs index 26136558eb..b04ba7b1fa 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -112,6 +112,30 @@ public async Task CanChangePropertyType() Assert.AreEqual(new CSharpType(typeof(int[])), modelTypeProvider.CustomCodeView.Properties[0].Type); } + [Test] + public async Task CanChangePropertyTypeToEnum() + { + var props = new[] + { + InputFactory.Property("Prop1", InputPrimitiveType.String) + }; + + var inputModel = InputFactory.Model("mockInputModel", properties: props); + + var plugin = await MockHelpers.LoadMockPluginAsync( + inputModelTypes: [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelTypeProvider = plugin.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel"); + AssertCommon(modelTypeProvider, "Sample.Models", "MockInputModel"); + + // the property should be added to the custom code view + Assert.AreEqual(1, modelTypeProvider.CustomCodeView!.Properties.Count); + // the property type should be changed + Assert.AreEqual("SomeEnum", modelTypeProvider.CustomCodeView.Properties[0].Type.Name); + Assert.IsTrue(modelTypeProvider.CustomCodeView.Properties[0].Type.IsNullable); + } + [Test] public async Task CanChangePropertyAccessibility() { diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangePropertyTypeToEnum/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangePropertyTypeToEnum/MockInputModel.cs new file mode 100644 index 0000000000..6b90385402 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangePropertyTypeToEnum/MockInputModel.cs @@ -0,0 +1,19 @@ +#nullable disable + +using Sample; +using Microsoft.Generator.CSharp.Customization; + +namespace Sample.Models +{ + public partial class MockInputModel + { + // CUSTOM: Changed type from string. + [CodeGenMember("Prop1")] + public SomeEnum? Prop1 { get; } + } + + public enum SomeEnum + { + Foo, + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs index 381059663a..d99d050684 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/NamedTypeSymbolProviders/NamedTypeSymbolProviderTests.cs @@ -71,6 +71,7 @@ public void ValidateProperties() [TestCase(typeof(IList))] [TestCase(typeof(IList))] [TestCase(typeof(IList))] + [TestCase(typeof(IList))] [TestCase(typeof(ReadOnlyMemory?))] [TestCase(typeof(ReadOnlyMemory))] [TestCase(typeof(ReadOnlyMemory))] @@ -80,7 +81,10 @@ public void ValidateProperties() [TestCase(typeof(string[]))] [TestCase(typeof(IDictionary))] [TestCase(typeof(BinaryData))] - public void ValidatePropertyTypes(Type propertyType) + [TestCase(typeof(SomeEnum), true)] + [TestCase(typeof(SomeEnum?), true)] + [TestCase(typeof(IDictionary))] + public void ValidatePropertyTypes(Type propertyType, bool isEnum = false) { // setup var namedSymbol = new NamedSymbol(propertyType); @@ -95,9 +99,15 @@ public void ValidatePropertyTypes(Type propertyType) var property = _namedTypeSymbolProvider.Properties.FirstOrDefault(); Assert.IsNotNull(property); - bool isNullable = Nullable.GetUnderlyingType(propertyType) != null; - var expectedType = propertyType.FullName!.StartsWith("System") ? new CSharpType(propertyType, isNullable) : - new CSharpType(propertyType.Name, propertyType.Namespace!, false, isNullable, null, [], false, false); + Type? nullableUnderlyingType = Nullable.GetUnderlyingType(propertyType); + var propertyName = nullableUnderlyingType?.Name ?? propertyType.Name; + bool isNullable = nullableUnderlyingType != null; + bool isSystemType = propertyType.FullName!.StartsWith("System") + && (!isNullable || nullableUnderlyingType?.Namespace?.StartsWith("System") == true); + + var expectedType = isSystemType + ? new CSharpType(propertyType, isNullable) + : new CSharpType(propertyName, propertyType.Namespace!, false, isNullable, null, [], false, false); var propertyCSharpType = property!.Type; @@ -188,5 +198,10 @@ public void ValidateFields() Assert.AreEqual(expected.InitializationValue, actual.InitializationValue); } } + + public enum SomeEnum + { + Foo, + } } } From 4e63496411502454b411dcbdbdc98cb6a9e0cead Mon Sep 17 00:00:00 2001 From: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:06:12 -0700 Subject: [PATCH 10/14] Ensure customized properties are included in full constructor (#4734) --- .../src/Providers/CanonicalTypeProvider.cs | 2 +- .../ModelProviders/ModelCustomizationTests.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs index ade5f63349..dfb8c31108 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/CanonicalTypeProvider.cs @@ -37,7 +37,7 @@ public CanonicalTypeProvider(TypeProvider generatedTypeProvider, InputType? inpu protected override PropertyProvider[] BuildProperties() { var specProperties = _inputModel?.Properties ?? []; - var specPropertiesMap = specProperties.ToDictionary(p => p.Name, p => p); + var specPropertiesMap = specProperties.ToDictionary(p => p.Name.ToCleanName(), p => p); var generatedProperties = _generatedTypeProvider.Properties; var customProperties = _generatedTypeProvider.CustomCodeView?.Properties ?? []; diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs index b04ba7b1fa..39dca8e7b7 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -36,7 +36,7 @@ public async Task CanChangePropertyName() { var props = new[] { - InputFactory.Property("Prop1", InputFactory.Array(InputPrimitiveType.String)) + InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String)) }; var inputModel = InputFactory.Model("mockInputModel", properties: props); @@ -57,6 +57,10 @@ public async Task CanChangePropertyName() Assert.AreEqual( "prop1", wireInfo!.SerializedName); Assert.AreEqual(0, modelTypeProvider.Properties.Count); + + var fullCtor = modelTypeProvider.Constructors.Last(); + Assert.IsTrue(fullCtor.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal)); + Assert.AreEqual(2, fullCtor.Signature.Parameters.Count); } [Test] @@ -64,7 +68,7 @@ public async Task CanChangePropertyNameAndRedefineOriginal() { var props = new[] { - InputFactory.Property("Prop1", InputFactory.Array(InputPrimitiveType.String)) + InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String)) }; var inputModel = InputFactory.Model("mockInputModel", properties: props); @@ -94,7 +98,7 @@ public async Task CanChangePropertyType() { var props = new[] { - InputFactory.Property("Prop1", InputFactory.Array(InputPrimitiveType.String)) + InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String)) }; var inputModel = InputFactory.Model("mockInputModel", properties: props); @@ -142,7 +146,7 @@ public async Task CanChangePropertyAccessibility() var plugin = await MockHelpers.LoadMockPluginAsync( inputModelTypes: new[] { InputFactory.Model("mockInputModel", properties: new[] { - InputFactory.Property("Prop1", InputPrimitiveType.String) + InputFactory.Property("prop1", InputPrimitiveType.String) }) }, compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); From 34624a02b4608b7d30f75ecf8678e93e73cdd1f1 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Tue, 15 Oct 2024 09:51:05 +0800 Subject: [PATCH 11/14] [http-client-python] Fix deserialization subtype (#4716) Related SDK diff: https://github.com/Azure/autorest.python/pull/2430/files --- packages/http-client-python/CHANGELOG.md | 6 ++++++ .../pygen/codegen/templates/serialization.py.jinja2 | 3 +-- packages/http-client-python/package-lock.json | 4 ++-- packages/http-client-python/package.json | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/http-client-python/CHANGELOG.md b/packages/http-client-python/CHANGELOG.md index 7b56e9672f..3b96703a9c 100644 --- a/packages/http-client-python/CHANGELOG.md +++ b/packages/http-client-python/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log - @typespec/http-client-python +## 0.3.1 + +### Bug Fixes + +- Avoid change original data when deserialize for polymorphic model + ## 0.3.0 ### Bump dependencies diff --git a/packages/http-client-python/generator/pygen/codegen/templates/serialization.py.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/serialization.py.jinja2 index 9f7c14d0f9..c01773dce0 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/serialization.py.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/serialization.py.jinja2 @@ -506,7 +506,6 @@ class Model(object): def _classify(cls, response, objects): """Check the class _subtype_map for any child classes. We want to ignore any inherited _subtype_maps. - Remove the polymorphic key from the initial data. :param dict response: The initial data :param dict objects: The class objects @@ -518,7 +517,7 @@ class Model(object): if not isinstance(response, ET.Element): rest_api_response_key = cls._get_rest_key_parts(subtype_key)[-1] - subtype_value = response.pop(rest_api_response_key, None) or response.pop(subtype_key, None) + subtype_value = response.get(rest_api_response_key, None) or response.get(subtype_key, None) else: subtype_value = xml_key_extractor(subtype_key, cls._attribute_map[subtype_key], response) if subtype_value: diff --git a/packages/http-client-python/package-lock.json b/packages/http-client-python/package-lock.json index fbd94259a7..51dd110ce6 100644 --- a/packages/http-client-python/package-lock.json +++ b/packages/http-client-python/package-lock.json @@ -1,12 +1,12 @@ { "name": "@typespec/http-client-python", - "version": "0.3.0", + "version": "0.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@typespec/http-client-python", - "version": "0.3.0", + "version": "0.3.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/http-client-python/package.json b/packages/http-client-python/package.json index 96ad2b68f3..e679a11886 100644 --- a/packages/http-client-python/package.json +++ b/packages/http-client-python/package.json @@ -1,6 +1,6 @@ { "name": "@typespec/http-client-python", - "version": "0.3.0", + "version": "0.3.1", "author": "Microsoft Corporation", "description": "TypeSpec emitter for Python SDKs", "homepage": "https://typespec.io", From 0487edc37274fce27a92b8e796ea26c3cfcc7bb4 Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:03:54 -0400 Subject: [PATCH 12/14] remove postprocess script (#4718) Co-authored-by: iscai-msft Co-authored-by: Chenjie Shi --- packages/http-client-python/CHANGELOG.md | 6 + .../pygen/codegen/templates/init.py.jinja2 | 2 +- .../pygen/codegen/templates/keywords.jinja2 | 8 + .../codegen/templates/model_init.py.jinja2 | 1 + .../operations_folder_init.py.jinja2 | 2 +- .../generator/pygen/postprocess/__init__.py | 183 ------------------ .../generator/pygen/postprocess/get_all.py | 19 -- .../generator/pygen/postprocess/venvtools.py | 75 ------- 8 files changed, 17 insertions(+), 279 deletions(-) delete mode 100644 packages/http-client-python/generator/pygen/postprocess/__init__.py delete mode 100644 packages/http-client-python/generator/pygen/postprocess/get_all.py delete mode 100644 packages/http-client-python/generator/pygen/postprocess/venvtools.py diff --git a/packages/http-client-python/CHANGELOG.md b/packages/http-client-python/CHANGELOG.md index 3b96703a9c..5345a8d481 100644 --- a/packages/http-client-python/CHANGELOG.md +++ b/packages/http-client-python/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log - @typespec/http-client-python +## 0.3.2 + +### Bug Fixes + +- Update generated code so there is no need to run the `postprocess` script when customizations are made #4718 + ## 0.3.1 ### Bug Fixes diff --git a/packages/http-client-python/generator/pygen/codegen/templates/init.py.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/init.py.jinja2 index 9f70f40bd6..815e59aef5 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/init.py.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/init.py.jinja2 @@ -1,7 +1,7 @@ {% import 'keywords.jinja2' as keywords %} # coding=utf-8 {{ code_model.options['license_header'] }} - +{{ keywords.path_type_checking_imports() }} {% if clients %} {% for client in clients %} from .{{ client.filename }} import {{ client.name }} diff --git a/packages/http-client-python/generator/pygen/codegen/templates/keywords.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/keywords.jinja2 index 0c5916fe68..abf427748e 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/keywords.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/keywords.jinja2 @@ -17,3 +17,11 @@ except ImportError: _patch_all = [] {% endif %} from ._patch import patch_sdk as _patch_sdk{% endmacro %} +{% macro path_type_checking_imports() %} +# pylint: disable=wrong-import-position + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import +{% endmacro %} diff --git a/packages/http-client-python/generator/pygen/codegen/templates/model_init.py.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/model_init.py.jinja2 index f471068084..34f0ef86de 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/model_init.py.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/model_init.py.jinja2 @@ -1,6 +1,7 @@ {% import 'keywords.jinja2' as keywords %} # coding=utf-8 {{ code_model.options['license_header'] }} +{{ keywords.path_type_checking_imports() }} {% if schemas %} {% for schema in schemas %} diff --git a/packages/http-client-python/generator/pygen/codegen/templates/operations_folder_init.py.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/operations_folder_init.py.jinja2 index bb38196f4a..e4ad6e4368 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/operations_folder_init.py.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/operations_folder_init.py.jinja2 @@ -3,7 +3,7 @@ {# actual template starts here #} # coding=utf-8 {{ code_model.options['license_header'] }} - +{{ keywords.path_type_checking_imports() }} {{ op_tools.serialize(operation_group_imports()) }} {{ keywords.patch_imports() }} __all__ = [ diff --git a/packages/http-client-python/generator/pygen/postprocess/__init__.py b/packages/http-client-python/generator/pygen/postprocess/__init__.py deleted file mode 100644 index 8e9439de64..0000000000 --- a/packages/http-client-python/generator/pygen/postprocess/__init__.py +++ /dev/null @@ -1,183 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -from typing import Tuple, Any -from pathlib import Path -import os -import shutil -from venv import EnvBuilder -import black -from black.report import NothingChanged -from .venvtools import ExtendedEnvBuilder, python_run - -from .. import Plugin - -_BLACK_MODE = black.Mode() # pyright: ignore [reportPrivateImportUsage] -_BLACK_MODE.line_length = 120 - - -def format_file(file: Path, file_content: str) -> str: - if not file.suffix == ".py": - return file_content - try: - file_content = black.format_file_contents(file_content, fast=True, mode=_BLACK_MODE) - except NothingChanged: - pass - return file_content - - -class PostProcessPlugin(Plugin): - def __init__(self, **kwargs: Any): - super().__init__(**kwargs) - output_folder_uri = self.options["outputFolderUri"] - if output_folder_uri.startswith("file:"): - output_folder_uri = output_folder_uri[5:] - if os.name == "nt" and output_folder_uri.startswith("///"): - output_folder_uri = output_folder_uri[3:] - self.output_folder = Path(output_folder_uri) # path to where the setup.py is - self.setup_venv() - - # set up the venv - # base folder is where the code starts, i.e. where we - self.base_folder, self.namespace = self.get_namespace(self.output_folder, "") - - def setup_venv(self): - venv_path = self.output_folder / Path(".temp_folder") / Path("temp_venv") - - if venv_path.exists(): - env_builder = EnvBuilder(with_pip=True) - self.venv_context = env_builder.ensure_directories(venv_path) - else: - env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True) - env_builder.create(venv_path) - self.venv_context = env_builder.context - python_run( - self.venv_context, - "pip", - ["install", "-e", str(self.output_folder)], - directory=self.output_folder, - ) - - def get_namespace(self, dir: Path, namespace: str) -> Tuple[Path, str]: - try: - init_file = next(d for d in dir.iterdir() if d.name == "__init__.py") - # we don't care about pkgutil inits, we skip over them - file_content = self.read_file(init_file.relative_to(self.output_folder)) - if "pkgutil" not in file_content: - return dir, namespace - except StopIteration: - pass - - try: - # first, see if we can get a folder that has the same name as the current output folder - start = self.output_folder.stem.split("-")[0] - next_dir = next(d for d in dir.iterdir() if d.is_dir() and d.name == start) - except StopIteration: - invalid_start_chars = [".", "_"] - invalid_dirs = [ - "swagger", - "out", - "tests", - "samples", - ] - - next_dir = next( - d - for d in dir.iterdir() - if d.is_dir() - and not str(d).endswith("egg-info") - and d.name[0] not in invalid_start_chars - and d.name not in invalid_dirs - ) - - namespace = f"{namespace}.{next_dir.name}" if namespace else next_dir.name - return self.get_namespace(next_dir, namespace) - - def process(self) -> bool: - folders = [f for f in self.base_folder.glob("**/*") if f.is_dir() and not f.stem.startswith("__")] - # will always have the root - self.fix_imports_in_init( - generated_file_name="_client", - folder_path=self.base_folder, - namespace=self.namespace, - ) - try: - aio_folder = next(f for f in folders if f.stem == "aio") - self.fix_imports_in_init( - generated_file_name="_client", - folder_path=aio_folder, - namespace=f"{self.namespace}.aio", - ) - except StopIteration: - pass - - try: - models_folder = next(f for f in folders if f.stem == "models") - self.fix_imports_in_init( - generated_file_name="_models", - folder_path=models_folder, - namespace=f"{self.namespace}.models", - ) - except StopIteration: - pass - operations_folders = [f for f in folders if f.stem in ["operations", "_operations"]] - for operations_folder in operations_folders: - sub_namespace = ".".join(str(operations_folder.relative_to(self.base_folder)).split(os.sep)) - self.fix_imports_in_init( - generated_file_name="_operations", - folder_path=operations_folder, - namespace=f"{self.namespace}.{sub_namespace}", - ) - shutil.rmtree(f"{str(self.output_folder)}/.temp_folder") - return True - - def fix_imports_in_init(self, generated_file_name: str, folder_path: Path, namespace: str) -> None: - customized_objects_str = python_run( - self.venv_context, - command=[namespace, str(self.output_folder)], - module="get_all", - ) - - if not customized_objects_str: - return - customized_objects = {k: None for k in customized_objects_str.split(",")}.keys() # filter out duplicates - file = (folder_path / "__init__.py").relative_to(self.output_folder) - file_content = self.read_file(file).replace("\r\n", "\n") - added_objs = [] - for obj in customized_objects: - if f" import {obj}\n" in file_content: - # means we're overriding a generated model - file_content = file_content.replace( - f"from .{generated_file_name} import {obj}\n", - f"from ._patch import {obj}\n", - ) - else: - added_objs.append(obj) - file_content = file_content.replace( - "try:\n from ._patch import __all__ as _patch_all\n " - "from ._patch import * # pylint: disable=unused-wildcard-import" - "\nexcept ImportError:\n _patch_all = []", - "", - ) - file_content = file_content.replace("from ._patch import __all__ as _patch_all", "") - file_content = file_content.replace( - "from ._patch import * # pylint: disable=unused-wildcard-import\n", - "", - ) - file_content = file_content.replace("__all__.extend([p for p in _patch_all if p not in __all__])", "") - if added_objs: - # add import - patch_sdk_import = "from ._patch import patch_sdk as _patch_sdk" - imports = "\n".join([f"from ._patch import {obj}" for obj in added_objs]) - if imports: - replacement = f"{imports}\n{patch_sdk_import}" - else: - replacement = patch_sdk_import - file_content = file_content.replace(patch_sdk_import, replacement) - # add to __all__ - added_objs_all = "\n".join([f' "{obj}",' for obj in added_objs]) + "\n" - file_content = file_content.replace("__all__ = [", f"__all__ = [\n{added_objs_all}", 1) - formatted_file = format_file(file, file_content) - self.write_file(file, formatted_file) diff --git a/packages/http-client-python/generator/pygen/postprocess/get_all.py b/packages/http-client-python/generator/pygen/postprocess/get_all.py deleted file mode 100644 index 4206e36a9c..0000000000 --- a/packages/http-client-python/generator/pygen/postprocess/get_all.py +++ /dev/null @@ -1,19 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -import sys -import importlib - - -def main(namespace): - sdk = importlib.import_module(namespace) - return sdk._patch.__all__ # pylint: disable=protected-access - - -if __name__ == "__main__": - patched = ",".join(main(sys.argv[1])) - output_folder = sys.argv[2] - with open(f"{output_folder}/.temp_folder/patched.txt", "w", encoding="utf-8-sig") as f: - f.write(patched) diff --git a/packages/http-client-python/generator/pygen/postprocess/venvtools.py b/packages/http-client-python/generator/pygen/postprocess/venvtools.py deleted file mode 100644 index ef286bf085..0000000000 --- a/packages/http-client-python/generator/pygen/postprocess/venvtools.py +++ /dev/null @@ -1,75 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -from typing import Optional -import subprocess -import venv -import sys -from pathlib import Path - - -_ROOT_DIR = Path(__file__).parent - - -class ExtendedEnvBuilder(venv.EnvBuilder): - """An extended env builder which saves the context, to have access - easily to bin path and such. - """ - - def __init__(self, *args, **kwargs): - self.context = None - if sys.version_info < (3, 9, 0): - # Not supported on Python 3.8, and we don't need it - kwargs.pop("upgrade_deps", None) - super().__init__(*args, **kwargs) - - def ensure_directories(self, env_dir): - self.context = super(ExtendedEnvBuilder, self).ensure_directories(env_dir) - return self.context - - -def create( - env_dir, - system_site_packages=False, - clear=False, - symlinks=False, - with_pip=False, - prompt=None, - upgrade_deps=False, -): - """Create a virtual environment in a directory.""" - builder = ExtendedEnvBuilder( - system_site_packages=system_site_packages, - clear=clear, - symlinks=symlinks, - with_pip=with_pip, - prompt=prompt, - upgrade_deps=upgrade_deps, - ) - builder.create(env_dir) - return builder.context - - -def python_run(venv_context, module, command, directory=_ROOT_DIR) -> Optional[str]: - try: - cmd_line = [ - venv_context.env_exe, - "-m", - module, - ] + command - print("Executing: {}".format(" ".join(cmd_line))) - subprocess.run( - cmd_line, - cwd=directory, - check=True, - stdout=False, - ) - if module == "get_all": - with open(f"{command[1]}/.temp_folder/patched.txt", "r", encoding="utf-8-sig") as f: - return f.read() - except subprocess.CalledProcessError as err: - print(err) - sys.exit(1) - return None From 657d4a0f1c86acb2a0d31b51aa1d128d2a4d6571 Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Wed, 16 Oct 2024 00:28:30 +0800 Subject: [PATCH 13/14] specs, fix mockapi in auth scenarios (#4740) for invalid case, test is considered pass on 403 response code. --- packages/http-specs/specs/authentication/api-key/mockapi.ts | 4 ++-- .../http-specs/specs/authentication/http/custom/mockapi.ts | 6 +++--- packages/http-specs/specs/authentication/oauth2/mockapi.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/http-specs/specs/authentication/api-key/mockapi.ts b/packages/http-specs/specs/authentication/api-key/mockapi.ts index 2d8bfd8073..34cc3972ee 100644 --- a/packages/http-specs/specs/authentication/api-key/mockapi.ts +++ b/packages/http-specs/specs/authentication/api-key/mockapi.ts @@ -1,8 +1,8 @@ -import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnCode, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; -Scenarios.Authentication_ApiKey_invalid = passOnSuccess({ +Scenarios.Authentication_ApiKey_invalid = passOnCode(403, { uri: `/authentication/api-key/invalid`, method: `get`, request: { diff --git a/packages/http-specs/specs/authentication/http/custom/mockapi.ts b/packages/http-specs/specs/authentication/http/custom/mockapi.ts index 9ebb14b972..1d9e4e5ac0 100644 --- a/packages/http-specs/specs/authentication/http/custom/mockapi.ts +++ b/packages/http-specs/specs/authentication/http/custom/mockapi.ts @@ -1,4 +1,4 @@ -import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnCode, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -16,12 +16,12 @@ Scenarios.Authentication_Http_Custom_valid = passOnSuccess({ kind: "MockApiDefinition", }); -Scenarios.Authentication_Http_Custom_invalid = passOnSuccess({ +Scenarios.Authentication_Http_Custom_invalid = passOnCode(403, { uri: `/authentication/http/custom/invalid`, method: "get", request: { headers: { - authorization: "SharedAccessKey valid-key", + authorization: "SharedAccessKey invalid-key", }, status: 403, }, diff --git a/packages/http-specs/specs/authentication/oauth2/mockapi.ts b/packages/http-specs/specs/authentication/oauth2/mockapi.ts index 85fcaf576c..3911a1e37d 100644 --- a/packages/http-specs/specs/authentication/oauth2/mockapi.ts +++ b/packages/http-specs/specs/authentication/oauth2/mockapi.ts @@ -1,4 +1,4 @@ -import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; +import { json, passOnCode, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; export const Scenarios: Record = {}; @@ -16,7 +16,7 @@ Scenarios.Authentication_OAuth2_valid = passOnSuccess({ kind: "MockApiDefinition", }); -Scenarios.Authentication_OAuth2_invalid = passOnSuccess({ +Scenarios.Authentication_OAuth2_invalid = passOnCode(403, { uri: `/authentication/oauth2/invalid`, method: "get", request: { From e9a65ca53d57c7c6e480cf1489355a71772df9dd Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Tue, 15 Oct 2024 12:12:26 -0700 Subject: [PATCH 14/14] Configure api extractor for json schema and add docs and refactor (#4732) Configure api extractor and add docs. Additionally refactored to use some newer pattern of decorator declaration. --- ...schema-api-extractor-2024-9-14-22-42-24.md | 8 + packages/json-schema/api-extractor.json | 4 + packages/json-schema/package.json | 5 +- packages/json-schema/src/decorators.ts | 365 +++++++++--------- packages/json-schema/src/index.ts | 64 ++- .../json-schema/src/json-schema-emitter.ts | 54 +-- packages/json-schema/src/lib.ts | 62 ++- packages/json-schema/src/on-emit.ts | 11 +- packages/json-schema/src/testing/index.ts | 2 +- packages/json-schema/src/tsp-index.ts | 4 +- packages/json-schema/src/utils.ts | 17 + packages/json-schema/test/extension.test.ts | 2 +- packages/json-schema/test/utils.ts | 4 +- packages/json-schema/tsconfig.json | 3 +- 14 files changed, 375 insertions(+), 230 deletions(-) create mode 100644 .chronus/changes/json-schema-api-extractor-2024-9-14-22-42-24.md create mode 100644 packages/json-schema/api-extractor.json create mode 100644 packages/json-schema/src/utils.ts diff --git a/.chronus/changes/json-schema-api-extractor-2024-9-14-22-42-24.md b/.chronus/changes/json-schema-api-extractor-2024-9-14-22-42-24.md new file mode 100644 index 0000000000..b598c64712 --- /dev/null +++ b/.chronus/changes/json-schema-api-extractor-2024-9-14-22-42-24.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - "@typespec/json-schema" +--- + +Document exported types diff --git a/packages/json-schema/api-extractor.json b/packages/json-schema/api-extractor.json new file mode 100644 index 0000000000..2069b8ac37 --- /dev/null +++ b/packages/json-schema/api-extractor.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "../../api-extractor.base.json" +} diff --git a/packages/json-schema/package.json b/packages/json-schema/package.json index 4d5eaeb5f6..277e7d288b 100644 --- a/packages/json-schema/package.json +++ b/packages/json-schema/package.json @@ -36,7 +36,7 @@ }, "scripts": { "clean": "rimraf ./dist ./temp", - "build": "npm run gen-extern-signature && tsc -p . && npm run lint-typespec-library", + "build": "npm run gen-extern-signature && tsc -p . && npm run lint-typespec-library && pnpm api-extractor", "watch": "tsc -p . --watch", "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .", "lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit", @@ -45,7 +45,8 @@ "test:ci": "vitest run --coverage --reporter=junit --reporter=default", "lint": "eslint . --max-warnings=0", "lint:fix": "eslint . --fix", - "regen-docs": "tspd doc . --enable-experimental --output-dir ../../docs/emitters/json-schema/reference" + "regen-docs": "tspd doc . --enable-experimental --output-dir ../../docs/emitters/json-schema/reference", + "api-extractor": "api-extractor run --local --verbose" }, "files": [ "lib/*.tsp", diff --git a/packages/json-schema/src/decorators.ts b/packages/json-schema/src/decorators.ts index cb132efaec..df2c93bf65 100644 --- a/packages/json-schema/src/decorators.ts +++ b/packages/json-schema/src/decorators.ts @@ -1,22 +1,20 @@ import { - DecoratorContext, - Enum, - Model, - ModelProperty, - Namespace, - Numeric, - Program, - Scalar, - Tuple, - Type, - Union, + type DecoratorContext, + type Enum, isType, + type Model, + type Namespace, + type Program, + type Scalar, setTypeSpecNamespace, + type Tuple, + type Type, typespecTypeToJson, + type Union, } from "@typespec/compiler"; -import { ValidatesRawJsonDecorator } from "../generated-defs/TypeSpec.JsonSchema.Private.js"; -import { - BaseUriDecorator, +import { unsafe_useStateMap, unsafe_useStateSet } from "@typespec/compiler/experimental"; +import type { ValidatesRawJsonDecorator } from "../generated-defs/TypeSpec.JsonSchema.Private.js"; +import type { ContainsDecorator, ContentEncodingDecorator, ContentMediaTypeDecorator, @@ -33,18 +31,26 @@ import { PrefixItemsDecorator, UniqueItemsDecorator, } from "../generated-defs/TypeSpec.JsonSchema.js"; -import { createStateSymbol } from "./lib.js"; +import { JsonSchemaStateKeys } from "./lib.js"; +import { createDataDecorator } from "./utils.js"; +/** + * TypeSpec Types that can create a json schmea declaration + */ export type JsonSchemaDeclaration = Model | Union | Enum | Scalar; -const jsonSchemaKey = createStateSymbol("JsonSchema"); - +export const [ + /** Check if the given type is annotated with `@jsonSchema` */ + getJsonSchema, + markJsonSchema, +] = unsafe_useStateSet(JsonSchemaStateKeys.JsonSchema); +/** {@inheritdoc JsonSchemaDecorator} */ export const $jsonSchema: JsonSchemaDecorator = ( context: DecoratorContext, target: Type, baseUriOrId?: string, ) => { - context.program.stateSet(jsonSchemaKey).add(target); + markJsonSchema(context.program, target); if (baseUriOrId) { if (target.kind === "Namespace") { context.call($baseUri, target, baseUriOrId); @@ -54,19 +60,15 @@ export const $jsonSchema: JsonSchemaDecorator = ( } }; -const baseUriKey = createStateSymbol("JsonSchema.baseURI"); -export const $baseUri: BaseUriDecorator = ( - context: DecoratorContext, - target: Namespace, - baseUri: string, -) => { - context.program.stateMap(baseUriKey).set(target, baseUri); -}; - -export function getBaseUri(program: Program, target: Type) { - return program.stateMap(baseUriKey).get(target); -} +export const [ + /** Get base uri set via `@baseUri` decorator */ + getBaseUri, + setBaseUri, + /** {@inheritdoc BaseUriDecorator} */ + $baseUri, +] = createDataDecorator(JsonSchemaStateKeys["JsonSchema.baseURI"]); +/** Find base uri for the given type. */ export function findBaseUri( program: Program, target: JsonSchemaDeclaration | Namespace, @@ -81,7 +83,12 @@ export function findBaseUri( return baseUrl; } -export function isJsonSchemaDeclaration(program: Program, target: JsonSchemaDeclaration) { +/** + * Check if the given type is annoted with `@jsonSchema` or within a container annoted with `@jsonSchema`. + * @param program TypeSpec program + * @param target Type + */ +export function isJsonSchemaDeclaration(program: Program, target: JsonSchemaDeclaration): boolean { let current: JsonSchemaDeclaration | Namespace | undefined = target; do { if (getJsonSchema(program, current)) { @@ -94,180 +101,157 @@ export function isJsonSchemaDeclaration(program: Program, target: JsonSchemaDecl return false; } +/** + * Returns types that are annotated with `@jsonSchema` or contained within a namespace that is annoted with `@jsonSchema`. + * @param program TypeSpec program + */ export function getJsonSchemaTypes(program: Program): (Namespace | Model)[] { - return [...(program.stateSet(jsonSchemaKey) || [])] as (Namespace | Model)[]; + return [...(program.stateSet(JsonSchemaStateKeys.JsonSchema) || [])] as (Namespace | Model)[]; } -export function getJsonSchema(program: Program, target: Type) { - return program.stateSet(jsonSchemaKey).has(target); -} +export const [ + /** Get value set by `@multipleOf` decorator as a `Numeric` type. */ + getMultipleOfAsNumeric, + setMultipleOf, + /** {@inheritdoc MultipleOfDecorator} */ -const multipleOfKey = createStateSymbol("JsonSchema.multipleOf"); -export const $multipleOf: MultipleOfDecorator = ( - context: DecoratorContext, - target: Scalar | ModelProperty, - value: Numeric, -) => { - context.program.stateMap(multipleOfKey).set(target, value); -}; + $multipleOf, +] = createDataDecorator(JsonSchemaStateKeys["JsonSchema.multipleOf"]); -export function getMultipleOfAsNumeric(program: Program, target: Type): Numeric | undefined { - return program.stateMap(multipleOfKey).get(target); -} +/** Get value set by `@multipleOf` decorator as a `number` type. If the value is not representable as a number or not set, returns undefined. */ export function getMultipleOf(program: Program, target: Type): number | undefined { return getMultipleOfAsNumeric(program, target)?.asNumber() ?? undefined; } -const idKey = createStateSymbol("JsonSchema.id"); -export const $id: IdDecorator = (context: DecoratorContext, target: Type, value: string) => { - context.program.stateMap(idKey).set(target, value); -}; - -export function getId(program: Program, target: Type) { - return program.stateMap(idKey).get(target); -} - -const oneOfKey = createStateSymbol("JsonSchema.oneOf"); +export const [ + /** Get id as set with `@id` decorator. */ + getId, + setId, + /** {@inheritdoc IdDecorator} */ + $id, +] = createDataDecorator(JsonSchemaStateKeys["JsonSchema.id"]); + +export const [ + /** Check if given type is annotated with `@oneOf` decorator */ + isOneOf, + markOneOf, +] = unsafe_useStateSet(JsonSchemaStateKeys["JsonSchema.oneOf"]); + +/** {@inheritdoc OneOfDecorator} */ export const $oneOf: OneOfDecorator = (context: DecoratorContext, target: Type) => { - context.program.stateMap(oneOfKey).set(target, true); -}; - -export function isOneOf(program: Program, target: Type) { - return program.stateMap(oneOfKey).has(target); -} - -const containsKey = createStateSymbol("JsonSchema.contains"); -export const $contains: ContainsDecorator = ( - context: DecoratorContext, - target: Type, - value: Type, -) => { - context.program.stateMap(containsKey).set(target, value); -}; - -export function getContains(program: Program, target: Type) { - return program.stateMap(containsKey).get(target); -} - -const minContainsKey = createStateSymbol("JsonSchema.minContains"); -export const $minContains: MinContainsDecorator = ( - context: DecoratorContext, - target: Type, - value: number, -) => { - context.program.stateMap(minContainsKey).set(target, value); + markOneOf(context.program, target); }; -export function getMinContains(program: Program, target: Type) { - return program.stateMap(minContainsKey).get(target); -} - -const maxContainsKey = createStateSymbol("JsonSchema.maxContains"); -export const $maxContains: MaxContainsDecorator = ( - context: DecoratorContext, - target: Type, - value: number, -) => { - context.program.stateMap(maxContainsKey).set(target, value); -}; - -export function getMaxContains(program: Program, target: Type) { - return program.stateMap(maxContainsKey).get(target); -} - -const uniqueItemsKey = createStateSymbol("JsonSchema.uniqueItems"); -export const $uniqueItems: UniqueItemsDecorator = (context: DecoratorContext, target: Type) => { - context.program.stateMap(uniqueItemsKey).set(target, true); -}; - -export function getUniqueItems(program: Program, target: Type) { - return program.stateMap(uniqueItemsKey).get(target); -} - -const minPropertiesKey = createStateSymbol("JsonSchema.minProperties"); -export const $minProperties: MinPropertiesDecorator = ( - context: DecoratorContext, - target: Type, - value: number, -) => { - context.program.stateMap(minPropertiesKey).set(target, value); -}; - -export function getMinProperties(program: Program, target: Type) { - return program.stateMap(minPropertiesKey).get(target); -} - -const maxPropertiesKey = createStateSymbol("JsonSchema.maxProperties"); -export const $maxProperties: MaxPropertiesDecorator = ( - context: DecoratorContext, - target: Type, - value: number, -) => { - context.program.stateMap(maxPropertiesKey).set(target, value); -}; - -export function getMaxProperties(program: Program, target: Type) { - return program.stateMap(maxPropertiesKey).get(target); -} - -const contentEncodingKey = createStateSymbol("JsonSchema.contentEncoding"); -export const $contentEncoding: ContentEncodingDecorator = ( - context: DecoratorContext, - target: Scalar | ModelProperty, - value: string, -) => { - context.program.stateMap(contentEncodingKey).set(target, value); -}; - -export function getContentEncoding(program: Program, target: Type): string { - return program.stateMap(contentEncodingKey).get(target); -} - -const contentMediaType = createStateSymbol("JsonSchema.contentMediaType"); -export const $contentMediaType: ContentMediaTypeDecorator = ( - context: DecoratorContext, - target: Scalar | ModelProperty, - value: string, -) => { - context.program.stateMap(contentMediaType).set(target, value); -}; - -export function getContentMediaType(program: Program, target: Type): string { - return program.stateMap(contentMediaType).get(target); -} - -const contentSchemaKey = createStateSymbol("JsonSchema.contentSchema"); -export const $contentSchema: ContentSchemaDecorator = ( - context: DecoratorContext, - target: Scalar | ModelProperty, - value: Type, -) => { - context.program.stateMap(contentSchemaKey).set(target, value); -}; - -export function getContentSchema(program: Program, target: Type) { - return program.stateMap(contentSchemaKey).get(target); -} - -const prefixItemsKey = createStateSymbol("JsonSchema.prefixItems"); +export const [ + /** Get contains value set by `@contains` decorator */ + getContains, + setContains, + /** {@inheritdoc ContainsDecorator} */ + $contains, +] = createDataDecorator(JsonSchemaStateKeys["JsonSchema.contains"]); + +export const [ + /** Get value set by `@minContains` decorator */ + getMinContains, + setMinContains, + /** {@inheritdoc MinContainsDecorator} */ + $minContains, +] = createDataDecorator(JsonSchemaStateKeys["JsonSchema.minContains"]); + +export const [ + /** Get value set by `@maxContains` decorator */ + getMaxContains, + setMaxContains, + /** {@inheritdoc MaxContainsDecorator} */ + $maxContains, +] = createDataDecorator(JsonSchemaStateKeys["JsonSchema.maxContains"]); + +export const [ + /** Check if the given array is annotated with `@uniqueItems` decorator */ + getUniqueItems, + setUniqueItems, +] = unsafe_useStateMap(JsonSchemaStateKeys["JsonSchema.uniqueItems"]); +/** {@inheritdoc UniqueItemsDecorator} */ +export const $uniqueItems: UniqueItemsDecorator = (context: DecoratorContext, target: Type) => + setUniqueItems(context.program, target, true); + +export const [ + /** Get minimum number of properties set by `@minProperties` decorator */ + getMinProperties, + setMinProperties, + /** {@inheritdoc MinPropertiesDecorator} */ + $minProperties, +] = createDataDecorator(JsonSchemaStateKeys["JsonSchema.minProperties"]); + +export const [ + /** Get maximum number of properties set by `@maxProperties` decorator */ + + getMaxProperties, + setMaxProperties, + /** {@inheritdoc MaxPropertiesDecorator} */ + $maxProperties, +] = createDataDecorator(JsonSchemaStateKeys["JsonSchema.maxProperties"]); + +export const [ + /** Get content encoding as configured by `@contentEncoding` decorator. */ + getContentEncoding, + setContentEncoding, + /** {@inheritdoc ContentEncodingDecorator} */ + $contentEncoding, +] = createDataDecorator( + JsonSchemaStateKeys["JsonSchema.contentEncoding"], +); + +export const [ + /** Get content media type as configured by `@contentMediaType` decorator. */ + getContentMediaType, + setContentMediaType, + /** {@inheritdoc ContentMediaTypeDecorator} */ + $contentMediaType, +] = createDataDecorator( + JsonSchemaStateKeys["JsonSchema.contentMediaType"], +); + +export const [ + /** Get content schema set with `@contentSchema` decorator */ + getContentSchema, + setContentSchema, + /** {@inheritdoc ContentSchemaDecorator} */ + $contentSchema, +] = createDataDecorator( + JsonSchemaStateKeys["JsonSchema.contentSchema"], +); + +export const [ + /** Get prefix items set with `@prefixItems` decorator */ + getPrefixItems, + setPrefixItems, +] = unsafe_useStateMap(JsonSchemaStateKeys["JsonSchema.prefixItems"]); + +/** {@inheritdoc PrefixItemsDecorator} */ export const $prefixItems: PrefixItemsDecorator = ( context: DecoratorContext, target: Type, value: Type, ) => { - context.program.stateMap(prefixItemsKey).set(target, value); + setPrefixItems(context.program, target, value as Tuple); // This cast is incorrect and would cause a crash https://github.com/microsoft/typespec/issues/4742 }; -export function getPrefixItems(program: Program, target: Type): Tuple | undefined { - return program.stateMap(prefixItemsKey).get(target); -} - +/** + * Data type containing information about an extension. + */ export interface ExtensionRecord { + /** Extension key */ key: string; + /** Extension value */ value: Type | unknown; } -const extensionsKey = createStateSymbol("JsonSchema.extension"); +const [getExtensionsInternal, _, getExtensionsStateMap] = unsafe_useStateMap< + Type, + ExtensionRecord[] +>(JsonSchemaStateKeys["JsonSchema.extension"]); +/** {@inheritdoc ExtensionDecorator} */ export const $extension: ExtensionDecorator = ( context: DecoratorContext, target: Type, @@ -277,12 +261,24 @@ export const $extension: ExtensionDecorator = ( setExtension(context.program, target, key, value); }; +/** + * Get extensions set via the `@extension` decorator on the given type + * @param program TypeSpec program + * @param target Type + */ export function getExtensions(program: Program, target: Type): ExtensionRecord[] { - return program.stateMap(extensionsKey).get(target) ?? []; + return getExtensionsInternal(program, target) ?? []; } - +/** + * Set extension on the given type(Same as calling `@extension` decorator) + * @param program TypeSpec program + * @param target Type + * @param key Extension key + * @param value Extension value + */ export function setExtension(program: Program, target: Type, key: string, value: unknown) { - const stateMap = program.stateMap(extensionsKey) as Map; + const stateMap = getExtensionsStateMap(program); + const extensions = stateMap.has(target) ? stateMap.get(target)! : stateMap.set(target, []).get(target)!; @@ -311,6 +307,7 @@ function isJsonTemplateType( ); } +/** @internal */ export const $validatesRawJson: ValidatesRawJsonDecorator = ( context: DecoratorContext, target: Model, diff --git a/packages/json-schema/src/index.ts b/packages/json-schema/src/index.ts index ac6a098380..bd181361ae 100644 --- a/packages/json-schema/src/index.ts +++ b/packages/json-schema/src/index.ts @@ -1,10 +1,70 @@ +export type { + BaseUriDecorator, + ContainsDecorator, + ContentEncodingDecorator, + ContentMediaTypeDecorator, + ContentSchemaDecorator, + ExtensionDecorator, + IdDecorator, + JsonSchemaDecorator, + MaxContainsDecorator, + MaxPropertiesDecorator, + MinContainsDecorator, + MinPropertiesDecorator, + MultipleOfDecorator, + OneOfDecorator, + PrefixItemsDecorator, + UniqueItemsDecorator, +} from "../generated-defs/TypeSpec.JsonSchema.js"; + +/** @internal */ export { JsonSchemaEmitter } from "./json-schema-emitter.js"; -export { $flags, $lib, EmitterOptionsSchema, JSONSchemaEmitterOptions } from "./lib.js"; +export { $flags, $lib, EmitterOptionsSchema } from "./lib.js"; +export type { JSONSchemaEmitterOptions } from "./lib.js"; /** @internal */ export const namespace = "TypeSpec.JsonSchema"; -export * from "./decorators.js"; +export { + $baseUri, + $contains, + $contentEncoding, + $contentMediaType, + $contentSchema, + $extension, + $id, + $jsonSchema, + $maxContains, + $maxProperties, + $minContains, + $minProperties, + $multipleOf, + $oneOf, + $prefixItems, + $uniqueItems, + findBaseUri, + getBaseUri, + getContains, + getContentEncoding, + getContentMediaType, + getContentSchema, + getExtensions, + getId, + getJsonSchema, + getJsonSchemaTypes, + getMaxContains, + getMaxProperties, + getMinContains, + getMinProperties, + getMultipleOf, + getMultipleOfAsNumeric, + getPrefixItems, + getUniqueItems, + isJsonSchemaDeclaration, + isOneOf, + setExtension, +} from "./decorators.js"; +export type { ExtensionRecord, JsonSchemaDeclaration } from "./decorators.js"; export { $onEmit } from "./on-emit.js"; /** @internal */ export { $decorators } from "./tsp-index.js"; diff --git a/packages/json-schema/src/json-schema-emitter.ts b/packages/json-schema/src/json-schema-emitter.ts index 2486b53cd3..94c19813b7 100644 --- a/packages/json-schema/src/json-schema-emitter.ts +++ b/packages/json-schema/src/json-schema-emitter.ts @@ -1,20 +1,20 @@ import { - BooleanLiteral, - DiagnosticTarget, - Enum, - EnumMember, - IntrinsicType, - Model, - ModelProperty, - NumericLiteral, - Program, - Scalar, - StringLiteral, - StringTemplate, - Tuple, - Type, - Union, - UnionVariant, + type BooleanLiteral, + type DiagnosticTarget, + type Enum, + type EnumMember, + type IntrinsicType, + type Model, + type ModelProperty, + type NumericLiteral, + type Program, + type Scalar, + type StringLiteral, + type StringTemplate, + type Tuple, + type Type, + type Union, + type UnionVariant, compilerAssert, emitFile, explainStringTemplateNotSerializable, @@ -42,22 +42,22 @@ import { } from "@typespec/compiler"; import { ArrayBuilder, - Context, + type Context, Declaration, - EmitEntity, - EmittedSourceFile, - EmitterOutput, + type EmitEntity, + type EmittedSourceFile, + type EmitterOutput, ObjectBuilder, Placeholder, - Scope, - SourceFile, - SourceFileScope, + type Scope, + type SourceFile, + type SourceFileScope, TypeEmitter, } from "@typespec/compiler/emitter-framework"; import { DuplicateTracker } from "@typespec/compiler/utils"; import { stringify } from "yaml"; import { - JsonSchemaDeclaration, + type JsonSchemaDeclaration, findBaseUri, getContains, getContentEncoding, @@ -75,7 +75,9 @@ import { isJsonSchemaDeclaration, isOneOf, } from "./index.js"; -import { JSONSchemaEmitterOptions, reportDiagnostic } from "./lib.js"; +import { type JSONSchemaEmitterOptions, reportDiagnostic } from "./lib.js"; + +/** @internal */ export class JsonSchemaEmitter extends TypeEmitter, JSONSchemaEmitterOptions> { #idDuplicateTracker = new DuplicateTracker(); #typeForSourceFile = new Map, JsonSchemaDeclaration>(); @@ -565,7 +567,7 @@ export class JsonSchemaEmitter extends TypeEmitter, JSONSche } }; - const applyTypeConstraint = (fn: (p: Program, t: Type) => Type, key: string) => { + const applyTypeConstraint = (fn: (p: Program, t: Type) => Type | undefined, key: string) => { const constraintType = fn(this.emitter.getProgram(), type); if (constraintType) { const ref = this.emitter.emitTypeReference(constraintType); diff --git a/packages/json-schema/src/lib.ts b/packages/json-schema/src/lib.ts index b810a538b8..24159c4bc2 100644 --- a/packages/json-schema/src/lib.ts +++ b/packages/json-schema/src/lib.ts @@ -1,17 +1,30 @@ import { createTypeSpecLibrary, definePackageFlags, - JSONSchemaType, + type JSONSchemaType, paramMessage, } from "@typespec/compiler"; +/** + * File type + */ export type FileType = "yaml" | "json"; + +/** + * Strategy for handling the int64 type in the resulting json schema. + * - string: As a string + * - number: As a number (In JavaScript, int64 cannot be accurately represented as number) + * + */ export type Int64Strategy = "string" | "number"; +/** + * Json schema emitter options + */ export interface JSONSchemaEmitterOptions { /** * Serialize the schema as either yaml or json. - * @default yaml, it not specified infer from the `output-file` extension + * @defaultValue yaml it not specified infer from the `output-file` extension */ "file-type"?: FileType; @@ -32,7 +45,7 @@ export interface JSONSchemaEmitterOptions { /** * When true, emit all model declarations to JSON Schema without requiring - * the @jsonSchema decorator. + * the `@jsonSchema` decorator. */ emitAllModels?: boolean; @@ -44,6 +57,9 @@ export interface JSONSchemaEmitterOptions { emitAllRefs?: boolean; } +/** + * Internal: Json Schema emitter options schema + */ export const EmitterOptionsSchema: JSONSchemaType = { type: "object", additionalProperties: false, @@ -85,6 +101,7 @@ export const EmitterOptionsSchema: JSONSchemaType = { required: [], }; +/** Internal: TypeSpec library definition */ export const $lib = createTypeSpecLibrary({ name: "@typespec/json-schema", diagnostics: { @@ -110,10 +127,47 @@ export const $lib = createTypeSpecLibrary({ emitter: { options: EmitterOptionsSchema as JSONSchemaType, }, + state: { + JsonSchema: { description: "State indexing types marked with @jsonSchema" }, + "JsonSchema.baseURI": { description: "Contains data configured with @baseUri decorator" }, + "JsonSchema.multipleOf": { description: "Contains data configured with @multipleOf decorator" }, + "JsonSchema.id": { description: "Contains data configured with @id decorator" }, + "JsonSchema.oneOf": { description: "Contains data configured with @oneOf decorator" }, + "JsonSchema.contains": { description: "Contains data configured with @contains decorator" }, + "JsonSchema.minContains": { + description: "Contains data configured with @minContains decorator", + }, + "JsonSchema.maxContains": { + description: "Contains data configured with @maxContains decorator", + }, + "JsonSchema.uniqueItems": { + description: "Contains data configured with @uniqueItems decorator", + }, + "JsonSchema.minProperties": { + description: "Contains data configured with @minProperties decorator", + }, + "JsonSchema.maxProperties": { + description: "Contains data configured with @maxProperties decorator", + }, + "JsonSchema.contentEncoding": { + description: "Contains data configured with @contentEncoding decorator", + }, + "JsonSchema.contentSchema": { + description: "Contains data configured with @contentSchema decorator", + }, + "JsonSchema.contentMediaType": { + description: "Contains data configured with @contentMediaType decorator", + }, + "JsonSchema.prefixItems": { + description: "Contains data configured with @prefixItems decorator", + }, + "JsonSchema.extension": { description: "Contains data configured with @extension decorator" }, + }, } as const); +/** Internal: TypeSpec flags */ export const $flags = definePackageFlags({}); -export const { reportDiagnostic, createStateSymbol } = $lib; +export const { reportDiagnostic, createStateSymbol, stateKeys: JsonSchemaStateKeys } = $lib; export type JsonSchemaLibrary = typeof $lib; diff --git a/packages/json-schema/src/on-emit.ts b/packages/json-schema/src/on-emit.ts index c2e0321eca..33fc8137e5 100644 --- a/packages/json-schema/src/on-emit.ts +++ b/packages/json-schema/src/on-emit.ts @@ -1,15 +1,16 @@ -import { EmitContext, Enum, Model, Scalar, Union } from "@typespec/compiler"; +import type { EmitContext, Enum, Model, Scalar, Union } from "@typespec/compiler"; import { createAssetEmitter } from "@typespec/compiler/emitter-framework"; import { getJsonSchemaTypes } from "./decorators.js"; import { JsonSchemaEmitter } from "./json-schema-emitter.js"; -import { JSONSchemaEmitterOptions } from "./lib.js"; - -export { JsonSchemaEmitter } from "./json-schema-emitter.js"; -export { $flags, $lib, EmitterOptionsSchema, JSONSchemaEmitterOptions } from "./lib.js"; +import type { JSONSchemaEmitterOptions } from "./lib.js"; +export { $flags, $lib, EmitterOptionsSchema, type JSONSchemaEmitterOptions } from "./lib.js"; export const namespace = "TypeSpec.JsonSchema"; export type JsonSchemaDeclaration = Model | Union | Enum | Scalar; +/** + * Internal: TypeSpec emitter entry point + */ export async function $onEmit(context: EmitContext) { const emitter = createAssetEmitter(context.program, JsonSchemaEmitter as any, context); diff --git a/packages/json-schema/src/testing/index.ts b/packages/json-schema/src/testing/index.ts index b59565f2f5..78267d6095 100644 --- a/packages/json-schema/src/testing/index.ts +++ b/packages/json-schema/src/testing/index.ts @@ -1,7 +1,7 @@ import { createTestLibrary, findTestPackageRoot, - TypeSpecTestLibrary, + type TypeSpecTestLibrary, } from "@typespec/compiler/testing"; export const JsonSchemaTestLibrary: TypeSpecTestLibrary = createTestLibrary({ diff --git a/packages/json-schema/src/tsp-index.ts b/packages/json-schema/src/tsp-index.ts index ac065b5744..6d96dd80ad 100644 --- a/packages/json-schema/src/tsp-index.ts +++ b/packages/json-schema/src/tsp-index.ts @@ -1,5 +1,5 @@ -import { TypeSpecJsonSchemaDecorators } from "../generated-defs/TypeSpec.JsonSchema.js"; -import { TypeSpecJsonSchemaPrivateDecorators } from "../generated-defs/TypeSpec.JsonSchema.Private.js"; +import type { TypeSpecJsonSchemaDecorators } from "../generated-defs/TypeSpec.JsonSchema.js"; +import type { TypeSpecJsonSchemaPrivateDecorators } from "../generated-defs/TypeSpec.JsonSchema.Private.js"; import { $baseUri, $contains, diff --git a/packages/json-schema/src/utils.ts b/packages/json-schema/src/utils.ts new file mode 100644 index 0000000000..463957f4c6 --- /dev/null +++ b/packages/json-schema/src/utils.ts @@ -0,0 +1,17 @@ +import type { DecoratorFunction, Type } from "@typespec/compiler"; +import { unsafe_useStateMap } from "@typespec/compiler/experimental"; + +export function createDataDecorator< + T extends DecoratorFunction, + Target extends Type = Parameters[1], +>(key: symbol, validate?: (...args: Parameters) => boolean) { + const [getData, setData] = unsafe_useStateMap[2]>(key); + const decorator = (...args: Parameters) => { + if (validate && !validate(...args)) { + return; + } + const [context, target, value] = args; + setData(context.program, target, value); + }; + return [getData, setData, decorator as T] as const; +} diff --git a/packages/json-schema/test/extension.test.ts b/packages/json-schema/test/extension.test.ts index c650873784..1a021ffe32 100644 --- a/packages/json-schema/test/extension.test.ts +++ b/packages/json-schema/test/extension.test.ts @@ -1,4 +1,4 @@ -import { DecoratorContext, Type } from "@typespec/compiler"; +import type { DecoratorContext, Type } from "@typespec/compiler"; import { expectDiagnosticEmpty } from "@typespec/compiler/testing"; import assert from "assert"; import { describe, it } from "vitest"; diff --git a/packages/json-schema/test/utils.ts b/packages/json-schema/test/utils.ts index b94c80bee6..17c91ede9e 100644 --- a/packages/json-schema/test/utils.ts +++ b/packages/json-schema/test/utils.ts @@ -1,9 +1,9 @@ -import { Diagnostic } from "@typespec/compiler"; +import type { Diagnostic } from "@typespec/compiler"; import { createAssetEmitter } from "@typespec/compiler/emitter-framework"; import { createTestHost, expectDiagnosticEmpty } from "@typespec/compiler/testing"; import { parse } from "yaml"; import { JsonSchemaEmitter } from "../src/json-schema-emitter.js"; -import { JSONSchemaEmitterOptions } from "../src/lib.js"; +import type { JSONSchemaEmitterOptions } from "../src/lib.js"; import { JsonSchemaTestLibrary } from "../src/testing/index.js"; export async function getHostForCadlFile(contents: string, decorators?: Record) { diff --git a/packages/json-schema/tsconfig.json b/packages/json-schema/tsconfig.json index 284b90bcdc..19de0b3a83 100644 --- a/packages/json-schema/tsconfig.json +++ b/packages/json-schema/tsconfig.json @@ -4,7 +4,8 @@ "compilerOptions": { "outDir": "dist", "rootDir": ".", - "tsBuildInfoFile": "temp/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "temp/tsconfig.tsbuildinfo", + "verbatimModuleSyntax": true }, "include": ["src/**/*.ts", "generated-defs/**/*.ts", "test/**/*.ts"] }