@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.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