@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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
package KOTLINPACKAGE.utility
|
|
2
|
+
|
|
3
|
+
import KOTLINPACKAGE.core.Context
|
|
4
|
+
import KOTLINPACKAGE.core.Result
|
|
5
|
+
import KOTLINPACKAGE.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
|
+
|
|
22
|
+
// Content type every GraphQL-over-HTTP request uses.
|
|
23
|
+
const val GRAPHQL_CONTENT_TYPE = "application/json"
|
|
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
|
+
fun graphqlErrorCode(gqlerr: Any?): String {
|
|
30
|
+
val ext = Struct.getprop(gqlerr, "extensions")
|
|
31
|
+
|
|
32
|
+
var code = Struct.getprop(ext, "code") as? String
|
|
33
|
+
if (code.isNullOrEmpty()) {
|
|
34
|
+
code = Struct.getprop(ext, "type") as? String
|
|
35
|
+
}
|
|
36
|
+
val raw = (code ?: "").uppercase()
|
|
37
|
+
|
|
38
|
+
if (raw.contains("AUTH") || raw.contains("FORBIDDEN") ||
|
|
39
|
+
raw.contains("UNAUTHENTICATED")
|
|
40
|
+
) {
|
|
41
|
+
return "request_auth"
|
|
42
|
+
}
|
|
43
|
+
if (raw.contains("RATELIMIT") || raw.contains("RATE_LIMIT") ||
|
|
44
|
+
raw.contains("TOO_MANY")
|
|
45
|
+
) {
|
|
46
|
+
return "request_ratelimit"
|
|
47
|
+
}
|
|
48
|
+
if (raw.contains("BAD_USER_INPUT") || raw.contains("VALIDATION") ||
|
|
49
|
+
raw.contains("INVALID")
|
|
50
|
+
) {
|
|
51
|
+
return "request_invalid"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return "request_graphql"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Build the request body for a GraphQL point.
|
|
58
|
+
//
|
|
59
|
+
// Variables come from the op's own arguments: a named variable binds to the
|
|
60
|
+
// like-named argument (`from`), and the input-object variable (empty
|
|
61
|
+
// `from`) takes the request data as a whole — which is what makes a
|
|
62
|
+
// generated create/update call look exactly like its REST equivalent.
|
|
63
|
+
@Suppress("UNCHECKED_CAST")
|
|
64
|
+
fun graphqlBody(ctx: Context): Any? {
|
|
65
|
+
val gql = Struct.getprop(ctx.point, "graphql") as? Map<String, Any?>
|
|
66
|
+
?: return null
|
|
67
|
+
|
|
68
|
+
// reqmatch/reqdata hold the caller's arguments for THIS call; data/match
|
|
69
|
+
// hold the entity's current state. Which pair depends on whether the op
|
|
70
|
+
// takes match or data input. A named variable falls back to the current
|
|
71
|
+
// state, so updating a loaded entity with just {title} still binds the
|
|
72
|
+
// stored id the mutation requires.
|
|
73
|
+
val datainput = null != ctx.op && "data" == ctx.op!!.input
|
|
74
|
+
val src = (if (datainput) ctx.reqdata else ctx.reqmatch) ?: mutableMapOf()
|
|
75
|
+
val statesrc = (if (datainput) ctx.data else ctx.match) ?: mutableMapOf()
|
|
76
|
+
|
|
77
|
+
val variables = mutableMapOf<String, Any?>()
|
|
78
|
+
|
|
79
|
+
val varlist = Struct.getprop(gql, "vars") as? List<Any?> ?: emptyList()
|
|
80
|
+
|
|
81
|
+
for (entry in varlist) {
|
|
82
|
+
val spec = entry as? Map<String, Any?> ?: continue
|
|
83
|
+
|
|
84
|
+
val name = Struct.getprop(spec, "name") as? String ?: continue
|
|
85
|
+
val from = Struct.getprop(spec, "from") as? String
|
|
86
|
+
|
|
87
|
+
if (from.isNullOrEmpty()) {
|
|
88
|
+
// The input object IS the request body. Strip the action selector,
|
|
89
|
+
// which is an SDK-side point discriminator, not an API field.
|
|
90
|
+
val body = mutableMapOf<String, Any?>()
|
|
91
|
+
for ((k, v) in src) {
|
|
92
|
+
if ("\$action" != k) {
|
|
93
|
+
body[k] = v
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
variables[name] = body
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Only send variables the caller actually supplied: sending an explicit
|
|
101
|
+
// null would clear a field on many APIs.
|
|
102
|
+
val value = Struct.getprop(src, from) ?: Struct.getprop(statesrc, from)
|
|
103
|
+
if (null != value) {
|
|
104
|
+
variables[name] = value
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return mutableMapOf<String, Any?>(
|
|
109
|
+
"query" to Struct.getprop(gql, "doc"),
|
|
110
|
+
"variables" to variables,
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Inspect a decoded GraphQL response body and record a failure when the
|
|
115
|
+
// server reported one. Returns true when an error was recorded.
|
|
116
|
+
//
|
|
117
|
+
// Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
118
|
+
// surface has no partial-success concept, and silently returning half an
|
|
119
|
+
// object would be worse than failing.
|
|
120
|
+
fun graphqlErrors(ctx: Context): Boolean {
|
|
121
|
+
val result = ctx.result ?: return false
|
|
122
|
+
if (null == ctx.point) return false
|
|
123
|
+
|
|
124
|
+
if ("graphql" != Struct.getprop(ctx.point, "kind") as? String) {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
val errors = Struct.getprop(result.body, "errors") as? List<Any?>
|
|
129
|
+
?: return false
|
|
130
|
+
if (errors.isEmpty()) return false
|
|
131
|
+
|
|
132
|
+
val first = errors[0]
|
|
133
|
+
var msg = Struct.getprop(first, "message") as? String
|
|
134
|
+
if (msg.isNullOrEmpty()) {
|
|
135
|
+
msg = "graphql error"
|
|
136
|
+
}
|
|
137
|
+
if (1 < errors.size) {
|
|
138
|
+
msg = msg + " (+" + (errors.size - 1) + " more)"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
result.err = ctx.makeError(graphqlErrorCode(first), "graphql: " + msg)
|
|
142
|
+
result.ok = false
|
|
143
|
+
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
@@ -63,7 +63,7 @@ fun makeOptions(ctx: Context): MutableMap<String, Any?> {
|
|
|
63
63
|
"\"headers\": { \"`\$CHILD`\": \"`\$STRING`\" }," +
|
|
64
64
|
"\"allow\": {" +
|
|
65
65
|
" \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\"," +
|
|
66
|
-
" \"op\": \"create,update,load,list,remove,command,direct\"" +
|
|
66
|
+
" \"op\": \"create,update,load,list,remove,command,direct,graphql\"" +
|
|
67
67
|
"}," +
|
|
68
68
|
"\"entity\": { \"`\$CHILD`\": {" +
|
|
69
69
|
" \"`\$OPEN`\": true, \"active\": false, \"alias\": {} } }," +
|
|
@@ -90,6 +90,12 @@ fun makeResponse(ctx: Context): Response {
|
|
|
90
90
|
utility.resultBasic(ctx)
|
|
91
91
|
utility.resultHeaders(ctx)
|
|
92
92
|
utility.resultBody(ctx)
|
|
93
|
+
|
|
94
|
+
// GraphQL reports failures as a top-level `errors` array under HTTP 200,
|
|
95
|
+
// so resultBasic's status check never sees them. Lift them here, before
|
|
96
|
+
// the response transform tries to unwrap data that is not there.
|
|
97
|
+
utility.graphqlErrors(ctx)
|
|
98
|
+
|
|
93
99
|
utility.transformResponse(ctx)
|
|
94
100
|
|
|
95
101
|
if (result.err == null) {
|
|
@@ -49,8 +49,23 @@ fun makeSpec(ctx: Context): Spec {
|
|
|
49
49
|
spec.params = utility.prepareParams(ctx)
|
|
50
50
|
spec.query = utility.prepareQuery(ctx)
|
|
51
51
|
spec.headers = utility.prepareHeaders(ctx)
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
|
|
53
|
+
if ("graphql" == Struct.getprop(ctx.point, "kind") as? String) {
|
|
54
|
+
// GraphQL addresses one endpoint: no path parts, no query string, and
|
|
55
|
+
// the body carries the operation. prepareBody is skipped deliberately
|
|
56
|
+
// — it only emits a body for data-input ops, whereas every GraphQL op
|
|
57
|
+
// posts one, including load/list/remove.
|
|
58
|
+
spec.body = utility.graphqlBody(ctx)
|
|
59
|
+
spec.path = ""
|
|
60
|
+
// prepareQuery already copied the op's match arguments into the query
|
|
61
|
+
// string. Those same values are bound as operation variables, so
|
|
62
|
+
// leaving them would send /graphql?id=i1.
|
|
63
|
+
spec.query = mutableMapOf()
|
|
64
|
+
spec.headers["content-type"] = GRAPHQL_CONTENT_TYPE
|
|
65
|
+
} else {
|
|
66
|
+
spec.body = utility.prepareBody(ctx)
|
|
67
|
+
spec.path = utility.preparePath(ctx)
|
|
68
|
+
}
|
|
54
69
|
|
|
55
70
|
if (ctx.ctrl.explain != null) {
|
|
56
71
|
ctx.ctrl.explain!!["spec"] = spec
|
|
@@ -30,6 +30,8 @@ object Register {
|
|
|
30
30
|
u.prepareParams = ::prepareParams
|
|
31
31
|
u.preparePath = ::preparePath
|
|
32
32
|
u.prepareQuery = ::prepareQuery
|
|
33
|
+
u.graphqlBody = ::graphqlBody
|
|
34
|
+
u.graphqlErrors = ::graphqlErrors
|
|
33
35
|
u.resultBasic = ::resultBasic
|
|
34
36
|
u.resultBody = ::resultBody
|
|
35
37
|
u.resultHeaders = ::resultHeaders
|
|
@@ -328,6 +328,122 @@ def prepareBody (ctx : Value) : SIO Value := do
|
|
|
328
328
|
if opInputOf (← opnameOf ctx) == "data" then transformRequest ctx
|
|
329
329
|
else pure .noval
|
|
330
330
|
|
|
331
|
+
-- ---------------------------------------------------------------------------
|
|
332
|
+
-- graphql (transport)
|
|
333
|
+
--
|
|
334
|
+
-- GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
|
|
335
|
+
-- produces uses this code unchanged. The API-specific part — which operations
|
|
336
|
+
-- exist and what each one's document is — is model data, computed once by
|
|
337
|
+
-- apidef and emitted into Config.
|
|
338
|
+
--
|
|
339
|
+
-- Two jobs:
|
|
340
|
+
--
|
|
341
|
+
-- graphqlBody — build { query, variables } for a point, binding the op's
|
|
342
|
+
-- arguments to the document's declared variables.
|
|
343
|
+
--
|
|
344
|
+
-- graphqlErrors — lift a GraphQL failure into an SDK error. GraphQL reports
|
|
345
|
+
-- failures as a top-level `errors` array under HTTP 200, so
|
|
346
|
+
-- the status-driven path in resultBasic never sees them.
|
|
347
|
+
-- ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
/-- Content type every GraphQL-over-HTTP request uses. -/
|
|
350
|
+
def graphqlContentType : String := "application/json"
|
|
351
|
+
|
|
352
|
+
private def hasSub (hay needle : String) : Bool :=
|
|
353
|
+
1 < (hay.splitOn needle).length
|
|
354
|
+
|
|
355
|
+
/-- Map a GraphQL error to the same error codes the HTTP path produces, so a
|
|
356
|
+
caller handles auth or rate limiting identically on both transports. Servers
|
|
357
|
+
put the machine-readable code in `extensions.code`; Linear-style APIs use
|
|
358
|
+
`extensions.type`. -/
|
|
359
|
+
def graphqlErrorCode (gqlerr : Value) : SIO String := do
|
|
360
|
+
let ext ← gp gqlerr "extensions"
|
|
361
|
+
let c ← gpS ext "code"
|
|
362
|
+
let code ← if c == "" then gpS ext "type" else pure c
|
|
363
|
+
let raw := code.toUpper
|
|
364
|
+
if hasSub raw "AUTH" || hasSub raw "FORBIDDEN" || hasSub raw "UNAUTHENTICATED" then
|
|
365
|
+
pure "request_auth"
|
|
366
|
+
else if hasSub raw "RATELIMIT" || hasSub raw "RATE_LIMIT" || hasSub raw "TOO_MANY" then
|
|
367
|
+
pure "request_ratelimit"
|
|
368
|
+
else if hasSub raw "BAD_USER_INPUT" || hasSub raw "VALIDATION" || hasSub raw "INVALID" then
|
|
369
|
+
pure "request_invalid"
|
|
370
|
+
else
|
|
371
|
+
pure "request_graphql"
|
|
372
|
+
|
|
373
|
+
/-- Build the request body for a GraphQL point.
|
|
374
|
+
|
|
375
|
+
Variables come from the op's own arguments: a named variable binds to the
|
|
376
|
+
like-named argument (`from`), and the input-object variable (empty `from`)
|
|
377
|
+
takes the request data as a whole — which is what makes a generated
|
|
378
|
+
create/update call look exactly like its REST equivalent. -/
|
|
379
|
+
def graphqlBody (ctx : Value) : SIO Value := do
|
|
380
|
+
let gql ← gp (← gp ctx "point") "graphql"
|
|
381
|
+
match gql with
|
|
382
|
+
| .map _ => do
|
|
383
|
+
-- reqmatch/reqdata hold the caller's arguments for THIS call; data/match
|
|
384
|
+
-- hold the entity's current state. Which pair depends on whether the op
|
|
385
|
+
-- takes match or data input. A named variable falls back to the current
|
|
386
|
+
-- state, so updating a loaded entity with just {title} still binds the
|
|
387
|
+
-- stored id the mutation requires.
|
|
388
|
+
let datainput := opInputOf (← opnameOf ctx) == "data"
|
|
389
|
+
let reqsrc ← asMap (← gp ctx (if datainput then "reqdata" else "reqmatch"))
|
|
390
|
+
let datasrc ← asMap (← gp ctx (if datainput then "data" else "match"))
|
|
391
|
+
let variables ← emptyMap
|
|
392
|
+
let varlist ← match (← gp gql "vars") with
|
|
393
|
+
| .list i => listItems i
|
|
394
|
+
| _ => pure #[]
|
|
395
|
+
for spec in varlist do
|
|
396
|
+
match spec with
|
|
397
|
+
| .map _ => do
|
|
398
|
+
let name ← gpS spec "name"
|
|
399
|
+
let from ← gpS spec "from"
|
|
400
|
+
if name != "" then
|
|
401
|
+
if from == "" then do
|
|
402
|
+
-- The input object IS the request body. Strip the action
|
|
403
|
+
-- selector, which is an SDK-side point discriminator, not an API
|
|
404
|
+
-- field.
|
|
405
|
+
let body ← emptyMap
|
|
406
|
+
for k in (← keysof reqsrc) do
|
|
407
|
+
if k != "$action" then sp body k (← gp reqsrc k)
|
|
408
|
+
sp variables name body
|
|
409
|
+
else do
|
|
410
|
+
-- Only send variables the caller actually supplied: sending an
|
|
411
|
+
-- explicit null would clear a field on many APIs.
|
|
412
|
+
let v0 ← gp reqsrc from
|
|
413
|
+
let v ← if isNullish v0 then gp datasrc from else pure v0
|
|
414
|
+
if !(isNullish v) then sp variables name v
|
|
415
|
+
| _ => pure ()
|
|
416
|
+
newMap #[("query", ← gp gql "doc"), ("variables", variables)]
|
|
417
|
+
| _ => pure .noval
|
|
418
|
+
|
|
419
|
+
/-- Inspect a decoded GraphQL response body and record a failure when the
|
|
420
|
+
server reported one. Returns true when an error was recorded.
|
|
421
|
+
|
|
422
|
+
Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
423
|
+
surface has no partial-success concept, and silently returning half an object
|
|
424
|
+
would be worse than failing. -/
|
|
425
|
+
def graphqlErrors (ctx : Value) : SIO Bool := do
|
|
426
|
+
let resultV ← gp ctx "result"
|
|
427
|
+
match resultV with
|
|
428
|
+
| .map _ => do
|
|
429
|
+
if (← gpS (← gp ctx "point") "kind") != "graphql" then pure false
|
|
430
|
+
else
|
|
431
|
+
let errors ← match (← gp (← gp resultV "body") "errors") with
|
|
432
|
+
| .list i => listItems i
|
|
433
|
+
| _ => pure #[]
|
|
434
|
+
if errors.size == 0 then pure false
|
|
435
|
+
else do
|
|
436
|
+
let first := errors[0]!
|
|
437
|
+
let m0 ← gpS first "message"
|
|
438
|
+
let m1 := if m0 == "" then "graphql error" else m0
|
|
439
|
+
let msg := if 1 < errors.size then
|
|
440
|
+
m1 ++ " (+" ++ toString (errors.size - 1) ++ " more)" else m1
|
|
441
|
+
let e ← mkErr (← graphqlErrorCode first) ("graphql: " ++ msg)
|
|
442
|
+
sp resultV "err" e
|
|
443
|
+
sp resultV "ok" (.bool false)
|
|
444
|
+
pure true
|
|
445
|
+
| _ => pure false
|
|
446
|
+
|
|
331
447
|
-- ---------------------------------------------------------------------------
|
|
332
448
|
-- result assembly
|
|
333
449
|
-- ---------------------------------------------------------------------------
|
|
@@ -404,7 +520,20 @@ def makeSpec (ctx : Value) : SIO (Value × Option Value) := do
|
|
|
404
520
|
sp spec "path" (.str (← preparePath ctx))
|
|
405
521
|
sp spec "params" (← prepareParams ctx)
|
|
406
522
|
sp spec "query" (← prepareQuery ctx)
|
|
407
|
-
|
|
523
|
+
let headers ← prepareHeaders ctx
|
|
524
|
+
sp spec "headers" headers
|
|
525
|
+
if (← gpS point "kind") == "graphql" then do
|
|
526
|
+
-- GraphQL addresses one endpoint: no path parts, no query string, and the
|
|
527
|
+
-- body carries the operation. prepareBody is skipped deliberately — it
|
|
528
|
+
-- only emits a body for data-input ops, whereas every GraphQL op posts
|
|
529
|
+
-- one, including load/list/remove.
|
|
530
|
+
sp spec "body" (← graphqlBody ctx)
|
|
531
|
+
sp spec "path" (.str "")
|
|
532
|
+
-- prepareQuery already copied the op's match arguments into the query
|
|
533
|
+
-- string. Those same values are bound as operation variables, so leaving
|
|
534
|
+
-- them would send /graphql?id=i1.
|
|
535
|
+
sp spec "query" (← emptyMap)
|
|
536
|
+
sp headers "content-type" (.str graphqlContentType)
|
|
408
537
|
pure (spec, none)
|
|
409
538
|
|
|
410
539
|
/-- base/prefix/path/suffix joined, `{param}` substituted, query appended. -/
|
|
@@ -613,6 +742,10 @@ def makeResponse (ctx : Value) : SIO (Value × Option Value) := do
|
|
|
613
742
|
resultBasic ctx
|
|
614
743
|
resultHeaders ctx
|
|
615
744
|
resultBody ctx
|
|
745
|
+
-- GraphQL reports failures as a top-level `errors` array under HTTP 200,
|
|
746
|
+
-- so resultBasic's status check never sees them. Lift them here, before
|
|
747
|
+
-- the response transform tries to unwrap data that is not there.
|
|
748
|
+
let _ ← graphqlErrors ctx
|
|
616
749
|
let _ ← transformResponse ctx
|
|
617
750
|
let errv ← gp resultV "err"
|
|
618
751
|
if !(← isErrV errv) then sp resultV "ok" (.bool true)
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
-- configurable.
|
|
14
14
|
|
|
15
15
|
local BaseFeature = require("feature.base_feature")
|
|
16
|
+
local vs = require("utility.struct.struct")
|
|
16
17
|
|
|
17
18
|
local PagingFeature = {}
|
|
18
19
|
PagingFeature.__index = PagingFeature
|
|
@@ -83,6 +84,14 @@ function PagingFeature:PreRequest(ctx)
|
|
|
83
84
|
paging = ctx.ctrl.paging
|
|
84
85
|
end
|
|
85
86
|
|
|
87
|
+
-- GraphQL paginates through operation VARIABLES, not the query string.
|
|
88
|
+
-- This hook runs after make_spec, so spec.body already holds the
|
|
89
|
+
-- { query, variables } envelope, and before make_fetch_def serialises it.
|
|
90
|
+
if vs.getprop(ctx.point, "kind") == "graphql" then
|
|
91
|
+
self:_graphql_pre_request(ctx, paging)
|
|
92
|
+
return
|
|
93
|
+
end
|
|
94
|
+
|
|
86
95
|
if paging["cursor"] ~= nil then
|
|
87
96
|
spec.query[cursor_param] = paging["cursor"]
|
|
88
97
|
elseif spec.query[page_param] == nil then
|
|
@@ -99,6 +108,47 @@ function PagingFeature:PreRequest(ctx)
|
|
|
99
108
|
end
|
|
100
109
|
|
|
101
110
|
|
|
111
|
+
-- Relay pagination: the cursor is the `after` variable (or whatever the model
|
|
112
|
+
-- named it), and the page size is `first`.
|
|
113
|
+
function PagingFeature:_graphql_pre_request(ctx, paging)
|
|
114
|
+
local body = ctx.spec.body
|
|
115
|
+
if type(body) ~= "table" then
|
|
116
|
+
return
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
local variables = body["variables"]
|
|
120
|
+
if type(variables) ~= "table" then
|
|
121
|
+
variables = {}
|
|
122
|
+
body["variables"] = variables
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
local after_var = self.options["afterVar"] or "after"
|
|
126
|
+
local first_var = self.options["firstVar"] or "first"
|
|
127
|
+
|
|
128
|
+
-- Only bind variables the operation actually declares, or the server
|
|
129
|
+
-- rejects the document.
|
|
130
|
+
local declared = {}
|
|
131
|
+
local varlist = vs.getpath(ctx.point, "graphql.vars")
|
|
132
|
+
if type(varlist) == "table" then
|
|
133
|
+
for _, v in ipairs(varlist) do
|
|
134
|
+
local name = vs.getprop(v, "name")
|
|
135
|
+
if name ~= nil then
|
|
136
|
+
declared[name] = true
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
if paging["cursor"] ~= nil and declared[after_var] then
|
|
142
|
+
variables[after_var] = paging["cursor"]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if self.options["limit"] ~= nil and variables[first_var] == nil
|
|
146
|
+
and declared[first_var] then
|
|
147
|
+
variables[first_var] = self.options["limit"]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
|
|
102
152
|
function PagingFeature:PreResult(ctx)
|
|
103
153
|
if not self.active then
|
|
104
154
|
return
|
|
@@ -131,6 +181,43 @@ function PagingFeature:PreResult(ctx)
|
|
|
131
181
|
end
|
|
132
182
|
end
|
|
133
183
|
|
|
184
|
+
-- Set when the response states hasMore outright, rather than leaving it to
|
|
185
|
+
-- be inferred from the presence of a cursor.
|
|
186
|
+
local explicit_more = false
|
|
187
|
+
|
|
188
|
+
-- Relay connections carry the cursor in pageInfo, at the path the model
|
|
189
|
+
-- recorded for this op.
|
|
190
|
+
local page = vs.getpath(ctx.point, "graphql.page")
|
|
191
|
+
if type(page) == "table" and type(body) == "table" then
|
|
192
|
+
-- `connpath` locates the connection object inside the response envelope
|
|
193
|
+
-- (data.<field>); the cursor/more paths are relative to it.
|
|
194
|
+
local conn = body
|
|
195
|
+
local connpath = page["connpath"]
|
|
196
|
+
if type(connpath) == "string" and connpath ~= "" then
|
|
197
|
+
local sub = vs.getpath(body, connpath)
|
|
198
|
+
if sub ~= nil then
|
|
199
|
+
conn = sub
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
local cursorpath = page["cursor"]
|
|
204
|
+
if type(cursorpath) == "string" and cursorpath ~= "" then
|
|
205
|
+
local cursor = vs.getpath(conn, cursorpath)
|
|
206
|
+
if cursor ~= nil then
|
|
207
|
+
paging.cursor = cursor
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
local morepath = page["more"]
|
|
212
|
+
if type(morepath) == "string" and morepath ~= "" then
|
|
213
|
+
local more = vs.getpath(conn, morepath)
|
|
214
|
+
if type(more) == "boolean" then
|
|
215
|
+
paging.hasMore = more
|
|
216
|
+
explicit_more = true
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
134
221
|
-- Body-level cursors.
|
|
135
222
|
if type(body) == "table" then
|
|
136
223
|
if body["next"] ~= nil and paging.next == nil then
|
|
@@ -144,11 +231,19 @@ function PagingFeature:PreResult(ctx)
|
|
|
144
231
|
end
|
|
145
232
|
if type(body["hasMore"]) == "boolean" then
|
|
146
233
|
paging.hasMore = body["hasMore"]
|
|
234
|
+
explicit_more = true
|
|
147
235
|
end
|
|
148
236
|
end
|
|
149
237
|
|
|
150
|
-
|
|
151
|
-
|
|
238
|
+
-- Cursor presence only INFERS another page. When the server stated the
|
|
239
|
+
-- answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
|
|
240
|
+
-- that wins: a final page normally carries both an end cursor and
|
|
241
|
+
-- hasNextPage false, and inferring from the cursor there would send the
|
|
242
|
+
-- caller back for a page that does not exist, forever.
|
|
243
|
+
if not explicit_more then
|
|
244
|
+
paging.hasMore = paging.hasMore or
|
|
245
|
+
paging.next ~= nil or paging.cursor ~= nil or paging.nextPage ~= nil
|
|
246
|
+
end
|
|
152
247
|
|
|
153
248
|
result.paging = paging
|
|
154
249
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
-- ProjectName SDK utility: graphql
|
|
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
|
+
-- graphql_body — build { query, variables } for a point, binding the
|
|
11
|
+
-- op's arguments to the document's declared variables.
|
|
12
|
+
--
|
|
13
|
+
-- graphql_errors — 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 result_basic
|
|
16
|
+
-- never sees them.
|
|
17
|
+
|
|
18
|
+
local vs = require("utility.struct.struct")
|
|
19
|
+
|
|
20
|
+
local M = {}
|
|
21
|
+
|
|
22
|
+
-- Content type every GraphQL-over-HTTP request uses.
|
|
23
|
+
M.CONTENT_TYPE = "application/json"
|
|
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 M.error_code(gqlerr)
|
|
30
|
+
local ext = vs.getprop(gqlerr, "extensions")
|
|
31
|
+
|
|
32
|
+
local raw = vs.getprop(ext, "code")
|
|
33
|
+
if raw == nil or raw == "" then
|
|
34
|
+
raw = vs.getprop(ext, "type")
|
|
35
|
+
end
|
|
36
|
+
raw = type(raw) == "string" and raw:upper() or ""
|
|
37
|
+
|
|
38
|
+
if raw:find("AUTH", 1, true) or raw:find("FORBIDDEN", 1, true)
|
|
39
|
+
or raw:find("UNAUTHENTICATED", 1, true) then
|
|
40
|
+
return "request_auth"
|
|
41
|
+
end
|
|
42
|
+
if raw:find("RATELIMIT", 1, true) or raw:find("RATE_LIMIT", 1, true)
|
|
43
|
+
or raw:find("TOO_MANY", 1, true) then
|
|
44
|
+
return "request_ratelimit"
|
|
45
|
+
end
|
|
46
|
+
if raw:find("BAD_USER_INPUT", 1, true) or raw:find("VALIDATION", 1, true)
|
|
47
|
+
or raw:find("INVALID", 1, true) then
|
|
48
|
+
return "request_invalid"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return "request_graphql"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
-- Build the request body for a GraphQL point.
|
|
55
|
+
--
|
|
56
|
+
-- Variables come from the op's own arguments: a named variable binds to the
|
|
57
|
+
-- like-named argument (`from`), and the input-object variable (empty
|
|
58
|
+
-- `from`) takes the request data as a whole — which is what makes a
|
|
59
|
+
-- generated create/update call look exactly like its REST equivalent.
|
|
60
|
+
function M.body(ctx)
|
|
61
|
+
local gql = vs.getprop(ctx.point, "graphql")
|
|
62
|
+
if type(gql) ~= "table" then
|
|
63
|
+
return nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
-- reqmatch/reqdata hold the caller's arguments for THIS call; data/match
|
|
67
|
+
-- hold the entity's current state. Which pair depends on whether the op
|
|
68
|
+
-- takes match or data input. A named variable falls back to the current
|
|
69
|
+
-- state, so updating a loaded entity with just {title} still binds the
|
|
70
|
+
-- stored id the mutation requires.
|
|
71
|
+
local reqsrc = ctx.reqmatch
|
|
72
|
+
local datasrc = ctx.match
|
|
73
|
+
if ctx.op ~= nil and ctx.op.input == "data" then
|
|
74
|
+
reqsrc = ctx.reqdata
|
|
75
|
+
datasrc = ctx.data
|
|
76
|
+
end
|
|
77
|
+
if type(reqsrc) ~= "table" then
|
|
78
|
+
reqsrc = {}
|
|
79
|
+
end
|
|
80
|
+
if type(datasrc) ~= "table" then
|
|
81
|
+
datasrc = {}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
local variables = {}
|
|
85
|
+
|
|
86
|
+
local varlist = vs.getprop(gql, "vars")
|
|
87
|
+
if type(varlist) == "table" then
|
|
88
|
+
for _, spec in ipairs(varlist) do
|
|
89
|
+
if type(spec) == "table" then
|
|
90
|
+
local name = vs.getprop(spec, "name")
|
|
91
|
+
local from = vs.getprop(spec, "from")
|
|
92
|
+
|
|
93
|
+
if name ~= nil then
|
|
94
|
+
if from == nil or from == "" then
|
|
95
|
+
-- The input object IS the request body. Strip the action
|
|
96
|
+
-- selector, which is an SDK-side point discriminator, not an
|
|
97
|
+
-- API field.
|
|
98
|
+
local body = {}
|
|
99
|
+
for k, v in pairs(reqsrc) do
|
|
100
|
+
if k ~= "$action" then
|
|
101
|
+
body[k] = v
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
variables[name] = body
|
|
105
|
+
else
|
|
106
|
+
-- Only send variables the caller actually supplied: sending an
|
|
107
|
+
-- explicit null would clear a field on many APIs.
|
|
108
|
+
local val = vs.getprop(reqsrc, from)
|
|
109
|
+
if val == nil then
|
|
110
|
+
val = vs.getprop(datasrc, from)
|
|
111
|
+
end
|
|
112
|
+
if val ~= nil then
|
|
113
|
+
variables[name] = val
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
query = vs.getprop(gql, "doc"),
|
|
123
|
+
variables = variables,
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
-- Inspect a decoded GraphQL response body and record a failure when the
|
|
128
|
+
-- server reported one. Returns true when an error was recorded.
|
|
129
|
+
--
|
|
130
|
+
-- Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
131
|
+
-- surface has no partial-success concept, and silently returning half an
|
|
132
|
+
-- object would be worse than failing.
|
|
133
|
+
function M.errors(ctx)
|
|
134
|
+
local result = ctx.result
|
|
135
|
+
if result == nil or ctx.point == nil then
|
|
136
|
+
return false
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if vs.getprop(ctx.point, "kind") ~= "graphql" then
|
|
140
|
+
return false
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
local errors = vs.getprop(result.body, "errors")
|
|
144
|
+
if type(errors) ~= "table" or #errors == 0 then
|
|
145
|
+
return false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
local first = errors[1]
|
|
149
|
+
local msg = vs.getprop(first, "message")
|
|
150
|
+
if type(msg) ~= "string" or msg == "" then
|
|
151
|
+
msg = "graphql error"
|
|
152
|
+
end
|
|
153
|
+
if 1 < #errors then
|
|
154
|
+
msg = msg .. " (+" .. tostring(#errors - 1) .. " more)"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
result.err = ctx:make_error(M.error_code(first), "graphql: " .. msg)
|
|
158
|
+
result.ok = false
|
|
159
|
+
|
|
160
|
+
return true
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
return M
|
|
@@ -66,7 +66,7 @@ local function make_options_util(ctx)
|
|
|
66
66
|
},
|
|
67
67
|
allow = {
|
|
68
68
|
method = "GET,PUT,POST,PATCH,DELETE,OPTIONS",
|
|
69
|
-
op = "create,update,load,list,remove,command,direct",
|
|
69
|
+
op = "create,update,load,list,remove,command,direct,graphql",
|
|
70
70
|
},
|
|
71
71
|
entity = {
|
|
72
72
|
["`$CHILD`"] = {
|
|
@@ -28,6 +28,12 @@ local function make_response_util(ctx)
|
|
|
28
28
|
utility.result_basic(ctx)
|
|
29
29
|
utility.result_headers(ctx)
|
|
30
30
|
utility.result_body(ctx)
|
|
31
|
+
|
|
32
|
+
-- GraphQL reports failures as a top-level `errors` array under HTTP 200,
|
|
33
|
+
-- so result_basic's status check never sees them. Lift them here, before
|
|
34
|
+
-- the response transform tries to unwrap data that is not there.
|
|
35
|
+
utility.graphql_errors(ctx)
|
|
36
|
+
|
|
31
37
|
utility.transform_response(ctx)
|
|
32
38
|
|
|
33
39
|
if result.err == nil then
|