Add support for custom deserializers on model fields#841
Add support for custom deserializers on model fields#841jhendrixMSFT wants to merge 1 commit intomainfrom
Conversation
This uses the `@clientOption` decorator to specify a custom deserializer function. Note that this will override any deserialize_with that would have been created by default.
fbd9b63 to
4c7992d
Compare
There was a problem hiding this comment.
Pull request overview
Adds support in the TypeSpec Rust emitter for specifying a custom serde deserialize_with function on individual model fields via the @clientOption decorator.
Changes:
- Introduces a model-field customization pipeline (
customizations) and wires it through the code model. - Updates the tcgc adapter to read
@clientOptionon model properties and recorddeserialize_with. - Updates model codegen to emit a custom
#[serde(deserialize_with = "...")]and adds a new end-to-end test fixture/workspace crate.
Reviewed changes
Copilot reviewed 12 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/typespec-rust/src/tcgcadapter/adapter.ts | Reads @clientOption from property decorators and stores a deserializeWith customization. |
| packages/typespec-rust/src/codemodel/customizations.ts | Adds a customization type/class (DeserializeWith) for model fields. |
| packages/typespec-rust/src/codemodel/types.ts | Adds customizations to ModelField and initializes it. |
| packages/typespec-rust/src/codemodel/index.ts | Re-exports customization types. |
| packages/typespec-rust/src/codegen/models.ts | Emits serde deserialize_with based on stored customization. |
| packages/typespec-rust/test/tsp/ClientOption/main.tsp | New TypeSpec test input applying @@clientOption(..., "deserialize_with", ..., "rust"). |
| packages/typespec-rust/test/other/client_option/src/lib.rs | Test crate providing the referenced deserializer function. |
| packages/typespec-rust/test/other/client_option/src/generated/models/models.rs | Expected generated model with #[serde(deserialize_with = "...")]. |
| packages/typespec-rust/test/other/client_option/src/generated/models/mod.rs | Generated models module plumbing for the test crate. |
| packages/typespec-rust/test/other/client_option/src/generated/mod.rs | Generated top-level module plumbing for the test crate. |
| packages/typespec-rust/test/other/client_option/Cargo.toml | New test crate manifest. |
| packages/typespec-rust/test/Cargo.toml | Adds the new test crate to the workspace members. |
| packages/typespec-rust/test/Cargo.lock | Adds lock entry for the new test crate. |
| packages/typespec-rust/.scripts/tspcompile.js | Adds compilation/generation step for the new ClientOption test. |
| packages/typespec-rust/package.json | Bumps @azure-tools/typespec-client-generator-core dependency and peer range. |
| packages/typespec-rust/pnpm-lock.yaml | Lockfile update for the dependency bump. |
| packages/typespec-rust/CHANGELOG.md | Documents the new @clientOption support. |
Files not reviewed (1)
- packages/typespec-rust/pnpm-lock.yaml: Language not supported
| if (deserializeWith) { | ||
| serdeParams.add(`deserialize_with = "${deserializeWith.name}"`); | ||
| } else if (unwrappedType.kind === 'encodedBytes' || unwrappedType.kind === 'enumValue' || unwrappedType.kind === 'literal' || unwrappedType.kind === 'offsetDateTime' || encodeAsString(unwrappedType)) { | ||
| addSerDeHelper(field, serdeParams, bodyFormat, use); |
There was a problem hiding this comment.
The new override skips addSerDeHelper entirely when a custom deserialize_with is present. addSerDeHelper also emits required non-deserialization serde params (e.g. serialize_with for base64/literal helpers, with = "azure_core::time::..." for date-times). Skipping it can break compilation or serialization; instead, keep the helper generation and only override the deserialize portion.
| if (deserializeWith) { | |
| serdeParams.add(`deserialize_with = "${deserializeWith.name}"`); | |
| } else if (unwrappedType.kind === 'encodedBytes' || unwrappedType.kind === 'enumValue' || unwrappedType.kind === 'literal' || unwrappedType.kind === 'offsetDateTime' || encodeAsString(unwrappedType)) { | |
| addSerDeHelper(field, serdeParams, bodyFormat, use); | |
| // always generate serde helpers for supported types; custom deserialize_with will override only the | |
| // deserialize_with portion while preserving other helper-generated serde params | |
| if (unwrappedType.kind === 'encodedBytes' || unwrappedType.kind === 'enumValue' || unwrappedType.kind === 'literal' || unwrappedType.kind === 'offsetDateTime' || encodeAsString(unwrappedType)) { | |
| addSerDeHelper(field, serdeParams, bodyFormat, use); | |
| } | |
| if (deserializeWith) { | |
| // remove any existing deserialize_with param emitted by addSerDeHelper so the custom one takes precedence | |
| for (const param of Array.from(serdeParams)) { | |
| if (param.startsWith('deserialize_with')) { | |
| serdeParams.delete(param); | |
| } | |
| } | |
| serdeParams.add(`deserialize_with = "${deserializeWith.name}"`); |
There was a problem hiding this comment.
Hmm I might need to retool this a bit.
This uses the
@clientOptiondecorator to specify a custom deserializer function. Note that this will override any deserialize_with that would have been created by default.