@typespec/http-server-js 0.58.0-alpha.12-dev.0 → 0.58.0-alpha.12-dev.1
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/dist/generated-defs/helpers/datetime.d.ts +4 -0
- package/dist/generated-defs/helpers/datetime.d.ts.map +1 -0
- package/dist/generated-defs/helpers/datetime.js +256 -0
- package/dist/generated-defs/helpers/datetime.js.map +1 -0
- package/dist/generated-defs/helpers/index.d.ts.map +1 -1
- package/dist/generated-defs/helpers/index.js +1 -0
- package/dist/generated-defs/helpers/index.js.map +1 -1
- package/dist/src/common/declaration.js +1 -1
- package/dist/src/common/declaration.js.map +1 -1
- package/dist/src/common/reference.js +1 -1
- package/dist/src/common/reference.js.map +1 -1
- package/dist/src/common/scalar.d.ts +175 -22
- package/dist/src/common/scalar.d.ts.map +1 -1
- package/dist/src/common/scalar.js +420 -93
- package/dist/src/common/scalar.js.map +1 -1
- package/dist/src/common/serialization/index.d.ts +2 -2
- package/dist/src/common/serialization/index.d.ts.map +1 -1
- package/dist/src/common/serialization/index.js +9 -3
- package/dist/src/common/serialization/index.js.map +1 -1
- package/dist/src/common/serialization/json.d.ts +2 -2
- package/dist/src/common/serialization/json.d.ts.map +1 -1
- package/dist/src/common/serialization/json.js +144 -42
- package/dist/src/common/serialization/json.js.map +1 -1
- package/dist/src/helpers/datetime.d.ts +92 -0
- package/dist/src/helpers/datetime.d.ts.map +1 -0
- package/dist/src/helpers/datetime.js +151 -0
- package/dist/src/helpers/datetime.js.map +1 -0
- package/dist/src/http/server/index.d.ts.map +1 -1
- package/dist/src/http/server/index.js +17 -12
- package/dist/src/http/server/index.js.map +1 -1
- package/dist/src/http/server/multipart.js +1 -1
- package/dist/src/http/server/multipart.js.map +1 -1
- package/dist/src/lib.d.ts +10 -1
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +6 -0
- package/dist/src/lib.js.map +1 -1
- package/dist/src/util/case.d.ts +9 -0
- package/dist/src/util/case.d.ts.map +1 -1
- package/dist/src/util/case.js +18 -0
- package/dist/src/util/case.js.map +1 -1
- package/dist/src/util/differentiate.d.ts +4 -4
- package/dist/src/util/differentiate.d.ts.map +1 -1
- package/dist/src/util/differentiate.js +10 -10
- package/dist/src/util/differentiate.js.map +1 -1
- package/generated-defs/helpers/datetime.ts +263 -0
- package/generated-defs/helpers/index.ts +1 -0
- package/package.json +7 -7
- package/src/common/declaration.ts +1 -1
- package/src/common/reference.ts +1 -1
- package/src/common/scalar.ts +709 -103
- package/src/common/serialization/index.ts +11 -4
- package/src/common/serialization/json.ts +174 -52
- package/src/helpers/datetime.ts +235 -0
- package/src/http/server/index.ts +29 -15
- package/src/http/server/multipart.ts +1 -1
- package/src/lib.ts +6 -0
- package/src/util/case.ts +19 -0
- package/src/util/differentiate.ts +15 -8
- package/temp/tsconfig.tsbuildinfo +1 -1
- package/test/datetime.test.ts +226 -0
- package/test/scalar.test.ts +345 -0
|
@@ -55,9 +55,15 @@ export function emitSerialization(ctx: JsContext): void {
|
|
|
55
55
|
const serializations = _SERIALIZATIONS_MAP.get(type)!;
|
|
56
56
|
|
|
57
57
|
const requiredSerializations = new Set<SerializationContentType>(
|
|
58
|
-
[...serializations].filter((serialization) =>
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
[...serializations].filter((serialization) => {
|
|
59
|
+
const isSynthetic = ctx.syntheticNames.has(type) || !type.namespace;
|
|
60
|
+
|
|
61
|
+
const module = isSynthetic
|
|
62
|
+
? ctx.syntheticModule
|
|
63
|
+
: createOrGetModuleForNamespace(ctx, type.namespace!);
|
|
64
|
+
|
|
65
|
+
return isSerializationRequired(ctx, module, type, serialization);
|
|
66
|
+
}),
|
|
61
67
|
);
|
|
62
68
|
|
|
63
69
|
if (requiredSerializations.size > 0) {
|
|
@@ -68,12 +74,13 @@ export function emitSerialization(ctx: JsContext): void {
|
|
|
68
74
|
|
|
69
75
|
export function isSerializationRequired(
|
|
70
76
|
ctx: JsContext,
|
|
77
|
+
module: Module,
|
|
71
78
|
type: Type,
|
|
72
79
|
serialization: SerializationContentType,
|
|
73
80
|
): boolean {
|
|
74
81
|
switch (serialization) {
|
|
75
82
|
case "application/json": {
|
|
76
|
-
return requiresJsonSerialization(ctx, type);
|
|
83
|
+
return requiresJsonSerialization(ctx, module, type);
|
|
77
84
|
}
|
|
78
85
|
default:
|
|
79
86
|
throw new Error(`Unreachable: serialization content type ${serialization satisfies never}`);
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
BooleanLiteral,
|
|
6
|
+
DiagnosticTarget,
|
|
6
7
|
IntrinsicType,
|
|
7
8
|
ModelProperty,
|
|
8
9
|
NoTarget,
|
|
9
10
|
NumericLiteral,
|
|
11
|
+
Scalar,
|
|
10
12
|
StringLiteral,
|
|
11
13
|
Type,
|
|
12
14
|
compilerAssert,
|
|
@@ -17,12 +19,15 @@ import {
|
|
|
17
19
|
} from "@typespec/compiler";
|
|
18
20
|
import { getHeaderFieldOptions, getPathParamOptions, getQueryParamOptions } from "@typespec/http";
|
|
19
21
|
import { JsContext, Module } from "../../ctx.js";
|
|
20
|
-
import {
|
|
22
|
+
import { reportDiagnostic } from "../../lib.js";
|
|
23
|
+
import { access, parseCase } from "../../util/case.js";
|
|
21
24
|
import { differentiateUnion, writeCodeTree } from "../../util/differentiate.js";
|
|
22
25
|
import { UnimplementedError } from "../../util/error.js";
|
|
23
26
|
import { indent } from "../../util/iter.js";
|
|
27
|
+
import { keywordSafe } from "../../util/keywords.js";
|
|
28
|
+
import { getFullyQualifiedTypeName } from "../../util/name.js";
|
|
24
29
|
import { emitTypeReference, escapeUnsafeChars } from "../reference.js";
|
|
25
|
-
import { getJsScalar } from "../scalar.js";
|
|
30
|
+
import { Encoder, JS_SCALAR_UNKNOWN, JsScalar, getJsScalar } from "../scalar.js";
|
|
26
31
|
import { SerializableType, SerializationContext, requireSerialization } from "./index.js";
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -30,7 +35,12 @@ import { SerializableType, SerializationContext, requireSerialization } from "./
|
|
|
30
35
|
*/
|
|
31
36
|
const _REQUIRES_JSON_SERIALIZATION = new WeakMap<SerializableType | ModelProperty, boolean>();
|
|
32
37
|
|
|
33
|
-
export function requiresJsonSerialization(
|
|
38
|
+
export function requiresJsonSerialization(
|
|
39
|
+
ctx: JsContext,
|
|
40
|
+
module: Module,
|
|
41
|
+
type: Type,
|
|
42
|
+
diagnosticTarget: DiagnosticTarget | typeof NoTarget = NoTarget,
|
|
43
|
+
): boolean {
|
|
34
44
|
if (!isSerializable(type)) return false;
|
|
35
45
|
|
|
36
46
|
if (_REQUIRES_JSON_SERIALIZATION.has(type)) {
|
|
@@ -48,28 +58,31 @@ export function requiresJsonSerialization(ctx: JsContext, type: Type): boolean {
|
|
|
48
58
|
case "Model": {
|
|
49
59
|
if (isArrayModelType(ctx.program, type)) {
|
|
50
60
|
const argumentType = type.indexer.value;
|
|
51
|
-
requiresSerialization = requiresJsonSerialization(ctx, argumentType);
|
|
61
|
+
requiresSerialization = requiresJsonSerialization(ctx, module, argumentType);
|
|
52
62
|
break;
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
requiresSerialization = [...type.properties.values()].some((property) =>
|
|
56
|
-
propertyRequiresJsonSerialization(ctx, property),
|
|
66
|
+
propertyRequiresJsonSerialization(ctx, module, property),
|
|
57
67
|
);
|
|
58
68
|
break;
|
|
59
69
|
}
|
|
60
70
|
case "Scalar": {
|
|
61
|
-
const scalar = getJsScalar(ctx
|
|
62
|
-
requiresSerialization =
|
|
71
|
+
const scalar = getJsScalar(ctx, module, type, diagnosticTarget);
|
|
72
|
+
requiresSerialization =
|
|
73
|
+
!scalar.isJsonCompatible ||
|
|
74
|
+
getEncode(ctx.program, type) !== undefined ||
|
|
75
|
+
scalar.getDefaultMimeEncoding("application/json") !== undefined;
|
|
63
76
|
break;
|
|
64
77
|
}
|
|
65
78
|
case "Union": {
|
|
66
79
|
requiresSerialization = [...type.variants.values()].some((variant) =>
|
|
67
|
-
requiresJsonSerialization(ctx, variant),
|
|
80
|
+
requiresJsonSerialization(ctx, module, variant),
|
|
68
81
|
);
|
|
69
82
|
break;
|
|
70
83
|
}
|
|
71
84
|
case "ModelProperty":
|
|
72
|
-
requiresSerialization = requiresJsonSerialization(ctx, type.type);
|
|
85
|
+
requiresSerialization = requiresJsonSerialization(ctx, module, type.type);
|
|
73
86
|
break;
|
|
74
87
|
}
|
|
75
88
|
|
|
@@ -78,12 +91,17 @@ export function requiresJsonSerialization(ctx: JsContext, type: Type): boolean {
|
|
|
78
91
|
return requiresSerialization;
|
|
79
92
|
}
|
|
80
93
|
|
|
81
|
-
function propertyRequiresJsonSerialization(
|
|
94
|
+
function propertyRequiresJsonSerialization(
|
|
95
|
+
ctx: JsContext,
|
|
96
|
+
module: Module,
|
|
97
|
+
property: ModelProperty,
|
|
98
|
+
): boolean {
|
|
82
99
|
return !!(
|
|
83
100
|
isHttpMetadata(ctx, property) ||
|
|
84
101
|
getEncode(ctx.program, property) ||
|
|
85
102
|
resolveEncodedName(ctx.program, property, "application/json") !== property.name ||
|
|
86
|
-
(isSerializable(property.type) &&
|
|
103
|
+
(isSerializable(property.type) &&
|
|
104
|
+
requiresJsonSerialization(ctx, module, property.type, property))
|
|
87
105
|
);
|
|
88
106
|
}
|
|
89
107
|
|
|
@@ -132,12 +150,40 @@ function* emitToJson(
|
|
|
132
150
|
const encodedName =
|
|
133
151
|
resolveEncodedName(ctx.program, property, "application/json") ?? property.name;
|
|
134
152
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
153
|
+
const propertyName = keywordSafe(parseCase(property.name).camelCase);
|
|
154
|
+
|
|
155
|
+
let expr: string = access("input", propertyName);
|
|
156
|
+
|
|
157
|
+
const encoding = getEncode(ctx.program, property);
|
|
158
|
+
|
|
159
|
+
if (property.type.kind === "Scalar" && encoding) {
|
|
160
|
+
const scalar = getJsScalar(ctx, module, property.type, property.type);
|
|
161
|
+
const scalarEncoder = scalar.getEncoding(encoding.encoding ?? "default", encoding.type);
|
|
162
|
+
|
|
163
|
+
if (scalarEncoder) {
|
|
164
|
+
expr = transposeExpressionToJson(
|
|
165
|
+
ctx,
|
|
166
|
+
// Assertion: scalarEncoder.target.scalar is defined because we resolved an encoder.
|
|
167
|
+
scalarEncoder.target.scalar as Scalar,
|
|
168
|
+
scalarEncoder.encode(expr),
|
|
169
|
+
module,
|
|
170
|
+
);
|
|
171
|
+
} else {
|
|
172
|
+
reportDiagnostic(ctx.program, {
|
|
173
|
+
code: "unknown-encoding",
|
|
174
|
+
target: NoTarget,
|
|
175
|
+
format: {
|
|
176
|
+
encoding: encoding.encoding ?? "<default>",
|
|
177
|
+
type: getFullyQualifiedTypeName(property.type),
|
|
178
|
+
target: getFullyQualifiedTypeName(encoding.type),
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// We treat this as unknown from here on out. The encoding was not deciphered.
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
expr = transposeExpressionToJson(ctx, property.type, expr, module);
|
|
186
|
+
}
|
|
141
187
|
|
|
142
188
|
yield ` ${encodedName}: ${expr},`;
|
|
143
189
|
}
|
|
@@ -151,12 +197,12 @@ function* emitToJson(
|
|
|
151
197
|
return;
|
|
152
198
|
}
|
|
153
199
|
case "Union": {
|
|
154
|
-
const codeTree = differentiateUnion(ctx, type);
|
|
200
|
+
const codeTree = differentiateUnion(ctx, module, type);
|
|
155
201
|
|
|
156
202
|
yield* writeCodeTree(ctx, codeTree, {
|
|
157
203
|
subject: "input",
|
|
158
204
|
referenceModelProperty(p) {
|
|
159
|
-
return "input
|
|
205
|
+
return access("input", parseCase(p.name).camelCase);
|
|
160
206
|
},
|
|
161
207
|
renderResult(type) {
|
|
162
208
|
return [`return ${transposeExpressionToJson(ctx, type, "input", module)};`];
|
|
@@ -179,15 +225,15 @@ function transposeExpressionToJson(
|
|
|
179
225
|
if (isArrayModelType(ctx.program, type)) {
|
|
180
226
|
const argumentType = type.indexer.value;
|
|
181
227
|
|
|
182
|
-
if (requiresJsonSerialization(ctx, argumentType)) {
|
|
183
|
-
return
|
|
228
|
+
if (requiresJsonSerialization(ctx, module, argumentType)) {
|
|
229
|
+
return `(${expr})?.map((item) => ${transposeExpressionToJson(ctx, argumentType, "item", module)})`;
|
|
184
230
|
} else {
|
|
185
231
|
return expr;
|
|
186
232
|
}
|
|
187
233
|
} else if (isRecordModelType(ctx.program, type)) {
|
|
188
234
|
const argumentType = type.indexer.value;
|
|
189
235
|
|
|
190
|
-
if (requiresJsonSerialization(ctx, argumentType)) {
|
|
236
|
+
if (requiresJsonSerialization(ctx, module, argumentType)) {
|
|
191
237
|
return `Object.fromEntries(Object.entries(${expr}).map(([key, value]) => [String(key), ${transposeExpressionToJson(
|
|
192
238
|
ctx,
|
|
193
239
|
argumentType,
|
|
@@ -197,7 +243,7 @@ function transposeExpressionToJson(
|
|
|
197
243
|
} else {
|
|
198
244
|
return expr;
|
|
199
245
|
}
|
|
200
|
-
} else if (!requiresJsonSerialization(ctx, type)) {
|
|
246
|
+
} else if (!requiresJsonSerialization(ctx, module, type)) {
|
|
201
247
|
return expr;
|
|
202
248
|
} else {
|
|
203
249
|
requireSerialization(ctx, type, "application/json");
|
|
@@ -207,18 +253,20 @@ function transposeExpressionToJson(
|
|
|
207
253
|
}
|
|
208
254
|
}
|
|
209
255
|
case "Scalar":
|
|
210
|
-
const scalar = getJsScalar(ctx
|
|
256
|
+
const scalar = getJsScalar(ctx, module, type, NoTarget);
|
|
211
257
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
258
|
+
const encoder: Encoder = getScalarEncoder(ctx, type, scalar);
|
|
259
|
+
|
|
260
|
+
const encoded = encoder.encode(expr);
|
|
261
|
+
|
|
262
|
+
if (encoder.target.isJsonCompatible || !encoder.target.scalar) {
|
|
263
|
+
return encoded;
|
|
264
|
+
} else {
|
|
265
|
+
// Assertion: encoder.target.scalar is a scalar because "unknown" is JSON compatible.
|
|
266
|
+
return transposeExpressionToJson(ctx, encoder.target.scalar as Scalar, encoded, module);
|
|
219
267
|
}
|
|
220
268
|
case "Union":
|
|
221
|
-
if (!requiresJsonSerialization(ctx, type)) {
|
|
269
|
+
if (!requiresJsonSerialization(ctx, module, type)) {
|
|
222
270
|
return expr;
|
|
223
271
|
} else {
|
|
224
272
|
requireSerialization(ctx, type, "application/json");
|
|
@@ -269,6 +317,48 @@ function transposeExpressionToJson(
|
|
|
269
317
|
}
|
|
270
318
|
}
|
|
271
319
|
|
|
320
|
+
function getScalarEncoder(ctx: SerializationContext, type: Scalar, scalar: JsScalar) {
|
|
321
|
+
const encoding = getEncode(ctx.program, type);
|
|
322
|
+
|
|
323
|
+
let encoder: Encoder;
|
|
324
|
+
|
|
325
|
+
if (encoding) {
|
|
326
|
+
const encodingName = encoding.encoding ?? "default";
|
|
327
|
+
const scalarEncoder = scalar.getEncoding(encodingName, encoding.type);
|
|
328
|
+
|
|
329
|
+
// TODO - we should detect this before realizing models and use a transform to represent
|
|
330
|
+
// the defective scalar as the encoding target type.
|
|
331
|
+
// See: https://github.com/microsoft/typespec/issues/6376
|
|
332
|
+
if (!scalarEncoder) {
|
|
333
|
+
reportDiagnostic(ctx.program, {
|
|
334
|
+
code: "unknown-encoding",
|
|
335
|
+
target: NoTarget,
|
|
336
|
+
format: {
|
|
337
|
+
encoding: encoding.encoding ?? "<default>",
|
|
338
|
+
type: getFullyQualifiedTypeName(type),
|
|
339
|
+
target: getFullyQualifiedTypeName(encoding.type),
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
encoder = {
|
|
344
|
+
target: JS_SCALAR_UNKNOWN,
|
|
345
|
+
encode: (expr) => expr,
|
|
346
|
+
decode: (expr) => expr,
|
|
347
|
+
};
|
|
348
|
+
} else {
|
|
349
|
+
encoder = scalarEncoder;
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
// No encoding specified, use the default content type encoding for json
|
|
353
|
+
encoder = scalar.getDefaultMimeEncoding("application/json") ?? {
|
|
354
|
+
target: JS_SCALAR_UNKNOWN,
|
|
355
|
+
encode: (expr) => expr,
|
|
356
|
+
decode: (expr) => expr,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return encoder;
|
|
360
|
+
}
|
|
361
|
+
|
|
272
362
|
function literalToExpr(type: StringLiteral | BooleanLiteral | NumericLiteral): string {
|
|
273
363
|
switch (type.kind) {
|
|
274
364
|
case "String":
|
|
@@ -292,14 +382,42 @@ function* emitFromJson(
|
|
|
292
382
|
const encodedName =
|
|
293
383
|
resolveEncodedName(ctx.program, property, "application/json") ?? property.name;
|
|
294
384
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
385
|
+
let expr = access("input", encodedName);
|
|
386
|
+
|
|
387
|
+
const encoding = getEncode(ctx.program, property);
|
|
388
|
+
|
|
389
|
+
if (property.type.kind === "Scalar" && encoding) {
|
|
390
|
+
const scalar = getJsScalar(ctx, module, property.type, property.type);
|
|
391
|
+
const scalarEncoder = scalar.getEncoding(encoding.encoding ?? "default", encoding.type);
|
|
392
|
+
|
|
393
|
+
if (scalarEncoder) {
|
|
394
|
+
expr = transposeExpressionFromJson(
|
|
395
|
+
ctx,
|
|
396
|
+
// Assertion: scalarEncoder.target.scalar is defined because we resolved an encoder.
|
|
397
|
+
scalarEncoder.target.scalar as Scalar,
|
|
398
|
+
scalarEncoder.decode(expr),
|
|
399
|
+
module,
|
|
400
|
+
);
|
|
401
|
+
} else {
|
|
402
|
+
reportDiagnostic(ctx.program, {
|
|
403
|
+
code: "unknown-encoding",
|
|
404
|
+
target: NoTarget,
|
|
405
|
+
format: {
|
|
406
|
+
encoding: encoding.encoding ?? "<default>",
|
|
407
|
+
type: getFullyQualifiedTypeName(property.type),
|
|
408
|
+
target: getFullyQualifiedTypeName(encoding.type),
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// We treat this as unknown from here on out. The encoding was not deciphered.
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
expr = transposeExpressionFromJson(ctx, property.type, expr, module);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const propertyName = keywordSafe(parseCase(property.name).camelCase);
|
|
301
419
|
|
|
302
|
-
yield ` ${
|
|
420
|
+
yield ` ${propertyName}: ${expr},`;
|
|
303
421
|
}
|
|
304
422
|
|
|
305
423
|
yield "};";
|
|
@@ -311,13 +429,13 @@ function* emitFromJson(
|
|
|
311
429
|
return;
|
|
312
430
|
}
|
|
313
431
|
case "Union": {
|
|
314
|
-
const codeTree = differentiateUnion(ctx, type);
|
|
432
|
+
const codeTree = differentiateUnion(ctx, module, type);
|
|
315
433
|
|
|
316
434
|
yield* writeCodeTree(ctx, codeTree, {
|
|
317
435
|
subject: "input",
|
|
318
436
|
referenceModelProperty(p) {
|
|
319
437
|
const jsonName = resolveEncodedName(ctx.program, p, "application/json") ?? p.name;
|
|
320
|
-
return "input
|
|
438
|
+
return access("input", jsonName);
|
|
321
439
|
},
|
|
322
440
|
renderResult(type) {
|
|
323
441
|
return [`return ${transposeExpressionFromJson(ctx, type, "input", module)};`];
|
|
@@ -340,15 +458,15 @@ function transposeExpressionFromJson(
|
|
|
340
458
|
if (isArrayModelType(ctx.program, type)) {
|
|
341
459
|
const argumentType = type.indexer.value;
|
|
342
460
|
|
|
343
|
-
if (requiresJsonSerialization(ctx, argumentType)) {
|
|
344
|
-
return
|
|
461
|
+
if (requiresJsonSerialization(ctx, module, argumentType)) {
|
|
462
|
+
return `(${expr})?.map((item: any) => ${transposeExpressionFromJson(ctx, argumentType, "item", module)})`;
|
|
345
463
|
} else {
|
|
346
464
|
return expr;
|
|
347
465
|
}
|
|
348
466
|
} else if (isRecordModelType(ctx.program, type)) {
|
|
349
467
|
const argumentType = type.indexer.value;
|
|
350
468
|
|
|
351
|
-
if (requiresJsonSerialization(ctx, argumentType)) {
|
|
469
|
+
if (requiresJsonSerialization(ctx, module, argumentType)) {
|
|
352
470
|
return `Object.fromEntries(Object.entries(${expr}).map(([key, value]) => [key, ${transposeExpressionFromJson(
|
|
353
471
|
ctx,
|
|
354
472
|
argumentType,
|
|
@@ -358,7 +476,7 @@ function transposeExpressionFromJson(
|
|
|
358
476
|
} else {
|
|
359
477
|
return expr;
|
|
360
478
|
}
|
|
361
|
-
} else if (!requiresJsonSerialization(ctx, type)) {
|
|
479
|
+
} else if (!requiresJsonSerialization(ctx, module, type)) {
|
|
362
480
|
return `${expr} as ${emitTypeReference(ctx, type, NoTarget, module)}`;
|
|
363
481
|
} else {
|
|
364
482
|
requireSerialization(ctx, type, "application/json");
|
|
@@ -368,16 +486,20 @@ function transposeExpressionFromJson(
|
|
|
368
486
|
}
|
|
369
487
|
}
|
|
370
488
|
case "Scalar":
|
|
371
|
-
const scalar = getJsScalar(ctx
|
|
489
|
+
const scalar = getJsScalar(ctx, module, type, type);
|
|
372
490
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
491
|
+
const encoder = getScalarEncoder(ctx, type, scalar);
|
|
492
|
+
|
|
493
|
+
const decoded = encoder.decode(expr);
|
|
494
|
+
|
|
495
|
+
if (encoder.target.isJsonCompatible || !encoder.target.scalar) {
|
|
496
|
+
return decoded;
|
|
497
|
+
} else {
|
|
498
|
+
// Assertion: encoder.target.scalar is a scalar because "unknown" is JSON compatible.
|
|
499
|
+
return transposeExpressionFromJson(ctx, encoder.target.scalar as Scalar, decoded, module);
|
|
378
500
|
}
|
|
379
501
|
case "Union":
|
|
380
|
-
if (!requiresJsonSerialization(ctx, type)) {
|
|
502
|
+
if (!requiresJsonSerialization(ctx, module, type)) {
|
|
381
503
|
return expr;
|
|
382
504
|
} else {
|
|
383
505
|
requireSerialization(ctx, type, "application/json");
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
// #region Duration
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Regular expression for matching ISO8601 duration strings.
|
|
8
|
+
*
|
|
9
|
+
* Yields:
|
|
10
|
+
* - 0: the full match
|
|
11
|
+
* - 1: the sign (optional)
|
|
12
|
+
* - 2: years (optional)
|
|
13
|
+
* - 3: months (optional)
|
|
14
|
+
* - 4: weeks (optional)
|
|
15
|
+
* - 5: days (optional)
|
|
16
|
+
* - 6: hours (optional)
|
|
17
|
+
* - 7: minutes (optional)
|
|
18
|
+
* - 8: seconds (optional)
|
|
19
|
+
*/
|
|
20
|
+
const ISO8601_DURATION_REGEX =
|
|
21
|
+
/^(-)?P(?:((?:\d*[.,])?\d+)Y)?(?:((?:\d*[.,])?\d+)M)?(?:((?:\d*[.,])?\d+)W)?(?:((?:\d*[.,])?\d+)D)?(?:T(?:((?:\d*[.,])?\d+)H)?(?:((?:\d*[.,])?\d+)M)?(?:((?:\d*[.,])?\d+)S)?)?$/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A duration of time, measured in years, months, weeks, days, hours, minutes, and seconds.
|
|
25
|
+
*
|
|
26
|
+
* The values may be fractional and are not normalized (e.g. 36 hours is not the same duration as 1 day and 12 hours
|
|
27
|
+
* when accounting for Daylight Saving Time changes or leap seconds).
|
|
28
|
+
*
|
|
29
|
+
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
|
|
30
|
+
*/
|
|
31
|
+
export interface Duration {
|
|
32
|
+
/**
|
|
33
|
+
* "+" if the duration is positive, "-" if the duration is negative.
|
|
34
|
+
*/
|
|
35
|
+
sign: "+" | "-";
|
|
36
|
+
/**
|
|
37
|
+
* The number of years in the duration.
|
|
38
|
+
*/
|
|
39
|
+
years: number;
|
|
40
|
+
/**
|
|
41
|
+
* The number of months in the duration.
|
|
42
|
+
*/
|
|
43
|
+
months: number;
|
|
44
|
+
/**
|
|
45
|
+
* The number of weeks in the duration.
|
|
46
|
+
*/
|
|
47
|
+
weeks: number;
|
|
48
|
+
/**
|
|
49
|
+
* The number of days in the duration.
|
|
50
|
+
*/
|
|
51
|
+
days: number;
|
|
52
|
+
/**
|
|
53
|
+
* The number of hours in the duration.
|
|
54
|
+
*/
|
|
55
|
+
hours: number;
|
|
56
|
+
/**
|
|
57
|
+
* The number of minutes in the duration.
|
|
58
|
+
*/
|
|
59
|
+
minutes: number;
|
|
60
|
+
/**
|
|
61
|
+
* The number of seconds in the duration.
|
|
62
|
+
*/
|
|
63
|
+
seconds: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const Duration = Object.freeze({
|
|
67
|
+
/**
|
|
68
|
+
* Parses an ISO8601 duration string into an object.
|
|
69
|
+
*
|
|
70
|
+
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
|
|
71
|
+
*
|
|
72
|
+
* @param duration - the duration string to parse
|
|
73
|
+
* @returns an object containing the parsed duration
|
|
74
|
+
*/
|
|
75
|
+
parseISO8601(duration: string, maxLength: number = 100): Duration {
|
|
76
|
+
duration = duration.trim();
|
|
77
|
+
if (duration.length > maxLength)
|
|
78
|
+
throw new Error(`ISO8601 duration string is too long: ${duration}`);
|
|
79
|
+
|
|
80
|
+
const match = duration.match(ISO8601_DURATION_REGEX);
|
|
81
|
+
|
|
82
|
+
if (!match) throw new Error(`Invalid ISO8601 duration: ${duration}`);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
sign: match[1] === undefined ? "+" : (match[1] as Duration["sign"]),
|
|
86
|
+
years: parseFloatNormal(match[2]),
|
|
87
|
+
months: parseFloatNormal(match[3]),
|
|
88
|
+
weeks: parseFloatNormal(match[4]),
|
|
89
|
+
days: parseFloatNormal(match[5]),
|
|
90
|
+
hours: parseFloatNormal(match[6]),
|
|
91
|
+
minutes: parseFloatNormal(match[7]),
|
|
92
|
+
seconds: parseFloatNormal(match[8]),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function parseFloatNormal(match: string | undefined): number {
|
|
96
|
+
if (match === undefined) return 0;
|
|
97
|
+
|
|
98
|
+
const normalized = match.replace(",", ".");
|
|
99
|
+
|
|
100
|
+
const parsed = parseFloat(normalized);
|
|
101
|
+
|
|
102
|
+
if (isNaN(parsed))
|
|
103
|
+
throw new Error(`Unreachable: Invalid number in ISO8601 duration string: ${match}`);
|
|
104
|
+
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
/**
|
|
109
|
+
* Writes a Duration to an ISO8601 duration string.
|
|
110
|
+
*
|
|
111
|
+
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
|
|
112
|
+
*
|
|
113
|
+
* @param duration - the duration to write to a string
|
|
114
|
+
* @returns a string in ISO8601 duration format
|
|
115
|
+
*/
|
|
116
|
+
toISO8601(duration: Duration): string {
|
|
117
|
+
const sign = duration.sign === "+" ? "" : "-";
|
|
118
|
+
|
|
119
|
+
const years =
|
|
120
|
+
duration.years !== 0 && !isNaN(Number(duration.years)) ? `${duration.years}Y` : "";
|
|
121
|
+
const months =
|
|
122
|
+
duration.months !== 0 && !isNaN(Number(duration.months)) ? `${duration.months}M` : "";
|
|
123
|
+
const weeks =
|
|
124
|
+
duration.weeks !== 0 && !isNaN(Number(duration.weeks)) ? `${duration.weeks}W` : "";
|
|
125
|
+
const days = duration.days !== 0 && !isNaN(Number(duration.days)) ? `${duration.days}D` : "";
|
|
126
|
+
|
|
127
|
+
let time = "";
|
|
128
|
+
|
|
129
|
+
const _hours = duration.hours !== 0 && !isNaN(Number(duration.hours));
|
|
130
|
+
const _minutes = duration.minutes !== 0 && !isNaN(Number(duration.minutes));
|
|
131
|
+
const _seconds = duration.seconds !== 0 && !isNaN(Number(duration.seconds));
|
|
132
|
+
|
|
133
|
+
if (_hours || _minutes || _seconds) {
|
|
134
|
+
const hours = _hours ? `${duration.hours}H` : "";
|
|
135
|
+
const minutes = _minutes ? `${duration.minutes}M` : "";
|
|
136
|
+
const seconds = _seconds ? `${duration.seconds}S` : "";
|
|
137
|
+
|
|
138
|
+
time = `T${hours}${minutes}${seconds}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return `${sign}P${years}${months}${weeks}${days}${time}`;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Gets the total number of seconds in a duration.
|
|
146
|
+
*
|
|
147
|
+
* This method will throw an Error if the duration contains any years, months, weeks, or days, as those require a reference
|
|
148
|
+
* point to calculate the total number of seconds.
|
|
149
|
+
*
|
|
150
|
+
* WARNING: If the total number of seconds is larger than the maximum safe integer in JavaScript, this method will
|
|
151
|
+
* lose precision. @see Duration.totalSecondsBigInt for a BigInt alternative.
|
|
152
|
+
*
|
|
153
|
+
* @param duration - the duration to calculate the total number of seconds for
|
|
154
|
+
* @returns the total number of seconds in the duration
|
|
155
|
+
*/
|
|
156
|
+
totalSeconds(duration: Duration): number {
|
|
157
|
+
if (
|
|
158
|
+
duration.years !== 0 ||
|
|
159
|
+
duration.months !== 0 ||
|
|
160
|
+
duration.weeks !== 0 ||
|
|
161
|
+
duration.days !== 0
|
|
162
|
+
) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
"Cannot calculate total seconds for a duration with years, months, weeks, or days.",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
duration.seconds +
|
|
170
|
+
duration.minutes * 60 +
|
|
171
|
+
duration.hours * 60 * 60 +
|
|
172
|
+
duration.weeks * 7 * 24 * 60 * 60
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Gets the total number of seconds in a duration.
|
|
178
|
+
*
|
|
179
|
+
* This method will throw an Error if the duration contains any years, months, weeks, or days, as those require a reference
|
|
180
|
+
* point to calculate the total number of seconds. It will also throw an error if any of the components are not integers.
|
|
181
|
+
*
|
|
182
|
+
* @param duration - the duration to calculate the total number of seconds for
|
|
183
|
+
* @returns the total number of seconds in the duration
|
|
184
|
+
*/
|
|
185
|
+
totalSecondsBigInt(duration: Duration): bigint {
|
|
186
|
+
if (
|
|
187
|
+
duration.years !== 0 ||
|
|
188
|
+
duration.months !== 0 ||
|
|
189
|
+
duration.weeks !== 0 ||
|
|
190
|
+
duration.days !== 0
|
|
191
|
+
) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
"Cannot calculate total seconds for a duration with years, months, weeks, or days.",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
!Number.isInteger(duration.seconds) ||
|
|
199
|
+
!Number.isInteger(duration.minutes) ||
|
|
200
|
+
!Number.isInteger(duration.hours) ||
|
|
201
|
+
!Number.isInteger(duration.weeks)
|
|
202
|
+
) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
"Cannot calculate total seconds as a BigInt for a duration with non-integer components.",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
BigInt(duration.seconds) +
|
|
210
|
+
BigInt(duration.minutes) * 60n +
|
|
211
|
+
BigInt(duration.hours) * 60n * 60n +
|
|
212
|
+
BigInt(duration.weeks) * 7n * 24n * 60n * 60n
|
|
213
|
+
);
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Creates a duration from a total number of seconds.
|
|
218
|
+
*
|
|
219
|
+
* The result is not normalized, so it will only contain a seconds field.
|
|
220
|
+
*/
|
|
221
|
+
fromTotalSeconds(seconds: number): Duration {
|
|
222
|
+
return {
|
|
223
|
+
sign: seconds < 0 ? "-" : "+",
|
|
224
|
+
years: 0,
|
|
225
|
+
months: 0,
|
|
226
|
+
weeks: 0,
|
|
227
|
+
days: 0,
|
|
228
|
+
hours: 0,
|
|
229
|
+
minutes: 0,
|
|
230
|
+
seconds: Math.abs(seconds),
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// #endregion
|