Azure / Azure/typespec-rust

[http-client-rust] Multi-inheritance for polymorphic types Implementation

Open
#517 1 comment 0 reactions 0 assignees View on GitHub
CodeGen PublicSurfaceArea
Dominant language
Rust
Stars
7
Forks
11
Avg merge
2d 5h
Merged PRs (30d)
5

Description

## Summary

Implement support for multi-level (nested) polymorphic discriminators, where a base type has a discriminator and a derived type introduces a second discriminator.

## Spector Test Coverage

**No spector test currently exists.** The `nested-discriminator` spec from `@typespec/http-specs` (`type/model/inheritance/nested-discriminator`) is not wired up in `tspcompile.js` and no test crate exists under `test/spector/`.

### Existing related tests:
- `type/model/inheritance/single-discriminator` ✅ (single-level discriminator)
- `type/model/inheritance/enum-discriminator` ✅ (enum-based discriminator)
- `type/model/inheritance/not-discriminated` ✅ (plain inheritance)
- `type/model/inheritance/recursive` — ✅
- **`type/model/inheritance/nested-discriminator`** — ❌ not wired up

## Scenario from `@typespec/http-specs`

The `nested-discriminator` TypeSpec spec defines:

```typespec
@discriminator("kind")
model Fish { age: int32; }

@discriminator("sharktype")
model Shark extends Fish { kind: "shark"; sharktype: string; }

model Salmon extends Fish { kind: "salmon"; friends?: Fish[]; hate?: Record; partner?: Fish; }

model SawShark extends Shark { sharktype: "saw"; }
model GoblinShark extends Shark { sharktype: "goblin"; }
```

Key challenge: `Fish` uses `"kind"` as its discriminator, and `Shark` (a subtype of `Fish`) introduces a second discriminator `"sharktype"` for its own subtypes.

### API operations in the spec:
1. `GET /model` — return a `GoblinShark` (`{"age": 1, "kind": "shark", "sharktype": "goblin"}`)
2. `PUT /model` — accept a `GoblinShark`
3. `GET /recursivemodel` — return a `Salmon` with nested polymorphic references (`Fish[]`, `Record`, `Fish`)
4. `PUT /recursivemodel` — accept recursive polymorphic input
5. `GET /missingdiscriminator` — return a `Fish` without the discriminator field
6. `GET /wrongdiscriminator` — return a `Fish` with an unknown discriminator value

## Implementation Plan

### 1. Emitter changes (`packages/typespec-rust/src/`)

The current `getDiscriminatedUnion()` in `src/tcgcadapter/adapter.ts` handles single-level discriminators. It needs to handle the case where a discriminated union member itself has `discriminatedSubtypes` (i.e., `Shark` is both a member of the `Fish` union and a base for the `Shark` union).

Key areas to investigate/modify:
- **`src/tcgcadapter/adapter.ts`** (lines ~235, 515-555): `getDiscriminatedUnion()` iterates `discriminatedSubtypes` but may not recursively handle subtypes that themselves define discriminators
- **`src/codegen/models.ts`**: Serde attribute generation needs to handle nested `#[serde(tag = ...)]` for multi-level discriminators
- **`src/codegen/unions.ts`**: Serialize implementation for nested discriminated unions

### 2. Add spector test

1. Add entry to `.scripts/tspcompile.js`:
```javascript
'spector_nesteddisc': {input: 'type/model/inheritance/nested-discriminator'},
```

2. Run `pnpm run tspcompile --filter=nesteddisc` to generate the test crate

3. Create integration tests in `test/spector/type/model/inheritance/nested-discriminator/tests/` covering:
- GET/PUT simple nested discriminator model
- GET/PUT recursive models with nested polymorphic references
- Missing discriminator handling
- Wrong/unknown discriminator handling

### 3. Verify Rust codegen output

The generated Rust types need to correctly represent the two-level discriminator hierarchy. Possible approaches:
- **Flatten**: A single `Fish` enum with variants for all leaf types (`Salmon`, `SawShark`, `GoblinShark`) with appropriate serde tagging
- **Nested enums**: A `Fish` enum containing a `Shark` enum variant that itself discriminates on `sharktype`

The approach should be consistent with how other Rust SDKs handle this pattern and should produce correct round-trip serialization.

### 4. Validation

- `pnpm build` — emitter compiles
- `pnpm test` — unit tests pass
- `cargo build` — generated Rust crates compile
- `cargo clippy` — no warnings
- `pnpm spector --start && cargo test` — integration tests pass

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.