@contractkit/plugin-csharp 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
5
5
  CLI tsup v8.5.1
6
6
  CLI Target: esnext
7
7
  ESM Build start
8
- ESM dist/index.js 87.30 KB
9
- ESM dist/index.js.map 188.87 KB
10
- ESM ⚡️ Build success in 180ms
8
+ ESM dist/index.js 88.92 KB
9
+ ESM dist/index.js.map 194.67 KB
10
+ ESM ⚡️ Build success in 108ms
11
11
  DTS Build start
12
- DTS ⚡️ Build success in 7420ms
12
+ DTS ⚡️ Build success in 5348ms
13
13
  DTS dist/index.d.ts 1.50 KB
@@ -3,34 +3,34 @@ $ vitest run --coverage
3
3
   RUN  v4.1.5 /home/runner/work/ContractKit/ContractKit/packages/plugin-csharp
4
4
  Coverage enabled with v8
5
5
 
6
- ✓ tests/codegen-client.test.ts (26 tests) 252ms
7
- ✓ tests/codegen-models.test.ts (65 tests) 282ms
8
- ✓ tests/index.test.ts (10 tests) 207ms
9
- ✓ tests/naming.test.ts (19 tests) 88ms
10
- ✓ tests/runtime.test.ts (14 tests) 51ms
11
- ✓ tests/scaffold.test.ts (5 tests) 14ms
12
- ✓ tests/hoist.test.ts (10 tests) 67ms
6
+ ✓ tests/naming.test.ts (23 tests) 51ms
7
+ ✓ tests/codegen-client.test.ts (40 tests) 189ms
8
+ ✓ tests/codegen-models.test.ts (66 tests) 210ms
9
+ ✓ tests/index.test.ts (10 tests) 70ms
10
+ ✓ tests/runtime.test.ts (14 tests) 28ms
11
+ ✓ tests/scaffold.test.ts (5 tests) 43ms
12
+ ✓ tests/hoist.test.ts (10 tests) 25ms
13
13
 
14
14
   Test Files  7 passed (7)
15
-  Tests  149 passed (149)
16
-  Start at  15:17:08
17
-  Duration  13.50s (transform 5.27s, setup 0ms, import 22.08s, tests 962ms, environment 10ms)
15
+  Tests  168 passed (168)
16
+  Start at  19:26:13
17
+  Duration  9.80s (transform 5.17s, setup 0ms, import 16.95s, tests 615ms, environment 12ms)
18
18
 
19
19
   % Coverage report from v8
20
20
  -------------------|---------|----------|---------|---------|-------------------
21
21
  File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
22
22
  -------------------|---------|----------|---------|---------|-------------------
23
- All files | 88.04 | 77.07 | 89.59 | 90.82 |
24
- src | 88.08 | 77.15 | 89.79 | 90.86 |
25
- ...gen-client.ts | 94.61 | 87.12 | 94.91 | 97.59 | ...94-496,513-514
26
- ...gen-models.ts | 91.29 | 75.25 | 96.61 | 93.02 | ...05,907,929-932
23
+ All files | 88.61 | 79.07 | 89.95 | 91 |
24
+ src | 88.68 | 78.83 | 90.19 | 91.05 |
25
+ ...gen-client.ts | 95.91 | 90.87 | 95.38 | 97.67 | ...28-530,547-548
26
+ ...gen-models.ts | 91.35 | 76.09 | 96.66 | 93.08 | ...18,920,942-945
27
27
  codegen-sdk.ts | 100 | 100 | 100 | 100 |
28
28
  hoist.ts | 78.66 | 61.9 | 93.1 | 79.06 | ...13-322,350-362
29
29
  index.ts | 70.74 | 71.21 | 58.06 | 75.59 | ...25-329,350-369
30
- naming.ts | 86.2 | 72.72 | 100 | 100 | ...40,164-191,204
30
+ naming.ts | 88.05 | 72.72 | 100 | 100 | ...40,187-214,227
31
31
  ...converters.ts | 100 | 100 | 100 | 100 |
32
32
  runtime.ts | 100 | 100 | 100 | 100 |
33
33
  scaffold.ts | 100 | 100 | 100 | 100 |
34
- tests | 86.95 | 75.67 | 88 | 89.74 |
35
- helpers.ts | 86.95 | 75.67 | 88 | 89.74 | 99,138,146,175
34
+ tests | 86.95 | 83.78 | 88 | 89.74 |
35
+ helpers.ts | 86.95 | 83.78 | 88 | 89.74 | 99,138,146,175
36
36
  -------------------|---------|----------|---------|---------|-------------------
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @contractkit/plugin-csharp
2
2
 
3
+ ## 0.1.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 153134e: A default on a `bigint` field is parsed as an exact `bigint`, and the generated TypeScript for one compiles.
8
+
9
+ `quantity: bigint = 9007199254740993` used to parse to the JS number `9007199254740992`, so every generator emitted the rounded value and `pnpm format` wrote it back into the file. The parser now reads the literal's digits into a `bigint`, for a `bigint` field or a `bigint | null` one. A non-integer default such as `= 1.5` is reported as an error instead. `FieldNode.default` and `OpParamNode.default` are typed `FieldDefault`, which adds `bigint`, so plugins that read a default have one more case to handle. The new `coerceDefault` export applies the rule.
10
+
11
+ The TypeScript plugin wrote a bigint default as `.default(5)`. Zod requires a default to be the schema's output type, so every generated project with a bigint default failed to typecheck (TS2769). At runtime the handler would also have received a `number`, because Zod returns a default without parsing it. It now writes `.default(5n)`.
12
+
13
+ Every other consumer handles the new value: Kotlin, Swift and C# initialize from the exact digits, Bruno and the `openapi` target write the digit string a `bigint` travels as, `openapi-to-ck` reads that string back with `BigInt()` rather than `Number()`, and the VS Code hover no longer throws when a referenced model carries a bigint default.
14
+
15
+ A default on a field whose type is a `ref` to a `bigint` alias is still parsed as a number, because the alias is not resolved at parse time.
16
+
17
+ - Updated dependencies [a440886]
18
+ - Updated dependencies [153134e]
19
+ - @contractkit/core@0.31.0
20
+
21
+ ## 0.1.4
22
+
23
+ ### Patch Changes
24
+
25
+ - 6f8f55b: Fix two naming collisions that made the generated SDK fail to compile.
26
+
27
+ A path param named after one of the method's own arguments or locals was declared twice: `/notes/{body}` on an operation with a request body gave `PutNoteAsync(string body, Note body, ...)`, error CS0100. A path param now gets a trailing underscore when it lands on `body`, `query`, `customHeaders`, `pathParams`, `cancellationToken`, the `response` and `headers` locals, or the client's `http` field, so that method takes `string body_`. Keyword escaping is unchanged (`string @class`). The pattern variable an optional response header is read into is renamed the same way, so a header named like a parameter no longer redeclares it (CS0136).
28
+
29
+ An operation that declares both a request `headers:` block and response headers generated two records named `<Method>Headers` in one namespace, error CS0101. The request record keeps that name, since it is the one a caller constructs, and the response record becomes `<Method>ResponseHeaders`. Operations declaring only one of the two are unchanged.
30
+
3
31
  ## 0.1.3
4
32
 
5
33
  ### Patch Changes
package/README.md CHANGED
@@ -143,7 +143,8 @@ that hold it.
143
143
  A method returns the response body directly. Three things change that:
144
144
 
145
145
  - A status declaring **response headers** returns `<Method>Result(Data, Headers)`, or
146
- `<Method>Headers` when there is no body.
146
+ `<Method>Headers` when there is no body. When the operation also declares request `headers:`,
147
+ those keep `<Method>Headers` and the response side is `<Method>ResponseHeaders`.
147
148
  - An operation the client can observe at **several statuses** returns an abstract
148
149
  `<Method>Response` with a `Status<code>` leaf each.
149
150
  - A status declaring **several content types** returns one leaf per mime.
@@ -24,7 +24,7 @@ export declare function generateCSharpClient(root: OpRootNode, opts: CSharpClien
24
24
  * values that came from the caller are percent-encoded. `params` says where a value lives: spread
25
25
  * across the signature, or behind one `pathParams` argument when the route declares a model.
26
26
  */
27
- export declare function buildPathExpression(path: string, params?: ParamSource): string;
27
+ export declare function buildPathExpression(path: string, params?: ParamSource, bindings?: ReadonlyMap<string, string>): string;
28
28
  /**
29
29
  * The SDK method name, in the same priority order every ContractKit SDK uses: an explicit `sdk:`,
30
30
  * then the operation's `name:`, then a name inferred from the verb and path. C# spells it
@@ -1 +1 @@
1
- {"version":3,"file":"codegen-client.d.ts","sourceRoot":"","sources":["../src/codegen-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,SAAS,EACT,eAAe,EAIf,UAAU,EACV,WAAW,EACX,WAAW,EACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAY9C,MAAM,WAAW,0BAA0B;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAwBD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,UAAQ,GAAG,OAAO,CAOtF;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,0BAA0B,GAAG,MAAM,CA2E/F;AAwZD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAY9E;AAkFD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAIhF"}
1
+ {"version":3,"file":"codegen-client.d.ts","sourceRoot":"","sources":["../src/codegen-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,SAAS,EACT,eAAe,EAIf,UAAU,EACV,WAAW,EACX,WAAW,EACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAa9C,MAAM,WAAW,0BAA0B;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAwBD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,eAAe,UAAQ,GAAG,OAAO,CAOtF;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,0BAA0B,GAAG,MAAM,CA2E/F;AAscD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAetH;AAuGD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAIhF"}
@@ -1 +1 @@
1
- {"version":3,"file":"codegen-models.d.ts","sourceRoot":"","sources":["../src/codegen-models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAa,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElH,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,YAAY,CAAC;AAK3D,MAAM,WAAW,yBAAyB;IACtC,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC;;;OAGG;IACH,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5C,kGAAkG;IAClG,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAiBD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,yBAAyB,GAAG,MAAM,CAwBpG;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAG3H;AAID,UAAU,aAAa;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC3C,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,oFAAoF;IACpF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,yBAAyB,GAAG;IAAE,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG,aAAa,CAS7H;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAYvI;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,UAAQ,GAAG,MAAM,CAqDrG;AAqBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CA2CrF;AA8pBD,YAAY,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"codegen-models.d.ts","sourceRoot":"","sources":["../src/codegen-models.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAA2B,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEhI,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,YAAY,CAAC;AAK3D,MAAM,WAAW,yBAAyB;IACtC,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC;;;OAGG;IACH,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC5C,kGAAkG;IAClG,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAiBD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,yBAAyB,GAAG,MAAM,CAwBpG;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAG3H;AAID,UAAU,aAAa;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC3C,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,oFAAoF;IACpF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,yBAAyB,GAAG;IAAE,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG,aAAa,CAS7H;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAYvI;AAID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,aAAa,EAAE,QAAQ,UAAQ,GAAG,MAAM,CAqDrG;AAqBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,MAAM,CA2CrF;AA2qBD,YAAY,EAAE,aAAa,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -115,6 +115,17 @@ function toCSharpParameterName(name) {
115
115
  if (/^\d/.test(result)) result = `_${result}`;
116
116
  return escapeCSharpIdentifier(result);
117
117
  }
118
+ function bindCSharpParameterNames(names, taken) {
119
+ const unavailable = new Set(taken);
120
+ const bindings = /* @__PURE__ */ new Map();
121
+ for (const name of names) {
122
+ let local = toCSharpParameterName(name).replace(/^@/, "");
123
+ while (unavailable.has(local)) local += "_";
124
+ unavailable.add(local);
125
+ bindings.set(name, escapeCSharpIdentifier(local));
126
+ }
127
+ return bindings;
128
+ }
118
129
  function safeMemberName(propertyName, ownerTypeName) {
119
130
  if (propertyName === ownerTypeName || RESERVED_MEMBER_NAMES.has(propertyName)) return `${propertyName}Value`;
120
131
  return propertyName;
@@ -329,11 +340,16 @@ function literalCSharpType(value, ctx) {
329
340
  if (typeof value === "boolean") return qualify("bool", "System.Boolean", ctx);
330
341
  return Number.isInteger(value) ? qualify("long", "System.Int64", ctx) : qualify("double", "System.Double", ctx);
331
342
  }
343
+ var MIN_SAFE_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
344
+ var MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
345
+ function isSafeInteger(value) {
346
+ return typeof value === "bigint" ? value >= MIN_SAFE_BIGINT && value <= MAX_SAFE_BIGINT : Number.isSafeInteger(value);
347
+ }
332
348
  function renderDefault(value, type, ctx, memberNames = /* @__PURE__ */ new Set()) {
333
349
  const enumType = (name) => memberNames.has(name) ? `global::${ctx.namespace}.Models.${name}` : name;
334
350
  const inner = type.kind === "lazy" ? type.inner : type;
335
351
  if (typeof value === "boolean") return String(value);
336
- if (typeof value === "number") {
352
+ if (typeof value === "number" || typeof value === "bigint") {
337
353
  if (inner.kind === "scalar") {
338
354
  switch (inner.name) {
339
355
  case "int":
@@ -343,7 +359,7 @@ function renderDefault(value, type, ctx, memberNames = /* @__PURE__ */ new Set()
343
359
  case "decimal":
344
360
  return `${value}m`;
345
361
  case "bigint":
346
- return Number.isSafeInteger(value) ? `new BigInteger(${value})` : `BigInteger.Parse("${value}")`;
362
+ return isSafeInteger(value) ? `new BigInteger(${value})` : `BigInteger.Parse("${value}")`;
347
363
  }
348
364
  }
349
365
  return Number.isInteger(value) ? `${value}L` : `${value}d`;
@@ -897,10 +913,12 @@ function observableOf(shape) {
897
913
  function generateMethod(route, op, ctx, methodName) {
898
914
  const base = methodBase(methodName);
899
915
  const shape = responseShape(op);
900
- const returnType = returnTypeFor(shape, base, ctx);
916
+ const returnType = returnTypeFor(shape, op, base, ctx);
901
917
  const observable = observableOf(shape);
902
918
  const expectStatuses = observable.filter((r) => r.statusCode < 200 || r.statusCode >= 300).map((r) => r.statusCode);
903
- const params = buildMethodParams(route, op, ctx);
919
+ const pathBindings = bindPathParams(route);
920
+ const params = buildMethodParams(route, op, ctx, pathBindings);
921
+ const bound = [...METHOD_LOCALS, ...params.map((p) => p.name.replace(/^@/, ""))];
904
922
  const signature = [...params.map((p) => `${p.type} ${p.name}${p.optional ? " = null" : ""}`), "CancellationToken cancellationToken = default"].join(
905
923
  ", "
906
924
  );
@@ -909,7 +927,7 @@ function generateMethod(route, op, ctx, methodName) {
909
927
  if (resolveModifiers(route, op).includes("deprecated")) lines.push('[Obsolete("Deprecated in the contract")]');
910
928
  lines.push(`public async ${returnType === "void" ? "Task" : `Task<${returnType}>`} ${methodName}(${signature})`);
911
929
  lines.push("{");
912
- const callArgs = [`HttpMethod.${httpMethodConstant(op.method)}`, buildPathExpression(route.path, route.params)];
930
+ const callArgs = [`HttpMethod.${httpMethodConstant(op.method)}`, buildPathExpression(route.path, route.params, pathBindings)];
913
931
  if (op.query) callArgs.push("query: http.Params(query)");
914
932
  if (op.headers) callArgs.push("headers: http.Params(customHeaders)");
915
933
  const content = bodyArgument(op);
@@ -921,16 +939,16 @@ function generateMethod(route, op, ctx, methodName) {
921
939
  callArgs.forEach((arg, index) => {
922
940
  lines.push(` ${arg}${index === callArgs.length - 1 ? ").ConfigureAwait(false);" : ","}`);
923
941
  });
924
- lines.push(...returnStatements(shape, base, ctx, where(route, op)));
942
+ lines.push(...returnStatements(shape, op, base, ctx, where(route, op), bound));
925
943
  lines.push("}");
926
944
  return lines;
927
945
  }
928
- function returnTypeFor(shape, base, ctx) {
946
+ function returnTypeFor(shape, op, base, ctx) {
929
947
  if (shape.kind !== "simple") return `${base}Response`;
930
948
  const response = shape.response;
931
949
  const body = response?.bodies[0];
932
950
  const headers = response?.headers ?? [];
933
- if (!body) return headers.length > 0 ? `${base}Headers` : "void";
951
+ if (!body) return headers.length > 0 ? headersRecordName(op, base) : "void";
934
952
  const dataType = bodyCSharpType(body, ctx);
935
953
  return headers.length > 0 ? `${base}Result` : dataType;
936
954
  }
@@ -954,18 +972,18 @@ function bodyReadExpr(body, ctx) {
954
972
  return `http.ReadJson<${renderCSharpType(body.bodyType, ctx, false)}>(response)`;
955
973
  }
956
974
  }
957
- function returnStatements(shape, base, ctx, place) {
975
+ function returnStatements(shape, op, base, ctx, place, bound) {
958
976
  if (shape.kind === "simple") {
959
977
  const response = shape.response;
960
978
  const body = response?.bodies[0];
961
979
  const headers = response?.headers ?? [];
962
980
  if (headers.length === 0) return body ? [` return ${bodyReadExpr(body, ctx)};`] : [];
963
- const lines2 = readHeaderLines(headers, `${base}Headers`, ctx, place, " ");
981
+ const lines2 = readHeaderLines(headers, headersRecordName(op, base), ctx, place, " ", bound);
964
982
  return body ? [...lines2, ` return new ${base}Result(${bodyReadExpr(body, ctx)}, headers);`] : [...lines2, " return headers;"];
965
983
  }
966
984
  if (shape.kind === "multiMime") {
967
985
  const headers = shape.response.headers ?? [];
968
- const lines2 = headers.length > 0 ? readHeaderLines(headers, `${base}Headers`, ctx, place, " ") : [];
986
+ const lines2 = headers.length > 0 ? readHeaderLines(headers, headersRecordName(op, base), ctx, place, " ", bound) : [];
969
987
  lines2.push(...mimeSwitch(shape.response, base, void 0, ctx, " ", headers.length > 0));
970
988
  return lines2;
971
989
  }
@@ -974,21 +992,21 @@ function returnStatements(shape, base, ctx, place) {
974
992
  for (const response of rest) {
975
993
  lines.push(` case ${response.statusCode}:`);
976
994
  lines.push(" {");
977
- lines.push(...statusBranch(response, base, response.statusCode, ctx, place, " "));
995
+ lines.push(...statusBranch(response, op, base, response.statusCode, ctx, place, " ", bound));
978
996
  lines.push(" }");
979
997
  lines.push("");
980
998
  }
981
999
  lines.push(" default:");
982
1000
  lines.push(" {");
983
- lines.push(...statusBranch(fallback, base, fallback.statusCode, ctx, place, " "));
1001
+ lines.push(...statusBranch(fallback, op, base, fallback.statusCode, ctx, place, " ", bound));
984
1002
  lines.push(" }");
985
1003
  lines.push(" }");
986
1004
  return lines;
987
1005
  }
988
- function statusBranch(response, base, statusCode, ctx, place, indent) {
1006
+ function statusBranch(response, op, base, statusCode, ctx, place, indent, bound) {
989
1007
  const lines = [];
990
1008
  const headers = response.headers ?? [];
991
- if (headers.length > 0) lines.push(...readHeaderLines(headers, headersRecordName(base, statusCode), ctx, place, indent));
1009
+ if (headers.length > 0) lines.push(...readHeaderLines(headers, headersRecordName(op, base, statusCode), ctx, place, indent, bound));
992
1010
  lines.push(...mimeSwitch(response, base, statusCode, ctx, indent, headers.length > 0));
993
1011
  return lines;
994
1012
  }
@@ -1029,8 +1047,12 @@ function bodyArgument(op) {
1029
1047
  return `content: http.JsonContent(body, ${mime})`;
1030
1048
  }
1031
1049
  }
1032
- function headersRecordName(base, statusCode) {
1033
- return statusCode === void 0 ? `${base}Headers` : `${base}${statusCode}Headers`;
1050
+ function headersRecordName(op, base, statusCode) {
1051
+ if (statusCode !== void 0) return `${base}${statusCode}Headers`;
1052
+ return declaresRequestHeadersRecord(op) ? `${base}ResponseHeaders` : `${base}Headers`;
1053
+ }
1054
+ function declaresRequestHeadersRecord(op) {
1055
+ return op.headers?.kind === "params" && op.headers.nodes.length > 0;
1034
1056
  }
1035
1057
  function leafRecordName(response, body, statusCode) {
1036
1058
  const statusPart = statusCode === void 0 ? "" : `Status${statusCode}`;
@@ -1056,12 +1078,12 @@ function responseDeclarations(route, op, ctx) {
1056
1078
  const response = shape.response;
1057
1079
  const headers = response?.headers ?? [];
1058
1080
  if (headers.length === 0) return lines;
1059
- headerRecord(headers, headersRecordName(base));
1081
+ headerRecord(headers, headersRecordName(op, base));
1060
1082
  const body = response?.bodies[0];
1061
1083
  if (body) {
1062
1084
  lines.push("");
1063
1085
  lines.push(...xmlDocLines(`The body of ${place}, with the response headers the contract declares.`, ""));
1064
- lines.push(`public sealed record ${base}Result(${bodyCSharpType(body, ctx)} Data, ${headersRecordName(base)} Headers);`);
1086
+ lines.push(`public sealed record ${base}Result(${bodyCSharpType(body, ctx)} Data, ${headersRecordName(op, base)} Headers);`);
1065
1087
  }
1066
1088
  return lines;
1067
1089
  }
@@ -1069,7 +1091,7 @@ function responseDeclarations(route, op, ctx) {
1069
1091
  const withStatus = shape.kind === "multiStatus";
1070
1092
  for (const response of responses) {
1071
1093
  const headers = response.headers ?? [];
1072
- if (headers.length > 0) headerRecord(headers, headersRecordName(base, withStatus ? response.statusCode : void 0));
1094
+ if (headers.length > 0) headerRecord(headers, headersRecordName(op, base, withStatus ? response.statusCode : void 0));
1073
1095
  }
1074
1096
  lines.push("");
1075
1097
  lines.push(
@@ -1091,7 +1113,7 @@ function responseDeclarations(route, op, ctx) {
1091
1113
  const name = leafRecordName(response, body, statusCode);
1092
1114
  const parameters = [];
1093
1115
  if (body) parameters.push(`${bodyCSharpType(body, ctx)} Data`);
1094
- if (headers.length > 0) parameters.push(`${headersRecordName(base, statusCode)} Headers`);
1116
+ if (headers.length > 0) parameters.push(`${headersRecordName(op, base, statusCode)} Headers`);
1095
1117
  lines.push("");
1096
1118
  lines.push(` public sealed record ${name}(${parameters.join(", ")}) : ${base}Response;`);
1097
1119
  }
@@ -1137,12 +1159,16 @@ function describeHeaderType(type) {
1137
1159
  if (type.kind === "ref") return `the contract '${type.name}'`;
1138
1160
  return `${type.kind === "array" || type.kind === "inlineObject" ? "an" : "a"} ${type.kind}`;
1139
1161
  }
1140
- function readHeaderLines(headers, typeName, ctx, place, indent) {
1162
+ function readHeaderLines(headers, typeName, ctx, place, indent, bound) {
1163
+ const locals = bindCSharpParameterNames(
1164
+ headers.filter((h) => h.optional).map((h) => h.name),
1165
+ bound
1166
+ );
1141
1167
  const args = headers.map((header) => {
1142
1168
  const reader = headerReader(header, place);
1143
1169
  const name = quoteCSharpString(header.name);
1144
1170
  if (!header.optional) return reader.read(`http.RequireHeader(response, ${name})`);
1145
- const local = toCSharpParameterName(header.name);
1171
+ const local = locals.get(header.name);
1146
1172
  return `response.Header(${name}) is { } ${local} ? ${reader.read(local)} : null`;
1147
1173
  });
1148
1174
  const lines = [`${indent}var headers = new ${typeName}(`];
@@ -1165,22 +1191,30 @@ function httpMethodConstant(method) {
1165
1191
  return lower.charAt(0).toUpperCase() + lower.slice(1);
1166
1192
  }
1167
1193
  var PATH_PLACEHOLDER = /\{([a-zA-Z_$][a-zA-Z0-9_$.-]*)\}/g;
1168
- function buildPathExpression(path, params) {
1194
+ function buildPathExpression(path, params, bindings) {
1169
1195
  const args = path.split("/").filter(Boolean).map((raw) => {
1170
1196
  PATH_PLACEHOLDER.lastIndex = 0;
1171
1197
  const match = PATH_PLACEHOLDER.exec(raw);
1172
1198
  if (!match || match[0] !== raw) return quoteCSharpString(raw);
1173
- const value = params && params.kind !== "params" ? `pathParams.${toCSharpPropertyName(match[1])}` : toCSharpParameterName(match[1]);
1199
+ const value = params && params.kind !== "params" ? `pathParams.${toCSharpPropertyName(match[1])}` : bindings?.get(match[1]) ?? toCSharpParameterName(match[1]);
1174
1200
  return `http.Segment(${value})`;
1175
1201
  });
1176
1202
  return `http.Path(${args.join(", ")})`;
1177
1203
  }
1178
- function buildMethodParams(route, op, ctx) {
1204
+ var METHOD_LOCALS = ["body", "query", "customHeaders", "pathParams", "cancellationToken", "response", "headers", "http"];
1205
+ function bindPathParams(route) {
1206
+ if (route.params?.kind !== "params") return /* @__PURE__ */ new Map();
1207
+ return bindCSharpParameterNames(
1208
+ route.params.nodes.map((n) => n.name),
1209
+ METHOD_LOCALS
1210
+ );
1211
+ }
1212
+ function buildMethodParams(route, op, ctx, pathBindings) {
1179
1213
  const params = [];
1180
1214
  if (route.params) {
1181
1215
  if (route.params.kind === "params") {
1182
1216
  for (const node of route.params.nodes) {
1183
- params.push({ name: toCSharpParameterName(node.name), type: renderCSharpType(node.type, ctx, true), optional: false });
1217
+ params.push({ name: pathBindings.get(node.name), type: renderCSharpType(node.type, ctx, true), optional: false });
1184
1218
  }
1185
1219
  } else {
1186
1220
  params.push({ name: "pathParams", type: renderParamSourceType(route.params, ctx, ""), optional: false });