@typespec/http-server-js 0.58.0-alpha.10-dev.3
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/CHANGELOG.md +69 -0
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/build-helpers.ts +170 -0
- package/dist/generated-defs/helpers/header.d.ts +4 -0
- package/dist/generated-defs/helpers/header.d.ts.map +1 -0
- package/dist/generated-defs/helpers/header.js +76 -0
- package/dist/generated-defs/helpers/header.js.map +1 -0
- package/dist/generated-defs/helpers/http.d.ts +4 -0
- package/dist/generated-defs/helpers/http.d.ts.map +1 -0
- package/dist/generated-defs/helpers/http.js +134 -0
- package/dist/generated-defs/helpers/http.js.map +1 -0
- package/dist/generated-defs/helpers/index.d.ts +4 -0
- package/dist/generated-defs/helpers/index.d.ts.map +1 -0
- package/dist/generated-defs/helpers/index.js +21 -0
- package/dist/generated-defs/helpers/index.js.map +1 -0
- package/dist/generated-defs/helpers/multipart.d.ts +4 -0
- package/dist/generated-defs/helpers/multipart.d.ts.map +1 -0
- package/dist/generated-defs/helpers/multipart.js +249 -0
- package/dist/generated-defs/helpers/multipart.js.map +1 -0
- package/dist/generated-defs/helpers/router.d.ts +4 -0
- package/dist/generated-defs/helpers/router.d.ts.map +1 -0
- package/dist/generated-defs/helpers/router.js +259 -0
- package/dist/generated-defs/helpers/router.js.map +1 -0
- package/dist/src/common/declaration.d.ts +13 -0
- package/dist/src/common/declaration.d.ts.map +1 -0
- package/dist/src/common/declaration.js +45 -0
- package/dist/src/common/declaration.js.map +1 -0
- package/dist/src/common/documentation.d.ts +12 -0
- package/dist/src/common/documentation.d.ts.map +1 -0
- package/dist/src/common/documentation.js +21 -0
- package/dist/src/common/documentation.js.map +1 -0
- package/dist/src/common/enum.d.ts +10 -0
- package/dist/src/common/enum.d.ts.map +1 -0
- package/dist/src/common/enum.js +21 -0
- package/dist/src/common/enum.js.map +1 -0
- package/dist/src/common/interface.d.ts +50 -0
- package/dist/src/common/interface.d.ts.map +1 -0
- package/dist/src/common/interface.js +194 -0
- package/dist/src/common/interface.js.map +1 -0
- package/dist/src/common/model.d.ts +26 -0
- package/dist/src/common/model.d.ts.map +1 -0
- package/dist/src/common/model.js +115 -0
- package/dist/src/common/model.js.map +1 -0
- package/dist/src/common/namespace.d.ts +38 -0
- package/dist/src/common/namespace.d.ts.map +1 -0
- package/dist/src/common/namespace.js +184 -0
- package/dist/src/common/namespace.js.map +1 -0
- package/dist/src/common/reference.d.ts +46 -0
- package/dist/src/common/reference.d.ts.map +1 -0
- package/dist/src/common/reference.js +243 -0
- package/dist/src/common/reference.js.map +1 -0
- package/dist/src/common/scalar.d.ts +50 -0
- package/dist/src/common/scalar.d.ts.map +1 -0
- package/dist/src/common/scalar.js +144 -0
- package/dist/src/common/scalar.js.map +1 -0
- package/dist/src/common/serialization/index.d.ts +11 -0
- package/dist/src/common/serialization/index.d.ts.map +1 -0
- package/dist/src/common/serialization/index.js +72 -0
- package/dist/src/common/serialization/index.js.map +1 -0
- package/dist/src/common/serialization/json.d.ts +6 -0
- package/dist/src/common/serialization/json.d.ts.map +1 -0
- package/dist/src/common/serialization/json.js +341 -0
- package/dist/src/common/serialization/json.js.map +1 -0
- package/dist/src/common/union.d.ts +23 -0
- package/dist/src/common/union.d.ts.map +1 -0
- package/dist/src/common/union.js +57 -0
- package/dist/src/common/union.js.map +1 -0
- package/dist/src/ctx.d.ts +242 -0
- package/dist/src/ctx.d.ts.map +1 -0
- package/dist/src/ctx.js +211 -0
- package/dist/src/ctx.js.map +1 -0
- package/dist/src/helpers/header.d.ts +14 -0
- package/dist/src/helpers/header.d.ts.map +1 -0
- package/dist/src/helpers/header.js +38 -0
- package/dist/src/helpers/header.js.map +1 -0
- package/dist/src/helpers/http.d.ts +70 -0
- package/dist/src/helpers/http.d.ts.map +1 -0
- package/dist/src/helpers/http.js +86 -0
- package/dist/src/helpers/http.js.map +1 -0
- package/dist/src/helpers/multipart.d.ts +26 -0
- package/dist/src/helpers/multipart.d.ts.map +1 -0
- package/dist/src/helpers/multipart.js +182 -0
- package/dist/src/helpers/multipart.js.map +1 -0
- package/dist/src/helpers/router.d.ts +176 -0
- package/dist/src/helpers/router.d.ts.map +1 -0
- package/dist/src/helpers/router.js +55 -0
- package/dist/src/helpers/router.js.map +1 -0
- package/dist/src/http/index.d.ts +24 -0
- package/dist/src/http/index.d.ts.map +1 -0
- package/dist/src/http/index.js +52 -0
- package/dist/src/http/index.js.map +1 -0
- package/dist/src/http/server/index.d.ts +11 -0
- package/dist/src/http/server/index.d.ts.map +1 -0
- package/dist/src/http/server/index.js +413 -0
- package/dist/src/http/server/index.js.map +1 -0
- package/dist/src/http/server/multipart.d.ts +16 -0
- package/dist/src/http/server/multipart.d.ts.map +1 -0
- package/dist/src/http/server/multipart.js +214 -0
- package/dist/src/http/server/multipart.js.map +1 -0
- package/dist/src/http/server/router.d.ts +15 -0
- package/dist/src/http/server/router.d.ts.map +1 -0
- package/dist/src/http/server/router.js +459 -0
- package/dist/src/http/server/router.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib.d.ts +141 -0
- package/dist/src/lib.d.ts.map +1 -0
- package/dist/src/lib.js +116 -0
- package/dist/src/lib.js.map +1 -0
- package/dist/src/scripts/scaffold/bin.d.mts +14 -0
- package/dist/src/scripts/scaffold/bin.d.mts.map +1 -0
- package/dist/src/scripts/scaffold/bin.mjs +559 -0
- package/dist/src/scripts/scaffold/bin.mjs.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +6 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/util/case.d.ts +81 -0
- package/dist/src/util/case.d.ts.map +1 -0
- package/dist/src/util/case.js +111 -0
- package/dist/src/util/case.js.map +1 -0
- package/dist/src/util/differentiate.d.ts +251 -0
- package/dist/src/util/differentiate.d.ts.map +1 -0
- package/dist/src/util/differentiate.js +580 -0
- package/dist/src/util/differentiate.js.map +1 -0
- package/dist/src/util/error.d.ts +13 -0
- package/dist/src/util/error.d.ts.map +1 -0
- package/dist/src/util/error.js +25 -0
- package/dist/src/util/error.js.map +1 -0
- package/dist/src/util/extends.d.ts +10 -0
- package/dist/src/util/extends.d.ts.map +1 -0
- package/dist/src/util/extends.js +31 -0
- package/dist/src/util/extends.js.map +1 -0
- package/dist/src/util/iter.d.ts +39 -0
- package/dist/src/util/iter.d.ts.map +1 -0
- package/dist/src/util/iter.js +72 -0
- package/dist/src/util/iter.js.map +1 -0
- package/dist/src/util/keywords.d.ts +10 -0
- package/dist/src/util/keywords.d.ts.map +1 -0
- package/dist/src/util/keywords.js +85 -0
- package/dist/src/util/keywords.js.map +1 -0
- package/dist/src/util/name.d.ts +12 -0
- package/dist/src/util/name.d.ts.map +1 -0
- package/dist/src/util/name.js +26 -0
- package/dist/src/util/name.js.map +1 -0
- package/dist/src/util/once-queue.d.ts +24 -0
- package/dist/src/util/once-queue.d.ts.map +1 -0
- package/dist/src/util/once-queue.js +34 -0
- package/dist/src/util/once-queue.js.map +1 -0
- package/dist/src/util/openapi3.d.ts +23 -0
- package/dist/src/util/openapi3.d.ts.map +1 -0
- package/dist/src/util/openapi3.js +40 -0
- package/dist/src/util/openapi3.js.map +1 -0
- package/dist/src/util/pluralism.d.ts +23 -0
- package/dist/src/util/pluralism.d.ts.map +1 -0
- package/dist/src/util/pluralism.js +36 -0
- package/dist/src/util/pluralism.js.map +1 -0
- package/dist/src/util/scope.d.ts +85 -0
- package/dist/src/util/scope.d.ts.map +1 -0
- package/dist/src/util/scope.js +111 -0
- package/dist/src/util/scope.js.map +1 -0
- package/dist/src/write.d.ts +23 -0
- package/dist/src/write.d.ts.map +1 -0
- package/dist/src/write.js +62 -0
- package/dist/src/write.js.map +1 -0
- package/generated-defs/helpers/header.ts +83 -0
- package/generated-defs/helpers/http.ts +141 -0
- package/generated-defs/helpers/index.ts +27 -0
- package/generated-defs/helpers/multipart.ts +256 -0
- package/generated-defs/helpers/router.ts +266 -0
- package/package.json +71 -0
- package/src/common/declaration.ts +52 -0
- package/src/common/documentation.ts +26 -0
- package/src/common/enum.ts +28 -0
- package/src/common/interface.ts +264 -0
- package/src/common/model.ts +160 -0
- package/src/common/namespace.ts +243 -0
- package/src/common/reference.ts +319 -0
- package/src/common/scalar.ts +173 -0
- package/src/common/serialization/index.ts +124 -0
- package/src/common/serialization/json.ts +444 -0
- package/src/common/union.ts +76 -0
- package/src/ctx.ts +497 -0
- package/src/helpers/header.ts +55 -0
- package/src/helpers/http.ts +113 -0
- package/src/helpers/multipart.ts +228 -0
- package/src/helpers/router.ts +238 -0
- package/src/http/index.ts +81 -0
- package/src/http/server/index.ts +548 -0
- package/src/http/server/multipart.ts +272 -0
- package/src/http/server/router.ts +686 -0
- package/src/index.ts +56 -0
- package/src/lib.ts +130 -0
- package/src/scripts/scaffold/bin.mts +781 -0
- package/src/testing/index.ts +10 -0
- package/src/util/case.ts +182 -0
- package/src/util/differentiate.ts +957 -0
- package/src/util/error.ts +28 -0
- package/src/util/extends.ts +43 -0
- package/src/util/iter.ts +85 -0
- package/src/util/keywords.ts +90 -0
- package/src/util/name.ts +33 -0
- package/src/util/once-queue.ts +55 -0
- package/src/util/openapi3.ts +53 -0
- package/src/util/pluralism.ts +37 -0
- package/src/util/scope.ts +211 -0
- package/src/write.ts +88 -0
- package/temp/tsconfig.tsbuildinfo +1 -0
- package/test/header.test.ts +26 -0
- package/test/multipart.test.ts +169 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +4 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DiagnosticTarget,
|
|
6
|
+
IntrinsicType,
|
|
7
|
+
LiteralType,
|
|
8
|
+
Namespace,
|
|
9
|
+
NoTarget,
|
|
10
|
+
Type,
|
|
11
|
+
compilerAssert,
|
|
12
|
+
getEffectiveModelType,
|
|
13
|
+
getFriendlyName,
|
|
14
|
+
isArrayModelType,
|
|
15
|
+
} from "@typespec/compiler";
|
|
16
|
+
import { JsContext, Module, isImportableType } from "../ctx.js";
|
|
17
|
+
import { reportDiagnostic } from "../lib.js";
|
|
18
|
+
import { parseCase } from "../util/case.js";
|
|
19
|
+
import { asArrayType, getArrayElementName } from "../util/pluralism.js";
|
|
20
|
+
import { emitModelLiteral, emitWellKnownModel, isWellKnownModel } from "./model.js";
|
|
21
|
+
import { createOrGetModuleForNamespace } from "./namespace.js";
|
|
22
|
+
import { getJsScalar } from "./scalar.js";
|
|
23
|
+
import { emitUnionType } from "./union.js";
|
|
24
|
+
|
|
25
|
+
export type NamespacedType = Extract<Type, { namespace?: Namespace }>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Options for emitting a type reference.
|
|
29
|
+
*/
|
|
30
|
+
export interface EmitTypeReferenceOptions {
|
|
31
|
+
/**
|
|
32
|
+
* An optional alternative name to use for the type if it is not named.
|
|
33
|
+
*/
|
|
34
|
+
altName?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Require a declaration for types that may be represented anonymously.
|
|
38
|
+
*/
|
|
39
|
+
requireDeclaration?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Emits a reference to a host type.
|
|
44
|
+
*
|
|
45
|
+
* This function will automatically ensure that the referenced type is included in the emit graph, and will import the
|
|
46
|
+
* type into the current module if necessary.
|
|
47
|
+
*
|
|
48
|
+
* Optionally, a `preferredAlternativeName` may be supplied. This alternative name will be used if a declaration is
|
|
49
|
+
* required, but the type is anonymous. The alternative name can only be set once. If two callers provide different
|
|
50
|
+
* alternative names for the same anonymous type, the first one is used in all cases. If a declaration _is_ required,
|
|
51
|
+
* and no alternative name is supplied (or has been supplied in a prior call to `emitTypeReference`), this function will
|
|
52
|
+
* throw an error. Callers must be sure to provide an alternative name if the type _may_ have an unknown name. However,
|
|
53
|
+
* callers may know that they have previously emitted a reference to the type and provided an alternative name in that
|
|
54
|
+
* call, in which case the alternative name may be safely omitted.
|
|
55
|
+
*
|
|
56
|
+
* @param ctx - The emitter context.
|
|
57
|
+
* @param type - The type to emit a reference to.
|
|
58
|
+
* @param position - The syntactic position of the reference, for diagnostics.
|
|
59
|
+
* @param module - The module that the reference is being emitted into.
|
|
60
|
+
* @param preferredAlternativeName - An optional alternative name to use for the type if it is not named.
|
|
61
|
+
* @returns a string containing a reference to the TypeScript type that represents the given TypeSpec type.
|
|
62
|
+
*/
|
|
63
|
+
export function emitTypeReference(
|
|
64
|
+
ctx: JsContext,
|
|
65
|
+
type: Type,
|
|
66
|
+
position: DiagnosticTarget | typeof NoTarget,
|
|
67
|
+
module: Module,
|
|
68
|
+
options: EmitTypeReferenceOptions = {},
|
|
69
|
+
): string {
|
|
70
|
+
switch (type.kind) {
|
|
71
|
+
case "Scalar":
|
|
72
|
+
// Get the scalar and return it directly, as it is a primitive.
|
|
73
|
+
return getJsScalar(ctx.program, type, position);
|
|
74
|
+
case "Model": {
|
|
75
|
+
// First handle arrays.
|
|
76
|
+
if (isArrayModelType(ctx.program, type)) {
|
|
77
|
+
const argumentType = type.indexer.value;
|
|
78
|
+
|
|
79
|
+
const argTypeReference = emitTypeReference(ctx, argumentType, position, module, {
|
|
80
|
+
altName: options.altName && getArrayElementName(options.altName),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (isImportableType(ctx, argumentType) && argumentType.namespace) {
|
|
84
|
+
module.imports.push({
|
|
85
|
+
binder: [argTypeReference],
|
|
86
|
+
from: createOrGetModuleForNamespace(ctx, argumentType.namespace),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return asArrayType(argTypeReference);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Now other well-known models.
|
|
94
|
+
if (isWellKnownModel(ctx, type)) {
|
|
95
|
+
return emitWellKnownModel(ctx, type, module, options.altName);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Try to reduce the model to an effective model if possible.
|
|
99
|
+
const effectiveModel = getEffectiveModelType(ctx.program, type);
|
|
100
|
+
|
|
101
|
+
if (effectiveModel.name === "") {
|
|
102
|
+
// We might have seen the model before and synthesized a declaration for it already.
|
|
103
|
+
if (ctx.syntheticNames.has(effectiveModel)) {
|
|
104
|
+
const name = ctx.syntheticNames.get(effectiveModel)!;
|
|
105
|
+
module.imports.push({
|
|
106
|
+
binder: [name],
|
|
107
|
+
from: ctx.syntheticModule,
|
|
108
|
+
});
|
|
109
|
+
return name;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Require preferredAlternativeName at this point, as we have an anonymous model that we have not visited.
|
|
113
|
+
if (!options.altName) {
|
|
114
|
+
return emitModelLiteral(ctx, effectiveModel, module);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Anonymous model, synthesize a new model with the preferredName
|
|
118
|
+
ctx.synthetics.push({
|
|
119
|
+
kind: "anonymous",
|
|
120
|
+
name: options.altName,
|
|
121
|
+
underlying: effectiveModel,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
module.imports.push({
|
|
125
|
+
binder: [options.altName],
|
|
126
|
+
from: ctx.syntheticModule,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
ctx.syntheticNames.set(effectiveModel, options.altName);
|
|
130
|
+
|
|
131
|
+
return options.altName;
|
|
132
|
+
} else {
|
|
133
|
+
// The effective model is good for a declaration, so enqueue it.
|
|
134
|
+
ctx.typeQueue.add(effectiveModel);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const friendlyName = getFriendlyName(ctx.program, effectiveModel);
|
|
138
|
+
|
|
139
|
+
// The model may be a template instance, so we generate a name for it.
|
|
140
|
+
const templatedName = parseCase(
|
|
141
|
+
friendlyName
|
|
142
|
+
? friendlyName
|
|
143
|
+
: effectiveModel.templateMapper
|
|
144
|
+
? effectiveModel
|
|
145
|
+
.templateMapper!.args.map((a) => ("name" in a ? String(a.name) : ""))
|
|
146
|
+
.join("_") + effectiveModel.name
|
|
147
|
+
: effectiveModel.name,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (!effectiveModel.namespace) {
|
|
151
|
+
throw new Error("UNREACHABLE: no parent namespace of named model in emitTypeReference");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const parentModule = createOrGetModuleForNamespace(ctx, effectiveModel.namespace);
|
|
155
|
+
|
|
156
|
+
module.imports.push({
|
|
157
|
+
binder: [templatedName.pascalCase],
|
|
158
|
+
from: parentModule,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return templatedName.pascalCase;
|
|
162
|
+
}
|
|
163
|
+
case "Union": {
|
|
164
|
+
if (type.variants.size === 0) return "never";
|
|
165
|
+
else if (type.variants.size === 1)
|
|
166
|
+
return emitTypeReference(ctx, [...type.variants.values()][0], position, module, options);
|
|
167
|
+
|
|
168
|
+
if (options.requireDeclaration) {
|
|
169
|
+
if (type.name) {
|
|
170
|
+
const nameCase = parseCase(type.name);
|
|
171
|
+
|
|
172
|
+
ctx.typeQueue.add(type);
|
|
173
|
+
|
|
174
|
+
module.imports.push({
|
|
175
|
+
binder: [nameCase.pascalCase],
|
|
176
|
+
from: createOrGetModuleForNamespace(ctx, type.namespace!),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return type.name;
|
|
180
|
+
} else {
|
|
181
|
+
const existingSyntheticName = ctx.syntheticNames.get(type);
|
|
182
|
+
|
|
183
|
+
if (existingSyntheticName) {
|
|
184
|
+
module.imports.push({
|
|
185
|
+
binder: [existingSyntheticName],
|
|
186
|
+
from: ctx.syntheticModule,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return existingSyntheticName;
|
|
190
|
+
} else {
|
|
191
|
+
const altName = options.altName;
|
|
192
|
+
|
|
193
|
+
if (!altName) {
|
|
194
|
+
throw new Error("UNREACHABLE: anonymous union without preferredAlternativeName");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
ctx.synthetics.push({
|
|
198
|
+
kind: "anonymous",
|
|
199
|
+
name: altName,
|
|
200
|
+
underlying: type,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
module.imports.push({
|
|
204
|
+
binder: [altName],
|
|
205
|
+
from: ctx.syntheticModule,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
ctx.syntheticNames.set(type, altName);
|
|
209
|
+
|
|
210
|
+
return altName;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
return emitUnionType(ctx, [...type.variants.values()], module);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
case "Enum": {
|
|
218
|
+
ctx.typeQueue.add(type);
|
|
219
|
+
|
|
220
|
+
const name = parseCase(type.name).pascalCase;
|
|
221
|
+
|
|
222
|
+
module.imports.push({
|
|
223
|
+
binder: [name],
|
|
224
|
+
from: createOrGetModuleForNamespace(ctx, type.namespace!),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return name;
|
|
228
|
+
}
|
|
229
|
+
case "String":
|
|
230
|
+
return escapeUnsafeChars(JSON.stringify(type.value));
|
|
231
|
+
case "Number":
|
|
232
|
+
case "Boolean":
|
|
233
|
+
return String(type.value);
|
|
234
|
+
case "Intrinsic":
|
|
235
|
+
switch (type.name) {
|
|
236
|
+
case "never":
|
|
237
|
+
return "never";
|
|
238
|
+
case "null":
|
|
239
|
+
return "null";
|
|
240
|
+
case "void":
|
|
241
|
+
// It's a bit strange to have a void property, but it's possible, and TypeScript allows it. Void is simply
|
|
242
|
+
// only assignable from undefined or void itself.
|
|
243
|
+
return "void";
|
|
244
|
+
case "ErrorType":
|
|
245
|
+
compilerAssert(
|
|
246
|
+
false,
|
|
247
|
+
"ErrorType should not be encountered in emitTypeReference",
|
|
248
|
+
position === NoTarget ? type : position,
|
|
249
|
+
);
|
|
250
|
+
return "unknown";
|
|
251
|
+
case "unknown":
|
|
252
|
+
return "unknown";
|
|
253
|
+
default:
|
|
254
|
+
reportDiagnostic(ctx.program, {
|
|
255
|
+
code: "unrecognized-intrinsic",
|
|
256
|
+
format: { intrinsic: (type satisfies never as IntrinsicType).name },
|
|
257
|
+
target: position,
|
|
258
|
+
});
|
|
259
|
+
return "unknown";
|
|
260
|
+
}
|
|
261
|
+
case "Interface": {
|
|
262
|
+
if (type.namespace === undefined) {
|
|
263
|
+
throw new Error("UNREACHABLE: unparented interface");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const typeName = parseCase(type.name).pascalCase;
|
|
267
|
+
|
|
268
|
+
ctx.typeQueue.add(type);
|
|
269
|
+
|
|
270
|
+
const parentModule = createOrGetModuleForNamespace(ctx, type.namespace);
|
|
271
|
+
|
|
272
|
+
module.imports.push({
|
|
273
|
+
binder: [typeName],
|
|
274
|
+
from: parentModule,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
return typeName;
|
|
278
|
+
}
|
|
279
|
+
case "ModelProperty": {
|
|
280
|
+
// Forward to underlying type.
|
|
281
|
+
return emitTypeReference(ctx, type.type, position, module, options);
|
|
282
|
+
}
|
|
283
|
+
default:
|
|
284
|
+
throw new Error(`UNREACHABLE: ${type.kind}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const UNSAFE_CHAR_MAP: { [k: string]: string } = {
|
|
288
|
+
"<": "\\u003C",
|
|
289
|
+
">": "\\u003E",
|
|
290
|
+
"/": "\\u002F",
|
|
291
|
+
"\\": "\\\\",
|
|
292
|
+
"\b": "\\b",
|
|
293
|
+
"\f": "\\f",
|
|
294
|
+
"\n": "\\n",
|
|
295
|
+
"\r": "\\r",
|
|
296
|
+
"\t": "\\t",
|
|
297
|
+
"\0": "\\0",
|
|
298
|
+
"\u2028": "\\u2028",
|
|
299
|
+
"\u2029": "\\u2029",
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
export function escapeUnsafeChars(s: string) {
|
|
303
|
+
return s.replace(/[<>/\\\b\f\n\r\t\0\u2028\u2029]/g, (x) => UNSAFE_CHAR_MAP[x]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export type JsTypeSpecLiteralType = LiteralType | (IntrinsicType & { name: "null" });
|
|
307
|
+
|
|
308
|
+
export function isValueLiteralType(t: Type): t is JsTypeSpecLiteralType {
|
|
309
|
+
switch (t.kind) {
|
|
310
|
+
case "String":
|
|
311
|
+
case "Number":
|
|
312
|
+
case "Boolean":
|
|
313
|
+
return true;
|
|
314
|
+
case "Intrinsic":
|
|
315
|
+
return t.name === "null";
|
|
316
|
+
default:
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
import { DiagnosticTarget, NoTarget, Program, Scalar, formatDiagnostic } from "@typespec/compiler";
|
|
5
|
+
import { JsContext } from "../ctx.js";
|
|
6
|
+
import { reportDiagnostic } from "../lib.js";
|
|
7
|
+
import { parseCase } from "../util/case.js";
|
|
8
|
+
import { UnimplementedError } from "../util/error.js";
|
|
9
|
+
import { getFullyQualifiedTypeName } from "../util/name.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Emits a declaration for a scalar type.
|
|
13
|
+
*
|
|
14
|
+
* This is rare in TypeScript, as the scalar will ordinarily be used inline, but may be desirable in some cases.
|
|
15
|
+
*
|
|
16
|
+
* @param ctx - The emitter context.
|
|
17
|
+
* @param scalar - The scalar to emit.
|
|
18
|
+
* @returns a string that declares an alias to the scalar type in TypeScript.
|
|
19
|
+
*/
|
|
20
|
+
export function emitScalar(ctx: JsContext, scalar: Scalar): string {
|
|
21
|
+
const jsScalar = getJsScalar(ctx.program, scalar, scalar.node.id);
|
|
22
|
+
|
|
23
|
+
const name = parseCase(scalar.name).pascalCase;
|
|
24
|
+
|
|
25
|
+
return `type ${name} = ${jsScalar};`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the string parsing template for a given scalar.
|
|
30
|
+
*
|
|
31
|
+
* It is common that a scalar type is encoded as a string. For example, in HTTP path parameters or query parameters
|
|
32
|
+
* where the value may be an integer, but the APIs expose it as a string. In such cases the parse template may be
|
|
33
|
+
* used to coerce the string value to the correct scalar type.
|
|
34
|
+
*
|
|
35
|
+
* The result of this function contains the string "{}" exactly once, which should be replaced with the text of an
|
|
36
|
+
* expression evaluating to the string representation of the scalar.
|
|
37
|
+
*
|
|
38
|
+
* For example, scalars that are represented by JS `number` are parsed with the template `Number({})`, which will
|
|
39
|
+
* convert the string to a number.
|
|
40
|
+
*
|
|
41
|
+
* @param ctx - The emitter context.
|
|
42
|
+
* @param scalar - The scalar to parse from a string
|
|
43
|
+
* @returns a template expression string that can be used to parse a string into the scalar type.
|
|
44
|
+
*/
|
|
45
|
+
export function parseTemplateForScalar(ctx: JsContext, scalar: Scalar): string {
|
|
46
|
+
const jsScalar = getJsScalar(ctx.program, scalar, scalar);
|
|
47
|
+
|
|
48
|
+
switch (jsScalar) {
|
|
49
|
+
case "string":
|
|
50
|
+
return "{}";
|
|
51
|
+
case "number":
|
|
52
|
+
return "Number({})";
|
|
53
|
+
case "bigint":
|
|
54
|
+
return "BigInt({})";
|
|
55
|
+
case "Uint8Array":
|
|
56
|
+
return "Buffer.from({}, 'base64')";
|
|
57
|
+
default:
|
|
58
|
+
throw new UnimplementedError(`parse template for scalar '${jsScalar}'`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the string encoding template for a given scalar.
|
|
64
|
+
* @param ctx
|
|
65
|
+
* @param scalar
|
|
66
|
+
*/
|
|
67
|
+
export function encodeTemplateForScalar(ctx: JsContext, scalar: Scalar): string {
|
|
68
|
+
const jsScalar = getJsScalar(ctx.program, scalar, scalar);
|
|
69
|
+
|
|
70
|
+
switch (jsScalar) {
|
|
71
|
+
case "string":
|
|
72
|
+
return "{}";
|
|
73
|
+
case "number":
|
|
74
|
+
return "String({})";
|
|
75
|
+
case "bigint":
|
|
76
|
+
return "String({})";
|
|
77
|
+
case "Uint8Array":
|
|
78
|
+
return "{}.toString('base64')";
|
|
79
|
+
default:
|
|
80
|
+
throw new UnimplementedError(`encode template for scalar '${jsScalar}'`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const __JS_SCALARS_MAP = new Map<Program, Map<Scalar, string>>();
|
|
85
|
+
|
|
86
|
+
function getScalarsMap(program: Program): Map<Scalar, string> {
|
|
87
|
+
let scalars = __JS_SCALARS_MAP.get(program);
|
|
88
|
+
|
|
89
|
+
if (scalars === undefined) {
|
|
90
|
+
scalars = createScalarsMap(program);
|
|
91
|
+
__JS_SCALARS_MAP.set(program, scalars);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return scalars;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function createScalarsMap(program: Program): Map<Scalar, string> {
|
|
98
|
+
const entries = [
|
|
99
|
+
[program.resolveTypeReference("TypeSpec.bytes"), "Uint8Array"],
|
|
100
|
+
[program.resolveTypeReference("TypeSpec.boolean"), "boolean"],
|
|
101
|
+
[program.resolveTypeReference("TypeSpec.string"), "string"],
|
|
102
|
+
[program.resolveTypeReference("TypeSpec.float32"), "number"],
|
|
103
|
+
[program.resolveTypeReference("TypeSpec.float64"), "number"],
|
|
104
|
+
|
|
105
|
+
[program.resolveTypeReference("TypeSpec.uint32"), "number"],
|
|
106
|
+
[program.resolveTypeReference("TypeSpec.uint16"), "number"],
|
|
107
|
+
[program.resolveTypeReference("TypeSpec.uint8"), "number"],
|
|
108
|
+
[program.resolveTypeReference("TypeSpec.int32"), "number"],
|
|
109
|
+
[program.resolveTypeReference("TypeSpec.int16"), "number"],
|
|
110
|
+
[program.resolveTypeReference("TypeSpec.int8"), "number"],
|
|
111
|
+
|
|
112
|
+
[program.resolveTypeReference("TypeSpec.safeint"), "number"],
|
|
113
|
+
[program.resolveTypeReference("TypeSpec.integer"), "bigint"],
|
|
114
|
+
[program.resolveTypeReference("TypeSpec.plainDate"), "Date"],
|
|
115
|
+
[program.resolveTypeReference("TypeSpec.plainTime"), "Date"],
|
|
116
|
+
[program.resolveTypeReference("TypeSpec.utcDateTime"), "Date"],
|
|
117
|
+
] as const;
|
|
118
|
+
|
|
119
|
+
for (const [[type, diagnostics]] of entries) {
|
|
120
|
+
if (!type) {
|
|
121
|
+
const diagnosticString = diagnostics.map((x) => formatDiagnostic(x)).join("\n");
|
|
122
|
+
throw new Error(`failed to construct TypeSpec -> JavaScript scalar map: ${diagnosticString}`);
|
|
123
|
+
} else if (type.kind !== "Scalar") {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`type ${(type as any).name ?? "<anonymous>"} is a '${type.kind}', expected 'scalar'`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return new Map<Scalar, string>(entries.map(([[type], scalar]) => [type! as Scalar, scalar]));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Gets a TypeScript type that can represent a given TypeSpec scalar.
|
|
135
|
+
*
|
|
136
|
+
* Scalar recognition is recursive. If a scalar is not recognized, we will treat it as its parent scalar and try again.
|
|
137
|
+
*
|
|
138
|
+
* If no scalar in the chain is recognized, it will be treated as `unknown` and a warning will be issued.
|
|
139
|
+
*
|
|
140
|
+
* @param program - The program that contains the scalar
|
|
141
|
+
* @param scalar - The scalar to get the TypeScript type for
|
|
142
|
+
* @param diagnosticTarget - Where to report a diagnostic if the scalar is not recognized.
|
|
143
|
+
* @returns a string containing a TypeScript type that can represent the scalar
|
|
144
|
+
*/
|
|
145
|
+
export function getJsScalar(
|
|
146
|
+
program: Program,
|
|
147
|
+
scalar: Scalar,
|
|
148
|
+
diagnosticTarget: DiagnosticTarget | typeof NoTarget,
|
|
149
|
+
): string {
|
|
150
|
+
const scalars = getScalarsMap(program);
|
|
151
|
+
|
|
152
|
+
let _scalar: Scalar | undefined = scalar;
|
|
153
|
+
|
|
154
|
+
while (_scalar !== undefined) {
|
|
155
|
+
const jsScalar = scalars.get(_scalar);
|
|
156
|
+
|
|
157
|
+
if (jsScalar !== undefined) {
|
|
158
|
+
return jsScalar;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_scalar = _scalar.baseScalar;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
reportDiagnostic(program, {
|
|
165
|
+
code: "unrecognized-scalar",
|
|
166
|
+
target: diagnosticTarget,
|
|
167
|
+
format: {
|
|
168
|
+
scalar: getFullyQualifiedTypeName(scalar),
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return "unknown";
|
|
173
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation
|
|
2
|
+
// Licensed under the MIT license.
|
|
3
|
+
|
|
4
|
+
import { Model, NoTarget, Scalar, Type, Union } from "@typespec/compiler";
|
|
5
|
+
import { JsContext, Module, completePendingDeclarations } from "../../ctx.js";
|
|
6
|
+
import { UnimplementedError } from "../../util/error.js";
|
|
7
|
+
import { indent } from "../../util/iter.js";
|
|
8
|
+
import { createOrGetModuleForNamespace } from "../namespace.js";
|
|
9
|
+
import { emitTypeReference } from "../reference.js";
|
|
10
|
+
import { emitJsonSerialization, requiresJsonSerialization } from "./json.js";
|
|
11
|
+
|
|
12
|
+
export type SerializableType = Model | Scalar | Union;
|
|
13
|
+
|
|
14
|
+
export function isSerializableType(t: Type): t is SerializableType {
|
|
15
|
+
return t.kind === "Model" || t.kind === "Scalar" || t.kind === "Union";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type SerializationContentType = "application/json";
|
|
19
|
+
|
|
20
|
+
const _SERIALIZATIONS_MAP = new WeakMap<SerializableType, Set<SerializationContentType>>();
|
|
21
|
+
|
|
22
|
+
export function requireSerialization(
|
|
23
|
+
ctx: JsContext,
|
|
24
|
+
type: Type,
|
|
25
|
+
contentType: SerializationContentType,
|
|
26
|
+
): void {
|
|
27
|
+
if (!isSerializableType(type)) {
|
|
28
|
+
throw new UnimplementedError(`no implementation of JSON serialization for type '${type.kind}'`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let serializationsForType = _SERIALIZATIONS_MAP.get(type);
|
|
32
|
+
|
|
33
|
+
if (!serializationsForType) {
|
|
34
|
+
serializationsForType = new Set();
|
|
35
|
+
_SERIALIZATIONS_MAP.set(type, serializationsForType);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
serializationsForType.add(contentType);
|
|
39
|
+
|
|
40
|
+
ctx.serializations.add(type);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SerializationContext extends JsContext {}
|
|
44
|
+
|
|
45
|
+
export function emitSerialization(ctx: JsContext): void {
|
|
46
|
+
completePendingDeclarations(ctx);
|
|
47
|
+
|
|
48
|
+
const serializationContext: SerializationContext = {
|
|
49
|
+
...ctx,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
while (!ctx.serializations.isEmpty()) {
|
|
53
|
+
const type = ctx.serializations.take()!;
|
|
54
|
+
|
|
55
|
+
const serializations = _SERIALIZATIONS_MAP.get(type)!;
|
|
56
|
+
|
|
57
|
+
const requiredSerializations = new Set<SerializationContentType>(
|
|
58
|
+
[...serializations].filter((serialization) =>
|
|
59
|
+
isSerializationRequired(ctx, type, serialization),
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (requiredSerializations.size > 0) {
|
|
64
|
+
emitSerializationsForType(serializationContext, type, serializations);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isSerializationRequired(
|
|
70
|
+
ctx: JsContext,
|
|
71
|
+
type: Type,
|
|
72
|
+
serialization: SerializationContentType,
|
|
73
|
+
): boolean {
|
|
74
|
+
switch (serialization) {
|
|
75
|
+
case "application/json": {
|
|
76
|
+
return requiresJsonSerialization(ctx, type);
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`Unreachable: serialization content type ${serialization satisfies never}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function emitSerializationsForType(
|
|
84
|
+
ctx: SerializationContext,
|
|
85
|
+
type: SerializableType,
|
|
86
|
+
serializations: Set<SerializationContentType>,
|
|
87
|
+
): void {
|
|
88
|
+
const isSynthetic = ctx.syntheticNames.has(type) || !type.namespace;
|
|
89
|
+
|
|
90
|
+
const module = isSynthetic
|
|
91
|
+
? ctx.syntheticModule
|
|
92
|
+
: createOrGetModuleForNamespace(ctx, type.namespace!);
|
|
93
|
+
|
|
94
|
+
const typeName = emitTypeReference(ctx, type, NoTarget, module);
|
|
95
|
+
|
|
96
|
+
const serializationCode = [`export const ${typeName} = {`];
|
|
97
|
+
|
|
98
|
+
for (const serialization of serializations) {
|
|
99
|
+
serializationCode.push(
|
|
100
|
+
...indent(emitSerializationForType(ctx, type, serialization, module, typeName)),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
serializationCode.push("} as const;");
|
|
105
|
+
|
|
106
|
+
module.declarations.push(serializationCode);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function* emitSerializationForType(
|
|
110
|
+
ctx: SerializationContext,
|
|
111
|
+
type: SerializableType,
|
|
112
|
+
contentType: SerializationContentType,
|
|
113
|
+
module: Module,
|
|
114
|
+
typeName: string,
|
|
115
|
+
): Iterable<string> {
|
|
116
|
+
switch (contentType) {
|
|
117
|
+
case "application/json": {
|
|
118
|
+
yield* emitJsonSerialization(ctx, type, module, typeName);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
default:
|
|
122
|
+
throw new Error(`Unreachable: serialization content type ${contentType satisfies never}`);
|
|
123
|
+
}
|
|
124
|
+
}
|