@lovrozagar/honey 0.6.8 → 0.7.1
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 +514 -192
- package/bin/honey.js +25 -0
- package/dist/accepts.d.ts +7 -1
- package/dist/accepts.d.ts.map +1 -1
- package/dist/accepts.js +126 -0
- package/dist/accepts.js.map +1 -0
- package/dist/body-kind.d.ts +26 -0
- package/dist/body-kind.d.ts.map +1 -0
- package/dist/body-kind.js +70 -0
- package/dist/body-kind.js.map +1 -0
- package/dist/body-limit.d.ts +5 -1
- package/dist/body-limit.d.ts.map +1 -1
- package/dist/body-limit.js +126 -0
- package/dist/body-limit.js.map +1 -0
- package/dist/build/index.d.ts +19 -3
- package/dist/build/index.d.ts.map +1 -1
- package/dist/build/index.js +173 -0
- package/dist/build/index.js.map +1 -0
- package/dist/cli-args.d.ts +33 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +102 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-go/auth.go +26 -0
- package/dist/cli-go/config.go +192 -0
- package/dist/cli-go/errors.go +75 -0
- package/dist/cli-go/multipart.go +79 -0
- package/dist/cli-go/output.go +163 -0
- package/dist/cli-go/root.go +30 -0
- package/dist/cli-go/stream.go +88 -0
- package/dist/cli-go/version.go +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +209 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/defaults.js +48 -0
- package/dist/client/defaults.js.map +1 -0
- package/dist/client/error.d.ts +35 -0
- package/dist/client/error.d.ts.map +1 -1
- package/dist/client/error.js +71 -0
- package/dist/client/error.js.map +1 -0
- package/dist/client/http.d.ts +67 -8
- package/dist/client/http.d.ts.map +1 -1
- package/dist/client/http.js +571 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +95 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/path.d.ts +26 -0
- package/dist/client/path.d.ts.map +1 -0
- package/dist/client/path.js +163 -0
- package/dist/client/path.js.map +1 -0
- package/dist/client/redirect-policy.d.ts +12 -0
- package/dist/client/redirect-policy.d.ts.map +1 -0
- package/dist/client/redirect-policy.js +20 -0
- package/dist/client/redirect-policy.js.map +1 -0
- package/dist/client/sdk.d.ts +36 -0
- package/dist/client/sdk.d.ts.map +1 -1
- package/dist/client/sdk.js +292 -0
- package/dist/client/sdk.js.map +1 -0
- package/dist/client/sse.d.ts +13 -0
- package/dist/client/sse.d.ts.map +1 -1
- package/dist/client/sse.js +129 -0
- package/dist/client/sse.js.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/ws.d.ts +9 -2
- package/dist/client/ws.d.ts.map +1 -1
- package/dist/client/ws.js +112 -0
- package/dist/client/ws.js.map +1 -0
- package/dist/client-go/errors.go +236 -0
- package/dist/client-go/export.go +44 -0
- package/dist/client-go/invalidation.go +371 -0
- package/dist/client-go/nullable.go +62 -0
- package/dist/client-go/realtime.go +354 -0
- package/dist/client-go/result.go +20 -0
- package/dist/client-go/runtime.go +975 -0
- package/dist/client-go/sse.go +159 -0
- package/dist/client-go/transport.go +495 -0
- package/dist/client-go/ws.go +128 -0
- package/dist/client-info.d.ts +36 -0
- package/dist/client-info.d.ts.map +1 -0
- package/dist/client-info.js +53 -0
- package/dist/client-info.js.map +1 -0
- package/dist/client-mcp/README.md +10 -0
- package/dist/client-mcp/server.ts +221 -0
- package/dist/client-mcp/types.ts +19 -0
- package/dist/client-python/_errors.py +115 -0
- package/dist/client-python/_invalidation.py +494 -0
- package/dist/client-python/_realtime.py +179 -0
- package/dist/client-python/_runtime.py +860 -0
- package/dist/client-python/_sse.py +155 -0
- package/dist/client-python/_transport.py +366 -0
- package/dist/client-python/_ws.py +159 -0
- package/dist/client-python/py.typed +0 -0
- package/dist/client-rust/errors.rs +220 -0
- package/dist/client-rust/invalidation.rs +412 -0
- package/dist/client-rust/invalidation_sync.rs +93 -0
- package/dist/client-rust/nullable.rs +17 -0
- package/dist/client-rust/realtime.rs +702 -0
- package/dist/client-rust/result.rs +22 -0
- package/dist/client-rust/runtime.rs +980 -0
- package/dist/client-rust/runtime_sync.rs +484 -0
- package/dist/client-rust/sse.rs +188 -0
- package/dist/client-rust/ws.rs +167 -0
- package/dist/codegen-go-cli.d.ts.map +1 -1
- package/dist/codegen-go-cli.js +964 -0
- package/dist/codegen-go-cli.js.map +1 -0
- package/dist/codegen-go.d.ts +15 -3
- package/dist/codegen-go.d.ts.map +1 -1
- package/dist/codegen-go.js +872 -0
- package/dist/codegen-go.js.map +1 -0
- package/dist/codegen-hash.js +9 -0
- package/dist/codegen-hash.js.map +1 -0
- package/dist/codegen-ir.d.ts +69 -2
- package/dist/codegen-ir.d.ts.map +1 -1
- package/dist/codegen-ir.js +661 -0
- package/dist/codegen-ir.js.map +1 -0
- package/dist/codegen-lang.d.ts +65 -0
- package/dist/codegen-lang.d.ts.map +1 -0
- package/dist/codegen-lang.js +526 -0
- package/dist/codegen-lang.js.map +1 -0
- package/dist/codegen-load.d.ts +31 -0
- package/dist/codegen-load.d.ts.map +1 -0
- package/dist/codegen-load.js +138 -0
- package/dist/codegen-load.js.map +1 -0
- package/dist/codegen-loaders.js +55 -0
- package/dist/codegen-loaders.js.map +1 -0
- package/dist/codegen-mcp.d.ts.map +1 -1
- package/dist/codegen-mcp.js +368 -0
- package/dist/codegen-mcp.js.map +1 -0
- package/dist/codegen-python.d.ts +13 -1
- package/dist/codegen-python.d.ts.map +1 -1
- package/dist/codegen-python.js +828 -0
- package/dist/codegen-python.js.map +1 -0
- package/dist/codegen-route-tree-intern.d.ts +15 -5
- package/dist/codegen-route-tree-intern.d.ts.map +1 -1
- package/dist/codegen-route-tree-intern.js +221 -0
- package/dist/codegen-route-tree-intern.js.map +1 -0
- package/dist/codegen-rust.d.ts.map +1 -1
- package/dist/codegen-rust.js +1293 -0
- package/dist/codegen-rust.js.map +1 -0
- package/dist/codegen-sanitize.d.ts.map +1 -1
- package/dist/codegen-sanitize.js +56 -0
- package/dist/codegen-sanitize.js.map +1 -0
- package/dist/codegen-schema-naming.d.ts +5 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -1
- package/dist/codegen-schema-naming.js +150 -0
- package/dist/codegen-schema-naming.js.map +1 -0
- package/dist/codegen-sdk-model.d.ts +71 -0
- package/dist/codegen-sdk-model.d.ts.map +1 -0
- package/dist/codegen-sdk-model.js +179 -0
- package/dist/codegen-sdk-model.js.map +1 -0
- package/dist/codegen.d.ts +41 -23
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +4610 -0
- package/dist/codegen.js.map +1 -0
- package/dist/context.d.ts +36 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +242 -0
- package/dist/context.js.map +1 -0
- package/dist/cookie-sign.d.ts +16 -2
- package/dist/cookie-sign.d.ts.map +1 -1
- package/dist/cookie-sign.js +104 -0
- package/dist/cookie-sign.js.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -1
- package/dist/cookie.js +138 -0
- package/dist/cookie.js.map +1 -0
- package/dist/cors.d.ts +1 -0
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +87 -0
- package/dist/cors.js.map +1 -0
- package/dist/crypto.d.ts +6 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +38 -0
- package/dist/crypto.js.map +1 -0
- package/dist/csrf.d.ts +19 -0
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +79 -0
- package/dist/csrf.js.map +1 -0
- package/dist/curl-logger.d.ts +14 -3
- package/dist/curl-logger.d.ts.map +1 -1
- package/dist/curl-logger.js +199 -0
- package/dist/curl-logger.js.map +1 -0
- package/dist/detect-runtime.js +13 -0
- package/dist/detect-runtime.js.map +1 -0
- package/dist/dict.d.ts +11 -0
- package/dist/dict.d.ts.map +1 -0
- package/dist/dict.js +13 -0
- package/dist/dict.js.map +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +44 -0
- package/dist/error.js.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/errors.js.map +1 -0
- package/dist/etag.d.ts +2 -0
- package/dist/etag.d.ts.map +1 -1
- package/dist/etag.js +82 -0
- package/dist/etag.js.map +1 -0
- package/dist/feature-detect.d.ts +25 -0
- package/dist/feature-detect.d.ts.map +1 -0
- package/dist/feature-detect.js +144 -0
- package/dist/feature-detect.js.map +1 -0
- package/dist/feature-load.js +36 -0
- package/dist/feature-load.js.map +1 -0
- package/dist/feature-slots.d.ts +44 -0
- package/dist/feature-slots.d.ts.map +1 -0
- package/dist/feature-slots.js +48 -0
- package/dist/feature-slots.js.map +1 -0
- package/dist/fetch-request.d.ts +10 -0
- package/dist/fetch-request.d.ts.map +1 -0
- package/dist/fetch-request.js +13 -0
- package/dist/fetch-request.js.map +1 -0
- package/dist/gen-process.d.ts +15 -0
- package/dist/gen-process.d.ts.map +1 -0
- package/dist/gen-process.js +43 -0
- package/dist/gen-process.js.map +1 -0
- package/dist/gen-write.d.ts +19 -0
- package/dist/gen-write.d.ts.map +1 -0
- package/dist/gen-write.js +120 -0
- package/dist/gen-write.js.map +1 -0
- package/dist/glob.d.ts +21 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +115 -0
- package/dist/glob.js.map +1 -0
- package/dist/go-type-emitter.d.ts +18 -4
- package/dist/go-type-emitter.d.ts.map +1 -1
- package/dist/go-type-emitter.js +594 -0
- package/dist/go-type-emitter.js.map +1 -0
- package/dist/honey-response.d.ts.map +1 -1
- package/dist/honey-response.js +338 -0
- package/dist/honey-response.js.map +1 -0
- package/dist/i18n-slot.d.ts +8 -0
- package/dist/i18n-slot.d.ts.map +1 -1
- package/dist/i18n-slot.js +14 -0
- package/dist/i18n-slot.js.map +1 -0
- package/dist/i18n.d.ts +3 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +250 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +408 -148
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2324 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +3 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +140 -0
- package/dist/init.js.map +1 -0
- package/dist/input.js +4 -0
- package/dist/input.js.map +1 -0
- package/dist/invalidate-check.d.ts +11 -0
- package/dist/invalidate-check.d.ts.map +1 -1
- package/dist/invalidate-check.js +163 -0
- package/dist/invalidate-check.js.map +1 -0
- package/dist/invoke-user.d.ts +35 -0
- package/dist/invoke-user.d.ts.map +1 -0
- package/dist/invoke-user.js +73 -0
- package/dist/invoke-user.js.map +1 -0
- package/dist/ip-restrict.d.ts +13 -1
- package/dist/ip-restrict.d.ts.map +1 -1
- package/dist/ip-restrict.js +60 -0
- package/dist/ip-restrict.js.map +1 -0
- package/dist/ip.d.ts +39 -0
- package/dist/ip.d.ts.map +1 -0
- package/dist/ip.js +209 -0
- package/dist/ip.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +113 -0
- package/dist/logger.js.map +1 -0
- package/dist/media-type.d.ts +30 -0
- package/dist/media-type.d.ts.map +1 -0
- package/dist/media-type.js +97 -0
- package/dist/media-type.js.map +1 -0
- package/dist/meta-spec-merge.d.ts +32 -0
- package/dist/meta-spec-merge.d.ts.map +1 -0
- package/dist/meta-spec-merge.js +68 -0
- package/dist/meta-spec-merge.js.map +1 -0
- package/dist/meta-spec.d.ts +16 -3
- package/dist/meta-spec.d.ts.map +1 -1
- package/dist/meta-spec.js +535 -0
- package/dist/meta-spec.js.map +1 -0
- package/dist/middleware.d.ts +23 -6
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +253 -0
- package/dist/middleware.js.map +1 -0
- package/dist/node-request.d.ts +57 -12
- package/dist/node-request.d.ts.map +1 -1
- package/dist/node-request.js +407 -0
- package/dist/node-request.js.map +1 -0
- package/dist/node.d.ts +20 -3
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +396 -0
- package/dist/node.js.map +1 -0
- package/dist/openapi/collect.d.ts +6 -3
- package/dist/openapi/collect.d.ts.map +1 -1
- package/dist/openapi/collect.js +30 -0
- package/dist/openapi/collect.js.map +1 -0
- package/dist/openapi/docs-page.d.ts +42 -0
- package/dist/openapi/docs-page.d.ts.map +1 -0
- package/dist/openapi/docs-page.js +80 -0
- package/dist/openapi/docs-page.js.map +1 -0
- package/dist/openapi/document.d.ts +4 -0
- package/dist/openapi/document.d.ts.map +1 -1
- package/dist/openapi/document.js +785 -0
- package/dist/openapi/document.js.map +1 -0
- package/dist/openapi/json-schema-slot.d.ts +7 -0
- package/dist/openapi/json-schema-slot.d.ts.map +1 -1
- package/dist/openapi/json-schema-slot.js +38 -0
- package/dist/openapi/json-schema-slot.js.map +1 -0
- package/dist/openapi/register.d.ts.map +1 -1
- package/dist/openapi/register.js +20 -0
- package/dist/openapi/register.js.map +1 -0
- package/dist/openapi/scalar.d.ts +3 -1
- package/dist/openapi/scalar.d.ts.map +1 -1
- package/dist/openapi/scalar.js +21 -0
- package/dist/openapi/scalar.js.map +1 -0
- package/dist/openapi/serve.d.ts +9 -0
- package/dist/openapi/serve.d.ts.map +1 -0
- package/dist/openapi/serve.js +36 -0
- package/dist/openapi/serve.js.map +1 -0
- package/dist/openapi/spec-factory.d.ts +30 -1
- package/dist/openapi/spec-factory.d.ts.map +1 -1
- package/dist/openapi/spec-factory.js +52 -0
- package/dist/openapi/spec-factory.js.map +1 -0
- package/dist/openapi/spec.d.ts +14 -5
- package/dist/openapi/spec.d.ts.map +1 -1
- package/dist/openapi/spec.js +50 -0
- package/dist/openapi/spec.js.map +1 -0
- package/dist/openapi/swagger.d.ts +3 -1
- package/dist/openapi/swagger.d.ts.map +1 -1
- package/dist/openapi/swagger.js +22 -0
- package/dist/openapi/swagger.js.map +1 -0
- package/dist/pattern.d.ts +71 -0
- package/dist/pattern.d.ts.map +1 -0
- package/dist/pattern.js +239 -0
- package/dist/pattern.js.map +1 -0
- package/dist/peer.d.ts +17 -0
- package/dist/peer.d.ts.map +1 -0
- package/dist/peer.js +50 -0
- package/dist/peer.js.map +1 -0
- package/dist/plugin.d.ts +67 -16
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +649 -0
- package/dist/plugin.js.map +1 -0
- package/dist/powered-by.d.ts.map +1 -1
- package/dist/powered-by.js +11 -0
- package/dist/powered-by.js.map +1 -0
- package/dist/pretty-json.d.ts.map +1 -1
- package/dist/pretty-json.js +47 -0
- package/dist/pretty-json.js.map +1 -0
- package/dist/producer-stream.d.ts +47 -0
- package/dist/producer-stream.d.ts.map +1 -0
- package/dist/producer-stream.js +267 -0
- package/dist/producer-stream.js.map +1 -0
- package/dist/proxy.d.ts +47 -8
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +274 -0
- package/dist/proxy.js.map +1 -0
- package/dist/python-type-emitter.d.ts +14 -1
- package/dist/python-type-emitter.d.ts.map +1 -1
- package/dist/python-type-emitter.js +157 -0
- package/dist/python-type-emitter.js.map +1 -0
- package/dist/realtime/bus.d.ts +17 -1
- package/dist/realtime/bus.d.ts.map +1 -1
- package/dist/realtime/bus.js +112 -0
- package/dist/realtime/bus.js.map +1 -0
- package/dist/realtime/register.d.ts +3 -0
- package/dist/realtime/register.d.ts.map +1 -0
- package/dist/realtime/register.js +10 -0
- package/dist/realtime/register.js.map +1 -0
- package/dist/realtime/route.d.ts +65 -26
- package/dist/realtime/route.d.ts.map +1 -1
- package/dist/realtime/route.js +72 -0
- package/dist/realtime/route.js.map +1 -0
- package/dist/realtime/server.d.ts +43 -0
- package/dist/realtime/server.d.ts.map +1 -0
- package/dist/realtime/server.js +255 -0
- package/dist/realtime/server.js.map +1 -0
- package/dist/request-id.d.ts +7 -0
- package/dist/request-id.d.ts.map +1 -1
- package/dist/request-id.js +16 -0
- package/dist/request-id.js.map +1 -0
- package/dist/request-limits.d.ts +3 -0
- package/dist/request-limits.d.ts.map +1 -0
- package/dist/request-limits.js +3 -0
- package/dist/request-limits.js.map +1 -0
- package/dist/request-path.d.ts +33 -0
- package/dist/request-path.d.ts.map +1 -0
- package/dist/request-path.js +178 -0
- package/dist/request-path.js.map +1 -0
- package/dist/request-to-curl.d.ts +7 -1
- package/dist/request-to-curl.d.ts.map +1 -1
- package/dist/request-to-curl.js +48 -0
- package/dist/request-to-curl.js.map +1 -0
- package/dist/response.d.ts +25 -2
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +313 -0
- package/dist/response.js.map +1 -0
- package/dist/rust-type-emitter.d.ts +14 -2
- package/dist/rust-type-emitter.d.ts.map +1 -1
- package/dist/rust-type-emitter.js +463 -0
- package/dist/rust-type-emitter.js.map +1 -0
- package/dist/secure-headers.d.ts.map +1 -1
- package/dist/secure-headers.js +53 -0
- package/dist/secure-headers.js.map +1 -0
- package/dist/serve-register.d.ts +4 -0
- package/dist/serve-register.d.ts.map +1 -1
- package/dist/serve-register.js +9 -0
- package/dist/serve-register.js.map +1 -0
- package/dist/serve-slot.d.ts +3 -0
- package/dist/serve-slot.d.ts.map +1 -1
- package/dist/serve-slot.js +52 -0
- package/dist/serve-slot.js.map +1 -0
- package/dist/serve.d.ts +18 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +157 -0
- package/dist/serve.js.map +1 -0
- package/dist/server-timing.d.ts.map +1 -1
- package/dist/server-timing.js +51 -0
- package/dist/server-timing.js.map +1 -0
- package/dist/static.d.ts +9 -0
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +102 -0
- package/dist/static.js.map +1 -0
- package/dist/telemetry/otel.d.ts +2 -0
- package/dist/telemetry/otel.d.ts.map +1 -1
- package/dist/telemetry/otel.js +135 -0
- package/dist/telemetry/otel.js.map +1 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +124 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeout.d.ts +6 -0
- package/dist/timeout.d.ts.map +1 -1
- package/dist/timeout.js +36 -0
- package/dist/timeout.js.map +1 -0
- package/dist/tree.d.ts +133 -36
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +437 -0
- package/dist/tree.js.map +1 -0
- package/dist/trust.d.ts +21 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +100 -0
- package/dist/trust.js.map +1 -0
- package/dist/ts-type-emitter.d.ts.map +1 -1
- package/dist/ts-type-emitter.js +150 -0
- package/dist/ts-type-emitter.js.map +1 -0
- package/dist/type-emitter.d.ts +16 -2
- package/dist/type-emitter.d.ts.map +1 -1
- package/dist/type-emitter.js +729 -0
- package/dist/type-emitter.js.map +1 -0
- package/dist/type-extractor.d.ts +8 -4
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +819 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +96 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +14 -2
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +335 -0
- package/dist/validation.js.map +1 -0
- package/dist/with-headers.d.ts +15 -0
- package/dist/with-headers.d.ts.map +1 -0
- package/dist/with-headers.js +45 -0
- package/dist/with-headers.js.map +1 -0
- package/dist/ws/bun.d.ts +11 -5
- package/dist/ws/bun.d.ts.map +1 -1
- package/dist/ws/bun.js +82 -0
- package/dist/ws/bun.js.map +1 -0
- package/dist/ws/cloudflare.d.ts +25 -3
- package/dist/ws/cloudflare.d.ts.map +1 -1
- package/dist/ws/cloudflare.js +117 -0
- package/dist/ws/cloudflare.js.map +1 -0
- package/dist/ws/deno.d.ts +4 -4
- package/dist/ws/deno.d.ts.map +1 -1
- package/dist/ws/deno.js +133 -0
- package/dist/ws/deno.js.map +1 -0
- package/dist/ws/node.d.ts +20 -3
- package/dist/ws/node.d.ts.map +1 -1
- package/dist/ws/node.js +173 -0
- package/dist/ws/node.js.map +1 -0
- package/dist/ws/session.d.ts +11 -0
- package/dist/ws/session.d.ts.map +1 -0
- package/dist/ws/session.js +60 -0
- package/dist/ws/session.js.map +1 -0
- package/dist/ws/shared.d.ts +65 -0
- package/dist/ws/shared.d.ts.map +1 -0
- package/dist/ws/shared.js +103 -0
- package/dist/ws/shared.js.map +1 -0
- package/dist/ws-origin.d.ts +20 -0
- package/dist/ws-origin.d.ts.map +1 -0
- package/dist/ws-origin.js +41 -0
- package/dist/ws-origin.js.map +1 -0
- package/dist/yaml.d.ts +15 -1
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +101 -0
- package/dist/yaml.js.map +1 -0
- package/docs/invalidate-spec.md +13 -1
- package/docs/meta-spec.md +36 -12
- package/docs/packaging.md +43 -24
- package/docs/regression-matrix/README.md +32 -0
- package/docs/regression-matrix/review.md +44 -0
- package/docs/regression-matrix/ws1-3.md +97 -0
- package/docs/regression-matrix/ws4-8.md +107 -0
- package/docs/regression-matrix/ws9-12.md +266 -0
- package/docs/sdk.md +102 -52
- package/package.json +183 -52
- package/src/accepts.ts +91 -33
- package/src/body-kind.ts +67 -0
- package/src/body-limit.ts +102 -87
- package/src/build/index.ts +104 -54
- package/src/cli-args.ts +123 -0
- package/src/cli-go/auth.go +12 -1
- package/src/cli-go/config.go +29 -2
- package/src/cli-go/errors.go +28 -10
- package/src/cli-go/multipart.go +51 -27
- package/src/cli-go/output.go +58 -4
- package/src/cli-go/root.go +1 -1
- package/src/cli-go/version.go +2 -2
- package/src/cli.ts +154 -148
- package/src/client/error.ts +56 -1
- package/src/client/http.ts +479 -256
- package/src/client/index.ts +55 -17
- package/src/client/path.ts +169 -0
- package/src/client/redirect-policy.ts +19 -0
- package/src/client/sdk.ts +180 -101
- package/src/client/sse.ts +92 -66
- package/src/client/ws.ts +67 -31
- package/src/client-go/errors.go +41 -12
- package/src/client-go/export.go +44 -0
- package/src/client-go/invalidation.go +52 -54
- package/src/client-go/nullable.go +62 -0
- package/src/client-go/realtime.go +147 -110
- package/src/client-go/runtime.go +646 -88
- package/src/client-go/sse.go +104 -94
- package/src/client-go/transport.go +67 -27
- package/src/client-go/ws.go +49 -25
- package/src/client-info.ts +79 -0
- package/src/client-mcp/server.ts +133 -3
- package/src/client-python/_invalidation.py +207 -291
- package/src/client-python/_realtime.py +63 -40
- package/src/client-python/_runtime.py +501 -119
- package/src/client-python/_sse.py +130 -74
- package/src/client-python/_transport.py +37 -9
- package/src/client-python/_ws.py +50 -22
- package/src/client-rust/errors.rs +21 -1
- package/src/client-rust/invalidation.rs +202 -231
- package/src/client-rust/invalidation_sync.rs +16 -229
- package/src/client-rust/nullable.rs +17 -0
- package/src/client-rust/realtime.rs +217 -217
- package/src/client-rust/runtime.rs +602 -177
- package/src/client-rust/runtime_sync.rs +169 -145
- package/src/client-rust/sse.rs +142 -121
- package/src/client-rust/ws.rs +61 -37
- package/src/codegen-go-cli.ts +607 -728
- package/src/codegen-go.ts +710 -770
- package/src/codegen-ir.ts +381 -75
- package/src/codegen-lang.ts +517 -0
- package/src/codegen-load.ts +150 -0
- package/src/codegen-mcp.ts +207 -108
- package/src/codegen-python.ts +684 -816
- package/src/codegen-route-tree-intern.ts +51 -25
- package/src/codegen-rust.ts +946 -1240
- package/src/codegen-sanitize.ts +0 -4
- package/src/codegen-schema-naming.ts +19 -6
- package/src/codegen-sdk-model.ts +228 -0
- package/src/codegen.ts +1488 -1140
- package/src/context.ts +130 -11
- package/src/cookie-sign.ts +85 -16
- package/src/cookie.ts +66 -11
- package/src/cors.ts +55 -54
- package/src/crypto.ts +27 -5
- package/src/csrf.ts +57 -25
- package/src/curl-logger.ts +155 -114
- package/src/dict.ts +12 -0
- package/src/error.ts +4 -2
- package/src/etag.ts +58 -34
- package/src/feature-detect.ts +147 -0
- package/src/feature-slots.ts +70 -0
- package/src/fetch-request.ts +13 -0
- package/src/gen-process.ts +42 -0
- package/src/gen-write.ts +127 -0
- package/src/glob.ts +109 -0
- package/src/go-type-emitter.ts +406 -155
- package/src/honey-response.ts +36 -9
- package/src/i18n-slot.ts +10 -0
- package/src/i18n.ts +98 -23
- package/src/index.ts +1887 -1756
- package/src/init.ts +40 -28
- package/src/invalidate-check.ts +42 -4
- package/src/invoke-user.ts +95 -0
- package/src/ip-restrict.ts +47 -161
- package/src/ip.ts +194 -0
- package/src/logger.ts +65 -36
- package/src/media-type.ts +104 -0
- package/src/meta-spec-merge.ts +80 -0
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +112 -12
- package/src/node-request.ts +242 -52
- package/src/node.ts +318 -75
- package/src/openapi/collect.ts +19 -44
- package/src/openapi/docs-page.ts +89 -0
- package/src/openapi/document.ts +503 -224
- package/src/openapi/json-schema-slot.ts +40 -0
- package/src/openapi/register.ts +7 -2
- package/src/openapi/scalar.ts +11 -10
- package/src/openapi/serve.ts +39 -0
- package/src/openapi/spec-factory.ts +50 -1
- package/src/openapi/spec.ts +53 -30
- package/src/openapi/swagger.ts +12 -11
- package/src/pattern.ts +245 -0
- package/src/peer.ts +53 -0
- package/src/plugin.ts +270 -185
- package/src/powered-by.ts +2 -2
- package/src/pretty-json.ts +25 -9
- package/src/producer-stream.ts +296 -0
- package/src/proxy.ts +253 -55
- package/src/python-type-emitter.ts +68 -56
- package/src/realtime/bus.ts +56 -9
- package/src/realtime/register.ts +13 -0
- package/src/realtime/route.ts +129 -80
- package/src/realtime/server.ts +298 -0
- package/src/request-id.ts +13 -3
- package/src/request-limits.ts +2 -0
- package/src/request-path.ts +165 -0
- package/src/request-to-curl.ts +28 -7
- package/src/response.ts +143 -103
- package/src/rust-type-emitter.ts +265 -170
- package/src/secure-headers.ts +8 -4
- package/src/serve-register.ts +4 -0
- package/src/serve-slot.ts +38 -0
- package/src/serve.ts +113 -25
- package/src/server-timing.ts +11 -7
- package/src/static.ts +85 -32
- package/src/telemetry/otel.ts +29 -7
- package/src/testing.ts +76 -14
- package/src/timeout.ts +15 -7
- package/src/tree.ts +405 -333
- package/src/trust.ts +117 -0
- package/src/ts-type-emitter.ts +18 -12
- package/src/type-emitter.ts +227 -89
- package/src/type-extractor.ts +603 -423
- package/src/types.ts +8 -0
- package/src/validation.ts +221 -96
- package/src/with-headers.ts +45 -0
- package/src/ws/bun.ts +53 -15
- package/src/ws/cloudflare.ts +81 -19
- package/src/ws/deno.ts +124 -36
- package/src/ws/node.ts +173 -79
- package/src/ws/session.ts +73 -0
- package/src/ws/shared.ts +140 -0
- package/src/ws-origin.ts +53 -0
- package/src/yaml.ts +45 -22
- package/dist/client-mcp/server.d.ts +0 -10
- package/dist/client-mcp/server.d.ts.map +0 -1
- package/dist/client-mcp/types.d.ts +0 -16
- package/dist/client-mcp/types.d.ts.map +0 -1
- package/dist/cloudflare/realtime.d.ts +0 -30
- package/dist/cloudflare/realtime.d.ts.map +0 -1
- package/dist/realtime/buffer.d.ts +0 -18
- package/dist/realtime/buffer.d.ts.map +0 -1
- package/dist/realtime/longpoll.d.ts +0 -23
- package/dist/realtime/longpoll.d.ts.map +0 -1
- package/dist/realtime/protocol.d.ts +0 -50
- package/dist/realtime/protocol.d.ts.map +0 -1
- package/dist/realtime/runtime.d.ts +0 -74
- package/dist/realtime/runtime.d.ts.map +0 -1
- package/src/cloudflare/realtime.ts +0 -234
- package/src/realtime/buffer.ts +0 -101
- package/src/realtime/longpoll.ts +0 -148
- package/src/realtime/protocol.ts +0 -128
- package/src/realtime/runtime.ts +0 -427
|
@@ -35,7 +35,8 @@ pub type OnResponseHook = Arc<
|
|
|
35
35
|
/// RequestContext carries mutable request metadata through OnRequestHook invocations.
|
|
36
36
|
/// `selector` / `is_stale` / `invalidated_by` mirror the TS hook ctx and are
|
|
37
37
|
/// populated from the RequestMeta snapshot taken before the request fires;
|
|
38
|
-
/// zero values when the tracker is disabled.
|
|
38
|
+
/// zero values when the tracker is disabled. Header names are unique
|
|
39
|
+
/// case-insensitively; use [`set_header`] to replace one.
|
|
39
40
|
pub struct RequestContext {
|
|
40
41
|
pub method: reqwest::Method,
|
|
41
42
|
pub url: String,
|
|
@@ -62,18 +63,21 @@ pub struct ResponseContext {
|
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
/// RequestBody represents the different body types a request can carry.
|
|
65
|
-
/// `
|
|
66
|
-
///
|
|
67
|
-
/// because the underlying body is not `Clone`.
|
|
66
|
+
/// `Multipart` and `Stream` own single-shot bodies and are not retried on 401;
|
|
67
|
+
/// the others are sent again after an auth refresh.
|
|
68
68
|
pub enum RequestBody<'a> {
|
|
69
69
|
None,
|
|
70
70
|
Json(&'a serde_json::Value),
|
|
71
|
-
|
|
71
|
+
/// Ordered key/value pairs, sent as application/x-www-form-urlencoded.
|
|
72
|
+
FormUrl(Vec<(String, String)>),
|
|
73
|
+
/// Bytes with an explicit content type (text/plain, …).
|
|
74
|
+
Raw(&'static str, bytes::Bytes),
|
|
72
75
|
Multipart(reqwest::multipart::Form),
|
|
73
76
|
Stream(reqwest::Body),
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
/// OnAuthExpiredHook is the async auth-refresh callback type for ClientConfig.
|
|
80
|
+
/// Returning an empty token means "give up": the 401 is returned as an error.
|
|
77
81
|
pub type OnAuthExpiredHook =
|
|
78
82
|
Arc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send>> + Send + Sync>;
|
|
79
83
|
|
|
@@ -98,13 +102,22 @@ pub struct LogEntry {
|
|
|
98
102
|
pub type OnLogHook = Arc<dyn Fn(LogEntry) + Send + Sync>;
|
|
99
103
|
|
|
100
104
|
/// ClientConfig holds all configuration for the SDK client.
|
|
105
|
+
///
|
|
106
|
+
/// `timeout` bounds a whole regular request including the body read
|
|
107
|
+
/// (`Duration::ZERO` disables it); streams are not bounded by it.
|
|
108
|
+
/// `max_response_bytes` caps a buffered response body (0 disables the cap).
|
|
109
|
+
/// When `http_client` is None the SDK builds one that does not follow
|
|
110
|
+
/// redirects to another host, so auth and custom headers never leave the origin.
|
|
101
111
|
#[derive(Clone)]
|
|
102
112
|
pub struct ClientConfig {
|
|
103
113
|
pub base_url: String,
|
|
104
114
|
pub bearer_token: Option<String>,
|
|
105
115
|
pub headers: HashMap<String, String>,
|
|
106
116
|
pub timeout: Duration,
|
|
117
|
+
/// Ignored at runtime: whether methods return `SdkResult` is fixed when the SDK is generated.
|
|
118
|
+
#[deprecated(note = "has no effect; generate with `throwOnError: false` for SdkResult methods")]
|
|
107
119
|
pub throw_on_error: bool,
|
|
120
|
+
/// Fallback for `InvalidationConfig::stale_max_entries` when that is 0.
|
|
108
121
|
pub stale_max_entries: usize,
|
|
109
122
|
pub invalidation: Option<InvalidationConfig>,
|
|
110
123
|
pub on_auth_expired: Option<OnAuthExpiredHook>,
|
|
@@ -115,8 +128,10 @@ pub struct ClientConfig {
|
|
|
115
128
|
pub on_response: Vec<OnResponseHook>,
|
|
116
129
|
pub on_log: Option<OnLogHook>,
|
|
117
130
|
pub state: Option<Arc<Mutex<HashMap<String, serde_json::Value>>>>,
|
|
131
|
+
pub max_response_bytes: usize,
|
|
118
132
|
}
|
|
119
133
|
|
|
134
|
+
#[allow(deprecated)]
|
|
120
135
|
impl Default for ClientConfig {
|
|
121
136
|
fn default() -> Self {
|
|
122
137
|
ClientConfig {
|
|
@@ -135,10 +150,79 @@ impl Default for ClientConfig {
|
|
|
135
150
|
on_response: Vec::new(),
|
|
136
151
|
on_log: None,
|
|
137
152
|
state: None,
|
|
153
|
+
max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES,
|
|
138
154
|
}
|
|
139
155
|
}
|
|
140
156
|
}
|
|
141
157
|
|
|
158
|
+
pub const DEFAULT_MAX_RESPONSE_BYTES: usize = 100 * 1024 * 1024;
|
|
159
|
+
|
|
160
|
+
/// AuthState keeps the current token so a refreshed token is reused by later
|
|
161
|
+
/// calls, and serializes refreshes so concurrent 401s refresh once.
|
|
162
|
+
pub struct AuthState {
|
|
163
|
+
token: Mutex<Option<String>>,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
impl AuthState {
|
|
167
|
+
pub fn new(token: Option<String>) -> Self {
|
|
168
|
+
AuthState { token: Mutex::new(token) }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
pub async fn current(&self) -> Option<String> {
|
|
172
|
+
self.token.lock().await.clone()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// refresh calls the hook unless another call already replaced `rejected`.
|
|
176
|
+
pub async fn refresh(
|
|
177
|
+
&self,
|
|
178
|
+
hook: &OnAuthExpiredHook,
|
|
179
|
+
rejected: Option<&str>,
|
|
180
|
+
) -> Result<Option<String>, Error> {
|
|
181
|
+
let mut guard = self.token.lock().await;
|
|
182
|
+
if guard.as_deref() != rejected {
|
|
183
|
+
return Ok(guard.clone());
|
|
184
|
+
}
|
|
185
|
+
let token = hook().await.map_err(|e| Error::AuthExpired(e.to_string()))?;
|
|
186
|
+
if token.is_empty() {
|
|
187
|
+
return Ok(None);
|
|
188
|
+
}
|
|
189
|
+
*guard = Some(token.clone());
|
|
190
|
+
Ok(Some(token))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// default_http_client builds a client that follows redirects on the same
|
|
195
|
+
/// scheme and host only.
|
|
196
|
+
pub fn default_http_client() -> reqwest::Client {
|
|
197
|
+
reqwest::Client::builder()
|
|
198
|
+
.redirect(same_host_redirects())
|
|
199
|
+
.build()
|
|
200
|
+
.unwrap_or_default()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// same_host_redirects follows at most 10 redirects, never to another origin.
|
|
204
|
+
pub fn same_host_redirects() -> reqwest::redirect::Policy {
|
|
205
|
+
reqwest::redirect::Policy::custom(|attempt| {
|
|
206
|
+
if attempt.previous().len() >= 10 {
|
|
207
|
+
return attempt.stop();
|
|
208
|
+
}
|
|
209
|
+
let same = attempt
|
|
210
|
+
.previous()
|
|
211
|
+
.first()
|
|
212
|
+
.map(|first| {
|
|
213
|
+
first.scheme() == attempt.url().scheme()
|
|
214
|
+
&& first.host_str() == attempt.url().host_str()
|
|
215
|
+
&& first.port_or_known_default() == attempt.url().port_or_known_default()
|
|
216
|
+
})
|
|
217
|
+
.unwrap_or(false);
|
|
218
|
+
if same {
|
|
219
|
+
attempt.follow()
|
|
220
|
+
} else {
|
|
221
|
+
attempt.stop()
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
142
226
|
/* emit_log invokes the on_log callback if configured, swallowing any panic so a
|
|
143
227
|
faulty logger cannot break the request path. Mirrors Python try/except and Go
|
|
144
228
|
defer recover. */
|
|
@@ -179,6 +263,17 @@ pub struct ResponseMeta {
|
|
|
179
263
|
pub body: Vec<u8>,
|
|
180
264
|
}
|
|
181
265
|
|
|
266
|
+
impl ResponseMeta {
|
|
267
|
+
pub(crate) fn empty(status: u16) -> Self {
|
|
268
|
+
ResponseMeta {
|
|
269
|
+
status,
|
|
270
|
+
headers: reqwest::header::HeaderMap::new(),
|
|
271
|
+
url: String::new(),
|
|
272
|
+
body: Vec::new(),
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
182
277
|
/// RequestResult carries the raw bytes, status, headers, and final URL from a completed request.
|
|
183
278
|
#[allow(dead_code)]
|
|
184
279
|
pub(crate) struct RequestResult {
|
|
@@ -188,6 +283,327 @@ pub(crate) struct RequestResult {
|
|
|
188
283
|
pub url: String,
|
|
189
284
|
}
|
|
190
285
|
|
|
286
|
+
/* ── URLs, path params, query encoding — shared with runtime_sync ── */
|
|
287
|
+
|
|
288
|
+
fn percent_encode(s: &str, keep: &[u8], space_as_plus: bool) -> String {
|
|
289
|
+
const HEX: &[u8; 16] = b"0123456789ABCDEF";
|
|
290
|
+
let mut out = String::with_capacity(s.len());
|
|
291
|
+
for &b in s.as_bytes() {
|
|
292
|
+
if b.is_ascii_alphanumeric() || keep.contains(&b) {
|
|
293
|
+
out.push(b as char);
|
|
294
|
+
} else if b == b' ' && space_as_plus {
|
|
295
|
+
out.push('+');
|
|
296
|
+
} else {
|
|
297
|
+
out.push('%');
|
|
298
|
+
out.push(HEX[(b >> 4) as usize] as char);
|
|
299
|
+
out.push(HEX[(b & 0x0f) as usize] as char);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
out
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// escape_segment encodes like JavaScript's encodeURIComponent, so every SDK
|
|
306
|
+
/// language (and the TypeScript client) sends the same path bytes.
|
|
307
|
+
pub fn escape_segment(s: &str) -> String {
|
|
308
|
+
percent_encode(s, b"-_.!~*'()", false)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// escape_query encodes like URLSearchParams (space becomes '+').
|
|
312
|
+
pub fn escape_query(s: &str) -> String {
|
|
313
|
+
percent_encode(s, b"*-._", true)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
fn missing(name: &str) -> Error {
|
|
317
|
+
Error::Other(format!("Missing path param: {}", name))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
fn encode_segment_value(name: &str, value: &str) -> Result<String, Error> {
|
|
321
|
+
if value.is_empty() || value == "." || value == ".." {
|
|
322
|
+
return Err(Error::Other(format!(
|
|
323
|
+
"Invalid path param {:?}: {:?} is not a path segment",
|
|
324
|
+
name, value
|
|
325
|
+
)));
|
|
326
|
+
}
|
|
327
|
+
Ok(escape_segment(value))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// expand_path fills a path template with the TypeScript client's grammar:
|
|
331
|
+
/// `:name` / `:name?` take a whole segment, a final `*name` takes the rest,
|
|
332
|
+
/// `{name}` may sit inside a segment. Values are encoded; "", "." and ".."
|
|
333
|
+
/// are rejected.
|
|
334
|
+
pub fn expand_path(template: &str, params: &[(&str, &str)]) -> Result<String, Error> {
|
|
335
|
+
let get = |name: &str| params.iter().find(|(k, _)| *k == name).map(|(_, v)| *v);
|
|
336
|
+
let segments: Vec<&str> = template.split('/').collect();
|
|
337
|
+
let mut out: Vec<String> = Vec::with_capacity(segments.len());
|
|
338
|
+
for (i, seg) in segments.iter().enumerate() {
|
|
339
|
+
if seg.len() > 1 && seg.starts_with(':') {
|
|
340
|
+
let optional = seg.ends_with('?');
|
|
341
|
+
let name = if optional { &seg[1..seg.len() - 1] } else { &seg[1..] };
|
|
342
|
+
match get(name) {
|
|
343
|
+
Some(v) => out.push(encode_segment_value(name, v)?),
|
|
344
|
+
None if optional => continue,
|
|
345
|
+
None => return Err(missing(name)),
|
|
346
|
+
}
|
|
347
|
+
} else if seg.starts_with('*') && i == segments.len() - 1 {
|
|
348
|
+
let name = if seg.len() > 1 { &seg[1..] } else { "*" };
|
|
349
|
+
match get(name) {
|
|
350
|
+
Some("") => out.push(String::new()),
|
|
351
|
+
Some(v) => {
|
|
352
|
+
let parts: Result<Vec<String>, Error> =
|
|
353
|
+
v.split('/').map(|p| encode_segment_value(name, p)).collect();
|
|
354
|
+
out.push(parts?.join("/"));
|
|
355
|
+
}
|
|
356
|
+
None if seg.len() == 1 => out.push(String::new()),
|
|
357
|
+
None => return Err(missing(name)),
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
let mut piece = String::with_capacity(seg.len());
|
|
361
|
+
let mut rest: &str = seg;
|
|
362
|
+
loop {
|
|
363
|
+
let open = rest.find('{');
|
|
364
|
+
let close = open.and_then(|o| rest[o..].find('}').map(|c| o + c));
|
|
365
|
+
match (open, close) {
|
|
366
|
+
(Some(o), Some(c)) if !rest[o + 1..c].contains('{') => {
|
|
367
|
+
let name = &rest[o + 1..c];
|
|
368
|
+
let v = get(name).ok_or_else(|| missing(name))?;
|
|
369
|
+
piece.push_str(&rest[..o]);
|
|
370
|
+
piece.push_str(&encode_segment_value(name, v)?);
|
|
371
|
+
rest = &rest[c + 1..];
|
|
372
|
+
}
|
|
373
|
+
_ => {
|
|
374
|
+
piece.push_str(rest);
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
out.push(piece);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
Ok(out.join("/"))
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/// format_f64 renders a float like JavaScript's Number#toString, so every SDK
|
|
386
|
+
/// language sends the same query bytes: shortest round-trip digits, fixed
|
|
387
|
+
/// notation for 1e-6 <= |v| < 1e21, otherwise `<mantissa>e±<exp>`.
|
|
388
|
+
pub fn format_f64(v: f64) -> String {
|
|
389
|
+
if v.is_nan() {
|
|
390
|
+
return "NaN".to_string();
|
|
391
|
+
}
|
|
392
|
+
if v.is_infinite() {
|
|
393
|
+
return if v > 0.0 { "Infinity" } else { "-Infinity" }.to_string();
|
|
394
|
+
}
|
|
395
|
+
if v == 0.0 {
|
|
396
|
+
return "0".to_string();
|
|
397
|
+
}
|
|
398
|
+
/* `{:e}` gives the shortest round-trip digits: "-1.2345678901234568e20" */
|
|
399
|
+
let sci = format!("{:e}", v);
|
|
400
|
+
let (mantissa, exp) = sci.split_once('e').unwrap_or((sci.as_str(), "0"));
|
|
401
|
+
let exp: i32 = exp.parse().unwrap_or(0);
|
|
402
|
+
let (sign, mantissa) = match mantissa.strip_prefix('-') {
|
|
403
|
+
Some(m) => ("-", m),
|
|
404
|
+
None => ("", mantissa),
|
|
405
|
+
};
|
|
406
|
+
let digits: String = mantissa.chars().filter(|c| *c != '.').collect();
|
|
407
|
+
let abs = v.abs();
|
|
408
|
+
if !(1e-6..1e21).contains(&abs) {
|
|
409
|
+
return format!("{}{}e{}{}", sign, mantissa, if exp > 0 { "+" } else { "-" }, exp.abs());
|
|
410
|
+
}
|
|
411
|
+
let n = digits.len() as i32;
|
|
412
|
+
let body = if exp >= n - 1 {
|
|
413
|
+
format!("{}{}", digits, "0".repeat((exp - (n - 1)) as usize))
|
|
414
|
+
} else if exp >= 0 {
|
|
415
|
+
let split = (exp + 1) as usize;
|
|
416
|
+
format!("{}.{}", &digits[..split], &digits[split..])
|
|
417
|
+
} else {
|
|
418
|
+
format!("0.{}{}", "0".repeat((-exp - 1) as usize), digits)
|
|
419
|
+
};
|
|
420
|
+
format!("{}{}", sign, body)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/// QueryValue renders a value as one or more query strings: scalars once,
|
|
424
|
+
/// sequences once per element, None not at all.
|
|
425
|
+
pub trait QueryValue {
|
|
426
|
+
fn push_query(&self, key: &str, out: &mut Vec<(String, String)>);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
macro_rules! display_query_value {
|
|
430
|
+
($($t:ty),*) => {$(
|
|
431
|
+
impl QueryValue for $t {
|
|
432
|
+
fn push_query(&self, key: &str, out: &mut Vec<(String, String)>) {
|
|
433
|
+
out.push((key.to_string(), self.to_string()));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
)*};
|
|
437
|
+
}
|
|
438
|
+
display_query_value!(String, str, bool, i8, i16, i32, i64, u8, u16, u32, u64, usize, isize);
|
|
439
|
+
|
|
440
|
+
impl QueryValue for f64 {
|
|
441
|
+
fn push_query(&self, key: &str, out: &mut Vec<(String, String)>) {
|
|
442
|
+
out.push((key.to_string(), format_f64(*self)));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
impl QueryValue for f32 {
|
|
447
|
+
fn push_query(&self, key: &str, out: &mut Vec<(String, String)>) {
|
|
448
|
+
out.push((key.to_string(), format_f64(*self as f64)));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
impl<T: QueryValue + ?Sized> QueryValue for &T {
|
|
453
|
+
fn push_query(&self, key: &str, out: &mut Vec<(String, String)>) {
|
|
454
|
+
(**self).push_query(key, out)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
impl<T: QueryValue> QueryValue for Option<T> {
|
|
459
|
+
fn push_query(&self, key: &str, out: &mut Vec<(String, String)>) {
|
|
460
|
+
if let Some(v) = self {
|
|
461
|
+
v.push_query(key, out)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
impl<T: QueryValue> QueryValue for Vec<T> {
|
|
467
|
+
fn push_query(&self, key: &str, out: &mut Vec<(String, String)>) {
|
|
468
|
+
for v in self {
|
|
469
|
+
v.push_query(key, out)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
impl QueryValue for serde_json::Value {
|
|
475
|
+
fn push_query(&self, key: &str, out: &mut Vec<(String, String)>) {
|
|
476
|
+
match self {
|
|
477
|
+
serde_json::Value::Null => {}
|
|
478
|
+
serde_json::Value::String(s) => out.push((key.to_string(), s.clone())),
|
|
479
|
+
serde_json::Value::Bool(b) => out.push((key.to_string(), b.to_string())),
|
|
480
|
+
serde_json::Value::Number(n) => {
|
|
481
|
+
let s = if n.is_f64() { format_f64(n.as_f64().unwrap_or_default()) } else { n.to_string() };
|
|
482
|
+
out.push((key.to_string(), s))
|
|
483
|
+
}
|
|
484
|
+
serde_json::Value::Array(items) => {
|
|
485
|
+
for item in items {
|
|
486
|
+
item.push_query(key, out)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
other => out.push((key.to_string(), other.to_string())),
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/// query_value_of serializes an enum or other Serialize value and pushes it as
|
|
495
|
+
/// a query value (a JSON string becomes its bare text).
|
|
496
|
+
pub fn push_serialized<T: serde::Serialize>(key: &str, value: &T, out: &mut Vec<(String, String)>) {
|
|
497
|
+
if let Ok(v) = serde_json::to_value(value) {
|
|
498
|
+
v.push_query(key, out)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/// form_pairs flattens a serialized body object into form fields (arrays repeat the key).
|
|
503
|
+
pub fn form_pairs<T: serde::Serialize>(body: &T) -> Vec<(String, String)> {
|
|
504
|
+
let mut out = Vec::new();
|
|
505
|
+
if let Ok(serde_json::Value::Object(map)) = serde_json::to_value(body) {
|
|
506
|
+
let mut keys: Vec<&String> = map.keys().collect();
|
|
507
|
+
keys.sort();
|
|
508
|
+
for k in keys {
|
|
509
|
+
map[k].push_query(k, &mut out);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
out
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/// encode_query joins pairs with every byte outside the unreserved set escaped.
|
|
516
|
+
pub fn encode_query(pairs: &[(String, String)]) -> String {
|
|
517
|
+
pairs
|
|
518
|
+
.iter()
|
|
519
|
+
.map(|(k, v)| format!("{}={}", escape_query(k), escape_query(v)))
|
|
520
|
+
.collect::<Vec<_>>()
|
|
521
|
+
.join("&")
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/// build_url joins base and an already-escaped path, keeping the base path and
|
|
525
|
+
/// base query, then appends the query pairs.
|
|
526
|
+
pub fn build_url(base_url: &str, path: &str, query: &[(String, String)]) -> Result<url::Url, Error> {
|
|
527
|
+
let mut url = url::Url::parse(base_url)?;
|
|
528
|
+
if url.cannot_be_a_base() || url.host_str().is_none() {
|
|
529
|
+
return Err(Error::Other(format!("base URL {:?} needs a scheme and host", base_url)));
|
|
530
|
+
}
|
|
531
|
+
let path = if path.starts_with('/') { path.to_string() } else { format!("/{}", path) };
|
|
532
|
+
let joined = format!("{}{}", url.path().trim_end_matches('/'), path);
|
|
533
|
+
url.set_path(&joined);
|
|
534
|
+
url.set_fragment(None);
|
|
535
|
+
let extra = encode_query(query);
|
|
536
|
+
let q = match (url.query(), extra.is_empty()) {
|
|
537
|
+
(Some(existing), false) if !existing.is_empty() => Some(format!("{}&{}", existing, extra)),
|
|
538
|
+
(_, false) => Some(extra),
|
|
539
|
+
(existing, true) => existing.map(|s| s.to_string()),
|
|
540
|
+
};
|
|
541
|
+
url.set_query(q.as_deref());
|
|
542
|
+
Ok(url)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/// set_header inserts `name` replacing any other casing of it.
|
|
546
|
+
pub fn set_header(headers: &mut HashMap<String, String>, name: &str, value: String) {
|
|
547
|
+
headers.retain(|k, _| !k.eq_ignore_ascii_case(name));
|
|
548
|
+
headers.insert(name.to_string(), value);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/// merged_headers merges config headers, then per-call headers, then auth, then
|
|
552
|
+
/// an X-Request-Id when absent — case-insensitively, later wins.
|
|
553
|
+
pub(crate) fn merged_headers(
|
|
554
|
+
cfg_headers: &HashMap<String, String>,
|
|
555
|
+
call_headers: Option<&HashMap<String, String>>,
|
|
556
|
+
auth_header_name: Option<&str>,
|
|
557
|
+
auth_header_prefix: Option<&str>,
|
|
558
|
+
bearer_token: Option<&str>,
|
|
559
|
+
) -> HashMap<String, String> {
|
|
560
|
+
let mut out = HashMap::new();
|
|
561
|
+
let mut cfg: Vec<(&String, &String)> = cfg_headers.iter().collect();
|
|
562
|
+
cfg.sort();
|
|
563
|
+
for (k, v) in cfg {
|
|
564
|
+
set_header(&mut out, k, v.clone());
|
|
565
|
+
}
|
|
566
|
+
if let Some(ch) = call_headers {
|
|
567
|
+
let mut call: Vec<(&String, &String)> = ch.iter().collect();
|
|
568
|
+
call.sort();
|
|
569
|
+
for (k, v) in call {
|
|
570
|
+
set_header(&mut out, k, v.clone());
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if let Some(token) = bearer_token {
|
|
574
|
+
if !token.is_empty() {
|
|
575
|
+
let name = auth_header_name.unwrap_or("Authorization");
|
|
576
|
+
let prefix = auth_header_prefix.unwrap_or("Bearer ");
|
|
577
|
+
set_header(&mut out, name, format!("{}{}", prefix, token));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if !out.keys().any(|k| k.eq_ignore_ascii_case("x-request-id")) {
|
|
581
|
+
out.insert("x-request-id".to_string(), Uuid::new_v4().to_string());
|
|
582
|
+
}
|
|
583
|
+
out
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/// map_send_error keeps the cause: a refused connection, DNS or TLS failure is
|
|
587
|
+
/// a Transport error, never "canceled".
|
|
588
|
+
pub(crate) fn map_send_error(e: reqwest::Error) -> Error {
|
|
589
|
+
if e.is_timeout() {
|
|
590
|
+
Error::Timeout
|
|
591
|
+
} else {
|
|
592
|
+
Error::Transport(e)
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async fn read_capped(mut resp: reqwest::Response, limit: usize) -> Result<Vec<u8>, Error> {
|
|
597
|
+
let mut out = Vec::new();
|
|
598
|
+
while let Some(chunk) = resp.chunk().await.map_err(map_send_error)? {
|
|
599
|
+
if limit > 0 && out.len() + chunk.len() > limit {
|
|
600
|
+
return Err(Error::Other(format!("response body exceeds {} bytes", limit)));
|
|
601
|
+
}
|
|
602
|
+
out.extend_from_slice(&chunk);
|
|
603
|
+
}
|
|
604
|
+
Ok(out)
|
|
605
|
+
}
|
|
606
|
+
|
|
191
607
|
/// do_request executes an HTTP request, injects auth, merges headers, handles
|
|
192
608
|
/// on_auth_expired retry on 401, fires on_request/on_response hooks, and wraps
|
|
193
609
|
/// errors into typed Error variants. Emits on_log lifecycle entries (request_start
|
|
@@ -197,6 +613,7 @@ pub(crate) struct RequestResult {
|
|
|
197
613
|
pub(crate) async fn do_request(
|
|
198
614
|
client: &reqwest::Client,
|
|
199
615
|
cfg: &ClientConfig,
|
|
616
|
+
auth: &AuthState,
|
|
200
617
|
method: reqwest::Method,
|
|
201
618
|
raw_url: &str,
|
|
202
619
|
query: &[(String, String)],
|
|
@@ -208,21 +625,24 @@ pub(crate) async fn do_request(
|
|
|
208
625
|
request_meta: Option<&crate::invalidation::RequestMeta>,
|
|
209
626
|
) -> Result<RequestResult, Error> {
|
|
210
627
|
let start = std::time::Instant::now();
|
|
628
|
+
let log_error = |e: &Error| {
|
|
629
|
+
emit_log(
|
|
630
|
+
cfg,
|
|
631
|
+
LogEntry {
|
|
632
|
+
level: "error".to_string(),
|
|
633
|
+
event: "error".to_string(),
|
|
634
|
+
operation: operation.to_string(),
|
|
635
|
+
duration_ms: start.elapsed().as_millis() as i64,
|
|
636
|
+
status: err_status(e),
|
|
637
|
+
error: Some(format!("{:?}", e)),
|
|
638
|
+
},
|
|
639
|
+
)
|
|
640
|
+
};
|
|
211
641
|
|
|
212
642
|
/* Pre-flight cancellation check — short-circuit before any work. */
|
|
213
643
|
if let Some(ref tok) = cancel_token {
|
|
214
644
|
if tok.is_cancelled() {
|
|
215
|
-
|
|
216
|
-
cfg,
|
|
217
|
-
LogEntry {
|
|
218
|
-
level: "error".to_string(),
|
|
219
|
-
event: "error".to_string(),
|
|
220
|
-
operation: operation.to_string(),
|
|
221
|
-
duration_ms: start.elapsed().as_millis() as i64,
|
|
222
|
-
status: None,
|
|
223
|
-
error: Some("Canceled".to_string()),
|
|
224
|
-
},
|
|
225
|
-
);
|
|
645
|
+
log_error(&Error::Canceled);
|
|
226
646
|
return Err(Error::Canceled);
|
|
227
647
|
}
|
|
228
648
|
}
|
|
@@ -239,16 +659,17 @@ pub(crate) async fn do_request(
|
|
|
239
659
|
},
|
|
240
660
|
);
|
|
241
661
|
|
|
242
|
-
/*
|
|
243
|
-
Multipart + Stream own non-Clone values and cannot be retried — 401 falls through) */
|
|
662
|
+
/* Json/FormUrl/Raw can be sent again; Multipart + Stream own non-Clone values */
|
|
244
663
|
let retry_body: Option<RequestBody<'_>> = match &body {
|
|
245
664
|
RequestBody::None => Some(RequestBody::None),
|
|
246
665
|
RequestBody::Json(v) => Some(RequestBody::Json(v)),
|
|
247
|
-
RequestBody::FormUrl(m) => Some(RequestBody::FormUrl(m)),
|
|
666
|
+
RequestBody::FormUrl(m) => Some(RequestBody::FormUrl(m.clone())),
|
|
667
|
+
RequestBody::Raw(ct, b) => Some(RequestBody::Raw(ct, b.clone())),
|
|
248
668
|
RequestBody::Multipart(_) => None,
|
|
249
669
|
RequestBody::Stream(_) => None,
|
|
250
670
|
};
|
|
251
671
|
|
|
672
|
+
let token = auth.current().await;
|
|
252
673
|
let mut result = match execute_request(
|
|
253
674
|
client,
|
|
254
675
|
cfg,
|
|
@@ -257,7 +678,7 @@ pub(crate) async fn do_request(
|
|
|
257
678
|
query,
|
|
258
679
|
body,
|
|
259
680
|
call_headers,
|
|
260
|
-
|
|
681
|
+
token.as_deref(),
|
|
261
682
|
false,
|
|
262
683
|
timeout,
|
|
263
684
|
cancel_token.clone(),
|
|
@@ -267,42 +688,21 @@ pub(crate) async fn do_request(
|
|
|
267
688
|
{
|
|
268
689
|
Ok(r) => r,
|
|
269
690
|
Err(e) => {
|
|
270
|
-
|
|
271
|
-
cfg,
|
|
272
|
-
LogEntry {
|
|
273
|
-
level: "error".to_string(),
|
|
274
|
-
event: "error".to_string(),
|
|
275
|
-
operation: operation.to_string(),
|
|
276
|
-
duration_ms: start.elapsed().as_millis() as i64,
|
|
277
|
-
status: err_status(&e),
|
|
278
|
-
error: Some(format!("{:?}", e)),
|
|
279
|
-
},
|
|
280
|
-
);
|
|
691
|
+
log_error(&e);
|
|
281
692
|
return Err(e);
|
|
282
693
|
}
|
|
283
694
|
};
|
|
284
695
|
|
|
285
696
|
if result.status == 401 {
|
|
286
|
-
if let Some(
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
level: "error".to_string(),
|
|
296
|
-
event: "error".to_string(),
|
|
297
|
-
operation: operation.to_string(),
|
|
298
|
-
duration_ms: start.elapsed().as_millis() as i64,
|
|
299
|
-
status: err_status(&mapped),
|
|
300
|
-
error: Some(format!("{:?}", mapped)),
|
|
301
|
-
},
|
|
302
|
-
);
|
|
303
|
-
return Err(mapped);
|
|
304
|
-
}
|
|
305
|
-
};
|
|
697
|
+
if let (Some(hook), Some(retry)) = (cfg.on_auth_expired.as_ref(), retry_body) {
|
|
698
|
+
let new_token = match auth.refresh(hook, token.as_deref()).await {
|
|
699
|
+
Ok(t) => t,
|
|
700
|
+
Err(e) => {
|
|
701
|
+
log_error(&e);
|
|
702
|
+
return Err(e);
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
if let Some(new_token) = new_token {
|
|
306
706
|
result = match execute_request(
|
|
307
707
|
client,
|
|
308
708
|
cfg,
|
|
@@ -321,22 +721,11 @@ pub(crate) async fn do_request(
|
|
|
321
721
|
{
|
|
322
722
|
Ok(r) => r,
|
|
323
723
|
Err(e) => {
|
|
324
|
-
|
|
325
|
-
cfg,
|
|
326
|
-
LogEntry {
|
|
327
|
-
level: "error".to_string(),
|
|
328
|
-
event: "error".to_string(),
|
|
329
|
-
operation: operation.to_string(),
|
|
330
|
-
duration_ms: start.elapsed().as_millis() as i64,
|
|
331
|
-
status: err_status(&e),
|
|
332
|
-
error: Some(format!("{:?}", e)),
|
|
333
|
-
},
|
|
334
|
-
);
|
|
724
|
+
log_error(&e);
|
|
335
725
|
return Err(e);
|
|
336
726
|
}
|
|
337
727
|
};
|
|
338
728
|
}
|
|
339
|
-
/* Multipart/Stream 401: Form/Body is not Clone, cannot retry; fall through to error mapping */
|
|
340
729
|
}
|
|
341
730
|
}
|
|
342
731
|
|
|
@@ -354,26 +743,18 @@ pub(crate) async fn do_request(
|
|
|
354
743
|
},
|
|
355
744
|
);
|
|
356
745
|
|
|
357
|
-
if
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
event: "error".to_string(),
|
|
363
|
-
operation: operation.to_string(),
|
|
364
|
-
duration_ms: start.elapsed().as_millis() as i64,
|
|
365
|
-
status: Some(status_i32),
|
|
366
|
-
error: Some(format!("{:?}", err)),
|
|
367
|
-
},
|
|
368
|
-
);
|
|
369
|
-
return Err(err);
|
|
746
|
+
if !(200..300).contains(&result.status) {
|
|
747
|
+
if let Some(err) = raise_for_status(result.status, std::mem::take(&mut result.body)) {
|
|
748
|
+
log_error(&err);
|
|
749
|
+
return Err(err);
|
|
750
|
+
}
|
|
370
751
|
}
|
|
371
752
|
|
|
372
753
|
Ok(result)
|
|
373
754
|
}
|
|
374
755
|
|
|
375
756
|
#[allow(clippy::too_many_arguments)]
|
|
376
|
-
async fn
|
|
757
|
+
async fn build_request(
|
|
377
758
|
client: &reqwest::Client,
|
|
378
759
|
cfg: &ClientConfig,
|
|
379
760
|
method: reqwest::Method,
|
|
@@ -383,33 +764,31 @@ async fn execute_request(
|
|
|
383
764
|
call_headers: Option<&HashMap<String, String>>,
|
|
384
765
|
bearer_token: Option<&str>,
|
|
385
766
|
is_retry: bool,
|
|
386
|
-
per_call_timeout: Option<std::time::Duration>,
|
|
387
|
-
cancel_token: Option<CancellationToken>,
|
|
388
767
|
request_meta: Option<&crate::invalidation::RequestMeta>,
|
|
389
|
-
) -> Result<
|
|
768
|
+
) -> Result<(reqwest::RequestBuilder, Arc<Mutex<HashMap<String, serde_json::Value>>>), Error> {
|
|
390
769
|
let url = build_url(&cfg.base_url, raw_url, query)?;
|
|
391
|
-
let url_str = url.to_string();
|
|
392
770
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
771
|
+
let mut hook_headers = merged_headers(
|
|
772
|
+
&cfg.headers,
|
|
773
|
+
call_headers,
|
|
774
|
+
cfg.auth_header_name.as_deref(),
|
|
775
|
+
cfg.auth_header_prefix.as_deref(),
|
|
776
|
+
bearer_token,
|
|
777
|
+
);
|
|
778
|
+
match &body {
|
|
779
|
+
RequestBody::Json(_) => set_header(&mut hook_headers, "Content-Type", "application/json".into()),
|
|
780
|
+
RequestBody::FormUrl(_) => set_header(
|
|
781
|
+
&mut hook_headers,
|
|
782
|
+
"Content-Type",
|
|
783
|
+
"application/x-www-form-urlencoded".into(),
|
|
784
|
+
),
|
|
785
|
+
RequestBody::Raw(ct, _) => set_header(&mut hook_headers, "Content-Type", ct.to_string()),
|
|
786
|
+
RequestBody::Stream(_) => {
|
|
787
|
+
if !hook_headers.keys().any(|k| k.eq_ignore_ascii_case("content-type")) {
|
|
788
|
+
set_header(&mut hook_headers, "Content-Type", "application/octet-stream".into());
|
|
789
|
+
}
|
|
405
790
|
}
|
|
406
|
-
|
|
407
|
-
/* auto correlation id — config/call win; on_request hooks may overwrite */
|
|
408
|
-
if !hook_headers
|
|
409
|
-
.keys()
|
|
410
|
-
.any(|k| k.eq_ignore_ascii_case("x-request-id"))
|
|
411
|
-
{
|
|
412
|
-
hook_headers.insert("x-request-id".to_string(), Uuid::new_v4().to_string());
|
|
791
|
+
RequestBody::Multipart(_) | RequestBody::None => {}
|
|
413
792
|
}
|
|
414
793
|
|
|
415
794
|
let state = cfg
|
|
@@ -424,132 +803,178 @@ async fn execute_request(
|
|
|
424
803
|
|
|
425
804
|
let mut req_ctx = RequestContext {
|
|
426
805
|
method: method.clone(),
|
|
427
|
-
url:
|
|
806
|
+
url: url.to_string(),
|
|
428
807
|
path: raw_url.to_string(),
|
|
429
808
|
headers: hook_headers,
|
|
430
809
|
state: Arc::clone(&state),
|
|
431
810
|
is_retry,
|
|
432
|
-
selector: meta_selector
|
|
811
|
+
selector: meta_selector,
|
|
433
812
|
is_stale: meta_is_stale,
|
|
434
|
-
invalidated_by: meta_by
|
|
813
|
+
invalidated_by: meta_by,
|
|
435
814
|
};
|
|
436
815
|
|
|
437
|
-
/* fire on_request hooks */
|
|
438
816
|
for hook in cfg.on_request.iter() {
|
|
439
817
|
hook(&mut req_ctx).await?;
|
|
440
818
|
}
|
|
441
819
|
|
|
442
820
|
let mut req = client.request(req_ctx.method.clone(), &req_ctx.url);
|
|
443
|
-
|
|
444
|
-
/* apply headers from context (already merged above + any hook mutations) */
|
|
445
821
|
for (k, v) in &req_ctx.headers {
|
|
446
822
|
req = req.header(k.as_str(), v.as_str());
|
|
447
823
|
}
|
|
448
|
-
|
|
449
|
-
/* apply body and content-type */
|
|
450
824
|
req = match body {
|
|
451
|
-
RequestBody::Json(v) =>
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
RequestBody::FormUrl(m) => req.form(m),
|
|
825
|
+
RequestBody::Json(v) => req.body(serde_json::to_vec(v)?),
|
|
826
|
+
RequestBody::FormUrl(pairs) => req.body(encode_query(&pairs)),
|
|
827
|
+
RequestBody::Raw(_, b) => req.body(b),
|
|
456
828
|
RequestBody::Multipart(f) => req.multipart(f),
|
|
457
|
-
RequestBody::Stream(b) =>
|
|
458
|
-
req = req.header("Content-Type", "application/octet-stream");
|
|
459
|
-
req.body(b)
|
|
460
|
-
}
|
|
829
|
+
RequestBody::Stream(b) => req.body(b),
|
|
461
830
|
RequestBody::None => req,
|
|
462
831
|
};
|
|
832
|
+
Ok((req, state))
|
|
833
|
+
}
|
|
463
834
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
835
|
+
#[allow(clippy::too_many_arguments)]
|
|
836
|
+
async fn execute_request(
|
|
837
|
+
client: &reqwest::Client,
|
|
838
|
+
cfg: &ClientConfig,
|
|
839
|
+
method: reqwest::Method,
|
|
840
|
+
raw_url: &str,
|
|
841
|
+
query: &[(String, String)],
|
|
842
|
+
body: RequestBody<'_>,
|
|
843
|
+
call_headers: Option<&HashMap<String, String>>,
|
|
844
|
+
bearer_token: Option<&str>,
|
|
845
|
+
is_retry: bool,
|
|
846
|
+
per_call_timeout: Option<std::time::Duration>,
|
|
847
|
+
cancel_token: Option<CancellationToken>,
|
|
848
|
+
request_meta: Option<&crate::invalidation::RequestMeta>,
|
|
849
|
+
) -> Result<RequestResult, Error> {
|
|
850
|
+
let (mut req, state) = build_request(
|
|
851
|
+
client,
|
|
852
|
+
cfg,
|
|
853
|
+
method,
|
|
854
|
+
raw_url,
|
|
855
|
+
query,
|
|
856
|
+
body,
|
|
857
|
+
call_headers,
|
|
858
|
+
bearer_token,
|
|
859
|
+
is_retry,
|
|
860
|
+
request_meta,
|
|
861
|
+
)
|
|
862
|
+
.await?;
|
|
468
863
|
|
|
469
|
-
/*
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
tokio::select! {
|
|
474
|
-
biased;
|
|
475
|
-
_ = tok.cancelled() => return Err(Error::Canceled),
|
|
476
|
-
r = req.send() => r,
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
None => req.send().await,
|
|
864
|
+
/* per-call timeout overrides cfg.timeout; ZERO means no timeout */
|
|
865
|
+
let effective_timeout = per_call_timeout.unwrap_or(cfg.timeout);
|
|
866
|
+
if !effective_timeout.is_zero() {
|
|
867
|
+
req = req.timeout(effective_timeout);
|
|
480
868
|
}
|
|
481
|
-
.map_err(|e| {
|
|
482
|
-
if e.is_timeout() {
|
|
483
|
-
Error::Timeout
|
|
484
|
-
} else if e.is_request() {
|
|
485
|
-
Error::Canceled
|
|
486
|
-
} else {
|
|
487
|
-
Error::Transport(e)
|
|
488
|
-
}
|
|
489
|
-
})?;
|
|
490
869
|
|
|
491
|
-
|
|
492
|
-
let
|
|
493
|
-
|
|
494
|
-
|
|
870
|
+
/* Race send + body read against cancel_token so external cancellation wins. */
|
|
871
|
+
let work = async {
|
|
872
|
+
let resp = req.send().await.map_err(map_send_error)?;
|
|
873
|
+
let status = resp.status().as_u16();
|
|
874
|
+
let resp_headers = resp.headers().clone();
|
|
875
|
+
let resp_url = resp.url().to_string();
|
|
876
|
+
let body = read_capped(resp, cfg.max_response_bytes).await?;
|
|
877
|
+
Ok::<_, Error>((status, resp_headers, resp_url, body))
|
|
878
|
+
};
|
|
879
|
+
let (status, resp_headers, resp_url, body_bytes) = match cancel_token {
|
|
495
880
|
Some(tok) => {
|
|
496
881
|
tokio::select! {
|
|
497
882
|
biased;
|
|
498
883
|
_ = tok.cancelled() => return Err(Error::Canceled),
|
|
499
|
-
r =
|
|
884
|
+
r = work => r?,
|
|
500
885
|
}
|
|
501
886
|
}
|
|
502
|
-
None =>
|
|
503
|
-
}
|
|
504
|
-
.map_err(|e| {
|
|
505
|
-
if e.is_timeout() {
|
|
506
|
-
Error::Timeout
|
|
507
|
-
} else {
|
|
508
|
-
Error::Transport(e)
|
|
509
|
-
}
|
|
510
|
-
})?;
|
|
511
|
-
let body_bytes = bytes.to_vec();
|
|
887
|
+
None => work.await?,
|
|
888
|
+
};
|
|
512
889
|
|
|
890
|
+
let (meta_selector, meta_is_stale, meta_by) = match request_meta {
|
|
891
|
+
Some(m) => (m.selector.clone(), m.is_stale, m.invalidated_by.clone()),
|
|
892
|
+
None => (String::new(), false, Vec::new()),
|
|
893
|
+
};
|
|
513
894
|
let mut resp_ctx = ResponseContext {
|
|
514
895
|
status,
|
|
515
|
-
response: body_bytes
|
|
516
|
-
headers: resp_headers
|
|
517
|
-
state
|
|
896
|
+
response: body_bytes,
|
|
897
|
+
headers: resp_headers,
|
|
898
|
+
state,
|
|
518
899
|
is_retry,
|
|
519
900
|
selector: meta_selector,
|
|
520
901
|
is_stale: meta_is_stale,
|
|
521
902
|
invalidated_by: meta_by,
|
|
522
903
|
};
|
|
523
904
|
|
|
524
|
-
/* fire on_response hooks */
|
|
525
905
|
for hook in cfg.on_response.iter() {
|
|
526
906
|
hook(&mut resp_ctx).await?;
|
|
527
907
|
}
|
|
528
908
|
|
|
529
909
|
Ok(RequestResult {
|
|
530
|
-
body:
|
|
910
|
+
body: resp_ctx.response,
|
|
531
911
|
status,
|
|
532
|
-
headers:
|
|
912
|
+
headers: resp_ctx.headers,
|
|
533
913
|
url: resp_url,
|
|
534
914
|
})
|
|
535
915
|
}
|
|
536
916
|
|
|
537
|
-
///
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
917
|
+
/// open_stream sends a streaming request (SSE) through the same URL, header,
|
|
918
|
+
/// auth and hook pipeline as a regular request, without a body-read timeout.
|
|
919
|
+
/// A non-2xx status becomes a typed error.
|
|
920
|
+
#[allow(clippy::too_many_arguments)]
|
|
921
|
+
pub(crate) async fn open_stream(
|
|
922
|
+
client: &reqwest::Client,
|
|
923
|
+
cfg: &ClientConfig,
|
|
924
|
+
auth: &AuthState,
|
|
925
|
+
method: reqwest::Method,
|
|
926
|
+
raw_url: &str,
|
|
542
927
|
query: &[(String, String)],
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
let
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
928
|
+
body: RequestBody<'_>,
|
|
929
|
+
call_headers: Option<&HashMap<String, String>>,
|
|
930
|
+
) -> Result<reqwest::Response, Error> {
|
|
931
|
+
let token = auth.current().await;
|
|
932
|
+
let (req, _state) = build_request(
|
|
933
|
+
client,
|
|
934
|
+
cfg,
|
|
935
|
+
method,
|
|
936
|
+
raw_url,
|
|
937
|
+
query,
|
|
938
|
+
body,
|
|
939
|
+
call_headers,
|
|
940
|
+
token.as_deref(),
|
|
941
|
+
false,
|
|
942
|
+
None,
|
|
943
|
+
)
|
|
944
|
+
.await?;
|
|
945
|
+
let resp = req.send().await.map_err(map_send_error)?;
|
|
946
|
+
let status = resp.status().as_u16();
|
|
947
|
+
if !(200..300).contains(&status) {
|
|
948
|
+
let body = read_capped(resp, 64 * 1024).await.unwrap_or_default();
|
|
949
|
+
return Err(raise_for_status(status, body).unwrap_or(Error::Other(format!("status {}", status))));
|
|
552
950
|
}
|
|
553
|
-
Ok(
|
|
951
|
+
Ok(resp)
|
|
554
952
|
}
|
|
555
953
|
|
|
954
|
+
/// auth_headers returns config headers, extra headers and the auth header, for
|
|
955
|
+
/// transports that cannot run the request pipeline (WebSocket dials).
|
|
956
|
+
pub(crate) async fn auth_headers(
|
|
957
|
+
cfg: &ClientConfig,
|
|
958
|
+
auth: &AuthState,
|
|
959
|
+
extra: Option<&HashMap<String, String>>,
|
|
960
|
+
) -> HashMap<String, String> {
|
|
961
|
+
let token = auth.current().await;
|
|
962
|
+
merged_headers(
|
|
963
|
+
&cfg.headers,
|
|
964
|
+
extra,
|
|
965
|
+
cfg.auth_header_name.as_deref(),
|
|
966
|
+
cfg.auth_header_prefix.as_deref(),
|
|
967
|
+
token.as_deref(),
|
|
968
|
+
)
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/// to_ws_url swaps the scheme only; a URL inside the query string is untouched.
|
|
972
|
+
pub fn to_ws_url(url: &str) -> String {
|
|
973
|
+
if let Some(rest) = url.strip_prefix("https://") {
|
|
974
|
+
format!("wss://{}", rest)
|
|
975
|
+
} else if let Some(rest) = url.strip_prefix("http://") {
|
|
976
|
+
format!("ws://{}", rest)
|
|
977
|
+
} else {
|
|
978
|
+
url.to_string()
|
|
979
|
+
}
|
|
980
|
+
}
|