@voxgig/sdkgen 1.5.1 → 2.0.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/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 +8 -8
- 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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
package SCALAPACKAGE.feature
|
|
2
2
|
|
|
3
|
-
import java.util.{LinkedHashMap, Map => JMap}
|
|
3
|
+
import java.util.{LinkedHashMap, List => JList, Map => JMap}
|
|
4
4
|
import java.util.regex.{Matcher, Pattern}
|
|
5
5
|
import SCALAPACKAGE.core.{Context, SdkClient}
|
|
6
|
+
import SCALAPACKAGE.utility.struct.Struct
|
|
6
7
|
|
|
7
8
|
// Pagination support for list operations. On the way out (PreRequest) it
|
|
8
9
|
// stamps page/limit (or a cursor) into the request query; on the way back
|
|
@@ -52,6 +53,14 @@ class PagingFeature extends BaseFeature("paging", "0.0.1", true) {
|
|
|
52
53
|
paging = new LinkedHashMap[String, Object]()
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
// GraphQL paginates through operation VARIABLES, not the query string.
|
|
57
|
+
// This hook runs after makeSpec, so spec.body already holds the
|
|
58
|
+
// { query, variables } envelope, and before makeFetchDef serialises it.
|
|
59
|
+
if ("graphql" == Struct.getprop(ctx.point, "kind")) {
|
|
60
|
+
graphqlPreRequest(ctx, paging)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
val cursor = paging.get("cursor")
|
|
56
65
|
if (cursor != null) {
|
|
57
66
|
spec.query.put(cursorParam, cursor)
|
|
@@ -69,6 +78,52 @@ class PagingFeature extends BaseFeature("paging", "0.0.1", true) {
|
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
|
|
81
|
+
// Relay pagination: the cursor is the `after` variable (or whatever the
|
|
82
|
+
// model named it), and the page size is `first`.
|
|
83
|
+
private def graphqlPreRequest(ctx: Context, paging: JMap[String, Object]): Unit = {
|
|
84
|
+
val body = ctx.spec.body match {
|
|
85
|
+
case m: JMap[_, _] => m.asInstanceOf[JMap[String, Object]]
|
|
86
|
+
case _ => return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
val variables = body.get("variables") match {
|
|
90
|
+
case m: JMap[_, _] => m.asInstanceOf[JMap[String, Object]]
|
|
91
|
+
case _ =>
|
|
92
|
+
val nv = new LinkedHashMap[String, Object]()
|
|
93
|
+
body.put("variables", nv)
|
|
94
|
+
nv
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
val afterVar = FeatureOptions.foptStr(this.options, "afterVar", "after")
|
|
98
|
+
val firstVar = FeatureOptions.foptStr(this.options, "firstVar", "first")
|
|
99
|
+
|
|
100
|
+
// Only bind variables the operation actually declares, or the server
|
|
101
|
+
// rejects the document.
|
|
102
|
+
val declared = new java.util.HashSet[String]()
|
|
103
|
+
Struct.getpath(ctx.point, java.util.List.of("graphql", "vars")) match {
|
|
104
|
+
case vl: JList[_] =>
|
|
105
|
+
val it = vl.iterator()
|
|
106
|
+
while (it.hasNext) {
|
|
107
|
+
Struct.getprop(it.next(), "name") match {
|
|
108
|
+
case n: String => declared.add(n)
|
|
109
|
+
case _ =>
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
case _ =>
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
val cursor = paging.get("cursor")
|
|
116
|
+
if (cursor != null && declared.contains(afterVar)) {
|
|
117
|
+
variables.put(afterVar, cursor)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.options.get("limit") != null && variables.get(firstVar) == null &&
|
|
121
|
+
declared.contains(firstVar)) {
|
|
122
|
+
variables.put(firstVar,
|
|
123
|
+
java.lang.Integer.valueOf(FeatureOptions.foptInt(this.options, "limit", 0)))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
72
127
|
override def preResult(ctx: Context): Unit = {
|
|
73
128
|
if (!this.active || !isList(ctx)) {
|
|
74
129
|
return
|
|
@@ -98,6 +153,45 @@ class PagingFeature extends BaseFeature("paging", "0.0.1", true) {
|
|
|
98
153
|
case _ =>
|
|
99
154
|
}
|
|
100
155
|
|
|
156
|
+
// Set when the response states hasMore outright, rather than leaving it
|
|
157
|
+
// to be inferred from the presence of a cursor.
|
|
158
|
+
var explicitMore = false
|
|
159
|
+
|
|
160
|
+
// Relay connections carry the cursor in pageInfo, at the path the model
|
|
161
|
+
// recorded for this op.
|
|
162
|
+
(Struct.getpath(ctx.point, java.util.List.of("graphql", "page")), body) match {
|
|
163
|
+
case (pg: JMap[_, _], _: JMap[_, _]) =>
|
|
164
|
+
val page = pg.asInstanceOf[JMap[String, Object]]
|
|
165
|
+
// `connpath` locates the connection object inside the response
|
|
166
|
+
// envelope (data.<field>); the cursor/more paths are relative to it.
|
|
167
|
+
var conn: Object = body
|
|
168
|
+
page.get("connpath") match {
|
|
169
|
+
case cp: String if cp.nonEmpty =>
|
|
170
|
+
val sub = Struct.getpath(body, cp)
|
|
171
|
+
if (sub != null) conn = sub
|
|
172
|
+
case _ =>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
page.get("cursor") match {
|
|
176
|
+
case cur: String if cur.nonEmpty =>
|
|
177
|
+
val c = Struct.getpath(conn, cur)
|
|
178
|
+
if (c != null) paging.put("cursor", c)
|
|
179
|
+
case _ =>
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
page.get("more") match {
|
|
183
|
+
case mp: String if mp.nonEmpty =>
|
|
184
|
+
Struct.getpath(conn, mp) match {
|
|
185
|
+
case b: java.lang.Boolean =>
|
|
186
|
+
paging.put("hasMore", b)
|
|
187
|
+
explicitMore = true
|
|
188
|
+
case _ =>
|
|
189
|
+
}
|
|
190
|
+
case _ =>
|
|
191
|
+
}
|
|
192
|
+
case _ =>
|
|
193
|
+
}
|
|
194
|
+
|
|
101
195
|
// Body-level cursors.
|
|
102
196
|
body match {
|
|
103
197
|
case bm0: JMap[_, _] =>
|
|
@@ -112,13 +206,20 @@ class PagingFeature extends BaseFeature("paging", "0.0.1", true) {
|
|
|
112
206
|
paging.put("cursor", bm.get("nextCursor"))
|
|
113
207
|
}
|
|
114
208
|
bm.get("hasMore") match {
|
|
115
|
-
case b: java.lang.Boolean =>
|
|
209
|
+
case b: java.lang.Boolean =>
|
|
210
|
+
paging.put("hasMore", b)
|
|
211
|
+
explicitMore = true
|
|
116
212
|
case _ =>
|
|
117
213
|
}
|
|
118
214
|
case _ =>
|
|
119
215
|
}
|
|
120
216
|
|
|
121
|
-
|
|
217
|
+
// Cursor presence only INFERS another page. When the server stated the
|
|
218
|
+
// answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
|
|
219
|
+
// that wins: a final page normally carries both an end cursor and
|
|
220
|
+
// hasNextPage false, and inferring from the cursor there would send the
|
|
221
|
+
// caller back for a page that does not exist, forever.
|
|
222
|
+
if (!explicitMore && !(java.lang.Boolean.TRUE == paging.get("hasMore"))
|
|
122
223
|
&& (paging.get("next") != null || paging.get("cursor") != null
|
|
123
224
|
|| paging.get("nextPage") != null)) {
|
|
124
225
|
paging.put("hasMore", java.lang.Boolean.TRUE)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
package SCALAPACKAGE.utility
|
|
2
|
+
|
|
3
|
+
import java.util.{ArrayList, LinkedHashMap, List => JList, Map => JMap}
|
|
4
|
+
import SCALAPACKAGE.core._
|
|
5
|
+
import SCALAPACKAGE.utility.struct.Struct
|
|
6
|
+
|
|
7
|
+
// GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
|
|
8
|
+
// produces uses this file unchanged. The API-specific part — which
|
|
9
|
+
// operations exist and what each one's document is — is model data,
|
|
10
|
+
// computed once by apidef and emitted into Config.
|
|
11
|
+
//
|
|
12
|
+
// Two jobs:
|
|
13
|
+
//
|
|
14
|
+
// graphqlBody — build { query, variables } for a point, binding the
|
|
15
|
+
// op's arguments to the document's declared variables.
|
|
16
|
+
//
|
|
17
|
+
// graphqlErrors — lift a GraphQL failure into an SDK error. GraphQL
|
|
18
|
+
// reports failures as a top-level `errors` array under
|
|
19
|
+
// HTTP 200, so the status-driven path in resultBasic
|
|
20
|
+
// never sees them.
|
|
21
|
+
object Graphql {
|
|
22
|
+
|
|
23
|
+
// Content type every GraphQL-over-HTTP request uses.
|
|
24
|
+
val ContentType: String = "application/json"
|
|
25
|
+
|
|
26
|
+
// Map a GraphQL error to the same error codes the HTTP path produces, so
|
|
27
|
+
// a caller handles auth or rate limiting identically on both transports.
|
|
28
|
+
// Servers put the machine-readable code in `extensions.code`;
|
|
29
|
+
// Linear-style APIs use `extensions.type`.
|
|
30
|
+
def errorCode(gqlerr: Object): String = {
|
|
31
|
+
val ext = Struct.getprop(gqlerr, "extensions")
|
|
32
|
+
|
|
33
|
+
var code = Struct.getprop(ext, "code") match {
|
|
34
|
+
case s: String => s
|
|
35
|
+
case _ => ""
|
|
36
|
+
}
|
|
37
|
+
if (code == null || code.isEmpty) {
|
|
38
|
+
code = Struct.getprop(ext, "type") match {
|
|
39
|
+
case s: String => s
|
|
40
|
+
case _ => ""
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
val raw = code.toUpperCase
|
|
44
|
+
|
|
45
|
+
if (raw.contains("AUTH") || raw.contains("FORBIDDEN") ||
|
|
46
|
+
raw.contains("UNAUTHENTICATED")) return "request_auth"
|
|
47
|
+
if (raw.contains("RATELIMIT") || raw.contains("RATE_LIMIT") ||
|
|
48
|
+
raw.contains("TOO_MANY")) return "request_ratelimit"
|
|
49
|
+
if (raw.contains("BAD_USER_INPUT") || raw.contains("VALIDATION") ||
|
|
50
|
+
raw.contains("INVALID")) return "request_invalid"
|
|
51
|
+
|
|
52
|
+
"request_graphql"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Build the request body for a GraphQL point.
|
|
56
|
+
//
|
|
57
|
+
// Variables come from the op's own arguments: a named variable binds to
|
|
58
|
+
// the like-named argument (`from`), and the input-object variable (empty
|
|
59
|
+
// `from`) takes the request data as a whole — which is what makes a
|
|
60
|
+
// generated create/update call look exactly like its REST equivalent.
|
|
61
|
+
def graphqlBody(ctx: Context): Object = {
|
|
62
|
+
val gql = Helpers.toMapAny(Struct.getprop(ctx.point, "graphql"))
|
|
63
|
+
if (gql == null) return null
|
|
64
|
+
|
|
65
|
+
// reqmatch/reqdata hold the caller's arguments for THIS call; data/match
|
|
66
|
+
// hold the entity's current state. Which pair depends on whether the op
|
|
67
|
+
// takes match or data input. A named variable falls back to the current
|
|
68
|
+
// state, so updating a loaded entity with just {title} still binds the
|
|
69
|
+
// stored id the mutation requires.
|
|
70
|
+
val datainput = ctx.op != null && "data" == ctx.op.input
|
|
71
|
+
var reqsrc = if (datainput) ctx.reqdata else ctx.reqmatch
|
|
72
|
+
var datasrc = if (datainput) ctx.data else ctx.matchData
|
|
73
|
+
if (reqsrc == null) reqsrc = new LinkedHashMap[String, Object]()
|
|
74
|
+
if (datasrc == null) datasrc = new LinkedHashMap[String, Object]()
|
|
75
|
+
|
|
76
|
+
val variables = new LinkedHashMap[String, Object]()
|
|
77
|
+
|
|
78
|
+
val varlist = Struct.getprop(gql, "vars") match {
|
|
79
|
+
case l: JList[_] => l.asInstanceOf[JList[Object]]
|
|
80
|
+
case _ => new ArrayList[Object]()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
val it = varlist.iterator()
|
|
84
|
+
while (it.hasNext) {
|
|
85
|
+
val spec = Helpers.toMapAny(it.next())
|
|
86
|
+
if (spec != null) {
|
|
87
|
+
val name = Struct.getprop(spec, "name") match {
|
|
88
|
+
case s: String => s
|
|
89
|
+
case _ => null
|
|
90
|
+
}
|
|
91
|
+
val from = Struct.getprop(spec, "from") match {
|
|
92
|
+
case s: String => s
|
|
93
|
+
case _ => ""
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (name != null) {
|
|
97
|
+
if (from == null || from.isEmpty) {
|
|
98
|
+
// The input object IS the request body. Strip the action
|
|
99
|
+
// selector, which is an SDK-side point discriminator, not an
|
|
100
|
+
// API field.
|
|
101
|
+
val body = new LinkedHashMap[String, Object]()
|
|
102
|
+
val ks = reqsrc.keySet().iterator()
|
|
103
|
+
while (ks.hasNext) {
|
|
104
|
+
val k = ks.next()
|
|
105
|
+
if ("$action" != k) body.put(k, reqsrc.get(k))
|
|
106
|
+
}
|
|
107
|
+
variables.put(name, body)
|
|
108
|
+
} else {
|
|
109
|
+
// Only send variables the caller actually supplied: sending an
|
|
110
|
+
// explicit null would clear a field on many APIs.
|
|
111
|
+
var v = Struct.getprop(reqsrc, from)
|
|
112
|
+
if (v == null) v = Struct.getprop(datasrc, from)
|
|
113
|
+
if (v != null) variables.put(name, v)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
val out = new LinkedHashMap[String, Object]()
|
|
120
|
+
out.put("query", Struct.getprop(gql, "doc"))
|
|
121
|
+
out.put("variables", variables)
|
|
122
|
+
out
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Inspect a decoded GraphQL response body and record a failure when the
|
|
126
|
+
// server reported one. Returns true when an error was recorded.
|
|
127
|
+
//
|
|
128
|
+
// Partial data (`data` alongside `errors`) is treated as failure: the
|
|
129
|
+
// REST surface has no partial-success concept, and silently returning
|
|
130
|
+
// half an object would be worse than failing.
|
|
131
|
+
def graphqlErrors(ctx: Context): java.lang.Boolean = {
|
|
132
|
+
val result = ctx.result
|
|
133
|
+
if (result == null || ctx.point == null) return false
|
|
134
|
+
|
|
135
|
+
val kind = Struct.getprop(ctx.point, "kind") match {
|
|
136
|
+
case s: String => s
|
|
137
|
+
case _ => ""
|
|
138
|
+
}
|
|
139
|
+
if ("graphql" != kind) return false
|
|
140
|
+
|
|
141
|
+
val errors = Struct.getprop(result.body, "errors") match {
|
|
142
|
+
case l: JList[_] => l.asInstanceOf[JList[Object]]
|
|
143
|
+
case _ => null
|
|
144
|
+
}
|
|
145
|
+
if (errors == null || errors.isEmpty) return false
|
|
146
|
+
|
|
147
|
+
val first = errors.get(0)
|
|
148
|
+
var msg = Struct.getprop(first, "message") match {
|
|
149
|
+
case s: String => s
|
|
150
|
+
case _ => ""
|
|
151
|
+
}
|
|
152
|
+
if (msg == null || msg.isEmpty) msg = "graphql error"
|
|
153
|
+
if (1 < errors.size) msg = msg + " (+" + (errors.size - 1) + " more)"
|
|
154
|
+
|
|
155
|
+
result.err = ctx.makeError(errorCode(first), "graphql: " + msg)
|
|
156
|
+
result.ok = false
|
|
157
|
+
|
|
158
|
+
true
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -198,7 +198,7 @@ object MakeOptions {
|
|
|
198
198
|
+ "\"headers\": { \"`$CHILD`\": \"`$STRING`\" },"
|
|
199
199
|
+ "\"allow\": {"
|
|
200
200
|
+ " \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\","
|
|
201
|
-
+ " \"op\": \"create,update,load,list,remove,command,direct\""
|
|
201
|
+
+ " \"op\": \"create,update,load,list,remove,command,direct,graphql\""
|
|
202
202
|
+ "},"
|
|
203
203
|
+ "\"entity\": { \"`$CHILD`\": {"
|
|
204
204
|
+ " \"`$OPEN`\": true, \"active\": false, \"alias\": {} } },"
|
|
@@ -363,6 +363,12 @@ object MakeResponse {
|
|
|
363
363
|
utility.resultBasic(ctx)
|
|
364
364
|
utility.resultHeaders(ctx)
|
|
365
365
|
utility.resultBody(ctx)
|
|
366
|
+
|
|
367
|
+
// GraphQL reports failures as a top-level `errors` array under HTTP
|
|
368
|
+
// 200, so resultBasic's status check never sees them. Lift them here,
|
|
369
|
+
// before the response transform tries to unwrap data that is not there.
|
|
370
|
+
utility.graphqlErrors(ctx)
|
|
371
|
+
|
|
366
372
|
utility.transformResponse(ctx)
|
|
367
373
|
|
|
368
374
|
if (result.err == null) result.ok = true
|
|
@@ -37,8 +37,28 @@ object MakeSpec {
|
|
|
37
37
|
ctx.spec.params = utility.prepareParams(ctx)
|
|
38
38
|
ctx.spec.query = utility.prepareQuery(ctx)
|
|
39
39
|
ctx.spec.headers = utility.prepareHeaders(ctx)
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
|
|
41
|
+
val pkind = Struct.getprop(ctx.point, "kind") match {
|
|
42
|
+
case s: String => s
|
|
43
|
+
case _ => ""
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if ("graphql" == pkind) {
|
|
47
|
+
// GraphQL addresses one endpoint: no path parts, no query string, and
|
|
48
|
+
// the body carries the operation. prepareBody is skipped deliberately
|
|
49
|
+
// — it only emits a body for data-input ops, whereas every GraphQL op
|
|
50
|
+
// posts one, including load/list/remove.
|
|
51
|
+
ctx.spec.body = utility.graphqlBody(ctx)
|
|
52
|
+
ctx.spec.path = ""
|
|
53
|
+
// prepareQuery already copied the op's match arguments into the query
|
|
54
|
+
// string. Those same values are bound as operation variables, so
|
|
55
|
+
// leaving them would send /graphql?id=i1.
|
|
56
|
+
ctx.spec.query = new LinkedHashMap[String, Object]()
|
|
57
|
+
ctx.spec.headers.put("content-type", Graphql.ContentType)
|
|
58
|
+
} else {
|
|
59
|
+
ctx.spec.body = utility.prepareBody(ctx)
|
|
60
|
+
ctx.spec.path = utility.preparePath(ctx)
|
|
61
|
+
}
|
|
42
62
|
|
|
43
63
|
if (ctx.ctrl.explain != null) ctx.ctrl.explain.put("spec", ctx.spec)
|
|
44
64
|
|
|
@@ -29,6 +29,8 @@ object Register {
|
|
|
29
29
|
u.prepareParams = (ctx) => PrepareParams.prepareParams(ctx)
|
|
30
30
|
u.preparePath = (ctx) => PreparePath.preparePath(ctx)
|
|
31
31
|
u.prepareQuery = (ctx) => PrepareQuery.prepareQuery(ctx)
|
|
32
|
+
u.graphqlBody = (ctx) => Graphql.graphqlBody(ctx)
|
|
33
|
+
u.graphqlErrors = (ctx) => Graphql.graphqlErrors(ctx)
|
|
32
34
|
u.resultBasic = (ctx) => ResultBasic.resultBasic(ctx)
|
|
33
35
|
u.resultBody = (ctx) => ResultBody.resultBody(ctx)
|
|
34
36
|
u.resultHeaders = (ctx) => ResultHeaders.resultHeaders(ctx)
|
|
@@ -29,6 +29,8 @@ public final class Utility {
|
|
|
29
29
|
public var prepareParams: ((Context) -> VMap)!
|
|
30
30
|
public var preparePath: ((Context) -> String)!
|
|
31
31
|
public var prepareQuery: ((Context) -> VMap)!
|
|
32
|
+
public var graphqlBody: ((Context) -> Value)!
|
|
33
|
+
public var graphqlErrors: ((Context) -> Bool)!
|
|
32
34
|
public var resultBasic: ((Context) -> Result)!
|
|
33
35
|
public var resultBody: ((Context) -> Result)!
|
|
34
36
|
public var resultHeaders: ((Context) -> Result)!
|
|
@@ -70,6 +72,8 @@ public final class Utility {
|
|
|
70
72
|
u.prepareParams = src.prepareParams
|
|
71
73
|
u.preparePath = src.preparePath
|
|
72
74
|
u.prepareQuery = src.prepareQuery
|
|
75
|
+
u.graphqlBody = src.graphqlBody
|
|
76
|
+
u.graphqlErrors = src.graphqlErrors
|
|
73
77
|
u.resultBasic = src.resultBasic
|
|
74
78
|
u.resultBody = src.resultBody
|
|
75
79
|
u.resultHeaders = src.resultHeaders
|
|
@@ -48,6 +48,14 @@ public final class PagingFeature: BaseFeature {
|
|
|
48
48
|
// A per-call cursor/page from ctrl takes priority (auto-iteration).
|
|
49
49
|
let paging = 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
|
+
if "graphql" == gp(ctx.point, "kind").asString {
|
|
55
|
+
graphqlPreRequest(ctx, paging)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
51
59
|
var cursor: Value = .noval
|
|
52
60
|
var hasCursor = false
|
|
53
61
|
if let paging = paging {
|
|
@@ -71,6 +79,49 @@ public final class PagingFeature: BaseFeature {
|
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
|
|
82
|
+
// Relay pagination: the cursor is the `after` variable (or whatever the
|
|
83
|
+
// model named it), and the page size is `first`.
|
|
84
|
+
private func graphqlPreRequest(_ ctx: Context, _ paging: VMap?) {
|
|
85
|
+
guard let spec = ctx.spec, let body = spec.body.asMap else {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let variables: VMap
|
|
90
|
+
if let v = body.entries["variables"]?.asMap {
|
|
91
|
+
variables = v
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
variables = VMap()
|
|
95
|
+
body.entries["variables"] = .map(variables)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let afterVar = foptStr(options, "afterVar", "after")
|
|
99
|
+
let firstVar = foptStr(options, "firstVar", "first")
|
|
100
|
+
|
|
101
|
+
// Only bind variables the operation actually declares, or the server
|
|
102
|
+
// rejects the document.
|
|
103
|
+
var declared: Set<String> = []
|
|
104
|
+
if let varlist = gpath(ctx.point, "graphql", "vars").asList {
|
|
105
|
+
for v in varlist.items {
|
|
106
|
+
if let name = gp(v, "name").asString {
|
|
107
|
+
declared.insert(name)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if let paging = paging {
|
|
113
|
+
let cursor = gp(paging, "cursor")
|
|
114
|
+
if !isNil(cursor) && declared.contains(afterVar) {
|
|
115
|
+
variables.entries[afterVar] = cursor
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if !isNil(fopt(options, "limit")) && isNil(gp(variables, firstVar))
|
|
120
|
+
&& declared.contains(firstVar) {
|
|
121
|
+
variables.entries[firstVar] = .int(Int64(foptInt(options, "limit", 0)))
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
74
125
|
public override func preResult(_ ctx: Context) {
|
|
75
126
|
if !active || !isList(ctx) {
|
|
76
127
|
return
|
|
@@ -98,6 +149,34 @@ public final class PagingFeature: BaseFeature {
|
|
|
98
149
|
}
|
|
99
150
|
}
|
|
100
151
|
|
|
152
|
+
// Set when the response states hasMore outright, rather than leaving it
|
|
153
|
+
// to be inferred from the presence of a cursor.
|
|
154
|
+
var explicitMore = false
|
|
155
|
+
|
|
156
|
+
// Relay connections carry the cursor in pageInfo, at the path the model
|
|
157
|
+
// recorded for this op.
|
|
158
|
+
if let page = gpath(ctx.point, "graphql", "page").asMap, nil != body.asMap {
|
|
159
|
+
// `connpath` locates the connection object inside the response
|
|
160
|
+
// envelope (data.<field>); the cursor/more paths are relative to it.
|
|
161
|
+
var conn = body
|
|
162
|
+
if let connpath = gp(page, "connpath").asString, !connpath.isEmpty {
|
|
163
|
+
let sub = getpath(body, jtpv(connpath.split(separator: ".").map(String.init)))
|
|
164
|
+
if !isNil(sub) { conn = sub }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if let cursorpath = gp(page, "cursor").asString, !cursorpath.isEmpty {
|
|
168
|
+
let c = getpath(conn, jtpv(cursorpath.split(separator: ".").map(String.init)))
|
|
169
|
+
if !isNil(c) { paging.entries["cursor"] = c }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if let morepath = gp(page, "more").asString, !morepath.isEmpty {
|
|
173
|
+
if let more = getpath(conn, jtpv(morepath.split(separator: ".").map(String.init))).asBool {
|
|
174
|
+
paging.entries["hasMore"] = .bool(more)
|
|
175
|
+
explicitMore = true
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
101
180
|
// Body-level cursors.
|
|
102
181
|
if let bm = body.asMap {
|
|
103
182
|
let next = gp(bm, "next")
|
|
@@ -114,11 +193,17 @@ public final class PagingFeature: BaseFeature {
|
|
|
114
193
|
}
|
|
115
194
|
if let hmb = gp(bm, "hasMore").asBool {
|
|
116
195
|
paging.entries["hasMore"] = .bool(hmb)
|
|
196
|
+
explicitMore = true
|
|
117
197
|
}
|
|
118
198
|
}
|
|
119
199
|
|
|
200
|
+
// Cursor presence only INFERS another page. When the server stated the
|
|
201
|
+
// answer outright - relay's `hasNextPage: false`, or a body `hasMore` -
|
|
202
|
+
// that wins: a final page normally carries both an end cursor and
|
|
203
|
+
// hasNextPage false, and inferring from the cursor there would send the
|
|
204
|
+
// caller back for a page that does not exist, forever.
|
|
120
205
|
let hasMoreNow = gp(paging, "hasMore").asBool ?? false
|
|
121
|
-
if !hasMoreNow &&
|
|
206
|
+
if !explicitMore && !hasMoreNow &&
|
|
122
207
|
(!isNil(gp(paging, "next")) || !isNil(gp(paging, "cursor")) || !isNil(gp(paging, "nextPage"))) {
|
|
123
208
|
paging.entries["hasMore"] = .bool(true)
|
|
124
209
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
// GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
|
|
4
|
+
// produces uses this file unchanged. The API-specific part — which
|
|
5
|
+
// operations exist and what each one's document is — is model data,
|
|
6
|
+
// computed once by apidef and emitted into Config.
|
|
7
|
+
//
|
|
8
|
+
// Two jobs:
|
|
9
|
+
//
|
|
10
|
+
// graphqlBodyUtil — build { query, variables } for a point, binding the
|
|
11
|
+
// op's arguments to the document's declared variables.
|
|
12
|
+
//
|
|
13
|
+
// graphqlErrorsUtil — lift a GraphQL failure into an SDK error. GraphQL
|
|
14
|
+
// reports failures as a top-level `errors` array under
|
|
15
|
+
// HTTP 200, so the status-driven path in resultBasic
|
|
16
|
+
// never sees them.
|
|
17
|
+
|
|
18
|
+
/// Content type every GraphQL-over-HTTP request uses.
|
|
19
|
+
let graphqlContentType = "application/json"
|
|
20
|
+
|
|
21
|
+
/// Map a GraphQL error to the same error codes the HTTP path produces, so a
|
|
22
|
+
/// caller handles auth or rate limiting identically on both transports.
|
|
23
|
+
/// Servers put the machine-readable code in `extensions.code`; Linear-style
|
|
24
|
+
/// APIs use `extensions.type`.
|
|
25
|
+
func graphqlErrorCode(_ gqlerr: Value) -> String {
|
|
26
|
+
let ext = gp(gqlerr, "extensions")
|
|
27
|
+
|
|
28
|
+
var code = gp(ext, "code").asString ?? ""
|
|
29
|
+
if code.isEmpty {
|
|
30
|
+
code = gp(ext, "type").asString ?? ""
|
|
31
|
+
}
|
|
32
|
+
let raw = code.uppercased()
|
|
33
|
+
|
|
34
|
+
if raw.contains("AUTH") || raw.contains("FORBIDDEN")
|
|
35
|
+
|| raw.contains("UNAUTHENTICATED") {
|
|
36
|
+
return "request_auth"
|
|
37
|
+
}
|
|
38
|
+
if raw.contains("RATELIMIT") || raw.contains("RATE_LIMIT")
|
|
39
|
+
|| raw.contains("TOO_MANY") {
|
|
40
|
+
return "request_ratelimit"
|
|
41
|
+
}
|
|
42
|
+
if raw.contains("BAD_USER_INPUT") || raw.contains("VALIDATION")
|
|
43
|
+
|| raw.contains("INVALID") {
|
|
44
|
+
return "request_invalid"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return "request_graphql"
|
|
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
|
|
54
|
+
/// `from`) takes the request data as a whole — which is what makes a
|
|
55
|
+
/// generated create/update call look exactly like its REST equivalent.
|
|
56
|
+
func graphqlBodyUtil(_ ctx: Context) -> Value {
|
|
57
|
+
let gql = gp(ctx.point, "graphql")
|
|
58
|
+
guard nil != gql.asMap else { return .noval }
|
|
59
|
+
|
|
60
|
+
// reqmatch/reqdata hold the caller's arguments for THIS call; data/match
|
|
61
|
+
// hold the entity's current state. Which pair depends on whether the op
|
|
62
|
+
// takes match or data input. A named variable falls back to the current
|
|
63
|
+
// state, so updating a loaded entity with just {title} still binds the
|
|
64
|
+
// stored id the mutation requires.
|
|
65
|
+
var reqsrc = ctx.reqmatch
|
|
66
|
+
var datasrc = ctx.match
|
|
67
|
+
if let op = ctx.op, "data" == op.input {
|
|
68
|
+
reqsrc = ctx.reqdata
|
|
69
|
+
datasrc = ctx.data
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let variables = VMap()
|
|
73
|
+
|
|
74
|
+
if let varlist = gp(gql, "vars").asList {
|
|
75
|
+
for spec in varlist.items {
|
|
76
|
+
guard nil != spec.asMap else { continue }
|
|
77
|
+
|
|
78
|
+
let name = gp(spec, "name").asString ?? ""
|
|
79
|
+
if name.isEmpty { continue }
|
|
80
|
+
|
|
81
|
+
let from = gp(spec, "from").asString ?? ""
|
|
82
|
+
|
|
83
|
+
if from.isEmpty {
|
|
84
|
+
// The input object IS the request body. Strip the action selector,
|
|
85
|
+
// which is an SDK-side point discriminator, not an API field.
|
|
86
|
+
let body = VMap()
|
|
87
|
+
for item in items(.map(reqsrc)) {
|
|
88
|
+
let key = item[0].asString ?? ""
|
|
89
|
+
if "$action" != key {
|
|
90
|
+
body.entries[key] = item[1]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
variables.entries[name] = .map(body)
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Only send variables the caller actually supplied: sending an
|
|
98
|
+
// explicit null would clear a field on many APIs.
|
|
99
|
+
var val = gp(.map(reqsrc), from)
|
|
100
|
+
if isNil(val) {
|
|
101
|
+
val = gp(.map(datasrc), from)
|
|
102
|
+
}
|
|
103
|
+
if !isNil(val) {
|
|
104
|
+
variables.entries[name] = val
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let out = VMap()
|
|
110
|
+
out.entries["query"] = gp(gql, "doc")
|
|
111
|
+
out.entries["variables"] = .map(variables)
|
|
112
|
+
|
|
113
|
+
return .map(out)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Inspect a decoded GraphQL response body and record a failure when the
|
|
117
|
+
/// server reported one. Returns true when an error was recorded.
|
|
118
|
+
///
|
|
119
|
+
/// Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
120
|
+
/// surface has no partial-success concept, and silently returning half an
|
|
121
|
+
/// object would be worse than failing.
|
|
122
|
+
@discardableResult
|
|
123
|
+
func graphqlErrorsUtil(_ ctx: Context) -> Bool {
|
|
124
|
+
guard let result = ctx.result else { return false }
|
|
125
|
+
|
|
126
|
+
guard "graphql" == gp(ctx.point, "kind").asString else { return false }
|
|
127
|
+
|
|
128
|
+
guard let errors = gp(result.body, "errors").asList else { return false }
|
|
129
|
+
if errors.items.isEmpty { return false }
|
|
130
|
+
|
|
131
|
+
let first = errors.items[0]
|
|
132
|
+
var msg = gp(first, "message").asString ?? ""
|
|
133
|
+
if msg.isEmpty { msg = "graphql error" }
|
|
134
|
+
if 1 < errors.items.count {
|
|
135
|
+
msg = msg + " (+\(errors.items.count - 1) more)"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
result.err = ctx.makeError(graphqlErrorCode(first), "graphql: " + msg)
|
|
139
|
+
result.ok = false
|
|
140
|
+
|
|
141
|
+
return true
|
|
142
|
+
}
|