@lovrozagar/honey 0.1.0 → 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/README.md +1596 -6
- package/dist/accepts.d.ts +6 -0
- package/dist/accepts.d.ts.map +1 -0
- package/dist/body-limit.d.ts +22 -0
- package/dist/body-limit.d.ts.map +1 -0
- package/dist/build/index.d.ts +42 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/client/error.d.ts +14 -0
- package/dist/client/error.d.ts.map +1 -0
- package/dist/client/http.d.ts +88 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/index.d.ts +22 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/sdk.d.ts +34 -0
- package/dist/client/sdk.d.ts.map +1 -0
- package/dist/client/sse.d.ts +10 -0
- package/dist/client/sse.d.ts.map +1 -0
- package/dist/client/types.d.ts +124 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/ws.d.ts +15 -0
- package/dist/client/ws.d.ts.map +1 -0
- package/dist/client-mcp/server.d.ts +10 -0
- package/dist/client-mcp/server.d.ts.map +1 -0
- package/dist/client-mcp/types.d.ts +16 -0
- package/dist/client-mcp/types.d.ts.map +1 -0
- package/dist/cloudflare/realtime.d.ts +30 -0
- package/dist/cloudflare/realtime.d.ts.map +1 -0
- package/dist/codegen-go-cli.d.ts +22 -0
- package/dist/codegen-go-cli.d.ts.map +1 -0
- package/dist/codegen-go.d.ts +29 -0
- package/dist/codegen-go.d.ts.map +1 -0
- package/dist/codegen-hash.d.ts +3 -0
- package/dist/codegen-hash.d.ts.map +1 -0
- package/dist/codegen-ir.d.ts +123 -0
- package/dist/codegen-ir.d.ts.map +1 -0
- package/dist/codegen-loaders.d.ts +12 -0
- package/dist/codegen-loaders.d.ts.map +1 -0
- package/dist/codegen-mcp.d.ts +13 -0
- package/dist/codegen-mcp.d.ts.map +1 -0
- package/dist/codegen-python.d.ts +21 -0
- package/dist/codegen-python.d.ts.map +1 -0
- package/dist/codegen-rust.d.ts +18 -0
- package/dist/codegen-rust.d.ts.map +1 -0
- package/dist/codegen-sanitize.d.ts +3 -0
- package/dist/codegen-sanitize.d.ts.map +1 -0
- package/dist/codegen-schema-naming.d.ts +22 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -0
- package/dist/codegen.d.ts +178 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/context.d.ts +56 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/cookie-sign.d.ts +3 -0
- package/dist/cookie-sign.d.ts.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cors.d.ts +13 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/crypto.d.ts +2 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/csrf.d.ts +9 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/curl-logger.d.ts +33 -0
- package/dist/curl-logger.d.ts.map +1 -0
- package/dist/detect-runtime.d.ts +16 -0
- package/dist/detect-runtime.d.ts.map +1 -0
- package/dist/error.d.ts +21 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/etag.d.ts +9 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/feature-load.d.ts +3 -0
- package/dist/feature-load.d.ts.map +1 -0
- package/dist/go-type-emitter.d.ts +33 -0
- package/dist/go-type-emitter.d.ts.map +1 -0
- package/dist/honey-response.d.ts +82 -0
- package/dist/honey-response.d.ts.map +1 -0
- package/dist/i18n-slot.d.ts +7 -0
- package/dist/i18n-slot.d.ts.map +1 -0
- package/dist/i18n.d.ts +26 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/index.d.ts +470 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init.d.ts +7 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/input.d.ts +6 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/ip-restrict.d.ts +12 -0
- package/dist/ip-restrict.d.ts.map +1 -0
- package/dist/logger.d.ts +45 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/meta-spec.d.ts +81 -0
- package/dist/meta-spec.d.ts.map +1 -0
- package/dist/middleware.d.ts +60 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/node-request.d.ts +59 -0
- package/dist/node-request.d.ts.map +1 -0
- package/dist/node.d.ts +13 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/openapi/register.d.ts +3 -0
- package/dist/openapi/register.d.ts.map +1 -0
- package/dist/openapi/scalar.d.ts +12 -0
- package/dist/openapi/scalar.d.ts.map +1 -0
- package/dist/openapi/spec-factory.d.ts +31 -0
- package/dist/openapi/spec-factory.d.ts.map +1 -0
- package/dist/openapi/spec.d.ts +19 -0
- package/dist/openapi/spec.d.ts.map +1 -0
- package/dist/openapi/swagger.d.ts +12 -0
- package/dist/openapi/swagger.d.ts.map +1 -0
- package/dist/plugin.d.ts +167 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/powered-by.d.ts +7 -0
- package/dist/powered-by.d.ts.map +1 -0
- package/dist/pretty-json.d.ts +10 -0
- package/dist/pretty-json.d.ts.map +1 -0
- package/dist/proxy.d.ts +51 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/python-type-emitter.d.ts +11 -0
- package/dist/python-type-emitter.d.ts.map +1 -0
- package/dist/realtime/buffer.d.ts +18 -0
- package/dist/realtime/buffer.d.ts.map +1 -0
- package/dist/realtime/bus.d.ts +11 -0
- package/dist/realtime/bus.d.ts.map +1 -0
- package/dist/realtime/longpoll.d.ts +23 -0
- package/dist/realtime/longpoll.d.ts.map +1 -0
- package/dist/realtime/protocol.d.ts +50 -0
- package/dist/realtime/protocol.d.ts.map +1 -0
- package/dist/realtime/route.d.ts +42 -0
- package/dist/realtime/route.d.ts.map +1 -0
- package/dist/realtime/runtime.d.ts +74 -0
- package/dist/realtime/runtime.d.ts.map +1 -0
- package/dist/request-id.d.ts +12 -0
- package/dist/request-id.d.ts.map +1 -0
- package/dist/request-to-curl.d.ts +9 -0
- package/dist/request-to-curl.d.ts.map +1 -0
- package/dist/response.d.ts +57 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/rust-type-emitter.d.ts +32 -0
- package/dist/rust-type-emitter.d.ts.map +1 -0
- package/dist/secure-headers.d.ts +16 -0
- package/dist/secure-headers.d.ts.map +1 -0
- package/dist/serve-register.d.ts +2 -0
- package/dist/serve-register.d.ts.map +1 -0
- package/dist/serve-slot.d.ts +6 -0
- package/dist/serve-slot.d.ts.map +1 -0
- package/dist/serve.d.ts +21 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/server-timing.d.ts +9 -0
- package/dist/server-timing.d.ts.map +1 -0
- package/dist/static.d.ts +12 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/telemetry/otel.d.ts +59 -0
- package/dist/telemetry/otel.d.ts.map +1 -0
- package/dist/testing.d.ts +24 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/timeout.d.ts +7 -0
- package/dist/timeout.d.ts.map +1 -0
- package/dist/tree.d.ts +90 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/ts-type-emitter.d.ts +4 -0
- package/dist/ts-type-emitter.d.ts.map +1 -0
- package/dist/type-emitter.d.ts +8 -0
- package/dist/type-emitter.d.ts.map +1 -0
- package/dist/type-extractor.d.ts +39 -0
- package/dist/type-extractor.d.ts.map +1 -0
- package/dist/types.d.ts +451 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validation.d.ts +18 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/ws/bun.d.ts +28 -0
- package/dist/ws/bun.d.ts.map +1 -0
- package/dist/ws/cloudflare.d.ts +55 -0
- package/dist/ws/cloudflare.d.ts.map +1 -0
- package/dist/ws/deno.d.ts +13 -0
- package/dist/ws/deno.d.ts.map +1 -0
- package/dist/ws/node.d.ts +10 -0
- package/dist/ws/node.d.ts.map +1 -0
- package/dist/yaml.d.ts +4 -0
- package/dist/yaml.d.ts.map +1 -0
- package/docs/api-ref/go.md +82 -82
- package/docs/api-ref/python.md +82 -82
- package/docs/api-ref/rust.md +82 -82
- package/docs/api-ref/typescript.md +82 -82
- package/docs/invalidate-spec.md +13 -13
- package/docs/meta-spec.md +513 -0
- package/docs/packaging.md +85 -0
- package/docs/sdk.md +87 -73
- package/package.json +195 -51
- package/src/body-limit.ts +2 -1
- package/src/build/index.ts +2 -9
- package/src/cli.ts +2 -3
- package/src/client/http.ts +204 -223
- package/src/client/index.ts +6 -14
- package/src/client/sdk.ts +24 -17
- package/src/client/types.ts +4 -9
- package/src/client/ws.ts +3 -5
- package/src/client-mcp/server.ts +1 -4
- package/src/cloudflare/realtime.ts +8 -10
- package/src/codegen-go-cli.ts +74 -67
- package/src/codegen-go.ts +71 -51
- package/src/codegen-ir.ts +16 -27
- package/src/codegen-loaders.ts +5 -8
- package/src/codegen-mcp.ts +9 -27
- package/src/codegen-python.ts +47 -27
- package/src/codegen-rust.ts +110 -66
- package/src/codegen-sanitize.ts +1 -7
- package/src/codegen-schema-naming.ts +8 -2
- package/src/codegen.ts +299 -196
- package/src/context.ts +91 -95
- package/src/cookie-sign.ts +5 -13
- package/src/cookie.ts +1 -2
- package/src/cors.ts +4 -6
- package/src/crypto.ts +1 -4
- package/src/csrf.ts +3 -3
- package/src/curl-logger.ts +14 -32
- package/src/detect-runtime.ts +1 -3
- package/src/errors.ts +1 -3
- package/src/etag.ts +3 -4
- package/src/go-type-emitter.ts +45 -40
- package/src/honey-response.ts +1 -3
- package/src/i18n.ts +3 -12
- package/src/index.ts +1168 -1412
- package/src/ip-restrict.ts +2 -1
- package/src/logger.ts +13 -13
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +111 -79
- package/src/node-request.ts +2 -1
- package/src/node.ts +9 -10
- package/src/openapi/register.ts +1 -2
- package/src/openapi/scalar.ts +9 -9
- package/src/openapi/spec-factory.ts +2 -2
- package/src/openapi/spec.ts +26 -19
- package/src/openapi/swagger.ts +9 -9
- package/src/plugin.ts +226 -256
- package/src/powered-by.ts +2 -1
- package/src/pretty-json.ts +2 -1
- package/src/proxy.ts +1 -3
- package/src/python-type-emitter.ts +37 -12
- package/src/realtime/route.ts +1 -1
- package/src/realtime/runtime.ts +9 -12
- package/src/request-id.ts +3 -4
- package/src/request-to-curl.ts +1 -4
- package/src/response.ts +150 -199
- package/src/rust-type-emitter.ts +66 -25
- package/src/secure-headers.ts +2 -1
- package/src/serve.ts +3 -12
- package/src/server-timing.ts +2 -1
- package/src/static.ts +3 -4
- package/src/telemetry/otel.ts +1 -6
- package/src/testing.ts +2 -11
- package/src/timeout.ts +2 -1
- package/src/tree.ts +12 -27
- package/src/ts-type-emitter.ts +71 -15
- package/src/type-emitter.ts +226 -237
- package/src/type-extractor.ts +260 -334
- package/src/types.ts +168 -68
- package/src/validation.ts +7 -30
- package/src/ws/bun.ts +2 -9
- package/src/ws/cloudflare.ts +13 -16
- package/src/ws/deno.ts +31 -40
- package/src/ws/node.ts +4 -19
package/src/codegen-rust.ts
CHANGED
|
@@ -6,7 +6,15 @@ import { collectSDKMethods, isSSEOperation, isStandardErrEnvelope } from "./code
|
|
|
6
6
|
import { detectAuthScheme } from "./codegen-go.ts"
|
|
7
7
|
import { methodsOf, namespacesOf, toIR } from "./codegen-ir.ts"
|
|
8
8
|
import type { IRNamespace, IROperation } from "./codegen-ir.ts"
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
RUST_KEYWORDS,
|
|
11
|
+
renderTopLevelRust,
|
|
12
|
+
renderUseRust,
|
|
13
|
+
rustIdent,
|
|
14
|
+
rustMethodIdent,
|
|
15
|
+
rustPascal,
|
|
16
|
+
rustSnake,
|
|
17
|
+
} from "./rust-type-emitter.ts"
|
|
10
18
|
|
|
11
19
|
export type RustSDKOptions = {
|
|
12
20
|
crateName?: string
|
|
@@ -37,7 +45,17 @@ let runtimeCache: Map<string, string> | null = null
|
|
|
37
45
|
export function loadRustRuntimeTemplates(): Map<string, string> {
|
|
38
46
|
if (runtimeCache) return runtimeCache
|
|
39
47
|
|
|
40
|
-
const names = [
|
|
48
|
+
const names = [
|
|
49
|
+
"errors.rs",
|
|
50
|
+
"result.rs",
|
|
51
|
+
"runtime.rs",
|
|
52
|
+
"invalidation.rs",
|
|
53
|
+
"invalidation_sync.rs",
|
|
54
|
+
"sse.rs",
|
|
55
|
+
"ws.rs",
|
|
56
|
+
"realtime.rs",
|
|
57
|
+
"runtime_sync.rs",
|
|
58
|
+
]
|
|
41
59
|
const cache = new Map<string, string>()
|
|
42
60
|
for (const name of names) {
|
|
43
61
|
const filePath = fileURLToPath(new URL(`./client-rust/${name}`, import.meta.url))
|
|
@@ -56,9 +74,7 @@ export function loadRustRuntimeTemplates(): Map<string, string> {
|
|
|
56
74
|
/** Validates and normalises a crate name. Throws if invalid per cargo rules. */
|
|
57
75
|
export function validateCrateName(name: string): string {
|
|
58
76
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
|
|
59
|
-
throw new Error(
|
|
60
|
-
`Invalid crate name: ${JSON.stringify(name)}. Must match ^[a-z][a-z0-9_-]*$`,
|
|
61
|
-
)
|
|
77
|
+
throw new Error(`Invalid crate name: ${JSON.stringify(name)}. Must match ^[a-z][a-z0-9_-]*$`)
|
|
62
78
|
}
|
|
63
79
|
return name
|
|
64
80
|
}
|
|
@@ -271,10 +287,7 @@ function invalidatesOf(irOp: IROperation, rawOp: Record<string, unknown>): strin
|
|
|
271
287
|
return []
|
|
272
288
|
}
|
|
273
289
|
|
|
274
|
-
function methodInfoFromIROp(
|
|
275
|
-
irOp: IROperation,
|
|
276
|
-
rawOp: Record<string, unknown>,
|
|
277
|
-
): MethodInfo {
|
|
290
|
+
function methodInfoFromIROp(irOp: IROperation, rawOp: Record<string, unknown>): MethodInfo {
|
|
278
291
|
const segments = irOp.id.split(".")
|
|
279
292
|
const isTopLevel = segments.length === 1
|
|
280
293
|
const resource = segments[0] ?? irOp.id
|
|
@@ -448,7 +461,9 @@ function emitSSEMethod(
|
|
|
448
461
|
if (qp.required) {
|
|
449
462
|
l.push(`\tquery.push((${JSON.stringify(qp.name)}.to_string(), ${fieldIdent}_owned.clone()));`)
|
|
450
463
|
} else {
|
|
451
|
-
l.push(
|
|
464
|
+
l.push(
|
|
465
|
+
`\tif let Some(ref v) = ${fieldIdent}_owned { query.push((${JSON.stringify(qp.name)}.to_string(), v.clone())); }`,
|
|
466
|
+
)
|
|
452
467
|
}
|
|
453
468
|
}
|
|
454
469
|
l.push(`\tlet cfg_clone = ${cfgAccess}.clone();`)
|
|
@@ -504,7 +519,9 @@ function emitWSMethod(
|
|
|
504
519
|
const fieldIdent = rustIdent(qp.name)
|
|
505
520
|
if (qp.required) {
|
|
506
521
|
l.push(`\tif !query_str.is_empty() { query_str.push('&'); }`)
|
|
507
|
-
l.push(
|
|
522
|
+
l.push(
|
|
523
|
+
`\tquery_str.push_str(&format!("{}={}", ${JSON.stringify(qp.name)}, urlencoding::encode(&opts.${fieldIdent})));`,
|
|
524
|
+
)
|
|
508
525
|
} else {
|
|
509
526
|
l.push(`\tif let Some(ref v) = opts.${fieldIdent} {`)
|
|
510
527
|
l.push(`\t\tif !query_str.is_empty() { query_str.push('&'); }`)
|
|
@@ -530,7 +547,9 @@ function emitWSMethod(
|
|
|
530
547
|
l.push(`\t\tif !protos.is_empty() {`)
|
|
531
548
|
l.push(`\t\t\treq.headers_mut().insert(`)
|
|
532
549
|
l.push(`\t\t\t\t"Sec-WebSocket-Protocol",`)
|
|
533
|
-
l.push(
|
|
550
|
+
l.push(
|
|
551
|
+
`\t\t\t\tprotos.join(", ").parse().map_err(|e: tokio_tungstenite::tungstenite::http::header::InvalidHeaderValue| Error::Other(e.to_string()))?,`,
|
|
552
|
+
)
|
|
534
553
|
l.push(`\t\t\t);`)
|
|
535
554
|
l.push(`\t\t}`)
|
|
536
555
|
l.push(`\t}`)
|
|
@@ -548,11 +567,7 @@ function buildAccessors(selfAccess: "self" | "self.client"): { cfg: string; stal
|
|
|
548
567
|
: { cfg: "self.client.cfg", stale: "self.client.stale", httpClient: "self.client.http_client" }
|
|
549
568
|
}
|
|
550
569
|
|
|
551
|
-
function emitMethod(
|
|
552
|
-
m: MethodInfo,
|
|
553
|
-
selfAccess: "self" | "self.client",
|
|
554
|
-
throwOnError: boolean,
|
|
555
|
-
): string[] {
|
|
570
|
+
function emitMethod(m: MethodInfo, selfAccess: "self" | "self.client", throwOnError: boolean): string[] {
|
|
556
571
|
const l: string[] = []
|
|
557
572
|
const actionFn = rustMethodIdent(m.action === "_call" ? m.resource : m.methodAction)
|
|
558
573
|
const optsName = optsStructName(m)
|
|
@@ -582,9 +597,7 @@ function emitMethod(
|
|
|
582
597
|
} else if (bi.kind === "stream") {
|
|
583
598
|
/* octet-stream body: generic over any `Stream<Item = Result<Bytes, reqwest::Error>>`
|
|
584
599
|
* (reqwest::Body::wrap_stream bound). Owned body, single-shot, non-retryable on 401. */
|
|
585
|
-
asyncGenerics.push(
|
|
586
|
-
`S: futures::Stream<Item = Result<bytes::Bytes, reqwest::Error>> + Send + Sync + 'static`,
|
|
587
|
-
)
|
|
600
|
+
asyncGenerics.push(`S: futures::Stream<Item = Result<bytes::Bytes, reqwest::Error>> + Send + Sync + 'static`)
|
|
588
601
|
params.push(`body: S`)
|
|
589
602
|
}
|
|
590
603
|
|
|
@@ -612,7 +625,9 @@ function emitMethod(
|
|
|
612
625
|
if (qp.required) {
|
|
613
626
|
l.push(`\tquery.push((${JSON.stringify(qp.name)}.to_string(), ${fieldIdent}_owned.clone()));`)
|
|
614
627
|
} else {
|
|
615
|
-
l.push(
|
|
628
|
+
l.push(
|
|
629
|
+
`\tif let Some(ref v) = ${fieldIdent}_owned { query.push((${JSON.stringify(qp.name)}.to_string(), v.clone())); }`,
|
|
630
|
+
)
|
|
616
631
|
}
|
|
617
632
|
}
|
|
618
633
|
l.push(`\tlet cfg_clone = ${cfgAccess}.clone();`)
|
|
@@ -661,7 +676,9 @@ function emitMethod(
|
|
|
661
676
|
const fieldIdent = rustIdent(qp.name)
|
|
662
677
|
if (qp.required) {
|
|
663
678
|
l.push(`\tif !query_str.is_empty() { query_str.push('&'); }`)
|
|
664
|
-
l.push(
|
|
679
|
+
l.push(
|
|
680
|
+
`\tquery_str.push_str(&format!("{}={}", ${JSON.stringify(qp.name)}, urlencoding::encode(&opts.${fieldIdent})));`,
|
|
681
|
+
)
|
|
665
682
|
} else {
|
|
666
683
|
l.push(`\tif let Some(ref v) = opts.${fieldIdent} {`)
|
|
667
684
|
l.push(`\t\tif !query_str.is_empty() { query_str.push('&'); }`)
|
|
@@ -687,7 +704,9 @@ function emitMethod(
|
|
|
687
704
|
l.push(`\t\tif !protos.is_empty() {`)
|
|
688
705
|
l.push(`\t\t\treq.headers_mut().insert(`)
|
|
689
706
|
l.push(`\t\t\t\t"Sec-WebSocket-Protocol",`)
|
|
690
|
-
l.push(
|
|
707
|
+
l.push(
|
|
708
|
+
`\t\t\t\tprotos.join(", ").parse().map_err(|e: tokio_tungstenite::tungstenite::http::header::InvalidHeaderValue| Error::Other(e.to_string()))?,`,
|
|
709
|
+
)
|
|
691
710
|
l.push(`\t\t\t);`)
|
|
692
711
|
l.push(`\t\t}`)
|
|
693
712
|
l.push(`\t}`)
|
|
@@ -741,14 +760,19 @@ function emitMethod(
|
|
|
741
760
|
* with x-invalidate; BuildRequestMeta short-circuits to None when disabled. */
|
|
742
761
|
const hasInvalidate = m.invalidates.length > 0
|
|
743
762
|
if (hasInvalidate) {
|
|
744
|
-
const pathParamsMapExpr =
|
|
745
|
-
|
|
746
|
-
|
|
763
|
+
const pathParamsMapExpr =
|
|
764
|
+
m.pathParams.length > 0
|
|
765
|
+
? `std::collections::HashMap::from([${m.pathParams.map((p) => `(${JSON.stringify(p)}.to_string(), ${rustSnake(p)}.to_string())`).join(", ")}])`
|
|
766
|
+
: `std::collections::HashMap::<String, String>::new()`
|
|
747
767
|
l.push(`\tlet _path_params_map: std::collections::HashMap<String, String> = ${pathParamsMapExpr};`)
|
|
748
|
-
l.push(
|
|
768
|
+
l.push(
|
|
769
|
+
`\tlet _concrete_path = crate::invalidation::interpolate_path(${JSON.stringify(m.path)}, &_path_params_map).unwrap_or_else(|_| ${JSON.stringify(m.path)}.to_string());`,
|
|
770
|
+
)
|
|
749
771
|
l.push(`\tlet _concrete_selector = format!("${httpMethod} {}", _concrete_path);`)
|
|
750
772
|
l.push(`\tlet _request_meta: Option<crate::invalidation::RequestMeta> = match ${staleAccess} {`)
|
|
751
|
-
l.push(
|
|
773
|
+
l.push(
|
|
774
|
+
`\t\tSome(ref s) => s.build_request_meta(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}).await,`,
|
|
775
|
+
)
|
|
752
776
|
l.push(`\t\tNone => None,`)
|
|
753
777
|
l.push(`\t};`)
|
|
754
778
|
}
|
|
@@ -761,7 +785,9 @@ function emitMethod(
|
|
|
761
785
|
* Matches TS/Python/Go emitter semantics (9.a.2 / 9.a.3 / 9.a.4). */
|
|
762
786
|
const emitIdem = m.isIdempotent && !m.isSSE && !m.isWS && !m.isRealtime
|
|
763
787
|
if (emitIdem) {
|
|
764
|
-
l.push(
|
|
788
|
+
l.push(
|
|
789
|
+
`\tlet mut _call_headers: std::collections::HashMap<String, String> = opts.headers.clone().unwrap_or_default();`,
|
|
790
|
+
)
|
|
765
791
|
l.push(`\tlet _has_idem = _call_headers.keys().any(|k| k.eq_ignore_ascii_case("Idempotency-Key"));`)
|
|
766
792
|
l.push(`\tif !_has_idem {`)
|
|
767
793
|
l.push(`\t\tlet _k = opts.idempotency_key.clone().unwrap_or_else(|| uuid::Uuid::new_v4().to_string());`)
|
|
@@ -800,7 +826,9 @@ function emitMethod(
|
|
|
800
826
|
l.push(`\t\t}`)
|
|
801
827
|
l.push(`\t\tif let Some(ref meta) = _request_meta {`)
|
|
802
828
|
l.push(`\t\t\tif meta.is_stale {`)
|
|
803
|
-
l.push(
|
|
829
|
+
l.push(
|
|
830
|
+
`\t\t\t\tstale.clear_stale(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}, meta.seq_snapshot).await;`,
|
|
831
|
+
)
|
|
804
832
|
l.push(`\t\t\t}`)
|
|
805
833
|
l.push(`\t\t}`)
|
|
806
834
|
l.push(`\t}`)
|
|
@@ -818,7 +846,9 @@ function emitMethod(
|
|
|
818
846
|
l.push(`\tOk(out)`)
|
|
819
847
|
} else {
|
|
820
848
|
l.push(`\tlet out: ${m.responseType} = serde_json::from_slice(&result.body)?;`)
|
|
821
|
-
l.push(
|
|
849
|
+
l.push(
|
|
850
|
+
`\tOk(SdkResult { data: Some(out), error: None, status: result.status, response: crate::runtime::ResponseMeta { status: result.status, headers: result.headers.clone(), url: result.url.clone(), body: result.body.clone() } })`,
|
|
851
|
+
)
|
|
822
852
|
}
|
|
823
853
|
|
|
824
854
|
l.push(`}`)
|
|
@@ -827,11 +857,7 @@ function emitMethod(
|
|
|
827
857
|
}
|
|
828
858
|
|
|
829
859
|
/** emitSyncMethod is the blocking twin of emitMethod — no async, calls do_request_blocking. */
|
|
830
|
-
function emitSyncMethod(
|
|
831
|
-
m: MethodInfo,
|
|
832
|
-
selfAccess: "self" | "self.client",
|
|
833
|
-
throwOnError: boolean,
|
|
834
|
-
): string[] {
|
|
860
|
+
function emitSyncMethod(m: MethodInfo, selfAccess: "self" | "self.client", throwOnError: boolean): string[] {
|
|
835
861
|
/* SSE and WS operations have no sync variant */
|
|
836
862
|
if (m.isSSE || m.isWS) return []
|
|
837
863
|
|
|
@@ -902,14 +928,19 @@ function emitSyncMethod(
|
|
|
902
928
|
/* invalidation pre-flight (sync) — same shape as async, no .await. */
|
|
903
929
|
const hasInvalidate = m.invalidates.length > 0
|
|
904
930
|
if (hasInvalidate) {
|
|
905
|
-
const pathParamsMapExpr =
|
|
906
|
-
|
|
907
|
-
|
|
931
|
+
const pathParamsMapExpr =
|
|
932
|
+
m.pathParams.length > 0
|
|
933
|
+
? `std::collections::HashMap::from([${m.pathParams.map((p) => `(${JSON.stringify(p)}.to_string(), ${rustSnake(p)}.to_string())`).join(", ")}])`
|
|
934
|
+
: `std::collections::HashMap::<String, String>::new()`
|
|
908
935
|
l.push(`\tlet _path_params_map: std::collections::HashMap<String, String> = ${pathParamsMapExpr};`)
|
|
909
|
-
l.push(
|
|
936
|
+
l.push(
|
|
937
|
+
`\tlet _concrete_path = crate::invalidation::interpolate_path(${JSON.stringify(m.path)}, &_path_params_map).unwrap_or_else(|_| ${JSON.stringify(m.path)}.to_string());`,
|
|
938
|
+
)
|
|
910
939
|
l.push(`\tlet _concrete_selector = format!("${httpMethod} {}", _concrete_path);`)
|
|
911
940
|
l.push(`\tlet _request_meta: Option<crate::invalidation::RequestMeta> = match ${staleAccess} {`)
|
|
912
|
-
l.push(
|
|
941
|
+
l.push(
|
|
942
|
+
`\t\tSome(ref s) => s.build_request_meta(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}),`,
|
|
943
|
+
)
|
|
913
944
|
l.push(`\t\tNone => None,`)
|
|
914
945
|
l.push(`\t};`)
|
|
915
946
|
}
|
|
@@ -917,7 +948,9 @@ function emitSyncMethod(
|
|
|
917
948
|
/* idempotency-key auto-injection (sync mirror of async path — same semantics). */
|
|
918
949
|
const emitIdem = m.isIdempotent && !m.isSSE && !m.isWS && !m.isRealtime
|
|
919
950
|
if (emitIdem) {
|
|
920
|
-
l.push(
|
|
951
|
+
l.push(
|
|
952
|
+
`\tlet mut _call_headers: std::collections::HashMap<String, String> = opts.headers.clone().unwrap_or_default();`,
|
|
953
|
+
)
|
|
921
954
|
l.push(`\tlet _has_idem = _call_headers.keys().any(|k| k.eq_ignore_ascii_case("Idempotency-Key"));`)
|
|
922
955
|
l.push(`\tif !_has_idem {`)
|
|
923
956
|
l.push(`\t\tlet _k = opts.idempotency_key.clone().unwrap_or_else(|| uuid::Uuid::new_v4().to_string());`)
|
|
@@ -952,7 +985,9 @@ function emitSyncMethod(
|
|
|
952
985
|
l.push(`\t\t}`)
|
|
953
986
|
l.push(`\t\tif let Some(ref meta) = _request_meta {`)
|
|
954
987
|
l.push(`\t\t\tif meta.is_stale {`)
|
|
955
|
-
l.push(
|
|
988
|
+
l.push(
|
|
989
|
+
`\t\t\t\tstale.clear_stale(&_concrete_selector, &_concrete_path, ${JSON.stringify(httpMethod)}, meta.seq_snapshot);`,
|
|
990
|
+
)
|
|
956
991
|
l.push(`\t\t\t}`)
|
|
957
992
|
l.push(`\t\t}`)
|
|
958
993
|
l.push(`\t}`)
|
|
@@ -970,7 +1005,9 @@ function emitSyncMethod(
|
|
|
970
1005
|
l.push(`\tOk(out)`)
|
|
971
1006
|
} else {
|
|
972
1007
|
l.push(`\tlet out: ${m.responseType} = serde_json::from_slice(&result.body)?;`)
|
|
973
|
-
l.push(
|
|
1008
|
+
l.push(
|
|
1009
|
+
`\tOk(SdkResult { data: Some(out), error: None, status: result.status, response: crate::runtime::ResponseMeta { status: result.status, headers: result.headers.clone(), url: result.url.clone(), body: result.body.clone() } })`,
|
|
1010
|
+
)
|
|
974
1011
|
}
|
|
975
1012
|
|
|
976
1013
|
l.push(`}`)
|
|
@@ -1006,7 +1043,9 @@ function emitQueryParamLoop(qps: QueryParam[], l: string[]): void {
|
|
|
1006
1043
|
l.push(`\tquery.push((${JSON.stringify(qp.name)}.to_string(), ${expr}));`)
|
|
1007
1044
|
} else {
|
|
1008
1045
|
const expr = rustQueryValueToString(qp.schema, `v`)
|
|
1009
|
-
l.push(
|
|
1046
|
+
l.push(
|
|
1047
|
+
`\tif let Some(ref v) = opts.${fieldIdent} { query.push((${JSON.stringify(qp.name)}.to_string(), ${expr})); }`,
|
|
1048
|
+
)
|
|
1010
1049
|
}
|
|
1011
1050
|
}
|
|
1012
1051
|
}
|
|
@@ -1093,7 +1132,10 @@ function buildRustTypes(spec: OpenApiSpec): string {
|
|
|
1093
1132
|
return l.join("\n")
|
|
1094
1133
|
}
|
|
1095
1134
|
|
|
1096
|
-
function buildCargoToml(
|
|
1135
|
+
function buildCargoToml(
|
|
1136
|
+
crateName: string,
|
|
1137
|
+
meta: { version: string; description?: string; homepage?: string; repository?: string; license?: string },
|
|
1138
|
+
): string {
|
|
1097
1139
|
const pkgLines = [
|
|
1098
1140
|
`[package]`,
|
|
1099
1141
|
`name = "${crateName}"`,
|
|
@@ -1186,23 +1228,25 @@ function buildRustClient(spec: OpenApiSpec, options: RustSDKOptions, ir: ReturnT
|
|
|
1186
1228
|
body.push(`}`)
|
|
1187
1229
|
body.push(``)
|
|
1188
1230
|
|
|
1189
|
-
body.push(
|
|
1231
|
+
body.push(
|
|
1232
|
+
`pub(crate) static INVALIDATION_MAP: once_cell::sync::Lazy<std::collections::HashMap<&'static str, std::collections::HashMap<&'static str, ServiceEntry>>> =`,
|
|
1233
|
+
)
|
|
1190
1234
|
body.push(`\tonce_cell::sync::Lazy::new(|| {`)
|
|
1191
1235
|
body.push(`\t\tlet mut map = std::collections::HashMap::new();`)
|
|
1192
1236
|
for (const [resource, actions] of Object.entries(serviceMap).sort(([a], [b]) => a.localeCompare(b))) {
|
|
1193
1237
|
body.push(`\t\t{`)
|
|
1194
1238
|
body.push(`\t\t\tlet mut resource_map = std::collections::HashMap::new();`)
|
|
1195
|
-
for (const [action, entry] of Object.entries(actions as Record<string, Record<string, unknown>>).sort(([a], [b]) =>
|
|
1239
|
+
for (const [action, entry] of Object.entries(actions as Record<string, Record<string, unknown>>).sort(([a], [b]) =>
|
|
1240
|
+
a.localeCompare(b),
|
|
1241
|
+
)) {
|
|
1196
1242
|
const inv = entry.invalidate as string[] | undefined
|
|
1197
|
-
const invStr = inv && inv.length > 0
|
|
1198
|
-
? `&[${inv.map((s) => JSON.stringify(s)).join(", ")}]`
|
|
1199
|
-
: `&[]`
|
|
1243
|
+
const invStr = inv && inv.length > 0 ? `&[${inv.map((s) => JSON.stringify(s)).join(", ")}]` : `&[]`
|
|
1200
1244
|
const params = entry.params as string[] | undefined
|
|
1201
|
-
const paramsStr = params && params.length > 0
|
|
1202
|
-
? `&[${params.map((s) => JSON.stringify(s)).join(", ")}]`
|
|
1203
|
-
: `&[]`
|
|
1245
|
+
const paramsStr = params && params.length > 0 ? `&[${params.map((s) => JSON.stringify(s)).join(", ")}]` : `&[]`
|
|
1204
1246
|
const isRealtime = realtimeLookup.get(`${resource}.${action}`) ?? false
|
|
1205
|
-
body.push(
|
|
1247
|
+
body.push(
|
|
1248
|
+
`\t\t\tresource_map.insert(${JSON.stringify(action)}, ServiceEntry { method: ${JSON.stringify(String(entry.method))}, path: ${JSON.stringify(String(entry.path))}, params: ${paramsStr}, sse: ${entry.sse === true}, ws: ${entry.ws === true}, realtime: ${isRealtime}, invalidate: ${invStr} });`,
|
|
1249
|
+
)
|
|
1206
1250
|
}
|
|
1207
1251
|
body.push(`\t\t\tmap.insert(${JSON.stringify(resource)}, resource_map);`)
|
|
1208
1252
|
body.push(`\t\t}`)
|
|
@@ -1256,7 +1300,9 @@ function buildRustClient(spec: OpenApiSpec, options: RustSDKOptions, ir: ReturnT
|
|
|
1256
1300
|
body.push(`\t\tClient {`)
|
|
1257
1301
|
for (const [seg] of namespacesOf(ir.tree)) {
|
|
1258
1302
|
const safeName = safeResourceName(seg)
|
|
1259
|
-
body.push(
|
|
1303
|
+
body.push(
|
|
1304
|
+
`\t\t\t${rustSnake(safeName)}: crate::resources::${rustSnake(safeName)}::new_${rustSnake(safeName)}_resource(&inner),`,
|
|
1305
|
+
)
|
|
1260
1306
|
}
|
|
1261
1307
|
body.push(`\t\t\tinner,`)
|
|
1262
1308
|
body.push(`\t\t}`)
|
|
@@ -1353,14 +1399,15 @@ function buildRustClient(spec: OpenApiSpec, options: RustSDKOptions, ir: ReturnT
|
|
|
1353
1399
|
body.push(`\t\tSyncClient {`)
|
|
1354
1400
|
for (const seg of syncNsSegs) {
|
|
1355
1401
|
const safeName = safeResourceName(seg)
|
|
1356
|
-
body.push(
|
|
1402
|
+
body.push(
|
|
1403
|
+
`\t\t\t${rustSnake(safeName)}: ${rustPascal(safeName)}ResourceSync { client: std::sync::Arc::clone(&inner) },`,
|
|
1404
|
+
)
|
|
1357
1405
|
}
|
|
1358
1406
|
body.push(`\t\t\tinner,`)
|
|
1359
1407
|
body.push(`\t\t}`)
|
|
1360
1408
|
body.push(`\t}`)
|
|
1361
1409
|
body.push(``)
|
|
1362
1410
|
|
|
1363
|
-
|
|
1364
1411
|
for (const seg of syncNsSegs) {
|
|
1365
1412
|
const safeName = safeResourceName(seg)
|
|
1366
1413
|
const structName = `${rustPascal(safeName)}ResourceSync`
|
|
@@ -1539,7 +1586,9 @@ function buildRustResources(
|
|
|
1539
1586
|
|
|
1540
1587
|
/* constructor free-fn used by Client::new / parent resource constructors */
|
|
1541
1588
|
const constructorFn = `new_${safePath.map(rustSnake).join("_")}_resource`
|
|
1542
|
-
fileLines.push(
|
|
1589
|
+
fileLines.push(
|
|
1590
|
+
`pub(crate) fn ${constructorFn}(client: &std::sync::Arc<crate::client::ClientInner>) -> ${structName} {`,
|
|
1591
|
+
)
|
|
1543
1592
|
fileLines.push(`\t${structName} {`)
|
|
1544
1593
|
fileLines.push(`\t\tclient: std::sync::Arc::clone(client),`)
|
|
1545
1594
|
for (const childSeg of childSegs) {
|
|
@@ -1585,9 +1634,7 @@ function buildRustResources(
|
|
|
1585
1634
|
|
|
1586
1635
|
/* determine file path: namespace with children → dir/mod.rs, leaf → dir.rs */
|
|
1587
1636
|
const dirPath = safePath.map(rustSnake).join("/")
|
|
1588
|
-
const filePath = hasChildren
|
|
1589
|
-
? `src/resources/${dirPath}/mod.rs`
|
|
1590
|
-
: `src/resources/${dirPath}.rs`
|
|
1637
|
+
const filePath = hasChildren ? `src/resources/${dirPath}/mod.rs` : `src/resources/${dirPath}.rs`
|
|
1591
1638
|
files.set(filePath, fileLines.join("\n"))
|
|
1592
1639
|
|
|
1593
1640
|
/* recurse into children */
|
|
@@ -1603,10 +1650,7 @@ function buildRustResources(
|
|
|
1603
1650
|
return files
|
|
1604
1651
|
}
|
|
1605
1652
|
|
|
1606
|
-
export function generateRustSDK(
|
|
1607
|
-
spec: Record<string, unknown>,
|
|
1608
|
-
options: RustSDKOptions = {},
|
|
1609
|
-
): GeneratedRustSDK {
|
|
1653
|
+
export function generateRustSDK(spec: Record<string, unknown>, options: RustSDKOptions = {}): GeneratedRustSDK {
|
|
1610
1654
|
const crateName = validateCrateName(options.crateName ?? "honey-sdk")
|
|
1611
1655
|
|
|
1612
1656
|
/* toIR validates operationId collisions — throws before any codegen runs */
|
package/src/codegen-sanitize.ts
CHANGED
|
@@ -13,13 +13,7 @@ const SCHEMA_VALUE_KEYS = new Set([
|
|
|
13
13
|
"unevaluatedProperties",
|
|
14
14
|
])
|
|
15
15
|
|
|
16
|
-
const SCHEMA_MAP_KEYS = new Set([
|
|
17
|
-
"$defs",
|
|
18
|
-
"definitions",
|
|
19
|
-
"dependentSchemas",
|
|
20
|
-
"patternProperties",
|
|
21
|
-
"properties",
|
|
22
|
-
])
|
|
16
|
+
const SCHEMA_MAP_KEYS = new Set(["$defs", "definitions", "dependentSchemas", "patternProperties", "properties"])
|
|
23
17
|
|
|
24
18
|
const SCHEMA_ARRAY_KEYS = new Set(["allOf", "anyOf", "oneOf", "prefixItems"])
|
|
25
19
|
|
|
@@ -114,7 +114,8 @@ export function deriveErrorEnvelopeName(schema: Record<string, unknown>, fallbac
|
|
|
114
114
|
const statusProp = props.status
|
|
115
115
|
let status: number | undefined
|
|
116
116
|
if (typeof statusProp.const === "number") status = statusProp.const
|
|
117
|
-
else if (Array.isArray(statusProp.enum) && statusProp.enum.length === 1 && typeof statusProp.enum[0] === "number")
|
|
117
|
+
else if (Array.isArray(statusProp.enum) && statusProp.enum.length === 1 && typeof statusProp.enum[0] === "number")
|
|
118
|
+
status = statusProp.enum[0]
|
|
118
119
|
else if (fallbackStatus !== undefined) status = fallbackStatus
|
|
119
120
|
else throw new Error("deriveErrorEnvelopeName: no status")
|
|
120
121
|
|
|
@@ -125,7 +126,12 @@ export function deriveErrorEnvelopeName(schema: Record<string, unknown>, fallbac
|
|
|
125
126
|
|
|
126
127
|
if (keys.length === 0) return `Err${status}`
|
|
127
128
|
const keysPascal = keys
|
|
128
|
-
.map((k) =>
|
|
129
|
+
.map((k) =>
|
|
130
|
+
k
|
|
131
|
+
.split(/[_-]/)
|
|
132
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
133
|
+
.join(""),
|
|
134
|
+
)
|
|
129
135
|
.join("")
|
|
130
136
|
return `Err${status}${keysPascal}`
|
|
131
137
|
}
|