@voxgig/sdkgen 1.5.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmp/AgentGuideContent.js +5 -0
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +13 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/helpers/canonType.d.ts +3 -1
- package/dist/helpers/canonType.js +44 -1
- package/dist/helpers/canonType.js.map +1 -1
- package/dist/helpers/packageMeta.js +7 -0
- package/dist/helpers/packageMeta.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -3
- package/dist/sdkgen.js +4 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/project/.sdk/model/feature/paging.aontu +7 -0
- package/project/.sdk/model/target/py-data.aontu +64 -0
- package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +89 -1
- package/project/.sdk/src/cmp/clojure/Main_clojure.ts +1 -0
- package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +97 -0
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +78 -0
- package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +84 -0
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +87 -0
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +75 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +85 -0
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +4 -0
- package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +80 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +86 -0
- package/project/.sdk/src/cmp/py/Entity_py.ts +9 -2
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +1 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +34 -6
- package/project/.sdk/src/cmp/py/Package_py.ts +6 -9
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +2 -2
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +3 -3
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +75 -8
- package/project/.sdk/src/cmp/py-data/Main_py-data.ts +987 -0
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +71 -0
- package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +95 -0
- package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +86 -0
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +75 -0
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +95 -0
- package/project/.sdk/tm/c/core/sdk.h +7 -0
- package/project/.sdk/tm/c/feature/paging.c +141 -2
- package/project/.sdk/tm/c/utility/graphql.c +178 -0
- package/project/.sdk/tm/c/utility/make_options.c +1 -1
- package/project/.sdk/tm/c/utility/make_response.c +6 -0
- package/project/.sdk/tm/c/utility/make_spec.c +23 -3
- package/project/.sdk/tm/clojure/src/sdk/client.clj +64 -1
- package/project/.sdk/tm/clojure/src/sdk/core.clj +126 -3
- package/project/.sdk/tm/clojure/src/sdk/features.clj +80 -14
- package/project/.sdk/tm/cpp/core/types.hpp +83 -0
- package/project/.sdk/tm/cpp/feature/paging.hpp +103 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +152 -3
- package/project/.sdk/tm/csharp/core/Utility.cs +4 -0
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +118 -1
- package/project/.sdk/tm/csharp/utility/Graphql.cs +190 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +1 -1
- package/project/.sdk/tm/csharp/utility/MakeResponse.cs +7 -0
- package/project/.sdk/tm/csharp/utility/MakeSpec.cs +21 -2
- package/project/.sdk/tm/csharp/utility/Register.cs +2 -0
- package/project/.sdk/tm/dart/lib/Point.dart +7 -0
- package/project/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +99 -4
- package/project/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +1 -1
- package/project/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +5 -0
- package/project/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +18 -2
- package/project/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +6 -1
- package/project/.sdk/tm/dart/lib/utility/Utility.dart +9 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +136 -13
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +187 -3
- package/project/.sdk/tm/go/core/utility_type.go +2 -0
- package/project/.sdk/tm/go/feature/paging_feature.go +87 -1
- package/project/.sdk/tm/go/utility/graphql.go +168 -0
- package/project/.sdk/tm/go/utility/make_options.go +1 -1
- package/project/.sdk/tm/go/utility/make_response.go +6 -0
- package/project/.sdk/tm/go/utility/make_spec.go +19 -2
- package/project/.sdk/tm/go/utility/register.go +2 -0
- package/project/.sdk/tm/haskell/src/SdkFeatures.hs +146 -7
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +153 -3
- package/project/.sdk/tm/java/core/SdkClient.java +93 -0
- package/project/.sdk/tm/java/core/Utility.java +4 -0
- package/project/.sdk/tm/java/feature/PagingFeature.java +100 -1
- package/project/.sdk/tm/java/utility/Graphql.java +191 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +1 -1
- package/project/.sdk/tm/java/utility/MakeResponse.java +6 -0
- package/project/.sdk/tm/java/utility/MakeSpec.java +17 -2
- package/project/.sdk/tm/java/utility/Register.java +2 -0
- package/project/.sdk/tm/js/src/Point.js +5 -0
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +82 -3
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +14 -2
- package/project/.sdk/tm/js/src/utility/GraphqlUtility.js +148 -0
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +1 -1
- package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +7 -0
- package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +19 -2
- package/project/.sdk/tm/js/src/utility/Utility.js +4 -0
- package/project/.sdk/tm/kotlin/core/SdkClient.kt +84 -0
- package/project/.sdk/tm/kotlin/core/Utility.kt +2 -0
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +92 -1
- package/project/.sdk/tm/kotlin/utility/Graphql.kt +145 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/MakeReqRes.kt +6 -0
- package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +17 -2
- package/project/.sdk/tm/kotlin/utility/Register.kt +2 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +134 -1
- package/project/.sdk/tm/lua/feature/paging_feature.lua +97 -2
- package/project/.sdk/tm/lua/utility/graphql.lua +163 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +1 -1
- package/project/.sdk/tm/lua/utility/make_response.lua +6 -0
- package/project/.sdk/tm/lua/utility/make_spec.lua +18 -2
- package/project/.sdk/tm/lua/utility/register.lua +3 -0
- package/project/.sdk/tm/ocaml/sdk_features.ml +164 -20
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +143 -4
- package/project/.sdk/tm/ocaml/sdk_types.ml +2 -0
- package/project/.sdk/tm/perl/feature/paging_feature.pm +91 -3
- package/project/.sdk/tm/perl/utility/graphql.pm +150 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +1 -1
- package/project/.sdk/tm/perl/utility/make_response.pm +6 -0
- package/project/.sdk/tm/perl/utility/make_spec.pm +20 -2
- package/project/.sdk/tm/perl/utility/register.pm +1 -0
- package/project/.sdk/tm/php/core/UtilityType.php +2 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +106 -4
- package/project/.sdk/tm/php/utility/Graphql.php +176 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +1 -1
- package/project/.sdk/tm/php/utility/MakeResponse.php +7 -0
- package/project/.sdk/tm/php/utility/MakeSpec.php +20 -2
- package/project/.sdk/tm/php/utility/Register.php +3 -0
- package/project/.sdk/tm/py/pkg/core/__init__.py +0 -0
- package/project/.sdk/tm/py/{core → pkg/core}/context.py +8 -8
- package/project/.sdk/tm/py/{core → pkg/core}/operation.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/response.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/result.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/utility_type.py +2 -0
- package/project/.sdk/tm/py/pkg/entity/__init__.py +0 -0
- package/project/.sdk/tm/py/pkg/feature/__init__.py +0 -0
- package/project/.sdk/tm/py/{feature → pkg/feature}/audit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/cache_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/clienttrack_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/debug_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/idempotency_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/log_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/metrics_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/netsim_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/paging_feature.py +87 -6
- package/project/.sdk/tm/py/{feature → pkg/feature}/proxy_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/ratelimit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/rbac_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/retry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/streaming_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/telemetry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/test_feature.py +2 -2
- package/project/.sdk/tm/py/{feature → pkg/feature}/timeout_feature.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/__init__.py +0 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/feature_init.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/fetcher.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/graphql.py +145 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_context.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_error.py +5 -5
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_fetch_def.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_options.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_point.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_response.py +6 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_spec.py +19 -4
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_url.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/param.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_auth.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_headers.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_method.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_params.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_path.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_query.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/register.py +71 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_response.py +2 -2
- package/project/.sdk/tm/py/test/feature_harness.py +6 -6
- package/project/.sdk/tm/py/test/runner.py +1 -1
- package/project/.sdk/tm/py/test/struct_runner.py +1 -1
- package/project/.sdk/tm/py/test/test_pipeline.py +5 -5
- package/project/.sdk/tm/py/test/test_primary_utility.py +8 -8
- package/project/.sdk/tm/py/test/test_struct_utility.py +3 -3
- package/project/.sdk/tm/py-data/LICENSE +22 -0
- package/project/.sdk/tm/py-data/Makefile +57 -0
- package/project/.sdk/tm/py-data/pkg/auth.py +131 -0
- package/project/.sdk/tm/py-data/pkg/fetch.py +178 -0
- package/project/.sdk/tm/py-data/pkg/frames.py +236 -0
- package/project/.sdk/tm/py-data/src/feature/README.md +8 -0
- package/project/.sdk/tm/py-data/src/feature/debug/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/idempotency/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/metrics/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/netsim/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/paging/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/proxy/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/ratelimit/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/rbac/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/retry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/streaming/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/telemetry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/timeout/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/test/__init__.py +0 -0
- package/project/.sdk/tm/py-data/test/test_auth.py +95 -0
- package/project/.sdk/tm/py-data/test/test_fetch.py +152 -0
- package/project/.sdk/tm/py-data/test/test_frames.py +250 -0
- package/project/.sdk/tm/rb/core/utility_type.rb +1 -0
- package/project/.sdk/tm/rb/feature/paging_feature.rb +89 -3
- package/project/.sdk/tm/rb/utility/graphql.rb +131 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +1 -1
- package/project/.sdk/tm/rb/utility/make_response.rb +6 -0
- package/project/.sdk/tm/rb/utility/make_spec.rb +18 -2
- package/project/.sdk/tm/rb/utility/register.rb +3 -0
- package/project/.sdk/tm/rust/feature/paging.rs +124 -2
- package/project/.sdk/tm/rust/utility/graphql.rs +193 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/rust/utility/make_response.rs +6 -0
- package/project/.sdk/tm/rust/utility/make_spec.rs +32 -4
- package/project/.sdk/tm/rust/utility/mod.rs +1 -0
- package/project/.sdk/tm/scala/core/SdkClient.scala +90 -0
- package/project/.sdk/tm/scala/core/Utility.scala +4 -0
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +104 -3
- package/project/.sdk/tm/scala/utility/Graphql.scala +160 -0
- package/project/.sdk/tm/scala/utility/Make.scala +7 -1
- package/project/.sdk/tm/scala/utility/MakeSpec.scala +22 -2
- package/project/.sdk/tm/scala/utility/Register.scala +2 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Utility.swift +4 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +86 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Graphql.swift +142 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +24 -2
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Register.swift +2 -0
- package/project/.sdk/tm/ts/src/Point.ts +7 -0
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +82 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +14 -2
- package/project/.sdk/tm/ts/src/utility/GraphqlUtility.ts +152 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +1 -1
- package/project/.sdk/tm/ts/src/utility/MakeResponseUtility.ts +7 -0
- package/project/.sdk/tm/ts/src/utility/MakeSpecUtility.ts +19 -2
- package/project/.sdk/tm/ts/src/utility/Utility.ts +4 -0
- package/project/.sdk/tm/zig/core/utility.zig +198 -3
- package/project/.sdk/tm/zig/feature/paging.zig +128 -1
- package/src/cmp/AgentGuideContent.ts +5 -0
- package/src/cmp/Feature.ts +13 -0
- package/src/helpers/canonType.ts +46 -0
- package/src/helpers/packageMeta.ts +7 -0
- package/src/sdkgen.ts +6 -1
- package/project/.sdk/tm/py/utility/register.py +0 -68
- /package/project/.sdk/tm/py/{core → pkg/core}/control.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/error.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/helpers.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/spec.py +0 -0
- /package/project/.sdk/tm/py/{feature → pkg/feature}/base_feature.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/clean.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/done.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_add.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_hook.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/make_result.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_basic.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_headers.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/__init__.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/voxgig_struct.py +0 -0
- /package/project/.sdk/tm/{py/core/__init__.py → py-data/src/feature/audit/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/entity/__init__.py → py-data/src/feature/base/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/feature/__init__.py → py-data/src/feature/cache/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/utility/__init__.py → py-data/src/feature/clienttrack/.gitkeep} +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# ProjectName Data — frames engine tests.
|
|
2
|
+
#
|
|
3
|
+
# These cover the runtime that is the same for every API, so they live in the
|
|
4
|
+
# template rather than being generated. The generated per-entity accessor
|
|
5
|
+
# tests are in test_entity_frames.py.
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import dataclasses
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from projectname_data.frames import (
|
|
15
|
+
build_frame,
|
|
16
|
+
to_record,
|
|
17
|
+
to_records,
|
|
18
|
+
apply_dtypes,
|
|
19
|
+
apply_dates,
|
|
20
|
+
order_columns,
|
|
21
|
+
records_df,
|
|
22
|
+
to_series,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclasses.dataclass
|
|
27
|
+
class Sample:
|
|
28
|
+
name: str
|
|
29
|
+
count: int
|
|
30
|
+
nested: dict
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FakeEntity:
|
|
34
|
+
"""Stands in for a generated ProjectName<X>Entity.
|
|
35
|
+
|
|
36
|
+
The private attributes matter: an entity's record is NOT in its public
|
|
37
|
+
__dict__, so anything relying on attribute scraping returns {} here.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, rec):
|
|
41
|
+
self._name = "thing"
|
|
42
|
+
self._data = rec
|
|
43
|
+
self._client = object()
|
|
44
|
+
|
|
45
|
+
def data_get(self):
|
|
46
|
+
return dict(self._data)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestToRecords:
|
|
50
|
+
|
|
51
|
+
def test_entity_object_is_unwrapped_via_data_get(self):
|
|
52
|
+
# list() yields entity OBJECTS, not dicts. Missing this produced a
|
|
53
|
+
# frame with the right row count and zero columns.
|
|
54
|
+
rec = to_record(FakeEntity({"id": "earth", "name": "Earth"}))
|
|
55
|
+
assert rec == {"id": "earth", "name": "Earth"}
|
|
56
|
+
|
|
57
|
+
def test_entities_build_a_populated_frame(self):
|
|
58
|
+
df = build_frame([
|
|
59
|
+
FakeEntity({"id": "earth", "diameter": 12756}),
|
|
60
|
+
FakeEntity({"id": "mars", "diameter": 6792}),
|
|
61
|
+
])
|
|
62
|
+
assert df.shape == (2, 2)
|
|
63
|
+
assert list(df["id"]) == ["earth", "mars"]
|
|
64
|
+
|
|
65
|
+
def test_entity_wins_over_the_dunder_dict_fallback(self):
|
|
66
|
+
rec = to_record(FakeEntity({"id": "x"}))
|
|
67
|
+
assert "_data" not in rec
|
|
68
|
+
assert rec != {}
|
|
69
|
+
|
|
70
|
+
def test_dataclass_becomes_dict(self):
|
|
71
|
+
rec = to_record(Sample(name="a", count=1, nested={"x": 2}))
|
|
72
|
+
assert rec == {"name": "a", "count": 1, "nested": {"x": 2}}
|
|
73
|
+
|
|
74
|
+
def test_dict_passes_through(self):
|
|
75
|
+
assert to_record({"a": 1}) == {"a": 1}
|
|
76
|
+
|
|
77
|
+
def test_none_is_none(self):
|
|
78
|
+
assert to_record(None) is None
|
|
79
|
+
|
|
80
|
+
def test_single_record_is_wrapped(self):
|
|
81
|
+
assert to_records({"a": 1}) == [{"a": 1}]
|
|
82
|
+
|
|
83
|
+
def test_none_is_empty(self):
|
|
84
|
+
assert to_records(None) == []
|
|
85
|
+
|
|
86
|
+
def test_list_of_dataclasses(self):
|
|
87
|
+
out = to_records([Sample("a", 1, {}), Sample("b", 2, {})])
|
|
88
|
+
assert [r["name"] for r in out] == ["a", "b"]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestFlatten:
|
|
92
|
+
|
|
93
|
+
def test_one_level_by_default(self):
|
|
94
|
+
df = build_frame([{"a": 1, "b": {"c": 2}}])
|
|
95
|
+
assert "b.c" in df.columns
|
|
96
|
+
assert "b" not in df.columns
|
|
97
|
+
|
|
98
|
+
def test_flatten_none_keeps_nesting(self):
|
|
99
|
+
df = build_frame([{"a": 1, "b": {"c": 2}}], flatten="none")
|
|
100
|
+
assert "b" in df.columns
|
|
101
|
+
assert df["b"].iloc[0] == {"c": 2}
|
|
102
|
+
|
|
103
|
+
def test_deep_nesting_stays_boxed_at_depth_one(self):
|
|
104
|
+
# The point of the depth-1 default: a deep payload must not explode
|
|
105
|
+
# into hundreds of columns.
|
|
106
|
+
df = build_frame([{"a": {"b": {"c": {"d": 1}}}}])
|
|
107
|
+
assert "a.b" in df.columns
|
|
108
|
+
assert isinstance(df["a.b"].iloc[0], dict)
|
|
109
|
+
|
|
110
|
+
def test_flatten_full_recurses(self):
|
|
111
|
+
df = build_frame([{"a": {"b": {"c": 1}}}], flatten="full")
|
|
112
|
+
assert "a.b.c" in df.columns
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TestDtypes:
|
|
116
|
+
|
|
117
|
+
def test_nullable_integer_survives_nulls(self):
|
|
118
|
+
# The whole reason for Int64 over int64: a null in an integer column
|
|
119
|
+
# must not silently turn the column into float64.
|
|
120
|
+
df = build_frame(
|
|
121
|
+
[{"n": 1}, {"n": None}, {"n": 3}],
|
|
122
|
+
dtypes={"n": "Int64"},
|
|
123
|
+
)
|
|
124
|
+
assert str(df["n"].dtype) == "Int64"
|
|
125
|
+
assert df["n"].iloc[0] == 1
|
|
126
|
+
assert pd.isna(df["n"].iloc[1])
|
|
127
|
+
|
|
128
|
+
def test_nullable_boolean_survives_nulls(self):
|
|
129
|
+
df = build_frame(
|
|
130
|
+
[{"b": True}, {"b": None}],
|
|
131
|
+
dtypes={"b": "boolean"},
|
|
132
|
+
)
|
|
133
|
+
assert str(df["b"].dtype) == "boolean"
|
|
134
|
+
|
|
135
|
+
def test_string_dtype_applied(self):
|
|
136
|
+
df = build_frame([{"s": "x"}], dtypes={"s": "string"})
|
|
137
|
+
assert str(df["s"].dtype) == "string"
|
|
138
|
+
|
|
139
|
+
def test_numeric_strings_are_rescued(self):
|
|
140
|
+
df = build_frame([{"n": "1"}, {"n": "2"}], dtypes={"n": "Int64"})
|
|
141
|
+
assert str(df["n"].dtype) == "Int64"
|
|
142
|
+
assert df["n"].iloc[1] == 2
|
|
143
|
+
|
|
144
|
+
def test_unconvertible_column_is_left_alone_not_raised(self):
|
|
145
|
+
# An analyst with a usable frame and one object column is far better
|
|
146
|
+
# served than one holding an exception.
|
|
147
|
+
df = build_frame([{"n": "not-a-number"}], dtypes={"n": "Int64"})
|
|
148
|
+
assert len(df) == 1
|
|
149
|
+
# And the VALUE must survive. to_numeric(errors="coerce") would have
|
|
150
|
+
# turned it into NA — a silent data loss dressed up as a successful
|
|
151
|
+
# conversion. The rescue is only allowed when it is lossless.
|
|
152
|
+
assert df["n"].iloc[0] == "not-a-number"
|
|
153
|
+
|
|
154
|
+
def test_partially_numeric_column_is_not_coerced(self):
|
|
155
|
+
# One bad value must not blank out the rest of the column.
|
|
156
|
+
df = build_frame(
|
|
157
|
+
[{"n": "1"}, {"n": "oops"}, {"n": "3"}],
|
|
158
|
+
dtypes={"n": "Int64"},
|
|
159
|
+
)
|
|
160
|
+
assert list(df["n"]) == ["1", "oops", "3"]
|
|
161
|
+
|
|
162
|
+
def test_numeric_rescue_still_applies_when_lossless(self):
|
|
163
|
+
# Nulls are not "lost" data, so a column of numeric strings with a
|
|
164
|
+
# None in it still converts.
|
|
165
|
+
df = build_frame(
|
|
166
|
+
[{"n": "1"}, {"n": None}, {"n": "3"}],
|
|
167
|
+
dtypes={"n": "Int64"},
|
|
168
|
+
)
|
|
169
|
+
assert str(df["n"].dtype) == "Int64"
|
|
170
|
+
assert df["n"].iloc[0] == 1
|
|
171
|
+
assert pd.isna(df["n"].iloc[1])
|
|
172
|
+
|
|
173
|
+
def test_missing_declared_column_is_ignored(self):
|
|
174
|
+
df = build_frame([{"a": 1}], dtypes={"absent": "Int64"})
|
|
175
|
+
assert "absent" not in df.columns
|
|
176
|
+
|
|
177
|
+
def test_dtype_false_skips_coercion(self):
|
|
178
|
+
df = build_frame([{"n": 1}], dtypes={"n": "string"}, dtype=False)
|
|
179
|
+
assert str(df["n"].dtype) != "string"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class TestOrder:
|
|
183
|
+
|
|
184
|
+
def test_declared_order_first_extras_appended(self):
|
|
185
|
+
df = build_frame(
|
|
186
|
+
[{"z": 1, "a": 2, "extra": 3}],
|
|
187
|
+
order=["a", "z"],
|
|
188
|
+
)
|
|
189
|
+
assert list(df.columns) == ["a", "z", "extra"]
|
|
190
|
+
|
|
191
|
+
def test_absent_declared_columns_are_skipped(self):
|
|
192
|
+
df = order_columns(pd.DataFrame({"a": [1]}), ["missing", "a"])
|
|
193
|
+
assert list(df.columns) == ["a"]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class TestEmpty:
|
|
197
|
+
|
|
198
|
+
def test_empty_result_keeps_columns_and_dtypes(self):
|
|
199
|
+
# A bare pd.DataFrame() would KeyError on the first column access;
|
|
200
|
+
# downstream code must behave the same whether or not rows came back.
|
|
201
|
+
df = build_frame([], order=["a", "b"], dtypes={"a": "Int64", "b": "string"})
|
|
202
|
+
assert list(df.columns) == ["a", "b"]
|
|
203
|
+
assert str(df["a"].dtype) == "Int64"
|
|
204
|
+
assert len(df) == 0
|
|
205
|
+
assert df.empty
|
|
206
|
+
|
|
207
|
+
def test_empty_without_model_is_still_a_frame(self):
|
|
208
|
+
df = build_frame([])
|
|
209
|
+
assert isinstance(df, pd.DataFrame)
|
|
210
|
+
assert len(df) == 0
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class TestDates:
|
|
214
|
+
|
|
215
|
+
def test_parse_dates_is_explicit_and_utc(self):
|
|
216
|
+
df = build_frame([{"when": "2026-01-02T03:04:05Z"}], parse_dates=["when"])
|
|
217
|
+
assert str(df["when"].dtype).startswith("datetime64")
|
|
218
|
+
assert df["when"].iloc[0].year == 2026
|
|
219
|
+
|
|
220
|
+
def test_unparseable_date_becomes_nat_not_an_error(self):
|
|
221
|
+
df = build_frame([{"when": "nonsense"}], parse_dates=["when"])
|
|
222
|
+
assert pd.isna(df["when"].iloc[0])
|
|
223
|
+
|
|
224
|
+
def test_dates_are_never_inferred(self):
|
|
225
|
+
# No $DATE sentinel exists in the model, so a date-looking string must
|
|
226
|
+
# stay a string unless the caller asks for parsing.
|
|
227
|
+
df = build_frame([{"when": "2026-01-02T03:04:05Z"}])
|
|
228
|
+
assert not str(df["when"].dtype).startswith("datetime64")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class TestScalarsAndSeries:
|
|
232
|
+
|
|
233
|
+
def test_scalar_list_becomes_value_column(self):
|
|
234
|
+
df = build_frame(["a", "b"])
|
|
235
|
+
assert list(df.columns) == ["value"]
|
|
236
|
+
assert list(df["value"]) == ["a", "b"]
|
|
237
|
+
|
|
238
|
+
def test_to_series_from_one_record(self):
|
|
239
|
+
s = to_series({"a": 1, "b": 2})
|
|
240
|
+
assert isinstance(s, pd.Series)
|
|
241
|
+
assert s["a"] == 1
|
|
242
|
+
|
|
243
|
+
def test_to_series_empty(self):
|
|
244
|
+
s = to_series(None)
|
|
245
|
+
assert isinstance(s, pd.Series)
|
|
246
|
+
assert len(s) == 0
|
|
247
|
+
|
|
248
|
+
def test_records_df_is_the_public_escape_hatch(self):
|
|
249
|
+
df = records_df([{"a": 1}])
|
|
250
|
+
assert list(df.columns) == ["a"]
|
|
@@ -7,6 +7,7 @@ class ProjectNameUtility
|
|
|
7
7
|
:make_point, :make_spec, :make_url, :param, :prepare_auth,
|
|
8
8
|
:prepare_body, :prepare_headers, :prepare_method,
|
|
9
9
|
:prepare_params, :prepare_path, :prepare_query,
|
|
10
|
+
:graphql_body, :graphql_errors,
|
|
10
11
|
:result_basic, :result_body, :result_headers,
|
|
11
12
|
:transform_request, :transform_response, :custom
|
|
12
13
|
|
|
@@ -45,6 +45,14 @@ class ProjectNamePagingFeature < ProjectNameBaseFeature
|
|
|
45
45
|
paging = ctx.ctrl.paging
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
# GraphQL paginates through operation VARIABLES, not the query string.
|
|
49
|
+
# This hook runs after make_spec, so spec.body already holds the
|
|
50
|
+
# { query, variables } envelope, and before make_fetch_def serialises it.
|
|
51
|
+
if "graphql" == VoxgigStruct.getprop(ctx.point, "kind")
|
|
52
|
+
_graphql_pre_request(ctx, paging)
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
48
56
|
if !paging["cursor"].nil?
|
|
49
57
|
spec.query[cursor_param] = paging["cursor"]
|
|
50
58
|
elsif spec.query[page_param].nil?
|
|
@@ -57,6 +65,41 @@ class ProjectNamePagingFeature < ProjectNameBaseFeature
|
|
|
57
65
|
end
|
|
58
66
|
end
|
|
59
67
|
|
|
68
|
+
# Relay pagination: the cursor is the `after` variable (or whatever the
|
|
69
|
+
# model named it), and the page size is `first`.
|
|
70
|
+
def _graphql_pre_request(ctx, paging)
|
|
71
|
+
body = ctx.spec.body
|
|
72
|
+
return unless body.is_a?(Hash)
|
|
73
|
+
|
|
74
|
+
variables = body["variables"]
|
|
75
|
+
unless variables.is_a?(Hash)
|
|
76
|
+
variables = {}
|
|
77
|
+
body["variables"] = variables
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
after_var = @options["afterVar"] || "after"
|
|
81
|
+
first_var = @options["firstVar"] || "first"
|
|
82
|
+
|
|
83
|
+
# Only bind variables the operation actually declares, or the server
|
|
84
|
+
# rejects the document.
|
|
85
|
+
declared = {}
|
|
86
|
+
varlist = VoxgigStruct.getpath(ctx.point, "graphql.vars")
|
|
87
|
+
if varlist.is_a?(Array)
|
|
88
|
+
varlist.each do |v|
|
|
89
|
+
name = VoxgigStruct.getprop(v, "name")
|
|
90
|
+
declared[name] = true unless name.nil?
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if !paging["cursor"].nil? && declared[after_var]
|
|
95
|
+
variables[after_var] = paging["cursor"]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if !@options["limit"].nil? && variables[first_var].nil? && declared[first_var]
|
|
99
|
+
variables[first_var] = @options["limit"]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
60
103
|
def PreResult(ctx)
|
|
61
104
|
return unless @active
|
|
62
105
|
return unless _list?(ctx)
|
|
@@ -75,6 +118,39 @@ class ProjectNamePagingFeature < ProjectNameBaseFeature
|
|
|
75
118
|
"hasMore" => false,
|
|
76
119
|
}
|
|
77
120
|
|
|
121
|
+
# Set when the response states hasMore outright, rather than leaving it
|
|
122
|
+
# to be inferred from the presence of a cursor.
|
|
123
|
+
explicit_more = false
|
|
124
|
+
|
|
125
|
+
# Relay connections carry the cursor in pageInfo, at the path the model
|
|
126
|
+
# recorded for this op.
|
|
127
|
+
page = VoxgigStruct.getpath(ctx.point, "graphql.page")
|
|
128
|
+
if page.is_a?(Hash) && body.is_a?(Hash)
|
|
129
|
+
# `connpath` locates the connection object inside the response envelope
|
|
130
|
+
# (data.<field>); the cursor/more paths are relative to it.
|
|
131
|
+
connpath = page["connpath"]
|
|
132
|
+
conn = body
|
|
133
|
+
if connpath.is_a?(String) && "" != connpath
|
|
134
|
+
sub = VoxgigStruct.getpath(body, connpath)
|
|
135
|
+
conn = sub unless sub.nil?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
cursorpath = page["cursor"]
|
|
139
|
+
if cursorpath.is_a?(String) && "" != cursorpath
|
|
140
|
+
cursor = VoxgigStruct.getpath(conn, cursorpath)
|
|
141
|
+
paging["cursor"] = cursor unless cursor.nil?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
morepath = page["more"]
|
|
145
|
+
if morepath.is_a?(String) && "" != morepath
|
|
146
|
+
more = VoxgigStruct.getpath(conn, morepath)
|
|
147
|
+
if more == true || more == false
|
|
148
|
+
paging["hasMore"] = more
|
|
149
|
+
explicit_more = true
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
78
154
|
# Link: <...>; rel="next"
|
|
79
155
|
link = _header(headers, "link")
|
|
80
156
|
unless link.nil?
|
|
@@ -87,11 +163,21 @@ class ProjectNamePagingFeature < ProjectNameBaseFeature
|
|
|
87
163
|
paging["next"] = paging["next"] || body["next"] unless body["next"].nil?
|
|
88
164
|
paging["cursor"] = body["cursor"] unless body["cursor"].nil?
|
|
89
165
|
paging["cursor"] = body["nextCursor"] unless body["nextCursor"].nil?
|
|
90
|
-
|
|
166
|
+
if body["hasMore"] == true || body["hasMore"] == false
|
|
167
|
+
paging["hasMore"] = body["hasMore"]
|
|
168
|
+
explicit_more = true
|
|
169
|
+
end
|
|
91
170
|
end
|
|
92
171
|
|
|
93
|
-
|
|
94
|
-
|
|
172
|
+
# Cursor presence only INFERS another page. When the server stated the
|
|
173
|
+
# answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
|
|
174
|
+
# that wins: a final page normally carries both an end cursor and
|
|
175
|
+
# hasNextPage false, and inferring from the cursor there would send the
|
|
176
|
+
# caller back for a page that does not exist, forever.
|
|
177
|
+
unless explicit_more
|
|
178
|
+
paging["hasMore"] = paging["hasMore"] ||
|
|
179
|
+
!paging["next"].nil? || !paging["cursor"].nil? || !paging["nextPage"].nil?
|
|
180
|
+
end
|
|
95
181
|
|
|
96
182
|
result.paging = paging
|
|
97
183
|
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
# GraphqlBody — build { query, variables } for a point, binding the
|
|
11
|
+
# op's arguments to the document's declared variables.
|
|
12
|
+
#
|
|
13
|
+
# GraphqlErrors — 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
|
+
require_relative 'struct/voxgig_struct'
|
|
19
|
+
|
|
20
|
+
module ProjectNameUtilities
|
|
21
|
+
# Content type every GraphQL-over-HTTP request uses.
|
|
22
|
+
GRAPHQL_CONTENT_TYPE = 'application/json'
|
|
23
|
+
|
|
24
|
+
# Map a GraphQL error to the same error codes the HTTP path produces, so
|
|
25
|
+
# a caller handles auth or rate limiting identically on both transports.
|
|
26
|
+
# Servers put the machine-readable code in `extensions.code`;
|
|
27
|
+
# Linear-style APIs use `extensions.type`.
|
|
28
|
+
GraphqlErrorCode = ->(gqlerr) {
|
|
29
|
+
ext = VoxgigStruct.getprop(gqlerr, 'extensions') || {}
|
|
30
|
+
|
|
31
|
+
raw = VoxgigStruct.getprop(ext, 'code') ||
|
|
32
|
+
VoxgigStruct.getprop(ext, 'type') || ''
|
|
33
|
+
raw = raw.to_s.upcase
|
|
34
|
+
|
|
35
|
+
if raw.include?('AUTH') || raw.include?('FORBIDDEN') ||
|
|
36
|
+
raw.include?('UNAUTHENTICATED')
|
|
37
|
+
next 'request_auth'
|
|
38
|
+
end
|
|
39
|
+
if raw.include?('RATELIMIT') || raw.include?('RATE_LIMIT') ||
|
|
40
|
+
raw.include?('TOO_MANY')
|
|
41
|
+
next 'request_ratelimit'
|
|
42
|
+
end
|
|
43
|
+
if raw.include?('BAD_USER_INPUT') || raw.include?('VALIDATION') ||
|
|
44
|
+
raw.include?('INVALID')
|
|
45
|
+
next 'request_invalid'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
'request_graphql'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Build the request body for a GraphQL point.
|
|
52
|
+
#
|
|
53
|
+
# Variables come from the op's own arguments: a named variable binds to
|
|
54
|
+
# the like-named argument (`from`), and the input-object variable (empty
|
|
55
|
+
# `from`) takes the request data as a whole — which is what makes a
|
|
56
|
+
# generated create/update call look exactly like its REST equivalent.
|
|
57
|
+
GraphqlBody = ->(ctx) {
|
|
58
|
+
gql = VoxgigStruct.getprop(ctx.point, 'graphql')
|
|
59
|
+
next nil unless gql.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
# reqmatch/reqdata hold the caller's arguments for THIS call; data/match
|
|
62
|
+
# hold the entity's current state. Which pair depends on whether the op
|
|
63
|
+
# takes match or data input. A named variable falls back to the current
|
|
64
|
+
# state, so updating a loaded entity with just {title} still binds the
|
|
65
|
+
# stored id the mutation requires.
|
|
66
|
+
datainput = ctx.op && 'data' == ctx.op.input
|
|
67
|
+
reqsrc = datainput ? ctx.reqdata : ctx.reqmatch
|
|
68
|
+
datasrc = datainput ? ctx.data : ctx.match
|
|
69
|
+
reqsrc = {} unless reqsrc.is_a?(Hash)
|
|
70
|
+
datasrc = {} unless datasrc.is_a?(Hash)
|
|
71
|
+
|
|
72
|
+
variables = {}
|
|
73
|
+
|
|
74
|
+
varlist = VoxgigStruct.getprop(gql, 'vars')
|
|
75
|
+
varlist = [] unless varlist.is_a?(Array)
|
|
76
|
+
|
|
77
|
+
varlist.each do |spec|
|
|
78
|
+
next unless spec.is_a?(Hash)
|
|
79
|
+
|
|
80
|
+
name = VoxgigStruct.getprop(spec, 'name')
|
|
81
|
+
from = VoxgigStruct.getprop(spec, 'from')
|
|
82
|
+
|
|
83
|
+
if from.nil? || '' == from
|
|
84
|
+
# The input object IS the request body. Strip the action selector,
|
|
85
|
+
# which is an SDK-side point discriminator, not an API field.
|
|
86
|
+
body = {}
|
|
87
|
+
reqsrc.each { |k, v| body[k] = v unless '$action' == k }
|
|
88
|
+
variables[name] = body
|
|
89
|
+
next
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Only send variables the caller actually supplied: sending an
|
|
93
|
+
# explicit null would clear a field on many APIs.
|
|
94
|
+
val = VoxgigStruct.getprop(reqsrc, from)
|
|
95
|
+
val = VoxgigStruct.getprop(datasrc, from) if val.nil?
|
|
96
|
+
variables[name] = val unless val.nil?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
{
|
|
100
|
+
'query' => VoxgigStruct.getprop(gql, 'doc'),
|
|
101
|
+
'variables' => variables
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Inspect a decoded GraphQL response body and record a failure when the
|
|
106
|
+
# server reported one. Returns true when an error was recorded.
|
|
107
|
+
#
|
|
108
|
+
# Partial data (`data` alongside `errors`) is treated as failure: the
|
|
109
|
+
# REST surface has no partial-success concept, and silently returning
|
|
110
|
+
# half an object would be worse than failing. The raw envelope stays
|
|
111
|
+
# available on the result for callers that need it.
|
|
112
|
+
GraphqlErrors = ->(ctx) {
|
|
113
|
+
result = ctx.result
|
|
114
|
+
point = ctx.point
|
|
115
|
+
|
|
116
|
+
next false if result.nil? || point.nil?
|
|
117
|
+
next false unless 'graphql' == VoxgigStruct.getprop(point, 'kind')
|
|
118
|
+
|
|
119
|
+
errors = VoxgigStruct.getprop(result.body, 'errors')
|
|
120
|
+
next false unless errors.is_a?(Array) && !errors.empty?
|
|
121
|
+
|
|
122
|
+
first = errors[0]
|
|
123
|
+
msg = VoxgigStruct.getprop(first, 'message') || 'graphql error'
|
|
124
|
+
msg = msg + ' (+' + (errors.length - 1).to_s + ' more)' if 1 < errors.length
|
|
125
|
+
|
|
126
|
+
result.err = ctx.make_error(GraphqlErrorCode.call(first), 'graphql: ' + msg)
|
|
127
|
+
result.ok = false
|
|
128
|
+
|
|
129
|
+
true
|
|
130
|
+
}
|
|
131
|
+
end
|
|
@@ -44,7 +44,7 @@ module ProjectNameUtilities
|
|
|
44
44
|
"headers" => { "`$CHILD`" => "`$STRING`" },
|
|
45
45
|
"allow" => {
|
|
46
46
|
"method" => "GET,PUT,POST,PATCH,DELETE,OPTIONS",
|
|
47
|
-
"op" => "create,update,load,list,remove,command,direct",
|
|
47
|
+
"op" => "create,update,load,list,remove,command,direct,graphql",
|
|
48
48
|
},
|
|
49
49
|
"entity" => { "`$CHILD`" => { "`$OPEN`" => true, "active" => false, "alias" => {} } },
|
|
50
50
|
"feature" => { "`$CHILD`" => { "`$OPEN`" => true, "active" => false } },
|
|
@@ -15,6 +15,12 @@ module ProjectNameUtilities
|
|
|
15
15
|
utility.result_basic.call(ctx)
|
|
16
16
|
utility.result_headers.call(ctx)
|
|
17
17
|
utility.result_body.call(ctx)
|
|
18
|
+
|
|
19
|
+
# GraphQL reports failures as a top-level `errors` array under HTTP
|
|
20
|
+
# 200, so result_basic's status check never sees them. Lift them here,
|
|
21
|
+
# before the response transform tries to unwrap data that is not there.
|
|
22
|
+
utility.graphql_errors.call(ctx)
|
|
23
|
+
|
|
18
24
|
utility.transform_response.call(ctx)
|
|
19
25
|
|
|
20
26
|
result.ok = true if result.err.nil?
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# ProjectName SDK utility: make_spec
|
|
2
2
|
require_relative 'struct/voxgig_struct'
|
|
3
|
+
require_relative 'graphql'
|
|
3
4
|
require_relative '../core/spec'
|
|
4
5
|
module ProjectNameUtilities
|
|
5
6
|
MakeSpec = ->(ctx) {
|
|
@@ -36,8 +37,23 @@ module ProjectNameUtilities
|
|
|
36
37
|
ctx.spec.params = utility.prepare_params.call(ctx)
|
|
37
38
|
ctx.spec.query = utility.prepare_query.call(ctx)
|
|
38
39
|
ctx.spec.headers = utility.prepare_headers.call(ctx)
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
if 'graphql' == VoxgigStruct.getprop(point, 'kind')
|
|
42
|
+
# GraphQL addresses one endpoint: no path parts, no query string, and
|
|
43
|
+
# the body carries the operation. prepare_body is skipped
|
|
44
|
+
# deliberately — it only emits a body for data-input ops, whereas
|
|
45
|
+
# every GraphQL op posts one, including load/list/remove.
|
|
46
|
+
ctx.spec.body = utility.graphql_body.call(ctx)
|
|
47
|
+
ctx.spec.path = ''
|
|
48
|
+
# prepare_query already copied the op's match arguments into the
|
|
49
|
+
# query string. Those same values are bound as operation variables,
|
|
50
|
+
# so leaving them would send /graphql?id=i1.
|
|
51
|
+
ctx.spec.query = {}
|
|
52
|
+
ctx.spec.headers['content-type'] = GRAPHQL_CONTENT_TYPE
|
|
53
|
+
else
|
|
54
|
+
ctx.spec.body = utility.prepare_body.call(ctx)
|
|
55
|
+
ctx.spec.path = utility.prepare_path.call(ctx)
|
|
56
|
+
end
|
|
41
57
|
|
|
42
58
|
ctx.ctrl.explain["spec"] = ctx.spec if ctx.ctrl.explain
|
|
43
59
|
|
|
@@ -24,6 +24,7 @@ require_relative 'prepare_method'
|
|
|
24
24
|
require_relative 'prepare_params'
|
|
25
25
|
require_relative 'prepare_path'
|
|
26
26
|
require_relative 'prepare_query'
|
|
27
|
+
require_relative 'graphql'
|
|
27
28
|
require_relative 'result_basic'
|
|
28
29
|
require_relative 'result_body'
|
|
29
30
|
require_relative 'result_headers'
|
|
@@ -55,6 +56,8 @@ ProjectNameUtility.registrar = ->(u) {
|
|
|
55
56
|
u.prepare_params = ProjectNameUtilities::PrepareParams
|
|
56
57
|
u.prepare_path = ProjectNameUtilities::PreparePath
|
|
57
58
|
u.prepare_query = ProjectNameUtilities::PrepareQuery
|
|
59
|
+
u.graphql_body = ProjectNameUtilities::GraphqlBody
|
|
60
|
+
u.graphql_errors = ProjectNameUtilities::GraphqlErrors
|
|
58
61
|
u.result_basic = ProjectNameUtilities::ResultBasic
|
|
59
62
|
u.result_body = ProjectNameUtilities::ResultBody
|
|
60
63
|
u.result_headers = ProjectNameUtilities::ResultHeaders
|