@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.
- package/.turbo/turbo-build$colon$ci.log +4 -4
- package/.turbo/turbo-test$colon$ci.log +17 -17
- package/CHANGELOG.md +21 -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 +67 -33
- 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/codegen-models.ts +34 -8
- package/src/naming.ts +23 -0
- package/tests/codegen-client.test.ts +103 -0
- package/tests/codegen-models.test.ts +15 -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/
|
|
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
|
|
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 |
|
|
24
|
-
src | 88.
|
|
25
|
-
...gen-client.ts |
|
|
26
|
-
...gen-models.ts | 91.
|
|
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 |
|
|
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,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.
|
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,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;
|
|
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
|
|
568
|
-
|
|
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
|
|
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 ?
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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 });
|