@voxgig/sdkgen 1.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmp/AgentGuideContent.js +5 -0
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +13 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/helpers/canonType.d.ts +3 -1
- package/dist/helpers/canonType.js +44 -1
- package/dist/helpers/canonType.js.map +1 -1
- package/dist/helpers/packageMeta.js +7 -0
- package/dist/helpers/packageMeta.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -3
- package/dist/sdkgen.js +4 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/project/.sdk/model/feature/paging.aontu +7 -0
- package/project/.sdk/model/target/py-data.aontu +64 -0
- package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +89 -1
- package/project/.sdk/src/cmp/clojure/Main_clojure.ts +1 -0
- package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +97 -0
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +78 -0
- package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +84 -0
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +87 -0
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +75 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +85 -0
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +4 -0
- package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +80 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +86 -0
- package/project/.sdk/src/cmp/py/Entity_py.ts +9 -2
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +1 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +34 -6
- package/project/.sdk/src/cmp/py/Package_py.ts +6 -9
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +2 -2
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +3 -3
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +75 -8
- package/project/.sdk/src/cmp/py-data/Main_py-data.ts +987 -0
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +71 -0
- package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +95 -0
- package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +86 -0
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +75 -0
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +95 -0
- package/project/.sdk/tm/c/core/sdk.h +7 -0
- package/project/.sdk/tm/c/feature/paging.c +141 -2
- package/project/.sdk/tm/c/utility/graphql.c +178 -0
- package/project/.sdk/tm/c/utility/make_options.c +1 -1
- package/project/.sdk/tm/c/utility/make_response.c +6 -0
- package/project/.sdk/tm/c/utility/make_spec.c +23 -3
- package/project/.sdk/tm/clojure/src/sdk/client.clj +64 -1
- package/project/.sdk/tm/clojure/src/sdk/core.clj +126 -3
- package/project/.sdk/tm/clojure/src/sdk/features.clj +80 -14
- package/project/.sdk/tm/cpp/core/types.hpp +83 -0
- package/project/.sdk/tm/cpp/feature/paging.hpp +103 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +152 -3
- package/project/.sdk/tm/csharp/core/Utility.cs +4 -0
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +118 -1
- package/project/.sdk/tm/csharp/utility/Graphql.cs +190 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +1 -1
- package/project/.sdk/tm/csharp/utility/MakeResponse.cs +7 -0
- package/project/.sdk/tm/csharp/utility/MakeSpec.cs +21 -2
- package/project/.sdk/tm/csharp/utility/Register.cs +2 -0
- package/project/.sdk/tm/dart/lib/Point.dart +7 -0
- package/project/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +99 -4
- package/project/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +1 -1
- package/project/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +5 -0
- package/project/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +18 -2
- package/project/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +6 -1
- package/project/.sdk/tm/dart/lib/utility/Utility.dart +9 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +136 -13
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +187 -3
- package/project/.sdk/tm/go/core/utility_type.go +2 -0
- package/project/.sdk/tm/go/feature/paging_feature.go +87 -1
- package/project/.sdk/tm/go/utility/graphql.go +168 -0
- package/project/.sdk/tm/go/utility/make_options.go +1 -1
- package/project/.sdk/tm/go/utility/make_response.go +6 -0
- package/project/.sdk/tm/go/utility/make_spec.go +19 -2
- package/project/.sdk/tm/go/utility/register.go +2 -0
- package/project/.sdk/tm/haskell/src/SdkFeatures.hs +146 -7
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +153 -3
- package/project/.sdk/tm/java/core/SdkClient.java +93 -0
- package/project/.sdk/tm/java/core/Utility.java +4 -0
- package/project/.sdk/tm/java/feature/PagingFeature.java +100 -1
- package/project/.sdk/tm/java/utility/Graphql.java +191 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +1 -1
- package/project/.sdk/tm/java/utility/MakeResponse.java +6 -0
- package/project/.sdk/tm/java/utility/MakeSpec.java +17 -2
- package/project/.sdk/tm/java/utility/Register.java +2 -0
- package/project/.sdk/tm/js/src/Point.js +5 -0
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +82 -3
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +14 -2
- package/project/.sdk/tm/js/src/utility/GraphqlUtility.js +148 -0
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +1 -1
- package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +7 -0
- package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +19 -2
- package/project/.sdk/tm/js/src/utility/Utility.js +4 -0
- package/project/.sdk/tm/kotlin/core/SdkClient.kt +84 -0
- package/project/.sdk/tm/kotlin/core/Utility.kt +2 -0
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +92 -1
- package/project/.sdk/tm/kotlin/utility/Graphql.kt +145 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/MakeReqRes.kt +6 -0
- package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +17 -2
- package/project/.sdk/tm/kotlin/utility/Register.kt +2 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +134 -1
- package/project/.sdk/tm/lua/feature/paging_feature.lua +97 -2
- package/project/.sdk/tm/lua/utility/graphql.lua +163 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +1 -1
- package/project/.sdk/tm/lua/utility/make_response.lua +6 -0
- package/project/.sdk/tm/lua/utility/make_spec.lua +18 -2
- package/project/.sdk/tm/lua/utility/register.lua +3 -0
- package/project/.sdk/tm/ocaml/sdk_features.ml +164 -20
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +143 -4
- package/project/.sdk/tm/ocaml/sdk_types.ml +2 -0
- package/project/.sdk/tm/perl/feature/paging_feature.pm +91 -3
- package/project/.sdk/tm/perl/utility/graphql.pm +150 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +1 -1
- package/project/.sdk/tm/perl/utility/make_response.pm +6 -0
- package/project/.sdk/tm/perl/utility/make_spec.pm +20 -2
- package/project/.sdk/tm/perl/utility/register.pm +1 -0
- package/project/.sdk/tm/php/core/UtilityType.php +2 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +106 -4
- package/project/.sdk/tm/php/utility/Graphql.php +176 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +1 -1
- package/project/.sdk/tm/php/utility/MakeResponse.php +7 -0
- package/project/.sdk/tm/php/utility/MakeSpec.php +20 -2
- package/project/.sdk/tm/php/utility/Register.php +3 -0
- package/project/.sdk/tm/py/pkg/core/__init__.py +0 -0
- package/project/.sdk/tm/py/{core → pkg/core}/context.py +8 -8
- package/project/.sdk/tm/py/{core → pkg/core}/operation.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/response.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/result.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/utility_type.py +2 -0
- package/project/.sdk/tm/py/pkg/entity/__init__.py +0 -0
- package/project/.sdk/tm/py/pkg/feature/__init__.py +0 -0
- package/project/.sdk/tm/py/{feature → pkg/feature}/audit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/cache_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/clienttrack_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/debug_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/idempotency_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/log_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/metrics_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/netsim_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/paging_feature.py +87 -6
- package/project/.sdk/tm/py/{feature → pkg/feature}/proxy_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/ratelimit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/rbac_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/retry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/streaming_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/telemetry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/test_feature.py +2 -2
- package/project/.sdk/tm/py/{feature → pkg/feature}/timeout_feature.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/__init__.py +0 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/feature_init.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/fetcher.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/graphql.py +145 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_context.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_error.py +5 -5
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_fetch_def.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_options.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_point.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_response.py +6 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_spec.py +19 -4
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_url.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/param.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_auth.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_headers.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_method.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_params.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_path.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_query.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/register.py +71 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_response.py +2 -2
- package/project/.sdk/tm/py/test/feature_harness.py +6 -6
- package/project/.sdk/tm/py/test/runner.py +1 -1
- package/project/.sdk/tm/py/test/struct_runner.py +1 -1
- package/project/.sdk/tm/py/test/test_pipeline.py +5 -5
- package/project/.sdk/tm/py/test/test_primary_utility.py +8 -8
- package/project/.sdk/tm/py/test/test_struct_utility.py +3 -3
- package/project/.sdk/tm/py-data/LICENSE +22 -0
- package/project/.sdk/tm/py-data/Makefile +57 -0
- package/project/.sdk/tm/py-data/pkg/auth.py +131 -0
- package/project/.sdk/tm/py-data/pkg/fetch.py +178 -0
- package/project/.sdk/tm/py-data/pkg/frames.py +236 -0
- package/project/.sdk/tm/py-data/src/feature/README.md +8 -0
- package/project/.sdk/tm/py-data/src/feature/debug/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/idempotency/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/metrics/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/netsim/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/paging/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/proxy/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/ratelimit/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/rbac/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/retry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/streaming/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/telemetry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/timeout/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/test/__init__.py +0 -0
- package/project/.sdk/tm/py-data/test/test_auth.py +95 -0
- package/project/.sdk/tm/py-data/test/test_fetch.py +152 -0
- package/project/.sdk/tm/py-data/test/test_frames.py +250 -0
- package/project/.sdk/tm/rb/core/utility_type.rb +1 -0
- package/project/.sdk/tm/rb/feature/paging_feature.rb +89 -3
- package/project/.sdk/tm/rb/utility/graphql.rb +131 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +1 -1
- package/project/.sdk/tm/rb/utility/make_response.rb +6 -0
- package/project/.sdk/tm/rb/utility/make_spec.rb +18 -2
- package/project/.sdk/tm/rb/utility/register.rb +3 -0
- package/project/.sdk/tm/rust/feature/paging.rs +124 -2
- package/project/.sdk/tm/rust/utility/graphql.rs +193 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/rust/utility/make_response.rs +6 -0
- package/project/.sdk/tm/rust/utility/make_spec.rs +32 -4
- package/project/.sdk/tm/rust/utility/mod.rs +1 -0
- package/project/.sdk/tm/scala/core/SdkClient.scala +90 -0
- package/project/.sdk/tm/scala/core/Utility.scala +4 -0
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +104 -3
- package/project/.sdk/tm/scala/utility/Graphql.scala +160 -0
- package/project/.sdk/tm/scala/utility/Make.scala +7 -1
- package/project/.sdk/tm/scala/utility/MakeSpec.scala +22 -2
- package/project/.sdk/tm/scala/utility/Register.scala +2 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Utility.swift +4 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +86 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Graphql.swift +142 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +24 -2
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Register.swift +2 -0
- package/project/.sdk/tm/ts/src/Point.ts +7 -0
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +82 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +14 -2
- package/project/.sdk/tm/ts/src/utility/GraphqlUtility.ts +152 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +1 -1
- package/project/.sdk/tm/ts/src/utility/MakeResponseUtility.ts +7 -0
- package/project/.sdk/tm/ts/src/utility/MakeSpecUtility.ts +19 -2
- package/project/.sdk/tm/ts/src/utility/Utility.ts +4 -0
- package/project/.sdk/tm/zig/core/utility.zig +198 -3
- package/project/.sdk/tm/zig/feature/paging.zig +128 -1
- package/src/cmp/AgentGuideContent.ts +5 -0
- package/src/cmp/Feature.ts +13 -0
- package/src/helpers/canonType.ts +46 -0
- package/src/helpers/packageMeta.ts +7 -0
- package/src/sdkgen.ts +6 -1
- package/project/.sdk/tm/py/utility/register.py +0 -68
- /package/project/.sdk/tm/py/{core → pkg/core}/control.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/error.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/helpers.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/spec.py +0 -0
- /package/project/.sdk/tm/py/{feature → pkg/feature}/base_feature.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/clean.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/done.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_add.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_hook.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/make_result.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_basic.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_headers.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/__init__.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/voxgig_struct.py +0 -0
- /package/project/.sdk/tm/{py/core/__init__.py → py-data/src/feature/audit/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/entity/__init__.py → py-data/src/feature/base/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/feature/__init__.py → py-data/src/feature/cache/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/utility/__init__.py → py-data/src/feature/clienttrack/.gitkeep} +0 -0
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
#include <ctype.h>
|
|
14
14
|
#include <stdlib.h>
|
|
15
|
+
#include <stdio.h>
|
|
15
16
|
#include <string.h>
|
|
16
17
|
|
|
17
18
|
typedef struct {
|
|
@@ -106,6 +107,93 @@ static void paging_init(Feature* f, Context* ctx, voxgig_value* options) {
|
|
|
106
107
|
pf->active = fopt_bool(options, "active", false);
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
// The model records connection/cursor/more as dot paths; getpath_c takes a
|
|
111
|
+
// NULL-terminated segment array. Both the copy and the segment array are
|
|
112
|
+
// sized from the path itself: a fixed buffer would silently truncate a long
|
|
113
|
+
// generated path and look up the wrong node, which reads as "no cursor" and
|
|
114
|
+
// stops pagination after one page.
|
|
115
|
+
static voxgig_value* getpath_dotted(voxgig_value* store, const char* path) {
|
|
116
|
+
if (!path || path[0] == '\0') return store;
|
|
117
|
+
|
|
118
|
+
size_t len = strlen(path);
|
|
119
|
+
|
|
120
|
+
// Segment count is bounded by dots + 1, so one allocation covers any path.
|
|
121
|
+
size_t maxseg = 2;
|
|
122
|
+
for (size_t i = 0; i < len; i++) {
|
|
123
|
+
if (path[i] == '.') maxseg++;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
char* buf = (char*)malloc(len + 1);
|
|
127
|
+
const char** keys = (const char**)malloc(maxseg * sizeof(char*));
|
|
128
|
+
if (!buf || !keys) {
|
|
129
|
+
free(buf);
|
|
130
|
+
free(keys);
|
|
131
|
+
return voxgig_new_undef();
|
|
132
|
+
}
|
|
133
|
+
memcpy(buf, path, len + 1);
|
|
134
|
+
|
|
135
|
+
size_t n = 0;
|
|
136
|
+
char* save = NULL;
|
|
137
|
+
for (char* tok = strtok_r(buf, ".", &save); tok;
|
|
138
|
+
tok = strtok_r(NULL, ".", &save)) {
|
|
139
|
+
keys[n++] = tok;
|
|
140
|
+
}
|
|
141
|
+
keys[n] = NULL;
|
|
142
|
+
|
|
143
|
+
voxgig_value* out = getpath_c(store, keys);
|
|
144
|
+
|
|
145
|
+
// getpath_c does not retain the key strings, so both buffers are ours to
|
|
146
|
+
// release once the lookup has returned.
|
|
147
|
+
free(buf);
|
|
148
|
+
free(keys);
|
|
149
|
+
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Relay pagination: the cursor is the `after` variable (or whatever the
|
|
154
|
+
// model named it), and the page size is `first`.
|
|
155
|
+
static void paging_graphql_pre_request(PagingFeature* pf, Context* ctx,
|
|
156
|
+
voxgig_value* paging) {
|
|
157
|
+
voxgig_value* options = pf->options;
|
|
158
|
+
voxgig_value* body = ctx->spec->body;
|
|
159
|
+
if (!voxgig_is_map(body)) return;
|
|
160
|
+
|
|
161
|
+
voxgig_value* variables = getp(body, "variables");
|
|
162
|
+
if (!voxgig_is_map(variables)) {
|
|
163
|
+
variables = voxgig_new_map();
|
|
164
|
+
setp(body, "variables", variables);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const char* after_var = fopt_str(options, "afterVar", "after");
|
|
168
|
+
const char* first_var = fopt_str(options, "firstVar", "first");
|
|
169
|
+
|
|
170
|
+
// Only bind variables the operation actually declares, or the server
|
|
171
|
+
// rejects the document.
|
|
172
|
+
bool after_declared = false, first_declared = false;
|
|
173
|
+
voxgig_value* varlist = getpath2(ctx->point, "graphql", "vars");
|
|
174
|
+
if (voxgig_is_list(varlist)) {
|
|
175
|
+
voxgig_list* vl = voxgig_as_list(varlist);
|
|
176
|
+
for (size_t i = 0; i < vl->len; i++) {
|
|
177
|
+
voxgig_value* nv = getp(vl->items[i], "name");
|
|
178
|
+
if (!voxgig_is_string(nv)) continue;
|
|
179
|
+
const char* n = voxgig_as_string(nv);
|
|
180
|
+
if (strcmp(n, after_var) == 0) after_declared = true;
|
|
181
|
+
if (strcmp(n, first_var) == 0) first_declared = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
voxgig_value* cursor = getp(paging, "cursor");
|
|
186
|
+
if (after_declared && !v_is_noval(cursor) && !v_is_null(cursor)) {
|
|
187
|
+
setp(variables, after_var, v_share(cursor));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
voxgig_value* limit = getp(options, "limit");
|
|
191
|
+
if (first_declared && !v_is_noval(limit) && !v_is_null(limit) &&
|
|
192
|
+
v_is_noval(getp(variables, first_var))) {
|
|
193
|
+
setp(variables, first_var, v_num((double)fopt_int(options, "limit", 0)));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
109
197
|
static void paging_pre_request(PagingFeature* pf, Context* ctx) {
|
|
110
198
|
voxgig_value* options = pf->options;
|
|
111
199
|
if (!pf->active || !is_list_op(options, ctx->op->name)) return;
|
|
@@ -124,6 +212,15 @@ static void paging_pre_request(PagingFeature* pf, Context* ctx) {
|
|
|
124
212
|
// A per-call cursor/page from ctrl takes priority (auto-iteration).
|
|
125
213
|
voxgig_value* paging = ctx->ctrl->paging;
|
|
126
214
|
|
|
215
|
+
// GraphQL paginates through operation VARIABLES, not the query string.
|
|
216
|
+
// This hook runs after make_spec, so spec->body already holds the
|
|
217
|
+
// { query, variables } envelope, and before make_fetch_def serialises it.
|
|
218
|
+
voxgig_value* kind = getp(ctx->point, "kind");
|
|
219
|
+
if (voxgig_is_string(kind) && strcmp(voxgig_as_string(kind), "graphql") == 0) {
|
|
220
|
+
paging_graphql_pre_request(pf, ctx, paging);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
127
224
|
voxgig_value* cursor = getp(paging, "cursor");
|
|
128
225
|
if (!v_is_noval(cursor) && !v_is_null(cursor)) {
|
|
129
226
|
setp(query, cursor_param, cursor);
|
|
@@ -165,6 +262,41 @@ static void paging_pre_result(PagingFeature* pf, Context* ctx) {
|
|
|
165
262
|
}
|
|
166
263
|
}
|
|
167
264
|
|
|
265
|
+
// Set when the response states hasMore outright, rather than leaving it to
|
|
266
|
+
// be inferred from the presence of a cursor.
|
|
267
|
+
bool explicit_more = false;
|
|
268
|
+
|
|
269
|
+
// Relay connections carry the cursor in pageInfo, at the path the model
|
|
270
|
+
// recorded for this op.
|
|
271
|
+
voxgig_value* page = getpath2(ctx->point, "graphql", "page");
|
|
272
|
+
if (voxgig_is_map(page) && voxgig_is_map(body)) {
|
|
273
|
+
// `connpath` locates the connection object inside the response envelope
|
|
274
|
+
// (data.<field>); the cursor/more paths are relative to it.
|
|
275
|
+
voxgig_value* conn = body;
|
|
276
|
+
voxgig_value* cpv = getp(page, "connpath");
|
|
277
|
+
if (voxgig_is_string(cpv) && voxgig_as_string(cpv)[0] != '\0') {
|
|
278
|
+
voxgig_value* sub = getpath_dotted(body, voxgig_as_string(cpv));
|
|
279
|
+
if (!v_is_noval(sub) && !v_is_null(sub)) conn = sub;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
voxgig_value* curv = getp(page, "cursor");
|
|
283
|
+
if (voxgig_is_string(curv) && voxgig_as_string(curv)[0] != '\0') {
|
|
284
|
+
voxgig_value* cursor = getpath_dotted(conn, voxgig_as_string(curv));
|
|
285
|
+
if (!v_is_noval(cursor) && !v_is_null(cursor)) {
|
|
286
|
+
setp(paging, "cursor", v_share(cursor));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
voxgig_value* morev = getp(page, "more");
|
|
291
|
+
if (voxgig_is_string(morev) && voxgig_as_string(morev)[0] != '\0') {
|
|
292
|
+
voxgig_value* mv = getpath_dotted(conn, voxgig_as_string(morev));
|
|
293
|
+
if (voxgig_is_bool(mv)) {
|
|
294
|
+
setp(paging, "hasMore", v_bool(voxgig_as_bool(mv)));
|
|
295
|
+
explicit_more = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
168
300
|
// Body-level cursors.
|
|
169
301
|
if (voxgig_is_map(body)) {
|
|
170
302
|
voxgig_value* next = getp(body, "next");
|
|
@@ -182,14 +314,21 @@ static void paging_pre_result(PagingFeature* pf, Context* ctx) {
|
|
|
182
314
|
bool has_more;
|
|
183
315
|
if (get_bool(body, "hasMore", &has_more)) {
|
|
184
316
|
setp(paging, "hasMore", v_bool(has_more));
|
|
317
|
+
explicit_more = true;
|
|
185
318
|
}
|
|
186
319
|
}
|
|
187
320
|
|
|
321
|
+
// Cursor presence only INFERS another page. When the server stated the
|
|
322
|
+
// answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
|
|
323
|
+
// that wins: a final page normally carries both an end cursor and
|
|
324
|
+
// hasNextPage false, and inferring from the cursor there would send the
|
|
325
|
+
// caller back for a page that does not exist, forever.
|
|
188
326
|
bool hm = false;
|
|
189
327
|
bool has = get_bool(paging, "hasMore", &hm);
|
|
190
328
|
bool is_true = has && hm;
|
|
191
|
-
if (!
|
|
192
|
-
|
|
329
|
+
if (!explicit_more && !is_true &&
|
|
330
|
+
(!v_is_noval(getp(paging, "next")) || !v_is_noval(getp(paging, "cursor")) ||
|
|
331
|
+
!v_is_noval(getp(paging, "nextPage")))) {
|
|
193
332
|
setp(paging, "hasMore", v_bool(true));
|
|
194
333
|
}
|
|
195
334
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// graphql utility (mirrors utility/GraphqlUtility.ts).
|
|
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_util — build { query, variables } for a point, binding
|
|
11
|
+
// the op's arguments to the document's declared
|
|
12
|
+
// variables.
|
|
13
|
+
//
|
|
14
|
+
// graphql_errors_util — lift a GraphQL failure into an SDK error.
|
|
15
|
+
// GraphQL reports failures as a top-level `errors`
|
|
16
|
+
// array under HTTP 200, so the status-driven path
|
|
17
|
+
// in result_basic never sees them.
|
|
18
|
+
|
|
19
|
+
#include "sdk.h"
|
|
20
|
+
|
|
21
|
+
#include <stdio.h>
|
|
22
|
+
#include <string.h>
|
|
23
|
+
#include <ctype.h>
|
|
24
|
+
|
|
25
|
+
// Uppercase copy, bounded. Used only to normalise an extension code.
|
|
26
|
+
static void upper_copy(const char* src, char* dst, size_t cap) {
|
|
27
|
+
size_t i = 0;
|
|
28
|
+
if (src != NULL) {
|
|
29
|
+
for (; src[i] != '\0' && i + 1 < cap; i++) {
|
|
30
|
+
dst[i] = (char)toupper((unsigned char)src[i]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
dst[i] = '\0';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Map a GraphQL error to the same error codes the HTTP path produces, so a
|
|
37
|
+
// caller handles auth or rate limiting identically on both transports.
|
|
38
|
+
// Servers put the machine-readable code in `extensions.code`; Linear-style
|
|
39
|
+
// APIs use `extensions.type`.
|
|
40
|
+
const char* graphql_error_code(voxgig_value* gqlerr) {
|
|
41
|
+
voxgig_value* ext = getp(gqlerr, "extensions");
|
|
42
|
+
|
|
43
|
+
voxgig_value* code = getp(ext, "code");
|
|
44
|
+
if (!voxgig_is_string(code)) {
|
|
45
|
+
code = getp(ext, "type");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
char raw[128];
|
|
49
|
+
upper_copy(voxgig_is_string(code) ? voxgig_as_string(code) : "", raw, sizeof(raw));
|
|
50
|
+
|
|
51
|
+
if (strstr(raw, "AUTH") || strstr(raw, "FORBIDDEN") ||
|
|
52
|
+
strstr(raw, "UNAUTHENTICATED")) {
|
|
53
|
+
return "request_auth";
|
|
54
|
+
}
|
|
55
|
+
if (strstr(raw, "RATELIMIT") || strstr(raw, "RATE_LIMIT") ||
|
|
56
|
+
strstr(raw, "TOO_MANY")) {
|
|
57
|
+
return "request_ratelimit";
|
|
58
|
+
}
|
|
59
|
+
if (strstr(raw, "BAD_USER_INPUT") || strstr(raw, "VALIDATION") ||
|
|
60
|
+
strstr(raw, "INVALID")) {
|
|
61
|
+
return "request_invalid";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return "request_graphql";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Build the request body for a GraphQL point.
|
|
68
|
+
//
|
|
69
|
+
// Variables come from the op's own arguments: a named variable binds to the
|
|
70
|
+
// like-named argument (`from`), and the input-object variable (empty
|
|
71
|
+
// `from`) takes the request data as a whole — which is what makes a
|
|
72
|
+
// generated create/update call look exactly like its REST equivalent.
|
|
73
|
+
voxgig_value* graphql_body_util(Context* ctx) {
|
|
74
|
+
voxgig_value* gql = getp(ctx->point, "graphql");
|
|
75
|
+
if (!voxgig_is_map(gql)) return voxgig_new_noval();
|
|
76
|
+
|
|
77
|
+
// reqmatch/reqdata hold the caller's arguments for THIS call; data/match
|
|
78
|
+
// hold the entity's current state. Which pair depends on whether the op
|
|
79
|
+
// takes match or data input. A named variable falls back to the current
|
|
80
|
+
// state, so updating a loaded entity with just {title} still binds the
|
|
81
|
+
// stored id the mutation requires.
|
|
82
|
+
voxgig_value* reqsrc = ctx->reqmatch;
|
|
83
|
+
voxgig_value* datasrc = ctx->mtch;
|
|
84
|
+
if (ctx->op != NULL && ctx->op->input != NULL &&
|
|
85
|
+
strcmp(ctx->op->input, "data") == 0) {
|
|
86
|
+
reqsrc = ctx->reqdata;
|
|
87
|
+
datasrc = ctx->data;
|
|
88
|
+
}
|
|
89
|
+
if (!voxgig_is_map(reqsrc)) reqsrc = voxgig_new_map();
|
|
90
|
+
if (!voxgig_is_map(datasrc)) datasrc = voxgig_new_map();
|
|
91
|
+
|
|
92
|
+
voxgig_value* variables = voxgig_new_map();
|
|
93
|
+
|
|
94
|
+
voxgig_value* varlist = getp(gql, "vars");
|
|
95
|
+
if (voxgig_is_list(varlist)) {
|
|
96
|
+
voxgig_list* vl = voxgig_as_list(varlist);
|
|
97
|
+
for (size_t i = 0; i < vl->len; i++) {
|
|
98
|
+
voxgig_value* spec = vl->items[i];
|
|
99
|
+
if (!voxgig_is_map(spec)) continue;
|
|
100
|
+
|
|
101
|
+
voxgig_value* nameval = getp(spec, "name");
|
|
102
|
+
if (!voxgig_is_string(nameval)) continue;
|
|
103
|
+
const char* name = voxgig_as_string(nameval);
|
|
104
|
+
|
|
105
|
+
voxgig_value* fromval = getp(spec, "from");
|
|
106
|
+
const char* from = voxgig_is_string(fromval) ? voxgig_as_string(fromval) : "";
|
|
107
|
+
|
|
108
|
+
if (from[0] == '\0') {
|
|
109
|
+
// The input object IS the request body. Strip the action selector,
|
|
110
|
+
// which is an SDK-side point discriminator, not an API field.
|
|
111
|
+
voxgig_value* body = voxgig_new_map();
|
|
112
|
+
voxgig_map* rm = voxgig_as_map(reqsrc);
|
|
113
|
+
for (size_t j = 0; j < rm->len; j++) {
|
|
114
|
+
if (strcmp(rm->entries[j].key, "$action") != 0) {
|
|
115
|
+
setp(body, rm->entries[j].key, v_share(rm->entries[j].value));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
setp(variables, name, body);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Only send variables the caller actually supplied: sending an
|
|
123
|
+
// explicit null would clear a field on many APIs.
|
|
124
|
+
voxgig_value* val = getp(reqsrc, from);
|
|
125
|
+
if (v_is_noval(val) || v_is_null(val)) {
|
|
126
|
+
val = getp(datasrc, from);
|
|
127
|
+
}
|
|
128
|
+
if (!v_is_noval(val) && !v_is_null(val)) {
|
|
129
|
+
setp(variables, name, v_share(val));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
voxgig_value* out = voxgig_new_map();
|
|
135
|
+
setp(out, "query", v_share(getp(gql, "doc")));
|
|
136
|
+
setp(out, "variables", variables);
|
|
137
|
+
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Inspect a decoded GraphQL response body and record a failure when the
|
|
142
|
+
// server reported one. Returns true when an error was recorded.
|
|
143
|
+
//
|
|
144
|
+
// Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
145
|
+
// surface has no partial-success concept, and silently returning half an
|
|
146
|
+
// object would be worse than failing. The raw envelope stays available on
|
|
147
|
+
// the result for callers that need it.
|
|
148
|
+
bool graphql_errors_util(Context* ctx) {
|
|
149
|
+
SdkResult* result = ctx->result;
|
|
150
|
+
if (result == NULL || ctx->point == NULL) return false;
|
|
151
|
+
|
|
152
|
+
voxgig_value* kind = getp(ctx->point, "kind");
|
|
153
|
+
if (!voxgig_is_string(kind) || strcmp(voxgig_as_string(kind), "graphql") != 0) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
voxgig_value* errors = getp(result->body, "errors");
|
|
158
|
+
if (!voxgig_is_list(errors)) return false;
|
|
159
|
+
|
|
160
|
+
voxgig_list* el = voxgig_as_list(errors);
|
|
161
|
+
if (el->len == 0) return false;
|
|
162
|
+
|
|
163
|
+
voxgig_value* first = el->items[0];
|
|
164
|
+
voxgig_value* msgval = getp(first, "message");
|
|
165
|
+
const char* msg = voxgig_is_string(msgval) ? voxgig_as_string(msgval) : "graphql error";
|
|
166
|
+
|
|
167
|
+
char buf[512];
|
|
168
|
+
if (el->len > 1) {
|
|
169
|
+
snprintf(buf, sizeof(buf), "graphql: %s (+%zu more)", msg, el->len - 1);
|
|
170
|
+
} else {
|
|
171
|
+
snprintf(buf, sizeof(buf), "graphql: %s", msg);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
result->err = context_make_error(ctx, graphql_error_code(first), buf);
|
|
175
|
+
result->ok = false;
|
|
176
|
+
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
@@ -64,7 +64,7 @@ voxgig_value* make_options_util(Context* ctx) {
|
|
|
64
64
|
"headers", cmap(1, "`$CHILD`", v_str("`$STRING`")),
|
|
65
65
|
"allow", cmap(2,
|
|
66
66
|
"method", v_str("GET,PUT,POST,PATCH,DELETE,OPTIONS"),
|
|
67
|
-
"op", v_str("create,update,load,list,remove,command,direct")),
|
|
67
|
+
"op", v_str("create,update,load,list,remove,command,direct,graphql")),
|
|
68
68
|
"entity", cmap(1, "`$CHILD`", cmap(3,
|
|
69
69
|
"`$OPEN`", v_bool(true),
|
|
70
70
|
"active", v_bool(false),
|
|
@@ -30,6 +30,12 @@ Response* make_response_util(Context* ctx, PNError** err) {
|
|
|
30
30
|
result_basic_util(ctx);
|
|
31
31
|
result_headers_util(ctx);
|
|
32
32
|
result_body_util(ctx);
|
|
33
|
+
|
|
34
|
+
// GraphQL reports failures as a top-level `errors` array under HTTP 200,
|
|
35
|
+
// so result_basic's status check never sees them. Lift them here, before
|
|
36
|
+
// the response transform tries to unwrap data that is not there.
|
|
37
|
+
graphql_errors_util(ctx);
|
|
38
|
+
|
|
33
39
|
transform_response_util(ctx);
|
|
34
40
|
|
|
35
41
|
if (result->err == NULL) {
|
|
@@ -43,9 +43,29 @@ Spec* make_spec_util(Context* ctx, PNError** err) {
|
|
|
43
43
|
spec->params = prepare_params_util(ctx);
|
|
44
44
|
spec->query = prepare_query_util(ctx);
|
|
45
45
|
spec->headers = prepare_headers_util(ctx);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
|
|
47
|
+
voxgig_value* pkind = getp(ctx->point, "kind");
|
|
48
|
+
bool is_graphql = voxgig_is_string(pkind) &&
|
|
49
|
+
strcmp(voxgig_as_string(pkind), "graphql") == 0;
|
|
50
|
+
|
|
51
|
+
if (is_graphql) {
|
|
52
|
+
// GraphQL addresses one endpoint: no path parts, no query string, and
|
|
53
|
+
// the body carries the operation. prepare_body is skipped deliberately
|
|
54
|
+
// — it only emits a body for data-input ops, whereas every GraphQL op
|
|
55
|
+
// posts one, including load/list/remove.
|
|
56
|
+
spec->body = graphql_body_util(ctx);
|
|
57
|
+
spec_set_path(spec, "");
|
|
58
|
+
// prepare_query already copied the op's match arguments into the query
|
|
59
|
+
// string. Those same values are bound as operation variables, so
|
|
60
|
+
// leaving them would send /graphql?id=i1.
|
|
61
|
+
spec->query = voxgig_new_map();
|
|
62
|
+
setp(spec->headers, "content-type",
|
|
63
|
+
voxgig_new_string(GRAPHQL_CONTENT_TYPE));
|
|
64
|
+
} else {
|
|
65
|
+
spec->body = prepare_body_util(ctx);
|
|
66
|
+
char* path = prepare_path_util(ctx);
|
|
67
|
+
spec_set_path(spec, path);
|
|
68
|
+
}
|
|
49
69
|
|
|
50
70
|
Control* c = ctx->ctrl;
|
|
51
71
|
if (control_has_explain(c)) {
|
|
@@ -83,7 +83,22 @@
|
|
|
83
83
|
(when fd-err (core/sdk-throw fd-err))
|
|
84
84
|
fetchdef)))
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
;; Is this raw-access op permitted by the SDK's allow.op option?
|
|
87
|
+
(defn- op-allowed? [client op]
|
|
88
|
+
(let [allow (vs/getpath (core/client-options-map client) "allow.op")]
|
|
89
|
+
(and (string? allow) (str/includes? allow op))))
|
|
90
|
+
|
|
91
|
+
(defn- op-denied [client op]
|
|
92
|
+
(let [allow (vs/getpath (core/client-options-map client) "allow.op")]
|
|
93
|
+
(vs/jm "ok" false
|
|
94
|
+
"err" (str "ProjectNameSDK: " op ": operation not allowed by"
|
|
95
|
+
" SDK option allow.op value: \"" (or allow "") "\""))))
|
|
96
|
+
|
|
97
|
+
;; Ungated request path shared by direct and graphql, each of which checks its
|
|
98
|
+
;; own allow.op token first. Private, rather than a flag on fetchargs: a
|
|
99
|
+
;; caller-supplied marker would let anyone opt straight back out of the gate
|
|
100
|
+
;; by passing it.
|
|
101
|
+
(defn- raw-request [client fetchargs]
|
|
87
102
|
(let [utility (core/client-utility client)
|
|
88
103
|
fetchargs (or fetchargs (vs/jm))
|
|
89
104
|
pr (try {:fd (prepare client fetchargs)}
|
|
@@ -109,6 +124,54 @@
|
|
|
109
124
|
(vs/jm "ok" (and (>= status 200) (< status 300)) "status" status "headers" headers "data" json-data))
|
|
110
125
|
:else (vs/jm "ok" false "err" (core/ctx-error ctx "direct_invalid" "invalid response type")))))))
|
|
111
126
|
|
|
127
|
+
;; Raw endpoint access is operator-controllable, like every entity op.
|
|
128
|
+
;; Blocking it means denying BOTH the 'direct' and 'graphql' tokens, since
|
|
129
|
+
;; either one reaches the same endpoint.
|
|
130
|
+
(defn direct [client fetchargs]
|
|
131
|
+
(if (op-allowed? client "direct")
|
|
132
|
+
(raw-request client fetchargs)
|
|
133
|
+
(op-denied client "direct")))
|
|
134
|
+
|
|
135
|
+
;; Raw GraphQL access: the pressure valve that makes the generated surface's
|
|
136
|
+
;; deliberate omissions (per-call selection sets, typed filter builders,
|
|
137
|
+
;; batching, subscriptions) livable — the whole schema stays reachable.
|
|
138
|
+
;;
|
|
139
|
+
;; Thin wrapper over the same prepare/fetch path direct uses, with the one
|
|
140
|
+
;; thing raw direct cannot do for GraphQL: a GraphQL failure rides HTTP 200 as
|
|
141
|
+
;; a top-level `errors` array, so status alone would report a failed query as
|
|
142
|
+
;; ok.
|
|
143
|
+
;;
|
|
144
|
+
;; NOTE: like direct, this bypasses the feature pipeline — no retry, ratelimit
|
|
145
|
+
;; or paging features apply.
|
|
146
|
+
(defn graphql
|
|
147
|
+
([client query] (graphql client query nil nil))
|
|
148
|
+
([client query variables] (graphql client query variables nil))
|
|
149
|
+
([client query variables ctrl]
|
|
150
|
+
(if-not (op-allowed? client "graphql")
|
|
151
|
+
(op-denied client "graphql")
|
|
152
|
+
(let [res (raw-request client
|
|
153
|
+
(vs/jm "method" "POST"
|
|
154
|
+
"headers" (vs/jm "content-type" "application/json")
|
|
155
|
+
"body" (vs/jm "query" query
|
|
156
|
+
"variables" (or variables (vs/jm)))
|
|
157
|
+
"ctrl" (or ctrl (vs/jm))))
|
|
158
|
+
;; Errors are read BEFORE any status check: a GraphQL parse or
|
|
159
|
+
;; validation failure comes back as HTTP 400 carrying the standard
|
|
160
|
+
;; { errors: [...] } body, and the raw path represents a non-2xx as
|
|
161
|
+
;; ok:false with no err — so returning early on status would
|
|
162
|
+
;; discard the server's own diagnostics, which are the only useful
|
|
163
|
+
;; part of that response.
|
|
164
|
+
errors (vs/getpath res "data.errors")
|
|
165
|
+
n (if (vs/islist errors) (vs/size errors) 0)]
|
|
166
|
+
(if (zero? n)
|
|
167
|
+
res
|
|
168
|
+
(let [m (vs/getprop (vs/getprop errors 0) "message")
|
|
169
|
+
m (if (and (string? m) (seq m)) m "graphql error")]
|
|
170
|
+
(.put ^java.util.Map res "ok" false)
|
|
171
|
+
(.put ^java.util.Map res "err" (str "ProjectNameSDK: graphql: " m))
|
|
172
|
+
(.put ^java.util.Map res "graphql" errors)
|
|
173
|
+
res))))))
|
|
174
|
+
|
|
112
175
|
(defn test-sdk [testopts sdkopts]
|
|
113
176
|
(let [sdkopts (let [c (vs/clone (or sdkopts (vs/jm)))] (if (vs/ismap c) c (vs/jm)))
|
|
114
177
|
testopts (let [c (vs/clone (or testopts (vs/jm)))] (if (vs/ismap c) c (vs/jm)))]
|
|
@@ -471,6 +471,108 @@
|
|
|
471
471
|
(defn u-prepare-body [ctx]
|
|
472
472
|
(if (= "data" (op-input (oget ctx :op))) (ucall ctx :transform-request) nil))
|
|
473
473
|
|
|
474
|
+
;; ---- graphql ---------------------------------------------------------------
|
|
475
|
+
;;
|
|
476
|
+
;; GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
|
|
477
|
+
;; produces uses this code unchanged. The API-specific part — which
|
|
478
|
+
;; operations exist and what each one's document is — is model data, computed
|
|
479
|
+
;; once by apidef and emitted into Config.
|
|
480
|
+
;;
|
|
481
|
+
;; Two jobs:
|
|
482
|
+
;;
|
|
483
|
+
;; u-graphql-body — build {query, variables} for a point, binding the op's
|
|
484
|
+
;; arguments to the document's declared variables.
|
|
485
|
+
;;
|
|
486
|
+
;; u-graphql-errors — lift a GraphQL failure into an SDK error. GraphQL
|
|
487
|
+
;; reports failures as a top-level `errors` array under
|
|
488
|
+
;; HTTP 200, so the status-driven path in result-basic
|
|
489
|
+
;; never sees them.
|
|
490
|
+
|
|
491
|
+
;; Content type every GraphQL-over-HTTP request uses.
|
|
492
|
+
(def GRAPHQL-CONTENT-TYPE "application/json")
|
|
493
|
+
|
|
494
|
+
;; Map a GraphQL error to the same error codes the HTTP path produces, so a
|
|
495
|
+
;; caller handles auth or rate limiting identically on both transports.
|
|
496
|
+
;; Servers put the machine-readable code in `extensions.code`; Linear-style
|
|
497
|
+
;; APIs use `extensions.type`.
|
|
498
|
+
(defn graphql-error-code [gqlerr]
|
|
499
|
+
(let [ext (vs/getprop gqlerr "extensions")
|
|
500
|
+
code (let [c (vs/getprop ext "code")]
|
|
501
|
+
(if (and (string? c) (seq c)) c (vs/getprop ext "type")))
|
|
502
|
+
raw (str/upper-case (if (string? code) code ""))]
|
|
503
|
+
(cond
|
|
504
|
+
(or (str/includes? raw "AUTH") (str/includes? raw "FORBIDDEN")
|
|
505
|
+
(str/includes? raw "UNAUTHENTICATED")) "request_auth"
|
|
506
|
+
(or (str/includes? raw "RATELIMIT") (str/includes? raw "RATE_LIMIT")
|
|
507
|
+
(str/includes? raw "TOO_MANY")) "request_ratelimit"
|
|
508
|
+
(or (str/includes? raw "BAD_USER_INPUT") (str/includes? raw "VALIDATION")
|
|
509
|
+
(str/includes? raw "INVALID")) "request_invalid"
|
|
510
|
+
:else "request_graphql")))
|
|
511
|
+
|
|
512
|
+
;; Build the request body for a GraphQL point.
|
|
513
|
+
;;
|
|
514
|
+
;; Variables come from the op's own arguments: a named variable binds to the
|
|
515
|
+
;; like-named argument (`from`), and the input-object variable (empty `from`)
|
|
516
|
+
;; takes the request data as a whole — which is what makes a generated
|
|
517
|
+
;; create/update call look exactly like its REST equivalent.
|
|
518
|
+
(defn u-graphql-body [ctx]
|
|
519
|
+
(let [gql (vs/getprop (oget ctx :point) "graphql")]
|
|
520
|
+
(when (vs/ismap gql)
|
|
521
|
+
;; reqmatch/reqdata hold the caller's arguments for this operation;
|
|
522
|
+
;; which one depends on whether the op takes match or data input.
|
|
523
|
+
(let [datainput (= "data" (op-input (oget ctx :op)))
|
|
524
|
+
reqsrc (let [r (if datainput (oget ctx :reqdata) (oget ctx :reqmatch))]
|
|
525
|
+
(if (vs/ismap r) r (vs/jm)))
|
|
526
|
+
;; data/match hold the entity's current state; a named variable
|
|
527
|
+
;; falls back to it, so updating a loaded entity with just
|
|
528
|
+
;; {title} still binds the stored id the mutation requires.
|
|
529
|
+
datasrc (let [d (if datainput (oget ctx :data) (oget ctx :match))]
|
|
530
|
+
(if (vs/ismap d) d (vs/jm)))
|
|
531
|
+
variables (vs/jm)
|
|
532
|
+
varlist (let [v (vs/getprop gql "vars")] (if (vs/islist v) v (vs/jt)))]
|
|
533
|
+
(doseq [spec (vec varlist)]
|
|
534
|
+
(when (vs/ismap spec)
|
|
535
|
+
(let [name (vs/getprop spec "name")
|
|
536
|
+
from (vs/getprop spec "from")]
|
|
537
|
+
(when (and (string? name) (seq name))
|
|
538
|
+
(if (or (nil? from) (not (string? from)) (empty? from))
|
|
539
|
+
;; The input object IS the request body. Strip the action
|
|
540
|
+
;; selector, which is an SDK-side point discriminator, not
|
|
541
|
+
;; an API field.
|
|
542
|
+
(let [body (vs/jm)]
|
|
543
|
+
(doseq [item (or (vs/items reqsrc) [])]
|
|
544
|
+
(let [k (vs/getprop item 0) v (vs/getprop item 1)]
|
|
545
|
+
(when (not= "$action" k) (.put ^java.util.Map body k v))))
|
|
546
|
+
(.put ^java.util.Map variables name body))
|
|
547
|
+
;; Only send variables the caller actually supplied: sending
|
|
548
|
+
;; an explicit null would clear a field on many APIs.
|
|
549
|
+
(let [val (let [v (vs/getprop reqsrc from)]
|
|
550
|
+
(if (some? v) v (vs/getprop datasrc from)))]
|
|
551
|
+
(when (some? val) (.put ^java.util.Map variables name val))))))))
|
|
552
|
+
(vs/jm "query" (vs/getprop gql "doc") "variables" variables)))))
|
|
553
|
+
|
|
554
|
+
;; Inspect a decoded GraphQL response body and record a failure when the
|
|
555
|
+
;; server reported one. Returns true when an error was recorded.
|
|
556
|
+
;;
|
|
557
|
+
;; Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
558
|
+
;; surface has no partial-success concept, and silently returning half an
|
|
559
|
+
;; object would be worse than failing.
|
|
560
|
+
(defn u-graphql-errors [ctx]
|
|
561
|
+
(let [result (oget ctx :result)
|
|
562
|
+
errors (when result (vs/getprop (oget result :body) "errors"))
|
|
563
|
+
n (if (vs/islist errors) (vs/size errors) 0)]
|
|
564
|
+
(if (or (nil? result)
|
|
565
|
+
(not= "graphql" (vs/getprop (oget ctx :point) "kind"))
|
|
566
|
+
(zero? n))
|
|
567
|
+
false
|
|
568
|
+
(let [first-err (vs/getprop errors 0)
|
|
569
|
+
m (vs/getprop first-err "message")
|
|
570
|
+
m (if (and (string? m) (seq m)) m "graphql error")
|
|
571
|
+
m (if (< 1 n) (str m " (+" (dec n) " more)") m)]
|
|
572
|
+
(oset! result :err (ctx-error ctx (graphql-error-code first-err) (str "graphql: " m)))
|
|
573
|
+
(oset! result :ok false)
|
|
574
|
+
true))))
|
|
575
|
+
|
|
474
576
|
(def HEADER-AUTH "authorization")
|
|
475
577
|
(def OPTION-APIKEY "apikey")
|
|
476
578
|
(def NOT-FOUND "__NOTFOUND__")
|
|
@@ -566,8 +668,23 @@
|
|
|
566
668
|
(oset! spec :params (ucall ctx :prepare-params))
|
|
567
669
|
(oset! spec :query (ucall ctx :prepare-query))
|
|
568
670
|
(oset! spec :headers (ucall ctx :prepare-headers))
|
|
569
|
-
(
|
|
570
|
-
|
|
671
|
+
(if (= "graphql" (vs/getprop point "kind"))
|
|
672
|
+
;; GraphQL addresses one endpoint: no path parts, no query
|
|
673
|
+
;; string, and the body carries the operation. prepare-body is
|
|
674
|
+
;; skipped deliberately — it only emits a body for data-input
|
|
675
|
+
;; ops, whereas every GraphQL op posts one, including
|
|
676
|
+
;; load/list/remove.
|
|
677
|
+
(do
|
|
678
|
+
(oset! spec :body (ucall ctx :graphql-body))
|
|
679
|
+
(oset! spec :path "")
|
|
680
|
+
;; prepare-query already copied the op's match arguments into
|
|
681
|
+
;; the query string. Those same values are bound as operation
|
|
682
|
+
;; variables, so leaving them would send /graphql?id=i1.
|
|
683
|
+
(oset! spec :query (vs/jm))
|
|
684
|
+
(.put ^java.util.Map (oget spec :headers) "content-type" GRAPHQL-CONTENT-TYPE))
|
|
685
|
+
(do
|
|
686
|
+
(oset! spec :body (ucall ctx :prepare-body))
|
|
687
|
+
(oset! spec :path (ucall ctx :prepare-path))))
|
|
571
688
|
(when-let [ex (oget (oget ctx :ctrl) :explain)] (.put ^java.util.Map ex "spec" spec))
|
|
572
689
|
(let [[s err] (ucall ctx :prepare-auth)]
|
|
573
690
|
(if err [nil err] (do (oset! ctx :spec s) [s nil])))))))))
|
|
@@ -696,6 +813,11 @@
|
|
|
696
813
|
(ucall ctx :result-basic)
|
|
697
814
|
(ucall ctx :result-headers)
|
|
698
815
|
(ucall ctx :result-body)
|
|
816
|
+
;; GraphQL reports failures as a top-level `errors` array under
|
|
817
|
+
;; HTTP 200, so result-basic's status check never sees them. Lift
|
|
818
|
+
;; them here, before the response transform tries to unwrap data
|
|
819
|
+
;; that is not there.
|
|
820
|
+
(ucall ctx :graphql-errors)
|
|
699
821
|
(ucall ctx :transform-response)
|
|
700
822
|
(when (nil? (oget result :err)) (oset! result :ok true))
|
|
701
823
|
(when-let [ex (oget (oget ctx :ctrl) :explain)] (.put ^java.util.Map ex "result" result))
|
|
@@ -800,7 +922,7 @@
|
|
|
800
922
|
"auth" (vs/jm "prefix" "")
|
|
801
923
|
"headers" (vs/jm "`$CHILD`" "`$STRING`")
|
|
802
924
|
"allow" (vs/jm "method" "GET,PUT,POST,PATCH,DELETE,OPTIONS"
|
|
803
|
-
"op" "create,update,load,list,remove,command,direct")
|
|
925
|
+
"op" "create,update,load,list,remove,command,direct,graphql")
|
|
804
926
|
"entity" (vs/jm "`$CHILD`" (vs/jm "`$OPEN`" true "active" false "alias" (vs/jm)))
|
|
805
927
|
"feature" (vs/jm "`$CHILD`" (vs/jm "`$OPEN`" true "active" false))
|
|
806
928
|
"utility" (vs/jm)
|
|
@@ -901,6 +1023,7 @@
|
|
|
901
1023
|
:param u-param :prepare-auth u-prepare-auth :prepare-body u-prepare-body
|
|
902
1024
|
:prepare-headers u-prepare-headers :prepare-method u-prepare-method :prepare-params u-prepare-params
|
|
903
1025
|
:prepare-path u-prepare-path :prepare-query u-prepare-query
|
|
1026
|
+
:graphql-body u-graphql-body :graphql-errors u-graphql-errors
|
|
904
1027
|
:result-basic u-result-basic :result-body u-result-body :result-headers u-result-headers
|
|
905
1028
|
:transform-request u-transform-request :transform-response u-transform-response})
|
|
906
1029
|
|