@voxgig/sdkgen 1.5.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmp/AgentGuideContent.js +5 -0
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +13 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/helpers/canonType.d.ts +3 -1
- package/dist/helpers/canonType.js +44 -1
- package/dist/helpers/canonType.js.map +1 -1
- package/dist/helpers/packageMeta.js +7 -0
- package/dist/helpers/packageMeta.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -3
- package/dist/sdkgen.js +4 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/project/.sdk/model/feature/paging.aontu +7 -0
- package/project/.sdk/model/target/py-data.aontu +64 -0
- package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +89 -1
- package/project/.sdk/src/cmp/clojure/Main_clojure.ts +1 -0
- package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +97 -0
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +78 -0
- package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +84 -0
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +87 -0
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +75 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +85 -0
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +4 -0
- package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +80 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +86 -0
- package/project/.sdk/src/cmp/py/Entity_py.ts +9 -2
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +1 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +34 -6
- package/project/.sdk/src/cmp/py/Package_py.ts +6 -9
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +2 -2
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +3 -3
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +75 -8
- package/project/.sdk/src/cmp/py-data/Main_py-data.ts +987 -0
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +71 -0
- package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +95 -0
- package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +86 -0
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +75 -0
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +95 -0
- package/project/.sdk/tm/c/core/sdk.h +7 -0
- package/project/.sdk/tm/c/feature/paging.c +141 -2
- package/project/.sdk/tm/c/utility/graphql.c +178 -0
- package/project/.sdk/tm/c/utility/make_options.c +1 -1
- package/project/.sdk/tm/c/utility/make_response.c +6 -0
- package/project/.sdk/tm/c/utility/make_spec.c +23 -3
- package/project/.sdk/tm/clojure/src/sdk/client.clj +64 -1
- package/project/.sdk/tm/clojure/src/sdk/core.clj +126 -3
- package/project/.sdk/tm/clojure/src/sdk/features.clj +80 -14
- package/project/.sdk/tm/cpp/core/types.hpp +83 -0
- package/project/.sdk/tm/cpp/feature/paging.hpp +103 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +152 -3
- package/project/.sdk/tm/csharp/core/Utility.cs +4 -0
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +118 -1
- package/project/.sdk/tm/csharp/utility/Graphql.cs +190 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +1 -1
- package/project/.sdk/tm/csharp/utility/MakeResponse.cs +7 -0
- package/project/.sdk/tm/csharp/utility/MakeSpec.cs +21 -2
- package/project/.sdk/tm/csharp/utility/Register.cs +2 -0
- package/project/.sdk/tm/dart/lib/Point.dart +7 -0
- package/project/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +99 -4
- package/project/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +1 -1
- package/project/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +5 -0
- package/project/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +18 -2
- package/project/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +6 -1
- package/project/.sdk/tm/dart/lib/utility/Utility.dart +9 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +136 -13
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +187 -3
- package/project/.sdk/tm/go/core/utility_type.go +2 -0
- package/project/.sdk/tm/go/feature/paging_feature.go +87 -1
- package/project/.sdk/tm/go/utility/graphql.go +168 -0
- package/project/.sdk/tm/go/utility/make_options.go +1 -1
- package/project/.sdk/tm/go/utility/make_response.go +6 -0
- package/project/.sdk/tm/go/utility/make_spec.go +19 -2
- package/project/.sdk/tm/go/utility/register.go +2 -0
- package/project/.sdk/tm/haskell/src/SdkFeatures.hs +146 -7
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +153 -3
- package/project/.sdk/tm/java/core/SdkClient.java +93 -0
- package/project/.sdk/tm/java/core/Utility.java +4 -0
- package/project/.sdk/tm/java/feature/PagingFeature.java +100 -1
- package/project/.sdk/tm/java/utility/Graphql.java +191 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +1 -1
- package/project/.sdk/tm/java/utility/MakeResponse.java +6 -0
- package/project/.sdk/tm/java/utility/MakeSpec.java +17 -2
- package/project/.sdk/tm/java/utility/Register.java +2 -0
- package/project/.sdk/tm/js/src/Point.js +5 -0
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +82 -3
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +14 -2
- package/project/.sdk/tm/js/src/utility/GraphqlUtility.js +148 -0
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +1 -1
- package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +7 -0
- package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +19 -2
- package/project/.sdk/tm/js/src/utility/Utility.js +4 -0
- package/project/.sdk/tm/kotlin/core/SdkClient.kt +84 -0
- package/project/.sdk/tm/kotlin/core/Utility.kt +2 -0
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +92 -1
- package/project/.sdk/tm/kotlin/utility/Graphql.kt +145 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/MakeReqRes.kt +6 -0
- package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +17 -2
- package/project/.sdk/tm/kotlin/utility/Register.kt +2 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +134 -1
- package/project/.sdk/tm/lua/feature/paging_feature.lua +97 -2
- package/project/.sdk/tm/lua/utility/graphql.lua +163 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +1 -1
- package/project/.sdk/tm/lua/utility/make_response.lua +6 -0
- package/project/.sdk/tm/lua/utility/make_spec.lua +18 -2
- package/project/.sdk/tm/lua/utility/register.lua +3 -0
- package/project/.sdk/tm/ocaml/sdk_features.ml +164 -20
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +143 -4
- package/project/.sdk/tm/ocaml/sdk_types.ml +2 -0
- package/project/.sdk/tm/perl/feature/paging_feature.pm +91 -3
- package/project/.sdk/tm/perl/utility/graphql.pm +150 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +1 -1
- package/project/.sdk/tm/perl/utility/make_response.pm +6 -0
- package/project/.sdk/tm/perl/utility/make_spec.pm +20 -2
- package/project/.sdk/tm/perl/utility/register.pm +1 -0
- package/project/.sdk/tm/php/core/UtilityType.php +2 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +106 -4
- package/project/.sdk/tm/php/utility/Graphql.php +176 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +1 -1
- package/project/.sdk/tm/php/utility/MakeResponse.php +7 -0
- package/project/.sdk/tm/php/utility/MakeSpec.php +20 -2
- package/project/.sdk/tm/php/utility/Register.php +3 -0
- package/project/.sdk/tm/py/pkg/core/__init__.py +0 -0
- package/project/.sdk/tm/py/{core → pkg/core}/context.py +8 -8
- package/project/.sdk/tm/py/{core → pkg/core}/operation.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/response.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/result.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/utility_type.py +2 -0
- package/project/.sdk/tm/py/pkg/entity/__init__.py +0 -0
- package/project/.sdk/tm/py/pkg/feature/__init__.py +0 -0
- package/project/.sdk/tm/py/{feature → pkg/feature}/audit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/cache_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/clienttrack_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/debug_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/idempotency_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/log_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/metrics_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/netsim_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/paging_feature.py +87 -6
- package/project/.sdk/tm/py/{feature → pkg/feature}/proxy_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/ratelimit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/rbac_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/retry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/streaming_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/telemetry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/test_feature.py +2 -2
- package/project/.sdk/tm/py/{feature → pkg/feature}/timeout_feature.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/__init__.py +0 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/feature_init.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/fetcher.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/graphql.py +145 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_context.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_error.py +5 -5
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_fetch_def.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_options.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_point.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_response.py +6 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_spec.py +19 -4
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_url.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/param.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_auth.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_headers.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_method.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_params.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_path.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_query.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/register.py +71 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_response.py +2 -2
- package/project/.sdk/tm/py/test/feature_harness.py +6 -6
- package/project/.sdk/tm/py/test/runner.py +1 -1
- package/project/.sdk/tm/py/test/struct_runner.py +1 -1
- package/project/.sdk/tm/py/test/test_pipeline.py +5 -5
- package/project/.sdk/tm/py/test/test_primary_utility.py +8 -8
- package/project/.sdk/tm/py/test/test_struct_utility.py +3 -3
- package/project/.sdk/tm/py-data/LICENSE +22 -0
- package/project/.sdk/tm/py-data/Makefile +57 -0
- package/project/.sdk/tm/py-data/pkg/auth.py +131 -0
- package/project/.sdk/tm/py-data/pkg/fetch.py +178 -0
- package/project/.sdk/tm/py-data/pkg/frames.py +236 -0
- package/project/.sdk/tm/py-data/src/feature/README.md +8 -0
- package/project/.sdk/tm/py-data/src/feature/debug/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/idempotency/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/metrics/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/netsim/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/paging/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/proxy/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/ratelimit/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/rbac/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/retry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/streaming/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/telemetry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/timeout/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/test/__init__.py +0 -0
- package/project/.sdk/tm/py-data/test/test_auth.py +95 -0
- package/project/.sdk/tm/py-data/test/test_fetch.py +152 -0
- package/project/.sdk/tm/py-data/test/test_frames.py +250 -0
- package/project/.sdk/tm/rb/core/utility_type.rb +1 -0
- package/project/.sdk/tm/rb/feature/paging_feature.rb +89 -3
- package/project/.sdk/tm/rb/utility/graphql.rb +131 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +1 -1
- package/project/.sdk/tm/rb/utility/make_response.rb +6 -0
- package/project/.sdk/tm/rb/utility/make_spec.rb +18 -2
- package/project/.sdk/tm/rb/utility/register.rb +3 -0
- package/project/.sdk/tm/rust/feature/paging.rs +124 -2
- package/project/.sdk/tm/rust/utility/graphql.rs +193 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/rust/utility/make_response.rs +6 -0
- package/project/.sdk/tm/rust/utility/make_spec.rs +32 -4
- package/project/.sdk/tm/rust/utility/mod.rs +1 -0
- package/project/.sdk/tm/scala/core/SdkClient.scala +90 -0
- package/project/.sdk/tm/scala/core/Utility.scala +4 -0
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +104 -3
- package/project/.sdk/tm/scala/utility/Graphql.scala +160 -0
- package/project/.sdk/tm/scala/utility/Make.scala +7 -1
- package/project/.sdk/tm/scala/utility/MakeSpec.scala +22 -2
- package/project/.sdk/tm/scala/utility/Register.scala +2 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Utility.swift +4 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +86 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Graphql.swift +142 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +24 -2
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Register.swift +2 -0
- package/project/.sdk/tm/ts/src/Point.ts +7 -0
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +82 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +14 -2
- package/project/.sdk/tm/ts/src/utility/GraphqlUtility.ts +152 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +1 -1
- package/project/.sdk/tm/ts/src/utility/MakeResponseUtility.ts +7 -0
- package/project/.sdk/tm/ts/src/utility/MakeSpecUtility.ts +19 -2
- package/project/.sdk/tm/ts/src/utility/Utility.ts +4 -0
- package/project/.sdk/tm/zig/core/utility.zig +198 -3
- package/project/.sdk/tm/zig/feature/paging.zig +128 -1
- package/src/cmp/AgentGuideContent.ts +5 -0
- package/src/cmp/Feature.ts +13 -0
- package/src/helpers/canonType.ts +46 -0
- package/src/helpers/packageMeta.ts +7 -0
- package/src/sdkgen.ts +6 -1
- package/project/.sdk/tm/py/utility/register.py +0 -68
- /package/project/.sdk/tm/py/{core → pkg/core}/control.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/error.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/helpers.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/spec.py +0 -0
- /package/project/.sdk/tm/py/{feature → pkg/feature}/base_feature.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/clean.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/done.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_add.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_hook.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/make_result.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_basic.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_headers.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/__init__.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/voxgig_struct.py +0 -0
- /package/project/.sdk/tm/{py/core/__init__.py → py-data/src/feature/audit/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/entity/__init__.py → py-data/src/feature/base/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/feature/__init__.py → py-data/src/feature/cache/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/utility/__init__.py → py-data/src/feature/clienttrack/.gitkeep} +0 -0
|
@@ -114,8 +114,24 @@ func makeSpecUtil(_ ctx: Context) throws -> Spec {
|
|
|
114
114
|
ctx.spec!.params = utility.prepareParams(ctx)
|
|
115
115
|
ctx.spec!.query = utility.prepareQuery(ctx)
|
|
116
116
|
ctx.spec!.headers = utility.prepareHeaders(ctx)
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
|
|
118
|
+
if "graphql" == gp(ctx.point, "kind").asString {
|
|
119
|
+
// GraphQL addresses one endpoint: no path parts, no query string, and
|
|
120
|
+
// the body carries the operation. prepareBody is skipped deliberately —
|
|
121
|
+
// it only emits a body for data-input ops, whereas every GraphQL op
|
|
122
|
+
// posts one, including load/list/remove.
|
|
123
|
+
ctx.spec!.body = utility.graphqlBody(ctx)
|
|
124
|
+
ctx.spec!.path = ""
|
|
125
|
+
// prepareQuery already copied the op's match arguments into the query
|
|
126
|
+
// string. Those same values are bound as operation variables, so
|
|
127
|
+
// leaving them would send /graphql?id=i1.
|
|
128
|
+
ctx.spec!.query = VMap()
|
|
129
|
+
ctx.spec!.headers.entries["content-type"] = .string(graphqlContentType)
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
ctx.spec!.body = utility.prepareBody(ctx)
|
|
133
|
+
ctx.spec!.path = utility.preparePath(ctx)
|
|
134
|
+
}
|
|
119
135
|
|
|
120
136
|
if let explain = ctx.ctrl.explain {
|
|
121
137
|
explain.entries["spec"] = .nat(ctx.spec!)
|
|
@@ -266,6 +282,12 @@ func makeResponseUtil(_ ctx: Context) throws -> Response {
|
|
|
266
282
|
_ = utility.resultBasic(ctx)
|
|
267
283
|
_ = utility.resultHeaders(ctx)
|
|
268
284
|
_ = utility.resultBody(ctx)
|
|
285
|
+
|
|
286
|
+
// GraphQL reports failures as a top-level `errors` array under HTTP 200,
|
|
287
|
+
// so resultBasic's status check never sees them. Lift them here, before
|
|
288
|
+
// the response transform tries to unwrap data that is not there.
|
|
289
|
+
_ = utility.graphqlErrors(ctx)
|
|
290
|
+
|
|
269
291
|
_ = utility.transformResponse(ctx)
|
|
270
292
|
|
|
271
293
|
if result.err == nil {
|
|
@@ -12,7 +12,7 @@ private func buildOptSpec() -> Value {
|
|
|
12
12
|
|
|
13
13
|
let allow = VMap()
|
|
14
14
|
allow.entries["method"] = .string("GET,PUT,POST,PATCH,DELETE,OPTIONS")
|
|
15
|
-
allow.entries["op"] = .string("create,update,load,list,remove,command,direct")
|
|
15
|
+
allow.entries["op"] = .string("create,update,load,list,remove,command,direct,graphql")
|
|
16
16
|
|
|
17
17
|
let entityChild = VMap()
|
|
18
18
|
entityChild.entries["`$OPEN`"] = .bool(true)
|
|
@@ -28,6 +28,8 @@ func registerAll(_ u: Utility) {
|
|
|
28
28
|
u.prepareParams = prepareParamsUtil
|
|
29
29
|
u.preparePath = preparePathUtil
|
|
30
30
|
u.prepareQuery = prepareQueryUtil
|
|
31
|
+
u.graphqlBody = graphqlBodyUtil
|
|
32
|
+
u.graphqlErrors = graphqlErrorsUtil
|
|
31
33
|
u.resultBasic = resultBasicUtil
|
|
32
34
|
u.resultBody = resultBodyUtil
|
|
33
35
|
u.resultHeaders = resultHeadersUtil
|
|
@@ -4,6 +4,11 @@ import { getprop } from './utility/StructUtility'
|
|
|
4
4
|
|
|
5
5
|
class Point {
|
|
6
6
|
args: { params: any[] }
|
|
7
|
+
// Transport this point speaks: 'http' (default) or 'graphql'. GraphQL
|
|
8
|
+
// points carry their operation document in `graphql` and address the
|
|
9
|
+
// single endpoint, so method is always POST and parts is empty.
|
|
10
|
+
kind: string
|
|
11
|
+
graphql?: any
|
|
7
12
|
rename: { params: Record<string, string> }
|
|
8
13
|
method: string
|
|
9
14
|
orig: string
|
|
@@ -17,6 +22,8 @@ class Point {
|
|
|
17
22
|
|
|
18
23
|
constructor(altmap: Record<string, any>) {
|
|
19
24
|
this.args = getprop(altmap, 'args', { params: [] })
|
|
25
|
+
this.kind = getprop(altmap, 'kind', 'http')
|
|
26
|
+
this.graphql = getprop(altmap, 'graphql')
|
|
20
27
|
this.rename = getprop(altmap, 'rename', { params: {} })
|
|
21
28
|
this.method = getprop(altmap, 'method', '')
|
|
22
29
|
this.orig = getprop(altmap, 'orig', '')
|
|
@@ -48,6 +48,14 @@ class PagingFeature extends BaseFeature {
|
|
|
48
48
|
// A per-call cursor/page from ctrl takes priority (used by auto-iteration).
|
|
49
49
|
const paging = (ctx.ctrl && ctx.ctrl.paging) || {}
|
|
50
50
|
|
|
51
|
+
// GraphQL paginates through operation VARIABLES, not the query string.
|
|
52
|
+
// This hook runs after makeSpec, so spec.body already holds the
|
|
53
|
+
// { query, variables } envelope, and before makeFetchDef serialises it.
|
|
54
|
+
const point: any = ctx.point
|
|
55
|
+
if ('graphql' === (null == point ? undefined : point.kind)) {
|
|
56
|
+
return this._graphqlPreRequest(ctx, paging)
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
if (null != paging.cursor) {
|
|
52
60
|
spec.query[cursorParam] = paging.cursor
|
|
53
61
|
}
|
|
@@ -73,6 +81,10 @@ class PagingFeature extends BaseFeature {
|
|
|
73
81
|
const headers = result.headers || {}
|
|
74
82
|
const body = result.body
|
|
75
83
|
|
|
84
|
+
// Set when the response states hasMore outright, rather than leaving it
|
|
85
|
+
// to be inferred from the presence of a cursor.
|
|
86
|
+
let explicitMore = false
|
|
87
|
+
|
|
76
88
|
const paging: any = {
|
|
77
89
|
page: this._num(this._header(headers, 'x-page')),
|
|
78
90
|
totalCount: this._num(this._header(headers, 'x-total-count')),
|
|
@@ -91,16 +103,49 @@ class PagingFeature extends BaseFeature {
|
|
|
91
103
|
}
|
|
92
104
|
}
|
|
93
105
|
|
|
106
|
+
// Relay connections carry the cursor in pageInfo, at the path the model
|
|
107
|
+
// recorded for this op.
|
|
108
|
+
const point: any = ctx.point
|
|
109
|
+
const page = null == point ? undefined : (point.graphql || {}).page
|
|
110
|
+
|
|
111
|
+
if (null != page && body && 'object' === typeof body) {
|
|
112
|
+
const struct: any = ctx.utility.struct
|
|
113
|
+
|
|
114
|
+
// `connpath` locates the connection object inside the response
|
|
115
|
+
// envelope (data.<field>); the cursor/more paths are relative to it.
|
|
116
|
+
const conn = null == page.connpath || '' === page.connpath ? body :
|
|
117
|
+
(struct.getpath(body, page.connpath) || body)
|
|
118
|
+
|
|
119
|
+
const cursor = struct.getpath(conn, page.cursor)
|
|
120
|
+
const more = struct.getpath(conn, page.more)
|
|
121
|
+
|
|
122
|
+
if (null != cursor) { paging.cursor = cursor }
|
|
123
|
+
if ('boolean' === typeof more) {
|
|
124
|
+
paging.hasMore = more
|
|
125
|
+
explicitMore = true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
// Body-level cursors.
|
|
95
130
|
if (body && 'object' === typeof body) {
|
|
96
131
|
if (null != body.next) { paging.next = paging.next || body.next }
|
|
97
132
|
if (null != body.cursor) { paging.cursor = body.cursor }
|
|
98
133
|
if (null != body.nextCursor) { paging.cursor = body.nextCursor }
|
|
99
|
-
if ('boolean' === typeof body.hasMore) {
|
|
134
|
+
if ('boolean' === typeof body.hasMore) {
|
|
135
|
+
paging.hasMore = body.hasMore
|
|
136
|
+
explicitMore = true
|
|
137
|
+
}
|
|
100
138
|
}
|
|
101
139
|
|
|
102
|
-
|
|
103
|
-
|
|
140
|
+
// Cursor presence only INFERS another page. When the server stated the
|
|
141
|
+
// answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
|
|
142
|
+
// that wins: a final page normally carries both an end cursor and
|
|
143
|
+
// hasNextPage false, and inferring from the cursor there would send the
|
|
144
|
+
// caller back for a page that does not exist, forever.
|
|
145
|
+
if (!explicitMore) {
|
|
146
|
+
paging.hasMore = paging.hasMore ||
|
|
147
|
+
null != paging.next || null != paging.cursor || null != paging.nextPage
|
|
148
|
+
}
|
|
104
149
|
|
|
105
150
|
result.paging = paging
|
|
106
151
|
|
|
@@ -109,6 +154,40 @@ class PagingFeature extends BaseFeature {
|
|
|
109
154
|
}
|
|
110
155
|
|
|
111
156
|
|
|
157
|
+
|
|
158
|
+
// Relay pagination: the cursor is the `after` variable (or whatever the
|
|
159
|
+
// model recorded), and the page size the `first` variable.
|
|
160
|
+
_graphqlPreRequest(this: any, ctx: any, paging: any) {
|
|
161
|
+
const spec = ctx.spec
|
|
162
|
+
const point = ctx.point
|
|
163
|
+
|
|
164
|
+
const body = spec.body
|
|
165
|
+
if (null == body || 'object' !== typeof body) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const vars = (body.variables = body.variables || {})
|
|
170
|
+
|
|
171
|
+
const afterVar = this._options.afterVar || 'after'
|
|
172
|
+
const firstVar = this._options.firstVar || 'first'
|
|
173
|
+
|
|
174
|
+
// Only bind variables the operation actually declares, or the server
|
|
175
|
+
// rejects the document.
|
|
176
|
+
const declared: Record<string, boolean> = {}
|
|
177
|
+
for (const v of ((point.graphql && point.graphql.vars) || [])) {
|
|
178
|
+
declared[v.name] = true
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (null != paging.cursor && declared[afterVar]) {
|
|
182
|
+
vars[afterVar] = paging.cursor
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (null != this._options.limit && null == vars[firstVar] &&
|
|
186
|
+
declared[firstVar]) {
|
|
187
|
+
vars[firstVar] = this._options.limit
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
112
191
|
_isList(this: any, ctx: any): boolean {
|
|
113
192
|
const ops = this._options.ops || ['list']
|
|
114
193
|
return ops.indexOf(ctx.op && ctx.op.name) >= 0
|
|
@@ -64,8 +64,20 @@ class TestFeature extends BaseFeature {
|
|
|
64
64
|
if (null == data || 'string' !== typeof restf) {
|
|
65
65
|
return data
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// Rebuild whatever nesting the op's response transform unwraps, so
|
|
68
|
+
// the mock agrees with the model. Multi-segment on purpose: GraphQL
|
|
69
|
+
// ops unwrap `body.data.<field>` (and `body.data.<field>.<entity>`
|
|
70
|
+
// for mutation payloads), not just a single envelope property.
|
|
71
|
+
const m = restf.match(/^`body\.(.+)`$/)
|
|
72
|
+
if (null == m) {
|
|
73
|
+
return data
|
|
74
|
+
}
|
|
75
|
+
let out: any = data
|
|
76
|
+
const segs = m[1].split('.')
|
|
77
|
+
for (let i = segs.length - 1; 0 <= i; i--) {
|
|
78
|
+
out = { [segs[i]]: out }
|
|
79
|
+
}
|
|
80
|
+
return out
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
function respond(status: number, data?: any, res?: any) {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
|
|
2
|
+
import { Context } from '../types'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
|
|
6
|
+
// produces uses this file unchanged. The API-specific part — which
|
|
7
|
+
// operations exist and what each one's document is — is model data,
|
|
8
|
+
// computed once by apidef and emitted into Config.
|
|
9
|
+
//
|
|
10
|
+
// Two jobs:
|
|
11
|
+
//
|
|
12
|
+
// graphqlBody — build { query, variables } for a point, binding the
|
|
13
|
+
// op's arguments to the document's declared variables.
|
|
14
|
+
//
|
|
15
|
+
// graphqlErrors — lift a GraphQL failure into an SDK error. GraphQL
|
|
16
|
+
// reports failures as a top-level `errors` array under
|
|
17
|
+
// HTTP 200, so the status-driven path in resultBasic
|
|
18
|
+
// never sees them.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// Content type every GraphQL-over-HTTP request uses.
|
|
22
|
+
const GRAPHQL_CONTENT_TYPE = 'application/json'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// Map a GraphQL error to the same error codes the HTTP path produces, so a
|
|
26
|
+
// caller handles auth or rate limiting identically on both transports.
|
|
27
|
+
// Servers put the machine-readable code in `extensions.code`; Linear-style
|
|
28
|
+
// APIs use `extensions.type`.
|
|
29
|
+
function graphqlErrorCode(gqlerr: any): string {
|
|
30
|
+
const ext = (null == gqlerr ? undefined : gqlerr.extensions) || {}
|
|
31
|
+
const raw = String(ext.code || ext.type || '').toUpperCase()
|
|
32
|
+
|
|
33
|
+
if (raw.includes('AUTH') || raw.includes('FORBIDDEN') ||
|
|
34
|
+
raw.includes('UNAUTHENTICATED')) {
|
|
35
|
+
return 'request_auth'
|
|
36
|
+
}
|
|
37
|
+
if (raw.includes('RATELIMIT') || raw.includes('RATE_LIMIT') ||
|
|
38
|
+
raw.includes('TOO_MANY')) {
|
|
39
|
+
return 'request_ratelimit'
|
|
40
|
+
}
|
|
41
|
+
if (raw.includes('BAD_USER_INPUT') || raw.includes('VALIDATION') ||
|
|
42
|
+
raw.includes('INVALID')) {
|
|
43
|
+
return 'request_invalid'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return 'request_graphql'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// Build the request body for a GraphQL point.
|
|
51
|
+
//
|
|
52
|
+
// Variables come from the op's own arguments: a named variable binds to the
|
|
53
|
+
// like-named argument (`from`), and the input-object variable (empty `from`)
|
|
54
|
+
// takes the request data as a whole — which is what makes a generated
|
|
55
|
+
// create/update call look exactly like its REST equivalent.
|
|
56
|
+
function graphqlBody(ctx: Context): any {
|
|
57
|
+
const utility = ctx.utility
|
|
58
|
+
const struct = utility.struct
|
|
59
|
+
const getprop = struct.getprop
|
|
60
|
+
|
|
61
|
+
const point: any = ctx.point
|
|
62
|
+
const gql = null == point ? undefined : point.graphql
|
|
63
|
+
|
|
64
|
+
if (null == gql) {
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const op: any = ctx.op
|
|
69
|
+
const input = null == op ? 'match' : op.input
|
|
70
|
+
|
|
71
|
+
// reqmatch/reqdata hold the caller's arguments for this operation.
|
|
72
|
+
const reqsrc = getprop(ctx, 'req' + input) || {}
|
|
73
|
+
const datasrc = getprop(ctx, input) || {}
|
|
74
|
+
|
|
75
|
+
const variables: Record<string, any> = {}
|
|
76
|
+
|
|
77
|
+
for (const spec of (gql.vars || [])) {
|
|
78
|
+
if ('' === spec.from || null == spec.from) {
|
|
79
|
+
// The input object IS the request body. Strip the action selector,
|
|
80
|
+
// which is an SDK-side point discriminator, not an API field.
|
|
81
|
+
const body: Record<string, any> = {}
|
|
82
|
+
for (const key of Object.keys(reqsrc)) {
|
|
83
|
+
if ('$action' !== key) {
|
|
84
|
+
body[key] = reqsrc[key]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
variables[spec.name] = body
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const val = undefined !== getprop(reqsrc, spec.from) ?
|
|
91
|
+
getprop(reqsrc, spec.from) : getprop(datasrc, spec.from)
|
|
92
|
+
|
|
93
|
+
// Only send variables the caller actually supplied: sending an
|
|
94
|
+
// explicit null would clear a field on many APIs.
|
|
95
|
+
if (undefined !== val && null !== val) {
|
|
96
|
+
variables[spec.name] = val
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
query: gql.doc,
|
|
103
|
+
variables,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
// Inspect a decoded GraphQL response body and record a failure when the
|
|
109
|
+
// server reported one. Returns true when an error was recorded.
|
|
110
|
+
//
|
|
111
|
+
// Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
112
|
+
// surface has no partial-success concept, and silently returning half an
|
|
113
|
+
// object would be worse than failing. The raw envelope stays available on
|
|
114
|
+
// the result for callers that need it.
|
|
115
|
+
function graphqlErrors(ctx: Context): boolean {
|
|
116
|
+
const result: any = ctx.result
|
|
117
|
+
const point: any = ctx.point
|
|
118
|
+
|
|
119
|
+
if (null == result || null == point || 'graphql' !== point.kind) {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const body: any = result.body
|
|
124
|
+
const errors = null == body ? undefined : body.errors
|
|
125
|
+
|
|
126
|
+
if (null == errors || !Array.isArray(errors) || 0 === errors.length) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const first = errors[0]
|
|
131
|
+
const msg = (null == first ? undefined : first.message) || 'graphql error'
|
|
132
|
+
const code = graphqlErrorCode(first)
|
|
133
|
+
|
|
134
|
+
const err: any = ctx.error(code,
|
|
135
|
+
'graphql: ' + msg + (1 < errors.length ?
|
|
136
|
+
' (+' + (errors.length - 1) + ' more)' : ''))
|
|
137
|
+
|
|
138
|
+
// Keep the full error list reachable for diagnostics.
|
|
139
|
+
err.graphql = errors
|
|
140
|
+
result.err = err
|
|
141
|
+
result.ok = false
|
|
142
|
+
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
export {
|
|
148
|
+
graphqlBody,
|
|
149
|
+
graphqlErrors,
|
|
150
|
+
graphqlErrorCode,
|
|
151
|
+
GRAPHQL_CONTENT_TYPE,
|
|
152
|
+
}
|
|
@@ -13,6 +13,7 @@ async function makeResponse(ctx: Context): Promise<Response | Error> {
|
|
|
13
13
|
const resultHeaders = utility.resultHeaders
|
|
14
14
|
const resultBody = utility.resultBody
|
|
15
15
|
const transformResponse = utility.transformResponse
|
|
16
|
+
const graphqlErrors = utility.graphqlErrors
|
|
16
17
|
|
|
17
18
|
const spec = ctx.spec
|
|
18
19
|
const result = ctx.result
|
|
@@ -38,6 +39,12 @@ async function makeResponse(ctx: Context): Promise<Response | Error> {
|
|
|
38
39
|
resultBasic(ctx)
|
|
39
40
|
resultHeaders(ctx)
|
|
40
41
|
await resultBody(ctx)
|
|
42
|
+
|
|
43
|
+
// GraphQL reports failures as a top-level `errors` array under HTTP 200,
|
|
44
|
+
// so resultBasic's status check never sees them. Lift them here, before
|
|
45
|
+
// the response transform tries to unwrap data that is not there.
|
|
46
|
+
graphqlErrors(ctx)
|
|
47
|
+
|
|
41
48
|
transformResponse(ctx)
|
|
42
49
|
|
|
43
50
|
if (null == result.err) {
|
|
@@ -39,8 +39,25 @@ function makeSpec(ctx: Context): Spec | Error {
|
|
|
39
39
|
ctx.spec.params = prepareParams(ctx)
|
|
40
40
|
ctx.spec.query = prepareQuery(ctx)
|
|
41
41
|
ctx.spec.headers = prepareHeaders(ctx)
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
|
|
43
|
+
if ('graphql' === (point as any).kind) {
|
|
44
|
+
// GraphQL addresses one endpoint: no path parts, no query string, and
|
|
45
|
+
// the body carries the operation. prepareBody is skipped deliberately —
|
|
46
|
+
// it only emits a body for data-input ops (create/update), whereas every
|
|
47
|
+
// GraphQL op posts one, including load/list/remove.
|
|
48
|
+
ctx.spec.body = utility.graphqlBody(ctx)
|
|
49
|
+
ctx.spec.path = ''
|
|
50
|
+
// prepareQuery already copied the op's match arguments into the query
|
|
51
|
+
// string. Those same values are bound as operation variables, so leaving
|
|
52
|
+
// them would send /graphql?id=i1 — duplicating the argument, leaking it
|
|
53
|
+
// into the URL, and failing servers that reject unknown query params.
|
|
54
|
+
ctx.spec.query = {}
|
|
55
|
+
ctx.spec.headers['content-type'] = utility.GRAPHQL_CONTENT_TYPE
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
ctx.spec.body = prepareBody(ctx)
|
|
59
|
+
ctx.spec.path = preparePath(ctx)
|
|
60
|
+
}
|
|
44
61
|
|
|
45
62
|
if (ctx.ctrl.explain) {
|
|
46
63
|
ctx.ctrl.explain.spec = ctx.spec
|
|
@@ -15,6 +15,7 @@ import { makeResponse } from './MakeResponseUtility'
|
|
|
15
15
|
import { makeResult } from './MakeResultUtility'
|
|
16
16
|
import { makePoint } from './MakePointUtility'
|
|
17
17
|
import { makeSpec } from './MakeSpecUtility'
|
|
18
|
+
import { graphqlBody, graphqlErrors, GRAPHQL_CONTENT_TYPE } from './GraphqlUtility'
|
|
18
19
|
import { makeUrl } from './MakeUrlUtility'
|
|
19
20
|
import { param } from './ParamUtility'
|
|
20
21
|
import { prepareAuth } from './PrepareAuthUtility'
|
|
@@ -50,6 +51,9 @@ class Utility {
|
|
|
50
51
|
makeResult = makeResult
|
|
51
52
|
makePoint = makePoint
|
|
52
53
|
makeSpec = makeSpec
|
|
54
|
+
graphqlBody = graphqlBody
|
|
55
|
+
graphqlErrors = graphqlErrors
|
|
56
|
+
GRAPHQL_CONTENT_TYPE = GRAPHQL_CONTENT_TYPE
|
|
53
57
|
makeUrl = makeUrl
|
|
54
58
|
param = param
|
|
55
59
|
prepareAuth = prepareAuth
|
|
@@ -350,7 +350,7 @@ pub fn make_options_util(ctx: *Context) Value {
|
|
|
350
350
|
.{ "headers", h.jo(&.{.{ "`$CHILD`", h.vstr("`$STRING`") }}) },
|
|
351
351
|
.{ "allow", h.jo(&.{
|
|
352
352
|
.{ "method", h.vstr("GET,PUT,POST,PATCH,DELETE,OPTIONS") },
|
|
353
|
-
.{ "op", h.vstr("create,update,load,list,remove,command,direct") },
|
|
353
|
+
.{ "op", h.vstr("create,update,load,list,remove,command,direct,graphql") },
|
|
354
354
|
}) },
|
|
355
355
|
.{ "entity", h.jo(&.{.{ "`$CHILD`", h.jo(&.{
|
|
356
356
|
.{ "`$OPEN`", h.vbool(true) },
|
|
@@ -565,8 +565,28 @@ pub fn make_spec_util(ctx: *Context) E!*Spec {
|
|
|
565
565
|
spec.params = prepare_params_util(ctx);
|
|
566
566
|
spec.query = prepare_query_util(ctx);
|
|
567
567
|
spec.headers = prepare_headers_util(ctx);
|
|
568
|
-
|
|
569
|
-
|
|
568
|
+
|
|
569
|
+
const kind: []const u8 = switch (h.getp(point, "kind")) {
|
|
570
|
+
.string => |s| s,
|
|
571
|
+
else => "",
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
if (std.mem.eql(u8, kind, "graphql")) {
|
|
575
|
+
// GraphQL addresses one endpoint: no path parts, no query string,
|
|
576
|
+
// and the body carries the operation. prepare_body is skipped
|
|
577
|
+
// deliberately — it only emits a body for data-input ops, whereas
|
|
578
|
+
// every GraphQL op posts one, including load/list/remove.
|
|
579
|
+
spec.body = graphql_body_util(ctx);
|
|
580
|
+
spec.path = "";
|
|
581
|
+
// prepare_query already copied the op's match arguments into the
|
|
582
|
+
// query string. Those same values are bound as operation variables,
|
|
583
|
+
// so leaving them would send /graphql?id=i1.
|
|
584
|
+
spec.query = h.omap();
|
|
585
|
+
h.setp(spec.headers, "content-type", h.vstr(GRAPHQL_CONTENT_TYPE));
|
|
586
|
+
} else {
|
|
587
|
+
spec.body = prepare_body_util(ctx);
|
|
588
|
+
spec.path = prepare_path_util(ctx);
|
|
589
|
+
}
|
|
570
590
|
|
|
571
591
|
const c = ctx.ctrl;
|
|
572
592
|
if (c.has_explain()) h.setp(c.explain, "spec", spec.to_value());
|
|
@@ -721,6 +741,12 @@ pub fn make_response_util(ctx: *Context) E!*Response {
|
|
|
721
741
|
_ = result_basic_util(ctx);
|
|
722
742
|
_ = result_headers_util(ctx);
|
|
723
743
|
_ = result_body_util(ctx);
|
|
744
|
+
|
|
745
|
+
// GraphQL reports failures as a top-level `errors` array under HTTP 200,
|
|
746
|
+
// so result_basic's status check never sees them. Lift them here, before
|
|
747
|
+
// the response transform tries to unwrap data that is not there.
|
|
748
|
+
_ = graphql_errors_util(ctx);
|
|
749
|
+
|
|
724
750
|
_ = transform_response_util(ctx);
|
|
725
751
|
|
|
726
752
|
if (result.err == null) result.ok = true;
|
|
@@ -936,6 +962,175 @@ pub fn prepare_query_util(ctx: *Context) Value {
|
|
|
936
962
|
return out;
|
|
937
963
|
}
|
|
938
964
|
|
|
965
|
+
// ============================================================================
|
|
966
|
+
// graphql (transport)
|
|
967
|
+
//
|
|
968
|
+
// GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
|
|
969
|
+
// produces uses this code unchanged. The API-specific part — which
|
|
970
|
+
// operations exist and what each one's document is — is model data,
|
|
971
|
+
// computed once by apidef and emitted into Config.
|
|
972
|
+
//
|
|
973
|
+
// Two jobs:
|
|
974
|
+
//
|
|
975
|
+
// graphql_body — build { query, variables } for a point, binding the
|
|
976
|
+
// op's arguments to the document's declared variables.
|
|
977
|
+
//
|
|
978
|
+
// graphql_errors — lift a GraphQL failure into an SDK error. GraphQL
|
|
979
|
+
// reports failures as a top-level `errors` array under
|
|
980
|
+
// HTTP 200, so the status-driven path in result_basic
|
|
981
|
+
// never sees them.
|
|
982
|
+
// ============================================================================
|
|
983
|
+
|
|
984
|
+
// Content type every GraphQL-over-HTTP request uses.
|
|
985
|
+
pub const GRAPHQL_CONTENT_TYPE = "application/json";
|
|
986
|
+
|
|
987
|
+
// Map a GraphQL error to the same error codes the HTTP path produces, so a
|
|
988
|
+
// caller handles auth or rate limiting identically on both transports.
|
|
989
|
+
// Servers put the machine-readable code in `extensions.code`; Linear-style
|
|
990
|
+
// APIs use `extensions.type`.
|
|
991
|
+
pub fn graphql_error_code(gqlerr: Value) []const u8 {
|
|
992
|
+
const ext = h.getp(gqlerr, "extensions");
|
|
993
|
+
|
|
994
|
+
var code: []const u8 = switch (h.getp(ext, "code")) {
|
|
995
|
+
.string => |s| s,
|
|
996
|
+
else => "",
|
|
997
|
+
};
|
|
998
|
+
if (code.len == 0) {
|
|
999
|
+
code = switch (h.getp(ext, "type")) {
|
|
1000
|
+
.string => |s| s,
|
|
1001
|
+
else => "",
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const raw: []const u8 = std.ascii.allocUpperString(h.A(), code) catch "";
|
|
1006
|
+
|
|
1007
|
+
if (std.mem.indexOf(u8, raw, "AUTH") != null or
|
|
1008
|
+
std.mem.indexOf(u8, raw, "FORBIDDEN") != null or
|
|
1009
|
+
std.mem.indexOf(u8, raw, "UNAUTHENTICATED") != null)
|
|
1010
|
+
{
|
|
1011
|
+
return "request_auth";
|
|
1012
|
+
}
|
|
1013
|
+
if (std.mem.indexOf(u8, raw, "RATELIMIT") != null or
|
|
1014
|
+
std.mem.indexOf(u8, raw, "RATE_LIMIT") != null or
|
|
1015
|
+
std.mem.indexOf(u8, raw, "TOO_MANY") != null)
|
|
1016
|
+
{
|
|
1017
|
+
return "request_ratelimit";
|
|
1018
|
+
}
|
|
1019
|
+
if (std.mem.indexOf(u8, raw, "BAD_USER_INPUT") != null or
|
|
1020
|
+
std.mem.indexOf(u8, raw, "VALIDATION") != null or
|
|
1021
|
+
std.mem.indexOf(u8, raw, "INVALID") != null)
|
|
1022
|
+
{
|
|
1023
|
+
return "request_invalid";
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
return "request_graphql";
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Build the request body for a GraphQL point.
|
|
1030
|
+
//
|
|
1031
|
+
// Variables come from the op's own arguments: a named variable binds to the
|
|
1032
|
+
// like-named argument (`from`), and the input-object variable (empty `from`)
|
|
1033
|
+
// takes the request data as a whole — which is what makes a generated
|
|
1034
|
+
// create/update call look exactly like its REST equivalent.
|
|
1035
|
+
pub fn graphql_body_util(ctx: *Context) Value {
|
|
1036
|
+
const gql = h.getp(ctx.point, "graphql");
|
|
1037
|
+
if (gql != .object) return h.vnull();
|
|
1038
|
+
|
|
1039
|
+
// reqmatch/reqdata hold the caller's arguments for this operation; which
|
|
1040
|
+
// one depends on whether the op takes match or data input.
|
|
1041
|
+
var reqsrc = ctx.reqmatch;
|
|
1042
|
+
var datasrc = ctx.mtch;
|
|
1043
|
+
if (std.mem.eql(u8, ctx.op.input, "data")) {
|
|
1044
|
+
reqsrc = ctx.reqdata;
|
|
1045
|
+
datasrc = ctx.data;
|
|
1046
|
+
}
|
|
1047
|
+
if (reqsrc != .object) reqsrc = h.omap();
|
|
1048
|
+
if (datasrc != .object) datasrc = h.omap();
|
|
1049
|
+
|
|
1050
|
+
const variables = h.omap();
|
|
1051
|
+
|
|
1052
|
+
const varlist = h.getp(gql, "vars");
|
|
1053
|
+
if (varlist == .array) {
|
|
1054
|
+
for (varlist.array.data.items) |spec| {
|
|
1055
|
+
if (spec != .object) continue;
|
|
1056
|
+
|
|
1057
|
+
const name: []const u8 = switch (h.getp(spec, "name")) {
|
|
1058
|
+
.string => |s| s,
|
|
1059
|
+
else => "",
|
|
1060
|
+
};
|
|
1061
|
+
if (name.len == 0) continue;
|
|
1062
|
+
|
|
1063
|
+
const from: []const u8 = switch (h.getp(spec, "from")) {
|
|
1064
|
+
.string => |s| s,
|
|
1065
|
+
else => "",
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
if (from.len == 0) {
|
|
1069
|
+
// The input object IS the request body. Strip the action
|
|
1070
|
+
// selector, which is an SDK-side point discriminator, not
|
|
1071
|
+
// an API field.
|
|
1072
|
+
const body = h.omap();
|
|
1073
|
+
var it = reqsrc.object.iterator();
|
|
1074
|
+
while (it.next()) |kv| {
|
|
1075
|
+
const key = kv.key_ptr.*;
|
|
1076
|
+
if (!std.mem.eql(u8, key, "$action")) {
|
|
1077
|
+
h.setp(body, key, kv.value_ptr.*);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
h.setp(variables, name, body);
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// Only send variables the caller actually supplied: sending an
|
|
1085
|
+
// explicit null would clear a field on many APIs.
|
|
1086
|
+
var val = h.getp(reqsrc, from);
|
|
1087
|
+
if (h.is_noval(val) or h.is_null(val)) val = h.getp(datasrc, from);
|
|
1088
|
+
if (!h.is_noval(val) and !h.is_null(val)) {
|
|
1089
|
+
h.setp(variables, name, val);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const out = h.omap();
|
|
1095
|
+
h.setp(out, "query", h.getp(gql, "doc"));
|
|
1096
|
+
h.setp(out, "variables", variables);
|
|
1097
|
+
return out;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Inspect a decoded GraphQL response body and record a failure when the
|
|
1101
|
+
// server reported one. Returns true when an error was recorded.
|
|
1102
|
+
//
|
|
1103
|
+
// Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
1104
|
+
// surface has no partial-success concept, and silently returning half an
|
|
1105
|
+
// object would be worse than failing.
|
|
1106
|
+
pub fn graphql_errors_util(ctx: *Context) bool {
|
|
1107
|
+
const result = ctx.result orelse return false;
|
|
1108
|
+
|
|
1109
|
+
const kind: []const u8 = switch (h.getp(ctx.point, "kind")) {
|
|
1110
|
+
.string => |s| s,
|
|
1111
|
+
else => "",
|
|
1112
|
+
};
|
|
1113
|
+
if (!std.mem.eql(u8, kind, "graphql")) return false;
|
|
1114
|
+
|
|
1115
|
+
const errors = h.getp(result.body, "errors");
|
|
1116
|
+
if (errors != .array or errors.array.data.items.len == 0) return false;
|
|
1117
|
+
|
|
1118
|
+
const count = errors.array.data.items.len;
|
|
1119
|
+
const first = errors.array.data.items[0];
|
|
1120
|
+
|
|
1121
|
+
var msg: []const u8 = switch (h.getp(first, "message")) {
|
|
1122
|
+
.string => |s| s,
|
|
1123
|
+
else => "",
|
|
1124
|
+
};
|
|
1125
|
+
if (msg.len == 0) msg = "graphql error";
|
|
1126
|
+
if (1 < count) msg = fmt("{s} (+{d} more)", .{ msg, count - 1 });
|
|
1127
|
+
|
|
1128
|
+
result.err = ctx.make_error(graphql_error_code(first), fmt("graphql: {s}", .{msg}));
|
|
1129
|
+
result.ok = false;
|
|
1130
|
+
|
|
1131
|
+
return true;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
939
1134
|
const HEADER_AUTH = "authorization";
|
|
940
1135
|
const OPTION_APIKEY = "apikey";
|
|
941
1136
|
const NOT_FOUND = "__NOTFOUND__";
|