[http-client-csharp] Fix duration integer encoding for non-Int32 wire types (#10832)
A TypeSpec `duration` encoded as integer milliseconds/seconds with a wire type other than `int32` (e.g. `integer`, `int64`, `safeint`, `uint*`) was serialized as a `double` via `TimeSpan.TotalMilliseconds` / `TotalSeconds`, producing fractional JSON output that violates the integer contract. Additionally, for wire types larger than `int32` (e.g. `int64`, `uint32`, `uint64`, `safeint`, unbounded `integer`), using `Int32`-based ser/deser would overflow/truncate large values. ```typespec @encode(DurationKnownEncoding.milliseconds, integer) audio_end_ms: duration; ``` Previously generated: ```csharp writer.WriteNumberValue(AudioEndMs.TotalMilliseconds); // double, may emit 123.45 ``` Now generates (for `int32`-sized wire types): ```csharp writer.WriteNumberValue(Convert.ToInt32(Math.Round(AudioEndMs.TotalMilliseconds))); ``` And for wire types larger than `int32` (`int64`, `uint32`, `uint64`, `safeint`, unbounded `integer`): ```csharp writer.WriteNumberValue(Convert.ToInt64(Math.Round(AudioEndMs.TotalMilliseconds))); ``` The `Math.Round` wrapper makes the rounding behavior explicit so fractional values (e.g. `1500.7ms`) are rounded to the nearest integer (`1501`) rather than relying on `Convert.ToInt32`/`ToInt64`'s implicit rounding. ### Changes - **`TypeFactory.GetSerializationFormat`**: The `DurationKnownEncoding.Seconds` / `Milliseconds` switches only matched `InputPrimitiveTypeKind.Int32` for the integer arm; every other integer kind fell through the `_` default into `Duration_*_Double`. Extended both arms to cover the full integer-kind set, splitting based on .NET range: - `Int8`, `Int16`, `Int32`, `UInt8`, `UInt16` → `Duration_Seconds` / `Duration_Milliseconds` (uses `Int32`). - `Int64`, `UInt32`, `UInt64`, `SafeInt`, unbounded `Integer` → new `Duration_Seconds_Int64` / `Duration_Milliseconds_Int64` (uses `Int64`). - `Float` / `Float32` still map to `_Float`, and `Float64` / others to `_Double`. - **New `SerializationFormat` values**: Added `Duration_Seconds_Int64` and `Duration_Milliseconds_Int64` to the input enum and to the generated `SerializationFormat` enum (`SerializationFormatDefinition`). - **`MrwSerializationTypeDefinition`**: Handles the new Int64 formats by emitting `JsonElement.GetInt64()` for deserialization and `Convert.ToInt64(Math.Round(...))` for JSON serialization. The Int32 formats now also wrap in `Math.Round` for explicit rounding. - **`TypeFormattersDefinition`**: Handles the new Int64 formats in the URI/query-string `ConvertToString` path with `Convert.ToInt64(Math.Round(...)).ToString(...)`. The Int32 formats now also wrap in `Math.Round`. - **`ConvertSnippets`**: Added `InvokeToInt64` helper. - **`MathSnippets`** (new): Added `InvokeRound` helper that emits `Math.Round(arg)`. - **Tests**: - `TypeFactoryTests.DurationIntegerWireTypeSerializationFormat` and `DurationFloatWireTypeSerializationFormat` cover every integer and float wire-type kind for both encodings, asserting Int32 vs Int64 routing. - `MrwSerializationTypeDefinitionTests` `TestTimeSpanDeserializeExpression` and `TestTimeSpanSerializeStatement` extended with the new Int64 format cases and the `Math.Round` wrapping. - `JsonModelCoreTests.DurationMillisecondsIntegerWireTypeWritesAsInt` / `DurationMillisecondsFloatWireTypeWritesAsDouble` and `DeserializationTests.TestDeserializationOfDurationMillisecondsIntegerWireType` build a model with a `duration` property and compare the full generated output against per-case `TestData` files (Int32 emits `GetInt32`/`Convert.ToInt32(Math.Round(...))`; Int64 and unbounded `integer` emit `GetInt64`/`Convert.ToInt64(Math.Round(...))`). ### Validation - Full C# generator unit-test suites (Generator, Generator.Input, Generator.ClientModel, TestProjects.Local): all passing. - `eng/scripts/Generate.ps1` ran to completion; the only regenerated changes are the expected updates to `TestProjects/Local/Sample-TypeSpec/src/Generated/Internal/SerializationFormat.cs` and `TypeFormatters.cs` reflecting the new Int64 enum members, switch arms, and `Math.Round` wrapping. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
C
Copilot committed
02cd17baac8207a8ca8505bde4c485571d562ce5
Parent: d2691e5
Committed by GitHub <noreply@github.com>
on 5/29/2026, 6:46:47 PM