@contractkit/plugin-csharp 0.1.5 → 0.2.0
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 +5 -5
- package/.turbo/turbo-test$colon$ci.log +16 -15
- package/CHANGELOG.md +28 -0
- package/README.md +86 -13
- package/dist/codegen-client.d.ts +3 -0
- package/dist/codegen-client.d.ts.map +1 -1
- package/dist/codegen-models.d.ts +15 -0
- package/dist/codegen-models.d.ts.map +1 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +566 -19
- package/dist/index.js.map +1 -1
- package/dist/runtime-converters.d.ts +8 -2
- package/dist/runtime-converters.d.ts.map +1 -1
- package/dist/runtime-polyfills.d.ts +20 -0
- package/dist/runtime-polyfills.d.ts.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/scaffold.d.ts +18 -4
- package/dist/scaffold.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/codegen-client.ts +19 -9
- package/src/codegen-models.ts +53 -2
- package/src/index.ts +74 -5
- package/src/runtime-converters.ts +135 -4
- package/src/runtime-polyfills.ts +352 -0
- package/src/runtime.ts +24 -0
- package/src/scaffold.ts +45 -6
- package/tests/codegen-client.test.ts +38 -4
- package/tests/codegen-models.test.ts +40 -3
- package/tests/index.test.ts +43 -0
- package/tests/runtime.test.ts +83 -1
- package/tests/scaffold.test.ts +22 -0
package/dist/index.js
CHANGED
|
@@ -189,6 +189,7 @@ function generateCSharpModels(root, opts) {
|
|
|
189
189
|
const modelIndex = opts.modelIndex ?? buildModelIndex(root.models);
|
|
190
190
|
const ctx = {
|
|
191
191
|
namespace: opts.namespace,
|
|
192
|
+
dateTypes: opts.dateTypes ?? "dateonly",
|
|
192
193
|
modelsWithInput,
|
|
193
194
|
modelIndex,
|
|
194
195
|
hoisted: opts.hoisted,
|
|
@@ -202,7 +203,7 @@ function generateCSharpModels(root, opts) {
|
|
|
202
203
|
};
|
|
203
204
|
for (const model of topoSortModels(root.models)) append(generateModel(model, ctx));
|
|
204
205
|
for (const decl of opts.hoisted?.byFile.get(root.file) ?? []) append(generateHoisted(decl, ctx));
|
|
205
|
-
return renderFile(`${opts.namespace}.Models`, ctx.globalAliases, [...MODEL_USINGS], bodies);
|
|
206
|
+
return renderFile(`${opts.namespace}.Models`, ctx.globalAliases, [...MODEL_USINGS, `using ${opts.namespace}.Runtime;`], bodies);
|
|
206
207
|
}
|
|
207
208
|
function resolveModelsWithInput(models, external = /* @__PURE__ */ new Set()) {
|
|
208
209
|
const seed = new Set(external);
|
|
@@ -211,6 +212,7 @@ function resolveModelsWithInput(models, external = /* @__PURE__ */ new Set()) {
|
|
|
211
212
|
function createRenderContext(opts) {
|
|
212
213
|
return {
|
|
213
214
|
namespace: opts.namespace,
|
|
215
|
+
dateTypes: opts.dateTypes ?? "dateonly",
|
|
214
216
|
modelsWithInput: opts.modelsWithInput,
|
|
215
217
|
modelIndex: opts.modelIndex ?? /* @__PURE__ */ new Map(),
|
|
216
218
|
hoisted: opts.hoisted,
|
|
@@ -310,8 +312,9 @@ function renderScalar(name, ctx) {
|
|
|
310
312
|
return qualify("decimal", "System.Decimal", ctx);
|
|
311
313
|
case "boolean":
|
|
312
314
|
return qualify("bool", "System.Boolean", ctx);
|
|
315
|
+
// Both carried as the wire form by a converter: `yyyy-MM-dd` and `HH:mm:ss`.
|
|
313
316
|
case "date":
|
|
314
|
-
return qualify("DateOnly", "System.DateOnly", ctx);
|
|
317
|
+
return ctx.dateTypes === "datetime" ? qualify("DateTime", "System.DateTime", ctx) : qualify("DateOnly", "System.DateOnly", ctx);
|
|
315
318
|
case "time":
|
|
316
319
|
return qualify("TimeOnly", "System.TimeOnly", ctx);
|
|
317
320
|
case "datetime":
|
|
@@ -506,8 +509,25 @@ function addAlias(name, type, ctx, forInput) {
|
|
|
506
509
|
`Contract '${name}' aliases a nullable type, which C# cannot express as a using alias; '${name}' is generated as '${aliased}'. Declare the nullability at each use site instead.`
|
|
507
510
|
);
|
|
508
511
|
}
|
|
512
|
+
const polyfilled = polyfillAliasTarget(aliased, ctx);
|
|
513
|
+
if (polyfilled) {
|
|
514
|
+
ctx.globalAliases.push(`#if NETSTANDARD2_0
|
|
515
|
+
global using ${name} = ${polyfilled};
|
|
516
|
+
#else
|
|
517
|
+
global using ${name} = ${aliased};
|
|
518
|
+
#endif`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
509
521
|
ctx.globalAliases.push(`global using ${name} = ${aliased};`);
|
|
510
522
|
}
|
|
523
|
+
var POLYFILLED_TYPES = ["System.DateOnly", "System.TimeOnly"];
|
|
524
|
+
function polyfillAliasTarget(target, ctx) {
|
|
525
|
+
let out = target;
|
|
526
|
+
for (const full of POLYFILLED_TYPES) {
|
|
527
|
+
out = out.split(full).join(`${ctx.namespace}.Runtime.${full.slice("System.".length)}`);
|
|
528
|
+
}
|
|
529
|
+
return out === target ? void 0 : out;
|
|
530
|
+
}
|
|
511
531
|
function isNullableValueType(type, ctx) {
|
|
512
532
|
const inner = type.kind === "lazy" ? type.inner : type;
|
|
513
533
|
if (inner.kind !== "union") return false;
|
|
@@ -524,6 +544,7 @@ var VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
|
524
544
|
"BigInteger",
|
|
525
545
|
"DateOnly",
|
|
526
546
|
"TimeOnly",
|
|
547
|
+
"DateTime",
|
|
527
548
|
"DateTimeOffset",
|
|
528
549
|
"TimeSpan",
|
|
529
550
|
"Guid",
|
|
@@ -927,7 +948,7 @@ function generateMethod(route, op, ctx, methodName) {
|
|
|
927
948
|
if (resolveModifiers(route, op).includes("deprecated")) lines.push('[Obsolete("Deprecated in the contract")]');
|
|
928
949
|
lines.push(`public async ${returnType === "void" ? "Task" : `Task<${returnType}>`} ${methodName}(${signature})`);
|
|
929
950
|
lines.push("{");
|
|
930
|
-
const callArgs = [
|
|
951
|
+
const callArgs = [httpMethodExpression(op.method), buildPathExpression(route.path, route.params, pathBindings)];
|
|
931
952
|
if (op.query) callArgs.push("query: http.Params(query)");
|
|
932
953
|
if (op.headers) callArgs.push("headers: http.Params(customHeaders)");
|
|
933
954
|
const content = bodyArgument(op);
|
|
@@ -1066,7 +1087,7 @@ function responseDeclarations(route, op, ctx) {
|
|
|
1066
1087
|
const lines = [];
|
|
1067
1088
|
const headerRecord = (headers, name) => {
|
|
1068
1089
|
const parameters = headers.map((header) => {
|
|
1069
|
-
const reader = headerReader(header, place);
|
|
1090
|
+
const reader = headerReader(header, place, ctx.dateTypes);
|
|
1070
1091
|
const type = header.optional ? `${reader.type}?` : reader.type;
|
|
1071
1092
|
return `${type} ${safeMemberName(toCSharpPropertyName(header.name), name)}`;
|
|
1072
1093
|
}).join(", ");
|
|
@@ -1121,7 +1142,7 @@ function responseDeclarations(route, op, ctx) {
|
|
|
1121
1142
|
lines.push("}");
|
|
1122
1143
|
return lines;
|
|
1123
1144
|
}
|
|
1124
|
-
function headerReader(header, place) {
|
|
1145
|
+
function headerReader(header, place, dateTypes) {
|
|
1125
1146
|
const scalar = header.type.kind === "scalar" ? header.type.name : void 0;
|
|
1126
1147
|
switch (scalar) {
|
|
1127
1148
|
case "string":
|
|
@@ -1141,7 +1162,7 @@ function headerReader(header, place) {
|
|
|
1141
1162
|
case "uuid":
|
|
1142
1163
|
return { type: "Guid", read: (raw) => `Guid.Parse(${raw})` };
|
|
1143
1164
|
case "date":
|
|
1144
|
-
return { type: "DateOnly", read: (raw) => `DateOnly.Parse(${raw}, CultureInfo.InvariantCulture)` };
|
|
1165
|
+
return dateTypes === "datetime" ? { type: "DateTime", read: (raw) => `DateTime.Parse(${raw}, CultureInfo.InvariantCulture)` } : { type: "DateOnly", read: (raw) => `DateOnly.Parse(${raw}, CultureInfo.InvariantCulture)` };
|
|
1145
1166
|
case "time":
|
|
1146
1167
|
return { type: "TimeOnly", read: (raw) => `TimeOnly.Parse(${raw}, CultureInfo.InvariantCulture)` };
|
|
1147
1168
|
case "datetime":
|
|
@@ -1165,7 +1186,7 @@ function readHeaderLines(headers, typeName, ctx, place, indent, bound) {
|
|
|
1165
1186
|
bound
|
|
1166
1187
|
);
|
|
1167
1188
|
const args = headers.map((header) => {
|
|
1168
|
-
const reader = headerReader(header, place);
|
|
1189
|
+
const reader = headerReader(header, place, ctx.dateTypes);
|
|
1169
1190
|
const name = quoteCSharpString(header.name);
|
|
1170
1191
|
if (!header.optional) return reader.read(`http.RequireHeader(response, ${name})`);
|
|
1171
1192
|
const local = locals.get(header.name);
|
|
@@ -1186,9 +1207,10 @@ function methodDoc(route, op, observable) {
|
|
|
1186
1207
|
if (thrown.length > 0) lines.push(`/// <exception cref="SdkException">On ${thrown.join(", ")}.</exception>`);
|
|
1187
1208
|
return lines;
|
|
1188
1209
|
}
|
|
1189
|
-
function
|
|
1210
|
+
function httpMethodExpression(method) {
|
|
1190
1211
|
const lower = method.toLowerCase();
|
|
1191
|
-
|
|
1212
|
+
if (lower === "patch") return "SdkHttp.Patch";
|
|
1213
|
+
return `HttpMethod.${lower.charAt(0).toUpperCase()}${lower.slice(1)}`;
|
|
1192
1214
|
}
|
|
1193
1215
|
var PATH_PLACEHOLDER = /\{([a-zA-Z_$][a-zA-Z0-9_$.-]*)\}/g;
|
|
1194
1216
|
function buildPathExpression(path, params, bindings) {
|
|
@@ -1624,7 +1646,13 @@ public sealed class SdkOptions
|
|
|
1624
1646
|
public class SdkException : HttpRequestException
|
|
1625
1647
|
{
|
|
1626
1648
|
public SdkException(int status, string body, HttpResponseHeaders? responseHeaders = null, string? message = null)
|
|
1649
|
+
#if NETSTANDARD2_0
|
|
1650
|
+
// .NET Standard 2.0's HttpRequestException carries no status of its own. Status below does,
|
|
1651
|
+
// so nothing is lost but the base type's own StatusCode property.
|
|
1652
|
+
: base(message ?? $"Request failed with status {status}")
|
|
1653
|
+
#else
|
|
1627
1654
|
: base(message ?? $"Request failed with status {status}", null, ToStatusCode(status))
|
|
1655
|
+
#endif
|
|
1628
1656
|
{
|
|
1629
1657
|
Status = status;
|
|
1630
1658
|
Body = body;
|
|
@@ -1683,8 +1711,10 @@ public class SdkException : HttpRequestException
|
|
|
1683
1711
|
}
|
|
1684
1712
|
}
|
|
1685
1713
|
|
|
1714
|
+
#if !NETSTANDARD2_0
|
|
1686
1715
|
private static HttpStatusCode? ToStatusCode(int status) =>
|
|
1687
1716
|
status is >= 100 and <= 599 ? (HttpStatusCode)status : null;
|
|
1717
|
+
#endif
|
|
1688
1718
|
}
|
|
1689
1719
|
|
|
1690
1720
|
/// <summary>
|
|
@@ -1772,6 +1802,16 @@ public sealed class SdkHttp : IDisposable
|
|
|
1772
1802
|
|
|
1773
1803
|
public JsonSerializerOptions Json { get; }
|
|
1774
1804
|
|
|
1805
|
+
/// <summary>
|
|
1806
|
+
/// The PATCH verb.
|
|
1807
|
+
/// </summary>
|
|
1808
|
+
/// <remarks>
|
|
1809
|
+
/// <c>HttpMethod</c> carries a static for every other verb a contract can declare, but not for
|
|
1810
|
+
/// this one on every framework the SDK builds against, so it is spelled once here rather than
|
|
1811
|
+
/// allocated per call.
|
|
1812
|
+
/// </remarks>
|
|
1813
|
+
public static readonly HttpMethod Patch = new HttpMethod("PATCH");
|
|
1814
|
+
|
|
1775
1815
|
/// <summary>
|
|
1776
1816
|
/// Send one request and read its body.
|
|
1777
1817
|
/// </summary>
|
|
@@ -1810,7 +1850,13 @@ public sealed class SdkHttp : IDisposable
|
|
|
1810
1850
|
}
|
|
1811
1851
|
|
|
1812
1852
|
var message = await Client.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false);
|
|
1853
|
+
#if NETSTANDARD2_0
|
|
1854
|
+
// The cancellable overload arrived in .NET 5. ResponseContentRead above has already buffered
|
|
1855
|
+
// the body, so this read is a copy rather than a wait on the network.
|
|
1856
|
+
var bytes = await message.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
|
|
1857
|
+
#else
|
|
1813
1858
|
var bytes = await message.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
|
1859
|
+
#endif
|
|
1814
1860
|
var response = new SdkResponse(message, bytes);
|
|
1815
1861
|
|
|
1816
1862
|
var status = response.Status;
|
|
@@ -1943,7 +1989,78 @@ public sealed class SdkHttp : IDisposable
|
|
|
1943
1989
|
}
|
|
1944
1990
|
|
|
1945
1991
|
// src/runtime-converters.ts
|
|
1946
|
-
|
|
1992
|
+
var ISO_DATE_CONVERTER = `/// <summary>
|
|
1993
|
+
/// A calendar date, as <c>yyyy-MM-dd</c>, carried in the date part of a <c>DateTime</c>.
|
|
1994
|
+
/// </summary>
|
|
1995
|
+
/// <remarks>
|
|
1996
|
+
/// The time is midnight and the kind is unspecified: a contract's <c>date</c> names neither, and
|
|
1997
|
+
/// pretending to either would put a zone offset on the wire.
|
|
1998
|
+
/// </remarks>
|
|
1999
|
+
public sealed class IsoDateConverter : JsonConverter<DateTime>
|
|
2000
|
+
{
|
|
2001
|
+
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
2002
|
+
{
|
|
2003
|
+
if (reader.TokenType != JsonTokenType.String)
|
|
2004
|
+
{
|
|
2005
|
+
throw new JsonException($"Expected a date string, got {reader.TokenType}.");
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
var text = reader.GetString() ?? throw new JsonException("Expected a date string.");
|
|
2009
|
+
if (DateTime.TryParseExact(text, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var exact))
|
|
2010
|
+
{
|
|
2011
|
+
return exact;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// A service sending more than the contract promised is read for the part it promised.
|
|
2015
|
+
if (DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed))
|
|
2016
|
+
{
|
|
2017
|
+
return parsed.Date;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
throw new JsonException($"'{text}' is not a date.");
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
|
2024
|
+
{
|
|
2025
|
+
writer.WriteStringValue(value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
`;
|
|
2030
|
+
var DATE_ONLY_CONVERTER = `
|
|
2031
|
+
/// <summary>
|
|
2032
|
+
/// A calendar date, as <c>yyyy-MM-dd</c>.
|
|
2033
|
+
/// </summary>
|
|
2034
|
+
/// <remarks>
|
|
2035
|
+
/// Compiled only where <c>DateOnly</c> is the SDK's own polyfill. The wire form is the one the
|
|
2036
|
+
/// framework's converter writes on net10.0, so a service reads a body from either leg of the build.
|
|
2037
|
+
/// </remarks>
|
|
2038
|
+
public sealed class DateOnlyConverter : JsonConverter<DateOnly>
|
|
2039
|
+
{
|
|
2040
|
+
public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
2041
|
+
{
|
|
2042
|
+
if (reader.TokenType != JsonTokenType.String)
|
|
2043
|
+
{
|
|
2044
|
+
throw new JsonException($"Expected a date string, got {reader.TokenType}.");
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
var text = reader.GetString() ?? throw new JsonException("Expected a date string.");
|
|
2048
|
+
if (!DateOnly.TryParse(text, CultureInfo.InvariantCulture, out var value))
|
|
2049
|
+
{
|
|
2050
|
+
throw new JsonException($"'{text}' is not a date.");
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
return value;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
|
|
2057
|
+
{
|
|
2058
|
+
writer.WriteStringValue(value.ToString());
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
`;
|
|
2062
|
+
function generateConvertersCs(namespaceName, dateTypes = "dateonly") {
|
|
2063
|
+
const asDateTime = dateTypes === "datetime";
|
|
1947
2064
|
return `// <auto-generated/>
|
|
1948
2065
|
// Generated by @contractkit/plugin-csharp. Do not edit manually.
|
|
1949
2066
|
#nullable enable
|
|
@@ -1977,6 +2094,11 @@ public static class SdkJson
|
|
|
1977
2094
|
options.Converters.Add(new BigIntegerConverter());
|
|
1978
2095
|
options.Converters.Add(new DecimalStringConverter());
|
|
1979
2096
|
options.Converters.Add(new IsoTimeSpanConverter());
|
|
2097
|
+
${asDateTime ? " options.Converters.Add(new IsoDateConverter());\n" : ""}#if NETSTANDARD2_0
|
|
2098
|
+
// These are the SDK's own types on this framework, so System.Text.Json has no built-in
|
|
2099
|
+
// converter for them. On net10.0 the framework handles them and these are not compiled.
|
|
2100
|
+
${asDateTime ? "" : " options.Converters.Add(new DateOnlyConverter());\n"} options.Converters.Add(new TimeOnlyConverter());
|
|
2101
|
+
#endif
|
|
1980
2102
|
return options;
|
|
1981
2103
|
}
|
|
1982
2104
|
}
|
|
@@ -2001,8 +2123,9 @@ public sealed class BigIntegerConverter : JsonConverter<BigInteger>
|
|
|
2001
2123
|
if (reader.TokenType == JsonTokenType.Number)
|
|
2002
2124
|
{
|
|
2003
2125
|
// Read the raw token rather than a long: the value may be wider than any BCL integer,
|
|
2004
|
-
// which is the whole reason the contract called it a bigint.
|
|
2005
|
-
|
|
2126
|
+
// which is the whole reason the contract called it a bigint. Copied to an array rather
|
|
2127
|
+
// than handed to the span overload of GetString, which netstandard2.0 does not have.
|
|
2128
|
+
var raw = Encoding.UTF8.GetString(reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan.ToArray());
|
|
2006
2129
|
return BigInteger.Parse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture);
|
|
2007
2130
|
}
|
|
2008
2131
|
|
|
@@ -2074,35 +2197,422 @@ public sealed class IsoTimeSpanConverter : JsonConverter<TimeSpan>
|
|
|
2074
2197
|
writer.WriteStringValue(XmlConvert.ToString(value));
|
|
2075
2198
|
}
|
|
2076
2199
|
}
|
|
2200
|
+
|
|
2201
|
+
${asDateTime ? ISO_DATE_CONVERTER : ""}#if NETSTANDARD2_0
|
|
2202
|
+
${asDateTime ? "" : DATE_ONLY_CONVERTER}
|
|
2203
|
+
/// <summary>
|
|
2204
|
+
/// A time of day, as <c>HH:mm:ss</c>, with a seven-digit fraction when there is one.
|
|
2205
|
+
/// </summary>
|
|
2206
|
+
/// <remarks>
|
|
2207
|
+
/// Compiled only where <c>TimeOnly</c> is the SDK's own polyfill, and writing what the framework's
|
|
2208
|
+
/// own converter writes on net10.0, so a service reads a body from either leg of the build.
|
|
2209
|
+
/// </remarks>
|
|
2210
|
+
public sealed class TimeOnlyConverter : JsonConverter<TimeOnly>
|
|
2211
|
+
{
|
|
2212
|
+
public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
2213
|
+
{
|
|
2214
|
+
if (reader.TokenType != JsonTokenType.String)
|
|
2215
|
+
{
|
|
2216
|
+
throw new JsonException($"Expected a time-of-day string, got {reader.TokenType}.");
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
var text = reader.GetString() ?? throw new JsonException("Expected a time-of-day string.");
|
|
2220
|
+
if (!TimeOnly.TryParse(text, CultureInfo.InvariantCulture, out var value))
|
|
2221
|
+
{
|
|
2222
|
+
throw new JsonException($"'{text}' is not a time of day.");
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
return value;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
|
|
2229
|
+
{
|
|
2230
|
+
writer.WriteStringValue(value.ToString());
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
#endif
|
|
2235
|
+
`;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// src/runtime-polyfills.ts
|
|
2239
|
+
function generatePolyfillsCs(namespaceName) {
|
|
2240
|
+
return `// <auto-generated/>
|
|
2241
|
+
// Generated by @contractkit/plugin-csharp. Do not edit manually.
|
|
2242
|
+
#nullable enable
|
|
2243
|
+
|
|
2244
|
+
#if NETSTANDARD2_0
|
|
2245
|
+
|
|
2246
|
+
using System;
|
|
2247
|
+
using System.Globalization;
|
|
2248
|
+
|
|
2249
|
+
namespace System.Runtime.CompilerServices
|
|
2250
|
+
{
|
|
2251
|
+
/// <summary>
|
|
2252
|
+
/// The marker the compiler requires before it will emit an <c>init</c> accessor.
|
|
2253
|
+
/// </summary>
|
|
2254
|
+
internal static class IsExternalInit
|
|
2255
|
+
{
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
/// <summary>
|
|
2259
|
+
/// Marks a member whose initialization the compiler requires at every construction site.
|
|
2260
|
+
/// </summary>
|
|
2261
|
+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
|
|
2262
|
+
internal sealed class RequiredMemberAttribute : Attribute
|
|
2263
|
+
{
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
/// <summary>
|
|
2267
|
+
/// Names a compiler feature a member depends on, so a compiler that does not have it refuses the
|
|
2268
|
+
/// member rather than misreading it.
|
|
2269
|
+
/// </summary>
|
|
2270
|
+
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
|
|
2271
|
+
internal sealed class CompilerFeatureRequiredAttribute : Attribute
|
|
2272
|
+
{
|
|
2273
|
+
public CompilerFeatureRequiredAttribute(string featureName)
|
|
2274
|
+
{
|
|
2275
|
+
FeatureName = featureName;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
public string FeatureName { get; }
|
|
2279
|
+
|
|
2280
|
+
public bool IsOptional { get; init; }
|
|
2281
|
+
|
|
2282
|
+
public const string RefStructs = nameof(RefStructs);
|
|
2283
|
+
|
|
2284
|
+
public const string RequiredMembers = nameof(RequiredMembers);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
namespace System.Diagnostics.CodeAnalysis
|
|
2289
|
+
{
|
|
2290
|
+
/// <summary>
|
|
2291
|
+
/// Marks a constructor that sets every required member itself.
|
|
2292
|
+
/// </summary>
|
|
2293
|
+
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
|
|
2294
|
+
internal sealed class SetsRequiredMembersAttribute : Attribute
|
|
2295
|
+
{
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
namespace ${namespaceName}.Runtime
|
|
2300
|
+
{
|
|
2301
|
+
/// <summary>
|
|
2302
|
+
/// A calendar date with no time and no offset. Stands in for <c>System.DateOnly</c>.
|
|
2303
|
+
/// </summary>
|
|
2304
|
+
/// <remarks>
|
|
2305
|
+
/// Carries what a generated SDK and the code around it need: construction, ordering, conversion
|
|
2306
|
+
/// to and from <see cref="DateTime"/>, and the <c>yyyy-MM-dd</c> form a contract's <c>date</c>
|
|
2307
|
+
/// travels as. Not a complete reimplementation of the framework type.
|
|
2308
|
+
/// </remarks>
|
|
2309
|
+
public readonly struct DateOnly : IEquatable<DateOnly>, IComparable<DateOnly>, IComparable
|
|
2310
|
+
{
|
|
2311
|
+
private readonly DateTime _value;
|
|
2312
|
+
|
|
2313
|
+
public DateOnly(int year, int month, int day)
|
|
2314
|
+
{
|
|
2315
|
+
_value = new DateTime(year, month, day);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
private DateOnly(DateTime value)
|
|
2319
|
+
{
|
|
2320
|
+
_value = value.Date;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
public static DateOnly MinValue => new DateOnly(DateTime.MinValue);
|
|
2324
|
+
|
|
2325
|
+
public static DateOnly MaxValue => new DateOnly(DateTime.MaxValue);
|
|
2326
|
+
|
|
2327
|
+
public int Year => _value.Year;
|
|
2328
|
+
|
|
2329
|
+
public int Month => _value.Month;
|
|
2330
|
+
|
|
2331
|
+
public int Day => _value.Day;
|
|
2332
|
+
|
|
2333
|
+
public int DayOfYear => _value.DayOfYear;
|
|
2334
|
+
|
|
2335
|
+
public DayOfWeek DayOfWeek => _value.DayOfWeek;
|
|
2336
|
+
|
|
2337
|
+
/// <summary>The date part of <paramref name="value"/>, dropping its time and kind.</summary>
|
|
2338
|
+
public static DateOnly FromDateTime(DateTime value) => new DateOnly(value);
|
|
2339
|
+
|
|
2340
|
+
/// <summary>This date at midnight, with an unspecified kind. What a XAML date picker binds to.</summary>
|
|
2341
|
+
public DateTime ToDateTime() => _value;
|
|
2342
|
+
|
|
2343
|
+
/// <summary>This date at <paramref name="time"/>, with an unspecified kind.</summary>
|
|
2344
|
+
public DateTime ToDateTime(TimeOnly time) => _value.Add(time.ToTimeSpan());
|
|
2345
|
+
|
|
2346
|
+
public DateOnly AddDays(int value) => new DateOnly(_value.AddDays(value));
|
|
2347
|
+
|
|
2348
|
+
public DateOnly AddMonths(int value) => new DateOnly(_value.AddMonths(value));
|
|
2349
|
+
|
|
2350
|
+
public DateOnly AddYears(int value) => new DateOnly(_value.AddYears(value));
|
|
2351
|
+
|
|
2352
|
+
public static DateOnly Parse(string s) => Parse(s, CultureInfo.InvariantCulture);
|
|
2353
|
+
|
|
2354
|
+
/// <exception cref="FormatException">When <paramref name="s"/> is not a date.</exception>
|
|
2355
|
+
public static DateOnly Parse(string s, IFormatProvider? provider)
|
|
2356
|
+
{
|
|
2357
|
+
if (!TryParse(s, provider, out var result))
|
|
2358
|
+
{
|
|
2359
|
+
throw new FormatException("'" + s + "' is not a date.");
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
return result;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
public static bool TryParse(string? s, out DateOnly result) => TryParse(s, CultureInfo.InvariantCulture, out result);
|
|
2366
|
+
|
|
2367
|
+
/// <remarks>
|
|
2368
|
+
/// The wire form is tried first and exactly; anything else the culture reads as a date is
|
|
2369
|
+
/// then taken by its date part, which is what the framework type does too.
|
|
2370
|
+
/// </remarks>
|
|
2371
|
+
public static bool TryParse(string? s, IFormatProvider? provider, out DateOnly result)
|
|
2372
|
+
{
|
|
2373
|
+
var culture = provider ?? CultureInfo.InvariantCulture;
|
|
2374
|
+
if (DateTime.TryParseExact(s, "yyyy-MM-dd", culture, DateTimeStyles.None, out var exact))
|
|
2375
|
+
{
|
|
2376
|
+
result = new DateOnly(exact);
|
|
2377
|
+
return true;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
if (DateTime.TryParse(s, culture, DateTimeStyles.None, out var parsed))
|
|
2381
|
+
{
|
|
2382
|
+
result = new DateOnly(parsed);
|
|
2383
|
+
return true;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
result = default;
|
|
2387
|
+
return false;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/// <summary>The ISO 8601 form, <c>yyyy-MM-dd</c>. Also how the date travels on the wire.</summary>
|
|
2391
|
+
public override string ToString() => _value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
|
2392
|
+
|
|
2393
|
+
public string ToString(string? format) => _value.ToString(format, CultureInfo.InvariantCulture);
|
|
2394
|
+
|
|
2395
|
+
public string ToString(string? format, IFormatProvider? provider) => _value.ToString(format, provider);
|
|
2396
|
+
|
|
2397
|
+
public bool Equals(DateOnly other) => _value == other._value;
|
|
2398
|
+
|
|
2399
|
+
public override bool Equals(object? obj) => obj is DateOnly other && Equals(other);
|
|
2400
|
+
|
|
2401
|
+
public override int GetHashCode() => _value.GetHashCode();
|
|
2402
|
+
|
|
2403
|
+
public int CompareTo(DateOnly other) => _value.CompareTo(other._value);
|
|
2404
|
+
|
|
2405
|
+
public int CompareTo(object? obj)
|
|
2406
|
+
{
|
|
2407
|
+
if (obj is null) return 1;
|
|
2408
|
+
if (obj is DateOnly other) return CompareTo(other);
|
|
2409
|
+
throw new ArgumentException("Object must be of type DateOnly.", nameof(obj));
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
public static bool operator ==(DateOnly left, DateOnly right) => left.Equals(right);
|
|
2413
|
+
|
|
2414
|
+
public static bool operator !=(DateOnly left, DateOnly right) => !left.Equals(right);
|
|
2415
|
+
|
|
2416
|
+
public static bool operator <(DateOnly left, DateOnly right) => left.CompareTo(right) < 0;
|
|
2417
|
+
|
|
2418
|
+
public static bool operator <=(DateOnly left, DateOnly right) => left.CompareTo(right) <= 0;
|
|
2419
|
+
|
|
2420
|
+
public static bool operator >(DateOnly left, DateOnly right) => left.CompareTo(right) > 0;
|
|
2421
|
+
|
|
2422
|
+
public static bool operator >=(DateOnly left, DateOnly right) => left.CompareTo(right) >= 0;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
/// <summary>
|
|
2426
|
+
/// A time of day with no date and no offset. Stands in for <c>System.TimeOnly</c>.
|
|
2427
|
+
/// </summary>
|
|
2428
|
+
/// <remarks>
|
|
2429
|
+
/// At least zero and less than 24 hours, which is what separates it from the
|
|
2430
|
+
/// <see cref="TimeSpan"/> a contract's <c>duration</c> maps to.
|
|
2431
|
+
/// </remarks>
|
|
2432
|
+
public readonly struct TimeOnly : IEquatable<TimeOnly>, IComparable<TimeOnly>, IComparable
|
|
2433
|
+
{
|
|
2434
|
+
private static readonly TimeSpan OneDay = TimeSpan.FromDays(1);
|
|
2435
|
+
|
|
2436
|
+
/// <summary>Accepted on the way in: the wire form, with or without a fraction, and <c>HH:mm</c>.</summary>
|
|
2437
|
+
private static readonly string[] Formats = { "HH:mm:ss.FFFFFFF", "HH:mm" };
|
|
2438
|
+
|
|
2439
|
+
private readonly TimeSpan _value;
|
|
2440
|
+
|
|
2441
|
+
public TimeOnly(int hour, int minute)
|
|
2442
|
+
: this(new TimeSpan(hour, minute, 0))
|
|
2443
|
+
{
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
public TimeOnly(int hour, int minute, int second)
|
|
2447
|
+
: this(new TimeSpan(hour, minute, second))
|
|
2448
|
+
{
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
public TimeOnly(int hour, int minute, int second, int millisecond)
|
|
2452
|
+
: this(new TimeSpan(0, hour, minute, second, millisecond))
|
|
2453
|
+
{
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
private TimeOnly(TimeSpan value)
|
|
2457
|
+
{
|
|
2458
|
+
if (value < TimeSpan.Zero || value >= OneDay)
|
|
2459
|
+
{
|
|
2460
|
+
throw new ArgumentOutOfRangeException(nameof(value), "A time of day is at least zero and less than 24 hours.");
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
_value = value;
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
public static TimeOnly MinValue => new TimeOnly(TimeSpan.Zero);
|
|
2467
|
+
|
|
2468
|
+
public static TimeOnly MaxValue => new TimeOnly(OneDay - TimeSpan.FromTicks(1));
|
|
2469
|
+
|
|
2470
|
+
public int Hour => _value.Hours;
|
|
2471
|
+
|
|
2472
|
+
public int Minute => _value.Minutes;
|
|
2473
|
+
|
|
2474
|
+
public int Second => _value.Seconds;
|
|
2475
|
+
|
|
2476
|
+
public int Millisecond => _value.Milliseconds;
|
|
2477
|
+
|
|
2478
|
+
public long Ticks => _value.Ticks;
|
|
2479
|
+
|
|
2480
|
+
/// <summary>The time part of <paramref name="value"/>.</summary>
|
|
2481
|
+
public static TimeOnly FromDateTime(DateTime value) => new TimeOnly(value.TimeOfDay);
|
|
2482
|
+
|
|
2483
|
+
/// <exception cref="ArgumentOutOfRangeException">When <paramref name="value"/> is negative or a day or more.</exception>
|
|
2484
|
+
public static TimeOnly FromTimeSpan(TimeSpan value) => new TimeOnly(value);
|
|
2485
|
+
|
|
2486
|
+
/// <summary>This time as a span since midnight. What a XAML time picker binds to.</summary>
|
|
2487
|
+
public TimeSpan ToTimeSpan() => _value;
|
|
2488
|
+
|
|
2489
|
+
public static TimeOnly Parse(string s) => Parse(s, CultureInfo.InvariantCulture);
|
|
2490
|
+
|
|
2491
|
+
/// <exception cref="FormatException">When <paramref name="s"/> is not a time of day.</exception>
|
|
2492
|
+
public static TimeOnly Parse(string s, IFormatProvider? provider)
|
|
2493
|
+
{
|
|
2494
|
+
if (!TryParse(s, provider, out var result))
|
|
2495
|
+
{
|
|
2496
|
+
throw new FormatException("'" + s + "' is not a time of day.");
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
return result;
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
public static bool TryParse(string? s, out TimeOnly result) => TryParse(s, CultureInfo.InvariantCulture, out result);
|
|
2503
|
+
|
|
2504
|
+
public static bool TryParse(string? s, IFormatProvider? provider, out TimeOnly result)
|
|
2505
|
+
{
|
|
2506
|
+
var culture = provider ?? CultureInfo.InvariantCulture;
|
|
2507
|
+
if (DateTime.TryParseExact(s, Formats, culture, DateTimeStyles.None, out var exact))
|
|
2508
|
+
{
|
|
2509
|
+
result = new TimeOnly(exact.TimeOfDay);
|
|
2510
|
+
return true;
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
if (DateTime.TryParse(s, culture, DateTimeStyles.None, out var parsed))
|
|
2514
|
+
{
|
|
2515
|
+
result = new TimeOnly(parsed.TimeOfDay);
|
|
2516
|
+
return true;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
result = default;
|
|
2520
|
+
return false;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
/// <summary>
|
|
2524
|
+
/// The ISO 8601 form: <c>HH:mm:ss</c>, and a fraction of seven digits when there is one.
|
|
2525
|
+
/// </summary>
|
|
2526
|
+
/// <remarks>
|
|
2527
|
+
/// Byte for byte what System.Text.Json's own converter writes for a <c>TimeOnly</c> on
|
|
2528
|
+
/// net10.0 \u2014 including the seven digits, which it does not trim \u2014 so a service cannot tell
|
|
2529
|
+
/// which leg of the build sent a body.
|
|
2530
|
+
/// </remarks>
|
|
2531
|
+
public override string ToString() =>
|
|
2532
|
+
_value.Ticks % TimeSpan.TicksPerSecond == 0
|
|
2533
|
+
? _value.ToString("hh':'mm':'ss", CultureInfo.InvariantCulture)
|
|
2534
|
+
: _value.ToString("hh':'mm':'ss'.'fffffff", CultureInfo.InvariantCulture);
|
|
2535
|
+
|
|
2536
|
+
public string ToString(string? format) => _value.ToString(format, CultureInfo.InvariantCulture);
|
|
2537
|
+
|
|
2538
|
+
public string ToString(string? format, IFormatProvider? provider) => _value.ToString(format, provider);
|
|
2539
|
+
|
|
2540
|
+
public bool Equals(TimeOnly other) => _value == other._value;
|
|
2541
|
+
|
|
2542
|
+
public override bool Equals(object? obj) => obj is TimeOnly other && Equals(other);
|
|
2543
|
+
|
|
2544
|
+
public override int GetHashCode() => _value.GetHashCode();
|
|
2545
|
+
|
|
2546
|
+
public int CompareTo(TimeOnly other) => _value.CompareTo(other._value);
|
|
2547
|
+
|
|
2548
|
+
public int CompareTo(object? obj)
|
|
2549
|
+
{
|
|
2550
|
+
if (obj is null) return 1;
|
|
2551
|
+
if (obj is TimeOnly other) return CompareTo(other);
|
|
2552
|
+
throw new ArgumentException("Object must be of type TimeOnly.", nameof(obj));
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
public static bool operator ==(TimeOnly left, TimeOnly right) => left.Equals(right);
|
|
2556
|
+
|
|
2557
|
+
public static bool operator !=(TimeOnly left, TimeOnly right) => !left.Equals(right);
|
|
2558
|
+
|
|
2559
|
+
public static bool operator <(TimeOnly left, TimeOnly right) => left.CompareTo(right) < 0;
|
|
2560
|
+
|
|
2561
|
+
public static bool operator <=(TimeOnly left, TimeOnly right) => left.CompareTo(right) <= 0;
|
|
2562
|
+
|
|
2563
|
+
public static bool operator >(TimeOnly left, TimeOnly right) => left.CompareTo(right) > 0;
|
|
2564
|
+
|
|
2565
|
+
public static bool operator >=(TimeOnly left, TimeOnly right) => left.CompareTo(right) >= 0;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
#endif
|
|
2077
2570
|
`;
|
|
2078
2571
|
}
|
|
2079
2572
|
|
|
2080
2573
|
// src/scaffold.ts
|
|
2081
2574
|
var SCAFFOLD_VERSIONS = {
|
|
2082
|
-
targetFramework: "net10.0"
|
|
2575
|
+
targetFramework: "net10.0",
|
|
2576
|
+
systemTextJson: "10.0.12",
|
|
2577
|
+
/** The oldest language version the generated sources compile under: records, `required`, primary constructors. */
|
|
2578
|
+
netstandardLangVersion: "12.0"
|
|
2083
2579
|
};
|
|
2084
|
-
|
|
2580
|
+
var DEFAULT_TARGET_FRAMEWORKS = [SCAFFOLD_VERSIONS.targetFramework];
|
|
2581
|
+
function generateCsproj(namespaceName, sdkName, targetFrameworks = DEFAULT_TARGET_FRAMEWORKS) {
|
|
2582
|
+
const frameworks = targetFrameworks.length === 1 ? ` <TargetFramework>${targetFrameworks[0]}</TargetFramework>` : ` <TargetFrameworks>${targetFrameworks.join(";")}</TargetFrameworks>`;
|
|
2583
|
+
const netstandard = targetFrameworks.includes("netstandard2.0") ? `
|
|
2584
|
+
<!-- .NET Standard 2.0 predates the language features the generated sources use. -->
|
|
2585
|
+
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
|
2586
|
+
<LangVersion>${SCAFFOLD_VERSIONS.netstandardLangVersion}</LangVersion>
|
|
2587
|
+
</PropertyGroup>
|
|
2588
|
+
|
|
2589
|
+
<!-- The one framework where System.Text.Json is a package rather than part of the platform. -->
|
|
2590
|
+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
|
2591
|
+
<PackageReference Include="System.Text.Json" Version="${SCAFFOLD_VERSIONS.systemTextJson}" />
|
|
2592
|
+
</ItemGroup>
|
|
2593
|
+
` : "";
|
|
2085
2594
|
return `<!-- Created once by @contractkit/plugin-csharp. Yours to edit: it is never regenerated. -->
|
|
2086
2595
|
<Project Sdk="Microsoft.NET.Sdk">
|
|
2087
2596
|
|
|
2088
2597
|
<PropertyGroup>
|
|
2089
|
-
|
|
2598
|
+
${frameworks}
|
|
2090
2599
|
<Nullable>enable</Nullable>
|
|
2091
2600
|
<ImplicitUsings>disable</ImplicitUsings>
|
|
2092
2601
|
<RootNamespace>${namespaceName}</RootNamespace>
|
|
2093
2602
|
<AssemblyName>${sdkName}</AssemblyName>
|
|
2094
2603
|
</PropertyGroup>
|
|
2095
|
-
|
|
2604
|
+
${netstandard}
|
|
2096
2605
|
</Project>
|
|
2097
2606
|
`;
|
|
2098
2607
|
}
|
|
2099
2608
|
|
|
2100
2609
|
// src/index.ts
|
|
2101
|
-
var CSHARP_CODEGEN_VERSION = "
|
|
2610
|
+
var CSHARP_CODEGEN_VERSION = "2";
|
|
2102
2611
|
var CACHE_MANIFEST_FILENAME = "csharp-manifest.json";
|
|
2103
2612
|
var DEFAULT_BASE_DIR = "csharp-sdk";
|
|
2104
2613
|
var DEFAULT_NAMESPACE = "ContractKit.Sdk";
|
|
2105
2614
|
var DEFAULT_SDK_NAME = "Sdk";
|
|
2615
|
+
var DEFAULT_DATE_TYPES = "dateonly";
|
|
2106
2616
|
var plugin = {
|
|
2107
2617
|
name: "csharp-sdk",
|
|
2108
2618
|
async generateTargets(inputs, ctx) {
|
|
@@ -2148,12 +2658,38 @@ function assertValidConfig(config) {
|
|
|
2148
2658
|
throw new Error(`plugin-csharp: ${key} must be a boolean \u2014 got ${JSON.stringify(value)}.`);
|
|
2149
2659
|
}
|
|
2150
2660
|
}
|
|
2661
|
+
assertValidTargetFrameworks(config.targetFrameworks);
|
|
2662
|
+
if (config.dateTypes !== void 0 && !DATE_TYPES.includes(config.dateTypes)) {
|
|
2663
|
+
throw new Error(`plugin-csharp: dateTypes ${JSON.stringify(config.dateTypes)} is not supported \u2014 expected one of ${DATE_TYPES.join(", ")}.`);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
var DATE_TYPES = ["dateonly", "datetime"];
|
|
2667
|
+
var TARGET_FRAMEWORKS = ["netstandard2.0", "net10.0"];
|
|
2668
|
+
function assertValidTargetFrameworks(frameworks) {
|
|
2669
|
+
if (frameworks === void 0) return;
|
|
2670
|
+
if (!Array.isArray(frameworks) || frameworks.length === 0) {
|
|
2671
|
+
throw new Error(`plugin-csharp: targetFrameworks must be a non-empty array \u2014 got ${JSON.stringify(frameworks)}.`);
|
|
2672
|
+
}
|
|
2673
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2674
|
+
for (const framework of frameworks) {
|
|
2675
|
+
if (typeof framework !== "string" || !TARGET_FRAMEWORKS.includes(framework)) {
|
|
2676
|
+
throw new Error(
|
|
2677
|
+
`plugin-csharp: targetFrameworks entry ${JSON.stringify(framework)} is not supported \u2014 expected one of ${TARGET_FRAMEWORKS.join(", ")}.`
|
|
2678
|
+
);
|
|
2679
|
+
}
|
|
2680
|
+
if (seen.has(framework)) {
|
|
2681
|
+
throw new Error(`plugin-csharp: targetFrameworks lists '${framework}' twice.`);
|
|
2682
|
+
}
|
|
2683
|
+
seen.add(framework);
|
|
2684
|
+
}
|
|
2151
2685
|
}
|
|
2152
2686
|
async function runCSharpCodegen(inputs, ctx, config, rootDir) {
|
|
2153
2687
|
assertValidConfig(config);
|
|
2154
2688
|
const { contractRoots } = inputs;
|
|
2155
2689
|
const namespaceName = config.namespace ?? DEFAULT_NAMESPACE;
|
|
2156
2690
|
const sdkName = config.sdkName ?? DEFAULT_SDK_NAME;
|
|
2691
|
+
const targetFrameworks = config.targetFrameworks ?? DEFAULT_TARGET_FRAMEWORKS;
|
|
2692
|
+
const dateTypes = config.dateTypes ?? DEFAULT_DATE_TYPES;
|
|
2157
2693
|
const outDir = resolve(rootDir, config.baseDir ?? DEFAULT_BASE_DIR);
|
|
2158
2694
|
const manifestPath = resolve(ctx.cacheDir, CACHE_MANIFEST_FILENAME);
|
|
2159
2695
|
const allModels = contractRoots.flatMap((root) => root.models);
|
|
@@ -2181,6 +2717,7 @@ async function runCSharpCodegen(inputs, ctx, config, rootDir) {
|
|
|
2181
2717
|
v: CSHARP_CODEGEN_VERSION,
|
|
2182
2718
|
relPath,
|
|
2183
2719
|
namespace: namespaceName,
|
|
2720
|
+
dateTypes,
|
|
2184
2721
|
root,
|
|
2185
2722
|
externalBases,
|
|
2186
2723
|
modelsWithInput: relevantInputModels,
|
|
@@ -2195,6 +2732,7 @@ async function runCSharpCodegen(inputs, ctx, config, rootDir) {
|
|
|
2195
2732
|
relativePath: relPath,
|
|
2196
2733
|
content: generateCSharpModels(root, {
|
|
2197
2734
|
namespace: namespaceName,
|
|
2735
|
+
dateTypes,
|
|
2198
2736
|
modelsWithInput,
|
|
2199
2737
|
modelIndex,
|
|
2200
2738
|
hoisted,
|
|
@@ -2216,6 +2754,7 @@ async function runCSharpCodegen(inputs, ctx, config, rootDir) {
|
|
|
2216
2754
|
v: CSHARP_CODEGEN_VERSION,
|
|
2217
2755
|
relPath,
|
|
2218
2756
|
namespace: namespaceName,
|
|
2757
|
+
dateTypes,
|
|
2219
2758
|
root,
|
|
2220
2759
|
referencedModels,
|
|
2221
2760
|
modelsWithInput: relevantInputModels,
|
|
@@ -2229,6 +2768,7 @@ async function runCSharpCodegen(inputs, ctx, config, rootDir) {
|
|
|
2229
2768
|
relativePath: relPath,
|
|
2230
2769
|
content: generateCSharpClient(root, {
|
|
2231
2770
|
namespace: namespaceName,
|
|
2771
|
+
dateTypes,
|
|
2232
2772
|
modelsWithInput,
|
|
2233
2773
|
modelIndex,
|
|
2234
2774
|
hoisted,
|
|
@@ -2240,12 +2780,19 @@ async function runCSharpCodegen(inputs, ctx, config, rootDir) {
|
|
|
2240
2780
|
});
|
|
2241
2781
|
}
|
|
2242
2782
|
const globalFiles = [
|
|
2243
|
-
{ relativePath: "Runtime/Converters.cs", content: generateConvertersCs(namespaceName) },
|
|
2783
|
+
{ relativePath: "Runtime/Converters.cs", content: generateConvertersCs(namespaceName, dateTypes) },
|
|
2244
2784
|
{ relativePath: "Runtime/SdkRuntime.cs", content: generateRuntimeCs(namespaceName) },
|
|
2245
2785
|
{ relativePath: `${sdkName}.cs`, content: generateSdkCs(namespaceName, sdkName, clients) }
|
|
2246
2786
|
];
|
|
2787
|
+
if (targetFrameworks.includes("netstandard2.0")) {
|
|
2788
|
+
globalFiles.push({ relativePath: "Runtime/Polyfills.cs", content: generatePolyfillsCs(namespaceName) });
|
|
2789
|
+
}
|
|
2247
2790
|
if (config.scaffold) {
|
|
2248
|
-
globalFiles.push({
|
|
2791
|
+
globalFiles.push({
|
|
2792
|
+
relativePath: `${sdkName}.csproj`,
|
|
2793
|
+
content: generateCsproj(namespaceName, sdkName, targetFrameworks),
|
|
2794
|
+
ifAbsent: true
|
|
2795
|
+
});
|
|
2249
2796
|
}
|
|
2250
2797
|
const result = runIncrementalCodegen({
|
|
2251
2798
|
codegenVersion: CSHARP_CODEGEN_VERSION,
|