@huanglangjian/specs 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/dist/index.d.mts +864 -0
- package/dist/index.mjs +843 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +32 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
import { resolve } from "pathe";
|
|
2
|
+
import { format } from "prettier";
|
|
3
|
+
import PrettierJavaPlugin from "prettier-plugin-java";
|
|
4
|
+
import PrettierRustPlugin from "prettier-plugin-rust";
|
|
5
|
+
//#region src/type-system/basic.ts
|
|
6
|
+
function int32(options) {
|
|
7
|
+
return {
|
|
8
|
+
kind: "int32",
|
|
9
|
+
...options
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function int64(options) {
|
|
13
|
+
return {
|
|
14
|
+
kind: "int64",
|
|
15
|
+
...options
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function float32(options) {
|
|
19
|
+
return {
|
|
20
|
+
kind: "float32",
|
|
21
|
+
...options
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function float64(options) {
|
|
25
|
+
return {
|
|
26
|
+
kind: "float64",
|
|
27
|
+
...options
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function boolean(options) {
|
|
31
|
+
return {
|
|
32
|
+
kind: "boolean",
|
|
33
|
+
...options
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function string(options) {
|
|
37
|
+
return {
|
|
38
|
+
kind: "string",
|
|
39
|
+
...options
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function literal(options) {
|
|
43
|
+
return {
|
|
44
|
+
kind: "literal",
|
|
45
|
+
...options
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 创建一个可选类型模型。
|
|
50
|
+
*
|
|
51
|
+
* @param base 基础模型类型
|
|
52
|
+
* @param options 可选配置选项
|
|
53
|
+
* @returns 可选类型模型
|
|
54
|
+
*/
|
|
55
|
+
function optional(base, options) {
|
|
56
|
+
return {
|
|
57
|
+
kind: "optional",
|
|
58
|
+
base,
|
|
59
|
+
...options
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function array(base, options) {
|
|
63
|
+
return {
|
|
64
|
+
kind: "array",
|
|
65
|
+
base,
|
|
66
|
+
...options
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function set(base, options) {
|
|
70
|
+
return {
|
|
71
|
+
kind: "set",
|
|
72
|
+
base,
|
|
73
|
+
...options
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function map(base, options) {
|
|
77
|
+
return {
|
|
78
|
+
kind: "map",
|
|
79
|
+
base,
|
|
80
|
+
...options
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function record(options) {
|
|
84
|
+
return {
|
|
85
|
+
kind: "record",
|
|
86
|
+
...options
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function union(options) {
|
|
90
|
+
return {
|
|
91
|
+
kind: "union",
|
|
92
|
+
...options
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function datetime(options) {
|
|
96
|
+
return {
|
|
97
|
+
kind: "datetime",
|
|
98
|
+
...options
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function date(options) {
|
|
102
|
+
return {
|
|
103
|
+
kind: "date",
|
|
104
|
+
...options
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function duration(options) {
|
|
108
|
+
return {
|
|
109
|
+
kind: "duration",
|
|
110
|
+
...options
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function error(options) {
|
|
114
|
+
return {
|
|
115
|
+
kind: "error",
|
|
116
|
+
...options
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function enums(options) {
|
|
120
|
+
return {
|
|
121
|
+
kind: "enums",
|
|
122
|
+
...options
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function generateJsonSchema(options) {
|
|
126
|
+
const { model, target = "draft-2020-12", reference = "json-schema", depth = 0 } = options ?? {};
|
|
127
|
+
const $schema = getSchemaRef(target);
|
|
128
|
+
const convert = (partial) => ({
|
|
129
|
+
$schema: depth === 0 ? $schema : void 0,
|
|
130
|
+
title: model.title,
|
|
131
|
+
description: model.description,
|
|
132
|
+
examples: model.examples,
|
|
133
|
+
deprecated: model.deprecated,
|
|
134
|
+
default: model.default,
|
|
135
|
+
...partial,
|
|
136
|
+
..."schema" in model ? model.schema : {}
|
|
137
|
+
});
|
|
138
|
+
const getReferenceSchema = (schema) => {
|
|
139
|
+
switch (reference) {
|
|
140
|
+
case "inline": return generateJsonSchema({
|
|
141
|
+
model: schema,
|
|
142
|
+
target,
|
|
143
|
+
reference,
|
|
144
|
+
depth: depth + 1
|
|
145
|
+
});
|
|
146
|
+
case "json-schema": return schema.id != null ? { $ref: "#/$defs/" + schema.id } : generateJsonSchema({
|
|
147
|
+
model: schema,
|
|
148
|
+
target,
|
|
149
|
+
reference,
|
|
150
|
+
depth: depth + 1
|
|
151
|
+
});
|
|
152
|
+
case "openapi": return schema.id != null ? { $ref: "#/components/schemas/" + schema.id } : generateJsonSchema({
|
|
153
|
+
model: schema,
|
|
154
|
+
target,
|
|
155
|
+
reference,
|
|
156
|
+
depth: depth + 1
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
switch (model.kind) {
|
|
161
|
+
case "int32": return convert({
|
|
162
|
+
type: "integer",
|
|
163
|
+
format: "int32"
|
|
164
|
+
});
|
|
165
|
+
case "float32": return convert({
|
|
166
|
+
type: "number",
|
|
167
|
+
format: "float"
|
|
168
|
+
});
|
|
169
|
+
case "float64": return convert({
|
|
170
|
+
type: "number",
|
|
171
|
+
format: "float"
|
|
172
|
+
});
|
|
173
|
+
case "int64": return convert({
|
|
174
|
+
type: "string",
|
|
175
|
+
format: "int64"
|
|
176
|
+
});
|
|
177
|
+
case "boolean": return convert({ type: "boolean" });
|
|
178
|
+
case "string": return convert({ type: "string" });
|
|
179
|
+
case "literal": return convert({ const: model.value });
|
|
180
|
+
case "optional": {
|
|
181
|
+
const baseSchema = getReferenceSchema(model.base);
|
|
182
|
+
if ("$ref" in baseSchema) return convert({ oneOf: [baseSchema, { type: "null" }] });
|
|
183
|
+
const { type = [], ...others } = baseSchema;
|
|
184
|
+
return convert({
|
|
185
|
+
type: Array.isArray(type) ? [...new Set([...type, "null"])] : [type, "null"],
|
|
186
|
+
...others
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
case "array": return convert({
|
|
190
|
+
type: "array",
|
|
191
|
+
items: getReferenceSchema(model.base)
|
|
192
|
+
});
|
|
193
|
+
case "set": return {
|
|
194
|
+
$schema,
|
|
195
|
+
type: "array",
|
|
196
|
+
items: getReferenceSchema(model.base),
|
|
197
|
+
uniqueItems: true
|
|
198
|
+
};
|
|
199
|
+
case "map": return convert({
|
|
200
|
+
type: "object",
|
|
201
|
+
additionalProperties: getReferenceSchema(model.base)
|
|
202
|
+
});
|
|
203
|
+
case "record": return convert({
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: Object.fromEntries(Object.entries(model.properties).map(([k, v]) => [k, getReferenceSchema(v)])),
|
|
206
|
+
required: Object.entries(model.properties).filter(([_, v]) => v.kind !== "optional").map(([k]) => k),
|
|
207
|
+
additionalProperties: false
|
|
208
|
+
});
|
|
209
|
+
case "union": return convert({ oneOf: Object.entries(model.variants).map(([k, v]) => ({
|
|
210
|
+
type: "object",
|
|
211
|
+
title: v.title ?? k,
|
|
212
|
+
properties: { [k]: getReferenceSchema(v) },
|
|
213
|
+
required: [k],
|
|
214
|
+
additionalProperties: false
|
|
215
|
+
})) });
|
|
216
|
+
case "datetime": return convert({
|
|
217
|
+
type: "string",
|
|
218
|
+
format: "date-time"
|
|
219
|
+
});
|
|
220
|
+
case "date": return convert({
|
|
221
|
+
type: "string",
|
|
222
|
+
format: "date"
|
|
223
|
+
});
|
|
224
|
+
case "duration": return convert({
|
|
225
|
+
type: "string",
|
|
226
|
+
format: "duration"
|
|
227
|
+
});
|
|
228
|
+
case "error": return convert({
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
code: { const: model.code },
|
|
232
|
+
context: model.context == null ? void 0 : {
|
|
233
|
+
type: "object",
|
|
234
|
+
properties: Object.fromEntries(Object.entries(model.context).map(([k, v]) => [k, getReferenceSchema(v)])),
|
|
235
|
+
additionalProperties: false
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
additionalProperties: false
|
|
239
|
+
});
|
|
240
|
+
case "enums": return convert({
|
|
241
|
+
type: "string",
|
|
242
|
+
enum: Object.values(model.variants)
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function getSchemaRef(target) {
|
|
247
|
+
switch (target) {
|
|
248
|
+
case "draft-2020-12": return "https://json-schema.org/draft/2020-12/schema";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/type-system/http.ts
|
|
253
|
+
function generateOpenapi(options) {
|
|
254
|
+
const { info, routes, securitySchemes, tags, servers } = options;
|
|
255
|
+
const models = collectModelFromRoutes(routes);
|
|
256
|
+
return {
|
|
257
|
+
openapi: "3.2.0",
|
|
258
|
+
info,
|
|
259
|
+
paths: generatePathsObject(routes),
|
|
260
|
+
components: {
|
|
261
|
+
schemas: Object.fromEntries(generateComponentSchemasFromModels(models)),
|
|
262
|
+
securitySchemes
|
|
263
|
+
},
|
|
264
|
+
tags,
|
|
265
|
+
servers
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function collectModelFromRoutes(routes) {
|
|
269
|
+
const models = /* @__PURE__ */ new Map();
|
|
270
|
+
for (const route of routes) for (const [id, model] of collectModelsFromRoute(route)) models.set(id, model);
|
|
271
|
+
return models;
|
|
272
|
+
}
|
|
273
|
+
function collectModelDeep(model) {
|
|
274
|
+
const ret = /* @__PURE__ */ new Map();
|
|
275
|
+
if (model.id != null) ret.set(model.id, model);
|
|
276
|
+
switch (model.kind) {
|
|
277
|
+
case "optional":
|
|
278
|
+
case "array":
|
|
279
|
+
case "set":
|
|
280
|
+
case "map":
|
|
281
|
+
for (const [id, nestModel] of collectModelDeep(model.base)) ret.set(id, nestModel);
|
|
282
|
+
return ret;
|
|
283
|
+
case "record":
|
|
284
|
+
for (const property of Object.values(model.properties)) for (const [id, nestedModel] of collectModelDeep(property)) ret.set(id, nestedModel);
|
|
285
|
+
return ret;
|
|
286
|
+
case "union":
|
|
287
|
+
for (const variant of Object.values(model.variants)) for (const [id, nestedModel] of collectModelDeep(variant)) ret.set(id, nestedModel);
|
|
288
|
+
return ret;
|
|
289
|
+
case "error":
|
|
290
|
+
if (model.context) for (const detail of Object.values(model.context)) for (const [id, nestedModel] of collectModelDeep(detail)) ret.set(id, nestedModel);
|
|
291
|
+
return ret;
|
|
292
|
+
default: return ret;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function collectModelsFromRoute(route) {
|
|
296
|
+
const models = /* @__PURE__ */ new Map();
|
|
297
|
+
for (const model of [
|
|
298
|
+
route.variables,
|
|
299
|
+
route.queries,
|
|
300
|
+
route.headers,
|
|
301
|
+
route.cookies
|
|
302
|
+
].filter((o) => !!o).flatMap(Object.values)) for (const [id, subModel] of collectModelDeep(model)) models.set(id, subModel);
|
|
303
|
+
if (route.content) for (const [id, subModel] of collectModelsFromContent(route.content)) models.set(id, subModel);
|
|
304
|
+
if (route.responses) for (const response of Object.values(route.responses)) for (const [id, subModel] of collectModelsFromResponse(response)) models.set(id, subModel);
|
|
305
|
+
return models;
|
|
306
|
+
}
|
|
307
|
+
function collectModelsFromContent(content) {
|
|
308
|
+
const models = /* @__PURE__ */ new Map();
|
|
309
|
+
if (content.kind !== "binary-stream-content") {
|
|
310
|
+
if (content.model) for (const [id, subModel] of collectModelDeep(content.model)) models.set(id, subModel);
|
|
311
|
+
}
|
|
312
|
+
return models;
|
|
313
|
+
}
|
|
314
|
+
function collectModelsFromResponse(response) {
|
|
315
|
+
const models = /* @__PURE__ */ new Map();
|
|
316
|
+
for (const model of [response.cookies, response.headers].filter((o) => !!o).flatMap(Object.values)) for (const [id, subModel] of collectModelDeep(model)) models.set(id, subModel);
|
|
317
|
+
if (response.content) for (const [id, subModel] of collectModelsFromContent(response.content)) models.set(id, subModel);
|
|
318
|
+
return models;
|
|
319
|
+
}
|
|
320
|
+
function generateComponentSchemasFromModels(models) {
|
|
321
|
+
const ret = /* @__PURE__ */ new Map();
|
|
322
|
+
for (const [id, model] of models) {
|
|
323
|
+
const schema = generateJsonSchema({
|
|
324
|
+
model,
|
|
325
|
+
reference: "openapi"
|
|
326
|
+
});
|
|
327
|
+
ret.set(id, schema);
|
|
328
|
+
}
|
|
329
|
+
return ret;
|
|
330
|
+
}
|
|
331
|
+
function generatePathsObject(routes) {
|
|
332
|
+
const pathItems = /* @__PURE__ */ new Map();
|
|
333
|
+
for (const route of routes) {
|
|
334
|
+
const item = pathItems.get(route.path) ?? {};
|
|
335
|
+
pathItems.set(route.path, {
|
|
336
|
+
...item,
|
|
337
|
+
...generatePathItemObject(route)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return Object.fromEntries(pathItems);
|
|
341
|
+
}
|
|
342
|
+
function generatePathItemObject(route) {
|
|
343
|
+
return { [route.method]: generateOperationObject(route) };
|
|
344
|
+
}
|
|
345
|
+
function getSchemaReference(model) {
|
|
346
|
+
return model.id != null ? { $ref: "#/components/schemas/" + model.id } : generateJsonSchema({
|
|
347
|
+
model,
|
|
348
|
+
reference: "openapi"
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function generateOperationObject(route) {
|
|
352
|
+
const { id, summary, description, variables, queries, headers, cookies, content, responses } = route;
|
|
353
|
+
const parameterObjects = [];
|
|
354
|
+
for (const [name, model] of Object.entries(variables)) parameterObjects.push({
|
|
355
|
+
in: "path",
|
|
356
|
+
name,
|
|
357
|
+
required: true,
|
|
358
|
+
schema: getSchemaReference(model)
|
|
359
|
+
});
|
|
360
|
+
if (queries) for (const [name, model] of Object.entries(queries)) parameterObjects.push({
|
|
361
|
+
in: "query",
|
|
362
|
+
name,
|
|
363
|
+
required: model.kind !== "optional",
|
|
364
|
+
schema: getSchemaReference(model)
|
|
365
|
+
});
|
|
366
|
+
if (headers) for (const [name, model] of Object.entries(headers)) parameterObjects.push({
|
|
367
|
+
in: "header",
|
|
368
|
+
name,
|
|
369
|
+
required: model.kind !== "optional",
|
|
370
|
+
schema: getSchemaReference(model)
|
|
371
|
+
});
|
|
372
|
+
if (cookies) for (const [name, model] of Object.entries(cookies)) parameterObjects.push({
|
|
373
|
+
in: "cookie",
|
|
374
|
+
name,
|
|
375
|
+
required: model.kind !== "optional",
|
|
376
|
+
schema: getSchemaReference(model)
|
|
377
|
+
});
|
|
378
|
+
const responseObjects = /* @__PURE__ */ new Map();
|
|
379
|
+
if (responses) for (const [name, response] of Object.entries(responses)) responseObjects.set(response.status.toString(), generateResponseObjectFromResponse(name, response));
|
|
380
|
+
return {
|
|
381
|
+
operationId: id,
|
|
382
|
+
summary: summary ?? route.id,
|
|
383
|
+
description,
|
|
384
|
+
parameters: parameterObjects,
|
|
385
|
+
responses: Object.fromEntries(responseObjects),
|
|
386
|
+
requestBody: content == null ? void 0 : generateRequestBodyObject(content)
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function generateRequestBodyObject(content) {
|
|
390
|
+
switch (content.kind) {
|
|
391
|
+
case "plain-text-content":
|
|
392
|
+
case "json-content":
|
|
393
|
+
case "form-content": return { content: { [content.type]: generateMeidaTypeObject(content) } };
|
|
394
|
+
default: return {
|
|
395
|
+
description: content.description,
|
|
396
|
+
content: { [content.type]: generateMeidaTypeObject(content) }
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function generateMeidaTypeObject(content) {
|
|
401
|
+
switch (content.kind) {
|
|
402
|
+
case "plain-text-content": return { schema: content.model ? getSchemaReference(content.model) : { type: "string" } };
|
|
403
|
+
case "plain-text-stream-content": return {
|
|
404
|
+
schema: {
|
|
405
|
+
type: "string",
|
|
406
|
+
format: "binary"
|
|
407
|
+
},
|
|
408
|
+
itemSchema: content.model ? getSchemaReference(content.model) : { type: "string" }
|
|
409
|
+
};
|
|
410
|
+
case "json-content": return { schema: getSchemaReference(content.model) };
|
|
411
|
+
case "json-stream-content": return {
|
|
412
|
+
schema: {
|
|
413
|
+
type: "string",
|
|
414
|
+
format: "binary"
|
|
415
|
+
},
|
|
416
|
+
itemSchema: getSchemaReference(content.model)
|
|
417
|
+
};
|
|
418
|
+
case "binary-stream-content": return {
|
|
419
|
+
schema: {
|
|
420
|
+
type: "string",
|
|
421
|
+
format: "binary"
|
|
422
|
+
},
|
|
423
|
+
itemSchema: {
|
|
424
|
+
type: "string",
|
|
425
|
+
format: "binary"
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
case "form-content": return { schema: getSchemaReference(content.model) };
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function generateResponseObjectFromResponse(name, response) {
|
|
432
|
+
const headerObjects = /* @__PURE__ */ new Map();
|
|
433
|
+
if (response.headers) for (const [name, model] of Object.entries(response.headers)) headerObjects.set(name, {
|
|
434
|
+
schema: getSchemaReference(model),
|
|
435
|
+
required: model.kind !== "optional"
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
description: response.description ?? name,
|
|
439
|
+
headers: headerObjects.size > 0 ? Object.fromEntries(headerObjects) : void 0,
|
|
440
|
+
content: response.content == null ? void 0 : { [response.content.type]: generateMeidaTypeObject(response.content) }
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function route(options) {
|
|
444
|
+
return {
|
|
445
|
+
kind: "http-route",
|
|
446
|
+
...options
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function plainText(options) {
|
|
450
|
+
return {
|
|
451
|
+
kind: "plain-text-content",
|
|
452
|
+
...options
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function json(options) {
|
|
456
|
+
return {
|
|
457
|
+
kind: "json-content",
|
|
458
|
+
...options
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function plainTextStream(options) {
|
|
462
|
+
return {
|
|
463
|
+
kind: "plain-text-stream-content",
|
|
464
|
+
...options
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function jsonStream(options) {
|
|
468
|
+
return {
|
|
469
|
+
kind: "json-stream-content",
|
|
470
|
+
...options
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function binary(options) {
|
|
474
|
+
return {
|
|
475
|
+
kind: "binary-stream-content",
|
|
476
|
+
...options
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
function form(options) {
|
|
480
|
+
return {
|
|
481
|
+
kind: "form-content",
|
|
482
|
+
...options
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region src/generator/java.ts
|
|
487
|
+
function generateJavaClass(options) {
|
|
488
|
+
const { model, package: packageName } = options;
|
|
489
|
+
switch (model.kind) {
|
|
490
|
+
case "record": return `
|
|
491
|
+
package ${packageName};
|
|
492
|
+
|
|
493
|
+
import org.jspecify.annotations.NullMarked;
|
|
494
|
+
|
|
495
|
+
@NullMarked
|
|
496
|
+
public record ${model.id}(
|
|
497
|
+
${Object.entries(model.properties).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generatePropertyCode(packageName, k, v))}
|
|
498
|
+
){
|
|
499
|
+
${Object.entries(model.properties).filter(([__dirname, v]) => v.kind === "literal").map(([k, v]) => generateStaticPropertyCode(packageName, k, v)).join(";")}
|
|
500
|
+
}
|
|
501
|
+
`;
|
|
502
|
+
case "union": return `
|
|
503
|
+
package ${packageName};
|
|
504
|
+
|
|
505
|
+
import org.jspecify.annotations.NullMarked;
|
|
506
|
+
|
|
507
|
+
@NullMarked
|
|
508
|
+
public sealed interface ${model.id}{
|
|
509
|
+
${Object.entries(model.variants).map(([k, v]) => generateUnionVariantCode(packageName, model.id, k, v)).join("\n")}
|
|
510
|
+
}
|
|
511
|
+
`;
|
|
512
|
+
case "error": return `
|
|
513
|
+
package ${packageName};
|
|
514
|
+
|
|
515
|
+
import org.jspecify.annotations.NullMarked;
|
|
516
|
+
|
|
517
|
+
@NullMarked
|
|
518
|
+
public record ${model.id}(Context context){
|
|
519
|
+
${generateStaticPropertyCode(packageName, "code", literal({ value: model.code }))}
|
|
520
|
+
|
|
521
|
+
public ${model.id}(
|
|
522
|
+
${Object.entries(model.context ?? {}).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generatePropertyCode(packageName, k, v))}
|
|
523
|
+
){
|
|
524
|
+
this(new Context(${getPropertyNames(model.context)}));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
public record Context(
|
|
528
|
+
${Object.entries(model.context ?? {}).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generatePropertyCode(packageName, k, v))}
|
|
529
|
+
){
|
|
530
|
+
${Object.entries(model.context ?? {}).filter(([_, v]) => v.kind === "literal").map(([k, v]) => generateStaticPropertyCode(packageName, k, v)).join(";")}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
`;
|
|
534
|
+
case "enums": return `
|
|
535
|
+
|
|
536
|
+
package ${packageName};
|
|
537
|
+
|
|
538
|
+
import org.jspecify.annotations.NullMarked;
|
|
539
|
+
|
|
540
|
+
public enum ${model.id} {
|
|
541
|
+
|
|
542
|
+
${Object.entries(model.variants).map(([k, v]) => `${k}("${v}")`)};
|
|
543
|
+
|
|
544
|
+
private final String value;
|
|
545
|
+
|
|
546
|
+
public ${model.id}(String value){
|
|
547
|
+
this.value = value;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
@com.fasterxml.jackson.annotation.JsonValue
|
|
551
|
+
public String getValue() {
|
|
552
|
+
return value;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
`;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function getPropertyNames(properties) {
|
|
559
|
+
if (properties == null) return "";
|
|
560
|
+
return Object.keys(properties).join(",");
|
|
561
|
+
}
|
|
562
|
+
function generateUnionVariantCode(packageName, unionId, variantName, variant) {
|
|
563
|
+
switch (variant.kind) {
|
|
564
|
+
case "literal": return `
|
|
565
|
+
record ${variantName}() implements ${unionId} {
|
|
566
|
+
@com.fasterxml.jackson.annotation.JsonValue
|
|
567
|
+
${generateStaticPropertyCode(packageName, "value", variant)}
|
|
568
|
+
}
|
|
569
|
+
`;
|
|
570
|
+
default: return `
|
|
571
|
+
record ${variantName}(
|
|
572
|
+
@com.fasterxml.jackson.annotation.JsonCreator
|
|
573
|
+
@com.fasterxml.jackson.annotation.JsonValue
|
|
574
|
+
${generateModelSignature$1(packageName, variant)} value
|
|
575
|
+
) implements ${unionId} {}
|
|
576
|
+
`;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function generatePropertyCode(packageName, name, property) {
|
|
580
|
+
return `${generateModelSignature$1(packageName, property)} ${name}`;
|
|
581
|
+
}
|
|
582
|
+
function generateStaticPropertyCode(packageName, name, property) {
|
|
583
|
+
return `public ${generateModelSignature$1(packageName, property)} ${name} = ${generateStaticValue$1(property.value)}`;
|
|
584
|
+
}
|
|
585
|
+
function generateStaticValue$1(value) {
|
|
586
|
+
switch (typeof value) {
|
|
587
|
+
case "string": return `"${value}"`;
|
|
588
|
+
case "boolean": return String(value);
|
|
589
|
+
case "number": return Number.isInteger(value) ? String(value) : value + "d";
|
|
590
|
+
default: throw Error("Unsupported literal value");
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function generateModelSignature$1(packageName, model) {
|
|
594
|
+
switch (model.kind) {
|
|
595
|
+
case "string": return "String";
|
|
596
|
+
case "int32": return "int";
|
|
597
|
+
case "int64": return "long";
|
|
598
|
+
case "float32": return "float";
|
|
599
|
+
case "float64": return "double";
|
|
600
|
+
case "boolean": return "boolean";
|
|
601
|
+
case "date": return "java.time.LocalDate";
|
|
602
|
+
case "datetime": return "java.time.LocalDateTime";
|
|
603
|
+
case "duration": return "java.time.Duration";
|
|
604
|
+
case "array": return `Array<${generateModelSignature$1(packageName, model.base)}>`;
|
|
605
|
+
case "set": return `Set<${generateModelSignature$1(packageName, model.base)}>`;
|
|
606
|
+
case "map": return `Map<String, ${generateModelSignature$1(packageName, model.base)}>`;
|
|
607
|
+
case "optional": switch (model.base.kind) {
|
|
608
|
+
case "int32": return "@Nullable Integer";
|
|
609
|
+
case "int64": return "@Nullable Long";
|
|
610
|
+
case "float32": return "@Nullable Float";
|
|
611
|
+
case "float64": return "@Nullable Double";
|
|
612
|
+
case "boolean": return "@Nullable Boolean";
|
|
613
|
+
case "optional": return generateModelSignature$1(packageName, model.base);
|
|
614
|
+
default: return `@Nullable ${generateModelSignature$1(packageName, model.base)}`;
|
|
615
|
+
}
|
|
616
|
+
case "literal": switch (typeof model.value) {
|
|
617
|
+
case "string": return "String";
|
|
618
|
+
case "boolean": return "boolean";
|
|
619
|
+
case "number": return Number.isInteger(model.value) ? "int" : "double";
|
|
620
|
+
default: throw Error("Unsupported literal value");
|
|
621
|
+
}
|
|
622
|
+
default: return packageName + "." + model.id;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async function formatJava(code) {
|
|
626
|
+
try {
|
|
627
|
+
return await format(code, {
|
|
628
|
+
parser: "java",
|
|
629
|
+
plugins: [PrettierJavaPlugin]
|
|
630
|
+
});
|
|
631
|
+
} catch (error) {
|
|
632
|
+
console.error(error);
|
|
633
|
+
return code;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async function generateJavaCodes(options) {
|
|
637
|
+
const { package: packageName, srcDir, routes = [], models = [] } = options;
|
|
638
|
+
const codes = /* @__PURE__ */ new Map();
|
|
639
|
+
const namedModels = collectModelFromRoutes(routes);
|
|
640
|
+
for (const model of models) for (const [id, subModel] of collectModelDeep(model)) namedModels.set(id, subModel);
|
|
641
|
+
for (const [id, model] of namedModels) {
|
|
642
|
+
if (model.kind !== "union" && model.kind !== "record" && model.kind !== "enums" && model.kind !== "error") continue;
|
|
643
|
+
const path = resolve(srcDir, ...(packageName + "." + id).split("."), ".java");
|
|
644
|
+
const code = await formatJava(generateJavaClass({
|
|
645
|
+
package: packageName,
|
|
646
|
+
model
|
|
647
|
+
}));
|
|
648
|
+
codes.set(path, code);
|
|
649
|
+
}
|
|
650
|
+
return codes.entries().map(([path, code]) => ({
|
|
651
|
+
path,
|
|
652
|
+
code
|
|
653
|
+
})).toArray();
|
|
654
|
+
}
|
|
655
|
+
//#endregion
|
|
656
|
+
//#region src/generator/rust.ts
|
|
657
|
+
function generateRustCode(options) {
|
|
658
|
+
const { model } = options;
|
|
659
|
+
switch (model.kind) {
|
|
660
|
+
case "record": return generateRecordCode(model);
|
|
661
|
+
case "union": return generateUnionCode(model);
|
|
662
|
+
case "error": return generateErrorCode(model);
|
|
663
|
+
case "enums": return generateEnumCode(model);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function generateRecordCode(model) {
|
|
667
|
+
const structName = toPascalCase(model.id || "Unknown");
|
|
668
|
+
const structFields = Object.entries(model.properties).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generateStructField(k, v));
|
|
669
|
+
const constFields = Object.entries(model.properties).filter(([_, v]) => v.kind === "literal").map(([k, v]) => generateConstField(k, v));
|
|
670
|
+
const hasConstants = constFields.length > 0;
|
|
671
|
+
const hasFields = structFields.length > 0;
|
|
672
|
+
let code = `use serde::Serialize;\n\n`;
|
|
673
|
+
if (hasConstants && hasFields) {
|
|
674
|
+
code += `#[derive(Serialize)]\npub struct ${structName} {\n`;
|
|
675
|
+
structFields.forEach((f) => {
|
|
676
|
+
code += ` ${f};\n`;
|
|
677
|
+
});
|
|
678
|
+
code += `}\n\n`;
|
|
679
|
+
code += `impl ${structName} {\n`;
|
|
680
|
+
code += ` pub const CODE: &'static str = "${model.id}";\n`;
|
|
681
|
+
constFields.forEach((f) => {
|
|
682
|
+
code += ` pub ${f};\n`;
|
|
683
|
+
});
|
|
684
|
+
code += `}\n`;
|
|
685
|
+
} else if (hasFields) {
|
|
686
|
+
code += `#[derive(Serialize)]\npub struct ${structName} {\n`;
|
|
687
|
+
structFields.forEach((f) => {
|
|
688
|
+
code += ` ${f};\n`;
|
|
689
|
+
});
|
|
690
|
+
code += `}\n`;
|
|
691
|
+
} else if (hasConstants) {
|
|
692
|
+
code += `#[derive(Serialize)]\npub struct ${structName};\n\n`;
|
|
693
|
+
code += `impl ${structName} {\n`;
|
|
694
|
+
code += ` pub const CODE: &'static str = "${model.id}";\n`;
|
|
695
|
+
constFields.forEach((f) => {
|
|
696
|
+
code += ` pub ${f};\n`;
|
|
697
|
+
});
|
|
698
|
+
code += `}\n`;
|
|
699
|
+
}
|
|
700
|
+
return code;
|
|
701
|
+
}
|
|
702
|
+
function generateUnionCode(model) {
|
|
703
|
+
const enumName = toPascalCase(model.id || "Unknown");
|
|
704
|
+
let code = `use serde::Serialize;\n\n`;
|
|
705
|
+
code += `#[derive(Serialize)]\n#[serde(tag = "type", content = "value")]\npub enum ${enumName} {\n`;
|
|
706
|
+
Object.entries(model.variants).forEach(([k, v]) => {
|
|
707
|
+
const variantName = toPascalCase(k);
|
|
708
|
+
if (v.kind === "literal") code += ` ${variantName},\n`;
|
|
709
|
+
else {
|
|
710
|
+
const variantType = generateModelSignature(v);
|
|
711
|
+
code += ` ${variantName}(${variantType}),\n`;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
code += `}\n`;
|
|
715
|
+
return code;
|
|
716
|
+
}
|
|
717
|
+
function generateErrorCode(model) {
|
|
718
|
+
const structName = toPascalCase(model.id || "Unknown");
|
|
719
|
+
const contextFields = Object.entries(model.context ?? {}).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generateStructField(k, v));
|
|
720
|
+
const constFields = Object.entries(model.context ?? {}).filter(([_, v]) => v.kind === "literal").map(([k, v]) => generateConstField(k, v));
|
|
721
|
+
let code = `use serde::Serialize;\n\n`;
|
|
722
|
+
if (contextFields.length > 0 || constFields.length > 0) {
|
|
723
|
+
code += `#[derive(Serialize)]\npub struct ${structName} {\n`;
|
|
724
|
+
code += ` pub code: &'static str,\n`;
|
|
725
|
+
if (contextFields.length > 0) code += ` pub context: ${structName}Context,\n`;
|
|
726
|
+
code += `}\n\n`;
|
|
727
|
+
if (contextFields.length > 0) {
|
|
728
|
+
code += `#[derive(Serialize)]\npub struct ${structName}Context {\n`;
|
|
729
|
+
contextFields.forEach((f) => {
|
|
730
|
+
code += ` ${f};\n`;
|
|
731
|
+
});
|
|
732
|
+
code += `}\n\n`;
|
|
733
|
+
}
|
|
734
|
+
code += `impl ${structName} {\n`;
|
|
735
|
+
code += ` pub const CODE: &'static str = "${model.code}";\n`;
|
|
736
|
+
constFields.forEach((f) => {
|
|
737
|
+
code += ` pub ${f};\n`;
|
|
738
|
+
});
|
|
739
|
+
code += `}\n`;
|
|
740
|
+
} else {
|
|
741
|
+
code += `#[derive(Serialize)]\npub struct ${structName} {\n`;
|
|
742
|
+
code += ` pub code: &'static str,\n`;
|
|
743
|
+
code += `}\n\n`;
|
|
744
|
+
code += `impl ${structName} {\n`;
|
|
745
|
+
code += ` pub const CODE: &'static str = "${model.code}";\n`;
|
|
746
|
+
code += `}\n`;
|
|
747
|
+
}
|
|
748
|
+
return code;
|
|
749
|
+
}
|
|
750
|
+
function generateEnumCode(model) {
|
|
751
|
+
const enumName = toPascalCase(model.id || "Unknown");
|
|
752
|
+
let code = `use serde::Serialize;\n\n`;
|
|
753
|
+
code += `#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq)]\npub enum ${enumName} {\n`;
|
|
754
|
+
Object.entries(model.variants).forEach(([k, v]) => {
|
|
755
|
+
const variantName = toUpperSnakeCase(k);
|
|
756
|
+
code += ` #[serde(rename = "${v}")]\n`;
|
|
757
|
+
code += ` ${variantName},\n`;
|
|
758
|
+
});
|
|
759
|
+
code += `}\n`;
|
|
760
|
+
return code;
|
|
761
|
+
}
|
|
762
|
+
function generateStructField(name, property) {
|
|
763
|
+
return `pub ${toSnakeCase(name)}: ${generateModelSignature(property)}`;
|
|
764
|
+
}
|
|
765
|
+
function generateConstField(name, property) {
|
|
766
|
+
return `const ${toUpperSnakeCase(name)}: ${generateModelSignature(property)} = ${generateStaticValue(property.value)}`;
|
|
767
|
+
}
|
|
768
|
+
function generateStaticValue(value) {
|
|
769
|
+
switch (typeof value) {
|
|
770
|
+
case "string": return `"${value}"`;
|
|
771
|
+
case "boolean": return String(value);
|
|
772
|
+
case "number": return Number.isInteger(value) ? String(value) : `${value}.0`;
|
|
773
|
+
default: throw Error("Unsupported literal value");
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function generateModelSignature(model) {
|
|
777
|
+
switch (model.kind) {
|
|
778
|
+
case "string": return "String";
|
|
779
|
+
case "int32": return "i32";
|
|
780
|
+
case "int64": return "i64";
|
|
781
|
+
case "float32": return "f32";
|
|
782
|
+
case "float64": return "f64";
|
|
783
|
+
case "boolean": return "bool";
|
|
784
|
+
case "date": return "chrono::NaiveDate";
|
|
785
|
+
case "datetime": return "chrono::DateTime<chrono::Utc>";
|
|
786
|
+
case "duration": return "serde_json::Value";
|
|
787
|
+
case "array": return `Vec<${generateModelSignature(model.base)}>`;
|
|
788
|
+
case "set": return `Vec<${generateModelSignature(model.base)}>`;
|
|
789
|
+
case "map": return `std::collections::HashMap<String, ${generateModelSignature(model.base)}>`;
|
|
790
|
+
case "optional": return `Option<${generateModelSignature(model.base)}>`;
|
|
791
|
+
case "literal": switch (typeof model.value) {
|
|
792
|
+
case "string": return "&str";
|
|
793
|
+
case "boolean": return "bool";
|
|
794
|
+
case "number": return Number.isInteger(model.value) ? "i64" : "f64";
|
|
795
|
+
default: throw Error("Unsupported literal value");
|
|
796
|
+
}
|
|
797
|
+
default: return model.id ? toPascalCase(model.id) : "()";
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function toPascalCase(str) {
|
|
801
|
+
return str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (_, c) => c ? c.toUpperCase() : "");
|
|
802
|
+
}
|
|
803
|
+
function toSnakeCase(str) {
|
|
804
|
+
return str.replace(/([A-Z])/g, "_$1").replace(/[-\s]+/g, "_").replace(/^_/, "").toLowerCase();
|
|
805
|
+
}
|
|
806
|
+
function toUpperSnakeCase(str) {
|
|
807
|
+
return toSnakeCase(str).toUpperCase();
|
|
808
|
+
}
|
|
809
|
+
async function formatRust(code) {
|
|
810
|
+
try {
|
|
811
|
+
return await format(code, {
|
|
812
|
+
parser: "rust",
|
|
813
|
+
plugins: [PrettierRustPlugin]
|
|
814
|
+
});
|
|
815
|
+
} catch (error) {
|
|
816
|
+
console.error(error);
|
|
817
|
+
return code;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async function generateRustCodes(options) {
|
|
821
|
+
const { module_name, srcDir, routes = [], models = [] } = options;
|
|
822
|
+
const codes = /* @__PURE__ */ new Map();
|
|
823
|
+
const namedModels = collectModelFromRoutes(routes);
|
|
824
|
+
for (const model of models) for (const [id, subModel] of collectModelDeep(model)) namedModels.set(id, subModel);
|
|
825
|
+
for (const [id, model] of namedModels) {
|
|
826
|
+
if (model.kind !== "union" && model.kind !== "record" && model.kind !== "enums" && model.kind !== "error") continue;
|
|
827
|
+
const fullname = module_name + "::" + toSnakeCase(id);
|
|
828
|
+
const path = resolve(srcDir, ...fullname.split("::"), ".rs");
|
|
829
|
+
const code = await formatRust(generateRustCode({
|
|
830
|
+
module_name: fullname,
|
|
831
|
+
model
|
|
832
|
+
}));
|
|
833
|
+
codes.set(path, code);
|
|
834
|
+
}
|
|
835
|
+
return codes.entries().map(([path, code]) => ({
|
|
836
|
+
path,
|
|
837
|
+
code
|
|
838
|
+
})).toArray();
|
|
839
|
+
}
|
|
840
|
+
//#endregion
|
|
841
|
+
export { array, binary, boolean, collectModelDeep, collectModelFromRoutes, date, datetime, duration, enums, error, float32, float64, form, formatJava, formatRust, generateJavaClass, generateJavaCodes, generateJsonSchema, generateOpenapi, generateRustCode, generateRustCodes, int32, int64, json, jsonStream, literal, map, optional, plainText, plainTextStream, record, route, set, string, union };
|
|
842
|
+
|
|
843
|
+
//# sourceMappingURL=index.mjs.map
|