@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
|
@@ -17,6 +17,27 @@ package ProjectNameUtilities;
|
|
|
17
17
|
|
|
18
18
|
our %REGISTRY;
|
|
19
19
|
|
|
20
|
+
# How many path segments a point has.
|
|
21
|
+
sub _parts_len {
|
|
22
|
+
my ($point) = @_;
|
|
23
|
+
my $parts = ProjectNameHelpers::gp($point, 'parts');
|
|
24
|
+
return Voxgig::Struct::islist($parts) ? scalar(@$parts) : 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Does this point's path end in a parameter? A record route ends in the
|
|
28
|
+
# record's identifier (/boards/{id}); a cross-reference that also returns the
|
|
29
|
+
# entity ends in the relationship's name (/posts/{id}/author). That, then
|
|
30
|
+
# fewest segments, is what tells the entity's own route from a
|
|
31
|
+
# cross-reference. The same rule runs at generation time, in
|
|
32
|
+
# helpers/opShape.ts — both sides must move together.
|
|
33
|
+
sub _terminal_param {
|
|
34
|
+
my ($point) = @_;
|
|
35
|
+
my $parts = ProjectNameHelpers::gp($point, 'parts');
|
|
36
|
+
return 0 unless Voxgig::Struct::islist($parts) && 0 < scalar(@$parts);
|
|
37
|
+
my $last = $parts->[-1];
|
|
38
|
+
return (defined $last && !ref $last && $last =~ /^\{/) ? 1 : 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
20
41
|
$REGISTRY{make_point} = sub {
|
|
21
42
|
my ($ctx) = @_;
|
|
22
43
|
|
|
@@ -54,8 +75,8 @@ $REGISTRY{make_point} = sub {
|
|
|
54
75
|
my $selector = ('data' eq $op->{input}) ? $ctx->{data} : $ctx->{match};
|
|
55
76
|
|
|
56
77
|
my $point;
|
|
78
|
+
my $matched = 0;
|
|
57
79
|
for my $p (@{ $op->{points} }) {
|
|
58
|
-
$point = $p;
|
|
59
80
|
my $select_def = ProjectNameHelpers::to_map(ProjectNameHelpers::gp($p, 'select'));
|
|
60
81
|
my $found = 1;
|
|
61
82
|
|
|
@@ -79,7 +100,39 @@ $REGISTRY{make_point} = sub {
|
|
|
79
100
|
$found = 0 unless ProjectNameHelpers::eqv($req_action, $select_action);
|
|
80
101
|
}
|
|
81
102
|
|
|
82
|
-
|
|
103
|
+
if ($found) {
|
|
104
|
+
$point = $p;
|
|
105
|
+
$matched = 1;
|
|
106
|
+
last;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# select.exist can list more than the params needed to pick a point, so
|
|
111
|
+
# nothing matches — fall back to the entity's own route rather than
|
|
112
|
+
# whichever point came last.
|
|
113
|
+
unless ($matched) {
|
|
114
|
+
# A request naming an action reaches here only because that action's
|
|
115
|
+
# own point failed its exist test, so it is unbuildable whatever we
|
|
116
|
+
# pick. Refuse it BEFORE choosing a fallback: the guard below compares
|
|
117
|
+
# the chosen point's $action and would wave the request through
|
|
118
|
+
# whenever the fallback lands on the action point itself.
|
|
119
|
+
my $unmatched_action = $reqselector
|
|
120
|
+
? ProjectNameHelpers::gp($reqselector, '$action') : undef;
|
|
121
|
+
if (defined $unmatched_action) {
|
|
122
|
+
return (undef, $ctx->make_error('point_action_invalid',
|
|
123
|
+
'Operation "' . $op->{name} . '" action "' .
|
|
124
|
+
Voxgig::Struct::stringify($unmatched_action) . '" is not valid.'));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
$point = $op->{points}[0];
|
|
128
|
+
for my $p (@{ $op->{points} }) {
|
|
129
|
+
if (_terminal_param($p) != _terminal_param($point)) {
|
|
130
|
+
$point = $p if _terminal_param($p);
|
|
131
|
+
}
|
|
132
|
+
elsif (_parts_len($p) < _parts_len($point)) {
|
|
133
|
+
$point = $p;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
83
136
|
}
|
|
84
137
|
|
|
85
138
|
if ($reqselector) {
|
|
@@ -369,11 +369,40 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
|
|
|
369
369
|
// any missing piece falls back to "no required params".
|
|
370
370
|
$reqd_names = [];
|
|
371
371
|
if (is_string($opname) && is_string($entityName) && isset($ctx->config)) {
|
|
372
|
+
// Pick the entity's own endpoint, not a cross-reference from
|
|
373
|
+
// another resource that also returns it — the same rule
|
|
374
|
+
// MakePoint falls back to, so the seed-data query is built from
|
|
375
|
+
// the endpoint the request will actually be sent to: a terminal
|
|
376
|
+
// `{id}` marks a record route, and failing that the shallower
|
|
377
|
+
// path wins.
|
|
372
378
|
$points = \Voxgig\Struct\Struct::getpath(
|
|
373
379
|
['entity', $entityName, 'op', $opname, 'points'],
|
|
374
380
|
$ctx->config
|
|
375
381
|
);
|
|
376
|
-
$point = \Voxgig\Struct\Struct::getelem($points,
|
|
382
|
+
$point = \Voxgig\Struct\Struct::getelem($points, 0);
|
|
383
|
+
if (is_array($points)) {
|
|
384
|
+
$point_parts_len = function ($p) {
|
|
385
|
+
$parts = \Voxgig\Struct\Struct::getprop($p, 'parts');
|
|
386
|
+
return is_array($parts) ? count($parts) : 0;
|
|
387
|
+
};
|
|
388
|
+
$point_terminal_param = function ($p) {
|
|
389
|
+
$parts = \Voxgig\Struct\Struct::getprop($p, 'parts');
|
|
390
|
+
if (!is_array($parts) || 0 === count($parts)) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
$last = $parts[count($parts) - 1];
|
|
394
|
+
return is_string($last) && 0 === strpos($last, '{');
|
|
395
|
+
};
|
|
396
|
+
foreach ($points as $cand) {
|
|
397
|
+
if ($point_terminal_param($cand) !== $point_terminal_param($point)) {
|
|
398
|
+
if ($point_terminal_param($cand)) {
|
|
399
|
+
$point = $cand;
|
|
400
|
+
}
|
|
401
|
+
} elseif ($point_parts_len($cand) < $point_parts_len($point)) {
|
|
402
|
+
$point = $cand;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
377
406
|
$params = is_array($point) ? ($point['args']['params'] ?? null) : null;
|
|
378
407
|
if (is_array($params)) {
|
|
379
408
|
foreach ($params as $p) {
|
|
@@ -154,6 +154,26 @@ class PipelineTest extends TestCase
|
|
|
154
154
|
]));
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
// Station special case, mirroring test's: its transport wrap must sit
|
|
158
|
+
// immediately outside the base transport, so map-form activation hoists
|
|
159
|
+
// it to just after test - or first, when no test entry exists.
|
|
160
|
+
public function test_feature_order_map_hoists_station_after_test(): void
|
|
161
|
+
{
|
|
162
|
+
$this->assertSame('test,station,metrics', self::resolve_order([
|
|
163
|
+
'metrics' => ['active' => true],
|
|
164
|
+
'station' => ['active' => true],
|
|
165
|
+
'test' => ['active' => true],
|
|
166
|
+
]));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public function test_feature_order_map_hoists_station_first_without_test(): void
|
|
170
|
+
{
|
|
171
|
+
$this->assertSame('station,metrics', self::resolve_order([
|
|
172
|
+
'metrics' => ['active' => true],
|
|
173
|
+
'station' => ['active' => true],
|
|
174
|
+
]));
|
|
175
|
+
}
|
|
176
|
+
|
|
157
177
|
public function test_feature_order_no_test_deterministic(): void
|
|
158
178
|
{
|
|
159
179
|
$this->assertSame('cache,retry', self::resolve_order([
|
|
@@ -35,7 +35,8 @@ class ProjectNameFetcher
|
|
|
35
35
|
// Prefer cURL when available — its header capture is reliable across
|
|
36
36
|
// PHP versions, while file_get_contents + custom stream wrappers
|
|
37
37
|
// don't propagate $http_response_header for user-defined wrappers
|
|
38
|
-
// in PHP 8.3+.
|
|
38
|
+
// in PHP 8.3+. (cURL is configured with FOLLOWLOCATION off, so the
|
|
39
|
+
// manual-redirect annotation below holds there by construction.)
|
|
39
40
|
if (function_exists('curl_init')) {
|
|
40
41
|
return self::curlFetch($fullurl, $method_str, $body_str, $header_lines);
|
|
41
42
|
}
|
|
@@ -47,6 +48,16 @@ class ProjectNameFetcher
|
|
|
47
48
|
],
|
|
48
49
|
];
|
|
49
50
|
|
|
51
|
+
// Honour a redirect annotation on the fetch definition (set by the
|
|
52
|
+
// station feature's middleware under a hosts policy): "manual"
|
|
53
|
+
// surfaces a 3xx as an ordinary response instead of auto-following
|
|
54
|
+
// it — the http stream wrapper otherwise replays the request,
|
|
55
|
+
// headers included, against whatever host Location names, carrying
|
|
56
|
+
// injected credentials to a host no policy approved.
|
|
57
|
+
if (($fetchdef['redirect'] ?? null) === 'manual') {
|
|
58
|
+
$opts['http']['follow_location'] = 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
if (is_string($body_str)) {
|
|
51
62
|
$opts['http']['content'] = $body_str;
|
|
52
63
|
}
|
|
@@ -37,6 +37,34 @@ class ProjectNameMakeOptions
|
|
|
37
37
|
// Preserve system.fetch before merge/validate.
|
|
38
38
|
$sys_fetch = \Voxgig\Struct\Struct::getpath($options, 'system.fetch');
|
|
39
39
|
|
|
40
|
+
// Feature INSTANCES supplied at construction (the station adopt path)
|
|
41
|
+
// are consumed by the constructor's feature-add loop straight from the
|
|
42
|
+
// RAW construction options - extend is consumed exactly once, at
|
|
43
|
+
// construction. Strip it here so it never enters the cloned option
|
|
44
|
+
// map: Struct::clone flattens arbitrary objects, and options_map()
|
|
45
|
+
// re-clones $this->options on every request.
|
|
46
|
+
if (isset($options['extend'])) {
|
|
47
|
+
unset($options['extend']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The station feature's binding handle and caller opts are live
|
|
51
|
+
// values (connect/adopt/st->options() plant them): hold them aside so
|
|
52
|
+
// the clone below cannot flatten them, and reattach after validate.
|
|
53
|
+
$station_handle = null;
|
|
54
|
+
$station_caller = null;
|
|
55
|
+
$station_has_caller = false;
|
|
56
|
+
if (is_array($options['feature']['station'] ?? null)) {
|
|
57
|
+
if (is_object($options['feature']['station']['station'] ?? null)) {
|
|
58
|
+
$station_handle = $options['feature']['station']['station'];
|
|
59
|
+
unset($options['feature']['station']['station']);
|
|
60
|
+
}
|
|
61
|
+
if (array_key_exists('calleropts', $options['feature']['station'])) {
|
|
62
|
+
$station_caller = $options['feature']['station']['calleropts'];
|
|
63
|
+
$station_has_caller = true;
|
|
64
|
+
unset($options['feature']['station']['calleropts']);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
$opts = \Voxgig\Struct\Struct::clone($options);
|
|
41
69
|
$opts = self::to_array_deep($opts);
|
|
42
70
|
if (!is_array($opts)) {
|
|
@@ -106,6 +134,24 @@ class ProjectNameMakeOptions
|
|
|
106
134
|
$opts = [];
|
|
107
135
|
}
|
|
108
136
|
|
|
137
|
+
// Reattach the station binding handle held aside above (the feature
|
|
138
|
+
// optspec is `$OPEN`, so the extra keys are legal; they were only
|
|
139
|
+
// kept out of the clone/validate round-trip).
|
|
140
|
+
if (null !== $station_handle || $station_has_caller) {
|
|
141
|
+
if (!is_array($opts['feature'] ?? null)) {
|
|
142
|
+
$opts['feature'] = [];
|
|
143
|
+
}
|
|
144
|
+
if (!is_array($opts['feature']['station'] ?? null)) {
|
|
145
|
+
$opts['feature']['station'] = [];
|
|
146
|
+
}
|
|
147
|
+
if (null !== $station_handle) {
|
|
148
|
+
$opts['feature']['station']['station'] = $station_handle;
|
|
149
|
+
}
|
|
150
|
+
if ($station_has_caller) {
|
|
151
|
+
$opts['feature']['station']['calleropts'] = $station_caller;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
109
155
|
// Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
110
156
|
// Every placeholder must resolve to a non-empty value: from
|
|
111
157
|
// $options['server'] (user), else the config default. A placeholder
|
|
@@ -181,6 +227,18 @@ class ProjectNameMakeOptions
|
|
|
181
227
|
} else {
|
|
182
228
|
$featureorder = $names;
|
|
183
229
|
}
|
|
230
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
231
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
232
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
233
|
+
// or first, when no test entry exists. Without this the sorted
|
|
234
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
235
|
+
// features, turning its wire-truth events into fiction.
|
|
236
|
+
$si = array_search('station', $featureorder, true);
|
|
237
|
+
if (false !== $si) {
|
|
238
|
+
array_splice($featureorder, $si, 1);
|
|
239
|
+
$ti = array_search('test', $featureorder, true);
|
|
240
|
+
array_splice($featureorder, false === $ti ? 0 : $ti + 1, 0, ['station']);
|
|
241
|
+
}
|
|
184
242
|
}
|
|
185
243
|
|
|
186
244
|
$derived = ['clean' => $keyre === '' ? [] : ['keyre' => $keyre]];
|
|
@@ -42,8 +42,8 @@ class ProjectNameMakePoint
|
|
|
42
42
|
$selector = $op->input === 'data' ? $ctx->data : $ctx->match;
|
|
43
43
|
|
|
44
44
|
$point = null;
|
|
45
|
+
$matched = false;
|
|
45
46
|
foreach ($op->points as $p) {
|
|
46
|
-
$point = $p;
|
|
47
47
|
$select_def = ProjectNameHelpers::to_map(\Voxgig\Struct\Struct::getprop($p, 'select'));
|
|
48
48
|
$found = true;
|
|
49
49
|
|
|
@@ -70,10 +70,58 @@ class ProjectNameMakePoint
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
if ($found) {
|
|
73
|
+
$point = $p;
|
|
74
|
+
$matched = true;
|
|
73
75
|
break;
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
// select.exist can list more than the params needed to pick a
|
|
80
|
+
// point, so nothing matches — fall back to the entity's own
|
|
81
|
+
// route rather than the last point.
|
|
82
|
+
if (!$matched) {
|
|
83
|
+
// A request naming an action reaches here only because that
|
|
84
|
+
// action's own point failed its exist test, so it is
|
|
85
|
+
// unbuildable whatever we pick. Refuse it BEFORE choosing a
|
|
86
|
+
// fallback: the guard below compares the chosen point's
|
|
87
|
+
// $action and would wave the request through whenever the
|
|
88
|
+
// fallback lands on the action point itself.
|
|
89
|
+
$unmatched_action = $reqselector
|
|
90
|
+
? \Voxgig\Struct\Struct::getprop($reqselector, '$action') : null;
|
|
91
|
+
if (null !== $unmatched_action) {
|
|
92
|
+
return [null, $ctx->make_error('point_action_invalid',
|
|
93
|
+
"Operation \"{$op->name}\" action \"" .
|
|
94
|
+
\Voxgig\Struct\Struct::stringify($unmatched_action) .
|
|
95
|
+
"\" is not valid.")];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// A terminal parameter marks a record route (/boards/{id});
|
|
99
|
+
// a cross-reference ends in the relationship's name
|
|
100
|
+
// (/posts/{id}/author). Failing that, the shallower wins.
|
|
101
|
+
$parts_len = function ($p) {
|
|
102
|
+
$parts = \Voxgig\Struct\Struct::getprop($p, 'parts');
|
|
103
|
+
return is_array($parts) ? count($parts) : 0;
|
|
104
|
+
};
|
|
105
|
+
$terminal_param = function ($p) {
|
|
106
|
+
$parts = \Voxgig\Struct\Struct::getprop($p, 'parts');
|
|
107
|
+
if (!is_array($parts) || 0 === count($parts)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
$last = $parts[count($parts) - 1];
|
|
111
|
+
return is_string($last) && 0 === strpos($last, '{');
|
|
112
|
+
};
|
|
113
|
+
$point = $op->points[0];
|
|
114
|
+
foreach ($op->points as $p) {
|
|
115
|
+
if ($terminal_param($p) !== $terminal_param($point)) {
|
|
116
|
+
if ($terminal_param($p)) {
|
|
117
|
+
$point = $p;
|
|
118
|
+
}
|
|
119
|
+
} elseif ($parts_len($p) < $parts_len($point)) {
|
|
120
|
+
$point = $p;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
77
125
|
if ($reqselector) {
|
|
78
126
|
$req_action = \Voxgig\Struct\Struct::getprop($reqselector, '$action');
|
|
79
127
|
if ($req_action && $point) {
|
|
@@ -35,8 +35,20 @@ def _default_http_fetch(fullurl, fetchdef):
|
|
|
35
35
|
if not has_ua:
|
|
36
36
|
req.add_header("User-Agent", _DEFAULT_USER_AGENT)
|
|
37
37
|
|
|
38
|
+
# Manual redirects: fetchdef["redirect"] == "manual" surfaces a 3xx as
|
|
39
|
+
# an ordinary response instead of auto-following it — urllib would
|
|
40
|
+
# otherwise replay the request, headers included, against whatever host
|
|
41
|
+
# Location names. Set by the station feature's transport middleware
|
|
42
|
+
# under an egress hosts policy (mirrors the ts fetch option).
|
|
43
|
+
opener = None
|
|
44
|
+
if fetchdef.get("redirect") == "manual":
|
|
45
|
+
class _NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
46
|
+
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
|
47
|
+
return None
|
|
48
|
+
opener = urllib.request.build_opener(_NoRedirect)
|
|
49
|
+
|
|
38
50
|
try:
|
|
39
|
-
resp = urllib.request.urlopen(req)
|
|
51
|
+
resp = opener.open(req) if opener is not None else urllib.request.urlopen(req)
|
|
40
52
|
body = resp.read().decode("utf-8")
|
|
41
53
|
resp_headers = {}
|
|
42
54
|
for k, v in resp.getheaders():
|
|
@@ -15,6 +15,15 @@ def make_options_util(ctx):
|
|
|
15
15
|
for key, val in custom_utils.items():
|
|
16
16
|
utility.custom[key] = val
|
|
17
17
|
|
|
18
|
+
# Feature INSTANCES supplied at construction (the station adopt path)
|
|
19
|
+
# are consumed by the constructor's feature-add loop straight from the
|
|
20
|
+
# RAW construction options - extend is consumed exactly once, at
|
|
21
|
+
# construction. They are stripped here so they never enter the cloned
|
|
22
|
+
# option map: vs.clone flattens arbitrary objects, and options_map()
|
|
23
|
+
# re-clones self.options on every request.
|
|
24
|
+
if isinstance(options, dict) and options.get("extend") is not None:
|
|
25
|
+
options = {k: v for k, v in options.items() if k != "extend"}
|
|
26
|
+
|
|
18
27
|
opts = vs.clone(options)
|
|
19
28
|
if not isinstance(opts, dict):
|
|
20
29
|
opts = {}
|
|
@@ -74,6 +83,10 @@ def make_options_util(ctx):
|
|
|
74
83
|
},
|
|
75
84
|
},
|
|
76
85
|
"utility": {},
|
|
86
|
+
# Extension feature instances (see above) - stripped before the
|
|
87
|
+
# clone, but the key stays legal so a passed-through map cannot
|
|
88
|
+
# fail validation.
|
|
89
|
+
"extend": "`$ANY`",
|
|
77
90
|
"system": {},
|
|
78
91
|
"test": {
|
|
79
92
|
"active": False,
|
|
@@ -171,6 +184,16 @@ def make_options_util(ctx):
|
|
|
171
184
|
featureorder = ["test"] + [n for n in names if n != "test"]
|
|
172
185
|
else:
|
|
173
186
|
featureorder = names
|
|
187
|
+
# Station special case, mirroring test's: its transport wrap must
|
|
188
|
+
# sit immediately outside the base transport (inside retry/cache/
|
|
189
|
+
# netsim), so map-form activation hoists it to just after test -
|
|
190
|
+
# or first, when no test entry exists. Without this the sorted
|
|
191
|
+
# default would init station last and wrap OUTSIDE the recording
|
|
192
|
+
# features, turning its wire-truth events into fiction.
|
|
193
|
+
if "station" in featureorder:
|
|
194
|
+
featureorder = [n for n in featureorder if n != "station"]
|
|
195
|
+
at = featureorder.index("test") + 1 if "test" in featureorder else 0
|
|
196
|
+
featureorder.insert(at, "station")
|
|
174
197
|
|
|
175
198
|
derived = {"clean": {}}
|
|
176
199
|
if keyre != "":
|
|
@@ -5,6 +5,38 @@ from projectname_sdk.utility.voxgig_struct import voxgig_struct as vs
|
|
|
5
5
|
from projectname_sdk.core.helpers import to_map
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
def _parts_len(point):
|
|
9
|
+
parts = vs.getprop(point, "parts")
|
|
10
|
+
return len(parts) if isinstance(parts, list) else 0
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _terminal_param(point):
|
|
14
|
+
# A record route ends in the record's identifier (/boards/{id}); a
|
|
15
|
+
# cross-reference that also returns the entity ends in the
|
|
16
|
+
# relationship's name (/posts/{id}/author).
|
|
17
|
+
parts = vs.getprop(point, "parts")
|
|
18
|
+
if not isinstance(parts, list) or 0 == len(parts):
|
|
19
|
+
return False
|
|
20
|
+
return str(parts[-1]).startswith("{")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _own_point(points):
|
|
24
|
+
# The entity's OWN route: a terminal parameter first, then the fewest
|
|
25
|
+
# path segments. Ties keep the earlier point, so the model's sorted-key
|
|
26
|
+
# order decides. The same rule runs at generation time, in
|
|
27
|
+
# helpers/opShape.ts — both sides must move together.
|
|
28
|
+
best = points[0]
|
|
29
|
+
for cand in points:
|
|
30
|
+
cand_term = _terminal_param(cand)
|
|
31
|
+
best_term = _terminal_param(best)
|
|
32
|
+
if cand_term != best_term:
|
|
33
|
+
if cand_term:
|
|
34
|
+
best = cand
|
|
35
|
+
elif _parts_len(cand) < _parts_len(best):
|
|
36
|
+
best = cand
|
|
37
|
+
return best
|
|
38
|
+
|
|
39
|
+
|
|
8
40
|
def make_point_util(ctx):
|
|
9
41
|
pre = ctx.out.get("point")
|
|
10
42
|
if pre is not None:
|
|
@@ -40,9 +72,10 @@ def make_point_util(ctx):
|
|
|
40
72
|
selector = ctx.match
|
|
41
73
|
|
|
42
74
|
point = None
|
|
75
|
+
matched = False
|
|
43
76
|
for i in range(len(op.points)):
|
|
44
|
-
|
|
45
|
-
select_def = to_map(vs.getprop(
|
|
77
|
+
cand = op.points[i]
|
|
78
|
+
select_def = to_map(vs.getprop(cand, "select"))
|
|
46
79
|
found = True
|
|
47
80
|
|
|
48
81
|
if selector is not None and select_def is not None:
|
|
@@ -63,8 +96,29 @@ def make_point_util(ctx):
|
|
|
63
96
|
found = False
|
|
64
97
|
|
|
65
98
|
if found:
|
|
99
|
+
point = cand
|
|
100
|
+
matched = True
|
|
66
101
|
break
|
|
67
102
|
|
|
103
|
+
# select.exist can list more than the params needed to pick a point,
|
|
104
|
+
# so nothing matches — fall back to the entity's own route rather
|
|
105
|
+
# than whichever point came last.
|
|
106
|
+
if not matched:
|
|
107
|
+
# A request naming an action reaches here only because that
|
|
108
|
+
# action's own point failed its exist test, so it is unbuildable
|
|
109
|
+
# whatever we pick. Refuse it BEFORE choosing a fallback: the
|
|
110
|
+
# guard below compares the chosen point's $action and would wave
|
|
111
|
+
# the request through whenever the fallback lands on the action
|
|
112
|
+
# point itself.
|
|
113
|
+
req_action = vs.getprop(reqselector, "$action") \
|
|
114
|
+
if reqselector is not None else None
|
|
115
|
+
if req_action is not None:
|
|
116
|
+
return None, ctx.make_error("point_action_invalid",
|
|
117
|
+
'Operation "' + op.name + '" action "' +
|
|
118
|
+
vs.stringify(req_action) + '" is not valid.')
|
|
119
|
+
|
|
120
|
+
point = _own_point(op.points)
|
|
121
|
+
|
|
68
122
|
if reqselector is not None:
|
|
69
123
|
req_action = vs.getprop(reqselector, "$action")
|
|
70
124
|
if req_action is not None and point is not None:
|
|
@@ -49,6 +49,12 @@ module ProjectNameUtilities
|
|
|
49
49
|
"entity" => { "`$CHILD`" => { "`$OPEN`" => true, "active" => false, "alias" => {} } },
|
|
50
50
|
"feature" => { "`$CHILD`" => { "`$OPEN`" => true, "active" => false } },
|
|
51
51
|
"utility" => {},
|
|
52
|
+
# Feature INSTANCES supplied at construction (the station adopt
|
|
53
|
+
# path): consumed by the constructor's feature_add loop, so they are
|
|
54
|
+
# class instances, not data — `$ANY` accepts them verbatim. Without
|
|
55
|
+
# this entry the seam is dead: the constructor reads
|
|
56
|
+
# options["extend"], but validate rejected the key.
|
|
57
|
+
"extend" => "`$ANY`",
|
|
52
58
|
"system" => {},
|
|
53
59
|
"test" => { "active" => false, "entity" => { "`$OPEN`" => true } },
|
|
54
60
|
"clean" => { "keys" => "key,token,id" },
|
|
@@ -122,6 +128,18 @@ module ProjectNameUtilities
|
|
|
122
128
|
else
|
|
123
129
|
featureorder = names
|
|
124
130
|
end
|
|
131
|
+
# Station special case, mirroring test's: its transport wrap must
|
|
132
|
+
# sit immediately outside the base transport (inside retry/cache/
|
|
133
|
+
# netsim), so map-form activation hoists it to just after test -
|
|
134
|
+
# or first, when no test entry exists. Without this the sorted
|
|
135
|
+
# default would init station last and wrap OUTSIDE the recording
|
|
136
|
+
# features, turning its wire-truth events into fiction.
|
|
137
|
+
si = featureorder.index("station")
|
|
138
|
+
unless si.nil?
|
|
139
|
+
featureorder.delete_at(si)
|
|
140
|
+
ti = featureorder.index("test")
|
|
141
|
+
featureorder.insert(ti.nil? ? 0 : ti + 1, "station")
|
|
142
|
+
end
|
|
125
143
|
end
|
|
126
144
|
|
|
127
145
|
derived = { "clean" => keyre.empty? ? {} : { "keyre" => keyre } }
|
|
@@ -35,8 +35,8 @@ module ProjectNameUtilities
|
|
|
35
35
|
selector = op.input == "data" ? ctx.data : ctx.match
|
|
36
36
|
|
|
37
37
|
point = nil
|
|
38
|
+
matched = false
|
|
38
39
|
op.points.each do |p|
|
|
39
|
-
point = p
|
|
40
40
|
select_def = ProjectNameHelpers.to_map(VoxgigStruct.getprop(p, "select"))
|
|
41
41
|
found = true
|
|
42
42
|
|
|
@@ -60,7 +60,47 @@ module ProjectNameUtilities
|
|
|
60
60
|
found = false if req_action != select_action
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
if found
|
|
64
|
+
point = p
|
|
65
|
+
matched = true
|
|
66
|
+
break
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# select.exist can list more than the params needed to pick a point, so
|
|
71
|
+
# nothing matches — fall back to the entity's own route rather than
|
|
72
|
+
# whichever point came last.
|
|
73
|
+
unless matched
|
|
74
|
+
# A request naming an action reaches here only because that action's
|
|
75
|
+
# own point failed its exist test, so it is unbuildable whatever we
|
|
76
|
+
# pick. Refuse it BEFORE choosing a fallback: the guard below
|
|
77
|
+
# compares the chosen point's $action and would wave the request
|
|
78
|
+
# through whenever the fallback lands on the action point itself.
|
|
79
|
+
req_action = reqselector ? VoxgigStruct.getprop(reqselector, "$action") : nil
|
|
80
|
+
if req_action
|
|
81
|
+
return nil, ctx.make_error("point_action_invalid",
|
|
82
|
+
"Operation \"#{op.name}\" action \"#{VoxgigStruct.stringify(req_action)}\" is not valid.")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# A terminal parameter marks a record route (/boards/{id}); a
|
|
86
|
+
# cross-reference ends in the relationship's name
|
|
87
|
+
# (/posts/{id}/author). Failing that, the shallower path wins.
|
|
88
|
+
parts_len = ->(p) {
|
|
89
|
+
parts = VoxgigStruct.getprop(p, "parts")
|
|
90
|
+
parts.is_a?(Array) ? parts.length : 0
|
|
91
|
+
}
|
|
92
|
+
terminal_param = ->(p) {
|
|
93
|
+
parts = VoxgigStruct.getprop(p, "parts")
|
|
94
|
+
parts.is_a?(Array) && parts.length > 0 && parts[-1].to_s.start_with?("{")
|
|
95
|
+
}
|
|
96
|
+
point = op.points[0]
|
|
97
|
+
op.points.each do |p|
|
|
98
|
+
if terminal_param.call(p) != terminal_param.call(point)
|
|
99
|
+
point = p if terminal_param.call(p)
|
|
100
|
+
elsif parts_len.call(p) < parts_len.call(point)
|
|
101
|
+
point = p
|
|
102
|
+
end
|
|
103
|
+
end
|
|
64
104
|
end
|
|
65
105
|
|
|
66
106
|
if reqselector
|
|
@@ -295,7 +295,7 @@ pub fn match_deep(check: &Value, base: &Value) -> Option<String> {
|
|
|
295
295
|
// (.sdk/test/primary/<name>.aontu carries a PENDING header). Everything else
|
|
296
296
|
// MUST contribute cases.
|
|
297
297
|
pub const PENDING_SECTIONS: &[&str] = &[
|
|
298
|
-
"fetcher", "makeFetchDef", "
|
|
298
|
+
"fetcher", "makeFetchDef", "makeResult", "featureAdd",
|
|
299
299
|
"featureHook", "featureInit",
|
|
300
300
|
];
|
|
301
301
|
|
|
@@ -1826,6 +1826,33 @@ fn feature_proxy_inactive_does_not_wrap() {
|
|
|
1826
1826
|
);
|
|
1827
1827
|
}
|
|
1828
1828
|
|
|
1829
|
+
// --- station --------------------------------------------------------------------
|
|
1830
|
+
|
|
1831
|
+
// The station adapter (installed by @voxgig/sdkgen-station) with NO open
|
|
1832
|
+
// station must be an inert no-op that emits nothing and fails nothing
|
|
1833
|
+
// (station design 3.1). Constructed through the generated make_feature
|
|
1834
|
+
// factory - never a compile-time module reference - so this file stays
|
|
1835
|
+
// compilable both with and without the station feature installed: absent,
|
|
1836
|
+
// the factory answers with the inert BaseFeature and fh_present skips the
|
|
1837
|
+
// assertions. Bound behaviour (registration, injection, wire events) is
|
|
1838
|
+
// exercised by the station library's own suite and the consumer-side
|
|
1839
|
+
// station validation, not here.
|
|
1840
|
+
#[test]
|
|
1841
|
+
fn feature_station_inert_without_open_station() {
|
|
1842
|
+
if !fh_present(&["station"]) {
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
let f = RUSTCRATE::make_feature("station");
|
|
1846
|
+
assert_eq!(
|
|
1847
|
+
"station",
|
|
1848
|
+
f.borrow().name(),
|
|
1849
|
+
"generated factory has a station arm"
|
|
1850
|
+
);
|
|
1851
|
+
let h = fh_make(None, vec![(f, Value::Noval)]);
|
|
1852
|
+
let res = h.op(opspec("load"));
|
|
1853
|
+
assert!(res.ok, "expected inert station to pass the op through");
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1829
1856
|
// --- composition ----------------------------------------------------------------
|
|
1830
1857
|
|
|
1831
1858
|
#[test]
|
|
@@ -19,6 +19,14 @@ fn default_http_fetch(fullurl: &str, fetchdef: &Value) -> Result<Value, ProjectN
|
|
|
19
19
|
builder = builder.proxy(p);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
// A `redirect: "manual"` annotation (set by the station feature when a
|
|
23
|
+
// hosts policy is active - the same in-band channel as `proxy` above)
|
|
24
|
+
// disables automatic redirect following, so a 3xx rides back as a normal
|
|
25
|
+
// response: a Location pointing off an egress allowlist must never pull
|
|
26
|
+
// an automatic credentialed follow-up request.
|
|
27
|
+
if get_str(fetchdef, "redirect").as_deref() == Some("manual") {
|
|
28
|
+
builder = builder.redirects(0);
|
|
29
|
+
}
|
|
22
30
|
let agent = builder.build();
|
|
23
31
|
|
|
24
32
|
let mut req = agent.request(&method, fullurl);
|
|
@@ -228,6 +228,21 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
|
|
|
228
228
|
} else {
|
|
229
229
|
feature_order = names;
|
|
230
230
|
}
|
|
231
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
232
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
233
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
234
|
+
// or first, when no test entry exists. Without this the sorted
|
|
235
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
236
|
+
// features, turning its wire-truth events into fiction.
|
|
237
|
+
if let Some(si) = feature_order.iter().position(|n| n == "station") {
|
|
238
|
+
feature_order.remove(si);
|
|
239
|
+
let at = feature_order
|
|
240
|
+
.iter()
|
|
241
|
+
.position(|n| n == "test")
|
|
242
|
+
.map(|ti| ti + 1)
|
|
243
|
+
.unwrap_or(0);
|
|
244
|
+
feature_order.insert(at, "station".to_string());
|
|
245
|
+
}
|
|
231
246
|
}
|
|
232
247
|
}
|
|
233
248
|
let order_list =
|