@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.
- package/.turbo/turbo-build$colon$ci.log +4 -4
- package/.turbo/turbo-test$colon$ci.log +17 -17
- package/CHANGELOG.md +28 -0
- package/README.md +2 -1
- package/dist/codegen-client.d.ts +1 -1
- package/dist/codegen-client.d.ts.map +1 -1
- package/dist/codegen-models.d.ts.map +1 -1
- package/dist/index.js +61 -27
- package/dist/index.js.map +1 -1
- package/dist/naming.d.ts +12 -0
- package/dist/naming.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/codegen-client.ts +96 -25
- package/src/codegen-models.ts +17 -4
- package/src/naming.ts +23 -0
- package/tests/codegen-client.test.ts +103 -0
- package/tests/codegen-models.test.ts +9 -0
- package/tests/naming.test.ts +26 -0
|
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
|
|
|
5
5
|
[34mCLI[39m tsup v8.5.1
|
|
6
6
|
[34mCLI[39m Target: esnext
|
|
7
7
|
[34mESM[39m Build start
|
|
8
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
9
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
10
|
-
[32mESM[39m ⚡️ Build success in
|
|
8
|
+
[32mESM[39m [1mdist/index.js [22m[32m88.92 KB[39m
|
|
9
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m194.67 KB[39m
|
|
10
|
+
[32mESM[39m ⚡️ Build success in 108ms
|
|
11
11
|
[34mDTS[39m Build start
|
|
12
|
-
[32mDTS[39m ⚡️ Build success in
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 5348ms
|
|
13
13
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.50 KB[39m
|
|
@@ -3,34 +3,34 @@ $ vitest run --coverage
|
|
|
3
3
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.5 [39m[90m/home/runner/work/ContractKit/ContractKit/packages/plugin-csharp[39m
|
|
4
4
|
[2mCoverage enabled with [22m[33mv8[39m
|
|
5
5
|
|
|
6
|
-
[32m✓[39m tests/
|
|
7
|
-
[32m✓[39m tests/codegen-
|
|
8
|
-
[32m✓[39m tests/
|
|
9
|
-
[32m✓[39m tests/
|
|
10
|
-
[32m✓[39m tests/runtime.test.ts [2m([22m[2m14 tests[22m[2m)[22m[32m
|
|
11
|
-
[32m✓[39m tests/scaffold.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
12
|
-
[32m✓[39m tests/hoist.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
6
|
+
[32m✓[39m tests/naming.test.ts [2m([22m[2m23 tests[22m[2m)[22m[32m 51[2mms[22m[39m
|
|
7
|
+
[32m✓[39m tests/codegen-client.test.ts [2m([22m[2m40 tests[22m[2m)[22m[32m 189[2mms[22m[39m
|
|
8
|
+
[32m✓[39m tests/codegen-models.test.ts [2m([22m[2m66 tests[22m[2m)[22m[32m 210[2mms[22m[39m
|
|
9
|
+
[32m✓[39m tests/index.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 70[2mms[22m[39m
|
|
10
|
+
[32m✓[39m tests/runtime.test.ts [2m([22m[2m14 tests[22m[2m)[22m[32m 28[2mms[22m[39m
|
|
11
|
+
[32m✓[39m tests/scaffold.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 43[2mms[22m[39m
|
|
12
|
+
[32m✓[39m tests/hoist.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 25[2mms[22m[39m
|
|
13
13
|
|
|
14
14
|
[2m Test Files [22m [1m[32m7 passed[39m[22m[90m (7)[39m
|
|
15
|
-
[2m Tests [22m [1m[
|
|
16
|
-
[2m Start at [22m
|
|
17
|
-
[2m Duration [22m
|
|
15
|
+
[2m Tests [22m [1m[32m168 passed[39m[22m[90m (168)[39m
|
|
16
|
+
[2m Start at [22m 19:26:13
|
|
17
|
+
[2m Duration [22m 9.80s[2m (transform 5.17s, setup 0ms, import 16.95s, tests 615ms, environment 12ms)[22m
|
|
18
18
|
|
|
19
19
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
20
20
|
-------------------|---------|----------|---------|---------|-------------------
|
|
21
21
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
22
22
|
-------------------|---------|----------|---------|---------|-------------------
|
|
23
|
-
All files | 88.
|
|
24
|
-
src | 88.
|
|
25
|
-
...gen-client.ts |
|
|
26
|
-
...gen-models.ts | 91.
|
|
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 |
|
|
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 |
|
|
35
|
-
helpers.ts | 86.95 |
|
|
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.
|
package/dist/codegen-client.d.ts
CHANGED
|
@@ -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;
|
|
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,
|
|
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
|
|
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
|
|
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 ?
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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 });
|