@voxgig/sdkgen 1.5.0 → 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 +7 -6
- 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
|
@@ -480,6 +480,29 @@
|
|
|
480
480
|
(or (try (Long/parseLong s) (catch Exception _ nil))
|
|
481
481
|
(try (Double/parseDouble s) (catch Exception _ nil))))))
|
|
482
482
|
|
|
483
|
+
;; Relay pagination: the cursor is the `after` variable (or whatever the model
|
|
484
|
+
;; named it), and the page size is `first`.
|
|
485
|
+
(defn- graphql-pre-request [fa spec point paging]
|
|
486
|
+
(let [body (core/oget spec :body)]
|
|
487
|
+
(when (vs/ismap body)
|
|
488
|
+
(let [variables (let [v (mget body "variables")]
|
|
489
|
+
(if (vs/ismap v)
|
|
490
|
+
v
|
|
491
|
+
(let [nv (vs/jm)] (.put ^java.util.Map body "variables" nv) nv)))
|
|
492
|
+
after-var (opt fa "afterVar" "after")
|
|
493
|
+
first-var (opt fa "firstVar" "first")
|
|
494
|
+
;; Only bind variables the operation actually declares, or the
|
|
495
|
+
;; server rejects the document.
|
|
496
|
+
declared (let [vl (vs/getpath point "graphql.vars")]
|
|
497
|
+
(if (vs/islist vl)
|
|
498
|
+
(into #{} (keep #(vs/getprop % "name") (vec vl)))
|
|
499
|
+
#{}))]
|
|
500
|
+
(when (and (some? (mget paging "cursor")) (declared after-var))
|
|
501
|
+
(.put ^java.util.Map variables after-var (mget paging "cursor")))
|
|
502
|
+
(when (and (some? (opt fa "limit")) (nil? (mget variables first-var))
|
|
503
|
+
(declared first-var))
|
|
504
|
+
(.put ^java.util.Map variables first-var (opt fa "limit")))))))
|
|
505
|
+
|
|
483
506
|
(defn paging-feature []
|
|
484
507
|
(let [fa (new-feature "paging" false "0.0.1")
|
|
485
508
|
list? (fn [ctx] (let [ops (or (opt fa "ops") (vs/jt "list"))
|
|
@@ -501,12 +524,19 @@
|
|
|
501
524
|
cursor-param (opt fa "cursorParam" "cursor")
|
|
502
525
|
ctrl (core/oget ctx :ctrl)
|
|
503
526
|
paging (let [p (when ctrl (core/oget ctrl :paging))] (if (vs/ismap p) p (vs/jm)))]
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
(
|
|
509
|
-
(
|
|
527
|
+
;; GraphQL paginates through operation VARIABLES, not
|
|
528
|
+
;; the query string. This hook runs after make-spec, so
|
|
529
|
+
;; spec.body already holds the { query, variables }
|
|
530
|
+
;; envelope, and before make-fetch-def serialises it.
|
|
531
|
+
(if (= "graphql" (vs/getprop (core/oget ctx :point) "kind"))
|
|
532
|
+
(graphql-pre-request fa spec (core/oget ctx :point) paging)
|
|
533
|
+
(do
|
|
534
|
+
(cond
|
|
535
|
+
(some? (mget paging "cursor")) (.put ^java.util.Map q cursor-param (mget paging "cursor"))
|
|
536
|
+
(nil? (mget q page-param))
|
|
537
|
+
(.put ^java.util.Map q page-param (if (nil? (mget paging "page")) (opt fa "startPage" 1) (mget paging "page"))))
|
|
538
|
+
(when (and (some? (opt fa "limit")) (nil? (mget q limit-param)))
|
|
539
|
+
(.put ^java.util.Map q limit-param (opt fa "limit"))))))))))
|
|
510
540
|
"PreResult"
|
|
511
541
|
(fn [ctx]
|
|
512
542
|
(when (and (active? fa) (list? ctx))
|
|
@@ -522,14 +552,50 @@
|
|
|
522
552
|
(when (some? link)
|
|
523
553
|
(when-let [m (re-find #"(?i)<([^>]+)>\s*;\s*rel=\"?next\"?" (str link))]
|
|
524
554
|
(.put ^java.util.Map paging "next" (nth m 1))))
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
555
|
+
;; Set when the response states hasMore outright, rather
|
|
556
|
+
;; than leaving it to be inferred from a cursor.
|
|
557
|
+
(let [explicit-more (atom false)
|
|
558
|
+
point (core/oget ctx :point)
|
|
559
|
+
page (vs/getpath point "graphql.page")]
|
|
560
|
+
;; Relay connections carry the cursor in pageInfo, at
|
|
561
|
+
;; the path the model recorded for this op.
|
|
562
|
+
(when (and (vs/ismap page) (vs/ismap body))
|
|
563
|
+
;; `connpath` locates the connection object inside the
|
|
564
|
+
;; response envelope (data.<field>); the cursor/more
|
|
565
|
+
;; paths are relative to it.
|
|
566
|
+
(let [connpath (mget page "connpath")
|
|
567
|
+
conn (if (and (string? connpath) (seq connpath))
|
|
568
|
+
(or (vs/getpath body connpath) body)
|
|
569
|
+
body)
|
|
570
|
+
cursorpath (mget page "cursor")
|
|
571
|
+
morepath (mget page "more")]
|
|
572
|
+
(when (and (string? cursorpath) (seq cursorpath))
|
|
573
|
+
(when-let [c (vs/getpath conn cursorpath)]
|
|
574
|
+
(.put ^java.util.Map paging "cursor" c)))
|
|
575
|
+
(when (and (string? morepath) (seq morepath))
|
|
576
|
+
(let [more (vs/getpath conn morepath)]
|
|
577
|
+
(when (or (= more true) (= more false))
|
|
578
|
+
(.put ^java.util.Map paging "hasMore" more)
|
|
579
|
+
(reset! explicit-more true))))))
|
|
580
|
+
(when (vs/ismap body)
|
|
581
|
+
(when (some? (mget body "next")) (when (nil? (mget paging "next")) (.put ^java.util.Map paging "next" (mget body "next"))))
|
|
582
|
+
(when (some? (mget body "cursor")) (.put ^java.util.Map paging "cursor" (mget body "cursor")))
|
|
583
|
+
(when (some? (mget body "nextCursor")) (.put ^java.util.Map paging "cursor" (mget body "nextCursor")))
|
|
584
|
+
(let [hm (mget body "hasMore")]
|
|
585
|
+
(when (or (= hm true) (= hm false))
|
|
586
|
+
(.put ^java.util.Map paging "hasMore" hm)
|
|
587
|
+
(reset! explicit-more true))))
|
|
588
|
+
;; Cursor presence only INFERS another page. When the
|
|
589
|
+
;; server stated the answer outright — relay's
|
|
590
|
+
;; `hasNextPage: false`, or a body `hasMore` — that
|
|
591
|
+
;; wins: a final page normally carries both an end
|
|
592
|
+
;; cursor and hasNextPage false, and inferring from the
|
|
593
|
+
;; cursor there would send the caller back for a page
|
|
594
|
+
;; that does not exist, forever.
|
|
595
|
+
(when-not @explicit-more
|
|
596
|
+
(.put ^java.util.Map paging "hasMore"
|
|
597
|
+
(boolean (or (mget paging "hasMore") (some? (mget paging "next"))
|
|
598
|
+
(some? (mget paging "cursor")) (some? (mget paging "nextPage")))))))
|
|
533
599
|
(core/oset! result :paging paging)
|
|
534
600
|
(track-set! fa "_paging" (vs/jm "last" paging)))))))
|
|
535
601
|
)
|
|
@@ -560,6 +560,15 @@ public:
|
|
|
560
560
|
|
|
561
561
|
Value prepare(const Value& fetchargs);
|
|
562
562
|
Value direct(const Value& fetchargs);
|
|
563
|
+
Value graphql(const std::string& query, const Value& variables = Value::undef(),
|
|
564
|
+
const Value& ctrl = Value::undef());
|
|
565
|
+
|
|
566
|
+
private:
|
|
567
|
+
bool opAllowed(const std::string& op);
|
|
568
|
+
Value opDenied(const std::string& op);
|
|
569
|
+
Value rawRequest(const Value& fetchargs);
|
|
570
|
+
|
|
571
|
+
public:
|
|
563
572
|
|
|
564
573
|
static Value testOptions(const Value& testopts, const Value& sdkopts);
|
|
565
574
|
};
|
|
@@ -868,7 +877,81 @@ inline Value SdkClient::prepare(const Value& fetchargs_) {
|
|
|
868
877
|
return u->makeFetchDef(ctx);
|
|
869
878
|
}
|
|
870
879
|
|
|
880
|
+
// Is this raw-access op permitted by the SDK's allow.op option?
|
|
881
|
+
inline bool SdkClient::opAllowed(const std::string& op) {
|
|
882
|
+
Value allow = Struct::getpath(options, {"allow", "op"});
|
|
883
|
+
if (!allow.is_string()) return false;
|
|
884
|
+
return std::string::npos != allow.as_string().find(op);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
inline Value SdkClient::opDenied(const std::string& op) {
|
|
888
|
+
Value allow = Struct::getpath(options, {"allow", "op"});
|
|
889
|
+
std::string a = allow.is_string() ? allow.as_string() : "";
|
|
890
|
+
Value out = vmap();
|
|
891
|
+
map_put(out, "ok", Value(false));
|
|
892
|
+
map_put(out, "err", vmap({{"message", Value(
|
|
893
|
+
"ProjectNameSDK: " + op + ": operation not allowed by"
|
|
894
|
+
" SDK option allow.op value: \"" + a + "\"")}}));
|
|
895
|
+
return out;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Raw endpoint access is operator-controllable, like every entity op.
|
|
899
|
+
// Blocking it means denying BOTH the 'direct' and 'graphql' tokens, since
|
|
900
|
+
// either one reaches the same endpoint.
|
|
871
901
|
inline Value SdkClient::direct(const Value& fetchargs_) {
|
|
902
|
+
if (!opAllowed("direct")) return opDenied("direct");
|
|
903
|
+
return rawRequest(fetchargs_);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Raw GraphQL access: the pressure valve that makes the generated surface's
|
|
907
|
+
// deliberate omissions (per-call selection sets, typed filter builders,
|
|
908
|
+
// batching, subscriptions) livable — the whole schema stays reachable.
|
|
909
|
+
//
|
|
910
|
+
// Thin wrapper over the same prepare/fetch path direct uses, with the one
|
|
911
|
+
// thing raw direct cannot do for GraphQL: a GraphQL failure rides HTTP 200 as
|
|
912
|
+
// a top-level `errors` array, so status alone would report a failed query as
|
|
913
|
+
// ok.
|
|
914
|
+
//
|
|
915
|
+
// NOTE: like direct, this bypasses the feature pipeline — no retry, ratelimit
|
|
916
|
+
// or paging features apply.
|
|
917
|
+
inline Value SdkClient::graphql(const std::string& query, const Value& variables,
|
|
918
|
+
const Value& ctrl) {
|
|
919
|
+
if (!opAllowed("graphql")) return opDenied("graphql");
|
|
920
|
+
|
|
921
|
+
Value res = rawRequest(vmap({
|
|
922
|
+
{"method", Value(std::string("POST"))},
|
|
923
|
+
{"headers", vmap({{"content-type", Value(std::string("application/json"))}})},
|
|
924
|
+
{"body", vmap({
|
|
925
|
+
{"query", Value(query)},
|
|
926
|
+
{"variables", variables.is_map() ? variables : vmap()}})},
|
|
927
|
+
{"ctrl", ctrl.is_map() ? ctrl : vmap()}}));
|
|
928
|
+
|
|
929
|
+
// Errors are read BEFORE any status check: a GraphQL parse or validation
|
|
930
|
+
// failure comes back as HTTP 400 carrying the standard { errors: [...] }
|
|
931
|
+
// body, and the raw path represents a non-2xx as ok:false with no err — so
|
|
932
|
+
// returning early on status would discard the server's own diagnostics,
|
|
933
|
+
// which are the only useful part of that response.
|
|
934
|
+
Value errors = getp(getp(res, "data"), "errors");
|
|
935
|
+
|
|
936
|
+
if (errors.is_list() && !errors.as_list()->empty()) {
|
|
937
|
+
Value first = (*errors.as_list())[0];
|
|
938
|
+
Value m = getp(first, "message");
|
|
939
|
+
std::string msg = m.is_string() && !m.as_string().empty()
|
|
940
|
+
? m.as_string() : "graphql error";
|
|
941
|
+
map_put(res, "ok", Value(false));
|
|
942
|
+
map_put(res, "err", vmap({{"message",
|
|
943
|
+
Value("ProjectNameSDK: graphql: " + msg)}}));
|
|
944
|
+
map_put(res, "graphql", errors);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
return res;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Ungated request path shared by direct and graphql, each of which checks its
|
|
951
|
+
// own allow.op token first. Private, rather than a flag on fetchargs: a
|
|
952
|
+
// caller-supplied marker would let anyone opt straight back out of the gate
|
|
953
|
+
// by passing it.
|
|
954
|
+
inline Value SdkClient::rawRequest(const Value& fetchargs_) {
|
|
872
955
|
UtilityPtr u = utility;
|
|
873
956
|
Value fetchargs = fetchargs_.is_map() ? fetchargs_ : vmap();
|
|
874
957
|
|
|
@@ -49,6 +49,14 @@ public:
|
|
|
49
49
|
Value paging = ctx->ctrl ? ctx->ctrl->paging : Value::undef();
|
|
50
50
|
if (!paging.is_map()) paging = vmap();
|
|
51
51
|
|
|
52
|
+
// GraphQL paginates through operation VARIABLES, not the query string.
|
|
53
|
+
// This hook runs after makeSpec, so spec->body already holds the
|
|
54
|
+
// { query, variables } envelope, and before makeFetchDef serialises it.
|
|
55
|
+
if ("graphql" == as_str(getp(ctx->point, "kind"))) {
|
|
56
|
+
graphqlPreRequest(ctx, paging);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
Value cursor = getp(paging, "cursor");
|
|
53
61
|
if (!is_nullish(cursor)) {
|
|
54
62
|
map_put(spec->query, cursorParam, cursor);
|
|
@@ -66,6 +74,44 @@ public:
|
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
|
|
77
|
+
// Relay pagination: the cursor is the `after` variable (or whatever the
|
|
78
|
+
// model named it), and the page size is `first`.
|
|
79
|
+
void graphqlPreRequest(CtxPtr ctx, const Value& paging) {
|
|
80
|
+
Value body = ctx->spec->body;
|
|
81
|
+
if (!body.is_map()) return;
|
|
82
|
+
|
|
83
|
+
Value variables = getp(body, "variables");
|
|
84
|
+
if (!variables.is_map()) {
|
|
85
|
+
variables = vmap();
|
|
86
|
+
map_put(body, "variables", variables);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
std::string afterVar = fopt::foptStr(options, "afterVar", "after");
|
|
90
|
+
std::string firstVar = fopt::foptStr(options, "firstVar", "first");
|
|
91
|
+
|
|
92
|
+
// Only bind variables the operation actually declares, or the server
|
|
93
|
+
// rejects the document.
|
|
94
|
+
bool afterDeclared = false, firstDeclared = false;
|
|
95
|
+
Value varlist = Struct::getpath(ctx->point, {"graphql", "vars"});
|
|
96
|
+
if (varlist.is_list()) {
|
|
97
|
+
for (const auto& v : *varlist.as_list()) {
|
|
98
|
+
std::string n = as_str(getp(v, "name"));
|
|
99
|
+
if (n == afterVar) afterDeclared = true;
|
|
100
|
+
if (n == firstVar) firstDeclared = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Value cursor = getp(paging, "cursor");
|
|
105
|
+
if (afterDeclared && !is_nullish(cursor)) {
|
|
106
|
+
map_put(variables, afterVar, cursor);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (firstDeclared && !is_nullish(getp(options, "limit")) &&
|
|
110
|
+
is_nullish(getp(variables, firstVar))) {
|
|
111
|
+
map_put(variables, firstVar, Value(fopt::foptInt(options, "limit", 0)));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
69
115
|
void preResult(CtxPtr ctx) override {
|
|
70
116
|
if (!active || !isList(ctx)) return;
|
|
71
117
|
ResultPtr result = ctx->result;
|
|
@@ -92,6 +138,39 @@ public:
|
|
|
92
138
|
}
|
|
93
139
|
}
|
|
94
140
|
|
|
141
|
+
// Set when the response states hasMore outright, rather than leaving it
|
|
142
|
+
// to be inferred from the presence of a cursor.
|
|
143
|
+
bool explicitMore = false;
|
|
144
|
+
|
|
145
|
+
// Relay connections carry the cursor in pageInfo, at the path the model
|
|
146
|
+
// recorded for this op.
|
|
147
|
+
Value page = Struct::getpath(ctx->point, {"graphql", "page"});
|
|
148
|
+
if (page.is_map() && body.is_map()) {
|
|
149
|
+
// `connpath` locates the connection object inside the response
|
|
150
|
+
// envelope (data.<field>); the cursor/more paths are relative to it.
|
|
151
|
+
Value conn = body;
|
|
152
|
+
std::string connpath = as_str(getp(page, "connpath"));
|
|
153
|
+
if (!connpath.empty()) {
|
|
154
|
+
Value sub = Struct::getpath(body, splitDots(connpath));
|
|
155
|
+
if (!is_nullish(sub)) conn = sub;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
std::string cursorpath = as_str(getp(page, "cursor"));
|
|
159
|
+
if (!cursorpath.empty()) {
|
|
160
|
+
Value c = Struct::getpath(conn, splitDots(cursorpath));
|
|
161
|
+
if (!is_nullish(c)) map_put(paging, "cursor", c);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
std::string morepath = as_str(getp(page, "more"));
|
|
165
|
+
if (!morepath.empty()) {
|
|
166
|
+
Value m = Struct::getpath(conn, splitDots(morepath));
|
|
167
|
+
if (m.is_bool()) {
|
|
168
|
+
map_put(paging, "hasMore", m);
|
|
169
|
+
explicitMore = true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
95
174
|
// Body-level cursors.
|
|
96
175
|
if (body.is_map()) {
|
|
97
176
|
Value bnext = getp(body, "next");
|
|
@@ -109,10 +188,16 @@ public:
|
|
|
109
188
|
Value bhasMore = getp(body, "hasMore");
|
|
110
189
|
if (bhasMore.is_bool()) {
|
|
111
190
|
map_put(paging, "hasMore", bhasMore);
|
|
191
|
+
explicitMore = true;
|
|
112
192
|
}
|
|
113
193
|
}
|
|
114
194
|
|
|
115
|
-
|
|
195
|
+
// Cursor presence only INFERS another page. When the server stated the
|
|
196
|
+
// answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
|
|
197
|
+
// that wins: a final page normally carries both an end cursor and
|
|
198
|
+
// hasNextPage false, and inferring from the cursor there would send the
|
|
199
|
+
// caller back for a page that does not exist, forever.
|
|
200
|
+
if (!explicitMore && !is_true(getp(paging, "hasMore")) &&
|
|
116
201
|
(!is_nullish(getp(paging, "next")) || !is_nullish(getp(paging, "cursor")) ||
|
|
117
202
|
!is_nullish(getp(paging, "nextPage")))) {
|
|
118
203
|
map_put(paging, "hasMore", Value(true));
|
|
@@ -123,6 +208,23 @@ public:
|
|
|
123
208
|
}
|
|
124
209
|
|
|
125
210
|
private:
|
|
211
|
+
// The model records connection/cursor/more as dot paths; Struct::getpath
|
|
212
|
+
// takes pre-split segments.
|
|
213
|
+
static std::vector<std::string> splitDots(const std::string& path) {
|
|
214
|
+
std::vector<std::string> out;
|
|
215
|
+
size_t start = 0;
|
|
216
|
+
while (true) {
|
|
217
|
+
size_t dot = path.find('.', start);
|
|
218
|
+
if (std::string::npos == dot) {
|
|
219
|
+
out.push_back(path.substr(start));
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
out.push_back(path.substr(start, dot - start));
|
|
223
|
+
start = dot + 1;
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
|
|
126
228
|
bool isList(CtxPtr ctx) {
|
|
127
229
|
std::string opname = "";
|
|
128
230
|
if (ctx->op) opname = ctx->op->name;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
#include <algorithm>
|
|
11
11
|
#include <memory>
|
|
12
|
+
#include <cctype>
|
|
12
13
|
#include <string>
|
|
13
14
|
#include <vector>
|
|
14
15
|
|
|
@@ -376,6 +377,133 @@ inline Value makePoint(CtxPtr ctx) {
|
|
|
376
377
|
return ctx->point;
|
|
377
378
|
}
|
|
378
379
|
|
|
380
|
+
// ---- graphql ----------------------------------------------------------
|
|
381
|
+
//
|
|
382
|
+
// GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
|
|
383
|
+
// produces uses this unchanged. The API-specific part — which operations
|
|
384
|
+
// exist and what each one's document is — is model data, computed once by
|
|
385
|
+
// apidef and emitted into Config.
|
|
386
|
+
|
|
387
|
+
// Content type every GraphQL-over-HTTP request uses.
|
|
388
|
+
inline const char* graphqlContentType() { return "application/json"; }
|
|
389
|
+
|
|
390
|
+
// Map a GraphQL error to the same error codes the HTTP path produces, so a
|
|
391
|
+
// caller handles auth or rate limiting identically on both transports.
|
|
392
|
+
// Servers put the machine-readable code in `extensions.code`; Linear-style
|
|
393
|
+
// APIs use `extensions.type`.
|
|
394
|
+
inline std::string graphqlErrorCode(const Value& gqlerr) {
|
|
395
|
+
Value ext = getp(gqlerr, "extensions");
|
|
396
|
+
|
|
397
|
+
std::string raw = as_str(getp(ext, "code"));
|
|
398
|
+
if (raw.empty()) raw = as_str(getp(ext, "type"));
|
|
399
|
+
for (auto& ch : raw) ch = (char)std::toupper((unsigned char)ch);
|
|
400
|
+
|
|
401
|
+
if (raw.find("AUTH") != std::string::npos ||
|
|
402
|
+
raw.find("FORBIDDEN") != std::string::npos ||
|
|
403
|
+
raw.find("UNAUTHENTICATED") != std::string::npos) {
|
|
404
|
+
return "request_auth";
|
|
405
|
+
}
|
|
406
|
+
if (raw.find("RATELIMIT") != std::string::npos ||
|
|
407
|
+
raw.find("RATE_LIMIT") != std::string::npos ||
|
|
408
|
+
raw.find("TOO_MANY") != std::string::npos) {
|
|
409
|
+
return "request_ratelimit";
|
|
410
|
+
}
|
|
411
|
+
if (raw.find("BAD_USER_INPUT") != std::string::npos ||
|
|
412
|
+
raw.find("VALIDATION") != std::string::npos ||
|
|
413
|
+
raw.find("INVALID") != std::string::npos) {
|
|
414
|
+
return "request_invalid";
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return "request_graphql";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Build the request body for a GraphQL point.
|
|
421
|
+
//
|
|
422
|
+
// Variables come from the op's own arguments: a named variable binds to the
|
|
423
|
+
// like-named argument (`from`), and the input-object variable (empty
|
|
424
|
+
// `from`) takes the request data as a whole — which is what makes a
|
|
425
|
+
// generated create/update call look exactly like its REST equivalent.
|
|
426
|
+
inline Value graphqlBody(CtxPtr ctx) {
|
|
427
|
+
Value gql = getp(ctx->point, "graphql");
|
|
428
|
+
if (!gql.is_map()) return Value::undef();
|
|
429
|
+
|
|
430
|
+
// reqmatch/reqdata hold the caller's arguments for this operation; which
|
|
431
|
+
// one depends on whether the op takes match or data input.
|
|
432
|
+
Value reqsrc = ctx->reqmatch;
|
|
433
|
+
Value datasrc = ctx->match;
|
|
434
|
+
if (ctx->op && ctx->op->input == "data") {
|
|
435
|
+
reqsrc = ctx->reqdata;
|
|
436
|
+
datasrc = ctx->data;
|
|
437
|
+
}
|
|
438
|
+
if (!reqsrc.is_map()) reqsrc = vmap();
|
|
439
|
+
if (!datasrc.is_map()) datasrc = vmap();
|
|
440
|
+
|
|
441
|
+
Value variables = vmap();
|
|
442
|
+
|
|
443
|
+
Value varlist = getp(gql, "vars");
|
|
444
|
+
if (varlist.is_list()) {
|
|
445
|
+
for (const auto& spec : *varlist.as_list()) {
|
|
446
|
+
if (!spec.is_map()) continue;
|
|
447
|
+
|
|
448
|
+
std::string name = as_str(getp(spec, "name"));
|
|
449
|
+
std::string from = as_str(getp(spec, "from"));
|
|
450
|
+
|
|
451
|
+
if (from.empty()) {
|
|
452
|
+
// The input object IS the request body. Strip the action selector,
|
|
453
|
+
// which is an SDK-side point discriminator, not an API field.
|
|
454
|
+
Value body = vmap();
|
|
455
|
+
for (const auto& item : Struct::items(reqsrc)) {
|
|
456
|
+
std::string key = as_str(pair_key(item));
|
|
457
|
+
if ("$action" != key) map_put(body, key, pair_val(item));
|
|
458
|
+
}
|
|
459
|
+
map_put(variables, name, body);
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Only send variables the caller actually supplied: sending an
|
|
464
|
+
// explicit null would clear a field on many APIs.
|
|
465
|
+
Value val = getp(reqsrc, from);
|
|
466
|
+
if (is_nullish(val)) val = getp(datasrc, from);
|
|
467
|
+
if (!is_nullish(val)) map_put(variables, name, val);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
Value out = vmap();
|
|
472
|
+
map_put(out, "query", getp(gql, "doc"));
|
|
473
|
+
map_put(out, "variables", variables);
|
|
474
|
+
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Inspect a decoded GraphQL response body and record a failure when the
|
|
479
|
+
// server reported one. Returns true when an error was recorded.
|
|
480
|
+
//
|
|
481
|
+
// Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
482
|
+
// surface has no partial-success concept, and silently returning half an
|
|
483
|
+
// object would be worse than failing.
|
|
484
|
+
inline bool graphqlErrors(CtxPtr ctx) {
|
|
485
|
+
if (!ctx->result) return false;
|
|
486
|
+
if ("graphql" != as_str(getp(ctx->point, "kind"))) return false;
|
|
487
|
+
|
|
488
|
+
Value errors = getp(ctx->result->body, "errors");
|
|
489
|
+
if (!errors.is_list()) return false;
|
|
490
|
+
|
|
491
|
+
const auto& el = *errors.as_list();
|
|
492
|
+
if (el.empty()) return false;
|
|
493
|
+
|
|
494
|
+
Value first = el[0];
|
|
495
|
+
std::string msg = as_str(getp(first, "message"));
|
|
496
|
+
if (msg.empty()) msg = "graphql error";
|
|
497
|
+
if (1 < el.size()) {
|
|
498
|
+
msg = msg + " (+" + std::to_string(el.size() - 1) + " more)";
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
ctx->result->err = ctx->makeError(graphqlErrorCode(first), "graphql: " + msg);
|
|
502
|
+
ctx->result->ok = false;
|
|
503
|
+
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
|
|
379
507
|
// ---- makeSpec ---------------------------------------------------------
|
|
380
508
|
|
|
381
509
|
inline SpecPtr makeSpec(CtxPtr ctx) {
|
|
@@ -412,8 +540,23 @@ inline SpecPtr makeSpec(CtxPtr ctx) {
|
|
|
412
540
|
ctx->spec->params = utility->prepareParams(ctx);
|
|
413
541
|
ctx->spec->query = utility->prepareQuery(ctx);
|
|
414
542
|
ctx->spec->headers = utility->prepareHeaders(ctx);
|
|
415
|
-
|
|
416
|
-
|
|
543
|
+
|
|
544
|
+
if ("graphql" == as_str(getp(ctx->point, "kind"))) {
|
|
545
|
+
// GraphQL addresses one endpoint: no path parts, no query string, and
|
|
546
|
+
// the body carries the operation. prepareBody is skipped deliberately
|
|
547
|
+
// — it only emits a body for data-input ops, whereas every GraphQL op
|
|
548
|
+
// posts one, including load/list/remove.
|
|
549
|
+
ctx->spec->body = graphqlBody(ctx);
|
|
550
|
+
ctx->spec->path = "";
|
|
551
|
+
// prepareQuery already copied the op's match arguments into the query
|
|
552
|
+
// string. Those same values are bound as operation variables, so
|
|
553
|
+
// leaving them would send /graphql?id=i1.
|
|
554
|
+
ctx->spec->query = vmap();
|
|
555
|
+
map_put(ctx->spec->headers, "content-type", Value(graphqlContentType()));
|
|
556
|
+
} else {
|
|
557
|
+
ctx->spec->body = utility->prepareBody(ctx);
|
|
558
|
+
ctx->spec->path = utility->preparePath(ctx);
|
|
559
|
+
}
|
|
417
560
|
|
|
418
561
|
if (ctx->ctrl->explain.is_map()) {
|
|
419
562
|
map_put(ctx->ctrl->explain, "spec", ctx->spec->toValue());
|
|
@@ -591,6 +734,12 @@ inline ResponsePtr makeResponse(CtxPtr ctx) {
|
|
|
591
734
|
utility->resultBasic(ctx);
|
|
592
735
|
utility->resultHeaders(ctx);
|
|
593
736
|
utility->resultBody(ctx);
|
|
737
|
+
|
|
738
|
+
// GraphQL reports failures as a top-level `errors` array under HTTP 200,
|
|
739
|
+
// so resultBasic's status check never sees them. Lift them here, before
|
|
740
|
+
// the response transform tries to unwrap data that is not there.
|
|
741
|
+
graphqlErrors(ctx);
|
|
742
|
+
|
|
594
743
|
utility->transformResponse(ctx);
|
|
595
744
|
|
|
596
745
|
if (!result->err) result->ok = true;
|
|
@@ -879,7 +1028,7 @@ inline const char* OPTSPEC_JSON() {
|
|
|
879
1028
|
"\"headers\": { \"`$CHILD`\": \"`$STRING`\" },"
|
|
880
1029
|
"\"allow\": {"
|
|
881
1030
|
" \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\","
|
|
882
|
-
" \"op\": \"create,update,load,list,remove,command,direct\""
|
|
1031
|
+
" \"op\": \"create,update,load,list,remove,command,direct,graphql\""
|
|
883
1032
|
"},"
|
|
884
1033
|
"\"entity\": { \"`$CHILD`\": {"
|
|
885
1034
|
" \"`$OPEN`\": true, \"active\": false, \"alias\": {} } },"
|
|
@@ -30,6 +30,8 @@ public class Utility
|
|
|
30
30
|
public Func<Context, Dictionary<string, object?>> PrepareParams = null!;
|
|
31
31
|
public Func<Context, string> PreparePath = null!;
|
|
32
32
|
public Func<Context, Dictionary<string, object?>> PrepareQuery = null!;
|
|
33
|
+
public Func<Context, object?> GraphqlBody = null!;
|
|
34
|
+
public Func<Context, bool> GraphqlErrors = null!;
|
|
33
35
|
public Func<Context, Result> ResultBasic = null!;
|
|
34
36
|
public Func<Context, Result> ResultBody = null!;
|
|
35
37
|
public Func<Context, Result> ResultHeaders = null!;
|
|
@@ -76,6 +78,8 @@ public class Utility
|
|
|
76
78
|
PrepareParams = src.PrepareParams,
|
|
77
79
|
PreparePath = src.PreparePath,
|
|
78
80
|
PrepareQuery = src.PrepareQuery,
|
|
81
|
+
GraphqlBody = src.GraphqlBody,
|
|
82
|
+
GraphqlErrors = src.GraphqlErrors,
|
|
79
83
|
ResultBasic = src.ResultBasic,
|
|
80
84
|
ResultBody = src.ResultBody,
|
|
81
85
|
ResultHeaders = src.ResultHeaders,
|