Skip to content

Commit 9492d03

Browse files
authored
Add mutator framework and canonicalization engine (#8822)
This PR adds packages for a new mutation framework that is significantly more flexible and capable than the current `mutateSubgraph` suite of APIs, and provides an HTTP Canonicalization library that makes implementing HTTP clients and servers significantly easier than current state of the art. Consult the READMEs for more information!
1 parent 5efb4f0 commit 9492d03

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+5907
-10
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@typespec/http-canonicalization"
5+
---
6+
7+
Add experimental HTTP canonicalization utilities.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: feature
3+
packages:
4+
- "@typespec/mutator-framework"
5+
---
6+
7+
Add experimental mutator framework.

cspell.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ words:
3434
- cadleditor
3535
- cadleng
3636
- cadlplayground
37+
- canonicalizer
3738
- clsx
3839
- cobertura
3940
- codehaus
@@ -267,6 +268,8 @@ words:
267268
- Ungroup
268269
- uninstantiated
269270
- unioned
271+
- unionified
272+
- unionify
270273
- unparented
271274
- unprefixed
272275
- unprojected
@@ -314,6 +317,7 @@ ignorePaths:
314317
- packages/http-client-java/generator/http-client-generator-clientcore-test/**
315318
- packages/http-client-js/test/e2e/**
316319
- packages/http-client-js/sample/**
320+
- packages/mutator-framework/**/*.test.ts
317321
- packages/typespec-vscode/test/scenarios/**
318322
- pnpm-lock.yaml
319323
- "**/*.mp4"
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# @typespec/http-canonicalization
2+
3+
** WARNING: THIS PACKAGE IS EXPERIMENTAL AND WILL CHANGE **
4+
5+
Utilities for emitters and tooling that need to understand type shapes in the HTTP protocol. The canonicalizer produces a mutated type graph specifying shapes in the language and on the wire, as well as groups together relevant HTTP metadata from various compiler APIs in a convenient package.
6+
7+
## Why you might use it
8+
9+
- Get HTTP request and response shapes.
10+
- Apply visibility transforms.
11+
- Understand how types are serialized to HTTP.
12+
- Get tests to determine how to discriminate non-discriminated unions.
13+
14+
## Installation
15+
16+
```bash
17+
pnpm add @typespec/http-canonicalization
18+
```
19+
20+
Peer dependencies `@typespec/compiler` and `@typespec/http` must be installed.
21+
22+
## Quick start
23+
24+
TypeSpec service definition:
25+
26+
```typespec
27+
import "@typespec/http";
28+
29+
model Foo {
30+
@visibility(Lifecycle.Read)
31+
@encode(DateTimeKnownEncoding.rfc7231)
32+
createdAt: utcDateTime;
33+
34+
@visibility(Lifecycle.Create)
35+
name: string;
36+
}
37+
38+
@route("/foo")
39+
@post
40+
op createFoo(@body foo: Foo): Foo;
41+
```
42+
43+
Emitter-side usage:
44+
45+
```ts
46+
import { $ } from "@typespec/compiler/typekit";
47+
import { HttpCanonicalizer } from "@typespec/http-canonicalization";
48+
49+
const tk = $(program);
50+
const canonicalizer = new HttpCanonicalizer(tk);
51+
const http = canonicalizer.canonicalize(op);
52+
const body = http.requestParameters.body!.type;
53+
// body.type.languageType.name === "FooCreate"
54+
// body.type.visibleProperties => only "name"
55+
56+
const response = http.responses[0];
57+
// response.type.languageType.properties has both "name" and "createdAt"
58+
// response.responses[0].headers?.etag captures header metadata
59+
```
60+
61+
## What the result tells you
62+
63+
- **Models and properties**: `languageType` reflects the shape visible to generated code, while `wireType` describes the payload sent over the network. Properties removed for the selected visibility remain in `properties` but carry an `intrinsic.never` language type so you can detect deletions.
64+
- **Scalars**: Each scalar includes a `codec` (for example `rfc7231` for date headers) and may change its wire type (e.g., `int32` -> `float64`) to match HTTP expectations.
65+
- **Operations**: Requests expose grouped `headers`, `query`, `path`, and `body` parameters. Responses list status codes, per-content-type payloads, and derived body information (single, multipart, or file payloads) along with metadata such as content type properties and filenames.
66+
- **Unions**: Variant tests contain ordered type guards (primitive checks before object checks) and literal discriminants. If variants cannot be distinguished (for example, two object models with identical shapes), the canonicalizer throws so you can surface a clear error.
67+
68+
## API reference
69+
70+
### OperationHttpCanonicalization
71+
72+
- Canonicalizes operations by deriving HTTP-specific request and response shapes and tracks the language and wire projections for each operation.
73+
- `requestParameters`: Canonicalized request parameters grouped by location.
74+
- `requestHeaders`: Canonicalized header parameters for the request.
75+
- `queryParameters`: Canonicalized query parameters for the request.
76+
- `pathParameters`: Canonicalized path parameters for the request.
77+
- `responses`: Canonicalized responses produced by the operation.
78+
- `path`: Concrete path for the HTTP operation.
79+
- `uriTemplate`: URI template used for path and query expansion.
80+
- `parameterVisibility`: Visibility applied when canonicalizing request parameters.
81+
- `returnTypeVisibility`: Visibility applied when canonicalizing response payloads.
82+
- `method`: HTTP method verb for the operation.
83+
- `name`: Name assigned to the canonicalized operation.
84+
- `languageType`: Mutated language type for this operation.
85+
- `wireType`: Mutated wire type for this operation.
86+
87+
### ModelHttpCanonicalization
88+
89+
- Canonicalizes models for HTTP and supplies language and wire variants along with visibility-aware metadata.
90+
- `isDeclaration`: Indicates if the canonicalization wraps a named TypeSpec declaration.
91+
- `codec`: Codec chosen to transform language and wire types for this model.
92+
- `languageType`: Possibly mutated language type for the model.
93+
- `wireType`: Possibly mutated wire type for the model.
94+
- `visibleProperties`: Canonical properties visible under the current visibility options.
95+
96+
### ModelPropertyHttpCanonicalization
97+
98+
- Canonicalizes model properties, tracking HTTP metadata, visibility, and codecs while adjusting types per location.
99+
- `isDeclaration`: Indicates if this property corresponds to a named declaration.
100+
- `isVisible`: Whether the property is visible with the current visibility options.
101+
- `codec`: Codec used to transform the property's type between language and wire views.
102+
- `isQueryParameter`: True when the property is a query parameter.
103+
- `queryParameterName`: Query parameter name when applicable.
104+
- `isHeader`: True when the property is an HTTP header.
105+
- `headerName`: Header name when the property is a header.
106+
- `isPathParameter`: True when the property is a path parameter.
107+
- `pathParameterName`: Path parameter name when applicable.
108+
- `explode`: Whether structured values should use explode semantics.
109+
- `languageType`: Possibly mutated language type for the property.
110+
- `wireType`: Possibly mutated wire type for the property.
111+
112+
### ScalarHttpCanonicalization
113+
114+
- Canonicalizes scalar types by applying encoding-specific mutations driven by codecs.
115+
- `options`: Canonicalization options in effect for the scalar.
116+
- `codec`: Codec responsible for transforming the scalar into language and wire types.
117+
- `isDeclaration`: Indicates whether the scalar is a named TypeSpec declaration.
118+
- `languageType`: Possibly mutated language type for the scalar.
119+
- `wireType`: Possibly mutated wire type for the scalar.
120+
121+
### UnionHttpCanonicalization
122+
123+
- Canonicalizes union types, tracking discriminators, envelope structures, and runtime variant tests for both language and wire projections.
124+
- `options`: Canonicalization options guiding union transformation.
125+
- `isDeclaration`: Indicates if the union corresponds to a named declaration.
126+
- `isDiscriminated`: True when `@discriminator` is present on the union.
127+
- `envelopeKind`: Envelope structure used for discriminated unions.
128+
- `discriminatorProperty`: Canonicalized discriminator property for envelope unions.
129+
- `variantDescriptors`: Descriptors describing each canonicalized variant.
130+
- `languageVariantTests`: Runtime tests used to select a variant for language types.
131+
- `wireVariantTests`: Runtime tests used to select a variant for wire types.
132+
- `discriminatorPropertyName`: Name of the discriminator property when present.
133+
- `envelopePropertyName`: Name of the envelope property when present.
134+
- `visibleVariants`: Variants that remain visible under the current visibility rules.
135+
- `languageType`: Potentially mutated language type for this union.
136+
- `wireType`: Potentially mutated wire type for this union.
137+
138+
### UnionVariantHttpCanonicalization
139+
140+
- Canonicalizes individual union variants for HTTP, removing hidden variants and exposing mutated representations.
141+
- `options`: Canonicalization options.
142+
- `isDeclaration`: Indicates if the variant corresponds to a named declaration.
143+
- `isVisible`: Whether the variant is visible under the current visibility options.
144+
- `languageType`: Possibly mutated language type for this variant.
145+
- `wireType`: Possibly mutated wire type for this variant.
146+
147+
### IntrinsicHttpCanonicalization
148+
149+
- Canonicalizes intrinsic types for HTTP, producing language and wire projections directed by the active options.
150+
- `options`: Canonicalization options.
151+
- `isDeclaration`: Indicates if this intrinsic represents a named declaration.
152+
- `languageType`: Possibly mutated language type for this intrinsic.
153+
- `wireType`: Possibly mutated wire type for this intrinsic.
154+
155+
### LiteralHttpCanonicalization
156+
157+
- Canonicalizes literal types for HTTP, yielding language and wire variants for string, number, and boolean literals.
158+
- `options`: Canonicalization options.
159+
- `isDeclaration`: Indicates if the literal is a named declaration (always false for literals).
160+
- `languageType`: Possibly mutated language type for this literal.
161+
- `wireType`: Possibly mutated wire type for this literal.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "@typespec/http-canonicalization",
3+
"version": "0.12.0",
4+
"type": "module",
5+
"main": "dist/src/index.js",
6+
"repository": {
7+
"type": "git",
8+
"url": "git+https://github.com/microsoft/typespec.git"
9+
},
10+
"scripts": {
11+
"build": "tsc -p .",
12+
"clean": "rimraf ./dist",
13+
"format": "prettier . --write",
14+
"watch": "tsc -p . --watch",
15+
"test": "vitest run",
16+
"test:ui": "vitest --ui",
17+
"test:watch": "vitest -w",
18+
"test:ci": "vitest run --coverage --reporter=junit --reporter=default",
19+
"prepack": "tsx ../../eng/tsp-core/scripts/strip-dev-import-exports.ts",
20+
"postpack": "tsx ../../eng/tsp-core/scripts/strip-dev-import-exports.ts --restore",
21+
"lint": "eslint . --max-warnings=0",
22+
"lint:fix": "eslint . --fix"
23+
},
24+
"exports": {
25+
".": {
26+
"import": "./dist/src/index.js"
27+
}
28+
},
29+
"keywords": [],
30+
"author": "",
31+
"license": "MIT",
32+
"description": "",
33+
"dependencies": {
34+
"@typespec/mutator-framework": "workspace:^"
35+
},
36+
"peerDependencies": {
37+
"@typespec/compiler": "workspace:^",
38+
"@typespec/http": "workspace:^"
39+
},
40+
"devDependencies": {
41+
"@types/node": "~24.3.0",
42+
"concurrently": "^9.1.2",
43+
"prettier": "~3.6.2"
44+
}
45+
}

0 commit comments

Comments
 (0)