@contractkit/plugin-csharp 0.1.3 → 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.
- package/.turbo/turbo-build$colon$ci.log +4 -4
- package/.turbo/turbo-test$colon$ci.log +16 -16
- package/CHANGELOG.md +10 -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/index.js +54 -25
- 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 +1 -1
- package/src/codegen-client.ts +96 -25
- package/src/naming.ts +23 -0
- package/tests/codegen-client.test.ts +103 -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.63 KB[39m
|
|
9
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m193.78 KB[39m
|
|
10
|
+
[32mESM[39m ⚡️ Build success in 154ms
|
|
11
11
|
[34mDTS[39m Build start
|
|
12
|
-
[32mDTS[39m ⚡️ Build success in
|
|
12
|
+
[32mDTS[39m ⚡️ Build success in 6453ms
|
|
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 110[2mms[22m[39m
|
|
7
|
+
[32m✓[39m tests/codegen-client.test.ts [2m([22m[2m40 tests[22m[2m)[22m[33m 305[2mms[22m[39m
|
|
8
|
+
[32m✓[39m tests/codegen-models.test.ts [2m([22m[2m65 tests[22m[2m)[22m[33m 306[2mms[22m[39m
|
|
9
|
+
[32m✓[39m tests/index.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 199[2mms[22m[39m
|
|
10
|
+
[32m✓[39m tests/runtime.test.ts [2m([22m[2m14 tests[22m[2m)[22m[32m 65[2mms[22m[39m
|
|
11
|
+
[32m✓[39m tests/scaffold.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 46[2mms[22m[39m
|
|
12
|
+
[32m✓[39m tests/hoist.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 54[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 13.
|
|
15
|
+
[2m Tests [22m [1m[32m167 passed[39m[22m[90m (167)[39m
|
|
16
|
+
[2m Start at [22m 17:55:57
|
|
17
|
+
[2m Duration [22m 13.21s[2m (transform 4.92s, setup 0ms, import 21.10s, tests 1.08s, environment 1ms)[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 |
|
|
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
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 |
|
|
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,15 @@
|
|
|
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
|
+
|
|
3
13
|
## 0.1.3
|
|
4
14
|
|
|
5
15
|
### 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"}
|
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;
|
|
@@ -897,10 +908,12 @@ function observableOf(shape) {
|
|
|
897
908
|
function generateMethod(route, op, ctx, methodName) {
|
|
898
909
|
const base = methodBase(methodName);
|
|
899
910
|
const shape = responseShape(op);
|
|
900
|
-
const returnType = returnTypeFor(shape, base, ctx);
|
|
911
|
+
const returnType = returnTypeFor(shape, op, base, ctx);
|
|
901
912
|
const observable = observableOf(shape);
|
|
902
913
|
const expectStatuses = observable.filter((r) => r.statusCode < 200 || r.statusCode >= 300).map((r) => r.statusCode);
|
|
903
|
-
const
|
|
914
|
+
const pathBindings = bindPathParams(route);
|
|
915
|
+
const params = buildMethodParams(route, op, ctx, pathBindings);
|
|
916
|
+
const bound = [...METHOD_LOCALS, ...params.map((p) => p.name.replace(/^@/, ""))];
|
|
904
917
|
const signature = [...params.map((p) => `${p.type} ${p.name}${p.optional ? " = null" : ""}`), "CancellationToken cancellationToken = default"].join(
|
|
905
918
|
", "
|
|
906
919
|
);
|
|
@@ -909,7 +922,7 @@ function generateMethod(route, op, ctx, methodName) {
|
|
|
909
922
|
if (resolveModifiers(route, op).includes("deprecated")) lines.push('[Obsolete("Deprecated in the contract")]');
|
|
910
923
|
lines.push(`public async ${returnType === "void" ? "Task" : `Task<${returnType}>`} ${methodName}(${signature})`);
|
|
911
924
|
lines.push("{");
|
|
912
|
-
const callArgs = [`HttpMethod.${httpMethodConstant(op.method)}`, buildPathExpression(route.path, route.params)];
|
|
925
|
+
const callArgs = [`HttpMethod.${httpMethodConstant(op.method)}`, buildPathExpression(route.path, route.params, pathBindings)];
|
|
913
926
|
if (op.query) callArgs.push("query: http.Params(query)");
|
|
914
927
|
if (op.headers) callArgs.push("headers: http.Params(customHeaders)");
|
|
915
928
|
const content = bodyArgument(op);
|
|
@@ -921,16 +934,16 @@ function generateMethod(route, op, ctx, methodName) {
|
|
|
921
934
|
callArgs.forEach((arg, index) => {
|
|
922
935
|
lines.push(` ${arg}${index === callArgs.length - 1 ? ").ConfigureAwait(false);" : ","}`);
|
|
923
936
|
});
|
|
924
|
-
lines.push(...returnStatements(shape, base, ctx, where(route, op)));
|
|
937
|
+
lines.push(...returnStatements(shape, op, base, ctx, where(route, op), bound));
|
|
925
938
|
lines.push("}");
|
|
926
939
|
return lines;
|
|
927
940
|
}
|
|
928
|
-
function returnTypeFor(shape, base, ctx) {
|
|
941
|
+
function returnTypeFor(shape, op, base, ctx) {
|
|
929
942
|
if (shape.kind !== "simple") return `${base}Response`;
|
|
930
943
|
const response = shape.response;
|
|
931
944
|
const body = response?.bodies[0];
|
|
932
945
|
const headers = response?.headers ?? [];
|
|
933
|
-
if (!body) return headers.length > 0 ?
|
|
946
|
+
if (!body) return headers.length > 0 ? headersRecordName(op, base) : "void";
|
|
934
947
|
const dataType = bodyCSharpType(body, ctx);
|
|
935
948
|
return headers.length > 0 ? `${base}Result` : dataType;
|
|
936
949
|
}
|
|
@@ -954,18 +967,18 @@ function bodyReadExpr(body, ctx) {
|
|
|
954
967
|
return `http.ReadJson<${renderCSharpType(body.bodyType, ctx, false)}>(response)`;
|
|
955
968
|
}
|
|
956
969
|
}
|
|
957
|
-
function returnStatements(shape, base, ctx, place) {
|
|
970
|
+
function returnStatements(shape, op, base, ctx, place, bound) {
|
|
958
971
|
if (shape.kind === "simple") {
|
|
959
972
|
const response = shape.response;
|
|
960
973
|
const body = response?.bodies[0];
|
|
961
974
|
const headers = response?.headers ?? [];
|
|
962
975
|
if (headers.length === 0) return body ? [` return ${bodyReadExpr(body, ctx)};`] : [];
|
|
963
|
-
const lines2 = readHeaderLines(headers,
|
|
976
|
+
const lines2 = readHeaderLines(headers, headersRecordName(op, base), ctx, place, " ", bound);
|
|
964
977
|
return body ? [...lines2, ` return new ${base}Result(${bodyReadExpr(body, ctx)}, headers);`] : [...lines2, " return headers;"];
|
|
965
978
|
}
|
|
966
979
|
if (shape.kind === "multiMime") {
|
|
967
980
|
const headers = shape.response.headers ?? [];
|
|
968
|
-
const lines2 = headers.length > 0 ? readHeaderLines(headers,
|
|
981
|
+
const lines2 = headers.length > 0 ? readHeaderLines(headers, headersRecordName(op, base), ctx, place, " ", bound) : [];
|
|
969
982
|
lines2.push(...mimeSwitch(shape.response, base, void 0, ctx, " ", headers.length > 0));
|
|
970
983
|
return lines2;
|
|
971
984
|
}
|
|
@@ -974,21 +987,21 @@ function returnStatements(shape, base, ctx, place) {
|
|
|
974
987
|
for (const response of rest) {
|
|
975
988
|
lines.push(` case ${response.statusCode}:`);
|
|
976
989
|
lines.push(" {");
|
|
977
|
-
lines.push(...statusBranch(response, base, response.statusCode, ctx, place, " "));
|
|
990
|
+
lines.push(...statusBranch(response, op, base, response.statusCode, ctx, place, " ", bound));
|
|
978
991
|
lines.push(" }");
|
|
979
992
|
lines.push("");
|
|
980
993
|
}
|
|
981
994
|
lines.push(" default:");
|
|
982
995
|
lines.push(" {");
|
|
983
|
-
lines.push(...statusBranch(fallback, base, fallback.statusCode, ctx, place, " "));
|
|
996
|
+
lines.push(...statusBranch(fallback, op, base, fallback.statusCode, ctx, place, " ", bound));
|
|
984
997
|
lines.push(" }");
|
|
985
998
|
lines.push(" }");
|
|
986
999
|
return lines;
|
|
987
1000
|
}
|
|
988
|
-
function statusBranch(response, base, statusCode, ctx, place, indent) {
|
|
1001
|
+
function statusBranch(response, op, base, statusCode, ctx, place, indent, bound) {
|
|
989
1002
|
const lines = [];
|
|
990
1003
|
const headers = response.headers ?? [];
|
|
991
|
-
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));
|
|
992
1005
|
lines.push(...mimeSwitch(response, base, statusCode, ctx, indent, headers.length > 0));
|
|
993
1006
|
return lines;
|
|
994
1007
|
}
|
|
@@ -1029,8 +1042,12 @@ function bodyArgument(op) {
|
|
|
1029
1042
|
return `content: http.JsonContent(body, ${mime})`;
|
|
1030
1043
|
}
|
|
1031
1044
|
}
|
|
1032
|
-
function headersRecordName(base, statusCode) {
|
|
1033
|
-
|
|
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;
|
|
1034
1051
|
}
|
|
1035
1052
|
function leafRecordName(response, body, statusCode) {
|
|
1036
1053
|
const statusPart = statusCode === void 0 ? "" : `Status${statusCode}`;
|
|
@@ -1056,12 +1073,12 @@ function responseDeclarations(route, op, ctx) {
|
|
|
1056
1073
|
const response = shape.response;
|
|
1057
1074
|
const headers = response?.headers ?? [];
|
|
1058
1075
|
if (headers.length === 0) return lines;
|
|
1059
|
-
headerRecord(headers, headersRecordName(base));
|
|
1076
|
+
headerRecord(headers, headersRecordName(op, base));
|
|
1060
1077
|
const body = response?.bodies[0];
|
|
1061
1078
|
if (body) {
|
|
1062
1079
|
lines.push("");
|
|
1063
1080
|
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);`);
|
|
1081
|
+
lines.push(`public sealed record ${base}Result(${bodyCSharpType(body, ctx)} Data, ${headersRecordName(op, base)} Headers);`);
|
|
1065
1082
|
}
|
|
1066
1083
|
return lines;
|
|
1067
1084
|
}
|
|
@@ -1069,7 +1086,7 @@ function responseDeclarations(route, op, ctx) {
|
|
|
1069
1086
|
const withStatus = shape.kind === "multiStatus";
|
|
1070
1087
|
for (const response of responses) {
|
|
1071
1088
|
const headers = response.headers ?? [];
|
|
1072
|
-
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));
|
|
1073
1090
|
}
|
|
1074
1091
|
lines.push("");
|
|
1075
1092
|
lines.push(
|
|
@@ -1091,7 +1108,7 @@ function responseDeclarations(route, op, ctx) {
|
|
|
1091
1108
|
const name = leafRecordName(response, body, statusCode);
|
|
1092
1109
|
const parameters = [];
|
|
1093
1110
|
if (body) parameters.push(`${bodyCSharpType(body, ctx)} Data`);
|
|
1094
|
-
if (headers.length > 0) parameters.push(`${headersRecordName(base, statusCode)} Headers`);
|
|
1111
|
+
if (headers.length > 0) parameters.push(`${headersRecordName(op, base, statusCode)} Headers`);
|
|
1095
1112
|
lines.push("");
|
|
1096
1113
|
lines.push(` public sealed record ${name}(${parameters.join(", ")}) : ${base}Response;`);
|
|
1097
1114
|
}
|
|
@@ -1137,12 +1154,16 @@ function describeHeaderType(type) {
|
|
|
1137
1154
|
if (type.kind === "ref") return `the contract '${type.name}'`;
|
|
1138
1155
|
return `${type.kind === "array" || type.kind === "inlineObject" ? "an" : "a"} ${type.kind}`;
|
|
1139
1156
|
}
|
|
1140
|
-
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
|
+
);
|
|
1141
1162
|
const args = headers.map((header) => {
|
|
1142
1163
|
const reader = headerReader(header, place);
|
|
1143
1164
|
const name = quoteCSharpString(header.name);
|
|
1144
1165
|
if (!header.optional) return reader.read(`http.RequireHeader(response, ${name})`);
|
|
1145
|
-
const local =
|
|
1166
|
+
const local = locals.get(header.name);
|
|
1146
1167
|
return `response.Header(${name}) is { } ${local} ? ${reader.read(local)} : null`;
|
|
1147
1168
|
});
|
|
1148
1169
|
const lines = [`${indent}var headers = new ${typeName}(`];
|
|
@@ -1165,22 +1186,30 @@ function httpMethodConstant(method) {
|
|
|
1165
1186
|
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
1166
1187
|
}
|
|
1167
1188
|
var PATH_PLACEHOLDER = /\{([a-zA-Z_$][a-zA-Z0-9_$.-]*)\}/g;
|
|
1168
|
-
function buildPathExpression(path, params) {
|
|
1189
|
+
function buildPathExpression(path, params, bindings) {
|
|
1169
1190
|
const args = path.split("/").filter(Boolean).map((raw) => {
|
|
1170
1191
|
PATH_PLACEHOLDER.lastIndex = 0;
|
|
1171
1192
|
const match = PATH_PLACEHOLDER.exec(raw);
|
|
1172
1193
|
if (!match || match[0] !== raw) return quoteCSharpString(raw);
|
|
1173
|
-
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]);
|
|
1174
1195
|
return `http.Segment(${value})`;
|
|
1175
1196
|
});
|
|
1176
1197
|
return `http.Path(${args.join(", ")})`;
|
|
1177
1198
|
}
|
|
1178
|
-
|
|
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) {
|
|
1179
1208
|
const params = [];
|
|
1180
1209
|
if (route.params) {
|
|
1181
1210
|
if (route.params.kind === "params") {
|
|
1182
1211
|
for (const node of route.params.nodes) {
|
|
1183
|
-
params.push({ name:
|
|
1212
|
+
params.push({ name: pathBindings.get(node.name), type: renderCSharpType(node.type, ctx, true), optional: false });
|
|
1184
1213
|
}
|
|
1185
1214
|
} else {
|
|
1186
1215
|
params.push({ name: "pathParams", type: renderParamSourceType(route.params, ctx, ""), optional: false });
|