@lovrozagar/honey 0.1.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/README.md +51 -0
- package/docs/api-ref/go.md +399 -0
- package/docs/api-ref/python.md +399 -0
- package/docs/api-ref/rust.md +399 -0
- package/docs/api-ref/typescript.md +399 -0
- package/docs/invalidate-spec.md +303 -0
- package/docs/sdk.md +722 -0
- package/package.json +120 -0
- package/src/accepts.ts +78 -0
- package/src/body-limit.ts +100 -0
- package/src/build/index.ts +174 -0
- package/src/cli-go/auth.go +15 -0
- package/src/cli-go/config.go +165 -0
- package/src/cli-go/errors.go +57 -0
- package/src/cli-go/multipart.go +55 -0
- package/src/cli-go/output.go +109 -0
- package/src/cli-go/root.go +30 -0
- package/src/cli-go/stream.go +88 -0
- package/src/cli-go/version.go +5 -0
- package/src/cli.ts +204 -0
- package/src/client/error.ts +27 -0
- package/src/client/http.ts +502 -0
- package/src/client/index.ts +127 -0
- package/src/client/sdk.ts +265 -0
- package/src/client/sse.ts +102 -0
- package/src/client/types.ts +211 -0
- package/src/client/ws.ts +115 -0
- package/src/client-go/errors.go +207 -0
- package/src/client-go/invalidation.go +373 -0
- package/src/client-go/realtime.go +317 -0
- package/src/client-go/result.go +20 -0
- package/src/client-go/runtime.go +413 -0
- package/src/client-go/sse.go +149 -0
- package/src/client-go/transport.go +452 -0
- package/src/client-go/ws.go +104 -0
- package/src/client-mcp/README.md +10 -0
- package/src/client-mcp/server.ts +94 -0
- package/src/client-mcp/types.ts +19 -0
- package/src/client-python/_errors.py +115 -0
- package/src/client-python/_invalidation.py +578 -0
- package/src/client-python/_realtime.py +156 -0
- package/src/client-python/_runtime.py +469 -0
- package/src/client-python/_sse.py +99 -0
- package/src/client-python/_transport.py +335 -0
- package/src/client-python/_ws.py +131 -0
- package/src/client-python/py.typed +0 -0
- package/src/client-rust/errors.rs +200 -0
- package/src/client-rust/invalidation.rs +441 -0
- package/src/client-rust/invalidation_sync.rs +306 -0
- package/src/client-rust/realtime.rs +695 -0
- package/src/client-rust/result.rs +22 -0
- package/src/client-rust/runtime.rs +547 -0
- package/src/client-rust/runtime_sync.rs +452 -0
- package/src/client-rust/sse.rs +167 -0
- package/src/client-rust/ws.rs +143 -0
- package/src/cloudflare/realtime.ts +236 -0
- package/src/codegen-go-cli.ts +1180 -0
- package/src/codegen-go.ts +1010 -0
- package/src/codegen-hash.ts +8 -0
- package/src/codegen-ir.ts +517 -0
- package/src/codegen-loaders.ts +53 -0
- package/src/codegen-mcp.ts +364 -0
- package/src/codegen-python.ts +1028 -0
- package/src/codegen-rust.ts +1652 -0
- package/src/codegen-sanitize.ts +67 -0
- package/src/codegen-schema-naming.ts +146 -0
- package/src/codegen.ts +5015 -0
- package/src/context.ts +210 -0
- package/src/cookie-sign.ts +58 -0
- package/src/cookie.ts +89 -0
- package/src/cors.ts +96 -0
- package/src/crypto.ts +19 -0
- package/src/csrf.ts +54 -0
- package/src/curl-logger.ts +235 -0
- package/src/detect-runtime.ts +19 -0
- package/src/error.ts +52 -0
- package/src/errors.ts +87 -0
- package/src/etag.ts +74 -0
- package/src/feature-load.ts +23 -0
- package/src/go-type-emitter.ts +404 -0
- package/src/honey-response.ts +354 -0
- package/src/i18n-slot.ts +20 -0
- package/src/i18n.ts +234 -0
- package/src/index.ts +3843 -0
- package/src/init.ts +149 -0
- package/src/input.ts +8 -0
- package/src/ip-restrict.ts +192 -0
- package/src/logger.ts +149 -0
- package/src/middleware.ts +199 -0
- package/src/node-request.ts +240 -0
- package/src/node.ts +212 -0
- package/src/openapi/register.ts +19 -0
- package/src/openapi/scalar.ts +33 -0
- package/src/openapi/spec-factory.ts +46 -0
- package/src/openapi/spec.ts +31 -0
- package/src/openapi/swagger.ts +34 -0
- package/src/plugin.ts +771 -0
- package/src/powered-by.ts +17 -0
- package/src/pretty-json.ts +39 -0
- package/src/proxy.ts +155 -0
- package/src/python-type-emitter.ts +144 -0
- package/src/realtime/buffer.ts +101 -0
- package/src/realtime/bus.ts +93 -0
- package/src/realtime/longpoll.ts +148 -0
- package/src/realtime/protocol.ts +128 -0
- package/src/realtime/route.ts +104 -0
- package/src/realtime/runtime.ts +430 -0
- package/src/request-id.ts +23 -0
- package/src/request-to-curl.ts +34 -0
- package/src/response.ts +414 -0
- package/src/rust-type-emitter.ts +412 -0
- package/src/secure-headers.ts +69 -0
- package/src/serve-register.ts +8 -0
- package/src/serve-slot.ts +20 -0
- package/src/serve.ts +141 -0
- package/src/server-timing.ts +66 -0
- package/src/static.ts +60 -0
- package/src/telemetry/otel.ts +139 -0
- package/src/testing.ts +114 -0
- package/src/timeout.ts +36 -0
- package/src/tree.ts +464 -0
- package/src/ts-type-emitter.ts +106 -0
- package/src/type-emitter.ts +524 -0
- package/src/type-extractor.ts +749 -0
- package/src/types.ts +638 -0
- package/src/validation.ts +300 -0
- package/src/ws/bun.ts +89 -0
- package/src/ws/cloudflare.ts +133 -0
- package/src/ws/deno.ts +109 -0
- package/src/ws/node.ts +161 -0
- package/src/yaml.ts +80 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { schemaToIR } from "./codegen-ir.ts"
|
|
2
|
+
import type { IRSchema } from "./codegen-ir.ts"
|
|
3
|
+
|
|
4
|
+
export const GO_KEYWORDS = new Set([
|
|
5
|
+
"break", "case", "chan", "const", "continue", "default", "defer",
|
|
6
|
+
"else", "fallthrough", "for", "func", "go", "goto", "if", "import",
|
|
7
|
+
"interface", "map", "package", "range", "return", "select", "struct",
|
|
8
|
+
"switch", "type", "var",
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
/** Converts a string to PascalCase from kebab-case, snake_case, dotted, or camelCase. */
|
|
12
|
+
export function goPascal(name: string): string {
|
|
13
|
+
return name
|
|
14
|
+
.replace(/[-_.](.)/g, (_, c: string) => c.toUpperCase())
|
|
15
|
+
.replace(/^(.)/, (_, c: string) => c.toUpperCase())
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns a safe Go identifier. PascalCase makes all exported identifiers safe
|
|
20
|
+
* without keyword conflicts (Go keywords are all lowercase; Title casing avoids clashes).
|
|
21
|
+
* For unexported uses the keyword check adds `_` suffix.
|
|
22
|
+
*/
|
|
23
|
+
export function goIdent(name: string, exported = true): string {
|
|
24
|
+
const pascal = goPascal(name)
|
|
25
|
+
if (exported) return pascal
|
|
26
|
+
const lower = name.toLowerCase()
|
|
27
|
+
if (GO_KEYWORDS.has(lower)) return `${lower}_`
|
|
28
|
+
return name
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function goTag(jsonKey: string, omitempty: boolean): string {
|
|
32
|
+
return omitempty ? `\`json:"${jsonKey},omitempty"\`` : `\`json:"${jsonKey}"\``
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type RenderUseCtx = {
|
|
36
|
+
parentName: string
|
|
37
|
+
fieldName: string
|
|
38
|
+
decls: Map<string, string>
|
|
39
|
+
circularRefs?: Set<string>
|
|
40
|
+
depth?: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isNullable(schema: Record<string, unknown>): boolean {
|
|
44
|
+
if (schema.nullable === true) return true
|
|
45
|
+
const t = schema.type
|
|
46
|
+
if (Array.isArray(t) && t.includes("null")) return true
|
|
47
|
+
const variants = (schema.anyOf ?? schema.oneOf) as Record<string, unknown>[] | undefined
|
|
48
|
+
if (variants && variants.some((v) => v.type === "null")) return true
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isStringEnum(schema: Record<string, unknown>): boolean {
|
|
53
|
+
const e = schema.enum as unknown[] | undefined
|
|
54
|
+
if (!e || e.length === 0) return false
|
|
55
|
+
const t = schema.type
|
|
56
|
+
if (t === "string") return true
|
|
57
|
+
if (!t && e.every((v) => typeof v === "string")) return true
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isIntEnum(schema: Record<string, unknown>): boolean {
|
|
62
|
+
const e = schema.enum as unknown[] | undefined
|
|
63
|
+
if (!e || e.length === 0) return false
|
|
64
|
+
const t = schema.type
|
|
65
|
+
if (t === "integer") return true
|
|
66
|
+
if (!t && e.every((v) => typeof v === "number" && Number.isInteger(v))) return true
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isLiteralConst(schema: Record<string, unknown>): boolean {
|
|
71
|
+
return schema.const !== undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function hoistEnumName(parentName: string, fieldName: string): string {
|
|
75
|
+
return `${goPascal(parentName)}${goPascal(fieldName)}`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderHoistedStringEnum(typeName: string, enumVals: unknown[]): string {
|
|
79
|
+
const l: string[] = []
|
|
80
|
+
l.push(`type ${typeName} string`)
|
|
81
|
+
l.push(`const (`)
|
|
82
|
+
for (const v of enumVals) {
|
|
83
|
+
const label = typeof v === "string" ? goPascal(v) : String(v)
|
|
84
|
+
const val = typeof v === "string" ? JSON.stringify(v) : String(v)
|
|
85
|
+
l.push(`\t${typeName}${label} ${typeName} = ${val}`)
|
|
86
|
+
}
|
|
87
|
+
l.push(`)`)
|
|
88
|
+
return l.join("\n")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function renderHoistedIntEnum(typeName: string, enumVals: unknown[]): string {
|
|
92
|
+
const l: string[] = []
|
|
93
|
+
l.push(`type ${typeName} int`)
|
|
94
|
+
l.push(`const (`)
|
|
95
|
+
for (const v of enumVals) {
|
|
96
|
+
l.push(`\t${typeName}${String(v)} ${typeName} = ${String(v)}`)
|
|
97
|
+
}
|
|
98
|
+
l.push(`)`)
|
|
99
|
+
return l.join("\n")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function primitiveFor(t: string | undefined): string | null {
|
|
103
|
+
switch (t) {
|
|
104
|
+
case "string": return "string"
|
|
105
|
+
case "integer": return "int64"
|
|
106
|
+
case "number": return "float64"
|
|
107
|
+
case "boolean": return "bool"
|
|
108
|
+
default: return null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function constBaseType(val: unknown): string {
|
|
113
|
+
if (typeof val === "boolean") return "bool"
|
|
114
|
+
if (typeof val === "number") return Number.isInteger(val) ? "int64" : "float64"
|
|
115
|
+
return "string"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Mutates `ctx.decls` when hoisting enums. */
|
|
119
|
+
export function irRenderUse(ir: IRSchema, ctx: RenderUseCtx, depth = 0): string {
|
|
120
|
+
if (depth > 12) return "json.RawMessage"
|
|
121
|
+
|
|
122
|
+
switch (ir.kind) {
|
|
123
|
+
case "ref": {
|
|
124
|
+
const goName = goPascal(ir.name)
|
|
125
|
+
if (ctx.circularRefs?.has(goName)) return `*${goName}`
|
|
126
|
+
return goName
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
case "allOf": {
|
|
130
|
+
const firstRef = ir.parts.find((p) => p.kind === "ref")
|
|
131
|
+
if (firstRef && firstRef.kind === "ref") return goPascal(firstRef.name)
|
|
132
|
+
return "json.RawMessage"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case "nullable": {
|
|
136
|
+
const innerStr = irRenderUse(ir.inner, ctx, depth + 1)
|
|
137
|
+
/* already-nilable forms need no pointer prefix */
|
|
138
|
+
if (
|
|
139
|
+
innerStr === "json.RawMessage" ||
|
|
140
|
+
innerStr.startsWith("*") ||
|
|
141
|
+
innerStr.startsWith("[]") ||
|
|
142
|
+
innerStr.startsWith("map[")
|
|
143
|
+
) return innerStr
|
|
144
|
+
return `*${innerStr}`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case "union": {
|
|
148
|
+
/* union at use-position → always json.RawMessage (Go has no native union type) */
|
|
149
|
+
return "json.RawMessage"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case "scalar": {
|
|
153
|
+
if (ir.enum) {
|
|
154
|
+
const name = hoistEnumName(ctx.parentName, ctx.fieldName)
|
|
155
|
+
if (!ctx.decls.has(name)) {
|
|
156
|
+
if (ir.type === "string") {
|
|
157
|
+
ctx.decls.set(name, renderHoistedStringEnum(name, ir.enum as unknown[]))
|
|
158
|
+
} else {
|
|
159
|
+
ctx.decls.set(name, renderHoistedIntEnum(name, ir.enum as unknown[]))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return name
|
|
163
|
+
}
|
|
164
|
+
const prim = primitiveFor(ir.type)
|
|
165
|
+
if (prim) return prim
|
|
166
|
+
return "json.RawMessage"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case "const": {
|
|
170
|
+
return constBaseType(ir.value)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case "array": {
|
|
174
|
+
const el = irRenderUse(ir.items, ctx, depth + 1)
|
|
175
|
+
return `[]${el}`
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case "tuple": {
|
|
179
|
+
return `[${ir.items.length}]interface{}`
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case "object": {
|
|
183
|
+
const { fields, additional } = ir
|
|
184
|
+
|
|
185
|
+
if (fields.length === 0) {
|
|
186
|
+
if (additional) {
|
|
187
|
+
const valType = irRenderUse(additional, ctx, depth + 1)
|
|
188
|
+
return `map[string]${valType}`
|
|
189
|
+
}
|
|
190
|
+
if (additional === false) return "struct{}"
|
|
191
|
+
return "map[string]interface{}"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return irRenderAnonStruct(ir, ctx, depth)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
case "binary": {
|
|
198
|
+
/*
|
|
199
|
+
* dominant case: {type:"string", format:"binary"} → IR binary → "string".
|
|
200
|
+
* bare {format:"binary"} no-type is a 1-case deliberate divergence from raw
|
|
201
|
+
* (raw returns "json.RawMessage", IR returns "string"). Documented in spec §binary.
|
|
202
|
+
*/
|
|
203
|
+
return "string"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case "unknown": {
|
|
207
|
+
return "json.RawMessage"
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function irRenderAnonStruct(
|
|
213
|
+
ir: Extract<IRSchema, { kind: "object" }>,
|
|
214
|
+
ctx: RenderUseCtx,
|
|
215
|
+
depth: number,
|
|
216
|
+
): string {
|
|
217
|
+
const sorted = ir.fields.slice().sort((a, b) => a.name.localeCompare(b.name))
|
|
218
|
+
const lines: string[] = []
|
|
219
|
+
lines.push(`struct {`)
|
|
220
|
+
for (const field of sorted) {
|
|
221
|
+
const fieldGoName = goPascal(field.name)
|
|
222
|
+
const childParent = `${ctx.parentName}${goPascal(ctx.fieldName)}`
|
|
223
|
+
const childCtx: RenderUseCtx = {
|
|
224
|
+
circularRefs: ctx.circularRefs,
|
|
225
|
+
decls: ctx.decls,
|
|
226
|
+
depth: depth + 1,
|
|
227
|
+
fieldName: field.name,
|
|
228
|
+
parentName: childParent,
|
|
229
|
+
}
|
|
230
|
+
const fieldType = irRenderUse(field.schema, childCtx, depth + 1)
|
|
231
|
+
const alreadyNilable =
|
|
232
|
+
fieldType.startsWith("*") ||
|
|
233
|
+
fieldType.startsWith("[]") ||
|
|
234
|
+
fieldType.startsWith("map[")
|
|
235
|
+
const finalType = !field.required && !alreadyNilable ? `*${fieldType}` : fieldType
|
|
236
|
+
const tag = goTag(field.name, !field.required)
|
|
237
|
+
lines.push(`\t${fieldGoName} ${finalType} ${tag}`)
|
|
238
|
+
}
|
|
239
|
+
lines.push(`}`)
|
|
240
|
+
return lines.join("\n")
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* `raw` 4th arg is for the const-with-declared-type branch only: IR `const` kind
|
|
245
|
+
* strips the type field; `raw.type` provides the override.
|
|
246
|
+
*/
|
|
247
|
+
export function irRenderTopLevel(
|
|
248
|
+
name: string,
|
|
249
|
+
ir: IRSchema,
|
|
250
|
+
decls: Map<string, string>,
|
|
251
|
+
raw?: Record<string, unknown>,
|
|
252
|
+
): string {
|
|
253
|
+
const typeName = goPascal(name)
|
|
254
|
+
const circularRefs = new Set([typeName])
|
|
255
|
+
|
|
256
|
+
if (ir.kind === "scalar" && ir.enum) {
|
|
257
|
+
if (ir.type === "string") {
|
|
258
|
+
return renderHoistedStringEnum(typeName, ir.enum as unknown[])
|
|
259
|
+
}
|
|
260
|
+
return renderHoistedIntEnum(typeName, ir.enum as unknown[])
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (ir.kind === "const") {
|
|
264
|
+
const rawType = raw?.type as string | undefined
|
|
265
|
+
const base = rawType ? (primitiveFor(rawType) ?? "string") : constBaseType(ir.value)
|
|
266
|
+
const val = typeof ir.value === "string" ? JSON.stringify(ir.value) : String(ir.value)
|
|
267
|
+
return [
|
|
268
|
+
`type ${typeName} ${base}`,
|
|
269
|
+
`const ${typeName}Value ${typeName} = ${val}`,
|
|
270
|
+
].join("\n")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (ir.kind === "allOf") {
|
|
274
|
+
const l: string[] = []
|
|
275
|
+
l.push(`type ${typeName} struct {`)
|
|
276
|
+
for (const part of ir.parts) {
|
|
277
|
+
if (part.kind === "ref") {
|
|
278
|
+
l.push(`\t${goPascal(part.name)}`)
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
if (part.kind !== "object") continue
|
|
282
|
+
const sorted = part.fields.slice().sort((a, b) => a.name.localeCompare(b.name))
|
|
283
|
+
for (const field of sorted) {
|
|
284
|
+
const fieldGoName = goPascal(field.name)
|
|
285
|
+
const ft = irRenderUse(field.schema, {
|
|
286
|
+
circularRefs,
|
|
287
|
+
decls,
|
|
288
|
+
fieldName: field.name,
|
|
289
|
+
parentName: typeName,
|
|
290
|
+
})
|
|
291
|
+
const alreadyNilable =
|
|
292
|
+
ft.startsWith("*") || ft.startsWith("[]") || ft.startsWith("map[")
|
|
293
|
+
const finalType = !field.required && !alreadyNilable ? `*${ft}` : ft
|
|
294
|
+
l.push(`\t${fieldGoName} ${finalType} ${goTag(field.name, !field.required)}`)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
l.push(`}`)
|
|
298
|
+
return l.join("\n")
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (ir.kind === "union" && ir.discriminator) {
|
|
302
|
+
const discProp = ir.discriminator.propertyName
|
|
303
|
+
return [
|
|
304
|
+
`type ${typeName} struct {`,
|
|
305
|
+
`\tType string ${goTag(discProp, false)}`,
|
|
306
|
+
`\tPayload json.RawMessage ${goTag("payload", false)}`,
|
|
307
|
+
`}`,
|
|
308
|
+
].join("\n")
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (ir.kind === "object") {
|
|
312
|
+
const { fields, additional } = ir
|
|
313
|
+
|
|
314
|
+
if (fields.length === 0 && additional) {
|
|
315
|
+
const valType = irRenderUse(additional, {
|
|
316
|
+
circularRefs,
|
|
317
|
+
decls,
|
|
318
|
+
fieldName: "Value",
|
|
319
|
+
parentName: typeName,
|
|
320
|
+
})
|
|
321
|
+
return `type ${typeName} map[string]${valType}`
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (fields.length === 0) {
|
|
325
|
+
if (additional === false) return `type ${typeName} struct{}`
|
|
326
|
+
return `type ${typeName} map[string]interface{}`
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const sorted = fields.slice().sort((a, b) => a.name.localeCompare(b.name))
|
|
330
|
+
const l: string[] = []
|
|
331
|
+
l.push(`type ${typeName} struct {`)
|
|
332
|
+
for (const field of sorted) {
|
|
333
|
+
const fieldGoName = goPascal(field.name)
|
|
334
|
+
const ft = irRenderUse(field.schema, {
|
|
335
|
+
circularRefs,
|
|
336
|
+
decls,
|
|
337
|
+
fieldName: field.name,
|
|
338
|
+
parentName: typeName,
|
|
339
|
+
})
|
|
340
|
+
const alreadyNilable =
|
|
341
|
+
ft.startsWith("*") || ft.startsWith("[]") || ft.startsWith("map[")
|
|
342
|
+
let finalType = !field.required && !alreadyNilable ? `*${ft}` : ft
|
|
343
|
+
/* self-ref → pointer (circular) */
|
|
344
|
+
if (circularRefs.has(ft.replace(/^\*/, "")) && !finalType.startsWith("*")) {
|
|
345
|
+
finalType = `*${ft}`
|
|
346
|
+
}
|
|
347
|
+
l.push(`\t${fieldGoName} ${finalType} ${goTag(field.name, !field.required)}`)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (additional) {
|
|
351
|
+
const valType = irRenderUse(additional, {
|
|
352
|
+
circularRefs,
|
|
353
|
+
decls,
|
|
354
|
+
fieldName: "Extra",
|
|
355
|
+
parentName: typeName,
|
|
356
|
+
})
|
|
357
|
+
l.push(`\tExtra map[string]${valType} \`json:"-"\``)
|
|
358
|
+
l.push(`}`)
|
|
359
|
+
l.push(``)
|
|
360
|
+
l.push(`func (s *${typeName}) UnmarshalJSON(data []byte) error {`)
|
|
361
|
+
l.push(`\ttype plain ${typeName}`)
|
|
362
|
+
l.push(`\tvar base plain`)
|
|
363
|
+
l.push(`\tif err := json.Unmarshal(data, &base); err != nil {`)
|
|
364
|
+
l.push(`\t\treturn err`)
|
|
365
|
+
l.push(`\t}`)
|
|
366
|
+
l.push(`\t*s = ${typeName}(base)`)
|
|
367
|
+
l.push(`\treturn nil`)
|
|
368
|
+
l.push(`}`)
|
|
369
|
+
l.push(``)
|
|
370
|
+
l.push(`func (s ${typeName}) MarshalJSON() ([]byte, error) {`)
|
|
371
|
+
l.push(`\ttype plain ${typeName}`)
|
|
372
|
+
l.push(`\treturn json.Marshal(plain(s))`)
|
|
373
|
+
l.push(`}`)
|
|
374
|
+
return l.join("\n")
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
l.push(`}`)
|
|
378
|
+
return l.join("\n")
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const aliased = irRenderUse(ir, {
|
|
382
|
+
circularRefs,
|
|
383
|
+
decls,
|
|
384
|
+
fieldName: "Value",
|
|
385
|
+
parentName: typeName,
|
|
386
|
+
})
|
|
387
|
+
return `type ${typeName} = ${aliased}`
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function renderUse(
|
|
391
|
+
schema: Record<string, unknown> | undefined,
|
|
392
|
+
ctx: RenderUseCtx,
|
|
393
|
+
): string {
|
|
394
|
+
return irRenderUse(schemaToIR(schema), ctx, ctx.depth ?? 0)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function renderTopLevel(
|
|
398
|
+
name: string,
|
|
399
|
+
schema: Record<string, unknown>,
|
|
400
|
+
decls: Map<string, string>,
|
|
401
|
+
): string {
|
|
402
|
+
return irRenderTopLevel(name, schemaToIR(schema), decls, schema)
|
|
403
|
+
}
|
|
404
|
+
|