Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4a3f8cd
fix: do not unintentially walk non-root enum types (#10935)
jorgerangel-msft Jun 9, 2026
5f94e61
[python] Add mock_api coverage for single-discriminator no-subtypes a…
Copilot Jun 10, 2026
99197b7
[python] Add test for client-doc spector scenario (Azure typespec-azu…
Copilot Jun 10, 2026
31cb717
[python] Add mock API test (#10944)
msyyc Jun 10, 2026
132f445
[python] release new version for latest tsp version (#10948)
msyyc Jun 10, 2026
9b4f2f7
fix(http-client-csharp): pin cop version and migrate to provider() AP…
JoshLove-msft Jun 10, 2026
2623059
Add docs on authoring and using generator plugins for the C# emitter …
Copilot Jun 10, 2026
3cb9222
Bump TCGC to 0.69.0 for http-client-csharp (#10952)
Copilot Jun 10, 2026
ccf14f6
fix(http-client-csharp): strip [Experimental] attribute when internal…
JoshLove-msft Jun 10, 2026
cd22821
fix(http-client-java): align query tests with ConstantClient builder …
Copilot Jun 11, 2026
b8e49ed
Disable Java dollar-sign query tests pending http-specs release (#10961)
Copilot Jun 11, 2026
5cbe6d9
fix(http-client-csharp): bump pinned cop release to v2026.6.10.1 (#10…
JoshLove-msft Jun 11, 2026
9e923c0
Skip generating convenience methods when the protocol method is suppr…
Copilot Jun 11, 2026
3c918ef
Revert "fix(http-client-csharp): strip [Experimental] attribute when …
JoshLove-msft Jun 11, 2026
7d060ec
Skip convenience method generation for multipart/mixed requests (#10967)
Copilot Jun 11, 2026
df05365
Bump MessagePack from 2.5.192 to 2.5.301 (#10975)
dependabot[bot] Jun 12, 2026
c018877
[python] Fix Sphinx docstring rendering of annotations after code blo…
l0lawrence Jun 12, 2026
5b8f8dc
feat(http-client-csharp): add disable-roslyn-reduce emitter option (#…
JoshLove-msft Jun 12, 2026
3cf2638
[http-client-csharp] Expose extension point for custom-code attribute…
Copilot Jun 12, 2026
3a86d4e
[python] Refactor Python emitter node imports to be browser bundleabl…
JennyPng Jun 12, 2026
8494a66
fix(http-specs): align route to "/dollar-sign" in main and mockapi (#…
Copilot Jun 12, 2026
c8b8194
Support datetime.timedelta for seconds/milliseconds duration encoding…
Copilot Jun 15, 2026
c6e5c3d
[python] Add mock API test for Parameters_Query_SpecialChar_dollarSig…
Copilot Jun 15, 2026
d790c74
fix: regenpreview script for mgmt libraries (#10990)
jorgerangel-msft Jun 15, 2026
b42f330
fix(http-client-python): construct paging JSON body outside prepare_r…
l0lawrence Jun 16, 2026
68dd7b4
[python] Use specific exception types in status code range mock_api t…
msyyc Jun 16, 2026
2bde8ea
Fix OpenAPI import to emit `@cookie` for cookie parameters (#10995)
Copilot Jun 16, 2026
ca236c8
Add Spector cases for serializing lossy duration encodings as integer…
Copilot Jun 16, 2026
57cf0b7
fix(http-client-python): raise azure-core error types for customized …
l0lawrence Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@typespec/http-client-python"
---

Support `datetime.timedelta` for `duration` types encoded as `seconds` or `milliseconds`. SDK users can now pass a `datetime.timedelta` (instead of a raw `int`/`float`) and responses are deserialized back into `datetime.timedelta`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-python"
---

Add sync and async mock_api tests for the `service/multiple-services` Spector scenario and enable its regeneration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-specs"
---

Add encode/duration lossy Spector scenarios verifying a duration whose value carries more precision than the target integer encoding (fractional seconds and sub-millisecond milliseconds) is serialized as an integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/openapi3"
---

Fix OpenAPI import to emit `@cookie` decorators for cookie parameters, including nullable and type-null schema variants.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-client-python"
---

Fix `UnboundLocalError` for paging operations with a flattened JSON model body. The request body is now constructed once outside the `prepare_request` callback (and before the body is serialized into the request content) instead of inside the closure, where assigning `body` made it an unbound local on every page fetch.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-client-python"
---

Fix Sphinx docstring rendering when a `Required.` (or other) annotation followed a code block. The annotation is now inserted into the prose before the code block instead of being appended after it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-python"
---

Use specific exception types in status code range mock_api tests for sync and async
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-java"
---

Add e2e tests for parameters/body-root, parameters/query special char, type/model/inheritance/single-discriminator no-subtypes, azure/resourcemanager/common-properties arm-resource-identifiers, and azure/resourcemanager/management-group scenarios
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: dependencies
packages:
- "@typespec/http-client-java"
---

Update Node.js dependencies to latest (compiler 1.13.0, TCGC 0.69.0, azure-core 0.69.0) and bump version to 0.9.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-specs"
---

Fix the dollar-sign query scenario route to match the mock API.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-python"
---

Add sync and async mock_api tests for the management group scoped resources and ARM resource identifier Spector scenarios from azure-http-specs.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-python"
---

Add mock API test for the `Parameters_Query_SpecialChar_dollarSign` Spector scenario.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-client-python"
---

Raise the dedicated azure-core error type for standard status codes (401, 404, 409, 304) whenever a customized error model covers them (ranged or default error responses), instead of falling back to a generic `HttpResponseError` or raising without the error body. The error body is now deserialized into the customized error model first and attached to the raised error. For example, a `401` covered by a custom error model now raises `ClientAuthenticationError` with the customized error body shape.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/http-client-python"
---

Refactor Node usage in Python emitter to make it browser bundleable
1 change: 1 addition & 0 deletions .github/instructions/http-client-csharp.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ applyTo: "packages/http-client-csharp/**/*"
- Regenerate all generated libraries by running `eng/scripts/Generate.ps1`.
- Ensure all unit tests are passing.
- Do not comment out or delete any existing tests.
- Run the Cop static-analysis checks locally with `npm run cop` (which invokes `eng/scripts/Invoke-Cop.ps1`). This downloads the pinned Agent Cop release and runs the rules under `cop-checks/` against the generator, failing on any violation. Ensure it reports `cop checks passed.` before committing C# changes.
4 changes: 4 additions & 0 deletions cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ words:
- reinjected
- repr
- respecify
- responseasbool
- rjust
- rollup
- rpaas
Expand All @@ -250,6 +251,8 @@ words:
- sdkcore
- segmentof
- serde
- servicea
- serviceb
- setuppy
- sfixed
- shiki
Expand Down Expand Up @@ -307,6 +310,7 @@ words:
- venvtools
- VITE
- vitest
- vnet
- VNEXT
- vsix
- vsmarketplace
Expand Down
2 changes: 1 addition & 1 deletion packages/http-client-csharp/cop-checks/main.cop
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import code
import csharp

let cb = code.codebase('csharp')
let cb : Codebase = provider('csharp')

# Rule: braces.cop
command CHECK-BRACES = foreach cb.Lines:missingBraces => '{error:@red} {item.File.Path}:{item.Number}: missing braces -> {item.Text}'
Expand Down
19 changes: 18 additions & 1 deletion packages/http-client-csharp/docs/emitter.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ Set the log level for which to collect traces. The default value is `info`.

Set to `true` to disable XML documentation generation. The default value is `false`.

### `disable-roslyn-reduce`

**Type:** `boolean`

Set to `true` to skip the Roslyn reduce (simplification) post-processing step. This speeds up generation and is useful when iterating quickly. The default value is `false`.

### `generator-name`

**Type:** `string`
Expand All @@ -112,7 +118,18 @@ Allows emitter authors to specify the path to a custom emitter package, allowing

**Type:** `array`

Paths to generator plugin assemblies (DLLs) or directories containing plugin assemblies. Each plugin must contain a class that extends GeneratorPlugin.
Paths to generator plugin assemblies (DLLs) or directories containing plugin assemblies. Each plugin must contain a class that extends `GeneratorPlugin`. Paths may be absolute or relative to the resolved `emitter-output-dir`. For example, to load plugins that live in a `codegen` folder under the output directory:

```yaml
options:
"@typespec/http-client-csharp":
plugins:
- "codegen/MyPlugin.dll" # file relative to emitter-output-dir
- "codegen" # directory containing plugin assemblies
- "/abs/path/to/MyPlugin.dll" # absolute path used as-is
```

See [Generator plugins](./plugins.md) for a walkthrough on authoring and using plugins.

### `license`

Expand Down
4 changes: 4 additions & 0 deletions packages/http-client-csharp/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ npm install --save-peer @typespec/http-client-csharp

[See documentation](./emitter.md)

## Generator plugins

[See documentation](./plugins.md)

## TypeSpec.HttpClient

## TypeSpec.HttpClient.CSharp
Expand Down
101 changes: 101 additions & 0 deletions packages/http-client-csharp/docs/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: "Generator plugins"
---

## Generator plugins

Plugins are a lightweight way to customize the C# generator without authoring a full custom generator. A plugin can register additional visitors, rewriters, metadata references, or shared source directories that participate in the normal generation pipeline. This is useful when you want to tweak the generated output (for example, rename types, add attributes, or post-process models) while still using the built-in `ScmCodeModelGenerator`.

The generator discovers plugins in two ways:

1. **Automatically**, from the `dist` folder of any package listed in your project's `node_modules`.
2. **Explicitly**, from the paths supplied via the [`plugins`](./emitter.md#plugins) emitter option.

This document focuses on the `plugins` option, which lets emitter authors point the generator at one or more plugin assemblies (or directories/projects containing them).

## Authoring a plugin

A plugin is a C# class that extends `GeneratorPlugin` and overrides `Apply`. The base class is exported via [MEF](https://learn.microsoft.com/dotnet/framework/mef/) using `[InheritedExport]`, so any subclass is discovered automatically once its assembly is loaded — you do not need to add an `[Export]` attribute yourself.

```csharp
using Microsoft.TypeSpec.Generator;

public class MyPlugin : GeneratorPlugin
{
public override void Apply(CodeModelGenerator generator)
{
// Register a custom visitor that transforms the output library.
generator.AddVisitor(new MyLibraryVisitor());
}
}
```

The `CodeModelGenerator` passed to `Apply` exposes the extension points a plugin can use, including:

- `AddVisitor(LibraryVisitor visitor)` — add a visitor that traverses and modifies the output library.
- `AddRewriter(LibraryRewriter rewriter)` — add a rewriter to transform generated members.
- `AddMetadataReference(MetadataReference reference)` — make additional assemblies available to the generated code.
- `AddSharedSourceDirectory(string sharedSourceDirectory)` — include a directory of shared source files.

A visitor lets you hook into specific parts of the output. For example, to transform every model:

```csharp
using Microsoft.TypeSpec.Generator;
using Microsoft.TypeSpec.Generator.Providers;

public class MyLibraryVisitor : LibraryVisitor
{
protected override TypeProvider? VisitType(TypeProvider type)
{
// Inspect or modify the generated type here.
return base.VisitType(type);
}
}
```

### Plugin project file

Build your plugin as a class library that references the generator's published NuGet package. Referencing `Microsoft.TypeSpec.Generator.ClientModel` transitively brings in `Microsoft.TypeSpec.Generator`, which contains the `GeneratorPlugin` base class. Use the package version that matches the version of `@typespec/http-client-csharp` you are generating with. A minimal `.csproj` looks like this:

```xml
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.TypeSpec.Generator.ClientModel" Version="X.Y.Z" />
</ItemGroup>

</Project>
```

## Using the `plugins` option

Once you have a plugin, point the emitter at it using the `plugins` option in `tspconfig.yaml`. Each entry is a path that may be **absolute** or **relative to the resolved `emitter-output-dir`**. A path can point to either:

- a **file** — a pre-built plugin assembly (`*.dll`), or
- a **directory** containing pre-built plugin assemblies (`*.dll`) or a `.csproj` — when a `.csproj` is present the generator builds it (`dotnet build -c Release`) before loading the resulting assembly.

For example, to load plugins that live in a `codegen` folder under the output directory:

```yaml
emit:
- "@typespec/http-client-csharp"
options:
"@typespec/http-client-csharp":
plugins:
- "codegen/MyPlugin.dll" # file relative to emitter-output-dir
- "codegen" # directory containing plugin assemblies
- "/abs/path/to/MyPlugin.dll" # absolute path used as-is
```

Notes:

- Absolute paths are used as-is; relative paths are anchored to the resolved `emitter-output-dir`.
- When a directory contains a `.csproj`, it is built automatically; otherwise the directory is scanned for pre-built `*.dll` files.
- Plugins loaded via the `plugins` option are applied in addition to any plugins discovered through `node_modules`.

After the assemblies are loaded, every discovered `GeneratorPlugin` has its `Apply` method invoked on the selected generator before generation runs, so all of your registered visitors, rewriters, and references take effect.
2 changes: 2 additions & 0 deletions packages/http-client-csharp/emitter/src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ export function createConfiguration(
"unreferenced-types-handling": options["unreferenced-types-handling"],
"disable-xml-docs":
options["disable-xml-docs"] === false ? undefined : options["disable-xml-docs"],
"disable-roslyn-reduce":
options["disable-roslyn-reduce"] === false ? undefined : options["disable-roslyn-reduce"],
license: sdkContext.sdkPackage.licenseInfo,
};
}
6 changes: 6 additions & 0 deletions packages/http-client-csharp/emitter/src/lib/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ const diags: { [code: string]: DiagnosticDefinition<DiagnosticMessages> } = {
default: paramMessage`Convenience method is not supported for PATCH method, it will be turned off. Please set the '@convenientAPI' to false for operation ${"methodCrossLanguageDefinitionId"}.`,
},
},
"unsupported-multipart-convenience-method": {
severity: "warning",
messages: {
default: paramMessage`Convenience method is not supported for multipart content types other than 'multipart/form-data', it will be turned off. Please set the '@convenientAPI' to false for operation ${"methodCrossLanguageDefinitionId"}.`,
},
},
"unsupported-service-method": {
severity: "warning",
messages: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ export function fromSdkServiceMethodOperation(
generateConvenience = false;
}

const requestMediaTypes = getRequestMediaTypes(method.operation);
if (generateConvenience && isUnsupportedMultipart(requestMediaTypes)) {
diagnostics.add(
createDiagnostic({
code: "unsupported-multipart-convenience-method",
format: {
methodCrossLanguageDefinitionId: method.crossLanguageDefinitionId,
},
target: method.__raw ?? NoTarget,
}),
);
generateConvenience = false;
}

operation = {
name: method.name,
isExactName: method.isExactName,
Expand All @@ -217,7 +231,7 @@ export function fromSdkServiceMethodOperation(
uri: uri,
path: method.operation.path,
externalDocsUrl: getExternalDocs(sdkContext, method.operation.__raw.operation)?.url,
requestMediaTypes: getRequestMediaTypes(method.operation),
requestMediaTypes: requestMediaTypes,
bufferResponse: true,
generateProtocolMethod: shouldGenerateProtocol(sdkContext, method.operation.__raw.operation),
generateConvenienceMethod: generateConvenience,
Expand Down Expand Up @@ -751,6 +765,15 @@ function toStatusCodesArray(range: number | HttpStatusCodeRange): number[] {
return statusCodes;
}

function isUnsupportedMultipart(requestMediaTypes: string[] | undefined): boolean {
return (
requestMediaTypes?.some((mediaType) => {
const normalized = mediaType.toLowerCase();
return normalized.startsWith("multipart/") && normalized !== "multipart/form-data";
}) ?? false
);
}

function getRequestMediaTypes(op: SdkHttpOperation): string[] | undefined {
const contentTypes = op.parameters.filter(
(p) => p.kind === "header" && p.serializedName.toLocaleLowerCase() === "content-type",
Expand Down
Loading
Loading