From 74a1815f5b7120c05ecc2d71353f9b01d663c914 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 24 Jul 2024 12:20:31 -0700
Subject: [PATCH 1/7] Bump core from `39d4c60` to `10488de` (#1232)
Bumps [core](https://github.com/microsoft/typespec) from `39d4c60` to
`10488de`.
Commits
10488de
Allow spreading a model that has props added in previous version (#3911)
755df74
versioning - fixes crash when spreading incompatible versioned model as
param...
2914543
Add client ctor generation support (#3849)
- See full diff in compare
view
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)
---------
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Timothee Guerin
---
core | 2 +-
packages/samples/common-types/src/types.tsp | 22 +++++++++++----------
packages/typespec-autorest/vitest.config.ts | 2 +-
3 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/core b/core
index 39d4c60da5..10488deafe 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 39d4c60da5778ac470ea3dd01d7bbf9967e4d661
+Subproject commit 10488deafe2da16317b5306b4fcceb7dc8b70903
diff --git a/packages/samples/common-types/src/types.tsp b/packages/samples/common-types/src/types.tsp
index a5f7a68350..cd4544b40b 100644
--- a/packages/samples/common-types/src/types.tsp
+++ b/packages/samples/common-types/src/types.tsp
@@ -8,17 +8,19 @@ namespace Azure.ResourceManager.CommonTypes;
@@extension(ApiVersionParameter.apiVersion, "x-ms-parameter-location", "client");
@@extension(SubscriptionIdParameter.subscriptionId, "x-ms-parameter-location", "client");
-op registerParams(
- ...ApiVersionParameter,
- ...LocationParameter,
- ...ManagementGroupNameParameter,
- ...OperationIdParameter,
- ...ResourceGroupNameParameter,
- ...ScopeParameter,
- ...SubscriptionIdParameter,
- ...TenantIdParameter,
-): void;
+interface RegisterParams {
+ v3(
+ ...ApiVersionParameter,
+ ...LocationParameter,
+ ...OperationIdParameter,
+ ...ResourceGroupNameParameter,
+ ...SubscriptionIdParameter,
+ ): void;
+ // Params added in v4
+ @Versioning.added(Versions.v4)
+ v4(...ManagementGroupNameParameter, ...ScopeParameter, ...TenantIdParameter): void;
+}
@@extension(ErrorDetail.details, "x-ms-identifiers", ["message", "target"]);
@@extension(Resource, "x-ms-azure-resource", true);
diff --git a/packages/typespec-autorest/vitest.config.ts b/packages/typespec-autorest/vitest.config.ts
index dd8d9aa35f..1f060db852 100644
--- a/packages/typespec-autorest/vitest.config.ts
+++ b/packages/typespec-autorest/vitest.config.ts
@@ -5,7 +5,7 @@ export default mergeConfig(
defaultTypeSpecVitestConfig,
defineConfig({
test: {
- testTimeout: 10000,
+ testTimeout: 30000,
},
})
);
From a81147c59a843198a3cdb5bacc903ffd0605640b Mon Sep 17 00:00:00 2001
From: Chenjie Shi
Date: Thu, 25 Jul 2024 13:36:58 +0800
Subject: [PATCH 2/7] [dpg] update spread doc (#1092)
doc for the new behavior of spread, including one sample to show impact
for azure core template.
---------
Co-authored-by: Weidong Xu
Co-authored-by: qiaozha
Co-authored-by: Dapeng Zhang
---
.../08methodInputs.mdx | 401 ++++++++++++++++--
1 file changed, 372 insertions(+), 29 deletions(-)
diff --git a/docs/howtos/DataPlane Generation - DPG/08methodInputs.mdx b/docs/howtos/DataPlane Generation - DPG/08methodInputs.mdx
index 72489f1649..4d9d3f73ba 100644
--- a/docs/howtos/DataPlane Generation - DPG/08methodInputs.mdx
+++ b/docs/howtos/DataPlane Generation - DPG/08methodInputs.mdx
@@ -81,6 +81,13 @@ public User get();
```go
+type ClientGetOptions struct {
+}
+
+type ClientGetResponse struct {
+}
+
+func (client *Client) Get(ctx context.Context, options *ClientGetOptions) (ClientGetResponse, error)
```
@@ -171,7 +178,13 @@ public void post(User user);
```go
+type ClientPostOptions struct {
+}
+
+type ClientPostResponse struct {
+}
+func (client *Client) Post(ctx context.Context, user User, options *ClientPostOptions) (ClientPostResponse, error)
```
@@ -181,9 +194,9 @@ public void post(User user);
Please use the _spread_ feature with caution.
-- The anonymous model to be spread into operation should have less than 6 settable properties. See [simple methods](https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-parameters).
-- The anonymous model should be stable across api-versions. Adding an optional property across api-versions could result in one additional method overload in SDK client.
-- The anonymous model should not be used in [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386).
+- The model to be spread should have less than 6 settable properties. See [simple methods](https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-parameters).
+- The model to be spread should be stable across api-versions. Adding an optional property across api-versions could result in one additional method overload in SDK client.
+- The model to be spread should not be used in [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386).
### Alias
@@ -269,13 +282,20 @@ public void upload(String firstName, String lastName);
```go
+type ClientUploadOptions struct {
+}
+
+type ClientUploadResponse struct {
+}
+
+func (client *Client) Upload(ctx context.Context, firstName string, lastName string, options *ClientUploadOptions) (ClientUploadResponse, error)
```
-### Alias with HTTP Parameters
+### Alias with @header/@query/@path properties
@@ -294,8 +314,10 @@ op upload(...User): void;
+For Python, we will also generate the overloads described in the Http Post section, but omitting for brevity
+
```python
-def upload(id: str, first_name: str, last_name: str) -> None:
+def upload(self, id: str, first_name: str, last_name: str, *, content_type: str = "application/json") -> None:
...
```
@@ -366,7 +388,13 @@ public void upload(String id, String firstName, String lastName);
```go
+type ClientUploadOptions struct {
+}
+type ClientUploadResponse struct {
+}
+
+func (client *Client) Upload(ctx context.Context, id string, firstName string, lastName string, options *ClientUploadOptions) (ClientUploadResponse, error)
```
@@ -392,7 +420,7 @@ op upload(...User): void;
For Python, we will also generate the overloads described in the Http Post section, but omitting for brevity
```python
-def upload(self, user: [User, JSON, IO[bytes]], *, content_type: str = "application/json") -> None:
+def upload(self, first_name: str, last_name: str, *, content_type: str = "application/json") -> None:
...
```
@@ -402,7 +430,7 @@ def upload(self, user: [User, JSON, IO[bytes]], *, content_type: str = "applicat
```csharp
public partial class User
{
- public User(string firstName, string lastName){}
+ public User(string firstName, string lastName) { }
public string FirstName { get; }
public string LastName { get; }
}
@@ -411,7 +439,7 @@ public virtual async Task UploadAsync(RequestContent content, RequestC
public virtual Response Upload(RequestContent content, RequestContext context = null)
//convenience method
public virtual async Task UploadAsync(User user, CancellationToken cancellationToken = default)
-public virtual Response Upload(User user, CancellationToken cancellationToken = default)
+public virtual Response Upload(string firstName, string lastName, CancellationToken cancellationToken = default)
```
@@ -426,7 +454,10 @@ export type DemoServiceContext = Client & {
(path: "/users"): {
post(
options: {
- body: User;
+ body: {
+ firstName: string;
+ lastName: string;
+ };
} & RequestParameters
): StreamableMethod;
};
@@ -434,11 +465,15 @@ export type DemoServiceContext = Client & {
};
// Modular Api Layer
-export async function upload(body: User, options: UploadOptionalParams): Promise;
+export async function upload(
+ firstName: string,
+ lastName: string,
+ options: UploadOptionalParams
+): Promise;
// Modular classical client layer
export class DemoServiceClient {
- upload(body: User, options: UploadOptionalParams): Promise;
+ upload(firstName: string, lastName: string, options: UploadOptionalParams): Promise;
}
```
@@ -446,29 +481,26 @@ export class DemoServiceClient {
```java
-// Model class
-@Immutable
-public final class User implements JsonSerializable {
- public User(String firstName, String lastName);
- public String getFirstName();
- public String getLastName();
-}
-
-// Client API
-public void upload(User user);
+public void upload(String firstName, String lastName);
```
```go
+type ClientUploadOptions struct {
+}
+
+type ClientUploadResponse struct {
+}
+func (client *Client) Upload(ctx context.Context, firstName string, lastName string, options *ClientUploadOptions) (ClientUploadResponse, error)
```
-### Model with `@body` Property
+### Model with `@body` property
@@ -493,7 +525,7 @@ op upload(...UserRequest): void;
For Python, we will also generate the overloads described in the Http Post section, but omitting for brevity
```python
-def upload(body: [User, JSON, IO[bytes]], **kwargs: Any) -> None:
+def upload(self, body: [User, JSON, IO[bytes]], *, content_type: str = "application/json") -> None:
...
```
@@ -564,13 +596,24 @@ public void upload(User user);
```go
+type User struct {
+ firstName *string
+ lastName *string
+}
+
+type ClientUploadOptions struct {
+}
+type ClientUploadResponse struct {
+}
+
+func (client *Client) Upload(ctx context.Context, user User, options *ClientUploadOptions) (ClientUploadResponse, error)
```
-### Model with Decorated Properties
+### Model with @header/@query/@path properties
@@ -594,7 +637,7 @@ For Python, we will also generate the overloads described in the Http Post secti
```python
-def get_blob_properties(name: str, *, test_header: string, **kwargs: Any) -> None:
+def get_blob_properties(self, name: str, *, test_header: string, content_type: str = "application/json") -> None:
...
```
@@ -657,13 +700,19 @@ public void getBlobProperties(String name, String testHeader);
```go
+type ClientGetBlobPropertiesOptions struct {
+}
+type ClientGetBlobPropertiesResponse struct {
+}
+
+func (client *Client) GetBlobProperties(ctx context.Context, name string, testHeader string, options *ClientGetBlobPropertiesOptions) (ClientGetBlobPropertiesResponse, error)
```
-### Model with Decorated and non-Decorated Properties
+### Model mixed with normal and @header/@query/@path properties
@@ -687,7 +736,7 @@ For Python, we will also generate the overloads described in the Http Post secti
class Schema:
schema: bytes
-def register(body: [Schema, JSON, IO[bytes]], **kwargs: Any) -> None:
+def register(self, body: [Schema, JSON, IO[bytes]], *, content_type: str = "application/json") -> None:
...
```
@@ -724,7 +773,7 @@ export type DemoServiceContext = Client & {
"content-type": "application/json";
} & RawHttpHeaders;
body: {
- schema: string | Uint8Array | ReadableStream | NodeJS.ReadableStream;
+ schema: string;
};
} & RequestParameters
): StreamableMethod;
@@ -734,7 +783,7 @@ export type DemoServiceContext = Client & {
// Modular model
export interface Schema {
- schema: string | Uint8Array | ReadableStream | NodeJS.ReadableStream;
+ schema: string;
}
// Modular api layer
@@ -768,7 +817,301 @@ public void register(Schema schema);
```go
+type ClientRegisterOptions struct {
+}
+
+type ClientRegisterResponse struct {
+}
+
+func (client *Client) Register(ctx context.Context, schema []byte, options *ClientRegisterOptions) (ClientRegisterResponse, error)
+```
+
+
+
+
+### Using Azure.Core.ResourceOperations template
+
+Resource create and update operations are not impacted by spread since they all have explicit defined body parameter.
+Only resource action operations are impacted by spread.
+
+If the action parameter is a model, then the model will be spread.
+
+
+
+
+```typespec
+@resource("widgets")
+model Widget {
+ @key("widgetName")
+ name: string;
+}
+
+model RepairInfo {
+ problem: string;
+ contact: string;
+}
+
+model RepairResult {
+ reason: string;
+ info: string;
+}
+
+alias Operations = Azure.Core.ResourceOperations<{}>;
+
+op scheduleRepairs is Operations.ResourceAction;
+```
+
+
+
+
+For Python, we will also generate the overloads described in the Http Post section, but omitting for brevity
+
+```python
+class RepairInfo:
+ problem: str
+ contact: str
+
+class RepairResult:
+ reason: str
+ info: str
+
+def scheduleRepairs(self, widget_name: str, problem: str, contact: str, *, content_type: str = "application/json") -> RepairResult:
+ ...
+```
+
+
+
+
+```csharp
+
+```
+
+
+
+
+```typescript
+// from user experience perspective
+
+export interface RepairInfo {
+ problem: string;
+ contact: string;
+}
+
+export type WidgetServiceContext = Client & {
+ path: {
+ (
+ path: "/widgets/{widgetName}:scheduleRepairs",
+ widgetName: string
+ ): {
+ post(
+ options: {
+ body: RepairInfo;
+ } & RequestParameters
+ ): StreamableMethod;
+ };
+ };
+};
+
+// Modular api layer
+export async function scheduleRepairs(
+ context: Client,
+ widgetName: string,
+ problem: string,
+ contact: string,
+ options: ScheduleRepairsOptionalParams = { requestOptions: {} }
+): Promise;
+
+// Modular classical client layer
+export class WidgetServiceClient {
+ scheduleRepairs(
+ widgetName: string,
+ problem: string,
+ contact: string,
+ options: ScheduleRepairsOptionalParams = { requestOptions: {} }
+ ): Promise;
+}
+```
+
+
+
+
+```java
+public RepairResult scheduleRepairs(String widgetName, String problem, String contact);
+```
+
+
+
+
+```go
+type ClientScheduleRepairsOptions struct {
+}
+
+type RepairResult struct {
+ reason *string
+ info *string
+}
+
+type ClientScheduleRepairsResponse struct {
+ RepairResult
+}
+
+func (client *Client) ScheduleRepairs(ctx context.Context, widgetName string, problem string, contact string, options *ClientScheduleRepairsOptions) (ClientScheduleRepairsResponse, error)
+```
+
+
+
+
+If you want to keep the model, you could use a wrapper to explicit set the body to prevent spread.
+
+
+
+
+```typespec
+alias BodyParameter<
+ T,
+ TName extends valueof string = "body",
+ TDoc extends valueof string = "Body parameter."
+> = {
+ @doc(TDoc)
+ @friendlyName(TName)
+ @bodyRoot
+ body: T;
+};
+
+@resource("widgets")
+model Widget {
+ @key("widgetName")
+ name: string;
+}
+
+model RepairInfo {
+ problem: string;
+ contact: string;
+}
+
+model RepairResult {
+ reason: string;
+ info: string;
+}
+
+alias Operations = Azure.Core.ResourceOperations<{}>;
+
+op scheduleRepairs is Operations.ResourceAction, RepairResult>;
+```
+
+
+
+
+For Python, we will also generate the overloads described in the Http Post section, but omitting for brevity
+
+```python
+class RepairInfo:
+ problem: str
+ contact: str
+
+class RepairResult:
+ reason: str
+ info: str
+
+def scheduleRepairs(self, body: [Schema, JSON, IO[bytes]], *, content_type: str = "application/json") -> RepairResult:
+ ...
+```
+
+
+
+
+```csharp
+
+```
+
+
+
+
+```typescript
+// from user experience perspective
+
+export interface RepairInfo {
+ problem: string;
+ contact: string;
+}
+
+export type WidgetServiceContext = Client & {
+ path: {
+ (
+ path: "/widgets/{widgetName}:scheduleRepairs",
+ widgetName: string
+ ): {
+ post(
+ options: {
+ body: RepairInfo;
+ } & RequestParameters
+ ): StreamableMethod;
+ };
+ };
+};
+
+// Modular api layer
+export async function scheduleRepairs(
+ context: Client,
+ widgetName: string,
+ body: RepairInfo,
+ options: ScheduleRepairsOptionalParams = { requestOptions: {} }
+): Promise;
+
+// Modular classical client layer
+export class WidgetServiceClient {
+ scheduleRepairs(
+ widgetName: string,
+ body: RepairInfo,
+ options: ScheduleRepairsOptionalParams = { requestOptions: {} }
+ ): Promise;
+}
+```
+
+
+
+
+```java
+// Model class
+@Immutable
+public final class RepairInfo implements JsonSerializable {
+ public RepairInfo(String problem, String contact);
+ public String getProblem();
+ public String getContact();
+}
+
+@Immutable
+public final class RepairResult implements JsonSerializable {
+ public String getReason();
+ public String getInfo();
+}
+
+// Client API
+public RepairResult scheduleRepairs(String widgetName, RepairInfo body);
+```
+
+
+
+
+```go
+type RepairInfo struct {
+ problem *string
+ contact *string
+}
+
+type ClientScheduleRepairsOptions struct {
+}
+
+type RepairResult struct {
+ reason *string
+ info *string
+}
+
+type ClientScheduleRepairsResponse struct {
+ RepairResult
+}
+func (client *Client) ScheduleRepairs(ctx context.Context, widgetName string, body RepairInfo, options *ClientScheduleRepairsOptions) (ClientScheduleRepairsResponse, error)
```
From 78c82ec9281522ec9327163db6ba01c14defbaee Mon Sep 17 00:00:00 2001
From: Chenjie Shi
Date: Sat, 27 Jul 2024 02:27:19 +0800
Subject: [PATCH 3/7] Backmerge tcgc(0.44.2) and autorest (0.44.1) hotfix
(#1240)
Co-authored-by: Wei Hu
Co-authored-by: Timothee Guerin
Co-authored-by: Yuchao Yan
Co-authored-by: iscai-msft <43154838+iscai-msft@users.noreply.github.com>
Co-authored-by: iscai-msft
Co-authored-by: Dapeng Zhang
Co-authored-by: Mingzhe Huang
Co-authored-by: Mingzhe Huang (from Dev Box)
---
...rate_package_creation-2024-6-18-17-1-40.md | 9 -
.../07tcgcTypes.mdx | 37 +-
.../typespec-autorest-canonical/CHANGELOG.md | 7 +
.../typespec-autorest-canonical/package.json | 2 +-
packages/typespec-autorest/CHANGELOG.md | 7 +
packages/typespec-autorest/package.json | 2 +-
.../CHANGELOG.md | 21 +
.../doc/types.tsp | 794 ----------------
.../package.json | 5 +-
.../src/decorators.ts | 40 +-
.../src/example.ts | 632 +++++++++++++
.../src/http.ts | 7 +-
.../src/interfaces.ts | 218 ++++-
.../src/internal-utils.ts | 24 +-
.../typespec-client-generator-core/src/lib.ts | 26 +
.../src/package.ts | 24 +-
.../src/public-utils.ts | 12 +-
.../src/types.ts | 491 +++++++---
.../src/validate.ts | 5 +
.../test/decorators.test.ts | 83 +-
.../test/examples/example-types.test.ts | 892 ++++++++++++++++++
.../test/examples/example-types/getAny.json | 12 +
.../test/examples/example-types/getArray.json | 10 +
.../example-types/getArrayDiagnostic.json | 10 +
.../examples/example-types/getBoolean.json | 10 +
.../example-types/getBooleanDiagnostic.json | 10 +
.../examples/example-types/getDictionary.json | 14 +
.../getDictionaryDiagnostic.json | 10 +
.../test/examples/example-types/getModel.json | 13 +
.../getModelAdditionalProperties.json | 15 +
.../example-types/getModelDiagnostic.json | 14 +
.../example-types/getModelDiscriminator.json | 27 +
.../getModelDiscriminatorDiagnostic.json | 14 +
.../test/examples/example-types/getNull.json | 10 +
.../examples/example-types/getNumber.json | 10 +
.../example-types/getNumberDiagnostic.json | 10 +
.../example-types/getNumberFromDateTime.json | 10 +
.../example-types/getNumberFromDuration.json | 10 +
.../examples/example-types/getString.json | 10 +
.../example-types/getStringDiagnostic.json | 10 +
.../example-types/getStringFromConstant.json | 10 +
.../getStringFromConstantDiagnostic.json | 10 +
.../example-types/getStringFromDataTime.json | 10 +
.../example-types/getStringFromDuration.json | 10 +
.../example-types/getStringFromEnum.json | 10 +
.../getStringFromEnumDiagnostic.json | 10 +
.../example-types/getStringFromEnumValue.json | 10 +
.../getStringFromEnumValueDiagnostic.json | 10 +
.../test/examples/example-types/getUnion.json | 10 +
.../test/examples/helper.test.ts | 40 +
.../test/examples/helper/getOne.json | 10 +
.../test/examples/helper/getTwo.json | 10 +
.../examples/http-operation-examples.test.ts | 240 +++++
.../http-operation-examples/parameters.json | 11 +
.../parametersDiagnostic.json | 8 +
.../http-operation-examples/responses.json | 16 +
.../responsesDiagnostic.json | 18 +
.../http-operation-examples/simple.json | 6 +
.../test/examples/load.test.ts | 125 +++
.../test/examples/load/get.json | 11 +
.../test/examples/load/getAnother.json | 11 +
.../test/package.test.ts | 7 +
.../test/public-utils.test.ts | 13 +-
.../test/test-host.ts | 26 +-
.../test/types/built-in-types.test.ts | 33 +-
.../test/types/date-time-types.test.ts | 47 +-
.../test/types/duration-type.test.ts | 12 +-
.../types/general-decorators-list.test.ts | 49 +-
.../test/types/model-types.test.ts | 37 +-
.../test/types/multipart-types.test.ts | 347 ++++++-
.../test/types/usage-flags.test.ts | 16 +
pnpm-lock.yaml | 9 +-
72 files changed, 3661 insertions(+), 1098 deletions(-)
delete mode 100644 .chronus/changes/separate_package_creation-2024-6-18-17-1-40.md
delete mode 100644 packages/typespec-client-generator-core/doc/types.tsp
create mode 100644 packages/typespec-client-generator-core/src/example.ts
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types.test.ts
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getAny.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getArray.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getArrayDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getBoolean.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getBooleanDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getDictionary.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getDictionaryDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getModel.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getModelAdditionalProperties.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getModelDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getModelDiscriminator.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getModelDiscriminatorDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getNull.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getNumber.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getNumberDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getNumberFromDateTime.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getNumberFromDuration.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getString.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringFromConstant.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringFromConstantDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringFromDataTime.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringFromDuration.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnum.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumValue.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumValueDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/example-types/getUnion.json
create mode 100644 packages/typespec-client-generator-core/test/examples/helper.test.ts
create mode 100644 packages/typespec-client-generator-core/test/examples/helper/getOne.json
create mode 100644 packages/typespec-client-generator-core/test/examples/helper/getTwo.json
create mode 100644 packages/typespec-client-generator-core/test/examples/http-operation-examples.test.ts
create mode 100644 packages/typespec-client-generator-core/test/examples/http-operation-examples/parameters.json
create mode 100644 packages/typespec-client-generator-core/test/examples/http-operation-examples/parametersDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/http-operation-examples/responses.json
create mode 100644 packages/typespec-client-generator-core/test/examples/http-operation-examples/responsesDiagnostic.json
create mode 100644 packages/typespec-client-generator-core/test/examples/http-operation-examples/simple.json
create mode 100644 packages/typespec-client-generator-core/test/examples/load.test.ts
create mode 100644 packages/typespec-client-generator-core/test/examples/load/get.json
create mode 100644 packages/typespec-client-generator-core/test/examples/load/getAnother.json
create mode 100644 packages/typespec-client-generator-core/test/types/usage-flags.test.ts
diff --git a/.chronus/changes/separate_package_creation-2024-6-18-17-1-40.md b/.chronus/changes/separate_package_creation-2024-6-18-17-1-40.md
deleted file mode 100644
index 084a032315..0000000000
--- a/.chronus/changes/separate_package_creation-2024-6-18-17-1-40.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-changeKind: feature
-packages:
- - "@azure-tools/typespec-autorest-canonical"
- - "@azure-tools/typespec-autorest"
- - "@azure-tools/typespec-client-generator-core"
----
-
-expose createTcgcContext, which is the minimal context object that handles scope
\ No newline at end of file
diff --git a/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx b/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx
index 1f383e22c3..80d97c713c 100644
--- a/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx
+++ b/docs/howtos/DataPlane Generation - DPG/07tcgcTypes.mdx
@@ -1219,7 +1219,9 @@ interface SdkTypeBase {
// created by tcgc. Those won't have an original type
__raw?: Type;
kind: string;
- deprecations?: string;
+ deprecation?: string;
+ description?: string;
+ details?: string;
}
```
@@ -1236,6 +1238,9 @@ There is a one-to-one mapping between the TypeSpec scalar kinds and the `SdkBuil
export interface SdkBuiltInType extends SdkTypeBase {
kind: SdkBuiltInKinds;
encode: string;
+ name: string;
+ baseType?: SdkBuiltInType;
+ crossLanguageDefinitionId: string;
}
```
@@ -1243,9 +1248,12 @@ export interface SdkBuiltInType extends SdkTypeBase {
```ts
interface SdkDatetimeTypeBase extends SdkTypeBase {
+ name: string;
+ baseType?: SdkDateTimeType;
encode: DateTimeKnownEncoding;
// what we send over the wire. Often it's string
wireType: SdkBuiltInType;
+ crossLanguageDefinitionId: string;
}
interface SdkUtcDatetimeType extends SdkDatetimeTypeBase {
@@ -1262,9 +1270,12 @@ interface SdkOffsetDatetimeType extends SdkDatetimeTypeBase {
```ts
interface SdkDurationType extends SdkTypeBase {
kind: "duration";
+ name: string;
+ baseType?: SdkDurationType;
encode: DurationKnownEncoding;
// What we send over the wire. It's usually either a string or a float
wireType: SdkBuiltInType;
+ crossLanguageDefinitionId: string;
}
```
@@ -1273,8 +1284,9 @@ interface SdkDurationType extends SdkTypeBase {
```ts
interface SdkArrayType extends SdkTypeBase {
kind: "array";
+ name: string;
valueType: SdkType;
- nullableValues: boolean;
+ crossLanguageDefinitionId: string;
}
```
@@ -1285,7 +1297,6 @@ interface SdkDictionaryType extends SdkTypeBase {
kind: "dict";
keyType: SdkType; // currently can only be string
valueType: SdkType;
- nullableValues: boolean;
}
```
@@ -1296,17 +1307,16 @@ export interface SdkEnumType extends SdkTypeBase {
kind: "enum";
name: string;
// Determines whether the name was generated or not
- generatedName: boolean;
+ isGeneratedName: boolean;
valueType: SdkBuiltInType;
values: SdkEnumValueType[];
isFixed: boolean;
- description?: string;
- details?: string;
isFlags: boolean;
usage: UsageFlags;
access: AccessFlags;
crossLanguageDefinitionId: string;
apiVersions: string[];
+ isUnionAsEnum: boolean;
}
```
@@ -1318,9 +1328,7 @@ export interface SdkEnumValueType extends SdkTypeBase {
name: string;
value: string | number;
enumType: SdkEnumType;
- valueType: SdkType;
- description?: string;
- details?: string;
+ valueType: SdkBuiltInType;
}
```
@@ -1331,6 +1339,8 @@ export interface SdkConstantType extends SdkTypeBase {
kind: "constant";
value: string | number | boolean | null;
valueType: SdkBuiltInType;
+ name: string;
+ isGeneratedName: boolean;
}
```
@@ -1340,9 +1350,10 @@ export interface SdkConstantType extends SdkTypeBase {
export interface SdkUnionType extends SdkTypeBase {
name: string;
// determines if the union name was generated or not
- generatedName: boolean;
+ isGeneratedName: boolean;
kind: "union";
values: SdkType[];
+ crossLanguageDefinitionId: string;
}
```
@@ -1353,13 +1364,9 @@ export interface SdkModelType extends SdkTypeBase {
kind: "model";
// purposely can also be header / query params for fidelity with TypeSpec
properties: SdkModelPropertyType[];
- isFormDataType: boolean;
- isError: boolean;
// we will always have a name. generatedName determines if it's generated or not.
name: string;
- generatedName: string;
- description?: string;
- details?: string;
+ isGeneratedName: boolean;
access: AccessFlags;
usage: UsageFlags;
additionalProperties?: SdkType;
diff --git a/packages/typespec-autorest-canonical/CHANGELOG.md b/packages/typespec-autorest-canonical/CHANGELOG.md
index 267d5b65b1..5a72566a5b 100644
--- a/packages/typespec-autorest-canonical/CHANGELOG.md
+++ b/packages/typespec-autorest-canonical/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog - @azure-tools/typespec-autorest-canonical
+## 0.5.1
+
+### Features
+
+- [#1237](https://github.com/Azure/typespec-azure/pull/1237) Use new `createTcgcContext` from tcgc lib, which is the minimal context object that handles scope
+
+
## 0.5.0
### Bug Fixes
diff --git a/packages/typespec-autorest-canonical/package.json b/packages/typespec-autorest-canonical/package.json
index 23eeea5211..9822454506 100644
--- a/packages/typespec-autorest-canonical/package.json
+++ b/packages/typespec-autorest-canonical/package.json
@@ -1,6 +1,6 @@
{
"name": "@azure-tools/typespec-autorest-canonical",
- "version": "0.5.0",
+ "version": "0.5.1",
"author": "Microsoft Corporation",
"description": "TypeSpec library for emitting canonical swagger",
"homepage": "https://azure.github.io/typespec-azure",
diff --git a/packages/typespec-autorest/CHANGELOG.md b/packages/typespec-autorest/CHANGELOG.md
index 0c8ee57ea3..3003ba0734 100644
--- a/packages/typespec-autorest/CHANGELOG.md
+++ b/packages/typespec-autorest/CHANGELOG.md
@@ -1,5 +1,12 @@
# Change Log - @azure-tools/typespec-autorest
+## 0.44.1
+
+### Features
+
+- [#1237](https://github.com/Azure/typespec-azure/pull/1237) Use new `createTcgcContext` from tcgc lib, which is the minimal context object that handles scope
+
+
## 0.44.0
### Bug Fixes
diff --git a/packages/typespec-autorest/package.json b/packages/typespec-autorest/package.json
index 127564ef2a..657958abbe 100644
--- a/packages/typespec-autorest/package.json
+++ b/packages/typespec-autorest/package.json
@@ -1,6 +1,6 @@
{
"name": "@azure-tools/typespec-autorest",
- "version": "0.44.0",
+ "version": "0.44.1",
"author": "Microsoft Corporation",
"description": "TypeSpec library for emitting openapi from the TypeSpec REST protocol binding",
"homepage": "https://azure.github.io/typespec-azure",
diff --git a/packages/typespec-client-generator-core/CHANGELOG.md b/packages/typespec-client-generator-core/CHANGELOG.md
index 43e5449c9d..9e5e969c4a 100644
--- a/packages/typespec-client-generator-core/CHANGELOG.md
+++ b/packages/typespec-client-generator-core/CHANGELOG.md
@@ -1,5 +1,26 @@
# Change Log - @azure-tools/typespec-client-generator-core
+## 0.44.2
+
+### Bug Fixes
+
+- [#1231](https://github.com/Azure/typespec-azure/pull/1231) Fix the duplicate usageflags values for json and xml
+- [#1203](https://github.com/Azure/typespec-azure/pull/1203) Have `@clientName` work for operation groups as well
+- [#1222](https://github.com/Azure/typespec-azure/pull/1222) Validate `@clientName` conflict for operations inside interface
+
+### Features
+
+- [#1090](https://github.com/Azure/typespec-azure/pull/1090) Support model format of `@multipartBody`
+- [#1237](https://github.com/Azure/typespec-azure/pull/1237) Expose createTcgcContext, which is the minimal context object that handles scope
+- [#1223](https://github.com/Azure/typespec-azure/pull/1223) Report error diagnostic when trying to flattening a model with polymorphism
+- [#1076](https://github.com/Azure/typespec-azure/pull/1076) Add example types support
+- [#1204](https://github.com/Azure/typespec-azure/pull/1204) Add xml usage and change enumvalue arg representation in generic decorators
+
+### Breaking Changes
+
+- [#1015](https://github.com/Azure/typespec-azure/pull/1015) Refactor tcgc build-in types, please refer pr's description for details and migration guides
+
+
## 0.44.1
### Bug Fixes
diff --git a/packages/typespec-client-generator-core/doc/types.tsp b/packages/typespec-client-generator-core/doc/types.tsp
deleted file mode 100644
index 9b8cb73a17..0000000000
--- a/packages/typespec-client-generator-core/doc/types.tsp
+++ /dev/null
@@ -1,794 +0,0 @@
-import "@typespec/http";
-using TypeSpec.Reflection;
-using TypeSpec.Http;
-
-namespace TypeSpec.ClientGenerator.Core;
-
-/**
- * Base SdkType that all types polymorphically extend from.
- *
- * @property __raw: the original TypeSpec type
- * @property kind: the kind of type
- * @property deprecation: deprecated message if the type is deprecated
- */
-@discriminator("kind")
-model SdkType {
- __raw?: unknown;
- deprecation?: string;
-}
-
-/**
- * Flags enum to keep track of usages for models and enums.
- *
- * @enum input: If the object is used as input
- * @enum output: If the object is used as output
- */
-enum UsageFlags {
- input: 1,
- output: 2,
-}
-
-/**
- * Access flags to keep track of access levels for operations and models
- *
- * @enum public: Whether the object is a publicly-accessible object
- * @enum internal: Whether the object is an internal object
- */
-enum AccessFlags {
- public,
- internal,
-}
-
-/**
- * Flags enum to keep track of visibility
- *
- * @enum read: Whether the object is read
- */
-enum Visibility {
- read: 1,
-}
-
-/**
- * These types are more primitive and roughly correspond to the TypeSpec built in types
- *
- * @property encode: How we represent the type to SDK users
- */
-model SdkBuiltInType extends SdkType {
- kind:
- | "bytes"
- | "boolean"
- | "date"
- | "time"
- | "any"
- | "int32"
- | "int64"
- | "float32"
- | "float64"
- | "decimal"
- | "decimal128"
- | "string"
- | "guid"
- | "url"
- | "uuid"
- | "password"
- | "armId"
- | "ipAddress"
- | "azureLocation"
- | "eTag";
- encode: string;
-}
-
-/**
- * Represents a datetime type.
- *
- * @property encode: How to encode the datetime and represent to users
- * @property wireType: What type we end up sending over the wire for a datetime. Can be a string or an int type.
- */
-model SdkDateTimeType extends SdkType {
- kind: "datetime";
- encode: DateTimeKnownEncoding;
- wireType: SdkType;
-}
-
-/**
- * Represents a duration type
- *
- * @property encode: How to encode the duration type and represent it to users.
- * @property wireType: What type we end up sending over the wire for a duration. Can be a string, an int, or a float type.
- */
-model SdkDurationType extends SdkType {
- kind: "duration";
- encode: DurationKnownEncoding;
- wireType: SdkType;
-}
-
-/**
- * An array type
- *
- * @property valueType: The type of each value in the array
- */
-model SdkArrayType extends SdkType {
- kind: "array";
- valueType: SdkType;
-}
-
-/**
- * An tuple type
- *
- * @property values: The values in the tuple
- */
-model SdkTupleType extends SdkType {
- kind: "tuple";
- values: SdkType[];
-}
-
-/**
- * A dictionary type
- *
- * @property keyType: The type of the key. 99.9% of the time it's a string, but OpenAI does have it as an int.
- * @property valueType: The type of the values
- */
-model SdkDictionaryType extends SdkType {
- kind: "dict";
- keyType: SdkType;
- valueType: SdkType;
-}
-
-/**
- * Represents an enum type
- *
- * @property name: Name of the enum
- * @property valueType: The type of the enum values
- * @property values: List of enum values
- * @property isFixed: Whether it's a fixed value enum
- * @property description: Description of the enum
- * @property details: Optional details of the enum object
- * @property isFlags: Whether the enum represents a set of flags
- * @property access: Access level of the enum
- * @property usage: Usage cases for the enum. Used to filter when people only want input or output enums
- * @property summary: Summary of an enum
- * @property isUnionAsEnum: Whether the enum is converted from TypeSpec union type
- */
-model SdkEnumType extends SdkType {
- kind: "enum";
- name: string;
- valueType: SdkBuiltInType;
- values: SdkEnumValueType[];
- isFixed: boolean;
- description?: string;
- details?: string;
- isFlags: boolean;
- access: AccessFlags;
- usage: UsageFlags;
- summary: string;
- nameSpace: string;
- isUnionAsEnum: boolean;
-}
-
-/**
- * Represents an enum value type
- *
- * @property name: The name of the enum value
- * @property value: The value of the enum
- * @property enumType: The enum that this value belongs to
- * @property valueType: Type of the value. Same as the type listed in SdkEnumType.valueType
- * @property description: Description for the enum value
- * @property details: Optional details on the enum value
- */
-model SdkEnumValueType extends SdkType {
- kind: "enumvalue";
- name: string;
- value: string | numeric;
- enumType: SdkEnumType;
- valueType: SdkType;
- description?: string;
- details?: string;
-}
-
-/**
- * Represents a constant type
- *
- * @property value: The value of the constant
- * @property valueType: The type of the constant value
- */
-model SdkConstantType extends SdkType {
- kind: "constant";
- value: string | numeric | boolean | null;
- valueType: SdkBuiltInType;
-}
-
-/**
- * Represents a union type
- *
- * @property name: The name of the union type if it's a named union type, otherwise undefined
- * @property values: The various values that are unioned
- */
-@doc("Represents a union type")
-model SdkUnionType extends SdkType {
- kind: "union";
- name?: string;
- values: SdkType[];
-}
-
-/**
- * Represents a model type
- *
- * @property name: Name of the model
- * @property description: Description of the model
- * @property details: Optional details of the model
- * @property properties: List of properties on the model
- * @property access: Access level of the model
- * @property usage: Usage cases for the model. Used to filter when people only want input or output models.
- * @property additionalProperties: Model's additional properties type, if no additional properties, then undefined
- * @property discriminatorValue: Value of the discriminator if this is a discriminated subtype. Will be undefined if not.
- * @property discriminatedSubtypes: Mapping of discriminator value to this models discriminated subtypes if there are any.
- * @property discriminatorProperty: The property that is the discriminator for this model if it's a discriminated subtype. Will be undefined if not.
- * @property baseModel: The base model class of this model type if one exists.
- */
-model SdkModelType extends SdkType {
- kind: "model";
- name: string;
- description?: string;
- details?: string;
- summary: string;
- properties: SdkModelPropertyType[];
- access: AccessFlags;
- usage: UsageFlags;
- additionalProperties?: SdkType;
- discriminatorValue?: string;
- discriminatedSubtypes?: Record;
- discriminatorProperty?: SdkModelPropertyType;
- baseModel?: SdkModelType;
- nameSpace: string;
-}
-
-/**
- * Base class for our property types
- *
- * @property kind: The kind of property
- * @property __raw: The original TSP property
- * @property type: The type of the property
- * @property name: The name of the property in our client SDKs
- * @property description: Description for the property
- * @property details: Optional details of the property
- * @property apiVersions: Api versions the property is available for
- * @property onClient: Whether the property is on the client
- * @property optional: Whether its an optional property
- */
-@doc("Base class for our property types")
-@discriminator("kind")
-model SdkModelPropertyType {
- __raw?: ModelProperty;
- type: SdkType;
- name: string;
- description?: string;
- details?: string;
- apiVersions: string[];
- onClient: boolean;
- optional: boolean;
-}
-
-/**
- * If one to one between method parameter and sdk service parameter:
- * MethodParameter: {
- * name: "pathParam",
- * type: "string",
- * kind: "method"
- * }
- * SdkServiceParameter: {
- * name: "pathParam",
- * type: "string",
- * kind: "path"
- * }
- * mapping: {
- * "": SdkServiceParameter
- * }
- *
- * If one to many between method parameter and sdk service parameter
- * MethodParameter: {
- * name: "input",
- * type: {
- * kind: "model",
- * properties: [
- * {
- * name: "path_param",
- * serializedName: "pathParam"
- * type: "string",
- * kind: "path"
- * },
- * {
- * name: "query_param",
- * name: "queryParam",
- * type: "string",
- * kind: "query"
- * }
- * ]
- * },
- * kind: "method"
- * }
- * PathServiceParameter: {
- * name: "pathParam",
- * serializedName: "pathParam",
- * type: "string",
- * kind: "path",
- * mapping : {
- * logicalPath: "path_param",
- * type: "string",
- * }
- * }
- * QueryServiceParameter: {
- * name: "queryParam",
- * serializedName: "queryParam",
- * type: "string",
- * kind: "query",
- * mapping: {
- * logicalPath: "query_param",
- * type: "string"
- * }
- * }
- */
-/**
- * Represents a mapping from the method to the service or from the service to the method
- */
-model SdkMethodServiceMapping {
- logicalPath: string;
- property: SdkModelPropertyType;
-}
-
-/**
- * Represents a method parameter
- */
-model SdkMethodParameter extends SdkModelPropertyType {
- kind: "method";
-}
-
-/**
- * Represents a property for a body model type
- *
- * @property visibility: Visibility of the model
- * @property discriminator: Whether the property is a discriminator
- * @property serializedName: The name of the property we send over the wire to the services
- */
-model SdkBodyModelPropertyType extends SdkModelPropertyType {
- kind: "property";
- discriminator: boolean;
- serializedName: string;
- visibility?: Visibility;
-}
-
-/**
- * Represents a header parameter
- *
- * @property serializedName: The name of the property we send over the wire to the services
- * @property collectionFormat: The format for a collection of headers
- */
-model SdkHeaderServiceParameter extends SdkModelPropertyType {
- kind: "header";
- serializedName: string;
- collectionFormat?: "multi" | "csv" | "ssv" | "tsv" | "pipes";
- mapping: SdkMethodServiceMapping;
-}
-
-/**
- * Represents a query parameter.
- *
- * @property serializedName: The name of the property we send over the wire to the services
- * @property collectionFormat: The format for a collection of queries
- */
-model SdkQueryServiceParameter extends SdkModelPropertyType {
- kind: "query";
- serializedName: string;
- collectionFormat?: "multi" | "csv" | "ssv" | "tsv" | "pipes";
- mapping: SdkMethodServiceMapping;
-}
-
-/**
- * Represents a path parameter
- *
- * @property serializedName: The name of the property we send over the wire to the services
- * @property urlEncode: Whether to url encode the path parameter
- * @property validation: Any validation for the path parameter. Right now only including validation on path parameters because we only know about path params needing validation.
- * @property location: Whether the parameter is a client-level or operation-level parameter
- */
-model SdkPathServiceParameter extends SdkModelPropertyType {
- kind: "path";
- serializedName: string;
- urlEncode: boolean;
- validation?: SdkValidation;
- mapping: SdkMethodServiceMapping;
- optional: false;
-}
-
-/**
- * Represents an Oauth2Auth flow. Copied from typespec/http because it doesn't export
- *
- * @property id: id of the authentication scheme
- * @property description: Optional description
- * @property flows: Flows for the authentication
- */
-model Oauth2Auth {
- type: "oauth2";
- id: string;
- description?: string;
- flows: TFlows;
-}
-
-/**
- * Credential type for a client
- *
- * @property scheme: Potential schemes for the credential
- */
-model SdkCredentialType extends SdkType {
- kind: "credential";
- scheme: BasicAuth | BearerAuth | ApiKeyAuth | Oauth2Auth;
-}
-
-/**
- * Represents a credential parameter
- *
- * @property type: The type of the credential. Can be either a single SdkCredentialType or a union of SdkCredentialTypes
- */
-model SdkCredentialParameter extends SdkModelPropertyType {
- kind: "credential";
- type: SdkCredentialType | SdkUnionType;
- urlEncode: boolean;
-}
-
-/**
- * Represents the client endpoint parameter
- */
-model SdkEndpointParameter extends SdkModelPropertyType {
- kind: "endpoint";
- type: SdkType; // should be string
- urlEncode: boolean;
- serializedName?: string;
-}
-
-/**
- * Represents a body parameter to the service.
- *
- * @property contentTypes: The content types the body parameter is valid for
- * @property defaultContentType: What content type to send by default if none are specified
- * @property mapping: Mapping from the body parameter to the method parameter
- */
-model SdkBodyServiceParameter extends SdkModelPropertyType {
- kind: "body";
- contentTypes: string[];
- defaultContentType: string;
- mapping: SdkMethodServiceMapping;
-}
-
-/**
- * Model representing all parameters on an operation
- *
- * @property __raw: The original TSP type
- * @property parameters: List of query / header / path parameters
- * @property body: The body parameter, if one exists
- */
-model SdkOperationParameters {
- __raw?: unknown;
- parameters: (SdkQueryParameter | SdkHeaderParameter | SdkPathParameter)[];
- body?: SdkBodyParameter;
-}
-
-/**
- * Representing a response header
- *
- * @property __raw: The original TSP response header
- * @property serializedName: The serialized name of the response header
- * @property type: Type of the response header
- */
-model SdkResponseHeader {
- __raw?: ModelProperty;
- serializedName: string;
- type: SdkType;
-}
-
-/**
- * Representing a possible response of an operation
- *
- * @property __raw: The original TSP response
- * @property type: The type of the response
- */
-@discriminator("kind")
-model SdkResponse {
- __raw: unknown;
- type: SdkType;
-}
-
-/**
- * Represents a response from a service operation
- *
- * @property headers: Headers returned as part of the response
- */
-model SdkServiceOperationResponse extends SdkResponse {
- kind: "operation";
- headers: SdkResponseHeader[];
-}
-
-/**
- * Response from a method that calls a service operation
- *
- * @property mapping: Mapping from the responses of the service operation to the response the method returns
- */
-model SdkServiceMethodResponse extends SdkResponse {
- kind: "method";
- mapping: SdkMethodServiceMapping;
-}
-
-/**
- * Service parameter
- */
-alias SdkServiceParameter = SdkQueryServiceParameter | SdkHeaderServiceParameter | SdkPathServiceParameter;
-
-/**
- * Base class for representation of a client method.
- *
- * @property name: The name of the method
- * @property access: The access level of the method
- * @property parameters: Parameters object for the method
- * @property description: Description of the operation
- * @property details: Optional details of the operation
- */
-@discriminator("kind")
-model SdkMethod {
- name: string; // return myFunc for Python and Java, but include projectedNames
- access: AccessFlags;
- parameters: SdkMethodParameter[];
- description?: string;
- details?: string;
-}
-
-/**
- * Returns an sdk client from a top level client
- *
- * @property response: which client the operation returns
- */
-model SdkClientAccessor extends SdkMethod {
- kind: "client";
- response: SdkClientType;
-}
-
-/**
- * Represents a method that includes a service call
- *
- * @property operation: The operation that calls the service
- * @property response: The model the method returns
- * @property exceptions: Exceptions of the method
- */
-model SdkServiceMethod extends SdkMethod {
- kind: "methodforserviceoperation";
- operation: SdkServiceOperation;
- response: SdkServiceMethodResponse;
- exceptions: SdkServiceMethodResponse[];
-}
-
-/**
- * Represents an operation to the service. Is HTTP specific.
- *
- * @property __raw: The original TSP HTTP operation
- * @property path: Path of the operation
- * @property verb: Http verb
- * @property parameters: HTTP parameters for the service operation. These parameters will contain a mapping from the method to themselves
- * @property responses: Non-error responses of the operation
- * @property exceptions: Exceptions of the operation
- */
-@discriminator("kind")
-model SdkServiceOperation {
- __raw: unknown;
- path: string;
- verb: string;
- parameters: SdkServiceParameter[];
- bodyParameter?: SdkBodyServiceParameter;
- responses: Record; // int status code -> SdkServiceOperationResponse
- exception: SdkServiceOperationResponse;
-}
-
-/**
- * Representation of a basic operation
- *
- * @property kind: Discriminator for the type of operation
- */
-model SdkBasicServiceOperation extends SdkServiceOperation {
- kind: "basic";
-}
-
-/**
- * Sdk paging operation options
- *
- * @property __raw_paged_metadata: The original TSP paging metadata
- * @property itemsMapping: Mapping of the items to return in the iterable
- * @property nextLinkPath: Where to get the next link. Off if we know there won't be a second page
- * @property nextLinkOperation: The operation to call to get the next page, if there is one
- */
-alias SdkPagingServiceOperationOptions = {
- __raw_paged_metadata: unknown;
- itemsMapping: SdkMethodServiceMapping;
- nextLinkPath?: string;
- nextLinkOperation?: SdkServiceOperation;
-};
-
-/**
- * Representation of a paging operation
- */
-model SdkPagingServiceOperation extends SdkServiceOperation {
- kind: "paging";
- ...SdkPagingServiceOperationOptions;
-}
-
-/**
- * Sdk LRO operation options
- *
- * @property __raw_lro_metadata: The original TSP LRO metadata
- */
-alias SdkLroServiceOperationOptions = {
- __raw_lro_metadata: unknown;
-};
-
-/**
- * Representation of a lro operation
- */
-model SdkLroServiceOperation extends SdkServiceOperation {
- kind: "lro";
- ...SdkLroServiceOperationOptions;
-}
-
-/**
- * Representation of a lro paging operation
- */
-model SdkLroPagingServiceOperation extends SdkServiceOperation {
- kind: "lropaging";
- ...SdkPagingServiceOperationOptions;
- ...SdkLroServiceOperationOptions;
-}
-
-/**
- * Base class for validation on types
- *
- * @property kind: Kind for validation
- */
-@discriminator("kind")
-model SdkValidation {}
-
-/**
- * Validations for a string type
- *
- * @property pattern: Any validation on the pattern of the string
- * @property minLength: Validation on the min length of the string
- * @property maxLength: Validation on the max length of the string
- */
-model SdkStringValidation extends SdkValidation {
- kind: "string";
- pattern?: string;
- minLength?: int32;
- maxLength?: int32;
-}
-
-/**
- * Validations for a numeric type
- *
- * @template Kind Kind of the numeric type
- * @template Type SdkType of the min / max value
- * @property minValue: Validation on the min value of the number
- * @property maxValue: Validation on the max value of the number
- */
-model SdkNumericValidationTemplate extends SdkValidation {
- kind: Kind;
- minValue?: Type;
- maxValue?: Type;
-}
-
-/**
- * Validation for an int32 type
- */
-model SdkInt32Validation is SdkNumericValidationTemplate<"int32", int32>;
-
-/**
- * Validation for an int64 type
- */
-model SdkInt64Validation is SdkNumericValidationTemplate<"int64", int64>;
-
-/**
- * Validation for an float32 type
- */
-model SdkFloat32Validation is SdkNumericValidationTemplate<"float32", float32>;
-
-/**
- * Validation for an float64 type
- */
-model SdkFloat64Validation is SdkNumericValidationTemplate<"float64", float64>;
-
-/**
- * Representation of an SdkClient
- *
- * @property name: name of the client
- * @property description: documentation of the client
- * @property details: details of the client
- * @property initialization: initialization options for the client. Will include endpoint and credential options. Name defaults to {SdkClient.name}Options
- * @property methods: list of methods on the client. Can also be methods that return other clients.
- * @property apiVersions: List of api versions the client is available for
- * @property nameSpace: fully qualified namespace of the client
- * @property arm: is the client an arm client
- */
-model SdkClientType {
- kind: "client";
- name: string;
- description?: string;
- details?: string;
- initialization: SdkModelType;
- methods: SdkMethod[];
-
- // TODO: we have groups of clients (for example, network client is a grouping of resource clients).
- // these do not have api versions. Should we do separate models for these? Or keep api versions as optional?
- apiVersions: string[]; // look into the sorting of this, if versioning package already does order it
-
- nameSpace: string;
- arm: boolean;
-}
-
-/**
- * Represents and entire SDK package
- *
- * @property name: Name of the package
- * @property rootNamespace: Root namespace of the package
- * @property clients: List of clients in the package
- * @property models: List of models in the package
- * @property enums: List of enums in the package
- */
-model SdkPackage {
- name: string;
- rootNamespace: string; // TODO: how do we want to define this for languages in client.tsp. few samples for each language to fill in
- clients: SdkClientType[];
- models: SdkModelType[]; // 2 options: Have it as a function that takes in filter, or add filter as a context parameter
- enums: SdkEnumType[];
-}
-
-/**
- * Context that is passed around for an emitter's instance in TCGC.
- *
- * @property program: The TypeSpec program, a Program type.
- * @property emitContext: The emit context of the tsp emitter. Pass the full library name of your emitter
- * @property generateProtocolMethods: Whether to generate protocol methods by default
- * @property generateConvenienceMethods: Whether to generate convenience methods by default
- * @property filterOutCoreModels: Whether to filter out core models in returned models. Defaults to true.
- * @property packageName: The name of the package
- * @property emitterName: The name of the emitter. If not passed, we will find the value ourselves.
- * @property modelsMap: Type map we create to keep track of all models
- * @property operationModelsMap: Type map we create to keep track of mapping between operations and the models they use. Used for transitive closure on access.
- */
-model SdkContext {
- program: unknown;
- emitContext: unknown;
- generateProtocolMethods: boolean;
- generateConvenienceMethods: boolean;
- filterOutCoreModels: boolean = true;
- packageName?: string;
- emitterName?: string;
- modelsMap?: Record;
- operationModelsMap?: Record;
- returnEntireLroResult: boolean;
-}
-
-/**
- * getAllModelsRequestOptions
- *
- * @property input: boolean determining whether to return input models. Default is true
- * @property output: boolean determining whether to return output models. Default is true
- */
-alias GetAllModelsRequestOptions = {
- input: boolean = true;
- output: boolean = true;
-};
-
-/**
- * Return all of the types we want to generate filtered by usage flags if filter is specified.
- *
- * @param context: the sdk context
- * @returns List of models and enums to generate as types
- */
-op getAllModels(
- @doc("Sdk context")
- context: SdkContext,
-
- ...GetAllModelsRequestOptions,
-): (SdkModelType | SdkEnumType)[];
diff --git a/packages/typespec-client-generator-core/package.json b/packages/typespec-client-generator-core/package.json
index 8ac20021d8..51678c95c0 100644
--- a/packages/typespec-client-generator-core/package.json
+++ b/packages/typespec-client-generator-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@azure-tools/typespec-client-generator-core",
- "version": "0.44.1",
+ "version": "0.44.2",
"author": "Microsoft Corporation",
"description": "TypeSpec Data Plane Generation library",
"homepage": "https://azure.github.io/typespec-azure",
@@ -62,6 +62,7 @@
"@azure-tools/typespec-azure-core": "workspace:~",
"@typespec/compiler": "workspace:~",
"@typespec/http": "workspace:~",
+ "@typespec/openapi": "workspace:~",
"@typespec/rest": "workspace:~",
"@typespec/versioning": "workspace:~"
},
@@ -72,10 +73,10 @@
"@typespec/compiler": "workspace:~",
"@typespec/http": "workspace:~",
"@typespec/library-linter": "workspace:~",
+ "@typespec/openapi": "workspace:~",
"@typespec/prettier-plugin-typespec": "workspace:~",
"@typespec/rest": "workspace:~",
"@typespec/tspd": "workspace:~",
- "@typespec/versioning": "workspace:~",
"@typespec/xml": "workspace:~",
"@vitest/coverage-v8": "^2.0.4",
"@vitest/ui": "^2.0.4",
diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts
index 71388e1393..6b51345c49 100644
--- a/packages/typespec-client-generator-core/src/decorators.ts
+++ b/packages/typespec-client-generator-core/src/decorators.ts
@@ -17,6 +17,7 @@ import {
Type,
Union,
createDiagnosticCollector,
+ getDiscriminator,
getNamespaceFullName,
getProjectedName,
ignoreDiagnostics,
@@ -44,6 +45,7 @@ import {
UsageDecorator,
} from "../generated-defs/Azure.ClientGenerator.Core.js";
import { defaultDecoratorsAllowList } from "./configs.js";
+import { handleClientExamples } from "./example.js";
import {
AccessFlags,
LanguageScopes,
@@ -56,7 +58,12 @@ import {
TCGCContext,
UsageFlags,
} from "./interfaces.js";
-import { AllScopes, clientNameKey, parseEmitterName } from "./internal-utils.js";
+import {
+ AllScopes,
+ clientNameKey,
+ getValidApiVersion,
+ parseEmitterName,
+} from "./internal-utils.js";
import { createStateSymbol, reportDiagnostic } from "./lib.js";
import { getSdkPackage } from "./package.js";
import { getLibraryName } from "./public-utils.js";
@@ -245,14 +252,7 @@ function serviceVersioningProjection(context: TCGCContext, client: SdkClient) {
?.getVersions()
.map((x) => x.value);
if (!allApiVersions) return;
- let apiVersion = context.apiVersion;
- if (
- apiVersion === "latest" ||
- apiVersion === undefined ||
- !allApiVersions.includes(apiVersion)
- ) {
- apiVersion = allApiVersions[allApiVersions.length - 1];
- }
+ const apiVersion = getValidApiVersion(context, allApiVersions);
if (apiVersion === undefined) return;
const versionProjections = buildVersionProjections(context.program, client.service).filter(
(v) => apiVersion === v.version
@@ -628,14 +628,15 @@ export interface CreateSdkContextOptions {
additionalDecorators?: string[];
}
-export function createSdkContext<
+export async function createSdkContext<
TOptions extends Record = SdkEmitterOptions,
TServiceOperation extends SdkServiceOperation = SdkHttpOperation,
>(
context: EmitContext,
emitterName?: string,
options?: CreateSdkContextOptions
-): SdkContext {
+): Promise> {
+ const diagnostics = createDiagnosticCollector();
const protocolOptions = true; // context.program.getLibraryOptions("generate-protocol-methods");
const convenienceOptions = true; // context.program.getLibraryOptions("generate-convenience-methods");
const generateProtocolMethods = context.options["generate-protocol-methods"] ?? protocolOptions;
@@ -655,15 +656,15 @@ export function createSdkContext<
packageName: context.options["package-name"],
flattenUnionAsEnum: context.options["flatten-union-as-enum"] ?? true,
apiVersion: options?.versioning?.strategy === "ignore" ? "all" : context.options["api-version"],
+ examplesDirectory: context.options["examples-directory"],
decoratorsAllowList: [...defaultDecoratorsAllowList, ...(options?.additionalDecorators ?? [])],
previewStringRegex: options?.versioning?.previewStringRegex || tcgcContext.previewStringRegex,
};
- sdkContext.sdkPackage = getSdkPackage(sdkContext);
- if (sdkContext.diagnostics) {
- sdkContext.diagnostics = sdkContext.diagnostics.concat(
- sdkContext.sdkPackage.diagnostics // eslint-disable-line deprecation/deprecation
- );
+ sdkContext.sdkPackage = diagnostics.pipe(getSdkPackage(sdkContext));
+ for (const client of sdkContext.sdkPackage.clients) {
+ diagnostics.pipe(await handleClientExamples(sdkContext, client));
}
+ sdkContext.diagnostics = sdkContext.diagnostics.concat(diagnostics.diagnostics);
return sdkContext;
}
@@ -991,6 +992,13 @@ export const $flattenProperty: FlattenPropertyDecorator = (
target: ModelProperty,
scope?: LanguageScopes
) => {
+ if (getDiscriminator(context.program, target.type)) {
+ reportDiagnostic(context.program, {
+ code: "flatten-polymorphism",
+ format: {},
+ target: target,
+ });
+ }
setScopedDecoratorData(context, $flattenProperty, flattenPropertyKey, target, true, scope); // eslint-disable-line deprecation/deprecation
};
diff --git a/packages/typespec-client-generator-core/src/example.ts b/packages/typespec-client-generator-core/src/example.ts
new file mode 100644
index 0000000000..6043c0011a
--- /dev/null
+++ b/packages/typespec-client-generator-core/src/example.ts
@@ -0,0 +1,632 @@
+import {
+ Diagnostic,
+ DiagnosticCollector,
+ NoTarget,
+ SourceFile,
+ createDiagnosticCollector,
+ resolvePath,
+} from "@typespec/compiler";
+import { HttpStatusCodeRange } from "@typespec/http";
+import { resolveOperationId } from "@typespec/openapi";
+import {
+ SdkAnyExample,
+ SdkArrayExample,
+ SdkArrayType,
+ SdkBodyModelPropertyType,
+ SdkClientType,
+ SdkDictionaryExample,
+ SdkDictionaryType,
+ SdkHttpOperation,
+ SdkHttpOperationExample,
+ SdkHttpParameter,
+ SdkHttpParameterExample,
+ SdkHttpResponse,
+ SdkHttpResponseExample,
+ SdkModelExample,
+ SdkModelPropertyType,
+ SdkModelType,
+ SdkNullExample,
+ SdkServiceMethod,
+ SdkServiceOperation,
+ SdkType,
+ SdkTypeExample,
+ SdkUnionExample,
+ TCGCContext,
+ isSdkFloatKind,
+ isSdkIntKind,
+} from "./interfaces.js";
+import { getValidApiVersion } from "./internal-utils.js";
+import { createDiagnostic } from "./lib.js";
+
+interface LoadedExample {
+ readonly relativePath: string;
+ readonly file: SourceFile;
+ readonly data: any;
+}
+
+/**
+ * Load all examples for a client
+ *
+ * @param context
+ * @param apiVersion
+ * @returns a map of all operations' examples, key is operation's operation id,
+ * value is a map of examples, key is example's title, value is example's details
+ */
+async function loadExamples(
+ context: TCGCContext,
+ apiVersion: string | undefined
+): Promise<[Map>, readonly Diagnostic[]]> {
+ const diagnostics = createDiagnosticCollector();
+ if (!context.examplesDirectory) {
+ return diagnostics.wrap(new Map());
+ }
+
+ const exampleDir = apiVersion
+ ? resolvePath(context.examplesDirectory, apiVersion)
+ : resolvePath(context.examplesDirectory);
+ try {
+ if (!(await context.program.host.stat(exampleDir)).isDirectory())
+ return diagnostics.wrap(new Map());
+ } catch (err) {
+ diagnostics.add(
+ createDiagnostic({
+ code: "example-loading",
+ messageId: "noDirectory",
+ format: { directory: exampleDir },
+ target: NoTarget,
+ })
+ );
+ return diagnostics.wrap(new Map());
+ }
+
+ const map = new Map>();
+ const exampleFiles = await context.program.host.readDir(exampleDir);
+ for (const fileName of exampleFiles) {
+ try {
+ const exampleFile = await context.program.host.readFile(resolvePath(exampleDir, fileName));
+ const example = JSON.parse(exampleFile.text);
+ if (!example.operationId || !example.title) {
+ diagnostics.add(
+ createDiagnostic({
+ code: "example-loading",
+ messageId: "noOperationId",
+ format: { filename: fileName },
+ target: NoTarget,
+ })
+ );
+ continue;
+ }
+
+ if (!map.has(example.operationId.toLowerCase())) {
+ map.set(example.operationId.toLowerCase(), {});
+ }
+ const examples = map.get(example.operationId.toLowerCase())!;
+
+ if (example.title in examples) {
+ diagnostics.add(
+ createDiagnostic({
+ code: "duplicate-example-file",
+ target: NoTarget,
+ format: {
+ filename: fileName,
+ operationId: example.operationId,
+ title: example.title,
+ },
+ })
+ );
+ }
+
+ examples[example.title] = {
+ relativePath: fileName,
+ file: exampleFile,
+ data: example,
+ };
+ } catch (err) {
+ diagnostics.add(
+ createDiagnostic({
+ code: "example-loading",
+ messageId: "default",
+ format: { filename: fileName, error: err?.toString() ?? "" },
+ target: NoTarget,
+ })
+ );
+ }
+ }
+ return diagnostics.wrap(map);
+}
+
+export async function handleClientExamples(
+ context: TCGCContext,
+ client: SdkClientType
+): Promise<[void, readonly Diagnostic[]]> {
+ const diagnostics = createDiagnosticCollector();
+
+ const examples = diagnostics.pipe(
+ await loadExamples(context, getValidApiVersion(context, client.apiVersions))
+ );
+ const methodQueue = [...client.methods];
+ while (methodQueue.length > 0) {
+ const method = methodQueue.pop()!;
+ if (method.kind === "clientaccessor") {
+ methodQueue.push(...method.response.methods);
+ } else {
+ // since operation could have customization in client.tsp, we need to handle all the original operation (exclude the templated operation)
+ let operation = method.__raw;
+ while (operation && operation.templateMapper === undefined) {
+ const operationId = resolveOperationId(context.program, operation).toLowerCase();
+ if (examples.has(operationId)) {
+ diagnostics.pipe(handleMethodExamples(context, method, examples.get(operationId)!));
+ break;
+ }
+ operation = operation.sourceOperation;
+ }
+ }
+ }
+ return diagnostics.wrap(undefined);
+}
+
+function handleMethodExamples(
+ context: TCGCContext,
+ method: SdkServiceMethod,
+ examples: Record
+): [void, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+
+ if (method.operation.kind === "http") {
+ diagnostics.pipe(handleHttpOperationExamples(method.operation, examples));
+ if (method.operation.examples) {
+ if (!context.__httpOperationExamples) {
+ context.__httpOperationExamples = new Map();
+ }
+ context.__httpOperationExamples!.set(method.operation.__raw, method.operation.examples);
+ }
+ }
+
+ return diagnostics.wrap(undefined);
+}
+
+function handleHttpOperationExamples(
+ operation: SdkHttpOperation,
+ examples: Record
+) {
+ const diagnostics = createDiagnosticCollector();
+ operation.examples = [];
+
+ for (const [title, example] of Object.entries(examples)) {
+ const operationExample = {
+ kind: "http",
+ name: title,
+ description: title,
+ filePath: example.file.path,
+ parameters: diagnostics.pipe(
+ handleHttpParameters(
+ operation.bodyParam
+ ? [...operation.parameters, operation.bodyParam]
+ : operation.parameters,
+ example.data,
+ example.relativePath
+ )
+ ),
+ responses: diagnostics.pipe(
+ handleHttpResponses(operation.responses, example.data, example.relativePath)
+ ),
+ rawExample: example.data,
+ } as SdkHttpOperationExample;
+
+ operation.examples.push(operationExample);
+ }
+
+ return diagnostics.wrap(undefined);
+}
+
+function handleHttpParameters(
+ parameters: SdkHttpParameter[],
+ example: any,
+ relativePath: string
+): [SdkHttpParameterExample[], readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ const parameterExamples = [] as SdkHttpParameterExample[];
+ if ("parameters" in example && typeof example.parameters === "object") {
+ for (const name of Object.keys(example.parameters)) {
+ let parameter = parameters.find(
+ (p) => (p.kind !== "body" && p.serializedName === name) || p.name === name
+ );
+ // fallback to body in example for any body parameter
+ if (!parameter && name === "body") {
+ parameter = parameters.find((p) => p.kind === "body");
+ }
+ if (parameter) {
+ const value = diagnostics.pipe(
+ getSdkTypeExample(parameter.type, example.parameters[parameter.name], relativePath)
+ );
+ if (value) {
+ parameterExamples.push({
+ parameter,
+ value,
+ });
+ }
+ } else {
+ addExampleValueNoMappingDignostic(
+ diagnostics,
+ { [name]: example.parameters[name] },
+ relativePath
+ );
+ }
+ }
+ }
+ return diagnostics.wrap(parameterExamples);
+}
+
+function handleHttpResponses(
+ responses: Map,
+ example: any,
+ relativePath: string
+): [Map, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ const responseExamples = new Map();
+ if ("responses" in example && typeof example.responses === "object") {
+ for (const code of Object.keys(example.responses)) {
+ const statusCode = parseInt(code, 10);
+ let found = false;
+ for (const [responseCode, response] of responses.entries()) {
+ if (responseCode === statusCode) {
+ responseExamples.set(
+ statusCode,
+ diagnostics.pipe(handleHttpResponse(response, example.responses[code], relativePath))
+ );
+ found = true;
+ break;
+ } else if (
+ typeof responseCode === "object" &&
+ responseCode.start <= statusCode &&
+ responseCode.end >= statusCode
+ ) {
+ responseExamples.set(
+ statusCode,
+ diagnostics.pipe(handleHttpResponse(response, example.responses[code], relativePath))
+ );
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ addExampleValueNoMappingDignostic(
+ diagnostics,
+ { [code]: example.responses[code] },
+ relativePath
+ );
+ }
+ }
+ }
+ return diagnostics.wrap(responseExamples);
+}
+
+function handleHttpResponse(
+ response: SdkHttpResponse,
+ example: any,
+ relativePath: string
+): [SdkHttpResponseExample, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ const responseExample = {
+ response,
+ headers: [],
+ } as SdkHttpResponseExample;
+ if (typeof example === "object") {
+ for (const name of Object.keys(example)) {
+ if (name === "description") {
+ continue;
+ } else if (name === "body") {
+ if (response.type) {
+ responseExample.bodyValue = diagnostics.pipe(
+ getSdkTypeExample(response.type, example.body, relativePath)
+ );
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, { body: example.body }, relativePath);
+ }
+ } else if (name === "headers") {
+ for (const subName of Object.keys(example.headers)) {
+ const header = response.headers.find((p) => p.serializedName === subName);
+ if (header) {
+ const value = diagnostics.pipe(
+ getSdkTypeExample(header.type, example[name][subName], relativePath)
+ );
+ if (value) {
+ responseExample.headers.push({
+ header,
+ value,
+ });
+ }
+ } else {
+ addExampleValueNoMappingDignostic(
+ diagnostics,
+ { [subName]: example[name][subName] },
+ relativePath
+ );
+ }
+ }
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, { [name]: example[name] }, relativePath);
+ }
+ }
+ }
+ return diagnostics.wrap(responseExample);
+}
+
+function getSdkTypeExample(
+ type: SdkType | SdkModelPropertyType,
+ example: any,
+ relativePath: string
+): [SdkTypeExample | undefined, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+
+ if (isSdkIntKind(type.kind) || isSdkFloatKind(type.kind)) {
+ return getSdkBaseTypeExample("number", type as SdkType, example, relativePath);
+ } else {
+ switch (type.kind) {
+ case "string":
+ case "bytes":
+ return getSdkBaseTypeExample("string", type as SdkType, example, relativePath);
+ case "boolean":
+ return getSdkBaseTypeExample("boolean", type as SdkType, example, relativePath);
+ case "url":
+ case "plainDate":
+ case "plainTime":
+ return getSdkBaseTypeExample("string", type as SdkType, example, relativePath);
+ case "nullable":
+ if (example === null) {
+ return diagnostics.wrap({
+ kind: "null",
+ type,
+ value: null,
+ } as SdkNullExample);
+ } else {
+ return getSdkTypeExample(type.type, example, relativePath);
+ }
+ case "any":
+ return diagnostics.wrap({
+ kind: "any",
+ type,
+ value: example,
+ } as SdkAnyExample);
+ case "constant":
+ if (example === type.value) {
+ return getSdkBaseTypeExample(
+ typeof type.value as "string" | "number" | "boolean",
+ type,
+ example,
+ relativePath
+ );
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, example, relativePath);
+ return diagnostics.wrap(undefined);
+ }
+ case "enum":
+ if (type.values.some((v) => v.value === example)) {
+ return getSdkBaseTypeExample(
+ typeof example as "string" | "number",
+ type,
+ example,
+ relativePath
+ );
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, example, relativePath);
+ return diagnostics.wrap(undefined);
+ }
+ case "enumvalue":
+ if (type.value === example) {
+ return getSdkBaseTypeExample(
+ typeof example as "string" | "number",
+ type,
+ example,
+ relativePath
+ );
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, example, relativePath);
+ return diagnostics.wrap(undefined);
+ }
+ case "utcDateTime":
+ case "offsetDateTime":
+ case "duration":
+ const inner = diagnostics.pipe(getSdkTypeExample(type.wireType, example, relativePath));
+ if (inner) {
+ inner.type = type;
+ }
+ return diagnostics.wrap(inner);
+ case "union":
+ return diagnostics.wrap({
+ kind: "union",
+ type,
+ value: example,
+ } as SdkUnionExample);
+ case "array":
+ return getSdkArrayExample(type, example, relativePath);
+ case "dict":
+ return getSdkDictionaryExample(type, example, relativePath);
+ case "model":
+ return getSdkModelExample(type, example, relativePath);
+ }
+ }
+ return diagnostics.wrap(undefined);
+}
+
+function getSdkBaseTypeExample(
+ kind: "string" | "number" | "boolean",
+ type: SdkType,
+ example: any,
+ relativePath: string
+): [SdkTypeExample | undefined, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ if (typeof example === kind) {
+ return diagnostics.wrap({
+ kind,
+ type,
+ value: example,
+ } as SdkTypeExample);
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, example, relativePath);
+ }
+ return diagnostics.wrap(undefined);
+}
+
+function getSdkArrayExample(
+ type: SdkArrayType,
+ example: any,
+ relativePath: string
+): [SdkArrayExample | undefined, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ if (Array.isArray(example)) {
+ const arrayExample = [] as SdkTypeExample[];
+ for (const item of example) {
+ const result = diagnostics.pipe(getSdkTypeExample(type.valueType, item, relativePath));
+ if (result) {
+ arrayExample.push(result);
+ }
+ }
+ return diagnostics.wrap({
+ kind: "array",
+ type,
+ value: arrayExample,
+ } as SdkArrayExample);
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, example, relativePath);
+ return diagnostics.wrap(undefined);
+ }
+}
+
+function getSdkDictionaryExample(
+ type: SdkDictionaryType,
+ example: any,
+ relativePath: string
+): [SdkDictionaryExample | undefined, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ if (typeof example === "object") {
+ const dictionaryExample = {} as Record;
+ for (const key of Object.keys(example)) {
+ const result = diagnostics.pipe(
+ getSdkTypeExample(type.valueType, example[key], relativePath)
+ );
+ if (result) {
+ dictionaryExample[key] = result;
+ }
+ }
+ return diagnostics.wrap({
+ kind: "dict",
+ type,
+ value: dictionaryExample,
+ } as SdkDictionaryExample);
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, example, relativePath);
+ return diagnostics.wrap(undefined);
+ }
+}
+
+function getSdkModelExample(
+ type: SdkModelType,
+ example: any,
+ relativePath: string
+): [SdkModelExample | undefined, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ if (typeof example === "object") {
+ // handle discriminated model
+ if (type.discriminatorProperty) {
+ if (
+ type.discriminatorProperty.name in example &&
+ example[type.discriminatorProperty.name] in type.discriminatedSubtypes!
+ ) {
+ return getSdkModelExample(
+ type.discriminatedSubtypes![example[type.discriminatorProperty.name]],
+ example,
+ relativePath
+ );
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, example, relativePath);
+ return diagnostics.wrap(undefined);
+ }
+ }
+
+ let additionalPropertiesType: SdkType | undefined;
+ const additionalProperties: Record = new Map();
+ const additionalPropertiesExample: Record = {};
+
+ const properties: Map = new Map();
+ const propertiesExample: Record = {};
+
+ // get all properties type and additional properties type if exist
+ const modelQueue = [type];
+ while (modelQueue.length > 0) {
+ const model = modelQueue.pop()!;
+ for (let property of model.properties) {
+ property = property as SdkBodyModelPropertyType;
+ if (!properties.has(property.serializedName)) {
+ properties.set(property.serializedName, property);
+ }
+ }
+ if (model.additionalProperties && additionalPropertiesType === undefined) {
+ additionalPropertiesType = model.additionalProperties;
+ }
+ if (model.baseModel) {
+ modelQueue.push(model.baseModel);
+ }
+ }
+
+ for (const name of Object.keys(example)) {
+ const property = properties.get(name);
+ if (property) {
+ const result = diagnostics.pipe(
+ getSdkTypeExample(property.type, example[name], relativePath)
+ );
+ if (result) {
+ propertiesExample[name] = result;
+ }
+ } else {
+ additionalProperties[name] = example[name];
+ }
+ }
+
+ // handle additional properties
+ if (Object.keys(additionalProperties).length > 0) {
+ if (additionalPropertiesType) {
+ for (const [name, value] of Object.entries(additionalProperties)) {
+ const result = diagnostics.pipe(
+ getSdkTypeExample(additionalPropertiesType, value, relativePath)
+ );
+ if (result) {
+ additionalPropertiesExample[name] = result;
+ }
+ }
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, additionalProperties, relativePath);
+ }
+ }
+
+ return diagnostics.wrap({
+ kind: "model",
+ type,
+ value: propertiesExample,
+ additionalPropertiesValue:
+ Object.keys(additionalPropertiesExample).length > 0
+ ? additionalPropertiesExample
+ : undefined,
+ } as SdkModelExample);
+ } else {
+ addExampleValueNoMappingDignostic(diagnostics, example, relativePath);
+ return diagnostics.wrap(undefined);
+ }
+}
+
+function addExampleValueNoMappingDignostic(
+ diagnostics: DiagnosticCollector,
+ value: any,
+ relativePath: string
+) {
+ diagnostics.add(
+ createDiagnostic({
+ code: "example-value-no-mapping",
+ target: NoTarget,
+ format: {
+ value: JSON.stringify(value),
+ relativePath,
+ },
+ })
+ );
+}
diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts
index 01712f1f3a..07ea45cba5 100644
--- a/packages/typespec-client-generator-core/src/http.ts
+++ b/packages/typespec-client-generator-core/src/http.ts
@@ -56,6 +56,7 @@ import {
addFormatInfo,
getClientTypeWithDiagnostics,
getSdkModelPropertyTypeBase,
+ getTypeSpecBuiltInType,
} from "./types.js";
export function getSdkHttpOperation(
@@ -233,11 +234,7 @@ function createContentTypeOrAcceptHeader(
bodyObject: SdkBodyParameter | SdkHttpResponse
): Omit {
const name = bodyObject.kind === "body" ? "contentType" : "accept";
- let type: SdkType = {
- kind: "string",
- encode: "string",
- decorators: [],
- };
+ let type: SdkType = getTypeSpecBuiltInType(context, "string");
// for contentType, we treat it as a constant IFF there's one value and it's application/json.
// this is to prevent a breaking change when a service adds more content types in the future.
// e.g. the service accepting image/png then later image/jpeg should _not_ be a breaking change.
diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts
index 03953e8b6d..f4bf6cfd00 100644
--- a/packages/typespec-client-generator-core/src/interfaces.ts
+++ b/packages/typespec-client-generator-core/src/interfaces.ts
@@ -5,6 +5,7 @@ import {
DurationKnownEncoding,
EmitContext,
Interface,
+ IntrinsicScalarName,
Model,
ModelProperty,
Namespace,
@@ -48,7 +49,9 @@ export interface TCGCContext {
__rawClients?: SdkClient[];
apiVersion?: string;
__service_projection?: Map;
+ __httpOperationExamples?: Map;
originalProgram: Program;
+ examplesDirectory?: string;
decoratorsAllowList?: string[];
previewStringRegex: RegExp;
}
@@ -68,6 +71,7 @@ export interface SdkEmitterOptions {
"package-name"?: string;
"flatten-union-as-enum"?: boolean;
"api-version"?: string;
+ "examples-directory"?: string;
}
export interface SdkClient {
@@ -151,8 +155,29 @@ export type SdkType =
export interface SdkBuiltInType extends SdkTypeBase {
kind: SdkBuiltInKinds;
encode: string;
+ name: string;
+ baseType?: SdkBuiltInType;
+ crossLanguageDefinitionId: string;
}
+type TypeEquality = keyof T extends keyof U
+ ? keyof U extends keyof T
+ ? true
+ : false
+ : false;
+
+// these two vars are used to validate whether our SdkBuiltInKinds are exhaustive for all possible values from typespec
+// if it is not, a typescript compilation error will be thrown here.
+const _: TypeEquality, never> = true;
+const __: TypeEquality, never> = true;
+
+type SupportedBuiltInKinds =
+ | keyof typeof SdkIntKindsEnum
+ | keyof typeof SdkFloatingPointKindsEnum
+ | keyof typeof SdkFixedPointKindsEnum
+ | keyof typeof SdkGenericBuiltInStringKindsEnum
+ | keyof typeof SdkBuiltInKindsMiscellaneousEnum;
+
enum SdkIntKindsEnum {
numeric = "numeric",
integer = "integer",
@@ -167,30 +192,20 @@ enum SdkIntKindsEnum {
uint64 = "uint64",
}
-enum SdkFloatKindsEnum {
+enum SdkFloatingPointKindsEnum {
float = "float",
float32 = "float32",
float64 = "float64",
+}
+
+enum SdkFixedPointKindsEnum {
decimal = "decimal",
decimal128 = "decimal128",
}
-const SdkAzureBuiltInStringKindsMapping = {
- uuid: "uuid",
- ipV4Address: "ipV4Address",
- ipV6Address: "ipV6Address",
- eTag: "eTag",
- armId: "armResourceIdentifier",
- azureLocation: "azureLocation",
-};
-
enum SdkGenericBuiltInStringKindsEnum {
string = "string",
- password = "password",
- guid = "guid",
url = "url",
- uri = "uri",
- ipAddress = "ipAddress",
}
enum SdkBuiltInKindsMiscellaneousEnum {
@@ -201,29 +216,21 @@ enum SdkBuiltInKindsMiscellaneousEnum {
any = "any",
}
-export type SdkBuiltInKinds =
- | keyof typeof SdkBuiltInKindsMiscellaneousEnum
- | keyof typeof SdkIntKindsEnum
- | keyof typeof SdkFloatKindsEnum
- | keyof typeof SdkGenericBuiltInStringKindsEnum
- | keyof typeof SdkAzureBuiltInStringKindsMapping;
+export type SdkBuiltInKinds = Exclude | "any";
+
+type SdkBuiltInKindsExcludes = "utcDateTime" | "offsetDateTime" | "duration";
export function getKnownScalars(): Record {
const retval: Record = {};
const typespecNamespace = Object.keys(SdkBuiltInKindsMiscellaneousEnum)
.concat(Object.keys(SdkIntKindsEnum))
- .concat(Object.keys(SdkFloatKindsEnum))
+ .concat(Object.keys(SdkFloatingPointKindsEnum))
+ .concat(Object.keys(SdkFixedPointKindsEnum))
.concat(Object.keys(SdkGenericBuiltInStringKindsEnum));
for (const kind of typespecNamespace) {
if (!isSdkBuiltInKind(kind)) continue; // it will always be true
retval[`TypeSpec.${kind}`] = kind;
}
- for (const kind in SdkAzureBuiltInStringKindsMapping) {
- if (!isSdkBuiltInKind(kind)) continue; // it will always be true
- const kindMappedName =
- SdkAzureBuiltInStringKindsMapping[kind as keyof typeof SdkAzureBuiltInStringKindsMapping];
- retval[`Azure.Core.${kindMappedName}`] = kind;
- }
return retval;
}
@@ -232,8 +239,8 @@ export function isSdkBuiltInKind(kind: string): kind is SdkBuiltInKinds {
kind in SdkBuiltInKindsMiscellaneousEnum ||
isSdkIntKind(kind) ||
isSdkFloatKind(kind) ||
- kind in SdkGenericBuiltInStringKindsEnum ||
- kind in SdkAzureBuiltInStringKindsMapping
+ isSdkFixedPointKind(kind) ||
+ kind in SdkGenericBuiltInStringKindsEnum
);
}
@@ -241,8 +248,12 @@ export function isSdkIntKind(kind: string): kind is keyof typeof SdkIntKindsEnum
return kind in SdkIntKindsEnum;
}
-export function isSdkFloatKind(kind: string): kind is keyof typeof SdkFloatKindsEnum {
- return kind in SdkFloatKindsEnum;
+export function isSdkFloatKind(kind: string): kind is keyof typeof SdkFloatingPointKindsEnum {
+ return kind in SdkFloatingPointKindsEnum;
+}
+
+function isSdkFixedPointKind(kind: string): kind is keyof typeof SdkFixedPointKindsEnum {
+ return kind in SdkFixedPointKindsEnum;
}
const SdkDateTimeEncodingsConst = ["rfc3339", "rfc7231", "unixTimestamp"] as const;
@@ -252,8 +263,11 @@ export function isSdkDateTimeEncodings(encoding: string): encoding is DateTimeKn
}
interface SdkDateTimeTypeBase extends SdkTypeBase {
+ name: string;
+ baseType?: SdkDateTimeType;
encode: DateTimeKnownEncoding;
wireType: SdkBuiltInType;
+ crossLanguageDefinitionId: string;
}
interface SdkUtcDateTimeType extends SdkDateTimeTypeBase {
@@ -283,8 +297,11 @@ export type SdkOffsetDatetimeType = SdkOffsetDateTimeType;
export interface SdkDurationType extends SdkTypeBase {
kind: "duration";
+ name: string;
+ baseType?: SdkDurationType;
encode: DurationKnownEncoding;
wireType: SdkBuiltInType;
+ crossLanguageDefinitionId: string;
}
export interface SdkArrayType extends SdkTypeBase {
@@ -332,6 +349,7 @@ export interface SdkEnumValueType extends SdkTypeBase {
enumType: SdkEnumType;
valueType: SdkBuiltInType;
}
+
export interface SdkConstantType extends SdkTypeBase {
kind: "constant";
value: string | number | boolean | null;
@@ -420,11 +438,28 @@ export type SdkModelPropertyType =
| SdkBodyParameter
| SdkHeaderParameter;
+export interface MultipartOptions {
+ // whether this part is for file
+ isFilePart: boolean;
+ // whether this part is multi in request payload
+ isMulti: boolean;
+ // undefined if filename is not set explicitly in Typespec
+ filename?: SdkModelPropertyType;
+ // undefined if contentType is not set explicitly in Typespec
+ contentType?: SdkModelPropertyType;
+ // defined in Typespec or calculated by Typespec complier
+ defaultContentTypes: string[];
+}
+
export interface SdkBodyModelPropertyType extends SdkModelPropertyTypeBase {
kind: "property";
discriminator: boolean;
serializedName: string;
+ /*
+ @deprecated This property is deprecated. Use `.multipartOptions?.isFilePart` instead.
+ */
isMultipartFileInput: boolean;
+ multipartOptions?: MultipartOptions;
visibility?: Visibility[];
flatten: boolean;
}
@@ -512,6 +547,7 @@ export interface SdkHttpOperation extends SdkServiceOperationBase {
bodyParam?: SdkBodyParameter;
responses: Map;
exceptions: Map;
+ examples?: SdkHttpOperationExample[];
}
/**
@@ -607,10 +643,6 @@ export interface SdkPackage {
clients: SdkClientType[];
models: SdkModelType[];
enums: SdkEnumType[];
- /**
- * @deprecated This property is deprecated. Look at `.diagnostics` on SdkContext instead.
- */
- diagnostics: readonly Diagnostic[];
crossLanguagePackageId: string;
}
@@ -636,4 +668,120 @@ export enum UsageFlags {
Error = 1 << 7,
// Set when model is used in conjunction with an application/json content type.
Json = 1 << 8,
+ // Set when model is used in conjunction with an application/xml content type.
+ Xml = 1 << 9,
+}
+
+interface SdkExampleBase {
+ kind: string;
+ name: string;
+ description: string;
+ filePath: string;
+ rawExample: any;
+}
+
+export interface SdkHttpOperationExample extends SdkExampleBase {
+ kind: "http";
+ parameters: SdkHttpParameterExample[];
+ responses: Map;
+}
+
+export interface SdkHttpParameterExample {
+ parameter: SdkHttpParameter;
+ value: SdkTypeExample;
+}
+
+export interface SdkHttpResponseExample {
+ response: SdkHttpResponse;
+ headers: SdkHttpResponseHeaderExample[];
+ bodyValue?: SdkTypeExample;
+}
+
+export interface SdkHttpResponseHeaderExample {
+ header: SdkServiceResponseHeader;
+ value: SdkTypeExample;
+}
+
+export type SdkTypeExample =
+ | SdkStringExample
+ | SdkNumberExample
+ | SdkBooleanExample
+ | SdkNullExample
+ | SdkAnyExample
+ | SdkArrayExample
+ | SdkDictionaryExample
+ | SdkUnionExample
+ | SdkModelExample;
+
+export interface SdkExampleTypeBase {
+ kind: string;
+ type: SdkType;
+ value: unknown;
+}
+
+export interface SdkStringExample extends SdkExampleTypeBase {
+ kind: "string";
+ type:
+ | SdkBuiltInType
+ | SdkDateTimeType
+ | SdkDurationType
+ | SdkEnumType
+ | SdkEnumValueType
+ | SdkConstantType;
+ value: string;
+}
+
+export interface SdkNumberExample extends SdkExampleTypeBase {
+ kind: "number";
+ type:
+ | SdkBuiltInType
+ | SdkDateTimeType
+ | SdkDurationType
+ | SdkEnumType
+ | SdkEnumValueType
+ | SdkConstantType;
+ value: number;
+}
+
+export interface SdkBooleanExample extends SdkExampleTypeBase {
+ kind: "boolean";
+ type: SdkBuiltInType | SdkConstantType;
+ value: boolean;
+}
+
+export interface SdkNullExample extends SdkExampleTypeBase {
+ kind: "null";
+ type: SdkNullableType;
+ value: null;
+}
+
+export interface SdkAnyExample extends SdkExampleTypeBase {
+ kind: "any";
+ type: SdkBuiltInType;
+ value: unknown;
+}
+
+export interface SdkArrayExample extends SdkExampleTypeBase {
+ kind: "array";
+ type: SdkArrayType;
+ value: SdkTypeExample[];
+}
+
+export interface SdkDictionaryExample extends SdkExampleTypeBase {
+ kind: "dict";
+ type: SdkDictionaryType;
+ value: Record;
+}
+
+export interface SdkUnionExample extends SdkExampleTypeBase {
+ kind: "union";
+ type: SdkUnionType;
+ value: unknown;
+}
+
+export interface SdkModelExample extends SdkExampleTypeBase {
+ kind: "model";
+ type: SdkModelType;
+ value: Record;
+ additionalPropertiesValue?: Record;
}
diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts
index 151930ee85..156c1fcc5a 100644
--- a/packages/typespec-client-generator-core/src/internal-utils.ts
+++ b/packages/typespec-client-generator-core/src/internal-utils.ts
@@ -41,6 +41,7 @@ import {
getHttpOperationWithCache,
isApiVersion,
} from "./public-utils.js";
+import { getClientTypeWithDiagnostics } from "./types.js";
export const AllScopes = Symbol.for("@azure-core/typespec-client-generator-core/all-scopes");
@@ -296,7 +297,7 @@ export function getTypeDecorators(
};
for (let i = 0; i < decorator.args.length; i++) {
decoratorInfo.arguments[decorator.definition.parameters[i].name] = diagnostics.pipe(
- getDecoratorArgValue(decorator.args[i].jsValue, type, decoratorName)
+ getDecoratorArgValue(context, decorator.args[i].jsValue, type, decoratorName)
);
}
retval.push(decoratorInfo);
@@ -307,6 +308,7 @@ export function getTypeDecorators(
}
function getDecoratorArgValue(
+ context: TCGCContext,
arg:
| Type
| Record
@@ -323,7 +325,7 @@ function getDecoratorArgValue(
const diagnostics = createDiagnosticCollector();
if (typeof arg === "object" && arg !== null && "kind" in arg) {
if (arg.kind === "EnumMember") {
- return diagnostics.wrap(arg.value ?? arg.name);
+ return diagnostics.wrap(diagnostics.pipe(getClientTypeWithDiagnostics(context, arg)));
}
if (arg.kind === "String" || arg.kind === "Number" || arg.kind === "Boolean") {
return diagnostics.wrap(arg.value);
@@ -475,11 +477,24 @@ export function getAnyType(
const diagnostics = createDiagnosticCollector();
return diagnostics.wrap({
kind: "any",
+ name: "any",
encode: "string",
+ crossLanguageDefinitionId: "",
decorators: diagnostics.pipe(getTypeDecorators(context, type)),
});
}
+export function getValidApiVersion(context: TCGCContext, versions: string[]): string | undefined {
+ let apiVersion = context.apiVersion;
+ if (apiVersion === "all") {
+ return apiVersion;
+ }
+ if (apiVersion === "latest" || apiVersion === undefined || !versions.includes(apiVersion)) {
+ apiVersion = versions[versions.length - 1];
+ }
+ return apiVersion;
+}
+
export function getHttpOperationResponseHeaders(
response: HttpOperationResponseContent
): ModelProperty[] {
@@ -526,3 +541,8 @@ export function isJsonContentType(contentType: string): boolean {
const regex = new RegExp(/^(application|text)\/(.+\+)?json$/);
return regex.test(contentType);
}
+
+export function isXmlContentType(contentType: string): boolean {
+ const regex = new RegExp(/^(application|text)\/(.+\+)?xml$/);
+ return regex.test(contentType);
+}
diff --git a/packages/typespec-client-generator-core/src/lib.ts b/packages/typespec-client-generator-core/src/lib.ts
index 8d39e92393..a951ea8544 100644
--- a/packages/typespec-client-generator-core/src/lib.ts
+++ b/packages/typespec-client-generator-core/src/lib.ts
@@ -166,6 +166,32 @@ export const $lib = createTypeSpecLibrary({
nonDecorator: paramMessage`Client name: "${"name"}" is defined somewhere causing nameing conflicts in language scope: "${"scope"}"`,
},
},
+ "example-loading": {
+ severity: "warning",
+ messages: {
+ default: paramMessage`Skipped loading invalid example file: ${"filename"}. Error: ${"error"}`,
+ noDirectory: paramMessage`Skipping example loading from ${"directory"} because there was an error reading the directory.`,
+ noOperationId: paramMessage`Skipping example file ${"filename"} because it does not contain an operationId and/or title.`,
+ },
+ },
+ "duplicate-example-file": {
+ severity: "error",
+ messages: {
+ default: paramMessage`Example file ${"filename"} uses duplicate title '${"title"}' for operationId '${"operationId"}'`,
+ },
+ },
+ "example-value-no-mapping": {
+ severity: "warning",
+ messages: {
+ default: paramMessage`Value in example file '${"relativePath"}' does not follow its definition:\n${"value"}`,
+ },
+ },
+ "flatten-polymorphism": {
+ severity: "error",
+ messages: {
+ default: `Cannot flatten property of polymorphic type.`,
+ },
+ },
},
});
diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts
index 7f100a7bf2..9c27693d1a 100644
--- a/packages/typespec-client-generator-core/src/package.ts
+++ b/packages/typespec-client-generator-core/src/package.ts
@@ -13,6 +13,7 @@ import { resolveVersions } from "@typespec/versioning";
import { camelCase } from "change-case";
import {
getAccess,
+ getClientNameOverride,
listClients,
listOperationGroups,
listOperationsInOperationGroup,
@@ -73,6 +74,7 @@ import {
getClientTypeWithDiagnostics,
getSdkCredentialParameter,
getSdkModelPropertyType,
+ getTypeSpecBuiltInType,
} from "./types.js";
function getSdkServiceOperation(
@@ -437,11 +439,7 @@ function getSdkEndpointParameter(
optional: false,
serializedName: "endpoint",
correspondingMethodParams: [],
- type: {
- kind: "string",
- encode: "string",
- decorators: [],
- },
+ type: getTypeSpecBuiltInType(context, "string"),
isApiVersionParam: false,
apiVersions: context.__tspTypeToApiVersions.get(client.type)!,
crossLanguageDefinitionId: `${getCrossLanguageDefinitionId(context, client.service)}.endpoint`,
@@ -506,13 +504,18 @@ function createSdkClientType(
): [SdkClientType, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
const isClient = client.kind === "SdkClient";
- const clientName = isClient ? client.name : client.type.name;
+ let name = "";
+ if (isClient) {
+ name = client.name;
+ } else {
+ name = getClientNameOverride(context, client.type) ?? client.type.name;
+ }
// NOTE: getSdkMethods recursively calls createSdkClientType
const methods = diagnostics.pipe(getSdkMethods(context, client));
const docWrapper = getDocHelper(context, client.type);
const sdkClientType: SdkClientType = {
kind: "client",
- name: clientName,
+ name,
description: docWrapper.description,
details: docWrapper.details,
methods: methods,
@@ -559,18 +562,17 @@ function populateApiVersionInformation(context: TCGCContext): void {
export function getSdkPackage(
context: TCGCContext
-): SdkPackage {
+): [SdkPackage, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
populateApiVersionInformation(context);
const modelsAndEnums = diagnostics.pipe(getAllModelsWithDiagnostics(context));
const crossLanguagePackageId = diagnostics.pipe(getCrossLanguagePackageId(context));
- return {
+ return diagnostics.wrap({
name: getClientNamespaceString(context)!,
rootNamespace: getClientNamespaceString(context)!,
clients: listClients(context).map((c) => diagnostics.pipe(createSdkClientType(context, c))),
models: modelsAndEnums.filter((x): x is SdkModelType => x.kind === "model"),
enums: modelsAndEnums.filter((x): x is SdkEnumType => x.kind === "enum"),
- diagnostics: diagnostics.diagnostics,
crossLanguagePackageId,
- };
+ });
}
diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts
index ac9a5d2a9a..88afbd6ff9 100644
--- a/packages/typespec-client-generator-core/src/public-utils.ts
+++ b/packages/typespec-client-generator-core/src/public-utils.ts
@@ -28,7 +28,7 @@ import {
listOperationGroups,
listOperationsInOperationGroup,
} from "./decorators.js";
-import { TCGCContext } from "./interfaces.js";
+import { SdkHttpOperationExample, TCGCContext } from "./interfaces.js";
import {
TspLiteralType,
getClientNamespaceStringHelper,
@@ -614,3 +614,13 @@ export function getHttpOperationWithCache(
context.httpOperationCache.set(operation, httpOperation);
return httpOperation;
}
+
+/**
+ * Get the examples for a given http operation.
+ */
+export function getHttpOperationExamples(
+ context: TCGCContext,
+ operation: HttpOperation
+): SdkHttpOperationExample[] {
+ return context.__httpOperationExamples?.get(operation) ?? [];
+}
diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts
index d967a0827a..ee2ae7ea04 100644
--- a/packages/typespec-client-generator-core/src/types.ts
+++ b/packages/typespec-client-generator-core/src/types.ts
@@ -5,8 +5,10 @@ import {
DateTimeKnownEncoding,
Diagnostic,
DurationKnownEncoding,
+ EncodeData,
Enum,
EnumMember,
+ IntrinsicScalarName,
IntrinsicType,
Model,
ModelProperty,
@@ -17,13 +19,11 @@ import {
Tuple,
Type,
Union,
- UnionVariant,
createDiagnosticCollector,
getDiscriminator,
getEncode,
getFormat,
getKnownValues,
- getNamespaceFullName,
getVisibility,
ignoreDiagnostics,
isErrorModel,
@@ -31,10 +31,13 @@ import {
} from "@typespec/compiler";
import {
Authentication,
+ HttpOperationPart,
Visibility,
getAuthentication,
+ getHttpPart,
getServers,
isHeader,
+ isOrExtendsHttpFile,
isStatusCode,
} from "@typespec/http";
import {
@@ -78,7 +81,6 @@ import {
import {
createGeneratedName,
filterApiVersionsInEnum,
- getAnyType,
getAvailableApiVersions,
getDocHelper,
getHttpOperationResponseHeaders,
@@ -93,6 +95,7 @@ import {
isMultipartFormData,
isMultipartOperation,
isNeverOrVoidType,
+ isXmlContentType,
updateWithApiVersionInformation,
} from "./internal-utils.js";
import { createDiagnostic } from "./lib.js";
@@ -109,6 +112,28 @@ import { getVersions } from "@typespec/versioning";
import { UnionEnumVariant } from "../../typespec-azure-core/dist/src/helpers/union-enums.js";
import { getSdkHttpParameter, isSdkHttpParameter } from "./http.js";
+export function getTypeSpecBuiltInType(
+ context: TCGCContext,
+ kind: IntrinsicScalarName
+): SdkBuiltInType {
+ const global = context.program.getGlobalNamespaceType();
+ const typeSpecNamespace = global.namespaces!.get("TypeSpec");
+ const type = typeSpecNamespace!.scalars.get(kind)!;
+
+ return getSdkBuiltInType(context, type) as SdkBuiltInType;
+}
+
+function getAnyType(context: TCGCContext, type: Type): [SdkBuiltInType, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ const anyType: SdkBuiltInType = {
+ ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "any")),
+ name: getLibraryName(context, type),
+ encode: getEncodeHelper(context, type, "any"),
+ crossLanguageDefinitionId: "",
+ };
+ return diagnostics.wrap(anyType);
+}
+
function getEncodeHelper(context: TCGCContext, type: Type, kind: string): string {
if (type.kind === "ModelProperty" || type.kind === "Scalar") {
return getEncode(context.program, type)?.encoding || kind;
@@ -130,7 +155,11 @@ export function addFormatInfo(
propertyType: SdkType
): void {
const innerType = propertyType.kind === "nullable" ? propertyType.type : propertyType;
- const format = getFormat(context.program, type) ?? "";
+ let format = getFormat(context.program, type) ?? "";
+
+ // special case: we treat format: uri the same as format: url
+ if (format === "uri") format = "url";
+
if (isSdkBuiltInKind(format)) innerType.kind = format;
}
@@ -182,88 +211,211 @@ export function addEncodeInfo(
/**
* Mapping of typespec scalar kinds to the built in kinds exposed in the SDK
+ * @param context the TCGC context
* @param scalar the original typespec scalar
* @returns the corresponding sdk built in kind
*/
-function getScalarKind(scalar: Scalar): SdkBuiltInKinds {
- if (isSdkBuiltInKind(scalar.name)) {
+function getScalarKind(context: TCGCContext, scalar: Scalar): IntrinsicScalarName | "any" {
+ if (context.program.checker.isStdType(scalar)) {
return scalar.name;
}
- throw Error(`Unknown scalar kind ${scalar.name}`);
+
+ // for those scalar defined as `scalar newThing;`,
+ // the best we could do here is return as a `any` type with a name and namespace and let the generator figure what this is
+ if (scalar.baseScalar === undefined) {
+ return "any";
+ }
+
+ return getScalarKind(context, scalar.baseScalar);
}
/**
- * Get the sdk built in type for a given typespec type
- * @param context the sdk context
- * @param type the typespec type
- * @returns the corresponding sdk type
+ * This function converts a Scalar into SdkBuiltInType.
+ * @param context
+ * @param type
+ * @param kind
+ * @returns
*/
function getSdkBuiltInTypeWithDiagnostics(
context: TCGCContext,
- type: Scalar | IntrinsicType | NumericLiteral | StringLiteral | BooleanLiteral
+ type: Scalar,
+ kind: SdkBuiltInKinds
): [SdkBuiltInType, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
- if (context.program.checker.isStdType(type) || type.kind === "Intrinsic") {
- let kind: SdkBuiltInKinds = "any";
- if (type.kind === "Scalar") {
- if (isSdkBuiltInKind(type.name)) {
- kind = getScalarKind(type);
- }
- }
- const docWrapper = getDocHelper(context, type);
- return diagnostics.wrap({
- ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, kind)),
- encode: getEncodeHelper(context, type, kind),
- description: docWrapper.description,
- details: docWrapper.details,
- });
- } else if (type.kind === "String" || type.kind === "Boolean" || type.kind === "Number") {
- let kind: SdkBuiltInKinds;
+ const docWrapper = getDocHelper(context, type);
+ const stdType = {
+ ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, kind)),
+ name: getLibraryName(context, type),
+ encode: getEncodeHelper(context, type, kind),
+ description: docWrapper.description,
+ details: docWrapper.details,
+ baseType: type.baseScalar
+ ? diagnostics.pipe(getSdkBuiltInTypeWithDiagnostics(context, type.baseScalar, kind))
+ : undefined,
+ crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, type),
+ };
+ addEncodeInfo(context, type, stdType);
+ addFormatInfo(context, type, stdType);
+ return diagnostics.wrap(stdType);
+}
- if (type.kind === "String") {
- kind = "string";
- } else if (type.kind === "Boolean") {
- kind = "boolean";
- } else {
- kind = intOrFloat(type.value);
- }
- return diagnostics.wrap({
- ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, kind)),
- encode: getEncodeHelper(context, type, kind),
- });
+/**
+ * This function calculates the encode and wireType for a datetime or duration type.
+ * We always first try to get the `@encode` decorator on this type and returns it if any.
+ * If we did not get anything from the encode, we try to get the baseType's encode and wireType.
+ * @param context
+ * @param encodeData
+ * @param baseType
+ * @returns
+ */
+function getEncodeInfoForDateTimeOrDuration(
+ context: TCGCContext,
+ encodeData: EncodeData | undefined,
+ baseType: SdkDateTimeType | SdkDurationType | undefined
+): [string | undefined, SdkBuiltInType | undefined] {
+ const encode = encodeData?.encoding;
+ const wireType = encodeData?.type
+ ? (getClientType(context, encodeData.type) as SdkBuiltInType)
+ : undefined;
+
+ // if we get something from the encode
+ if (encode || wireType) {
+ return [encode, wireType];
}
- diagnostics.add(
- createDiagnostic({ code: "unsupported-kind", target: type, format: { kind: type.kind } })
+
+ // if we did not get anything from the encode, try the baseType
+ return [baseType?.encode, baseType?.wireType];
+}
+
+/**
+ * This function converts a Scalar into SdkDateTimeType.
+ * @param context
+ * @param type
+ * @param kind
+ * @returns
+ */
+function getSdkDateTimeType(
+ context: TCGCContext,
+ type: Scalar,
+ kind: "utcDateTime" | "offsetDateTime"
+): [SdkDateTimeType, readonly Diagnostic[]] {
+ const diagnostics = createDiagnosticCollector();
+ const docWrapper = getDocHelper(context, type);
+ const baseType = type.baseScalar
+ ? diagnostics.pipe(getSdkDateTimeType(context, type.baseScalar, kind))
+ : undefined;
+ const [encode, wireType] = getEncodeInfoForDateTimeOrDuration(
+ context,
+ getEncode(context.program, type),
+ baseType
);
- return diagnostics.wrap(diagnostics.pipe(getAnyType(context, type)));
+ return diagnostics.wrap({
+ ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, kind)),
+ name: getLibraryName(context, type),
+ encode: (encode ?? "rfc3339") as DateTimeKnownEncoding,
+ wireType: wireType ?? getTypeSpecBuiltInType(context, "string"),
+ baseType: baseType,
+ description: docWrapper.description,
+ details: docWrapper.details,
+ crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, type),
+ });
+}
+
+function getSdkDateTimeOrDurationOrBuiltInType(
+ context: TCGCContext,
+ type: Scalar
+): [SdkDateTimeType | SdkDurationType | SdkBuiltInType, readonly Diagnostic[]] {
+ // follow the extends hierarchy to determine the final kind of this type
+ const kind = getScalarKind(context, type);
+
+ if (kind === "utcDateTime" || kind === "offsetDateTime") {
+ return getSdkDateTimeType(context, type, kind);
+ }
+ if (kind === "duration") {
+ return getSdkDurationTypeWithDiagnostics(context, type, kind);
+ }
+ // handle the std types of typespec
+ return getSdkBuiltInTypeWithDiagnostics(context, type, kind);
+}
+
+function getSdkTypeForLiteral(
+ context: TCGCContext,
+ type: NumericLiteral | StringLiteral | BooleanLiteral
+): SdkBuiltInType {
+ let kind: SdkBuiltInKinds;
+
+ if (type.kind === "String") {
+ kind = "string";
+ } else if (type.kind === "Boolean") {
+ kind = "boolean";
+ } else {
+ kind = intOrFloat(type.value);
+ }
+ return getTypeSpecBuiltInType(context, kind);
+}
+
+function getSdkTypeForIntrinsic(context: TCGCContext, type: IntrinsicType): SdkBuiltInType {
+ const kind = "any";
+ const diagnostics = createDiagnosticCollector();
+ return {
+ ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, kind)),
+ name: getLibraryName(context, type),
+ crossLanguageDefinitionId: "",
+ encode: kind,
+ };
}
export function getSdkBuiltInType(
context: TCGCContext,
type: Scalar | IntrinsicType | NumericLiteral | StringLiteral | BooleanLiteral
-): SdkBuiltInType {
- return ignoreDiagnostics(getSdkBuiltInTypeWithDiagnostics(context, type));
+): SdkDateTimeType | SdkDurationType | SdkBuiltInType {
+ switch (type.kind) {
+ case "Scalar":
+ return ignoreDiagnostics(getSdkDateTimeOrDurationOrBuiltInType(context, type));
+ case "Intrinsic":
+ return getSdkTypeForIntrinsic(context, type);
+ case "String":
+ case "Number":
+ case "Boolean":
+ return getSdkTypeForLiteral(context, type);
+ }
}
export function getSdkDurationType(context: TCGCContext, type: Scalar): SdkDurationType {
- return ignoreDiagnostics(getSdkDurationTypeWithDiagnostics(context, type));
+ return ignoreDiagnostics(getSdkDurationTypeWithDiagnostics(context, type, "duration"));
}
+/**
+ * This function converts a Scalar into SdkDurationType.
+ * @param context
+ * @param type
+ * @param kind
+ * @returns
+ */
function getSdkDurationTypeWithDiagnostics(
context: TCGCContext,
- type: Scalar
+ type: Scalar,
+ kind: "duration"
): [SdkDurationType, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
- // we don't get encode info until we get to the property / parameter level
- // so we insert the default. Later in properties, we will check
- // for encoding info and override accordingly
+ const docWrapper = getDocHelper(context, type);
+ const baseType = type.baseScalar
+ ? diagnostics.pipe(getSdkDurationTypeWithDiagnostics(context, type.baseScalar, kind))
+ : undefined;
+ const [encode, wireType] = getEncodeInfoForDateTimeOrDuration(
+ context,
+ getEncode(context.program, type),
+ baseType
+ );
return diagnostics.wrap({
- ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "duration")),
- encode: "ISO8601",
- wireType: {
- ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "string")),
- encode: "string",
- },
+ ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, kind)),
+ name: getLibraryName(context, type),
+ encode: (encode ?? "ISO8601") as DurationKnownEncoding,
+ wireType: wireType ?? getTypeSpecBuiltInType(context, "string"),
+ baseType: baseType,
+ description: docWrapper.description,
+ details: docWrapper.details,
+ crossLanguageDefinitionId: getCrossLanguageDefinitionId(context, type),
});
}
@@ -407,7 +559,7 @@ function getSdkConstantWithDiagnostics(
case "Number":
case "String":
case "Boolean":
- const valueType = diagnostics.pipe(getSdkBuiltInTypeWithDiagnostics(context, type));
+ const valueType = getSdkTypeForLiteral(context, type);
return diagnostics.wrap({
...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "constant")),
value: type.value,
@@ -504,11 +656,7 @@ function addDiscriminatorToModelType(
discriminatorType = discriminatorProperty.type.enumType;
}
} else {
- discriminatorType = {
- kind: "string",
- encode: "string",
- decorators: [],
- };
+ discriminatorType = getTypeSpecBuiltInType(context, "string");
}
const name = discriminatorProperty ? discriminatorProperty.name : discriminator.propertyName;
model.properties.splice(0, 0, {
@@ -619,14 +767,7 @@ function getSdkEnumValueType(
): [SdkBuiltInType, readonly Diagnostic[]] {
const diagnostics = createDiagnosticCollector();
let kind: "string" | "int32" | "float32" = "string";
- let type: EnumMember | UnionVariant;
for (const value of values) {
- if ((value as EnumMember).kind) {
- type = value as EnumMember;
- } else {
- type = (value as UnionEnumVariant | UnionEnumVariant).type;
- }
-
if (typeof value.value === "number") {
kind = intOrFloat(value.value);
if (kind === "float32") {
@@ -638,10 +779,7 @@ function getSdkEnumValueType(
}
}
- return diagnostics.wrap({
- ...diagnostics.pipe(getSdkTypeBaseHelper(context, type!, kind!)),
- encode: kind!,
- });
+ return diagnostics.wrap(getTypeSpecBuiltInType(context, kind!));
}
function getUnionAsEnumValueType(
@@ -856,46 +994,25 @@ export function getClientTypeWithDiagnostics(
case "Model":
retval = diagnostics.pipe(getSdkArrayOrDictWithDiagnostics(context, type, operation));
if (retval === undefined) {
- retval = diagnostics.pipe(getSdkModelWithDiagnostics(context, type, operation));
+ const httpPart = getHttpPart(context.program, type);
+ if (httpPart === undefined) {
+ retval = diagnostics.pipe(getSdkModelWithDiagnostics(context, type, operation));
+ } else {
+ retval = diagnostics.pipe(
+ getClientTypeWithDiagnostics(context, httpPart.type, operation)
+ );
+ }
}
break;
case "Intrinsic":
- retval = diagnostics.pipe(getSdkBuiltInTypeWithDiagnostics(context, type));
+ retval = getSdkTypeForIntrinsic(context, type);
break;
case "Scalar":
- if (!context.program.checker.isStdType(type) && type.kind === "Scalar" && type.baseScalar) {
- const baseType = diagnostics.pipe(
- getClientTypeWithDiagnostics(context, type.baseScalar, operation)
- );
- addEncodeInfo(context, type, baseType);
- addFormatInfo(context, type, baseType);
- retval = diagnostics.pipe(getKnownValuesEnum(context, type, operation)) ?? baseType;
- const namespace = type.namespace ? getNamespaceFullName(type.namespace) : "";
- retval.kind = context.knownScalars[`${namespace}.${type.name}`] ?? retval.kind;
- const docWrapper = getDocHelper(context, type);
- retval.description = docWrapper.description;
- retval.details = docWrapper.details;
+ retval = diagnostics.pipe(getKnownValuesEnum(context, type, operation));
+ if (retval) {
break;
}
- if (type.name === "utcDateTime" || type.name === "offsetDateTime") {
- retval = {
- ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, type.name)),
- encode: "rfc3339",
- wireType: {
- ...diagnostics.pipe(getSdkTypeBaseHelper(context, type, "string")),
- encode: "string",
- },
- } as SdkDateTimeType;
- break;
- }
- if (type.name === "duration") {
- retval = diagnostics.pipe(getSdkDurationTypeWithDiagnostics(context, type));
- break;
- }
- const scalarType = diagnostics.pipe(getSdkBuiltInTypeWithDiagnostics(context, type));
- // just add default encode, normally encode is on extended scalar and model property
- addEncodeInfo(context, type, scalarType);
- retval = scalarType;
+ retval = diagnostics.pipe(getSdkDateTimeOrDurationOrBuiltInType(context, type));
break;
case "Enum":
retval = diagnostics.pipe(getSdkEnumWithDiagnostics(context, type, operation));
@@ -1061,6 +1178,137 @@ export function getSdkModelPropertyTypeBase(
});
}
+function isFilePart(context: TCGCContext, type: SdkType): boolean {
+ if (type.kind === "array") {
+ // HttpFile[]
+ return isFilePart(context, type.valueType);
+ } else if (type.kind === "bytes") {
+ // Http
+ return true;
+ } else if (type.kind === "model") {
+ if (type.__raw && isOrExtendsHttpFile(context.program, type.__raw)) {
+ // Http
+ return true;
+ }
+ // HttpPart<{@body body: bytes}> or HttpPart<{@body body: File}>
+ const body = type.properties.find((x) => x.kind === "body");
+ if (body) {
+ return isFilePart(context, body.type);
+ }
+ }
+ return false;
+}
+
+function getHttpOperationParts(context: TCGCContext, operation: Operation): HttpOperationPart[] {
+ const body = getHttpOperationWithCache(context, operation).parameters.body;
+ if (body?.bodyKind === "multipart") {
+ return body.parts;
+ }
+ return [];
+}
+
+function hasHttpPart(context: TCGCContext, type: Type): boolean {
+ if (type.kind === "Model") {
+ if (type.indexer) {
+ // HttpPart[]
+ return (
+ type.indexer.key.name === "integer" &&
+ getHttpPart(context.program, type.indexer.value) !== undefined
+ );
+ } else {
+ // HttpPart
+ return getHttpPart(context.program, type) !== undefined;
+ }
+ }
+ return false;
+}
+
+function getHttpOperationPart(
+ context: TCGCContext,
+ type: ModelProperty,
+ operation: Operation
+): HttpOperationPart | undefined {
+ if (hasHttpPart(context, type.type)) {
+ const httpOperationParts = getHttpOperationParts(context, operation);
+ if (
+ type.model &&
+ httpOperationParts.length > 0 &&
+ httpOperationParts.length === type.model.properties.size
+ ) {
+ const index = Array.from(type.model.properties.values()).findIndex((p) => p === type);
+ if (index !== -1) {
+ return httpOperationParts[index];
+ }
+ }
+ }
+ return undefined;
+}
+
+function updateMultiPartInfo(
+ context: TCGCContext,
+ type: ModelProperty,
+ base: SdkBodyModelPropertyType,
+ operation: Operation
+): [void, readonly Diagnostic[]] {
+ const httpOperationPart = getHttpOperationPart(context, type, operation);
+ const diagnostics = createDiagnosticCollector();
+ if (httpOperationPart) {
+ // body decorated with @multipartBody
+ base.multipartOptions = {
+ isFilePart: isFilePart(context, base.type),
+ isMulti: httpOperationPart.multi,
+ filename: httpOperationPart.filename
+ ? diagnostics.pipe(getSdkModelPropertyType(context, httpOperationPart.filename, operation))
+ : undefined,
+ contentType: httpOperationPart.body.contentTypeProperty
+ ? diagnostics.pipe(
+ getSdkModelPropertyType(context, httpOperationPart.body.contentTypeProperty, operation)
+ )
+ : undefined,
+ defaultContentTypes: httpOperationPart.body.contentTypes,
+ };
+ // after https://github.com/microsoft/typespec/issues/3779 fixed, could use httpOperationPart.name directly
+ const httpPart = getHttpPart(context.program, type.type);
+ if (httpPart?.options?.name) {
+ base.serializedName = httpPart?.options?.name;
+ }
+ } else {
+ // common body
+ const httpOperation = getHttpOperationWithCache(context, operation);
+ const operationIsMultipart = Boolean(
+ httpOperation && httpOperation.parameters.body?.contentTypes.includes("multipart/form-data")
+ );
+ if (operationIsMultipart) {
+ const isBytesInput =
+ base.type.kind === "bytes" ||
+ (base.type.kind === "array" && base.type.valueType.kind === "bytes");
+ // Currently we only recognize bytes and list of bytes as potential file inputs
+ if (isBytesInput && getEncode(context.program, type)) {
+ diagnostics.add(
+ createDiagnostic({
+ code: "encoding-multipart-bytes",
+ target: type,
+ })
+ );
+ }
+ base.multipartOptions = {
+ isFilePart: isBytesInput,
+ isMulti: base.type.kind === "array",
+ defaultContentTypes: [],
+ };
+ }
+ }
+ if (base.multipartOptions !== undefined) {
+ base.isMultipartFileInput = base.multipartOptions.isFilePart;
+ }
+ if (base.multipartOptions?.isMulti && base.type.kind === "array") {
+ // for "images: T[]" or "images: HttpPart[]", return type shall be "T" instead of "T[]"
+ base.type = base.type.valueType;
+ }
+
+ return diagnostics.wrap(undefined);
+}
+
export function getSdkModelPropertyType(
context: TCGCContext,
type: ModelProperty,
@@ -1070,36 +1318,20 @@ export function getSdkModelPropertyType(
const base = diagnostics.pipe(getSdkModelPropertyTypeBase(context, type, operation));
if (isSdkHttpParameter(context, type)) return getSdkHttpParameter(context, type, operation!);
- // I'm a body model property
- let operationIsMultipart = false;
- if (operation) {
- const httpOperation = getHttpOperationWithCache(context, operation);
- operationIsMultipart = Boolean(
- httpOperation && httpOperation.parameters.body?.contentTypes.includes("multipart/form-data")
- );
- }
- // Currently we only recognize bytes and list of bytes as potential file inputs
- const isBytesInput =
- base.type.kind === "bytes" ||
- (base.type.kind === "array" && base.type.valueType.kind === "bytes");
- if (isBytesInput && operationIsMultipart && getEncode(context.program, type)) {
- diagnostics.add(
- createDiagnostic({
- code: "encoding-multipart-bytes",
- target: type,
- })
- );
- }
- return diagnostics.wrap({
+ const result: SdkBodyModelPropertyType = {
...base,
kind: "property",
optional: type.optional,
visibility: getSdkVisibility(context, type),
discriminator: false,
serializedName: getPropertyNames(context, type)[1],
- isMultipartFileInput: isBytesInput && operationIsMultipart,
+ isMultipartFileInput: false,
flatten: shouldFlattenProperty(context, type),
- });
+ };
+ if (operation) {
+ diagnostics.pipe(updateMultiPartInfo(context, type, result, operation));
+ }
+ return diagnostics.wrap(result);
}
function addPropertiesToModelType(
@@ -1335,6 +1567,9 @@ function updateTypesFromOperation(
if (httpBody.contentTypes.some((x) => isJsonContentType(x))) {
updateUsageOfModel(context, UsageFlags.Json, sdkType);
}
+ if (httpBody.contentTypes.some((x) => isXmlContentType(x))) {
+ updateUsageOfModel(context, UsageFlags.Xml, sdkType);
+ }
if (httpBody.contentTypes.includes("application/merge-patch+json")) {
// will also have Json type
updateUsageOfModel(context, UsageFlags.JsonMergePatch, sdkType);
diff --git a/packages/typespec-client-generator-core/src/validate.ts b/packages/typespec-client-generator-core/src/validate.ts
index 365efc86bf..5af4dd87d9 100644
--- a/packages/typespec-client-generator-core/src/validate.ts
+++ b/packages/typespec-client-generator-core/src/validate.ts
@@ -57,6 +57,11 @@ function validateClientNamesPerNamespace(
// Check for duplicate client names for operations
validateClientNamesCore(tcgcContext, scope, namespace.operations.values());
+ // check for duplicate client names for operations in interfaces
+ for (const item of namespace.interfaces.values()) {
+ validateClientNamesCore(tcgcContext, scope, item.operations.values());
+ }
+
// Check for duplicate client names for interfaces
validateClientNamesCore(tcgcContext, scope, namespace.interfaces.values());
diff --git a/packages/typespec-client-generator-core/test/decorators.test.ts b/packages/typespec-client-generator-core/test/decorators.test.ts
index 2ef945a06e..9e09587e18 100644
--- a/packages/typespec-client-generator-core/test/decorators.test.ts
+++ b/packages/typespec-client-generator-core/test/decorators.test.ts
@@ -435,6 +435,27 @@ describe("typespec-client-generator-core: decorators", () => {
},
]);
});
+
+ it("with @clientName", async () => {
+ await runner.compileWithBuiltInService(
+ `
+ @operationGroup
+ @clientName("ClientModel")
+ interface Model {
+ op foo(): void;
+ }
+ `
+ );
+ const sdkPackage = runner.context.sdkPackage;
+ strictEqual(sdkPackage.clients.length, 1);
+ const mainClient = sdkPackage.clients[0];
+ strictEqual(mainClient.methods.length, 1);
+
+ const clientAccessor = mainClient.methods[0];
+ strictEqual(clientAccessor.kind, "clientaccessor");
+ strictEqual(clientAccessor.response.kind, "client");
+ strictEqual(clientAccessor.response.name, "ClientModel");
+ });
});
describe("listOperationGroups without @client and @operationGroup", () => {
@@ -1314,7 +1335,7 @@ describe("typespec-client-generator-core: decorators", () => {
const { test } = await runner.compileWithBuiltInService(testCode);
const actual = shouldGenerateProtocol(
- createSdkContextTestHelper(runner.context.program, {
+ await createSdkContextTestHelper(runner.context.program, {
generateProtocolMethods: globalValue,
generateConvenienceMethods: false,
}),
@@ -1355,7 +1376,7 @@ describe("typespec-client-generator-core: decorators", () => {
const { test } = await runner.compileWithBuiltInService(testCode);
const actual = shouldGenerateConvenient(
- createSdkContextTestHelper(runner.program, {
+ await createSdkContextTestHelper(runner.program, {
generateProtocolMethods: false,
generateConvenienceMethods: globalValue,
}),
@@ -1391,7 +1412,7 @@ describe("typespec-client-generator-core: decorators", () => {
`);
const actual = shouldGenerateConvenient(
- createSdkContextTestHelper(runner.program, {
+ await createSdkContextTestHelper(runner.program, {
generateProtocolMethods: false,
generateConvenienceMethods: false,
}),
@@ -2545,6 +2566,30 @@ describe("typespec-client-generator-core: decorators", () => {
code: "decorator-wrong-target",
});
});
+
+ it("throws error when used on a polymorphism type", async () => {
+ const diagnostics = await runner.diagnose(`
+ @service
+ @test namespace MyService {
+ #suppress "deprecated" "@flattenProperty decorator is not recommended to use."
+ @test
+ model Model1{
+ @flattenProperty
+ child: Model2;
+ }
+
+ @test
+ @discriminator("kind")
+ model Model2{
+ kind: string;
+ }
+ }
+ `);
+
+ expectDiagnostics(diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/flatten-polymorphism",
+ });
+ });
});
describe("@clientName", () => {
@@ -2836,6 +2881,36 @@ describe("typespec-client-generator-core: decorators", () => {
]);
});
+ it("duplicate operation in interface with all language scopes", async () => {
+ const diagnostics = await runner.diagnose(
+ `
+ @service
+ namespace Contoso.WidgetManager;
+
+ interface C {
+ @clientName("b")
+ @route("/a")
+ op a(): void;
+
+ @route("/b")
+ op b(): void;
+ }
+ `
+ );
+
+ expectDiagnostics(diagnostics, [
+ {
+ code: "@azure-tools/typespec-client-generator-core/duplicate-client-name",
+ message: 'Client name: "b" is duplicated in language scope: "AllScopes"',
+ },
+ {
+ code: "@azure-tools/typespec-client-generator-core/duplicate-client-name",
+ message:
+ 'Client name: "b" is defined somewhere causing nameing conflicts in language scope: "AllScopes"',
+ },
+ ]);
+ });
+
it("duplicate scalar with all language scopes", async () => {
const diagnostics = await runner.diagnose(
`
@@ -4175,7 +4250,7 @@ describe("typespec-client-generator-core: decorators", () => {
strictEqual(clients.length, 1);
ok(clients[0].type);
- const newSdkContext = createSdkContext(runnerWithVersion.context.emitContext);
+ const newSdkContext = await createSdkContext(runnerWithVersion.context.emitContext);
clients = listClients(newSdkContext);
strictEqual(clients.length, 1);
ok(clients[0].type);
diff --git a/packages/typespec-client-generator-core/test/examples/example-types.test.ts b/packages/typespec-client-generator-core/test/examples/example-types.test.ts
new file mode 100644
index 0000000000..d9491ee692
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types.test.ts
@@ -0,0 +1,892 @@
+import { expectDiagnostics } from "@typespec/compiler/testing";
+import { deepStrictEqual, ok, strictEqual } from "assert";
+import { beforeEach, describe, it } from "vitest";
+import {
+ SdkDateTimeType,
+ SdkDurationType,
+ SdkHttpOperation,
+ SdkNullableType,
+ SdkServiceMethod,
+} from "../../src/interfaces.js";
+import { SdkTestRunner, createSdkTestRunner } from "../test-host.js";
+
+describe("typespec-client-generator-core: example types", () => {
+ let runner: SdkTestRunner;
+
+ beforeEach(async () => {
+ runner = await createSdkTestRunner({
+ emitterName: "@azure-tools/typespec-java",
+ "examples-directory": `./examples`,
+ });
+ });
+
+ it("SdkStringExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getString.json",
+ `${__dirname}/example-types/getString.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getString(): string;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "string");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, "test");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "string");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkStringExample diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringDiagnostic.json",
+ `${__dirname}/example-types/getStringDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getStringDiagnostic(): string;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getStringDiagnostic.json' does not follow its definition:\n123`,
+ });
+ });
+
+ it("SdkStringExample from constant", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringFromConstant.json",
+ `${__dirname}/example-types/getStringFromConstant.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getStringFromConstant(): "test";
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "string");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, "test");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "constant");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkStringExample from constant diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringFromConstantDiagnostic.json",
+ `${__dirname}/example-types/getStringFromConstantDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getStringFromConstantDiagnostic(): "test";
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getStringFromConstantDiagnostic.json' does not follow its definition:\n123`,
+ });
+ });
+
+ it("SdkStringExample from enum", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringFromEnum.json",
+ `${__dirname}/example-types/getStringFromEnum.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ enum TestEnum {
+ one,two,three
+ }
+ op getStringFromEnum(): TestEnum;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "string");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, "one");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "enum");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkStringExample from enum diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringFromEnumDiagnostic.json",
+ `${__dirname}/example-types/getStringFromEnumDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ enum TestEnum {
+ one,two,three
+ }
+ op getStringFromEnumDiagnostic(): TestEnum;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getStringFromEnumDiagnostic.json' does not follow its definition:\n"four"`,
+ });
+ });
+
+ it("SdkStringExample from enum value", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringFromEnumValue.json",
+ `${__dirname}/example-types/getStringFromEnumValue.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ enum TestEnum {
+ one,two,three
+ }
+ op getStringFromEnumValue(): TestEnum.one;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "string");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, "one");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "enumvalue");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkStringExample from enum value diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringFromEnumValueDiagnostic.json",
+ `${__dirname}/example-types/getStringFromEnumValueDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ enum TestEnum {
+ one,two,three
+ }
+ op getStringFromEnumValueDiagnostic(): TestEnum.one;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getStringFromEnumValueDiagnostic.json' does not follow its definition:\n"four"`,
+ });
+ });
+
+ it("SdkStringExample from datetime", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringFromDataTime.json",
+ `${__dirname}/example-types/getStringFromDataTime.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getStringFromDataTime(): utcDateTime;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "string");
+ strictEqual(
+ operation.examples[0].responses.get(200)?.bodyValue?.value,
+ "2022-08-26T18:38:00.000Z"
+ );
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "utcDateTime");
+ strictEqual(
+ (operation.examples[0].responses.get(200)?.bodyValue?.type as SdkDateTimeType).wireType.kind,
+ "string"
+ );
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkStringExample from duration", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getStringFromDuration.json",
+ `${__dirname}/example-types/getStringFromDuration.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getStringFromDuration(): duration;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "string");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, "P40D");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "duration");
+ strictEqual(
+ (operation.examples[0].responses.get(200)?.bodyValue?.type as SdkDurationType).wireType.kind,
+ "string"
+ );
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkNumberExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getNumber.json",
+ `${__dirname}/example-types/getNumber.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getNumber(): float32;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "number");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, 31.752);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "float32");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkNumberExample diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getNumberDiagnostic.json",
+ `${__dirname}/example-types/getNumberDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getNumberDiagnostic(): float32;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getNumberDiagnostic.json' does not follow its definition:\n"123"`,
+ });
+ });
+
+ it("SdkNumberExample from datetime", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getNumberFromDateTime.json",
+ `${__dirname}/example-types/getNumberFromDateTime.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ @encode(DateTimeKnownEncoding.unixTimestamp, int64)
+ scalar timestamp extends utcDateTime;
+
+ op getNumberFromDateTime(): timestamp;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "number");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, 1686566864);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "utcDateTime");
+ strictEqual(
+ (operation.examples[0].responses.get(200)?.bodyValue?.type as SdkDateTimeType).wireType.kind,
+ "int64"
+ );
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkNumberExample from duration", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getNumberFromDuration.json",
+ `${__dirname}/example-types/getNumberFromDuration.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ @encode(DurationKnownEncoding.seconds, float)
+ scalar delta extends duration;
+
+ op getNumberFromDuration(): delta;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "number");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, 62.525);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "duration");
+ strictEqual(
+ (operation.examples[0].responses.get(200)?.bodyValue?.type as SdkDurationType).wireType.kind,
+ "float"
+ );
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkBooleanExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getBoolean.json",
+ `${__dirname}/example-types/getBoolean.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getBoolean(): boolean;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "boolean");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, true);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "boolean");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkBooleanExample diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getBooleanDiagnostic.json",
+ `${__dirname}/example-types/getBooleanDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getBooleanDiagnostic(): boolean;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getBooleanDiagnostic.json' does not follow its definition:\n123`,
+ });
+ });
+
+ it("SdkNullExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getNull.json",
+ `${__dirname}/example-types/getNull.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getNull(): {@body body: string | null};
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "null");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, null);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "nullable");
+ strictEqual(
+ (operation.examples[0].responses.get(200)?.bodyValue?.type as SdkNullableType).type.kind,
+ "string"
+ );
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkAnyExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getAny.json",
+ `${__dirname}/example-types/getAny.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getAny(): unknown;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "any");
+ deepStrictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, { test: 123 });
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkUnionExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getUnion.json",
+ `${__dirname}/example-types/getUnion.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getUnion(): {@body body: string | int32};
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.kind, "union");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.value, "test");
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue?.type.kind, "union");
+ });
+
+ it("SdkArrayExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getArray.json",
+ `${__dirname}/example-types/getArray.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getArray(): string[];
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ const example = operation.examples[0].responses.get(200)?.bodyValue;
+ ok(example);
+ strictEqual(example.kind, "array");
+ strictEqual(example.value.length, 3);
+ strictEqual(example.type.kind, "array");
+ strictEqual(example.type.valueType.kind, "string");
+ strictEqual(example.value[0].value, "a");
+ strictEqual(example.value[0].kind, "string");
+ strictEqual(example.value[0].type.kind, "string");
+ strictEqual(example.value[1].value, "b");
+ strictEqual(example.value[1].kind, "string");
+ strictEqual(example.value[1].type.kind, "string");
+ strictEqual(example.value[2].value, "c");
+ strictEqual(example.value[2].kind, "string");
+ strictEqual(example.value[2].type.kind, "string");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkArrayExample diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getArrayDiagnostic.json",
+ `${__dirname}/example-types/getArrayDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getArrayDiagnostic(): string[];
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getArrayDiagnostic.json' does not follow its definition:\n"test"`,
+ });
+ });
+
+ it("SdkDictionaryExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getDictionary.json",
+ `${__dirname}/example-types/getDictionary.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getDictionary(): Record;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ const example = operation.examples[0].responses.get(200)?.bodyValue;
+ ok(example);
+ strictEqual(example.kind, "dict");
+ strictEqual(Object.keys(example.value).length, 3);
+ strictEqual(example.value["a"].value, "a");
+ strictEqual(example.value["a"].kind, "string");
+ strictEqual(example.value["a"].type.kind, "string");
+ strictEqual(example.value["b"].value, "b");
+ strictEqual(example.value["b"].kind, "string");
+ strictEqual(example.value["b"].type.kind, "string");
+ strictEqual(example.value["c"].value, "c");
+ strictEqual(example.value["c"].kind, "string");
+ strictEqual(example.value["c"].type.kind, "string");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkDictionaryExample diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getDictionaryDiagnostic.json",
+ `${__dirname}/example-types/getDictionaryDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op getDictionaryDiagnostic(): Record;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getDictionaryDiagnostic.json' does not follow its definition:\n"test"`,
+ });
+ });
+
+ it("SdkModelExample", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getModel.json",
+ `${__dirname}/example-types/getModel.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ model Test {
+ a: string;
+ b: int32;
+ }
+
+ op getModel(): Test;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ const example = operation.examples[0].responses.get(200)?.bodyValue;
+ ok(example);
+ strictEqual(example.kind, "model");
+ strictEqual(example.type.kind, "model");
+ strictEqual(example.type.name, "Test");
+ strictEqual(Object.keys(example.value).length, 2);
+ strictEqual(example.value["a"].value, "a");
+ strictEqual(example.value["a"].kind, "string");
+ strictEqual(example.value["a"].type.kind, "string");
+ strictEqual(example.value["b"].value, 2);
+ strictEqual(example.value["b"].kind, "number");
+ strictEqual(example.value["b"].type.kind, "int32");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkModelExample diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getModelDiagnostic.json",
+ `${__dirname}/example-types/getModelDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ model Test {
+ a: string;
+ b: int32;
+ }
+
+ op getModelDiagnostic(): Test;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getModelDiagnostic.json' does not follow its definition:\n{"c":true}`,
+ });
+ });
+
+ it("SdkModelExample from discriminated types", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getModelDiscriminator.json",
+ `${__dirname}/example-types/getModelDiscriminator.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ @discriminator("kind")
+ model Fish {
+ friends?: Fish[];
+ hate?: Record;
+ partner?: Fish;
+ }
+
+ @discriminator("sharktype")
+ model Shark extends Fish {
+ kind: "shark";
+ age: int32;
+ }
+
+ model Salmon extends Fish {
+ kind: "salmon";
+ }
+
+ model SawShark extends Shark {
+ sharktype: "saw";
+ info: string[];
+ prop: int32[];
+ }
+
+ model GoblinShark extends Shark {
+ sharktype: "goblin";
+ }
+
+ op getModelDiscriminator(): Shark;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ const example = operation.examples[0].responses.get(200)?.bodyValue;
+ ok(example);
+ strictEqual(example.kind, "model");
+ strictEqual(example.type.kind, "model");
+ strictEqual(example.type.name, "SawShark");
+ strictEqual(Object.keys(example.value).length, 6);
+ strictEqual(example.value["kind"].value, "shark");
+ strictEqual(example.value["kind"].kind, "string");
+ strictEqual(example.value["kind"].type.kind, "constant");
+ strictEqual(example.value["sharktype"].value, "saw");
+ strictEqual(example.value["sharktype"].kind, "string");
+ strictEqual(example.value["sharktype"].type.kind, "constant");
+
+ strictEqual(example.value["friends"].kind, "array");
+ const friend = example.value["friends"].value[0];
+ ok(friend);
+ strictEqual(friend.type.kind, "model");
+ strictEqual(friend.type.name, "GoblinShark");
+ strictEqual(friend.kind, "model");
+ strictEqual(friend.value["kind"].value, "shark");
+ strictEqual(friend.value["kind"].kind, "string");
+ strictEqual(friend.value["kind"].type.kind, "constant");
+ strictEqual(friend.value["sharktype"].value, "goblin");
+ strictEqual(friend.value["sharktype"].kind, "string");
+ strictEqual(friend.value["sharktype"].type.kind, "constant");
+
+ strictEqual(example.value["hate"].kind, "dict");
+ const hate = example.value["hate"].value["most"];
+ ok(hate);
+ strictEqual(hate.type.kind, "model");
+ strictEqual(hate.type.name, "Salmon");
+ strictEqual(hate.kind, "model");
+ strictEqual(hate.value["kind"].value, "salmon");
+ strictEqual(hate.value["kind"].kind, "string");
+ strictEqual(hate.value["kind"].type.kind, "constant");
+
+ strictEqual(example.value["age"].value, 2);
+ strictEqual(example.value["age"].kind, "number");
+ strictEqual(example.value["age"].type.kind, "int32");
+
+ strictEqual(example.value["prop"].kind, "array");
+ strictEqual(example.value["prop"].value[0].value, 1);
+ strictEqual(example.value["prop"].value[0].kind, "number");
+ strictEqual(example.value["prop"].value[0].type.kind, "int32");
+ strictEqual(example.value["prop"].value[1].value, 2);
+ strictEqual(example.value["prop"].value[1].kind, "number");
+ strictEqual(example.value["prop"].value[1].type.kind, "int32");
+ strictEqual(example.value["prop"].value[2].value, 3);
+ strictEqual(example.value["prop"].value[2].kind, "number");
+ strictEqual(example.value["prop"].value[2].type.kind, "int32");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("SdkModelExample from discriminated types diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getModelDiscriminatorDiagnostic.json",
+ `${__dirname}/example-types/getModelDiscriminatorDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ @discriminator("kind")
+ model Fish {
+ }
+
+ @discriminator("sharktype")
+ model Shark extends Fish {
+ kind: "shark";
+ }
+
+ model Salmon extends Fish {
+ kind: "salmon";
+ }
+
+ model SawShark extends Shark {
+ sharktype: "saw";
+ }
+
+ model GoblinShark extends Shark {
+ sharktype: "goblin";
+ }
+
+ op getModelDiscriminatorDiagnostic(): Shark;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].responses.get(200)?.bodyValue, undefined);
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'getModelDiscriminatorDiagnostic.json' does not follow its definition:\n{"kind":"shark","sharktype":"test","age":2}`,
+ });
+ });
+ 1;
+ it("SdkModelExample with additional properties", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getModelAdditionalProperties.json",
+ `${__dirname}/example-types/getModelAdditionalProperties.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ model Test {
+ a: string;
+ b: int32;
+
+ ...Record;
+ }
+
+ op getModelAdditionalProperties(): Test;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ const example = operation.examples[0].responses.get(200)?.bodyValue;
+ ok(example);
+ strictEqual(example.kind, "model");
+ strictEqual(example.type.kind, "model");
+ strictEqual(example.type.name, "Test");
+ strictEqual(Object.keys(example.value).length, 2);
+ strictEqual(example.value["a"].value, "a");
+ strictEqual(example.value["a"].kind, "string");
+ strictEqual(example.value["a"].type.kind, "string");
+ strictEqual(example.value["b"].value, 2);
+ strictEqual(example.value["b"].kind, "number");
+ strictEqual(example.value["b"].type.kind, "int32");
+
+ ok(example.additionalPropertiesValue);
+ strictEqual(Object.keys(example.additionalPropertiesValue).length, 2);
+ strictEqual(example.additionalPropertiesValue["c"].value, true);
+ strictEqual(example.additionalPropertiesValue["c"].kind, "any");
+ strictEqual(example.additionalPropertiesValue["c"].type.kind, "any");
+ deepStrictEqual(example.additionalPropertiesValue["d"].value, [1, 2, 3]);
+ strictEqual(example.additionalPropertiesValue["d"].kind, "any");
+ strictEqual(example.additionalPropertiesValue["d"].type.kind, "any");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+});
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getAny.json b/packages/typespec-client-generator-core/test/examples/example-types/getAny.json
new file mode 100644
index 0000000000..f04bb12d0b
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getAny.json
@@ -0,0 +1,12 @@
+{
+ "operationId": "getAny",
+ "title": "getAny",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": {
+ "test": 123
+ }
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getArray.json b/packages/typespec-client-generator-core/test/examples/example-types/getArray.json
new file mode 100644
index 0000000000..7770605f29
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getArray.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getArray",
+ "title": "getArray",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": ["a", "b", "c"]
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getArrayDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getArrayDiagnostic.json
new file mode 100644
index 0000000000..a25fe6956b
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getArrayDiagnostic.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getArrayDiagnostic",
+ "title": "getArrayDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "test"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getBoolean.json b/packages/typespec-client-generator-core/test/examples/example-types/getBoolean.json
new file mode 100644
index 0000000000..e80d31001d
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getBoolean.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getBoolean",
+ "title": "getBoolean",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": true
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getBooleanDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getBooleanDiagnostic.json
new file mode 100644
index 0000000000..5f569a8019
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getBooleanDiagnostic.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getBooleanDiagnostic",
+ "title": "getBooleanDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": 123
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getDictionary.json b/packages/typespec-client-generator-core/test/examples/example-types/getDictionary.json
new file mode 100644
index 0000000000..c9506f6b4b
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getDictionary.json
@@ -0,0 +1,14 @@
+{
+ "operationId": "getDictionary",
+ "title": "getDictionary",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": {
+ "a": "a",
+ "b": "b",
+ "c": "c"
+ }
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getDictionaryDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getDictionaryDiagnostic.json
new file mode 100644
index 0000000000..d7680f8b31
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getDictionaryDiagnostic.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getDictionaryDiagnostic",
+ "title": "getDictionaryDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "test"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getModel.json b/packages/typespec-client-generator-core/test/examples/example-types/getModel.json
new file mode 100644
index 0000000000..de88bab8af
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getModel.json
@@ -0,0 +1,13 @@
+{
+ "operationId": "getModel",
+ "title": "getModel",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": {
+ "a": "a",
+ "b": 2
+ }
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getModelAdditionalProperties.json b/packages/typespec-client-generator-core/test/examples/example-types/getModelAdditionalProperties.json
new file mode 100644
index 0000000000..8fe79f1e79
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getModelAdditionalProperties.json
@@ -0,0 +1,15 @@
+{
+ "operationId": "getModelAdditionalProperties",
+ "title": "getModelAdditionalProperties",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": {
+ "a": "a",
+ "b": 2,
+ "c": true,
+ "d": [1, 2, 3]
+ }
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getModelDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getModelDiagnostic.json
new file mode 100644
index 0000000000..40a6d61e74
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getModelDiagnostic.json
@@ -0,0 +1,14 @@
+{
+ "operationId": "getModelDiagnostic",
+ "title": "getModelDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": {
+ "a": "a",
+ "b": 2,
+ "c": true
+ }
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getModelDiscriminator.json b/packages/typespec-client-generator-core/test/examples/example-types/getModelDiscriminator.json
new file mode 100644
index 0000000000..e92102f011
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getModelDiscriminator.json
@@ -0,0 +1,27 @@
+{
+ "operationId": "getModelDiscriminator",
+ "title": "getModelDiscriminator",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": {
+ "kind": "shark",
+ "sharktype": "saw",
+ "friends": [
+ {
+ "kind": "shark",
+ "sharktype": "goblin",
+ "age": 1
+ }
+ ],
+ "hate": {
+ "most": {
+ "kind": "salmon"
+ }
+ },
+ "age": 2,
+ "prop": [1, 2, 3]
+ }
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getModelDiscriminatorDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getModelDiscriminatorDiagnostic.json
new file mode 100644
index 0000000000..66a28f797e
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getModelDiscriminatorDiagnostic.json
@@ -0,0 +1,14 @@
+{
+ "operationId": "getModelDiscriminatorDiagnostic",
+ "title": "getModelDiscriminatorDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": {
+ "kind": "shark",
+ "sharktype": "test",
+ "age": 2
+ }
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getNull.json b/packages/typespec-client-generator-core/test/examples/example-types/getNull.json
new file mode 100644
index 0000000000..00585ff170
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getNull.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getNull",
+ "title": "getNull",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": null
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getNumber.json b/packages/typespec-client-generator-core/test/examples/example-types/getNumber.json
new file mode 100644
index 0000000000..3ac703b54f
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getNumber.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getNumber",
+ "title": "getNumber",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": 31.752
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getNumberDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getNumberDiagnostic.json
new file mode 100644
index 0000000000..0ae913bb57
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getNumberDiagnostic.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getNumberDiagnostic",
+ "title": "getNumberDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "123"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getNumberFromDateTime.json b/packages/typespec-client-generator-core/test/examples/example-types/getNumberFromDateTime.json
new file mode 100644
index 0000000000..5170a22a38
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getNumberFromDateTime.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getNumberFromDateTime",
+ "title": "getNumberFromDateTime",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": 1686566864
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getNumberFromDuration.json b/packages/typespec-client-generator-core/test/examples/example-types/getNumberFromDuration.json
new file mode 100644
index 0000000000..72d89f143c
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getNumberFromDuration.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getNumberFromDuration",
+ "title": "getNumberFromDuration",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": 62.525
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getString.json b/packages/typespec-client-generator-core/test/examples/example-types/getString.json
new file mode 100644
index 0000000000..f1cf65ecef
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getString.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getString",
+ "title": "getString",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "test"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringDiagnostic.json
new file mode 100644
index 0000000000..69f24eb949
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringDiagnostic.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringDiagnostic",
+ "title": "getStringDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": 123
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringFromConstant.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromConstant.json
new file mode 100644
index 0000000000..d19b457d3b
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromConstant.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringFromConstant",
+ "title": "getStringFromConstant",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "test"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringFromConstantDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromConstantDiagnostic.json
new file mode 100644
index 0000000000..7156e88840
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromConstantDiagnostic.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringFromConstantDiagnostic",
+ "title": "getStringFromConstantDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": 123
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringFromDataTime.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromDataTime.json
new file mode 100644
index 0000000000..946ae838c1
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromDataTime.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringFromDataTime",
+ "title": "getStringFromDataTime",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "2022-08-26T18:38:00.000Z"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringFromDuration.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromDuration.json
new file mode 100644
index 0000000000..8af9a53965
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromDuration.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringFromDuration",
+ "title": "getStringFromDuration",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "P40D"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnum.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnum.json
new file mode 100644
index 0000000000..44d1c3dac2
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnum.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringFromEnum",
+ "title": "getStringFromEnum",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "one"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumDiagnostic.json
new file mode 100644
index 0000000000..8f53d9be10
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumDiagnostic.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringFromEnumDiagnostic",
+ "title": "getStringFromEnumDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "four"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumValue.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumValue.json
new file mode 100644
index 0000000000..02a01069a0
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumValue.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringFromEnumValue",
+ "title": "getStringFromEnumValue",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "one"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumValueDiagnostic.json b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumValueDiagnostic.json
new file mode 100644
index 0000000000..b11ac19ec9
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getStringFromEnumValueDiagnostic.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getStringFromEnumValueDiagnostic",
+ "title": "getStringFromEnumValueDiagnostic",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "four"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/example-types/getUnion.json b/packages/typespec-client-generator-core/test/examples/example-types/getUnion.json
new file mode 100644
index 0000000000..baf90b3860
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/example-types/getUnion.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "getUnion",
+ "title": "getUnion",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "test"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/helper.test.ts b/packages/typespec-client-generator-core/test/examples/helper.test.ts
new file mode 100644
index 0000000000..f5974dbd4e
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/helper.test.ts
@@ -0,0 +1,40 @@
+import { Operation } from "@typespec/compiler";
+import { strictEqual } from "assert";
+import { beforeEach, describe, it } from "vitest";
+import { getHttpOperationExamples, getHttpOperationWithCache } from "../../src/public-utils.js";
+import { SdkTestRunner, createSdkTestRunner } from "../test-host.js";
+
+describe("typespec-client-generator-core: helper", () => {
+ let runner: SdkTestRunner;
+
+ beforeEach(async () => {
+ runner = await createSdkTestRunner({
+ emitterName: "@azure-tools/typespec-java",
+ "examples-directory": `./examples`,
+ });
+ });
+
+ it("getHttpOperationExamples", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getOne.json",
+ `${__dirname}/helper/getOne.json`
+ );
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getTwo.json",
+ `${__dirname}/helper/getTwo.json`
+ );
+ const { get } = await runner.compile(`
+ @service({})
+ namespace TestClient {
+ @test
+ op get(): string;
+ }
+ `);
+
+ const examples = getHttpOperationExamples(
+ runner.context,
+ getHttpOperationWithCache(runner.context, get as Operation)
+ );
+ strictEqual(examples.length, 2);
+ });
+});
diff --git a/packages/typespec-client-generator-core/test/examples/helper/getOne.json b/packages/typespec-client-generator-core/test/examples/helper/getOne.json
new file mode 100644
index 0000000000..29801a0b23
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/helper/getOne.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "get",
+ "title": "get two",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "two"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/helper/getTwo.json b/packages/typespec-client-generator-core/test/examples/helper/getTwo.json
new file mode 100644
index 0000000000..4f667cc2a5
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/helper/getTwo.json
@@ -0,0 +1,10 @@
+{
+ "operationId": "get",
+ "title": "get one",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "body": "one"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/http-operation-examples.test.ts b/packages/typespec-client-generator-core/test/examples/http-operation-examples.test.ts
new file mode 100644
index 0000000000..6ee6792fd1
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/http-operation-examples.test.ts
@@ -0,0 +1,240 @@
+import { expectDiagnostics, resolveVirtualPath } from "@typespec/compiler/testing";
+import { deepStrictEqual, ok, strictEqual } from "assert";
+import { beforeEach, describe, it } from "vitest";
+import { SdkHttpOperation, SdkServiceMethod } from "../../src/interfaces.js";
+import { SdkTestRunner, createSdkTestRunner } from "../test-host.js";
+
+describe("typespec-client-generator-core: http operation examples", () => {
+ let runner: SdkTestRunner;
+
+ beforeEach(async () => {
+ runner = await createSdkTestRunner({
+ emitterName: "@azure-tools/typespec-java",
+ "examples-directory": `./examples`,
+ });
+ });
+
+ it("simple case", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/simple.json",
+ `${__dirname}/http-operation-examples/simple.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op simple(): void;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].kind, "http");
+ strictEqual(operation.examples[0].name, "simple description");
+ strictEqual(operation.examples[0].description, "simple description");
+ strictEqual(operation.examples[0].filePath, resolveVirtualPath("./examples/simple.json"));
+ deepStrictEqual(operation.examples[0].rawExample, {
+ operationId: "simple",
+ title: "simple description",
+ parameters: {},
+ responses: {},
+ });
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("parameters", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/parameters.json",
+ `${__dirname}/http-operation-examples/parameters.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ @route("/{b}")
+ op parameters(
+ @header a: string,
+ @path b: string,
+ @query c: string,
+ @body d: string,
+ ): void;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].kind, "http");
+
+ const parameters = operation.examples[0].parameters;
+ ok(parameters);
+ strictEqual(parameters.length, 4);
+
+ strictEqual(parameters[0].value.kind, "string");
+ strictEqual(parameters[0].value.value, "header");
+ strictEqual(parameters[0].value.type.kind, "string");
+
+ strictEqual(parameters[1].value.kind, "string");
+ strictEqual(parameters[1].value.value, "path");
+ strictEqual(parameters[1].value.type.kind, "string");
+
+ strictEqual(parameters[2].value.kind, "string");
+ strictEqual(parameters[2].value.value, "query");
+ strictEqual(parameters[2].value.type.kind, "string");
+
+ strictEqual(parameters[3].value.kind, "string");
+ strictEqual(parameters[3].value.value, "body");
+ strictEqual(parameters[3].value.type.kind, "string");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("parameters diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/parametersDiagnostic.json",
+ `${__dirname}/http-operation-examples/parametersDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ @route("/{b}")
+ op parametersDiagnostic(
+ @header a: string,
+ @path b: string,
+ @query c: string,
+ @body d: string,
+ ): void;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].kind, "http");
+
+ const parameters = operation.examples[0].parameters;
+ ok(parameters);
+ strictEqual(parameters.length, 0);
+
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'parametersDiagnostic.json' does not follow its definition:\n{"test":"a"}`,
+ });
+ });
+
+ it("responses", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/responses.json",
+ `${__dirname}/http-operation-examples/responses.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op responses(): {
+ @statusCode
+ code: 200,
+ @body
+ body: string
+ } | {
+ @statusCode
+ code: 201,
+ @header
+ test: string
+ };
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].kind, "http");
+
+ const okResponse = operation.examples[0].responses.get(200);
+ ok(okResponse);
+ deepStrictEqual(okResponse.response, operation.responses.get(200));
+ ok(okResponse.bodyValue);
+
+ strictEqual(okResponse.bodyValue.kind, "string");
+ strictEqual(okResponse.bodyValue.value, "test");
+ strictEqual(okResponse.bodyValue.type.kind, "string");
+
+ const createdResponse = operation.examples[0].responses.get(201);
+ ok(createdResponse);
+ deepStrictEqual(createdResponse.response, operation.responses.get(201));
+
+ strictEqual(createdResponse.bodyValue, undefined);
+ strictEqual(createdResponse.headers.length, 1);
+
+ deepStrictEqual(createdResponse.headers[0].header, operation.responses.get(201)?.headers[0]);
+ strictEqual(createdResponse.headers[0].value.value, "test");
+ strictEqual(createdResponse.headers[0].value.kind, "string");
+ strictEqual(createdResponse.headers[0].value.type.kind, "string");
+
+ expectDiagnostics(runner.context.diagnostics, []);
+ });
+
+ it("responses diagnostic", async () => {
+ await runner.host.addRealTypeSpecFile(
+ "./examples/responsesDiagnostic.json",
+ `${__dirname}/http-operation-examples/responsesDiagnostic.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op responsesDiagnostic(): {
+ @statusCode
+ code: 200,
+ @body
+ body: string
+ } | {
+ @statusCode
+ code: 201,
+ @header
+ test: string
+ };
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ strictEqual(operation.examples[0].kind, "http");
+
+ strictEqual(operation.examples[0].responses.size, 1);
+ const createdResponse = operation.examples[0].responses.get(201);
+ ok(createdResponse);
+ deepStrictEqual(createdResponse.response, operation.responses.get(201));
+
+ strictEqual(createdResponse.bodyValue, undefined);
+ strictEqual(createdResponse.headers.length, 0);
+
+ expectDiagnostics(runner.context.diagnostics, [
+ {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'responsesDiagnostic.json' does not follow its definition:\n{"a":"test"}`,
+ },
+ {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'responsesDiagnostic.json' does not follow its definition:\n{"body":"test"}`,
+ },
+ {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'responsesDiagnostic.json' does not follow its definition:\n{"test":1}`,
+ },
+ {
+ code: "@azure-tools/typespec-client-generator-core/example-value-no-mapping",
+ message: `Value in example file 'responsesDiagnostic.json' does not follow its definition:\n{"203":{"headers":{},"body":"test"}}`,
+ },
+ ]);
+ });
+});
diff --git a/packages/typespec-client-generator-core/test/examples/http-operation-examples/parameters.json b/packages/typespec-client-generator-core/test/examples/http-operation-examples/parameters.json
new file mode 100644
index 0000000000..da862caee5
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/http-operation-examples/parameters.json
@@ -0,0 +1,11 @@
+{
+ "operationId": "parameters",
+ "title": "parameters",
+ "parameters": {
+ "a": "header",
+ "b": "path",
+ "c": "query",
+ "d": "body"
+ },
+ "responses": {}
+}
diff --git a/packages/typespec-client-generator-core/test/examples/http-operation-examples/parametersDiagnostic.json b/packages/typespec-client-generator-core/test/examples/http-operation-examples/parametersDiagnostic.json
new file mode 100644
index 0000000000..26846ae42d
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/http-operation-examples/parametersDiagnostic.json
@@ -0,0 +1,8 @@
+{
+ "operationId": "parametersDiagnostic",
+ "title": "parametersDiagnostic",
+ "parameters": {
+ "test": "a"
+ },
+ "responses": {}
+}
diff --git a/packages/typespec-client-generator-core/test/examples/http-operation-examples/responses.json b/packages/typespec-client-generator-core/test/examples/http-operation-examples/responses.json
new file mode 100644
index 0000000000..dbaded7c67
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/http-operation-examples/responses.json
@@ -0,0 +1,16 @@
+{
+ "operationId": "responses",
+ "title": "responses",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "headers": {},
+ "body": "test"
+ },
+ "201": {
+ "headers": {
+ "test": "test"
+ }
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/http-operation-examples/responsesDiagnostic.json b/packages/typespec-client-generator-core/test/examples/http-operation-examples/responsesDiagnostic.json
new file mode 100644
index 0000000000..41e5e85967
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/http-operation-examples/responsesDiagnostic.json
@@ -0,0 +1,18 @@
+{
+ "operationId": "responsesDiagnostic",
+ "title": "responsesDiagnostic",
+ "parameters": {},
+ "responses": {
+ "203": {
+ "headers": {},
+ "body": "test"
+ },
+ "201": {
+ "headers": {
+ "a": "test"
+ },
+ "body": "test",
+ "test": 1
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/http-operation-examples/simple.json b/packages/typespec-client-generator-core/test/examples/http-operation-examples/simple.json
new file mode 100644
index 0000000000..d684e8ca9a
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/http-operation-examples/simple.json
@@ -0,0 +1,6 @@
+{
+ "operationId": "simple",
+ "title": "simple description",
+ "parameters": {},
+ "responses": {}
+}
diff --git a/packages/typespec-client-generator-core/test/examples/load.test.ts b/packages/typespec-client-generator-core/test/examples/load.test.ts
new file mode 100644
index 0000000000..e50873e426
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/load.test.ts
@@ -0,0 +1,125 @@
+import { expectDiagnostics } from "@typespec/compiler/testing";
+import { ok, strictEqual } from "assert";
+import { beforeEach, describe, it } from "vitest";
+import { SdkHttpOperation, SdkServiceMethod } from "../../src/interfaces.js";
+import { SdkTestRunner, createSdkTestRunner } from "../test-host.js";
+
+describe("typespec-client-generator-core: load examples", () => {
+ let runner: SdkTestRunner;
+
+ beforeEach(async () => {
+ runner = await createSdkTestRunner({
+ emitterName: "@azure-tools/typespec-java",
+ "examples-directory": `./examples`,
+ });
+ });
+
+ it("no example folder found", async () => {
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op get(): string;
+ }
+ `);
+
+ expectDiagnostics(runner.context.diagnostics, {
+ code: "@azure-tools/typespec-client-generator-core/example-loading",
+ });
+ });
+
+ it("load example without version", async () => {
+ await runner.host.addRealTypeSpecFile("./examples/get.json", `${__dirname}/load/get.json`);
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op get(): string;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ });
+
+ it("load example with version", async () => {
+ await runner.host.addRealTypeSpecFile("./examples/v3/get.json", `${__dirname}/load/get.json`);
+ await runner.compile(`
+ @service({})
+ @versioned(Versions)
+ namespace TestClient {
+ op get(): string;
+ }
+
+ enum Versions {
+ v1,
+ v2,
+ v3,
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ });
+
+ it("load multiple example for one operation", async () => {
+ await runner.host.addRealTypeSpecFile("./examples/get.json", `${__dirname}/load/get.json`);
+ await runner.host.addRealTypeSpecFile(
+ "./examples/getAnother.json",
+ `${__dirname}/load/getAnother.json`
+ );
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op get(): string;
+ }
+ `);
+
+ const operation = (
+ runner.context.sdkPackage.clients[0].methods[0] as SdkServiceMethod
+ ).operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 2);
+ });
+
+ it("load example with client customization", async () => {
+ await runner.host.addRealTypeSpecFile("./examples/get.json", `${__dirname}/load/get.json`);
+ await runner.compile(`
+ @service({})
+ namespace TestClient {
+ op get(): string;
+ }
+ `);
+
+ await runner.compileWithCustomization(
+ `
+ @service({})
+ namespace TestClient {
+ op get(): string;
+ }
+ `,
+ `
+ @client({
+ name: "FooClient",
+ service: TestClient
+ })
+ namespace Customizations {
+ op test is TestClient.get;
+ }
+ `
+ );
+
+ const client = runner.context.sdkPackage.clients[0];
+ strictEqual(client.name, "FooClient");
+ const method = client.methods[0] as SdkServiceMethod;
+ ok(method);
+ strictEqual(method.name, "test");
+ const operation = method.operation;
+ ok(operation);
+ strictEqual(operation.examples?.length, 1);
+ });
+});
diff --git a/packages/typespec-client-generator-core/test/examples/load/get.json b/packages/typespec-client-generator-core/test/examples/load/get.json
new file mode 100644
index 0000000000..5c3a0efc7f
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/load/get.json
@@ -0,0 +1,11 @@
+{
+ "operationId": "get",
+ "title": "get",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "description": "ARM operation completed successfully.",
+ "body": "test"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/examples/load/getAnother.json b/packages/typespec-client-generator-core/test/examples/load/getAnother.json
new file mode 100644
index 0000000000..51c8823c72
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/examples/load/getAnother.json
@@ -0,0 +1,11 @@
+{
+ "operationId": "Get",
+ "title": "getAnother",
+ "parameters": {},
+ "responses": {
+ "200": {
+ "description": "ARM operation completed successfully.",
+ "body": "test"
+ }
+ }
+}
diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts
index 72377d6cba..d9b8cea43c 100644
--- a/packages/typespec-client-generator-core/test/package.test.ts
+++ b/packages/typespec-client-generator-core/test/package.test.ts
@@ -2694,6 +2694,12 @@ describe("typespec-client-generator-core: package", () => {
ok(nextLinkProperty);
strictEqual(nextLinkProperty.kind, "property");
strictEqual(nextLinkProperty.type.kind, "url");
+ strictEqual(nextLinkProperty.type.name, "ResourceLocation");
+ strictEqual(
+ nextLinkProperty.type.crossLanguageDefinitionId,
+ "TypeSpec.Rest.ResourceLocation"
+ );
+ strictEqual(nextLinkProperty.type.baseType?.kind, "url");
strictEqual(nextLinkProperty.serializedName, "nextLink");
strictEqual(nextLinkProperty.serializedName, listManufacturers.nextLinkPath);
@@ -2704,6 +2710,7 @@ describe("typespec-client-generator-core: package", () => {
strictEqual(clientRequestIdProperty.kind, "header");
});
});
+
describe("spread", () => {
it("plain model with no decorators", async () => {
await runner.compile(`@server("http://localhost:3000", "endpoint")
diff --git a/packages/typespec-client-generator-core/test/public-utils.test.ts b/packages/typespec-client-generator-core/test/public-utils.test.ts
index 92af96a4fa..fab7935ead 100644
--- a/packages/typespec-client-generator-core/test/public-utils.test.ts
+++ b/packages/typespec-client-generator-core/test/public-utils.test.ts
@@ -293,7 +293,7 @@ describe("typespec-client-generator-core: public-utils", () => {
`);
strictEqual(
getClientNamespaceString(
- createSdkContextTestHelper(runner.context.program, {
+ await createSdkContextTestHelper(runner.context.program, {
"generate-convenience-methods": true,
"generate-protocol-methods": true,
})
@@ -309,7 +309,7 @@ describe("typespec-client-generator-core: public-utils", () => {
`);
strictEqual(
getClientNamespaceString(
- createSdkContextTestHelper(runner.context.program, {
+ await createSdkContextTestHelper(runner.context.program, {
"generate-convenience-methods": true,
"generate-protocol-methods": true,
})
@@ -323,7 +323,7 @@ describe("typespec-client-generator-core: public-utils", () => {
`);
strictEqual(
getClientNamespaceString(
- createSdkContextTestHelper(runner.context.program, {
+ await createSdkContextTestHelper(runner.context.program, {
"generate-convenience-methods": true,
"generate-protocol-methods": true,
"package-name": "azure-pick-me",
@@ -338,7 +338,7 @@ describe("typespec-client-generator-core: public-utils", () => {
`);
strictEqual(
getClientNamespaceString(
- createSdkContextTestHelper(runner.context.program, {
+ await createSdkContextTestHelper(runner.context.program, {
"generate-convenience-methods": true,
"generate-protocol-methods": true,
"package-name": "Azure.Pick.Me",
@@ -356,7 +356,7 @@ describe("typespec-client-generator-core: public-utils", () => {
`);
strictEqual(
getClientNamespaceString(
- createSdkContextTestHelper(runner.context.program, {
+ await createSdkContextTestHelper(runner.context.program, {
"generate-convenience-methods": true,
"generate-protocol-methods": true,
"package-name": "azure.pick.me",
@@ -371,7 +371,7 @@ describe("typespec-client-generator-core: public-utils", () => {
`);
strictEqual(
getClientNamespaceString(
- createSdkContextTestHelper(runner.context.program, {
+ await createSdkContextTestHelper(runner.context.program, {
"generate-convenience-methods": true,
"generate-protocol-methods": true,
})
@@ -1430,7 +1430,6 @@ describe("typespec-client-generator-core: public-utils", () => {
const models = runner.context.sdkPackage.models;
const diagnostics = runner.context.diagnostics;
ok(diagnostics);
- deepStrictEqual(diagnostics, runner.context.sdkPackage.diagnostics);
strictEqual(models.length, 4);
const union = models[0].properties[0].type;
strictEqual(union.kind, "union");
diff --git a/packages/typespec-client-generator-core/test/test-host.ts b/packages/typespec-client-generator-core/test/test-host.ts
index 2ec027c6c0..e8980de81b 100644
--- a/packages/typespec-client-generator-core/test/test-host.ts
+++ b/packages/typespec-client-generator-core/test/test-host.ts
@@ -2,6 +2,7 @@ import { Diagnostic, EmitContext, Program, Type } from "@typespec/compiler";
import {
BasicTestRunner,
StandardTestLibrary,
+ TestHost,
TypeSpecTestLibrary,
createTestHost,
createTestWrapper,
@@ -29,6 +30,7 @@ export async function createSdkTestHost(options: CreateSdkTestRunnerOptions = {}
}
export interface SdkTestRunner extends BasicTestRunner {
+ host: TestHost;
context: SdkContext;
compileWithBuiltInService(code: string): Promise>;
compileWithBuiltInAzureCoreService(code: string): Promise>;
@@ -40,21 +42,21 @@ export interface SdkTestRunner extends BasicTestRunner {
): Promise<[Record, readonly Diagnostic[]]>;
}
-export function createSdkContextTestHelper<
+export async function createSdkContextTestHelper<
TOptions extends Record = CreateSdkTestRunnerOptions,
TServiceOperation extends SdkServiceOperation = SdkHttpOperation,
>(
program: Program,
options: TOptions,
sdkContextOption?: CreateSdkContextOptions
-): SdkContext {
+): Promise> {
const emitContext: EmitContext = {
program: program,
emitterOutputDir: "dummy",
options: options,
getAssetEmitter: null as any,
};
- return createSdkContext(
+ return await createSdkContext(
emitContext,
options.emitterName ?? "@azure-tools/typespec-csharp",
sdkContextOption
@@ -86,11 +88,13 @@ export async function createSdkTestRunner(
autoUsings: autoUsings,
}) as SdkTestRunner;
+ sdkTestRunner.host = host;
+
// compile
const baseCompile = sdkTestRunner.compile;
sdkTestRunner.compile = async function compile(code, compileOptions?) {
const result = await baseCompile(code, compileOptions);
- sdkTestRunner.context = createSdkContextTestHelper(
+ sdkTestRunner.context = await createSdkContextTestHelper(
sdkTestRunner.program,
options,
sdkContextOption
@@ -102,7 +106,7 @@ export async function createSdkTestRunner(
const baseDiagnose = sdkTestRunner.diagnose;
sdkTestRunner.diagnose = async function diagnose(code, compileOptions?) {
const result = await baseDiagnose(code, compileOptions);
- sdkTestRunner.context = createSdkContextTestHelper(
+ sdkTestRunner.context = await createSdkContextTestHelper(
sdkTestRunner.program,
options,
sdkContextOption
@@ -114,7 +118,7 @@ export async function createSdkTestRunner(
const baseCompileAndDiagnose = sdkTestRunner.compileAndDiagnose;
sdkTestRunner.compileAndDiagnose = async function compileAndDiagnose(code, compileOptions?) {
const result = await baseCompileAndDiagnose(code, compileOptions);
- sdkTestRunner.context = createSdkContextTestHelper(
+ sdkTestRunner.context = await createSdkContextTestHelper(
sdkTestRunner.program,
options,
sdkContextOption
@@ -131,7 +135,7 @@ export async function createSdkTestRunner(
noEmit: true,
}
);
- sdkTestRunner.context = createSdkContextTestHelper(
+ sdkTestRunner.context = await createSdkContextTestHelper(
sdkTestRunner.program,
options,
sdkContextOption
@@ -153,7 +157,7 @@ export async function createSdkTestRunner(
noEmit: true,
}
);
- sdkTestRunner.context = createSdkContextTestHelper(
+ sdkTestRunner.context = await createSdkContextTestHelper(
sdkTestRunner.program,
options,
sdkContextOption
@@ -183,7 +187,7 @@ export async function createSdkTestRunner(
host.addTypeSpecFile("./main.tsp", `${mainAutoCode}${mainCode}`);
host.addTypeSpecFile("./client.tsp", `${clientAutoCode}${clientCode}`);
const result = await host.compile("./client.tsp");
- sdkTestRunner.context = createSdkContextTestHelper(
+ sdkTestRunner.context = await createSdkContextTestHelper(
sdkTestRunner.program,
options,
sdkContextOption
@@ -216,7 +220,7 @@ export async function createSdkTestRunner(
noEmit: true,
}
);
- sdkTestRunner.context = createSdkContextTestHelper(
+ sdkTestRunner.context = await createSdkContextTestHelper(
sdkTestRunner.program,
options,
sdkContextOption
@@ -229,7 +233,7 @@ export async function createSdkTestRunner(
host.addTypeSpecFile("./main.tsp", `${mainAutoCode}${mainCode}`);
host.addTypeSpecFile("./client.tsp", `${clientAutoCode}${clientCode}`);
const result = await host.compileAndDiagnose("./client.tsp");
- sdkTestRunner.context = createSdkContextTestHelper(
+ sdkTestRunner.context = await createSdkContextTestHelper(
sdkTestRunner.program,
options,
sdkContextOption
diff --git a/packages/typespec-client-generator-core/test/types/built-in-types.test.ts b/packages/typespec-client-generator-core/test/types/built-in-types.test.ts
index 005928b854..7e4e205879 100644
--- a/packages/typespec-client-generator-core/test/types/built-in-types.test.ts
+++ b/packages/typespec-client-generator-core/test/types/built-in-types.test.ts
@@ -107,19 +107,23 @@ describe("typespec-client-generator-core: built-in types", () => {
await runner.compileWithBuiltInService(
`
@encode(BytesKnownEncoding.base64url)
- scalar Base64rulBytes extends bytes;
+ scalar Base64UrlBytes extends bytes;
@usage(Usage.input | Usage.output)
@access(Access.public)
model Test {
- value: Base64rulBytes[];
+ value: Base64UrlBytes[];
}
`
);
const sdkType = getSdkTypeHelper(runner);
strictEqual(sdkType.kind, "array");
strictEqual(sdkType.valueType.kind, "bytes");
+ strictEqual(sdkType.valueType.name, "Base64UrlBytes");
strictEqual(sdkType.valueType.encode, "base64url");
+ strictEqual(sdkType.valueType.crossLanguageDefinitionId, "TestService.Base64UrlBytes");
+ strictEqual(sdkType.valueType.baseType?.kind, "bytes");
+ strictEqual(sdkType.valueType.baseType.encode, "base64");
});
it("armId from Core", async function () {
@@ -142,7 +146,10 @@ describe("typespec-client-generator-core: built-in types", () => {
`
);
const models = runnerWithCore.context.sdkPackage.models;
- strictEqual(models[0].properties[0].type.kind, "armId");
+ const type = models[0].properties[0].type;
+ strictEqual(type.kind, "string");
+ strictEqual(type.name, "armResourceIdentifier");
+ strictEqual(type.crossLanguageDefinitionId, "Azure.Core.armResourceIdentifier");
});
it("format", async function () {
@@ -157,15 +164,9 @@ describe("typespec-client-generator-core: built-in types", () => {
@access(Access.public)
model Test {
urlScalar: url;
- uuidScalar: uuid;
- eTagScalar: eTag;
@format("url")
urlProperty: string;
- @format("uuid")
- uuidProperty: string;
- @format("eTag")
- eTagProperty: string;
}
`
);
@@ -208,7 +209,10 @@ describe("typespec-client-generator-core: built-in types", () => {
strictEqual(userModel.properties.length, 2);
const etagProperty = userModel.properties.find((x) => x.name === "etag");
ok(etagProperty);
- strictEqual(etagProperty.type.kind, "eTag");
+ strictEqual(etagProperty.type.kind, "string");
+ strictEqual(etagProperty.type.name, "eTag");
+ strictEqual(etagProperty.type.encode, "string");
+ strictEqual(etagProperty.type.crossLanguageDefinitionId, "Azure.Core.eTag");
});
it("unknown format", async function () {
@@ -252,7 +256,6 @@ describe("typespec-client-generator-core: built-in types", () => {
): void;
`
);
- expectDiagnostics(runner.context.sdkPackage.diagnostics, []);
expectDiagnostics(runner.context.diagnostics, []);
const m = runner.context.sdkPackage.models.find((x) => x.name === "TestModel");
const e1 = runner.context.sdkPackage.enums.find((x) => x.name === "TestEnum");
@@ -288,7 +291,11 @@ describe("typespec-client-generator-core: built-in types", () => {
);
const models = getAllModels(runner.context);
strictEqual(models[0].kind, "model");
- strictEqual(models[0].properties[0].type.description, "title");
- strictEqual(models[0].properties[0].type.details, "doc");
+ const type = models[0].properties[0].type;
+ strictEqual(type.kind, "string");
+ strictEqual(type.name, "TestScalar");
+ strictEqual(type.description, "title");
+ strictEqual(type.details, "doc");
+ strictEqual(type.crossLanguageDefinitionId, "TestService.TestScalar");
});
});
diff --git a/packages/typespec-client-generator-core/test/types/date-time-types.test.ts b/packages/typespec-client-generator-core/test/types/date-time-types.test.ts
index 29e9f63ff9..72014a5da1 100644
--- a/packages/typespec-client-generator-core/test/types/date-time-types.test.ts
+++ b/packages/typespec-client-generator-core/test/types/date-time-types.test.ts
@@ -25,6 +25,7 @@ describe("typespec-client-generator-core: date-time types", () => {
strictEqual(sdkType.wireType.kind, "string");
strictEqual(sdkType.encode, "rfc3339");
});
+
it("rfc3339", async function () {
await runner.compileWithBuiltInService(
`
@@ -41,6 +42,7 @@ describe("typespec-client-generator-core: date-time types", () => {
strictEqual(sdkType.wireType.kind, "string");
strictEqual(sdkType.encode, "rfc3339");
});
+
it("rfc7231", async function () {
await runner.compileWithBuiltInService(
`
@@ -75,6 +77,41 @@ describe("typespec-client-generator-core: date-time types", () => {
strictEqual(sdkType.encode, "unixTimestamp");
});
+ it("encode propagation", async function () {
+ await runner.compileWithBuiltInService(
+ `
+ @doc("doc")
+ @summary("title")
+ @encode(DateTimeKnownEncoding.unixTimestamp, int64)
+ scalar unixTimestampDatetime extends utcDateTime;
+
+ scalar extraLayerDateTime extends unixTimestampDatetime;
+
+ @usage(Usage.input | Usage.output)
+ @access(Access.public)
+ model Test {
+ value: extraLayerDateTime;
+ }
+ `
+ );
+ const sdkType = getSdkTypeHelper(runner);
+ strictEqual(sdkType.kind, "utcDateTime");
+ strictEqual(sdkType.name, "extraLayerDateTime");
+ strictEqual(sdkType.wireType.kind, "int64");
+ strictEqual(sdkType.encode, "unixTimestamp");
+ strictEqual(sdkType.crossLanguageDefinitionId, "TestService.extraLayerDateTime");
+ strictEqual(sdkType.baseType?.kind, "utcDateTime");
+ strictEqual(sdkType.baseType.name, "unixTimestampDatetime");
+ strictEqual(sdkType.baseType.wireType.kind, "int64");
+ strictEqual(sdkType.baseType.encode, "unixTimestamp");
+ strictEqual(sdkType.baseType.crossLanguageDefinitionId, "TestService.unixTimestampDatetime");
+ strictEqual(sdkType.baseType.baseType?.kind, "utcDateTime");
+ strictEqual(sdkType.baseType.baseType.wireType.kind, "string");
+ strictEqual(sdkType.baseType.baseType.encode, "rfc3339");
+ strictEqual(sdkType.baseType.baseType.name, "utcDateTime");
+ strictEqual(sdkType.baseType.baseType.crossLanguageDefinitionId, "TypeSpec.utcDateTime");
+ });
+
it("nullable unixTimestamp", async function () {
await runner.compileWithBuiltInService(
`
@@ -101,21 +138,25 @@ describe("typespec-client-generator-core: date-time types", () => {
@doc("doc")
@summary("title")
@encode(DateTimeKnownEncoding.unixTimestamp, int64)
- scalar unixTimestampDatetime extends utcDateTime;
+ scalar unixTimestampDateTime extends utcDateTime;
@usage(Usage.input | Usage.output)
@access(Access.public)
model Test {
- value: unixTimestampDatetime[];
+ value: unixTimestampDateTime[];
}
`
);
const sdkType = getSdkTypeHelper(runner);
strictEqual(sdkType.kind, "array");
strictEqual(sdkType.valueType.kind, "utcDateTime");
- strictEqual(sdkType.valueType.wireType.kind, "int64");
+ strictEqual(sdkType.valueType.name, "unixTimestampDateTime");
strictEqual(sdkType.valueType.encode, "unixTimestamp");
+ strictEqual(sdkType.valueType.wireType?.kind, "int64");
strictEqual(sdkType.valueType.description, "title");
strictEqual(sdkType.valueType.details, "doc");
+ strictEqual(sdkType.valueType.crossLanguageDefinitionId, "TestService.unixTimestampDateTime");
+ strictEqual(sdkType.valueType.baseType?.kind, "utcDateTime");
+ strictEqual(sdkType.valueType.baseType.wireType.kind, "string");
});
});
diff --git a/packages/typespec-client-generator-core/test/types/duration-type.test.ts b/packages/typespec-client-generator-core/test/types/duration-type.test.ts
index aeb989affa..68fe2efedf 100644
--- a/packages/typespec-client-generator-core/test/types/duration-type.test.ts
+++ b/packages/typespec-client-generator-core/test/types/duration-type.test.ts
@@ -113,9 +113,17 @@ describe("typespec-client-generator-core: duration types", () => {
const sdkType = getSdkTypeHelper(runner);
strictEqual(sdkType.kind, "array");
strictEqual(sdkType.valueType.kind, "duration");
- strictEqual(sdkType.valueType.wireType.kind, "float32");
- strictEqual(sdkType.valueType.encode, "seconds");
+ strictEqual(sdkType.valueType.name, "Float32Duration");
strictEqual(sdkType.valueType.description, "title");
strictEqual(sdkType.valueType.details, "doc");
+ // the encode and wireType will only be added to the outer type
+ strictEqual(sdkType.valueType.encode, "seconds");
+ strictEqual(sdkType.valueType.crossLanguageDefinitionId, "TestService.Float32Duration");
+ strictEqual(sdkType.valueType.wireType?.kind, "float32");
+ strictEqual(sdkType.valueType.baseType?.kind, "duration");
+ // the encode and wireType on the baseType will have its default value
+ strictEqual(sdkType.valueType.baseType.wireType.kind, "string");
+ strictEqual(sdkType.valueType.baseType.encode, "ISO8601");
+ strictEqual(sdkType.valueType.baseType.crossLanguageDefinitionId, "TypeSpec.duration");
});
});
diff --git a/packages/typespec-client-generator-core/test/types/general-decorators-list.test.ts b/packages/typespec-client-generator-core/test/types/general-decorators-list.test.ts
index ddf113dcae..a11d50683f 100644
--- a/packages/typespec-client-generator-core/test/types/general-decorators-list.test.ts
+++ b/packages/typespec-client-generator-core/test/types/general-decorators-list.test.ts
@@ -3,6 +3,7 @@ import { expectDiagnostics } from "@typespec/compiler/testing";
import { XmlTestLibrary } from "@typespec/xml/testing";
import { deepStrictEqual, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
+import { SdkEnumValueType } from "../../src/interfaces.js";
import { SdkTestRunner, createSdkTestRunner } from "../test-host.js";
describe("typespec-client-generator-core: general decorators list", () => {
@@ -72,14 +73,11 @@ describe("typespec-client-generator-core: general decorators list", () => {
const models = runner.context.sdkPackage.models;
strictEqual(models.length, 1);
- deepStrictEqual(models[0].properties[0].decorators, [
- {
- name: "TypeSpec.@encode",
- arguments: {
- encoding: "base64url",
- },
- },
- ]);
+ strictEqual(models[0].properties[0].decorators[0].name, "TypeSpec.@encode");
+ const encodeInfo = models[0].properties[0].decorators[0].arguments[
+ "encoding"
+ ] as SdkEnumValueType;
+ strictEqual(encodeInfo.value, "base64url");
expectDiagnostics(runner.context.diagnostics, []);
});
@@ -266,30 +264,17 @@ describe("typespec-client-generator-core: general decorators list", () => {
const models = runner.context.sdkPackage.models;
strictEqual(models.length, 1);
- deepStrictEqual(models[0].decorators, [
- {
- name: "TypeSpec.Xml.@ns",
- arguments: {
- ns: "https://example.com/ns1",
- },
- },
- ]);
- deepStrictEqual(models[0].properties[0].decorators, [
- {
- name: "TypeSpec.Xml.@ns",
- arguments: {
- ns: "https://example.com/ns1",
- },
- },
- ]);
- deepStrictEqual(models[0].properties[1].decorators, [
- {
- name: "TypeSpec.Xml.@ns",
- arguments: {
- ns: "https://example.com/ns2",
- },
- },
- ]);
+ strictEqual(models[0].decorators[0].name, "TypeSpec.Xml.@ns");
+ const modelArg = models[0].decorators[0].arguments["ns"] as SdkEnumValueType;
+ strictEqual(modelArg.value, "https://example.com/ns1");
+
+ strictEqual(models[0].properties[0].decorators[0].name, "TypeSpec.Xml.@ns");
+ let propArg = models[0].properties[0].decorators[0].arguments["ns"] as SdkEnumValueType;
+ strictEqual(propArg.value, "https://example.com/ns1");
+
+ strictEqual(models[0].properties[1].decorators[0].name, "TypeSpec.Xml.@ns");
+ propArg = models[0].properties[1].decorators[0].arguments["ns"] as SdkEnumValueType;
+ strictEqual(propArg.value, "https://example.com/ns2");
});
it("@unwrapped", async function () {
diff --git a/packages/typespec-client-generator-core/test/types/model-types.test.ts b/packages/typespec-client-generator-core/test/types/model-types.test.ts
index d0a139139a..7574650c58 100644
--- a/packages/typespec-client-generator-core/test/types/model-types.test.ts
+++ b/packages/typespec-client-generator-core/test/types/model-types.test.ts
@@ -268,7 +268,8 @@ describe("typespec-client-generator-core: model types", () => {
strictEqual(kindProperty.discriminator, true);
strictEqual(kindProperty.type.kind, "string");
strictEqual(kindProperty.__raw, undefined);
- strictEqual(kindProperty.type.__raw, undefined);
+ strictEqual(kindProperty.type.__raw?.kind, "Scalar");
+ strictEqual(kindProperty.type.__raw?.name, "string");
strictEqual(fish.discriminatorProperty, kindProperty);
});
@@ -1467,4 +1468,38 @@ describe("typespec-client-generator-core: model types", () => {
strictEqual(models[0].name, "Test");
strictEqual(models[0].properties.length, 0);
});
+
+ it("xml usage", async () => {
+ await runner.compileAndDiagnose(`
+ @service({})
+ namespace MyService {
+ model RoundTrip {
+ prop: string;
+ }
+
+ model Input {
+ prop: string;
+ }
+
+ @route("/test1")
+ op test1(@header("content-type") contentType: "application/xml", @body body: RoundTrip): RoundTrip;
+
+ @route("/test2")
+ op test2(@header("content-type") contentType: "application/xml", @body body: Input): void;
+ }
+ `);
+
+ const models = runner.context.sdkPackage.models;
+ strictEqual(models.length, 2);
+ const roundTripModel = models.find((x) => x.name === "RoundTrip");
+ const inputModel = models.find((x) => x.name === "Input");
+ ok(roundTripModel);
+ strictEqual(
+ roundTripModel.usage,
+ UsageFlags.Input | UsageFlags.Output | UsageFlags.Json | UsageFlags.Xml
+ );
+
+ ok(inputModel);
+ strictEqual(inputModel.usage, UsageFlags.Input | UsageFlags.Xml);
+ });
});
diff --git a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts
index 3ee5488f9c..f1d8ebe1c8 100644
--- a/packages/typespec-client-generator-core/test/types/multipart-types.test.ts
+++ b/packages/typespec-client-generator-core/test/types/multipart-types.test.ts
@@ -1,8 +1,13 @@
/* eslint-disable deprecation/deprecation */
import { expectDiagnostics } from "@typespec/compiler/testing";
-import { ok, strictEqual } from "assert";
+import { deepEqual, ok, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
-import { SdkClientType, SdkHttpOperation, UsageFlags } from "../../src/interfaces.js";
+import {
+ SdkBodyModelPropertyType,
+ SdkClientType,
+ SdkHttpOperation,
+ UsageFlags,
+} from "../../src/interfaces.js";
import { getAllModelsWithDiagnostics } from "../../src/types.js";
import { SdkTestRunner, createSdkTestRunner } from "../test-host.js";
@@ -39,6 +44,8 @@ describe("typespec-client-generator-core: multipart types", () => {
ok(profileImage);
strictEqual(profileImage.kind, "property");
strictEqual(profileImage.isMultipartFileInput, true);
+ ok(profileImage.multipartOptions);
+ strictEqual(profileImage.multipartOptions.isFilePart, true);
});
it("multipart conflicting model usage", async function () {
await runner.compile(
@@ -84,6 +91,8 @@ describe("typespec-client-generator-core: multipart types", () => {
const modelAProp = modelA.properties[0];
strictEqual(modelAProp.kind, "property");
strictEqual(modelAProp.isMultipartFileInput, true);
+ ok(modelAProp.multipartOptions);
+ strictEqual(modelAProp.multipartOptions.isFilePart, true);
const modelB = models.find((x) => x.name === "NormalOperationRequest");
ok(modelB);
@@ -135,6 +144,9 @@ describe("typespec-client-generator-core: multipart types", () => {
const pictures = model.properties[0];
strictEqual(pictures.kind, "property");
strictEqual(pictures.isMultipartFileInput, true);
+ ok(pictures.multipartOptions);
+ strictEqual(pictures.multipartOptions.isFilePart, true);
+ strictEqual(pictures.multipartOptions.isMulti, true);
});
it("multipart with encoding bytes raises error", async function () {
@@ -273,4 +285,335 @@ describe("typespec-client-generator-core: multipart types", () => {
ok(address);
strictEqual(address.usage & UsageFlags.MultipartFormData, 0);
});
+
+ it("Json[] and bytes[] in multipart/form-data", async function () {
+ await runner.compileWithBuiltInService(`
+ model MultiPartRequest {
+ profileImages: bytes[];
+ addresses: Address[];
+ }
+ model Address {
+ city: string;
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @body body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ strictEqual(models.length, 2);
+ const multiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(multiPartRequest);
+
+ for (const p of multiPartRequest.properties.values()) {
+ strictEqual(p.kind, "property");
+ ok(p.multipartOptions);
+ ok(p.type.kind === "bytes" || p.type.kind === "model");
+ strictEqual(p.multipartOptions.isMulti, true);
+ }
+ });
+
+ it("basic multipart with @multipartBody for model", async function () {
+ await runner.compileWithBuiltInService(`
+ model Address {
+ city: string;
+ }
+ model MultiPartRequest{
+ id?: HttpPart;
+ profileImage: HttpPart;
+ address: HttpPart;
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ strictEqual(models.length, 2);
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+ ok(MultiPartRequest.usage & UsageFlags.MultipartFormData);
+ const id = MultiPartRequest.properties.find((x) => x.name === "id") as SdkBodyModelPropertyType;
+ strictEqual(id.optional, true);
+ ok(id.multipartOptions);
+ strictEqual(id.multipartOptions.isFilePart, false);
+ deepEqual(id.multipartOptions.defaultContentTypes, ["text/plain"]);
+ const profileImage = MultiPartRequest.properties.find(
+ (x) => x.name === "profileImage"
+ ) as SdkBodyModelPropertyType;
+ strictEqual(profileImage.optional, false);
+ ok(profileImage.multipartOptions);
+ strictEqual(profileImage.multipartOptions.isFilePart, true);
+ strictEqual(profileImage.multipartOptions.filename, undefined);
+ strictEqual(profileImage.multipartOptions.contentType, undefined);
+ deepEqual(profileImage.multipartOptions.defaultContentTypes, ["application/octet-stream"]);
+ const address = MultiPartRequest.properties.find(
+ (x) => x.name === "address"
+ ) as SdkBodyModelPropertyType;
+ strictEqual(address.optional, false);
+ ok(address.multipartOptions);
+ strictEqual(address.multipartOptions.isFilePart, false);
+ deepEqual(address.multipartOptions.defaultContentTypes, ["application/json"]);
+ strictEqual(address.type.kind, "model");
+ });
+
+ it("File[] of multipart with @multipartBody for model", async function () {
+ await runner.compileWithBuiltInService(`
+ model MultiPartRequest{
+ fileArrayOnePart: HttpPart;
+ fileArrayMultiParts: HttpPart[];
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ strictEqual(models.length, 2);
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+ const fileArrayOnePart = MultiPartRequest.properties.find(
+ (x) => x.name === "fileArrayOnePart"
+ ) as SdkBodyModelPropertyType;
+ ok(fileArrayOnePart);
+ ok(fileArrayOnePart.multipartOptions);
+ strictEqual(fileArrayOnePart.type.kind, "array");
+ strictEqual(fileArrayOnePart.multipartOptions.isMulti, false);
+ strictEqual(fileArrayOnePart.multipartOptions.filename, undefined);
+ strictEqual(fileArrayOnePart.multipartOptions.contentType, undefined);
+ // Maybe we won't meet this case in real world, but we still need to test it.
+ deepEqual(fileArrayOnePart.multipartOptions.defaultContentTypes, ["application/json"]);
+
+ const fileArrayMultiParts = MultiPartRequest.properties.find(
+ (x) => x.name === "fileArrayMultiParts"
+ ) as SdkBodyModelPropertyType;
+ ok(fileArrayMultiParts);
+ ok(fileArrayMultiParts.multipartOptions);
+ strictEqual(fileArrayMultiParts.type.kind, "model");
+ strictEqual(fileArrayMultiParts.multipartOptions.isMulti, true);
+ ok(fileArrayMultiParts.multipartOptions.filename);
+ strictEqual(fileArrayMultiParts.multipartOptions.filename.optional, true);
+ ok(fileArrayMultiParts.multipartOptions.contentType);
+ strictEqual(fileArrayMultiParts.multipartOptions.contentType.optional, true);
+ // Typespec compiler will set default content type to ["*/*"] for "HttpPart[]"
+ deepEqual(fileArrayMultiParts.multipartOptions.defaultContentTypes, ["*/*"]);
+ });
+
+ it("File with specific content-type", async function () {
+ await runner.compileWithBuiltInService(`
+ model RequiredMetaData extends File {
+ filename: string;
+ contentType: "image/png";
+ }
+ model MultiPartRequest{
+ file: HttpPart;
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+ const fileOptionalFileName = MultiPartRequest.properties.find(
+ (x) => x.name === "file"
+ ) as SdkBodyModelPropertyType;
+ ok(fileOptionalFileName);
+ ok(fileOptionalFileName.multipartOptions);
+ deepEqual(fileOptionalFileName.multipartOptions.defaultContentTypes, ["image/png"]);
+ });
+
+ it("File of multipart with @multipartBody for model", async function () {
+ await runner.compileWithBuiltInService(`
+ model RequiredMetaData extends File {
+ filename: string;
+ contentType: string;
+ }
+ model MultiPartRequest{
+ fileOptionalFileName: HttpPart;
+ fileRequiredFileName: HttpPart;
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ strictEqual(models.length, 3);
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+ ok(MultiPartRequest.usage & UsageFlags.MultipartFormData);
+ const fileOptionalFileName = MultiPartRequest.properties.find(
+ (x) => x.name === "fileOptionalFileName"
+ ) as SdkBodyModelPropertyType;
+ ok(fileOptionalFileName);
+ strictEqual(fileOptionalFileName.optional, false);
+ ok(fileOptionalFileName.multipartOptions);
+ strictEqual(fileOptionalFileName.name, "fileOptionalFileName");
+ strictEqual(fileOptionalFileName.multipartOptions.isFilePart, true);
+ ok(fileOptionalFileName.multipartOptions.filename);
+ strictEqual(fileOptionalFileName.multipartOptions.filename.optional, true);
+ ok(fileOptionalFileName.multipartOptions.contentType);
+ strictEqual(fileOptionalFileName.multipartOptions.contentType.optional, true);
+
+ const fileRequiredFileName = MultiPartRequest.properties.find(
+ (x) => x.name === "fileRequiredFileName"
+ ) as SdkBodyModelPropertyType;
+ ok(fileRequiredFileName);
+ strictEqual(fileRequiredFileName.optional, false);
+ ok(fileRequiredFileName.multipartOptions);
+ strictEqual(fileRequiredFileName.name, "fileRequiredFileName");
+ strictEqual(fileRequiredFileName.multipartOptions.isFilePart, true);
+ ok(fileRequiredFileName.multipartOptions.filename);
+ strictEqual(fileRequiredFileName.multipartOptions.filename.optional, false);
+ ok(fileRequiredFileName.multipartOptions.contentType);
+ strictEqual(fileRequiredFileName.multipartOptions.contentType.optional, false);
+ });
+
+ it("check 'multi' of multipart with @multipartBody for model", async function () {
+ await runner.compileWithBuiltInService(`
+ model Address {
+ city: string;
+ }
+ model MultiPartRequest {
+ stringsOnePart: HttpPart;
+ stringsMultiParts: HttpPart[];
+ bytesOnePart: HttpPart;
+ bytesMultiParts: HttpPart[];
+ addressesOnePart: HttpPart;
+ addressesMultiParts: HttpPart[];
+ filesOnePart: HttpPart;
+ filesMultiParts: HttpPart[];
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ strictEqual(models.length, 3);
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+ for (const p of MultiPartRequest.properties.values()) {
+ strictEqual(p.kind, "property");
+ ok(p.multipartOptions);
+ strictEqual(p.multipartOptions.isMulti, p.name.toLowerCase().includes("multi"));
+ }
+ });
+
+ it("check returned sdkType of multipart with @multipartBody for model", async function () {
+ await runner.compileWithBuiltInService(`
+ model MultiPartRequest {
+ stringsOnePart: HttpPart;
+ stringsMultiParts: HttpPart[];
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ strictEqual(models.length, 1);
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+ const stringsOnePart = MultiPartRequest.properties.find(
+ (x) => x.name === "stringsOnePart"
+ ) as SdkBodyModelPropertyType;
+ ok(stringsOnePart);
+ strictEqual(stringsOnePart.type.kind, "array");
+ ok(stringsOnePart.multipartOptions);
+ strictEqual(stringsOnePart.multipartOptions.isMulti, false);
+ const stringsMultiParts = MultiPartRequest.properties.find(
+ (x) => x.name === "stringsMultiParts"
+ ) as SdkBodyModelPropertyType;
+ ok(stringsMultiParts);
+ strictEqual(stringsMultiParts.type.kind, "string");
+ ok(stringsMultiParts.multipartOptions);
+ strictEqual(stringsMultiParts.multipartOptions.isMulti, true);
+ });
+
+ it("check content-type in multipart with @multipartBody for model", async function () {
+ await runner.compileWithBuiltInService(`
+ model MultiPartRequest {
+ stringWithoutContentType: HttpPart,
+ stringWithContentType: HttpPart<{@body body: string, @header contentType: "text/html"}>,
+ bytesWithoutContentType: HttpPart,
+ bytesWithContentType: HttpPart<{@body body: string, @header contentType: "image/png"}>
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ strictEqual(models.length, 3);
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+ const stringWithoutContentType = MultiPartRequest.properties.find(
+ (x) => x.name === "stringWithoutContentType"
+ ) as SdkBodyModelPropertyType;
+ ok(stringWithoutContentType);
+ strictEqual(stringWithoutContentType.type.kind, "string");
+ ok(stringWithoutContentType.multipartOptions);
+ strictEqual(stringWithoutContentType.multipartOptions.contentType, undefined);
+ deepEqual(stringWithoutContentType.multipartOptions.defaultContentTypes, ["text/plain"]);
+
+ const stringWithContentType = MultiPartRequest.properties.find(
+ (x) => x.name === "stringWithContentType"
+ ) as SdkBodyModelPropertyType;
+ ok(stringWithContentType);
+ strictEqual(stringWithContentType.type.kind, "model");
+ ok(stringWithContentType.multipartOptions);
+ ok(stringWithContentType.multipartOptions.contentType);
+ deepEqual(stringWithContentType.multipartOptions.defaultContentTypes, ["text/html"]);
+
+ const bytesWithoutContentType = MultiPartRequest.properties.find(
+ (x) => x.name === "bytesWithoutContentType"
+ ) as SdkBodyModelPropertyType;
+ ok(bytesWithoutContentType);
+ strictEqual(bytesWithoutContentType.type.kind, "bytes");
+ ok(bytesWithoutContentType.multipartOptions);
+ strictEqual(bytesWithoutContentType.multipartOptions.contentType, undefined);
+ deepEqual(bytesWithoutContentType.multipartOptions.defaultContentTypes, [
+ "application/octet-stream",
+ ]);
+
+ const bytesWithContentType = MultiPartRequest.properties.find(
+ (x) => x.name === "bytesWithContentType"
+ ) as SdkBodyModelPropertyType;
+ ok(bytesWithContentType);
+ strictEqual(bytesWithContentType.type.kind, "model");
+ ok(bytesWithContentType.multipartOptions);
+ ok(bytesWithContentType.multipartOptions.contentType);
+ deepEqual(bytesWithContentType.multipartOptions.defaultContentTypes, ["image/png"]);
+ });
+
+ it("check isFilePart in multipart with @multipartBody for model", async function () {
+ await runner.compileWithBuiltInService(`
+ model MultiPartRequest {
+ bytesRaw: HttpPart,
+ bytesArrayRaw: HttpPart[],
+ fileRaw: HttpPart,
+ fileArrayRaw: HttpPart[],
+ bytesWithBody: HttpPart<{@body body: bytes}>,
+ bytesArrayWithBody: HttpPart<{@body body: bytes}>[],
+ fileWithBody: HttpPart<{@body body: File}>,
+ fileArrayWithBody: HttpPart<{@body body: File}>[],
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+
+ for (const p of MultiPartRequest.properties.values()) {
+ strictEqual(p.kind, "property");
+ ok(p.multipartOptions);
+ strictEqual(p.multipartOptions.isFilePart, true);
+ strictEqual(p.multipartOptions.isMulti, p.name.toLowerCase().includes("array"));
+ }
+ });
+
+ it("check serialized name with @multipartBody for model", async function () {
+ await runner.compileWithBuiltInService(`
+ model MultiPartRequest {
+ name: HttpPart,
+ }
+ @post
+ op upload(@header contentType: "multipart/form-data", @multipartBody body: MultiPartRequest): void;
+ `);
+ const models = runner.context.sdkPackage.models;
+ const MultiPartRequest = models.find((x) => x.name === "MultiPartRequest");
+ ok(MultiPartRequest);
+ const nameProperty = MultiPartRequest.properties.find((x) => x.name === "name");
+ ok(nameProperty);
+ strictEqual(nameProperty.name, "name");
+ strictEqual((nameProperty as SdkBodyModelPropertyType).serializedName, "serializedName");
+ });
});
diff --git a/packages/typespec-client-generator-core/test/types/usage-flags.test.ts b/packages/typespec-client-generator-core/test/types/usage-flags.test.ts
new file mode 100644
index 0000000000..443cf5a85f
--- /dev/null
+++ b/packages/typespec-client-generator-core/test/types/usage-flags.test.ts
@@ -0,0 +1,16 @@
+import { describe, expect, it } from "vitest";
+import { UsageFlags } from "../../src/interfaces.js";
+
+describe("typespec-client-generator-core: usage flags", () => {
+ it("all possible values in UsageFlags should be orthogonal", async () => {
+ const values = Object.values(UsageFlags).filter(
+ (value) => typeof value === "number"
+ ) as number[];
+
+ for (let i = 0; i < values.length; i++) {
+ for (let j = i + 1; j < values.length; j++) {
+ expect(values[i] & values[j]).toBe(0);
+ }
+ }
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bc908f43ff..f76980a5d9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2151,6 +2151,9 @@ importers:
packages/typespec-client-generator-core:
dependencies:
+ '@typespec/versioning':
+ specifier: workspace:~
+ version: link:../../core/packages/versioning
change-case:
specifier: ~5.4.4
version: 5.4.4
@@ -2176,6 +2179,9 @@ importers:
'@typespec/library-linter':
specifier: workspace:~
version: link:../../core/packages/library-linter
+ '@typespec/openapi':
+ specifier: workspace:~
+ version: link:../../core/packages/openapi
'@typespec/prettier-plugin-typespec':
specifier: workspace:~
version: link:../../core/packages/prettier-plugin-typespec
@@ -2185,9 +2191,6 @@ importers:
'@typespec/tspd':
specifier: workspace:~
version: link:../../core/packages/tspd
- '@typespec/versioning':
- specifier: workspace:~
- version: link:../../core/packages/versioning
'@typespec/xml':
specifier: workspace:~
version: link:../../core/packages/xml
From 422392c3fc6275e5f3d66544a70d606d6957bc6d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 26 Jul 2024 11:27:50 -0700
Subject: [PATCH 4/7] Bump core from `10488de` to `9f5630b` (#1239)
Bumps [core](https://github.com/microsoft/typespec) from `10488de` to
`9f5630b`.
Commits
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)
---------
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Timothee Guerin
---
core | 2 +-
.../test/primitive-types.test.ts | 289 ------------------
pnpm-lock.yaml | 10 +-
3 files changed, 6 insertions(+), 295 deletions(-)
delete mode 100644 packages/typespec-autorest-canonical/test/primitive-types.test.ts
diff --git a/core b/core
index 10488deafe..9f5630b430 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 10488deafe2da16317b5306b4fcceb7dc8b70903
+Subproject commit 9f5630b4303340ee40283a897094cad13487a239
diff --git a/packages/typespec-autorest-canonical/test/primitive-types.test.ts b/packages/typespec-autorest-canonical/test/primitive-types.test.ts
deleted file mode 100644
index 8eac9d95f4..0000000000
--- a/packages/typespec-autorest-canonical/test/primitive-types.test.ts
+++ /dev/null
@@ -1,289 +0,0 @@
-import { OpenAPI2Parameter, OpenAPI2Schema } from "@azure-tools/typespec-autorest";
-import { expectDiagnostics } from "@typespec/compiler/testing";
-import { deepStrictEqual, ok } from "assert";
-import { describe, it } from "vitest";
-import { diagnoseOpenApiFor, oapiForModel, openApiFor } from "./test-host.js";
-
-describe("handle typespec intrinsic types", () => {
- const cases = [
- ["unknown", {}],
- ["int8", { type: "integer", format: "int8" }],
- ["int16", { type: "integer", format: "int16" }],
- ["int32", { type: "integer", format: "int32" }],
- ["int64", { type: "integer", format: "int64" }],
- ["safeint", { type: "integer", format: "int64" }],
- ["uint8", { type: "integer", format: "uint8" }],
- ["uint16", { type: "integer", format: "uint16" }],
- ["uint32", { type: "integer", format: "uint32" }],
- ["uint64", { type: "integer", format: "uint64" }],
- ["float32", { type: "number", format: "float" }],
- ["float64", { type: "number", format: "double" }],
- ["string", { type: "string" }],
- ["boolean", { type: "boolean" }],
- ["plainDate", { type: "string", format: "date" }],
- ["utcDateTime", { type: "string", format: "date-time" }],
- ["offsetDateTime", { type: "string", format: "date-time" }],
- ["plainTime", { type: "string", format: "time" }],
- ["duration", { type: "string", format: "duration" }],
- ["bytes", { type: "string", format: "byte" }],
- ["decimal", { type: "number", format: "decimal" }],
- ["decimal128", { type: "number", format: "decimal" }],
- ];
-
- for (const test of cases) {
- it("knows schema for " + test[0], async () => {
- const res = await oapiForModel(
- "Pet",
- `
- model Pet { name: ${test[0]} };
- `
- );
-
- const schema = res.defs.Pet.properties.name;
- deepStrictEqual(schema, test[1]);
- });
- }
-});
-
-describe("handle nonspecific intrinsic types", () => {
- const cases = [
- [
- "numeric",
- "Scalar type 'numeric' is not specific enough. The more specific type 'int64' has been chosen.",
- ],
- [
- "integer",
- "Scalar type 'integer' is not specific enough. The more specific type 'int64' has been chosen.",
- ],
- [
- "float",
- "Scalar type 'float' is not specific enough. The more specific type 'float64' has been chosen.",
- ],
- ];
-
- for (const test of cases) {
- it("reports nonspecific scalar for " + test[0], async () => {
- const res = await diagnoseOpenApiFor(
- `
- @service({title: "Testing model"})
- @route("/")
- namespace root {
- #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "This is a test."
- op read(): void;
-
- model Pet { name: ${test[0]} };
- }
- `
- );
-
- expectDiagnostics(res, {
- code: "@azure-tools/typespec-autorest/nonspecific-scalar",
- message: test[1],
- });
- });
- }
-});
-
-it("defines models extended from primitives", async () => {
- const res = await oapiForModel(
- "Pet",
- `
- scalar shortString extends string;
- model Pet { name: shortString };
- `
- );
-
- ok(res.isRef);
- ok(res.defs.shortString, "expected definition named shortString");
- ok(res.defs.Pet, "expected definition named Pet");
- deepStrictEqual(res.defs.shortString, {
- type: "string",
- });
-});
-
-it("apply description on extended primitive (string)", async () => {
- const res = await oapiForModel(
- "shortString",
- `
- @doc("My custom description")
- scalar shortString extends string;
- `
- );
-
- ok(res.isRef);
- deepStrictEqual(res.defs.shortString, {
- type: "string",
- description: "My custom description",
- });
-});
-
-it("apply description on extended primitive (int32)", async () => {
- const res = await oapiForModel(
- "specialInt",
- `
- @doc("My custom description")
- scalar specialInt extends int32;
- `
- );
-
- ok(res.isRef);
- deepStrictEqual(res.defs.specialInt, {
- type: "integer",
- format: "int32",
- description: "My custom description",
- });
-});
-
-it("apply description on extended custom scalars", async () => {
- const res = await oapiForModel(
- "superSpecialint",
- `
- @doc("My custom description")
- scalar specialint extends int32;
- @doc("Override specialint description")
- scalar superSpecialint extends specialint;
- `
- );
-
- ok(res.isRef);
- deepStrictEqual(res.defs.superSpecialint, {
- type: "integer",
- format: "int32",
- description: "Override specialint description",
- });
-});
-
-it("defines scalar extended from primitives with attrs", async () => {
- const res = await oapiForModel(
- "Pet",
- `
- @maxLength(10) @minLength(10)
- scalar shortString extends string;
- model Pet { name: shortString };
- `
- );
-
- ok(res.isRef);
- ok(res.defs.shortString, "expected definition named shortString");
- ok(res.defs.Pet, "expected definition named Pet");
- deepStrictEqual(res.defs.shortString, {
- type: "string",
- minLength: 10,
- maxLength: 10,
- });
-});
-
-it("defines scalar extended from primitives with new attrs", async () => {
- const res = await oapiForModel(
- "Pet",
- `
- @maxLength(10)
- scalar shortString extends string;
- @minLength(1)
- scalar shortButNotEmptyString extends shortString;
- model Pet { name: shortButNotEmptyString, breed: shortString };
- `
- );
- ok(res.isRef);
- ok(res.defs.shortString, "expected definition named shortString");
- ok(res.defs.shortButNotEmptyString, "expected definition named shortButNotEmptyString");
- ok(res.defs.Pet, "expected definition named Pet");
-
- deepStrictEqual(res.defs.shortString, {
- type: "string",
- maxLength: 10,
- });
- deepStrictEqual(res.defs.shortButNotEmptyString, {
- type: "string",
- minLength: 1,
- maxLength: 10,
- });
-});
-
-it("includes extensions passed on the scalar", async () => {
- const res = await oapiForModel(
- "Pet",
- `
- @extension("x-custom", "my-value")
- scalar Pet extends string;
- `
- );
-
- ok(res.defs.Pet, "expected definition named Pet");
- deepStrictEqual(res.defs.Pet, {
- type: "string",
- "x-custom": "my-value",
- });
-});
-
-describe("using @encode decorator", () => {
- async function testEncode(
- scalar: string,
- expectedOpenApi: OpenAPI2Schema,
- encoding?: string,
- encodeAs?: string
- ) {
- const encodeAsParam = encodeAs ? `, ${encodeAs}` : "";
- const encodeDecorator = encoding ? `@encode("${encoding}"${encodeAsParam})` : "";
- const res1 = await oapiForModel("s", `${encodeDecorator} scalar s extends ${scalar};`);
- deepStrictEqual(res1.defs.s, expectedOpenApi);
- const res2 = await oapiForModel("Test", `model Test {${encodeDecorator} prop: ${scalar}};`);
- deepStrictEqual(res2.defs.Test.properties.prop, expectedOpenApi);
- }
-
- describe("utcDateTime", () => {
- it("set format to 'date-time' by default", () =>
- testEncode("utcDateTime", { type: "string", format: "date-time" }));
- it("set format to 'date-time-rfc7231' when encoding is rfc7231", () =>
- testEncode("utcDateTime", { type: "string", format: "date-time-rfc7231" }, "rfc7231"));
- it("set format to 'date-time-rfc7231' for a header when encoding is rfc7231", async () => {
- const oapi = await openApiFor(`
- model Test {@header @encode("rfc7231") param: utcDateTime};
-
- #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "This is a test."
- op read(...Test): void;
- `);
- const expected: OpenAPI2Parameter = {
- name: "param",
- in: "header",
- required: true,
- type: "string",
- format: "date-time-rfc7231",
- "x-ms-parameter-location": "method",
- };
- deepStrictEqual(oapi.parameters.Test, expected);
- });
- it("set format to 'http-date' when encoding is http-date", () =>
- testEncode("utcDateTime", { type: "string", format: "http-date" }, "http-date"));
- it("set type to integer and format to 'unixtime' when encoding is unixTimestamp (unixTimestamp info is lost)", async () => {
- const expected: OpenAPI2Schema = { type: "integer", format: "unixtime" };
- await testEncode("utcDateTime", expected, "unixTimestamp", "int32");
- await testEncode("utcDateTime", expected, "unixTimestamp", "int64");
- await testEncode("utcDateTime", expected, "unixTimestamp", "int8");
- await testEncode("utcDateTime", expected, "unixTimestamp", "uint8");
- });
- });
-
- describe("offsetDateTime", () => {
- it("set format to 'date-time' by default", () =>
- testEncode("offsetDateTime", { type: "string", format: "date-time" }));
- it("set format to 'date-time-rfc7231' when encoding is rfc7231", () =>
- testEncode("offsetDateTime", { type: "string", format: "date-time-rfc7231" }, "rfc7231"));
- it("set format to 'http-date' when encoding is http-date", () =>
- testEncode("offsetDateTime", { type: "string", format: "http-date" }, "http-date"));
- });
-
- describe("duration", () => {
- it("set format to 'duration' by default", () =>
- testEncode("duration", { type: "string", format: "duration" }));
- it("set integer with int32 format setting duration as seconds", () =>
- testEncode("duration", { type: "integer", format: "int32" }, "seconds", "int32"));
- });
-
- describe("bytes", () => {
- it("set format to 'base64' by default", () =>
- testEncode("bytes", { type: "string", format: "byte" }));
- it("set format to base64url when encoding bytes as base64url", () =>
- testEncode("bytes", { type: "string", format: "base64url" }, "base64url"));
- });
-});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f76980a5d9..d69297e51f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -205,8 +205,8 @@ importers:
specifier: ~4.4.0
version: 4.4.0
'@azure/storage-blob':
- specifier: ~12.23.0
- version: 12.23.0
+ specifier: ~12.24.0
+ version: 12.24.0
'@pnpm/find-workspace-packages':
specifier: ^6.0.9
version: 6.0.9(@pnpm/logger@5.0.0)
@@ -2515,8 +2515,8 @@ packages:
resolution: {integrity: sha512-8ECtug4RL+zsgh20VL8KYHjrRO3MJOeAKEPRXT2lwtiu5U3BdyIdBb50+QZthEkIi60K6pc/pdOx/k5Jp4sLng==}
engines: {node: '>=16'}
- '@azure/storage-blob@12.23.0':
- resolution: {integrity: sha512-c1KJ5R5hqR/HtvmFtTn/Y1BNMq45NUBp0LZH7yF8WFMET+wmESgEr0FVTu/Z5NonmfUjbgJZG5Nh8xHc5RdWGQ==}
+ '@azure/storage-blob@12.24.0':
+ resolution: {integrity: sha512-l8cmWM4C7RoNCBOImoFMxhTXe1Lr+8uQ/IgnhRNMpfoA9bAFWoLG4XrWm6O5rKXortreVQuD+fc1hbzWklOZbw==}
engines: {node: '>=18.0.0'}
'@babel/code-frame@7.12.11':
@@ -12634,7 +12634,7 @@ snapshots:
jsonwebtoken: 9.0.2
uuid: 8.3.2
- '@azure/storage-blob@12.23.0':
+ '@azure/storage-blob@12.24.0':
dependencies:
'@azure/abort-controller': 1.1.0
'@azure/core-auth': 1.7.2
From 9c8af1719de6b33558abd73d25ed201e17f163aa Mon Sep 17 00:00:00 2001
From: Timothee Guerin
Date: Fri, 26 Jul 2024 11:53:41 -0700
Subject: [PATCH 5/7] Improve arm id docs (#1245)
Docs was still using the old type name(from arm library) as well as
actually be just wrong(backslash escape)
---
.../improve-armid-doc-2024-6-26-10-21-28.md | 6 ++++
cspell.yaml | 9 +++---
.../azure-core/reference/data-types.md | 30 +++++++++++++++----
packages/typespec-azure-core/lib/models.tsp | 15 +++++++---
4 files changed, 47 insertions(+), 13 deletions(-)
create mode 100644 .chronus/changes/improve-armid-doc-2024-6-26-10-21-28.md
diff --git a/.chronus/changes/improve-armid-doc-2024-6-26-10-21-28.md b/.chronus/changes/improve-armid-doc-2024-6-26-10-21-28.md
new file mode 100644
index 0000000000..e33df47351
--- /dev/null
+++ b/.chronus/changes/improve-armid-doc-2024-6-26-10-21-28.md
@@ -0,0 +1,6 @@
+---
+changeKind: internal
+packages:
+ - "@azure-tools/typespec-azure-core"
+---
+
diff --git a/cspell.yaml b/cspell.yaml
index f9c8299c50..b00c53d92c 100644
--- a/cspell.yaml
+++ b/cspell.yaml
@@ -27,7 +27,6 @@ enableFiletypes:
- typespec
words:
- allof
- - mobo
- apim
- apos
- armId
@@ -35,6 +34,7 @@ words:
- Bazs
- byos
- clsx
+ - contosowidgetmanager
- DMSS
- Donezo
- dynatrace
@@ -49,12 +49,14 @@ words:
- LINUXNEXTVMIMAGE
- LINUXOS
- LINUXVMIMAGE
+ - locationyaml
- logz
- LRO
- lropaging
- Lucene
- MACVMIMAGE
- mgmt
+ - mobo
- msazure
- msdata
- mylocation
@@ -64,8 +66,8 @@ words:
- oncophenotype
- PAYG
- prismjs
- - pytest
- psscriptanalyzer
+ - pytest
- qnas
- regionality
- Reranker
@@ -74,6 +76,5 @@ words:
- SERVICERP
- tcgc
- userrp
+ - vnet
- WINDOWSVMIMAGE
- - contosowidgetmanager
- - locationyaml
diff --git a/docs/libraries/azure-core/reference/data-types.md b/docs/libraries/azure-core/reference/data-types.md
index df2ff96993..12d9ca1719 100644
--- a/docs/libraries/azure-core/reference/data-types.md
+++ b/docs/libraries/azure-core/reference/data-types.md
@@ -510,15 +510,35 @@ union Azure.Core.RepeatabilityResult
A type definition that refers the id to an Azure Resource Manager resource.
-Sample usage:
-otherArmId: ResourceIdentifier;
-networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]>
-vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]>
-
```typespec
scalar Azure.Core.armResourceIdentifier
```
+#### Examples
+
+```tsp
+model MyModel {
+ otherArmId: armResourceIdentifier;
+ networkId: armResourceIdentifier<[
+ {
+ type: "Microsoft.Network/vnet";
+ }
+ ]>;
+ vmIds: armResourceIdentifier<[
+ {
+ type: "Microsoft.Compute/vm";
+ scopes: ["*"];
+ }
+ ]>;
+ scoped: armResourceIdentifier<[
+ {
+ type: "Microsoft.Compute/vm";
+ scopes: ["tenant", "resourceGroup"];
+ }
+ ]>;
+}
+```
+
### `azureLocation` {#Azure.Core.azureLocation}
Represents an Azure geography region where supported resource providers live.
diff --git a/packages/typespec-azure-core/lib/models.tsp b/packages/typespec-azure-core/lib/models.tsp
index 7661e3dc07..f34f2adbc5 100644
--- a/packages/typespec-azure-core/lib/models.tsp
+++ b/packages/typespec-azure-core/lib/models.tsp
@@ -358,11 +358,18 @@ scalar azureLocation extends string;
/**
* A type definition that refers the id to an Azure Resource Manager resource.
*
- * Sample usage:
- * otherArmId: ResourceIdentifier;
- * networkId: ResourceIdentifier<[{type:"\\Microsoft.Network\\vnet"}]>
- * vmIds: ResourceIdentifier<[{type:"\\Microsoft.Compute\\vm", scopes["*"]}]>
* @template AllowedResourceTypes An array of allowed resource types for the resource reference
+ *
+ * @example
+ *
+ * ```tsp
+ * model MyModel {
+ * otherArmId: armResourceIdentifier;
+ * networkId: armResourceIdentifier<[{type:"Microsoft.Network/vnet"}]>
+ * vmIds: armResourceIdentifier<[{type:"Microsoft.Compute/vm", scopes: ["*"]}]>
+ * scoped: armResourceIdentifier<[{type:"Microsoft.Compute/vm", scopes: ["tenant", "resourceGroup"]}]>
+ * }
+ * ```
*/
@doc("A type definition that refers the id to an Azure Resource Manager resource.")
@format("arm-id")
From f6af1eea273be861a143b54e0e1f06fb42b98850 Mon Sep 17 00:00:00 2001
From: Timothee Guerin
Date: Fri, 26 Jul 2024 11:55:32 -0700
Subject: [PATCH 6/7] Improve autorest breaking change doc for release 0.44
(#1243)
The breaking change entry wasn't very helpful to someone coming across
it
---
docs/release-notes/release-2024-07-16.md | 10 +++++++++-
packages/typespec-autorest/CHANGELOG.md | 11 +++++++++--
.../release-notes/release-2024-07-16.md | 10 +++++++++-
3 files changed, 27 insertions(+), 4 deletions(-)
diff --git a/docs/release-notes/release-2024-07-16.md b/docs/release-notes/release-2024-07-16.md
index f5c58ae968..afd82452a5 100644
--- a/docs/release-notes/release-2024-07-16.md
+++ b/docs/release-notes/release-2024-07-16.md
@@ -14,7 +14,15 @@ This release contains breaking changes and deprecation
### @azure-tools/typespec-autorest
-- [#1105](https://github.com/Azure/typespec-azure/pull/1105) `x-ms-client-flatten` extension on some of resource properties property is now configurable to be emitted by autorest emitter. Default is false which will skip emission of that extension.
+- [#1105](https://github.com/Azure/typespec-azure/pull/1105) `x-ms-client-flatten` extension on some of resource properties property is now configurable to be emitted by autorest emitter(`arm-resource-flattening` option). Default is false which will skip emission of that extension.
+ To revert to previous behavior update your `tspconfig.yaml` with the following
+
+ ```diff
+ options:
+ "@azure-tools/typespec-autorest":
+ # ...other options
+ + arm-resource-flattening: true
+ ```
### @azure-tools/typespec-azure-resource-manager
diff --git a/packages/typespec-autorest/CHANGELOG.md b/packages/typespec-autorest/CHANGELOG.md
index 3003ba0734..823cf32929 100644
--- a/packages/typespec-autorest/CHANGELOG.md
+++ b/packages/typespec-autorest/CHANGELOG.md
@@ -26,8 +26,15 @@
### Breaking Changes
-- [#1105](https://github.com/Azure/typespec-azure/pull/1105) `x-ms-client-flatten` extension on some of resource properties property is now configurable to be emitted by autorest emitter. Default is false which will skip emission of that extension.
-
+- [#1105](https://github.com/Azure/typespec-azure/pull/1105) `x-ms-client-flatten` extension on some of resource properties property is now configurable to be emitted by autorest emitter(`arm-resource-flattening` option). Default is false which will skip emission of that extension.
+ To revert to previous behavior update your `tspconfig.yaml` with the following
+
+ ```diff
+ options:
+ "@azure-tools/typespec-autorest":
+ # ...other options
+ + arm-resource-flattening: true
+ ```
## 0.43.0
diff --git a/packages/website/versioned_docs/version-latest/release-notes/release-2024-07-16.md b/packages/website/versioned_docs/version-latest/release-notes/release-2024-07-16.md
index f5c58ae968..afd82452a5 100644
--- a/packages/website/versioned_docs/version-latest/release-notes/release-2024-07-16.md
+++ b/packages/website/versioned_docs/version-latest/release-notes/release-2024-07-16.md
@@ -14,7 +14,15 @@ This release contains breaking changes and deprecation
### @azure-tools/typespec-autorest
-- [#1105](https://github.com/Azure/typespec-azure/pull/1105) `x-ms-client-flatten` extension on some of resource properties property is now configurable to be emitted by autorest emitter. Default is false which will skip emission of that extension.
+- [#1105](https://github.com/Azure/typespec-azure/pull/1105) `x-ms-client-flatten` extension on some of resource properties property is now configurable to be emitted by autorest emitter(`arm-resource-flattening` option). Default is false which will skip emission of that extension.
+ To revert to previous behavior update your `tspconfig.yaml` with the following
+
+ ```diff
+ options:
+ "@azure-tools/typespec-autorest":
+ # ...other options
+ + arm-resource-flattening: true
+ ```
### @azure-tools/typespec-azure-resource-manager
From 4434dc25ffa76b14b6c1a3c2301c742cfaf68a4d Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 26 Jul 2024 19:28:31 +0000
Subject: [PATCH 7/7] Bump core from `9f5630b` to `eb24510` (#1246)
Bumps [core](https://github.com/microsoft/typespec) from `9f5630b` to
`eb24510`.
Commits
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)
---------
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Timothee Guerin
---
core | 2 +-
.../azure-core/reference/interfaces.md | 28 +++++++++++++++++++
.../reference/data-types.md | 4 +++
.../reference/interfaces.md | 16 +++++++++++
.../reference/decorators.md | 20 +++++++++++++
.../typespec-client-generator-core/README.md | 10 +++++++
6 files changed, 79 insertions(+), 1 deletion(-)
diff --git a/core b/core
index 9f5630b430..eb245109c0 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 9f5630b4303340ee40283a897094cad13487a239
+Subproject commit eb245109c05e98e10fdd75c48040d33c432a6a98
diff --git a/docs/libraries/azure-core/reference/interfaces.md b/docs/libraries/azure-core/reference/interfaces.md
index f3a8f10fb0..d0bfbeb104 100644
--- a/docs/libraries/azure-core/reference/interfaces.md
+++ b/docs/libraries/azure-core/reference/interfaces.md
@@ -338,6 +338,10 @@ op Azure.Core.LongRunningResourceCollectionAction(apiVersion: string): Azure.Cor
### `LongRunningResourceCreateOrReplace` {#Azure.Core.LongRunningResourceCreateOrReplace}
+:::warning
+**Deprecated**: Use `LongRunningResourceCreateOrReplace` from a `ResourceOperations` interface instance.
+:::
+
DEPRECATED: Use `LongRunningResourceCreateOrReplace` from a `ResourceOperations` interface instance.
This can be done by instantiating your own version with the traits you want `alias Operations = Azure.Core.ResourceOperations;`.
See https://azure.github.io/typespec-azure/docs/getstarted/azure-core/step05#defining-the-operation-interface for details on how to use.
@@ -357,6 +361,10 @@ op Azure.Core.LongRunningResourceCreateOrReplace(apiVersion: string, resource: R
### `LongRunningResourceCreateOrUpdate` {#Azure.Core.LongRunningResourceCreateOrUpdate}
+:::warning
+**Deprecated**: Use `LongRunningResourceCreateOrUpdate` from a `ResourceOperations` interface instance.
+:::
+
DEPRECATED: Use `LongRunningResourceCreateOrUpdate` from a `ResourceOperations` interface instance.
This can be done by instantiating your own version with the traits you want `alias Operations = Azure.Core.ResourceOperations;`.
See https://azure.github.io/typespec-azure/docs/getstarted/azure-core/step05#defining-the-operation-interface for details on how to use.
@@ -376,6 +384,10 @@ op Azure.Core.LongRunningResourceCreateOrUpdate(apiVersion: string, contentType:
### `LongRunningResourceCreateWithServiceProvidedName` {#Azure.Core.LongRunningResourceCreateWithServiceProvidedName}
+:::warning
+**Deprecated**: Use `LongRunningResourceCreateWithServiceProvidedName` from a `ResourceOperations` interface instance.
+:::
+
DEPRECATED: Use `LongRunningResourceCreateWithServiceProvidedName` from a `ResourceOperations` interface instance.
This can be done by instantiating your own version with the traits you want `alias Operations = Azure.Core.ResourceOperations;`.
See https://azure.github.io/typespec-azure/docs/getstarted/azure-core/step05#defining-the-operation-interface for details on how to use.
@@ -476,6 +488,10 @@ op Azure.Core.ResourceCollectionAction(apiVersion: string): {} | Azure.Core.Foun
### `ResourceCreateOrReplace` {#Azure.Core.ResourceCreateOrReplace}
+:::warning
+**Deprecated**: Use `ResourceCreateOrReplace` from a `ResourceOperations` interface instance.
+:::
+
DEPRECATED: Use `ResourceCreateOrReplace` from a `ResourceOperations` interface instance.
This can be done by instantiating your own version with the traits you want `alias Operations = Azure.Core.ResourceOperations;`.
See https://azure.github.io/typespec-azure/docs/getstarted/azure-core/step05#defining-the-operation-interface for details on how to use.
@@ -495,6 +511,10 @@ op Azure.Core.ResourceCreateOrReplace(apiVersion: string, resource: Resource): {
### `ResourceCreateOrUpdate` {#Azure.Core.ResourceCreateOrUpdate}
+:::warning
+**Deprecated**: Use `LongRunningResourceCreateOrReplace` from a `ResourceOperations` interface instance.
+:::
+
DEPRECATED: Use `ResourceCreateOrUpdate` from a `ResourceOperations` interface instance.
This can be done by instantiating your own version with the traits you want `alias Operations = Azure.Core.ResourceOperations;`.
See https://azure.github.io/typespec-azure/docs/getstarted/azure-core/step05#defining-the-operation-interface for details on how to use.
@@ -514,6 +534,10 @@ op Azure.Core.ResourceCreateOrUpdate(apiVersion: string, contentType: "applicati
### `ResourceCreateWithServiceProvidedName` {#Azure.Core.ResourceCreateWithServiceProvidedName}
+:::warning
+**Deprecated**: Use `ResourceCreateWithServiceProvidedName` from a `ResourceOperations` interface instance.
+:::
+
DEPRECATED: Use `ResourceCreateWithServiceProvidedName` from a `ResourceOperations` interface instance.
This can be done by instantiating your own version with the traits you want `alias Operations = Azure.Core.ResourceOperations;`.
See https://azure.github.io/typespec-azure/docs/getstarted/azure-core/step05#defining-the-operation-interface for details on how to use.
@@ -590,6 +614,10 @@ op Azure.Core.ResourceRead(apiVersion: string): {} | Azure.Core.Foundations.Erro
### `ResourceUpdate` {#Azure.Core.ResourceUpdate}
+:::warning
+**Deprecated**: Use `ResourceUpdate` from a `ResourceOperations` interface instance.
+:::
+
DEPRECATED: Use `ResourceUpdate` from a `ResourceOperations` interface instance.
This can be done by instantiating your own version with the traits you want `alias Operations = Azure.Core.ResourceOperations;`.
See https://azure.github.io/typespec-azure/docs/getstarted/azure-core/step05#defining-the-operation-interface for details on how to use.
diff --git a/docs/libraries/azure-resource-manager/reference/data-types.md b/docs/libraries/azure-resource-manager/reference/data-types.md
index d67893a7b1..769f6b7d37 100644
--- a/docs/libraries/azure-resource-manager/reference/data-types.md
+++ b/docs/libraries/azure-resource-manager/reference/data-types.md
@@ -1694,6 +1694,10 @@ model Azure.ResourceManager.CommonTypes.TrackedResource
### `UserAssignedIdentities` {#Azure.ResourceManager.CommonTypes.UserAssignedIdentities}
+:::warning
+**Deprecated**: Do not use this model. Instead, use Record directly. Using this model will result in a different client SDK when generated from TypeSpec compared to the Swagger.
+:::
+
The set of user assigned identities associated with the resource. The userAssignedIdentities dictionary keys will be ARM resource ids in the form: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}. The dictionary values can be empty objects ({}) in requests.
```typespec
diff --git a/docs/libraries/azure-resource-manager/reference/interfaces.md b/docs/libraries/azure-resource-manager/reference/interfaces.md
index 9b424d61f0..0fc27525a0 100644
--- a/docs/libraries/azure-resource-manager/reference/interfaces.md
+++ b/docs/libraries/azure-resource-manager/reference/interfaces.md
@@ -247,6 +247,10 @@ op Azure.ResourceManager.ResourceCreateSync.createOrUpdate(provider: "Microsoft.
### `ResourceDeleteAsync` {#Azure.ResourceManager.ResourceDeleteAsync}
+:::warning
+**Deprecated**: This should be deprecated in a future release
+:::
+
```typespec
interface Azure.ResourceManager.ResourceDeleteAsync
```
@@ -390,6 +394,10 @@ op Azure.ResourceManager.ResourceListBySubscription.listBySubscription(apiVersio
### `ResourceOperations` {#Azure.ResourceManager.ResourceOperations}
+:::warning
+**Deprecated**: Use Azure.ResourceManager.TrackedResourceOperations instead
+:::
+
```typespec
interface Azure.ResourceManager.ResourceOperations
```
@@ -845,6 +853,10 @@ op Azure.ResourceManager.ArmResourceCreateOrUpdateAsync(provider: "Microsoft.Thi
### `ArmResourceCreateOrUpdateSync` {#Azure.ResourceManager.ArmResourceCreateOrUpdateSync}
+:::warning
+**Deprecated**: Please use ArmResourceCreateOrReplaceSync instead
+:::
+
DEPRECATED: Please use ArmResourceCreateOrReplaceSync instead
```typespec
@@ -863,6 +875,10 @@ op Azure.ResourceManager.ArmResourceCreateOrUpdateSync(provider: "Microsoft.This
### `ArmResourceDeleteAsync` {#Azure.ResourceManager.ArmResourceDeleteAsync}
+:::warning
+**Deprecated**: Use 'ArmResourceDeleteWithoutOkAsync' instead
+:::
+
```typespec
op Azure.ResourceManager.ArmResourceDeleteAsync(provider: "Microsoft.ThisWillBeReplaced"): Response | Error
```
diff --git a/docs/libraries/typespec-client-generator-core/reference/decorators.md b/docs/libraries/typespec-client-generator-core/reference/decorators.md
index 36bd651b37..d1cd81aaf6 100644
--- a/docs/libraries/typespec-client-generator-core/reference/decorators.md
+++ b/docs/libraries/typespec-client-generator-core/reference/decorators.md
@@ -201,6 +201,10 @@ interface MyInterface {}
### `@clientFormat` {#@Azure.ClientGenerator.Core.clientFormat}
+:::warning
+**Deprecated**: @clientFormat decorator is deprecated. Use `@encode` decorator in `@typespec/compiler` instead.
+:::
+
DEPRECATED: Use `@encode` decorator in `@typespec/compiler` instead.
Can be used to explain the client type that the current TYPESPEC
@@ -291,6 +295,10 @@ op test: void;
### `@exclude` {#@Azure.ClientGenerator.Core.exclude}
+:::warning
+**Deprecated**: @exclude decorator is deprecated. Use `@usage` and `@access` decorator instead.
+:::
+
DEPRECATED: Use `@usage` and `@access` decorator instead.
Whether to exclude a model from generation for specific languages. By default we generate
@@ -321,6 +329,10 @@ model ModelToExclude {
### `@flattenProperty` {#@Azure.ClientGenerator.Core.flattenProperty}
+:::warning
+**Deprecated**: @flattenProperty decorator is not recommended to use.
+:::
+
Set whether a model property should be flattened or not.
```typespec
@@ -349,6 +361,10 @@ model Bar {}
### `@include` {#@Azure.ClientGenerator.Core.include}
+:::warning
+**Deprecated**: @include decorator is deprecated. Use `@usage` and `@access` decorator instead.
+:::
+
DEPRECATED: Use `@usage` and `@access` decorator instead.
Whether to include a model in generation for specific languages. By default we generate
@@ -379,6 +395,10 @@ model ModelToInclude {
### `@internal` {#@Azure.ClientGenerator.Core.internal}
+:::warning
+**Deprecated**: @internal decorator is deprecated. Use `@access` decorator instead.
+:::
+
DEPRECATED: Use `@access` decorator instead.
Whether to mark an operation as internal for specific languages,
diff --git a/packages/typespec-client-generator-core/README.md b/packages/typespec-client-generator-core/README.md
index 13e4b1f39a..d29f159e2a 100644
--- a/packages/typespec-client-generator-core/README.md
+++ b/packages/typespec-client-generator-core/README.md
@@ -218,6 +218,8 @@ interface MyInterface {}
#### `@clientFormat`
+_Deprecated: @clientFormat decorator is deprecated. Use `@encode` decorator in `@typespec/compiler` instead._
+
DEPRECATED: Use `@encode` decorator in `@typespec/compiler` instead.
Can be used to explain the client type that the current TYPESPEC
@@ -308,6 +310,8 @@ op test: void;
#### `@exclude`
+_Deprecated: @exclude decorator is deprecated. Use `@usage` and `@access` decorator instead._
+
DEPRECATED: Use `@usage` and `@access` decorator instead.
Whether to exclude a model from generation for specific languages. By default we generate
@@ -338,6 +342,8 @@ model ModelToExclude {
#### `@flattenProperty`
+_Deprecated: @flattenProperty decorator is not recommended to use._
+
Set whether a model property should be flattened or not.
```typespec
@@ -366,6 +372,8 @@ model Bar {}
#### `@include`
+_Deprecated: @include decorator is deprecated. Use `@usage` and `@access` decorator instead._
+
DEPRECATED: Use `@usage` and `@access` decorator instead.
Whether to include a model in generation for specific languages. By default we generate
@@ -396,6 +404,8 @@ model ModelToInclude {
#### `@internal`
+_Deprecated: @internal decorator is deprecated. Use `@access` decorator instead._
+
DEPRECATED: Use `@access` decorator instead.
Whether to mark an operation as internal for specific languages,