@voxgig/sdkgen 3.5.1 → 3.7.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/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/AgentGuideTop.js +17 -0
- package/dist/cmp/AgentGuideTop.js.map +1 -1
- package/dist/cmp/ExternalTarget.js +1 -1
- package/dist/cmp/ExternalTarget.js.map +1 -1
- package/dist/cmp/Readme.js +2 -0
- package/dist/cmp/Readme.js.map +1 -1
- package/dist/cmp/ReadmeStation.d.ts +2 -0
- package/dist/cmp/ReadmeStation.js +57 -0
- package/dist/cmp/ReadmeStation.js.map +1 -0
- package/dist/helpers/naming.d.ts +6 -1
- package/dist/helpers/naming.js +156 -5
- package/dist/helpers/naming.js.map +1 -1
- package/dist/helpers/opShape.js +72 -0
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/helpers/packageMeta.d.ts +2 -1
- package/dist/helpers/packageMeta.js +57 -20
- package/dist/helpers/packageMeta.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -3
- package/dist/sdkgen.js +63 -18
- package/dist/sdkgen.js.map +1 -1
- package/dist/testkit.d.ts +39 -0
- package/dist/testkit.js +663 -0
- package/dist/testkit.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +1 -1
- package/dist/utility.js +16 -2
- package/dist/utility.js.map +1 -1
- package/package.json +32 -8
- package/project/.sdk/src/cmp/c/Config_c.ts +34 -13
- package/project/.sdk/src/cmp/c/Test_c.ts +5 -7
- package/project/.sdk/src/cmp/clojure/Test_clojure.ts +3 -7
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +9 -46
- package/project/.sdk/src/cmp/cpp/Test_cpp.ts +4 -7
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +7 -1
- package/project/.sdk/src/cmp/csharp/Package_csharp.ts +1 -1
- package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -7
- package/project/.sdk/src/cmp/dart/Config_dart.ts +14 -2
- package/project/.sdk/src/cmp/dart/Test_dart.ts +3 -4
- package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +5 -0
- package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -0
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +13 -4
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +12 -2
- package/project/.sdk/src/cmp/elixir/Package_elixir.ts +42 -3
- package/project/.sdk/src/cmp/elixir/Test_elixir.ts +5 -6
- package/project/.sdk/src/cmp/go/Config_go.ts +8 -1
- package/project/.sdk/src/cmp/go/EntityTypes_go.ts +1 -1
- package/project/.sdk/src/cmp/go/Entity_go.ts +1 -1
- package/project/.sdk/src/cmp/go/Gitignore_go.ts +6 -0
- package/project/.sdk/src/cmp/go/Main_go.ts +2 -2
- package/project/.sdk/src/cmp/go/Package_go.ts +1 -1
- package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +59 -6
- package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +1 -1
- package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +1 -1
- package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +1 -1
- package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +1 -1
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +1 -1
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +1 -1
- package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +1 -1
- package/project/.sdk/src/cmp/go/Test_go.ts +6 -8
- package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +6 -2
- package/project/.sdk/src/cmp/go-mcp/Main_go-mcp.ts +6 -2
- package/project/.sdk/src/cmp/java/Config_java.ts +8 -1
- package/project/.sdk/src/cmp/java/Test_java.ts +4 -7
- package/project/.sdk/src/cmp/js/Config_js.ts +9 -1
- package/project/.sdk/src/cmp/js/Package_js.ts +1 -1
- package/project/.sdk/src/cmp/js/Test_js.ts +4 -7
- package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +7 -0
- package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +8 -0
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +33 -5
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +8 -1
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +4 -7
- package/project/.sdk/src/cmp/lean/Test_lean.ts +3 -3
- package/project/.sdk/src/cmp/lua/Config_lua.ts +11 -2
- package/project/.sdk/src/cmp/lua/Package_lua.ts +26 -3
- package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -7
- package/project/.sdk/src/cmp/ocaml/Test_ocaml.ts +3 -7
- package/project/.sdk/src/cmp/perl/Config_perl.ts +8 -60
- package/project/.sdk/src/cmp/perl/Package_perl.ts +1 -1
- package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -7
- package/project/.sdk/src/cmp/php/Config_php.ts +11 -2
- package/project/.sdk/src/cmp/php/EntityTypes_php.ts +13 -2
- package/project/.sdk/src/cmp/php/Main_php.ts +25 -0
- package/project/.sdk/src/cmp/php/Package_php.ts +2 -2
- package/project/.sdk/src/cmp/php/Test_php.ts +5 -7
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +23 -6
- package/project/.sdk/src/cmp/py/Config_py.ts +11 -2
- package/project/.sdk/src/cmp/py/Main_py.ts +17 -7
- package/project/.sdk/src/cmp/py/Package_py.ts +2 -2
- package/project/.sdk/src/cmp/py/Test_py.ts +5 -7
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +23 -6
- package/project/.sdk/src/cmp/py-data/Main_py-data.ts +14 -19
- package/project/.sdk/src/cmp/rb/Config_rb.ts +7 -1
- package/project/.sdk/src/cmp/rb/Package_rb.ts +3 -3
- package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +1 -1
- package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -7
- package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -2
- package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -7
- package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -2
- package/project/.sdk/src/cmp/seneca-provider/Gitignore_seneca-provider.ts +161 -0
- package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +20 -11
- package/project/.sdk/src/cmp/swift/Config_swift.ts +8 -54
- package/project/.sdk/src/cmp/swift/Package_swift.ts +52 -10
- package/project/.sdk/src/cmp/swift/Test_swift.ts +5 -7
- package/project/.sdk/src/cmp/ts/Config_ts.ts +9 -1
- package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +5 -2
- package/project/.sdk/src/cmp/ts/Entity_ts.ts +8 -2
- package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
- package/project/.sdk/src/cmp/ts/Test_ts.ts +8 -7
- package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +7 -0
- package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +8 -0
- package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +1 -1
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +12 -4
- package/project/.sdk/src/cmp/zig/Test_zig.ts +3 -7
- package/project/.sdk/tm/c/Makefile +6 -1
- package/project/.sdk/tm/c/utility/make_options.c +12 -1
- package/project/.sdk/tm/c/utility/make_point.c +64 -3
- package/project/.sdk/tm/clojure/src/sdk/core.clj +49 -14
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +64 -4
- package/project/.sdk/tm/csharp/feature/TestFeature.cs +36 -2
- package/project/.sdk/tm/csharp/utility/Fetcher.cs +23 -5
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +13 -0
- package/project/.sdk/tm/csharp/utility/MakePoint.cs +65 -2
- package/project/.sdk/tm/dart/lib/feature/test/TestFeature.dart +34 -4
- package/project/.sdk/tm/dart/lib/utility/FetcherUtility.dart +9 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +17 -0
- package/project/.sdk/tm/dart/lib/utility/MakePointUtility.dart +48 -2
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +109 -13
- package/project/.sdk/tm/elixir/test/feature_test.exs +19 -0
- package/project/.sdk/tm/elixir/test/primary_utility_test.exs +72 -0
- package/project/.sdk/tm/go/feature/test_feature.go +34 -2
- package/project/.sdk/tm/go/test/primary_utility_test.go +42 -20
- package/project/.sdk/tm/go/test/runner_test.go +1 -1
- package/project/.sdk/tm/go/utility/fetcher.go +14 -0
- package/project/.sdk/tm/go/utility/make_options.go +31 -4
- package/project/.sdk/tm/go/utility/make_point.go +66 -2
- package/project/.sdk/tm/java/feature/TestFeature.java +42 -2
- package/project/.sdk/tm/java/utility/Fetcher.java +11 -1
- package/project/.sdk/tm/java/utility/MakeOptions.java +11 -0
- package/project/.sdk/tm/java/utility/MakePoint.java +61 -2
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +22 -3
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +20 -2
- package/project/.sdk/tm/js/src/utility/MakePointUtility.js +72 -2
- package/project/.sdk/tm/kotlin/feature/TestFeature.kt +31 -2
- package/project/.sdk/tm/kotlin/utility/Fetcher.kt +10 -1
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +11 -0
- package/project/.sdk/tm/kotlin/utility/MakePoint.kt +58 -2
- package/project/.sdk/tm/lean/src/SdkUtility.lean +28 -0
- package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +13 -2
- package/project/.sdk/tm/lua/utility/make_options.lua +44 -0
- package/project/.sdk/tm/lua/utility/make_point.lua +65 -2
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +40 -2
- package/project/.sdk/tm/perl/utility/fetcher.pm +10 -1
- package/project/.sdk/tm/perl/utility/make_options.pm +36 -0
- package/project/.sdk/tm/perl/utility/make_point.pm +55 -2
- package/project/.sdk/tm/php/feature/TestFeature.php +30 -1
- package/project/.sdk/tm/php/test/PipelineTest.php +20 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +12 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +58 -0
- package/project/.sdk/tm/php/utility/MakePoint.php +49 -1
- package/project/.sdk/tm/py/pkg/utility/fetcher.py +13 -1
- package/project/.sdk/tm/py/pkg/utility/make_options.py +23 -0
- package/project/.sdk/tm/py/pkg/utility/make_point.py +56 -2
- package/project/.sdk/tm/rb/utility/make_options.rb +18 -0
- package/project/.sdk/tm/rb/utility/make_point.rb +42 -2
- package/project/.sdk/tm/rust/tests/common/mod.rs +1 -1
- package/project/.sdk/tm/rust/tests/feature_test.rs +27 -0
- package/project/.sdk/tm/rust/utility/fetcher.rs +8 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +15 -0
- package/project/.sdk/tm/rust/utility/make_point.rs +69 -2
- package/project/.sdk/tm/scala/feature/TestFeature.scala +36 -1
- package/project/.sdk/tm/scala/utility/Make.scala +57 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +29 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +24 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +51 -2
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +12 -0
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +22 -3
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +20 -2
- package/project/.sdk/tm/ts/src/utility/MakePointUtility.ts +72 -2
- package/project/.sdk/tm/ts/test/utility/Corpus.test.ts +85 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +10 -17
- package/project/.sdk/tm/zig/core/utility.zig +66 -3
- package/project/sdkgen-package.json +1 -2
- package/src/cmp/AgentGuideTop.ts +18 -0
- package/src/cmp/ExternalTarget.ts +1 -1
- package/src/cmp/Readme.ts +2 -0
- package/src/cmp/ReadmeStation.ts +69 -0
- package/src/helpers/naming.ts +172 -5
- package/src/helpers/opShape.ts +85 -0
- package/src/helpers/packageMeta.ts +59 -20
- package/src/sdkgen.ts +70 -20
- package/src/testkit.ts +863 -0
- package/src/utility.ts +17 -2
- package/testkit.d.ts +3 -0
- package/testkit.js +12 -0
- package/project/.sdk/model/target/haskell.aontu +0 -72
- package/project/.sdk/src/cmp/haskell/Config_haskell.ts +0 -98
- package/project/.sdk/src/cmp/haskell/Entity_haskell.ts +0 -13
- package/project/.sdk/src/cmp/haskell/Gitignore_haskell.ts +0 -27
- package/project/.sdk/src/cmp/haskell/Main_haskell.ts +0 -123
- package/project/.sdk/src/cmp/haskell/Package_haskell.ts +0 -60
- package/project/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +0 -186
- package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +0 -135
- package/project/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +0 -48
- package/project/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +0 -168
- package/project/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +0 -51
- package/project/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +0 -67
- package/project/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +0 -162
- package/project/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +0 -74
- package/project/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +0 -241
- package/project/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +0 -408
- package/project/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +0 -31
- package/project/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +0 -104
- package/project/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +0 -80
- package/project/.sdk/src/cmp/haskell/Test_haskell.ts +0 -300
- package/project/.sdk/src/cmp/haskell/tsconfig.json +0 -15
- package/project/.sdk/src/cmp/haskell/utility_haskell.ts +0 -166
- package/project/.sdk/tm/haskell/LICENSE +0 -21
- package/project/.sdk/tm/haskell/Makefile +0 -22
- package/project/.sdk/tm/haskell/VERSION +0 -1
- package/project/.sdk/tm/haskell/src/SdkFeatures.hs +0 -1440
- package/project/.sdk/tm/haskell/src/SdkHelpers.hs +0 -337
- package/project/.sdk/tm/haskell/src/SdkJson.hs +0 -111
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +0 -1219
- package/project/.sdk/tm/haskell/src/SdkTypes.hs +0 -195
- package/project/.sdk/tm/haskell/src/VoxgigStruct.hs +0 -2299
- package/project/.sdk/tm/haskell/src/Vregex.hs +0 -237
- package/project/.sdk/tm/haskell/src/feature/README.md +0 -5
- package/project/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/test/Harness.hs +0 -281
- package/project/.sdk/tm/haskell/test/Runner.hs +0 -47
- package/project/.sdk/tm/haskell/test/StructCorpus.hs +0 -449
- package/project/.sdk/tm/haskell/test/TCustomUtility.hs +0 -38
- package/project/.sdk/tm/haskell/test/TFeature.hs +0 -794
- package/project/.sdk/tm/haskell/test/TNetsim.hs +0 -50
- package/project/.sdk/tm/haskell/test/TPipeline.hs +0 -334
- package/project/.sdk/tm/haskell/test/TPrimaryUtility.hs +0 -386
- package/project/.sdk/tm/haskell/test/Testutil.hs +0 -59
|
@@ -7,6 +7,33 @@ use crate::core::types::OutVal;
|
|
|
7
7
|
use crate::utility::voxgigstruct as vs;
|
|
8
8
|
use crate::utility::voxgigstruct::Value;
|
|
9
9
|
|
|
10
|
+
// How many path segments a point has.
|
|
11
|
+
fn parts_len(point: &Value) -> usize {
|
|
12
|
+
match getp(point, "parts") {
|
|
13
|
+
Value::List(l) => l.borrow().len(),
|
|
14
|
+
_ => 0,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
19
|
+
// record's identifier (/boards/{id}); a cross-reference that also returns
|
|
20
|
+
// the entity ends in the relationship's name (/posts/{id}/author). That,
|
|
21
|
+
// then fewest segments, is what tells the entity's own route from a
|
|
22
|
+
// cross-reference. The same rule runs at generation time, in
|
|
23
|
+
// helpers/opShape.ts — both sides must move together.
|
|
24
|
+
fn terminal_param(point: &Value) -> bool {
|
|
25
|
+
match getp(point, "parts") {
|
|
26
|
+
Value::List(l) => {
|
|
27
|
+
let parts = l.borrow();
|
|
28
|
+
match parts.last() {
|
|
29
|
+
Some(Value::Str(s)) => s.starts_with('{'),
|
|
30
|
+
_ => false,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
_ => false,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
10
37
|
pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
|
|
11
38
|
match ctx.out_get("point") {
|
|
12
39
|
// A PrePoint feature hook (e.g. rbac) may short-circuit the
|
|
@@ -60,9 +87,10 @@ pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
|
|
|
60
87
|
};
|
|
61
88
|
|
|
62
89
|
let mut point = Value::Noval;
|
|
90
|
+
let mut matched = false;
|
|
63
91
|
for i in 0..plen {
|
|
64
|
-
|
|
65
|
-
let select_def = to_map(&getp(&
|
|
92
|
+
let cand = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
|
|
93
|
+
let select_def = to_map(&getp(&cand, "select"));
|
|
66
94
|
let mut found = true;
|
|
67
95
|
|
|
68
96
|
if !selector.is_noval() && !select_def.is_noval() {
|
|
@@ -89,10 +117,49 @@ pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
|
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
if found {
|
|
120
|
+
point = cand;
|
|
121
|
+
matched = true;
|
|
92
122
|
break;
|
|
93
123
|
}
|
|
94
124
|
}
|
|
95
125
|
|
|
126
|
+
// select.exist can list more than the params needed to pick a point,
|
|
127
|
+
// so nothing matches — fall back to the entity's own route rather
|
|
128
|
+
// than whichever point came last.
|
|
129
|
+
if !matched {
|
|
130
|
+
// A request naming an action reaches here only because that
|
|
131
|
+
// action's own point failed its exist test, so it is unbuildable
|
|
132
|
+
// whatever we pick. Refuse it BEFORE choosing a fallback: the
|
|
133
|
+
// guard below compares the chosen point's $action and would wave
|
|
134
|
+
// the request through whenever the fallback lands on the action
|
|
135
|
+
// point itself.
|
|
136
|
+
let unmatched_action = getp(&reqselector, "$action");
|
|
137
|
+
if !unmatched_action.is_noval() {
|
|
138
|
+
return Err(ctx.make_error(
|
|
139
|
+
"point_action_invalid",
|
|
140
|
+
&format!(
|
|
141
|
+
"Operation \"{}\" action \"{}\" is not valid.",
|
|
142
|
+
op.name,
|
|
143
|
+
vs::stringify(&unmatched_action, None, false)
|
|
144
|
+
),
|
|
145
|
+
));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
point = vs::get_elem(&points, &Value::Num(0.0), Value::Noval);
|
|
149
|
+
for i in 0..plen {
|
|
150
|
+
let cand = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
|
|
151
|
+
let cand_term = terminal_param(&cand);
|
|
152
|
+
let best_term = terminal_param(&point);
|
|
153
|
+
if cand_term != best_term {
|
|
154
|
+
if cand_term {
|
|
155
|
+
point = cand;
|
|
156
|
+
}
|
|
157
|
+
} else if parts_len(&cand) < parts_len(&point) {
|
|
158
|
+
point = cand;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
96
163
|
let req_action = getp(&reqselector, "$action");
|
|
97
164
|
if !req_action.is_noval() && !point.is_noval() {
|
|
98
165
|
let point_select = to_map(&getp(&point, "select"));
|
|
@@ -215,9 +215,44 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
|
|
|
215
215
|
private def buildArgs(ctx: Context, op: Operation, args: JMap[String, Object]): Object = {
|
|
216
216
|
val opname = op.name
|
|
217
217
|
|
|
218
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
219
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
220
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
221
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
222
|
+
// failing that the shallower path wins.
|
|
218
223
|
val points = Struct.getpath(ctx.config,
|
|
219
224
|
java.util.List.of("entity", if (ctx.entity == null) "" else ctx.entity.getName(), "op", opname, "points"))
|
|
220
|
-
|
|
225
|
+
var point = Struct.getelem(points, java.lang.Integer.valueOf(0))
|
|
226
|
+
points match {
|
|
227
|
+
case plist: java.util.List[_] =>
|
|
228
|
+
def pointPartsLen(p: Object): Int =
|
|
229
|
+
Struct.getprop(p, "parts") match {
|
|
230
|
+
case parts: java.util.List[_] => parts.size()
|
|
231
|
+
case _ => 0
|
|
232
|
+
}
|
|
233
|
+
def pointTerminalParam(p: Object): Boolean =
|
|
234
|
+
Struct.getprop(p, "parts") match {
|
|
235
|
+
case parts: java.util.List[_] if parts.size() > 0 =>
|
|
236
|
+
parts.get(parts.size() - 1) match {
|
|
237
|
+
case s: String => s.startsWith("{")
|
|
238
|
+
case _ => false
|
|
239
|
+
}
|
|
240
|
+
case _ => false
|
|
241
|
+
}
|
|
242
|
+
var k = 0
|
|
243
|
+
while (k < plist.size()) {
|
|
244
|
+
val cand = plist.get(k).asInstanceOf[Object]
|
|
245
|
+
val candTerm = pointTerminalParam(cand)
|
|
246
|
+
val bestTerm = pointTerminalParam(point)
|
|
247
|
+
if (candTerm != bestTerm) {
|
|
248
|
+
if (candTerm) point = cand
|
|
249
|
+
} else if (pointPartsLen(cand) < pointPartsLen(point)) {
|
|
250
|
+
point = cand
|
|
251
|
+
}
|
|
252
|
+
k += 1
|
|
253
|
+
}
|
|
254
|
+
case _ =>
|
|
255
|
+
}
|
|
221
256
|
|
|
222
257
|
val paramsPath = Struct.getpath(point, java.util.List.of("args", "params"))
|
|
223
258
|
val reqdParams = Struct.select(paramsPath, Struct.jm("reqd", java.lang.Boolean.TRUE))
|
|
@@ -94,11 +94,12 @@ object MakePoint {
|
|
|
94
94
|
val selector: JMap[String, Object] = if ("data" == op.input) ctx.data else ctx.matchData
|
|
95
95
|
|
|
96
96
|
var point: JMap[String, Object] = null
|
|
97
|
+
var matched = false
|
|
97
98
|
var i = 0
|
|
98
99
|
var break = false
|
|
99
100
|
while (i < op.points.size() && !break) {
|
|
100
|
-
|
|
101
|
-
val selectDef = Helpers.toMapAny(Struct.getprop(
|
|
101
|
+
val cand = op.points.get(i)
|
|
102
|
+
val selectDef = Helpers.toMapAny(Struct.getprop(cand, "select"))
|
|
102
103
|
var found = true
|
|
103
104
|
|
|
104
105
|
if (selector != null && selectDef != null) {
|
|
@@ -122,7 +123,60 @@ object MakePoint {
|
|
|
122
123
|
if (!Objects.equals(reqAction, selectAction)) found = false
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
if (found) break = true else i += 1
|
|
126
|
+
if (found) { point = cand; matched = true; break = true } else i += 1
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// select.exist can list more than the params needed to pick a point
|
|
130
|
+
// (for /boards/{id} it is Trello's 17 optional query-includes), so a
|
|
131
|
+
// plain {id} call matches NOTHING. Fall back to the entity's own route
|
|
132
|
+
// rather than whichever point came last.
|
|
133
|
+
if (!matched) {
|
|
134
|
+
// A request naming an action reaches here only because that action's
|
|
135
|
+
// own point failed its exist test, so it is unbuildable whatever we
|
|
136
|
+
// pick. Refuse it BEFORE choosing a fallback: the guard below
|
|
137
|
+
// compares the chosen point's $action and would wave the request
|
|
138
|
+
// through whenever the fallback lands on the action point itself.
|
|
139
|
+
val unmatchedAction =
|
|
140
|
+
if (reqselector != null) Struct.getprop(reqselector, "$action", null) else null
|
|
141
|
+
if (unmatchedAction != null) {
|
|
142
|
+
throw ctx.makeError("point_action_invalid",
|
|
143
|
+
"Operation \"" + op.name + "\" action \"" +
|
|
144
|
+
Struct.stringify(unmatchedAction) + "\" is not valid.")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// A terminal parameter marks a record route (/boards/{id}); a
|
|
148
|
+
// cross-reference ends in the relationship's name
|
|
149
|
+
// (/posts/{id}/author). Failing that, the shallower path wins. The
|
|
150
|
+
// same rule runs at generation time, in helpers/opShape.ts — both
|
|
151
|
+
// sides must move together.
|
|
152
|
+
def partsLen(p: JMap[String, Object]): Int =
|
|
153
|
+
Struct.getprop(p, "parts") match {
|
|
154
|
+
case parts: JList[_] => parts.size()
|
|
155
|
+
case _ => 0
|
|
156
|
+
}
|
|
157
|
+
def terminalParam(p: JMap[String, Object]): Boolean =
|
|
158
|
+
Struct.getprop(p, "parts") match {
|
|
159
|
+
case parts: JList[_] if parts.size() > 0 =>
|
|
160
|
+
parts.get(parts.size() - 1) match {
|
|
161
|
+
case s: String => s.startsWith("{")
|
|
162
|
+
case _ => false
|
|
163
|
+
}
|
|
164
|
+
case _ => false
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
point = op.points.get(0)
|
|
168
|
+
var j = 0
|
|
169
|
+
while (j < op.points.size()) {
|
|
170
|
+
val cand = op.points.get(j)
|
|
171
|
+
val candTerm = terminalParam(cand)
|
|
172
|
+
val bestTerm = terminalParam(point)
|
|
173
|
+
if (candTerm != bestTerm) {
|
|
174
|
+
if (candTerm) point = cand
|
|
175
|
+
} else if (partsLen(cand) < partsLen(point)) {
|
|
176
|
+
point = cand
|
|
177
|
+
}
|
|
178
|
+
j += 1
|
|
179
|
+
}
|
|
126
180
|
}
|
|
127
181
|
|
|
128
182
|
if (reqselector != null) {
|
|
@@ -38,7 +38,35 @@ private func testBuildArgs(_ ctx: Context, _ op: Operation, _ args: VMap?) -> Va
|
|
|
38
38
|
|
|
39
39
|
let cfg: Value = ctx.config == nil ? .noval : .map(ctx.config!)
|
|
40
40
|
let points = getpath(cfg, jtp("entity", ctx.entity!.getName(), "op", opname, "points"))
|
|
41
|
-
|
|
41
|
+
|
|
42
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
43
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
44
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
45
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
46
|
+
// failing that the shallower path wins.
|
|
47
|
+
func pointPartsLen(_ p: Value) -> Int {
|
|
48
|
+
return getprop(p, .string("parts")).asList?.items.count ?? 0
|
|
49
|
+
}
|
|
50
|
+
func pointTerminalParam(_ p: Value) -> Bool {
|
|
51
|
+
guard let items = getprop(p, .string("parts")).asList?.items, let last = items.last else {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
return (last.asString ?? "").hasPrefix("{")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var point = getelem(points, .int(0))
|
|
58
|
+
if let candidates = points.asList?.items {
|
|
59
|
+
for cand in candidates {
|
|
60
|
+
let candTerm = pointTerminalParam(cand)
|
|
61
|
+
let bestTerm = pointTerminalParam(point)
|
|
62
|
+
if candTerm != bestTerm {
|
|
63
|
+
if candTerm { point = cand }
|
|
64
|
+
}
|
|
65
|
+
else if pointPartsLen(cand) < pointPartsLen(point) {
|
|
66
|
+
point = cand
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
42
70
|
|
|
43
71
|
let paramsPath = getpath(point, jtp("args", "params"))
|
|
44
72
|
let reqdParams = select(paramsPath, .map(vm(("reqd", .bool(true)))))
|
|
@@ -6,6 +6,27 @@ import Foundation
|
|
|
6
6
|
import FoundationNetworking
|
|
7
7
|
#endif
|
|
8
8
|
|
|
9
|
+
// Non-following twin of URLSession.shared. The station feature's
|
|
10
|
+
// middleware sets `redirect: manual` on the fetch definition under a
|
|
11
|
+
// hosts egress policy: a 3xx must come back as a response like any
|
|
12
|
+
// other, because an automatic follow would carry injected credentials
|
|
13
|
+
// to a Location no policy approved (the ts donor's fetch honours the
|
|
14
|
+
// same key natively; redirect policy is per-delegate in URLSession,
|
|
15
|
+
// hence a second session).
|
|
16
|
+
private final class ManualRedirectDelegate: NSObject, URLSessionTaskDelegate {
|
|
17
|
+
func urlSession(
|
|
18
|
+
_ session: URLSession, task: URLSessionTask,
|
|
19
|
+
willPerformHTTPRedirection response: HTTPURLResponse,
|
|
20
|
+
newRequest request: URLRequest,
|
|
21
|
+
completionHandler: @escaping (URLRequest?) -> Void
|
|
22
|
+
) {
|
|
23
|
+
completionHandler(nil)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private let manualRedirectSession = URLSession(
|
|
28
|
+
configuration: .default, delegate: ManualRedirectDelegate(), delegateQueue: nil)
|
|
29
|
+
|
|
9
30
|
func defaultHttpFetch(_ fullurl: String, _ fetchdef: VMap) throws -> Value {
|
|
10
31
|
guard let url = URL(string: fullurl) else {
|
|
11
32
|
let m = VMap()
|
|
@@ -45,7 +66,9 @@ func defaultHttpFetch(_ fullurl: String, _ fetchdef: VMap) throws -> Value {
|
|
|
45
66
|
}
|
|
46
67
|
let box = FetchBox()
|
|
47
68
|
let sem = DispatchSemaphore(value: 0)
|
|
48
|
-
let
|
|
69
|
+
let session = gp(fetchdef, "redirect").asString == "manual"
|
|
70
|
+
? manualRedirectSession : URLSession.shared
|
|
71
|
+
let task = session.dataTask(with: req) { d, r, e in
|
|
49
72
|
box.data = d; box.resp = r; box.err = e; sem.signal()
|
|
50
73
|
}
|
|
51
74
|
task.resume()
|
|
@@ -40,8 +40,8 @@ func makePointUtil(_ ctx: Context) throws -> VMap? {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
var point: VMap? = nil
|
|
43
|
+
var matched = false
|
|
43
44
|
for candidate in op.points {
|
|
44
|
-
point = candidate
|
|
45
45
|
let selectDef = gp(candidate, "select").asMap
|
|
46
46
|
var found = true
|
|
47
47
|
|
|
@@ -62,7 +62,56 @@ func makePointUtil(_ ctx: Context) throws -> VMap? {
|
|
|
62
62
|
if reqAction != selectAction { found = false }
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
if found {
|
|
65
|
+
if found {
|
|
66
|
+
point = candidate
|
|
67
|
+
matched = true
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// select.exist can list more than the params needed to pick a point (for
|
|
73
|
+
// /boards/{id} it is Trello's 17 optional query-includes), so a plain
|
|
74
|
+
// {id} call matches NOTHING. Fall back to the entity's own route rather
|
|
75
|
+
// than whichever point came last.
|
|
76
|
+
if !matched {
|
|
77
|
+
// A request naming an action reaches here only because that action's
|
|
78
|
+
// own point failed its exist test, so it is unbuildable whatever we
|
|
79
|
+
// pick. Refuse it BEFORE choosing a fallback: the guard below compares
|
|
80
|
+
// the chosen point's $action and would wave the request through
|
|
81
|
+
// whenever the fallback lands on the action point itself.
|
|
82
|
+
let unmatchedAction = gp(reqselector, "$action")
|
|
83
|
+
if !isNil(unmatchedAction) {
|
|
84
|
+
throw ctx.makeError("point_action_invalid",
|
|
85
|
+
"Operation \"\(op.name)\" action \"\(stringify(unmatchedAction))\" is not valid.")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A terminal parameter marks a record route (/boards/{id}); a
|
|
89
|
+
// cross-reference ends in the relationship's name (/posts/{id}/author).
|
|
90
|
+
// Failing that, the shallower path wins. The same rule runs at
|
|
91
|
+
// generation time, in helpers/opShape.ts — both sides must move
|
|
92
|
+
// together.
|
|
93
|
+
func partsLen(_ p: VMap) -> Int {
|
|
94
|
+
return gp(p, "parts").asList?.items.count ?? 0
|
|
95
|
+
}
|
|
96
|
+
func terminalParam(_ p: VMap) -> Bool {
|
|
97
|
+
guard let items = gp(p, "parts").asList?.items, let last = items.last else {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
return (last.asString ?? "").hasPrefix("{")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
point = op.points.first
|
|
104
|
+
for candidate in op.points {
|
|
105
|
+
guard let best = point else { break }
|
|
106
|
+
let candTerm = terminalParam(candidate)
|
|
107
|
+
let bestTerm = terminalParam(best)
|
|
108
|
+
if candTerm != bestTerm {
|
|
109
|
+
if candTerm { point = candidate }
|
|
110
|
+
}
|
|
111
|
+
else if partsLen(candidate) < partsLen(best) {
|
|
112
|
+
point = candidate
|
|
113
|
+
}
|
|
114
|
+
}
|
|
66
115
|
}
|
|
67
116
|
|
|
68
117
|
let reqAction = gp(reqselector, "$action")
|
|
@@ -139,6 +139,18 @@ func makeOptionsUtil(_ ctx: Context) -> VMap {
|
|
|
139
139
|
} else {
|
|
140
140
|
for n in names { featureorder.append(.string(n)) }
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
144
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
145
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
146
|
+
// or first, when no test entry exists. Without this the sorted
|
|
147
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
148
|
+
// features, turning its wire-truth events into fiction.
|
|
149
|
+
if let si = featureorder.firstIndex(where: { $0.asString == "station" }) {
|
|
150
|
+
featureorder.remove(at: si)
|
|
151
|
+
let ti = featureorder.firstIndex(where: { $0.asString == "test" })
|
|
152
|
+
featureorder.insert(.string("station"), at: (ti ?? -1) + 1)
|
|
153
|
+
}
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
let derived = VMap()
|
|
@@ -253,9 +253,28 @@ class TestFeature extends BaseFeature {
|
|
|
253
253
|
const isempty = struct.isempty
|
|
254
254
|
|
|
255
255
|
const opname = getprop(op, 'name')
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
const points = getpath(ctx.config, [
|
|
257
|
+
'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']) || []
|
|
258
|
+
|
|
259
|
+
// Pick the entity's own endpoint, not a cross-reference from another
|
|
260
|
+
// resource that also returns it — the same rule makePoint falls back to,
|
|
261
|
+
// so the seed-data query is built from the endpoint the request will
|
|
262
|
+
// actually be sent to: a terminal `{id}` marks a record route, and
|
|
263
|
+
// failing that the shallower path wins.
|
|
264
|
+
const isterm = (pt: any) => {
|
|
265
|
+
const parts = pt.parts
|
|
266
|
+
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
267
|
+
return 'string' === typeof last && 0 === last.indexOf('{')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let point = getelem(points, 0)
|
|
271
|
+
for (let i = 1; i < points.length; i++) {
|
|
272
|
+
const cand = getelem(points, i)
|
|
273
|
+
if (isterm(cand) !== isterm(point) ? isterm(cand) :
|
|
274
|
+
cand.parts.length < point.parts.length) {
|
|
275
|
+
point = cand
|
|
276
|
+
}
|
|
277
|
+
}
|
|
259
278
|
|
|
260
279
|
const reqd = transform(
|
|
261
280
|
select(getpath(point, ['args', 'params']), { reqd: true }),
|
|
@@ -71,6 +71,12 @@ function makeOptions(ctx: Context) {
|
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
utility: {},
|
|
74
|
+
// Feature INSTANCES supplied at construction (the station adopt
|
|
75
|
+
// path): consumed by the constructor's featureAdd loop, so they are
|
|
76
|
+
// class instances, not data - `$ANY` accepts them verbatim. Without
|
|
77
|
+
// this entry the seam is dead: the constructor reads
|
|
78
|
+
// options.extend, but validate rejected the key.
|
|
79
|
+
extend: '`$ANY`' as any,
|
|
74
80
|
system: {
|
|
75
81
|
fetch: undefined as any
|
|
76
82
|
},
|
|
@@ -134,10 +140,22 @@ function makeOptions(ctx: Context) {
|
|
|
134
140
|
// otherwise order the map test-first, then the remaining names sorted, so
|
|
135
141
|
// the outcome is deterministic and `test` is always the base transport.
|
|
136
142
|
if (0 === featureorder.length) {
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
let names = Object.keys(opts.feature || {}).sort()
|
|
144
|
+
names = names.indexOf('test') < 0
|
|
139
145
|
? names
|
|
140
146
|
: ['test'].concat(names.filter((n: string) => 'test' !== n))
|
|
147
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
148
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
149
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
150
|
+
// or first, when no test entry exists. Without this the sorted
|
|
151
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
152
|
+
// features, turning its wire-truth events into fiction.
|
|
153
|
+
const si = names.indexOf('station')
|
|
154
|
+
if (0 <= si) {
|
|
155
|
+
names.splice(si, 1)
|
|
156
|
+
names.splice(names.indexOf('test') + 1, 0, 'station')
|
|
157
|
+
}
|
|
158
|
+
featureorder = names
|
|
141
159
|
}
|
|
142
160
|
|
|
143
161
|
opts.__derived__ = {
|
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
import { Context, Point } from '../types'
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
// The entity's OWN route among an op's points, as opposed to a
|
|
6
|
+
// CROSS-REFERENCE: another resource's route that happens to return this
|
|
7
|
+
// entity (`/notifications/{id}/board` for a board). Both are plain GETs
|
|
8
|
+
// with no `$action`, so the path itself is all there is to go on.
|
|
9
|
+
//
|
|
10
|
+
// Two signals, in order:
|
|
11
|
+
//
|
|
12
|
+
// 1. A record route ends in the record's identifier (`/boards/{id}`,
|
|
13
|
+
// `/accounts/{account_id}/users/{id}`); a cross-reference ends in the
|
|
14
|
+
// relationship's name (`/posts/{id}/author`). A terminal parameter is
|
|
15
|
+
// therefore the stronger signal, and unlike depth it survives an entity
|
|
16
|
+
// nested more deeply than the route pointing at it.
|
|
17
|
+
// 2. Failing that, fewest path segments — the shallower route is the one
|
|
18
|
+
// the entity is named for.
|
|
19
|
+
//
|
|
20
|
+
// Ties keep the earlier point, so the sorted-key order of the model decides
|
|
21
|
+
// and the output stays byte-stable.
|
|
22
|
+
//
|
|
23
|
+
// The same rule runs at GENERATION time, in `shortestPoint`/`ownPoint` in
|
|
24
|
+
// helpers/opShape.ts. It cannot be shared with this file — a template ships
|
|
25
|
+
// standalone, outside that package — so it is written twice on purpose, and
|
|
26
|
+
// both sides must move together.
|
|
27
|
+
function terminalParam(point: any): boolean {
|
|
28
|
+
const parts = point.parts
|
|
29
|
+
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
30
|
+
return 'string' === typeof last && 0 === last.indexOf('{')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
function ownPoint(points: any[]): any {
|
|
35
|
+
let best = points[0]
|
|
36
|
+
|
|
37
|
+
for (const cand of points) {
|
|
38
|
+
const candterm = terminalParam(cand)
|
|
39
|
+
const bestterm = terminalParam(best)
|
|
40
|
+
|
|
41
|
+
if (candterm !== bestterm ? candterm :
|
|
42
|
+
cand.parts.length < best.parts.length) {
|
|
43
|
+
best = cand
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return best
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
5
51
|
function makePoint(ctx: Context): Point | Error {
|
|
6
52
|
if (ctx.out.point) {
|
|
7
53
|
return ctx.point = ctx.out.point
|
|
@@ -31,9 +77,10 @@ function makePoint(ctx: Context): Point | Error {
|
|
|
31
77
|
const selector = getprop(ctx, op.input)
|
|
32
78
|
|
|
33
79
|
let point
|
|
80
|
+
let matched = false
|
|
34
81
|
for (let i = 0; i < op.points.length; i++) {
|
|
35
|
-
|
|
36
|
-
const select =
|
|
82
|
+
const cand = op.points[i]
|
|
83
|
+
const select = cand.select
|
|
37
84
|
let found = true
|
|
38
85
|
|
|
39
86
|
if (selector && select.exist) {
|
|
@@ -56,10 +103,33 @@ function makePoint(ctx: Context): Point | Error {
|
|
|
56
103
|
}
|
|
57
104
|
|
|
58
105
|
if (found) {
|
|
106
|
+
point = cand
|
|
107
|
+
matched = true
|
|
59
108
|
break
|
|
60
109
|
}
|
|
61
110
|
}
|
|
62
111
|
|
|
112
|
+
// select.exist can list more than the params needed to pick a point (for
|
|
113
|
+
// /boards/{id} it is Trello's 17 optional query-includes), so a plain
|
|
114
|
+
// {id} call matches NOTHING. Fall back to the entity's OWN route rather
|
|
115
|
+
// than whichever point came last.
|
|
116
|
+
if (!matched) {
|
|
117
|
+
// A request naming an action reaches here only because that action's
|
|
118
|
+
// own point failed its exist test, so the request is unbuildable
|
|
119
|
+
// whatever we pick. Refuse it BEFORE choosing a fallback: the guard
|
|
120
|
+
// below compares the chosen point's $action, and would wave the
|
|
121
|
+
// request through whenever the action point is itself the one the
|
|
122
|
+
// fallback selects (a short action route folded into an entity whose
|
|
123
|
+
// canonical route is longer). An unbuildable action request is an
|
|
124
|
+
// error, never a silent request to some other endpoint.
|
|
125
|
+
if (null != reqselector.$action) {
|
|
126
|
+
return ctx.error('point_action_invalid', 'Operation "' + op.name +
|
|
127
|
+
'" action "' + reqselector.$action + '" is not valid.')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
point = ownPoint(op.points)
|
|
131
|
+
}
|
|
132
|
+
|
|
63
133
|
if (
|
|
64
134
|
null != reqselector.$action &&
|
|
65
135
|
null != point &&
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { test, describe } from 'node:test'
|
|
2
|
+
import { ok, equal } from 'node:assert'
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { TEST_JSON_FILE } from './index'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// Guards the shared corpus AS A WHOLE, which the per-section guard in the
|
|
10
|
+
// language runners cannot do.
|
|
11
|
+
//
|
|
12
|
+
// That guard only fires for a section some test actually runs. Seven sections
|
|
13
|
+
// — fetcher, makeFetchDef, makePoint, makeResult, featureAdd, featureHook and
|
|
14
|
+
// featureInit — had no test calling them at all, so they sat at `set: []`
|
|
15
|
+
// through two reviews reporting nothing. A fixture nobody runs is
|
|
16
|
+
// indistinguishable from a fixture that passes.
|
|
17
|
+
//
|
|
18
|
+
// Deferral is therefore DATA (`basic.pending`), not a comment: comments do not
|
|
19
|
+
// survive compilation to test.json, so a marker written only in the .aontu
|
|
20
|
+
// source cannot be checked by the thing that consumes it. Being data, these
|
|
21
|
+
// invariants hold for every port, not just this one.
|
|
22
|
+
describe('Corpus', () => {
|
|
23
|
+
|
|
24
|
+
// Resolved the same way runner.ts resolves it — from dist-test/, one level
|
|
25
|
+
// up from this file's dist-test/utility/.
|
|
26
|
+
const corpus = JSON.parse(
|
|
27
|
+
readFileSync(join(__dirname, '..', TEST_JSON_FILE), 'utf8'))
|
|
28
|
+
|
|
29
|
+
const primary: Record<string, any> = corpus.primary || {}
|
|
30
|
+
const names = Object.keys(primary).sort()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
test('the corpus is not empty', () => {
|
|
34
|
+
ok(0 < names.length, 'no primary sections found — check TEST_JSON_FILE')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
// A section that compiles to zero cases must say why, in the fixture.
|
|
39
|
+
test('every empty section declares why it is deferred', () => {
|
|
40
|
+
const undeclared = names.filter((n) => {
|
|
41
|
+
const basic = primary[n]?.basic
|
|
42
|
+
return Array.isArray(basic?.set) && 0 === basic.set.length &&
|
|
43
|
+
'string' !== typeof basic?.pending
|
|
44
|
+
})
|
|
45
|
+
equal(undeclared.join(','), '',
|
|
46
|
+
'these sections compile to ZERO cases and carry no `basic: pending` ' +
|
|
47
|
+
'reason — add cases, or state the blocker in .sdk/test/primary/<name>.aontu')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// "PENDING" with no reason is just a way to keep a hole open.
|
|
52
|
+
test('every deferral gives a real reason', () => {
|
|
53
|
+
for (const n of names) {
|
|
54
|
+
const pending = primary[n]?.basic?.pending
|
|
55
|
+
if (null == pending) continue
|
|
56
|
+
ok(20 < String(pending).length,
|
|
57
|
+
`${n}: 'pending' needs a reason, not just a marker`)
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
// The other direction: a section that has gained cases must drop its
|
|
63
|
+
// deferral, or the marker rots into a lie and the runners keep excusing it.
|
|
64
|
+
test('a deferred section that gained cases is promoted', () => {
|
|
65
|
+
const stale = names.filter((n) => {
|
|
66
|
+
const basic = primary[n]?.basic
|
|
67
|
+
return null != basic?.pending && Array.isArray(basic?.set) &&
|
|
68
|
+
0 < basic.set.length
|
|
69
|
+
})
|
|
70
|
+
equal(stale.join(','), '',
|
|
71
|
+
'these sections now have cases — remove `basic: pending` from the ' +
|
|
72
|
+
'fixture and from the PENDING list in the language runners')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// Every section must actually be a section: `basic.set` is what the runners
|
|
77
|
+
// iterate, and a fixture that failed to compile silently loses it.
|
|
78
|
+
test('every section has a basic.set list', () => {
|
|
79
|
+
for (const n of names) {
|
|
80
|
+
ok(Array.isArray(primary[n]?.basic?.set),
|
|
81
|
+
`${n}: no basic.set list — the fixture did not compile as expected`)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
})
|