@contractkit/plugin-csharp 0.1.2 → 0.1.4

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 86.93 KB
9
- ESM dist/index.js.map 187.54 KB
10
- ESM ⚡️ Build success in 141ms
8
+ ESM dist/index.js 88.63 KB
9
+ ESM dist/index.js.map 193.78 KB
10
+ ESM ⚡️ Build success in 154ms
11
11
  DTS Build start
12
- DTS ⚡️ Build success in 6474ms
12
+ DTS ⚡️ Build success in 6453ms
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) 231ms
7
- ✓ tests/codegen-models.test.ts (63 tests) 272ms
8
- ✓ tests/index.test.ts (10 tests) 212ms
9
- ✓ tests/runtime.test.ts (14 tests) 67ms
10
- ✓ tests/naming.test.ts (19 tests) 76ms
11
- ✓ tests/scaffold.test.ts (5 tests) 28ms
12
- ✓ tests/hoist.test.ts (10 tests) 61ms
6
+ ✓ tests/naming.test.ts (23 tests) 110ms
7
+ ✓ tests/codegen-client.test.ts (40 tests) 305ms
8
+ ✓ tests/codegen-models.test.ts (65 tests) 306ms
9
+ ✓ tests/index.test.ts (10 tests) 199ms
10
+ ✓ tests/runtime.test.ts (14 tests) 65ms
11
+ ✓ tests/scaffold.test.ts (5 tests) 46ms
12
+ ✓ tests/hoist.test.ts (10 tests) 54ms
13
13
 
14
14
   Test Files  7 passed (7)
15
-  Tests  147 passed (147)
16
-  Start at  13:40:51
17
-  Duration  12.55s (transform 6.16s, setup 0ms, import 20.87s, tests 948ms, environment 18ms)
15
+  Tests  167 passed (167)
16
+  Start at  17:55:57
17
+  Duration  13.21s (transform 4.92s, setup 0ms, import 21.10s, tests 1.08s, environment 1ms)
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 | 87.99 | 76.98 | 89.44 | 90.79 |
24
- src | 88.03 | 77.05 | 89.63 | 90.83 |
25
- ...gen-client.ts | 94.61 | 87.12 | 94.91 | 97.59 | ...94-496,513-514
26
- ...gen-models.ts | 91.19 | 75 | 96.42 | 92.97 | ...79,881,903-906
23
+ All files | 88.59 | 78.77 | 89.91 | 90.97 |
24
+ src | 88.65 | 78.52 | 90.14 | 91.02 |
25
+ ...gen-client.ts | 95.91 | 90.87 | 95.38 | 97.67 | ...28-530,547-548
26
+ ...gen-models.ts | 91.29 | 75.25 | 96.61 | 93.02 | ...05,907,929-932
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,26 @@
1
1
  # @contractkit/plugin-csharp
2
2
 
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 6f8f55b: Fix two naming collisions that made the generated SDK fail to compile.
8
+
9
+ 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).
10
+
11
+ 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.
12
+
13
+ ## 0.1.3
14
+
15
+ ### Patch Changes
16
+
17
+ - ad6b72e: A default on a field whose property shares its enum's name now compiles. `rating?: Rating = neutral`
18
+ generated `public Rating? Rating { get; init; } = Rating.Neutral;`, and inside the record `Rating`
19
+ resolved to the property rather than the type, so the build failed with CS0236. C#'s rule that lets a
20
+ member share its type's name only applies when the member's type is exactly that type, and `Rating?`
21
+ is `Nullable<Rating>`. The initializer is now written from the global namespace whenever a property of
22
+ the same record would shadow the enum.
23
+
3
24
  ## 0.1.2
4
25
 
5
26
  ### 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;AAooBD,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,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"}
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,7 +340,8 @@ 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
  }
332
- function renderDefault(value, type, ctx) {
343
+ function renderDefault(value, type, ctx, memberNames = /* @__PURE__ */ new Set()) {
344
+ const enumType = (name) => memberNames.has(name) ? `global::${ctx.namespace}.Models.${name}` : name;
333
345
  const inner = type.kind === "lazy" ? type.inner : type;
334
346
  if (typeof value === "boolean") return String(value);
335
347
  if (typeof value === "number") {
@@ -350,13 +362,13 @@ function renderDefault(value, type, ctx) {
350
362
  if (inner.kind === "enum") {
351
363
  const decl = ctx.hoisted?.byNode.get(inner);
352
364
  if (!decl || !inner.values.includes(value)) return void 0;
353
- return `${decl.name}.${enumMemberNames(inner.values).get(value)}`;
365
+ return `${enumType(decl.name)}.${enumMemberNames(inner.values).get(value)}`;
354
366
  }
355
367
  if (inner.kind === "ref") {
356
368
  const target = ctx.modelIndex.get(inner.name);
357
369
  const targetType = target?.type?.kind === "lazy" ? target.type.inner : target?.type;
358
370
  if (targetType?.kind !== "enum" || !targetType.values.includes(value)) return void 0;
359
- return `${inner.name}.${enumMemberNames(targetType.values).get(value)}`;
371
+ return `${enumType(inner.name)}.${enumMemberNames(targetType.values).get(value)}`;
360
372
  }
361
373
  if (inner.kind === "scalar") {
362
374
  switch (inner.name) {
@@ -555,24 +567,28 @@ function renderRecord(name, fields, ctx, forInput, supertypes, description, depr
555
567
  lines.push(`public sealed record ${name}${implementsClause};`);
556
568
  return lines;
557
569
  }
570
+ const memberNames = new Set(fields.map((field) => memberName(field, name)));
558
571
  lines.push(`public sealed record ${name}${implementsClause}`);
559
572
  lines.push("{");
560
573
  fields.forEach((field, index) => {
561
574
  if (index > 0) lines.push("");
562
- lines.push(...renderField(field, ctx, forInput, name, wireCase));
575
+ lines.push(...renderField(field, ctx, forInput, name, memberNames, wireCase));
563
576
  });
564
577
  lines.push("}");
565
578
  return lines;
566
579
  }
567
- function renderField(field, ctx, forInput, ownerTypeName, wireCase) {
568
- const propName = safeMemberName(toCSharpPropertyName(field.name), ownerTypeName);
580
+ function memberName(field, ownerTypeName) {
581
+ return safeMemberName(toCSharpPropertyName(field.name), ownerTypeName);
582
+ }
583
+ function renderField(field, ctx, forInput, ownerTypeName, memberNames, wireCase) {
584
+ const propName = memberName(field, ownerTypeName);
569
585
  const wireName = applyWireCase(field.name, wireCase);
570
586
  let typeStr = renderCSharpType(field.type, ctx, forInput);
571
587
  if ((field.optional || field.nullable) && !typeStr.endsWith("?")) typeStr += "?";
572
- let initializer = field.default !== void 0 ? renderDefault(field.default, field.type, ctx) : void 0;
588
+ let initializer = field.default !== void 0 ? renderDefault(field.default, field.type, ctx, memberNames) : void 0;
573
589
  if (initializer === void 0 && !field.optional && !field.nullable) {
574
590
  const inner = field.type.kind === "lazy" ? field.type.inner : field.type;
575
- if (inner.kind === "literal") initializer = renderDefault(inner.value, inner, ctx);
591
+ if (inner.kind === "literal") initializer = renderDefault(inner.value, inner, ctx, memberNames);
576
592
  }
577
593
  const isRequired = !field.optional && initializer === void 0;
578
594
  const lines = [];
@@ -892,10 +908,12 @@ function observableOf(shape) {
892
908
  function generateMethod(route, op, ctx, methodName) {
893
909
  const base = methodBase(methodName);
894
910
  const shape = responseShape(op);
895
- const returnType = returnTypeFor(shape, base, ctx);
911
+ const returnType = returnTypeFor(shape, op, base, ctx);
896
912
  const observable = observableOf(shape);
897
913
  const expectStatuses = observable.filter((r) => r.statusCode < 200 || r.statusCode >= 300).map((r) => r.statusCode);
898
- const params = buildMethodParams(route, op, ctx);
914
+ const pathBindings = bindPathParams(route);
915
+ const params = buildMethodParams(route, op, ctx, pathBindings);
916
+ const bound = [...METHOD_LOCALS, ...params.map((p) => p.name.replace(/^@/, ""))];
899
917
  const signature = [...params.map((p) => `${p.type} ${p.name}${p.optional ? " = null" : ""}`), "CancellationToken cancellationToken = default"].join(
900
918
  ", "
901
919
  );
@@ -904,7 +922,7 @@ function generateMethod(route, op, ctx, methodName) {
904
922
  if (resolveModifiers(route, op).includes("deprecated")) lines.push('[Obsolete("Deprecated in the contract")]');
905
923
  lines.push(`public async ${returnType === "void" ? "Task" : `Task<${returnType}>`} ${methodName}(${signature})`);
906
924
  lines.push("{");
907
- const callArgs = [`HttpMethod.${httpMethodConstant(op.method)}`, buildPathExpression(route.path, route.params)];
925
+ const callArgs = [`HttpMethod.${httpMethodConstant(op.method)}`, buildPathExpression(route.path, route.params, pathBindings)];
908
926
  if (op.query) callArgs.push("query: http.Params(query)");
909
927
  if (op.headers) callArgs.push("headers: http.Params(customHeaders)");
910
928
  const content = bodyArgument(op);
@@ -916,16 +934,16 @@ function generateMethod(route, op, ctx, methodName) {
916
934
  callArgs.forEach((arg, index) => {
917
935
  lines.push(` ${arg}${index === callArgs.length - 1 ? ").ConfigureAwait(false);" : ","}`);
918
936
  });
919
- lines.push(...returnStatements(shape, base, ctx, where(route, op)));
937
+ lines.push(...returnStatements(shape, op, base, ctx, where(route, op), bound));
920
938
  lines.push("}");
921
939
  return lines;
922
940
  }
923
- function returnTypeFor(shape, base, ctx) {
941
+ function returnTypeFor(shape, op, base, ctx) {
924
942
  if (shape.kind !== "simple") return `${base}Response`;
925
943
  const response = shape.response;
926
944
  const body = response?.bodies[0];
927
945
  const headers = response?.headers ?? [];
928
- if (!body) return headers.length > 0 ? `${base}Headers` : "void";
946
+ if (!body) return headers.length > 0 ? headersRecordName(op, base) : "void";
929
947
  const dataType = bodyCSharpType(body, ctx);
930
948
  return headers.length > 0 ? `${base}Result` : dataType;
931
949
  }
@@ -949,18 +967,18 @@ function bodyReadExpr(body, ctx) {
949
967
  return `http.ReadJson<${renderCSharpType(body.bodyType, ctx, false)}>(response)`;
950
968
  }
951
969
  }
952
- function returnStatements(shape, base, ctx, place) {
970
+ function returnStatements(shape, op, base, ctx, place, bound) {
953
971
  if (shape.kind === "simple") {
954
972
  const response = shape.response;
955
973
  const body = response?.bodies[0];
956
974
  const headers = response?.headers ?? [];
957
975
  if (headers.length === 0) return body ? [` return ${bodyReadExpr(body, ctx)};`] : [];
958
- const lines2 = readHeaderLines(headers, `${base}Headers`, ctx, place, " ");
976
+ const lines2 = readHeaderLines(headers, headersRecordName(op, base), ctx, place, " ", bound);
959
977
  return body ? [...lines2, ` return new ${base}Result(${bodyReadExpr(body, ctx)}, headers);`] : [...lines2, " return headers;"];
960
978
  }
961
979
  if (shape.kind === "multiMime") {
962
980
  const headers = shape.response.headers ?? [];
963
- const lines2 = headers.length > 0 ? readHeaderLines(headers, `${base}Headers`, ctx, place, " ") : [];
981
+ const lines2 = headers.length > 0 ? readHeaderLines(headers, headersRecordName(op, base), ctx, place, " ", bound) : [];
964
982
  lines2.push(...mimeSwitch(shape.response, base, void 0, ctx, " ", headers.length > 0));
965
983
  return lines2;
966
984
  }
@@ -969,21 +987,21 @@ function returnStatements(shape, base, ctx, place) {
969
987
  for (const response of rest) {
970
988
  lines.push(` case ${response.statusCode}:`);
971
989
  lines.push(" {");
972
- lines.push(...statusBranch(response, base, response.statusCode, ctx, place, " "));
990
+ lines.push(...statusBranch(response, op, base, response.statusCode, ctx, place, " ", bound));
973
991
  lines.push(" }");
974
992
  lines.push("");
975
993
  }
976
994
  lines.push(" default:");
977
995
  lines.push(" {");
978
- lines.push(...statusBranch(fallback, base, fallback.statusCode, ctx, place, " "));
996
+ lines.push(...statusBranch(fallback, op, base, fallback.statusCode, ctx, place, " ", bound));
979
997
  lines.push(" }");
980
998
  lines.push(" }");
981
999
  return lines;
982
1000
  }
983
- function statusBranch(response, base, statusCode, ctx, place, indent) {
1001
+ function statusBranch(response, op, base, statusCode, ctx, place, indent, bound) {
984
1002
  const lines = [];
985
1003
  const headers = response.headers ?? [];
986
- if (headers.length > 0) lines.push(...readHeaderLines(headers, headersRecordName(base, statusCode), ctx, place, indent));
1004
+ if (headers.length > 0) lines.push(...readHeaderLines(headers, headersRecordName(op, base, statusCode), ctx, place, indent, bound));
987
1005
  lines.push(...mimeSwitch(response, base, statusCode, ctx, indent, headers.length > 0));
988
1006
  return lines;
989
1007
  }
@@ -1024,8 +1042,12 @@ function bodyArgument(op) {
1024
1042
  return `content: http.JsonContent(body, ${mime})`;
1025
1043
  }
1026
1044
  }
1027
- function headersRecordName(base, statusCode) {
1028
- return statusCode === void 0 ? `${base}Headers` : `${base}${statusCode}Headers`;
1045
+ function headersRecordName(op, base, statusCode) {
1046
+ if (statusCode !== void 0) return `${base}${statusCode}Headers`;
1047
+ return declaresRequestHeadersRecord(op) ? `${base}ResponseHeaders` : `${base}Headers`;
1048
+ }
1049
+ function declaresRequestHeadersRecord(op) {
1050
+ return op.headers?.kind === "params" && op.headers.nodes.length > 0;
1029
1051
  }
1030
1052
  function leafRecordName(response, body, statusCode) {
1031
1053
  const statusPart = statusCode === void 0 ? "" : `Status${statusCode}`;
@@ -1051,12 +1073,12 @@ function responseDeclarations(route, op, ctx) {
1051
1073
  const response = shape.response;
1052
1074
  const headers = response?.headers ?? [];
1053
1075
  if (headers.length === 0) return lines;
1054
- headerRecord(headers, headersRecordName(base));
1076
+ headerRecord(headers, headersRecordName(op, base));
1055
1077
  const body = response?.bodies[0];
1056
1078
  if (body) {
1057
1079
  lines.push("");
1058
1080
  lines.push(...xmlDocLines(`The body of ${place}, with the response headers the contract declares.`, ""));
1059
- lines.push(`public sealed record ${base}Result(${bodyCSharpType(body, ctx)} Data, ${headersRecordName(base)} Headers);`);
1081
+ lines.push(`public sealed record ${base}Result(${bodyCSharpType(body, ctx)} Data, ${headersRecordName(op, base)} Headers);`);
1060
1082
  }
1061
1083
  return lines;
1062
1084
  }
@@ -1064,7 +1086,7 @@ function responseDeclarations(route, op, ctx) {
1064
1086
  const withStatus = shape.kind === "multiStatus";
1065
1087
  for (const response of responses) {
1066
1088
  const headers = response.headers ?? [];
1067
- if (headers.length > 0) headerRecord(headers, headersRecordName(base, withStatus ? response.statusCode : void 0));
1089
+ if (headers.length > 0) headerRecord(headers, headersRecordName(op, base, withStatus ? response.statusCode : void 0));
1068
1090
  }
1069
1091
  lines.push("");
1070
1092
  lines.push(
@@ -1086,7 +1108,7 @@ function responseDeclarations(route, op, ctx) {
1086
1108
  const name = leafRecordName(response, body, statusCode);
1087
1109
  const parameters = [];
1088
1110
  if (body) parameters.push(`${bodyCSharpType(body, ctx)} Data`);
1089
- if (headers.length > 0) parameters.push(`${headersRecordName(base, statusCode)} Headers`);
1111
+ if (headers.length > 0) parameters.push(`${headersRecordName(op, base, statusCode)} Headers`);
1090
1112
  lines.push("");
1091
1113
  lines.push(` public sealed record ${name}(${parameters.join(", ")}) : ${base}Response;`);
1092
1114
  }
@@ -1132,12 +1154,16 @@ function describeHeaderType(type) {
1132
1154
  if (type.kind === "ref") return `the contract '${type.name}'`;
1133
1155
  return `${type.kind === "array" || type.kind === "inlineObject" ? "an" : "a"} ${type.kind}`;
1134
1156
  }
1135
- function readHeaderLines(headers, typeName, ctx, place, indent) {
1157
+ function readHeaderLines(headers, typeName, ctx, place, indent, bound) {
1158
+ const locals = bindCSharpParameterNames(
1159
+ headers.filter((h) => h.optional).map((h) => h.name),
1160
+ bound
1161
+ );
1136
1162
  const args = headers.map((header) => {
1137
1163
  const reader = headerReader(header, place);
1138
1164
  const name = quoteCSharpString(header.name);
1139
1165
  if (!header.optional) return reader.read(`http.RequireHeader(response, ${name})`);
1140
- const local = toCSharpParameterName(header.name);
1166
+ const local = locals.get(header.name);
1141
1167
  return `response.Header(${name}) is { } ${local} ? ${reader.read(local)} : null`;
1142
1168
  });
1143
1169
  const lines = [`${indent}var headers = new ${typeName}(`];
@@ -1160,22 +1186,30 @@ function httpMethodConstant(method) {
1160
1186
  return lower.charAt(0).toUpperCase() + lower.slice(1);
1161
1187
  }
1162
1188
  var PATH_PLACEHOLDER = /\{([a-zA-Z_$][a-zA-Z0-9_$.-]*)\}/g;
1163
- function buildPathExpression(path, params) {
1189
+ function buildPathExpression(path, params, bindings) {
1164
1190
  const args = path.split("/").filter(Boolean).map((raw) => {
1165
1191
  PATH_PLACEHOLDER.lastIndex = 0;
1166
1192
  const match = PATH_PLACEHOLDER.exec(raw);
1167
1193
  if (!match || match[0] !== raw) return quoteCSharpString(raw);
1168
- const value = params && params.kind !== "params" ? `pathParams.${toCSharpPropertyName(match[1])}` : toCSharpParameterName(match[1]);
1194
+ const value = params && params.kind !== "params" ? `pathParams.${toCSharpPropertyName(match[1])}` : bindings?.get(match[1]) ?? toCSharpParameterName(match[1]);
1169
1195
  return `http.Segment(${value})`;
1170
1196
  });
1171
1197
  return `http.Path(${args.join(", ")})`;
1172
1198
  }
1173
- function buildMethodParams(route, op, ctx) {
1199
+ var METHOD_LOCALS = ["body", "query", "customHeaders", "pathParams", "cancellationToken", "response", "headers", "http"];
1200
+ function bindPathParams(route) {
1201
+ if (route.params?.kind !== "params") return /* @__PURE__ */ new Map();
1202
+ return bindCSharpParameterNames(
1203
+ route.params.nodes.map((n) => n.name),
1204
+ METHOD_LOCALS
1205
+ );
1206
+ }
1207
+ function buildMethodParams(route, op, ctx, pathBindings) {
1174
1208
  const params = [];
1175
1209
  if (route.params) {
1176
1210
  if (route.params.kind === "params") {
1177
1211
  for (const node of route.params.nodes) {
1178
- params.push({ name: toCSharpParameterName(node.name), type: renderCSharpType(node.type, ctx, true), optional: false });
1212
+ params.push({ name: pathBindings.get(node.name), type: renderCSharpType(node.type, ctx, true), optional: false });
1179
1213
  }
1180
1214
  } else {
1181
1215
  params.push({ name: "pathParams", type: renderParamSourceType(route.params, ctx, ""), optional: false });