@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
|
@@ -112,6 +112,78 @@ defmodule ProjectName.PrimaryUtilityTest do
|
|
|
112
112
|
assert S.getpath(opts, "__derived__.clean.keyre") == "key|token|id"
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
+
# --- feature add-order ----------------------------------------------------
|
|
116
|
+
# options.feature accepts an ordered LIST (developer add-order) or a map
|
|
117
|
+
# (defaults test-first); make_options records the resolved order in
|
|
118
|
+
# __derived__.featureorder. Driven with a synthetic ctx (empty config
|
|
119
|
+
# options) so the assertions do not depend on this SDK's own features.
|
|
120
|
+
|
|
121
|
+
defp resolve_order(feature) do
|
|
122
|
+
ctx =
|
|
123
|
+
S.jm([
|
|
124
|
+
"utility", Utility.new(),
|
|
125
|
+
"options", H.deep(%{"feature" => feature}),
|
|
126
|
+
"config", H.deep(%{"options" => %{}})
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
fo = S.getpath(Utility.make_options(ctx), "__derived__.featureorder")
|
|
130
|
+
n = S.size(fo)
|
|
131
|
+
order = if n == 0, do: [], else: Enum.map(0..(n - 1), fn i -> S.getelem(fo, i) end)
|
|
132
|
+
Enum.join(order, ",")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
test "feature order: map form is test-first" do
|
|
136
|
+
assert "test,metrics" ==
|
|
137
|
+
resolve_order(%{"metrics" => %{"active" => true}, "test" => %{"active" => true}})
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
test "feature order: an explicit list preserves its order" do
|
|
141
|
+
assert "metrics,test" ==
|
|
142
|
+
resolve_order([
|
|
143
|
+
%{"name" => "metrics", "active" => true},
|
|
144
|
+
%{"name" => "test", "active" => true}
|
|
145
|
+
])
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Station special case, mirroring test's: its transport wrap must sit
|
|
149
|
+
# immediately outside the base transport, so map-form activation hoists
|
|
150
|
+
# it to just after test - or first, when no test entry exists.
|
|
151
|
+
test "feature order: map form hoists station after test" do
|
|
152
|
+
assert "test,station,metrics" ==
|
|
153
|
+
resolve_order(%{
|
|
154
|
+
"metrics" => %{"active" => true},
|
|
155
|
+
"station" => %{"active" => true},
|
|
156
|
+
"test" => %{"active" => true}
|
|
157
|
+
})
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
test "feature order: station first when no test entry exists" do
|
|
161
|
+
assert "station,metrics" ==
|
|
162
|
+
resolve_order(%{
|
|
163
|
+
"metrics" => %{"active" => true},
|
|
164
|
+
"station" => %{"active" => true}
|
|
165
|
+
})
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# The extend seam (feature INSTANCES supplied at construction - the
|
|
169
|
+
# station adopt path): validate must accept the key verbatim, or the
|
|
170
|
+
# constructor's extend loop reads a stripped option and the seam is dead.
|
|
171
|
+
test "make_options preserves options.extend through validation" do
|
|
172
|
+
marker = S.jm(["name", "station"])
|
|
173
|
+
|
|
174
|
+
ctx =
|
|
175
|
+
S.jm([
|
|
176
|
+
"utility", Utility.new(),
|
|
177
|
+
"options", S.jm(["extend", S.jt([marker])]),
|
|
178
|
+
"config", H.deep(%{"options" => %{}})
|
|
179
|
+
])
|
|
180
|
+
|
|
181
|
+
extend = S.getprop(Utility.make_options(ctx), "extend")
|
|
182
|
+
assert S.islist(extend)
|
|
183
|
+
assert S.size(extend) == 1
|
|
184
|
+
assert S.getprop(S.getelem(extend, 0), "name") == "station"
|
|
185
|
+
end
|
|
186
|
+
|
|
115
187
|
test "make_error formats a namespaced message and raises by default" do
|
|
116
188
|
c = client()
|
|
117
189
|
ctx = ctx(c, "load")
|
|
@@ -4,6 +4,7 @@ import (
|
|
|
4
4
|
"fmt"
|
|
5
5
|
"math/rand"
|
|
6
6
|
"regexp"
|
|
7
|
+
"strings"
|
|
7
8
|
|
|
8
9
|
vs "github.com/voxgig/struct"
|
|
9
10
|
|
|
@@ -310,9 +311,40 @@ func (f *TestFeature) makeNetsim(net map[string]any, inner core.FetcherFunc) cor
|
|
|
310
311
|
func (f *TestFeature) buildArgs(ctx *core.Context, op *core.Operation, args map[string]any) any {
|
|
311
312
|
opname := op.Name
|
|
312
313
|
|
|
313
|
-
//
|
|
314
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
315
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
316
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
317
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
318
|
+
// failing that the shallower path wins.
|
|
314
319
|
points := vs.GetPath([]any{"entity", ctx.Entity.GetName(), "op", opname, "points"}, ctx.Config)
|
|
315
|
-
point := vs.GetElem(points,
|
|
320
|
+
point := vs.GetElem(points, 0)
|
|
321
|
+
if plist, ok := points.([]any); ok {
|
|
322
|
+
partsLen := func(p any) int {
|
|
323
|
+
if parts, ok := vs.GetProp(p, "parts").([]any); ok {
|
|
324
|
+
return len(parts)
|
|
325
|
+
}
|
|
326
|
+
return 0
|
|
327
|
+
}
|
|
328
|
+
terminalParam := func(p any) bool {
|
|
329
|
+
parts, ok := vs.GetProp(p, "parts").([]any)
|
|
330
|
+
if !ok || 0 == len(parts) {
|
|
331
|
+
return false
|
|
332
|
+
}
|
|
333
|
+
last, _ := parts[len(parts)-1].(string)
|
|
334
|
+
return strings.HasPrefix(last, "{")
|
|
335
|
+
}
|
|
336
|
+
for _, cand := range plist {
|
|
337
|
+
candTerm := terminalParam(cand)
|
|
338
|
+
bestTerm := terminalParam(point)
|
|
339
|
+
if candTerm != bestTerm {
|
|
340
|
+
if candTerm {
|
|
341
|
+
point = cand
|
|
342
|
+
}
|
|
343
|
+
} else if partsLen(cand) < partsLen(point) {
|
|
344
|
+
point = cand
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
316
348
|
|
|
317
349
|
// Get required params.
|
|
318
350
|
paramsPath := vs.GetPath([]any{"args", "params"}, point)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package sdktest
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"errors"
|
|
4
5
|
"fmt"
|
|
5
6
|
"strings"
|
|
6
7
|
"testing"
|
|
@@ -556,27 +557,48 @@ func TestPrimaryUtility(t *testing.T) {
|
|
|
556
557
|
})
|
|
557
558
|
})
|
|
558
559
|
|
|
560
|
+
// Was one hand-written case (the single-point path) covering one of this
|
|
561
|
+
// utility's seven branches, which is how the corpus fixture came to be
|
|
562
|
+
// marked deferred as "needs a real client". It does not: NewContext
|
|
563
|
+
// rebuilds Op from opname + entity + config, and Options can be supplied
|
|
564
|
+
// literally. Driven from the corpus now, so TS and Go assert the same
|
|
565
|
+
// branches.
|
|
566
|
+
//
|
|
567
|
+
// TS returns the error AS the value; Go returns it as a second result. The
|
|
568
|
+
// corpus says `match: out: code` for both, so the error is normalised to a
|
|
569
|
+
// map carrying its code here rather than forking the fixture per language.
|
|
559
570
|
t.Run("makePoint-basic", func(t *testing.T) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
"
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
571
|
+
runsetNamed(t, "makePoint", getSpec(primary, "makePoint", "basic"), func(entry map[string]any) (any, error) {
|
|
572
|
+
ctxmap, _ := entry["ctx"].(map[string]any)
|
|
573
|
+
|
|
574
|
+
// NewContext resolves Op from the ENTITY NAME, and reaches it
|
|
575
|
+
// through the Entity interface — a literal {name:...} map from the
|
|
576
|
+
// fixture is not one, so entname would be "" and every lookup would
|
|
577
|
+
// miss, reporting point_no_points for all seven cases. TS reads the
|
|
578
|
+
// same field with getprop and accepts the plain map. Swap in the
|
|
579
|
+
// package's minimal Entity so both ports resolve the same op.
|
|
580
|
+
if em, ok := ctxmap["entity"].(map[string]any); ok {
|
|
581
|
+
name, _ := em["name"].(string)
|
|
582
|
+
made := []any{}
|
|
583
|
+
swapped := map[string]any{}
|
|
584
|
+
for k, v := range ctxmap {
|
|
585
|
+
swapped[k] = v
|
|
586
|
+
}
|
|
587
|
+
swapped["entity"] = &plEntity{name: name, made: &made}
|
|
588
|
+
ctxmap = swapped
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
ctx := makeCtxFromMap(ctxmap, client, utility)
|
|
592
|
+
point, err := utility.MakePoint(ctx)
|
|
593
|
+
if err != nil {
|
|
594
|
+
var sdkErr *sdk.ProjectNameError
|
|
595
|
+
if errors.As(err, &sdkErr) {
|
|
596
|
+
return map[string]any{"code": sdkErr.Code}, nil
|
|
597
|
+
}
|
|
598
|
+
return nil, err
|
|
599
|
+
}
|
|
600
|
+
return point, nil
|
|
601
|
+
})
|
|
580
602
|
})
|
|
581
603
|
|
|
582
604
|
t.Run("makeUrl-basic", func(t *testing.T) {
|
|
@@ -218,7 +218,7 @@ type RunSubject func(entry map[string]any) (any, error)
|
|
|
218
218
|
// PENDING sections are the ones deliberately left empty in the shared corpus
|
|
219
219
|
// (.sdk/test/primary/<name>.aontu). Everything else MUST contribute cases.
|
|
220
220
|
var pendingSections = map[string]bool{
|
|
221
|
-
"fetcher": true, "makeFetchDef": true, "
|
|
221
|
+
"fetcher": true, "makeFetchDef": true, "makeResult": true,
|
|
222
222
|
"featureAdd": true, "featureHook": true, "featureInit": true,
|
|
223
223
|
}
|
|
224
224
|
|
|
@@ -57,6 +57,20 @@ func defaultHTTPFetch(fullurl string, fetchdef map[string]any) (map[string]any,
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Honour a redirect annotation: "manual" surfaces a 3xx as an ordinary
|
|
61
|
+
// response instead of auto-following it. In the ts/js targets fetchdef
|
|
62
|
+
// rides straight into fetch(), where `redirect` is a native option -
|
|
63
|
+
// this is the same seam for Go's auto-following http.Client. The
|
|
64
|
+
// station feature sets it under a hosts egress policy, so a Location
|
|
65
|
+
// off the allowlist cannot pull an automatic credentialed follow-up.
|
|
66
|
+
if redirect, ok := fetchdef["redirect"].(string); ok && redirect == "manual" {
|
|
67
|
+
manual := *client
|
|
68
|
+
manual.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
|
69
|
+
return http.ErrUseLastResponse
|
|
70
|
+
}
|
|
71
|
+
client = &manual
|
|
72
|
+
}
|
|
73
|
+
|
|
60
74
|
resp, err := client.Do(req)
|
|
61
75
|
if err != nil {
|
|
62
76
|
return nil, err
|
|
@@ -217,17 +217,44 @@ func makeOptionsUtil(ctx *core.Context) map[string]any {
|
|
|
217
217
|
hasTest = true
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
|
+
ordered := make([]string, 0, len(names))
|
|
220
221
|
if hasTest {
|
|
221
|
-
|
|
222
|
+
ordered = append(ordered, "test")
|
|
222
223
|
for _, n := range names {
|
|
223
224
|
if n != "test" {
|
|
224
|
-
|
|
225
|
+
ordered = append(ordered, n)
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
} else {
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
ordered = append(ordered, names...)
|
|
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
|
+
si := -1
|
|
238
|
+
for i, n := range ordered {
|
|
239
|
+
if n == "station" {
|
|
240
|
+
si = i
|
|
241
|
+
break
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if si >= 0 {
|
|
245
|
+
ordered = append(ordered[:si], ordered[si+1:]...)
|
|
246
|
+
ti := 0
|
|
247
|
+
for i, n := range ordered {
|
|
248
|
+
if n == "test" {
|
|
249
|
+
ti = i + 1
|
|
250
|
+
break
|
|
251
|
+
}
|
|
230
252
|
}
|
|
253
|
+
ordered = append(ordered[:ti],
|
|
254
|
+
append([]string{"station"}, ordered[ti:]...)...)
|
|
255
|
+
}
|
|
256
|
+
for _, n := range ordered {
|
|
257
|
+
featureorder = append(featureorder, n)
|
|
231
258
|
}
|
|
232
259
|
}
|
|
233
260
|
|
|
@@ -8,6 +8,47 @@ import (
|
|
|
8
8
|
"GOMODULE/core"
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
// How many path segments a point has.
|
|
12
|
+
func partsLen(point map[string]any) int {
|
|
13
|
+
if parts, ok := vs.GetProp(point, "parts").([]any); ok {
|
|
14
|
+
return len(parts)
|
|
15
|
+
}
|
|
16
|
+
return 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
20
|
+
// record's identifier (`/boards/{id}`); a cross-reference that also returns
|
|
21
|
+
// the entity ends in the relationship's name (`/posts/{id}/author`).
|
|
22
|
+
func terminalParam(point map[string]any) bool {
|
|
23
|
+
parts, ok := vs.GetProp(point, "parts").([]any)
|
|
24
|
+
if !ok || 0 == len(parts) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
last, _ := parts[len(parts)-1].(string)
|
|
28
|
+
return strings.HasPrefix(last, "{")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The entity's OWN route among an op's points: a terminal parameter first,
|
|
32
|
+
// then the fewest path segments. Ties keep the earlier point, so the model's
|
|
33
|
+
// sorted-key order decides. The same rule runs at generation time, in
|
|
34
|
+
// helpers/opShape.ts — a template ships standalone, so both sides must move
|
|
35
|
+
// together.
|
|
36
|
+
func ownPoint(points []map[string]any) map[string]any {
|
|
37
|
+
best := points[0]
|
|
38
|
+
for _, cand := range points {
|
|
39
|
+
candTerm := terminalParam(cand)
|
|
40
|
+
bestTerm := terminalParam(best)
|
|
41
|
+
if candTerm != bestTerm {
|
|
42
|
+
if candTerm {
|
|
43
|
+
best = cand
|
|
44
|
+
}
|
|
45
|
+
} else if partsLen(cand) < partsLen(best) {
|
|
46
|
+
best = cand
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return best
|
|
50
|
+
}
|
|
51
|
+
|
|
11
52
|
func makePointUtil(ctx *core.Context) (map[string]any, error) {
|
|
12
53
|
if ctx.Out["point"] != nil {
|
|
13
54
|
// A PrePoint feature hook (e.g. rbac) may short-circuit the
|
|
@@ -52,9 +93,10 @@ func makePointUtil(ctx *core.Context) (map[string]any, error) {
|
|
|
52
93
|
}
|
|
53
94
|
|
|
54
95
|
var point map[string]any
|
|
96
|
+
matched := false
|
|
55
97
|
for i := 0; i < len(op.Points); i++ {
|
|
56
|
-
|
|
57
|
-
selectDef := core.ToMapAny(vs.GetProp(
|
|
98
|
+
cand := op.Points[i]
|
|
99
|
+
selectDef := core.ToMapAny(vs.GetProp(cand, "select"))
|
|
58
100
|
found := true
|
|
59
101
|
|
|
60
102
|
if selector != nil && selectDef != nil {
|
|
@@ -82,10 +124,32 @@ func makePointUtil(ctx *core.Context) (map[string]any, error) {
|
|
|
82
124
|
}
|
|
83
125
|
|
|
84
126
|
if found {
|
|
127
|
+
point = cand
|
|
128
|
+
matched = true
|
|
85
129
|
break
|
|
86
130
|
}
|
|
87
131
|
}
|
|
88
132
|
|
|
133
|
+
// select.exist can list more than the params needed to pick a point,
|
|
134
|
+
// so nothing matches — fall back to the entity's own route rather
|
|
135
|
+
// than whichever point came last.
|
|
136
|
+
if !matched {
|
|
137
|
+
// A request naming an action reaches here only because that
|
|
138
|
+
// action's own point failed its exist test, so it is unbuildable
|
|
139
|
+
// whatever we pick. Refuse it BEFORE choosing a fallback: the
|
|
140
|
+
// guard below compares the chosen point's $action and would wave
|
|
141
|
+
// the request through whenever the fallback lands on the action
|
|
142
|
+
// point itself.
|
|
143
|
+
if reqselector != nil && vs.GetProp(reqselector, "$action") != nil {
|
|
144
|
+
return nil, ctx.MakeError("point_action_invalid",
|
|
145
|
+
"Operation \""+op.Name+
|
|
146
|
+
"\" action \""+vs.Stringify(vs.GetProp(reqselector, "$action"))+
|
|
147
|
+
"\" is not valid.")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
point = ownPoint(op.Points)
|
|
151
|
+
}
|
|
152
|
+
|
|
89
153
|
if reqselector != nil {
|
|
90
154
|
reqAction := vs.GetProp(reqselector, "$action")
|
|
91
155
|
if reqAction != nil && point != nil {
|
|
@@ -26,6 +26,29 @@ public class TestFeature extends BaseFeature {
|
|
|
26
26
|
private Map<String, Object> options;
|
|
27
27
|
private int netcalls = 0;
|
|
28
28
|
|
|
29
|
+
// How many path segments a point has, and whether its path ends in a
|
|
30
|
+
// parameter — the rule makePoint uses to tell the entity's own route from a
|
|
31
|
+
// cross-reference that also returns it.
|
|
32
|
+
@SuppressWarnings("unchecked")
|
|
33
|
+
private static int pointPartsLen(Object point) {
|
|
34
|
+
Object parts = Struct.getprop(point, "parts");
|
|
35
|
+
return parts instanceof List ? ((List<Object>) parts).size() : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@SuppressWarnings("unchecked")
|
|
39
|
+
private static boolean pointTerminalParam(Object point) {
|
|
40
|
+
Object parts = Struct.getprop(point, "parts");
|
|
41
|
+
if (!(parts instanceof List)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
List<Object> list = (List<Object>) parts;
|
|
45
|
+
if (list.isEmpty()) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
Object last = list.get(list.size() - 1);
|
|
49
|
+
return last instanceof String && ((String) last).startsWith("{");
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
public TestFeature() {
|
|
30
53
|
super("test", "0.0.1", true);
|
|
31
54
|
}
|
|
@@ -289,11 +312,28 @@ public class TestFeature extends BaseFeature {
|
|
|
289
312
|
private Object buildArgs(Context ctx, Operation op, Map<String, Object> args) {
|
|
290
313
|
String opname = op.name;
|
|
291
314
|
|
|
292
|
-
//
|
|
315
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
316
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
317
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
318
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
319
|
+
// failing that the shallower path wins.
|
|
293
320
|
Object points = Struct.getpath(ctx.config,
|
|
294
321
|
List.of("entity", ctx.entity == null ? "" : ctx.entity.getName(),
|
|
295
322
|
"op", opname, "points"));
|
|
296
|
-
Object point = Struct.getelem(points,
|
|
323
|
+
Object point = Struct.getelem(points, 0);
|
|
324
|
+
if (points instanceof List) {
|
|
325
|
+
for (Object cand : (List<Object>) points) {
|
|
326
|
+
boolean candTerm = pointTerminalParam(cand);
|
|
327
|
+
boolean bestTerm = pointTerminalParam(point);
|
|
328
|
+
if (candTerm != bestTerm) {
|
|
329
|
+
if (candTerm) {
|
|
330
|
+
point = cand;
|
|
331
|
+
}
|
|
332
|
+
} else if (pointPartsLen(cand) < pointPartsLen(point)) {
|
|
333
|
+
point = cand;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
297
337
|
|
|
298
338
|
// Get required params.
|
|
299
339
|
Object paramsPath = Struct.getpath(point, List.of("args", "params"));
|
|
@@ -59,10 +59,20 @@ final class Fetcher {
|
|
|
59
59
|
reqb.setHeader("User-Agent", "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)");
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// Honour a redirect annotation on the fetch definition (set by the
|
|
63
|
+
// station feature's middleware under a hosts policy): "manual" returns
|
|
64
|
+
// a 3xx like any other response instead of following it — the ts
|
|
65
|
+
// donor's fetch honours the same key natively, and an automatic follow
|
|
66
|
+
// would carry injected credentials to a host no policy approved.
|
|
67
|
+
HttpClient.Redirect redirectPolicy =
|
|
68
|
+
"manual".equals(fetchdef.get("redirect"))
|
|
69
|
+
? HttpClient.Redirect.NEVER
|
|
70
|
+
: HttpClient.Redirect.NORMAL;
|
|
71
|
+
|
|
62
72
|
// Honour a proxy annotation on the fetch definition (set by the proxy
|
|
63
73
|
// feature): route the request through a proxied HttpClient.
|
|
64
74
|
HttpClient.Builder clientb = HttpClient.newBuilder()
|
|
65
|
-
.followRedirects(
|
|
75
|
+
.followRedirects(redirectPolicy);
|
|
66
76
|
Object proxy = fetchdef.get("proxy");
|
|
67
77
|
if (proxy instanceof String && !"".equals(proxy)) {
|
|
68
78
|
try {
|
|
@@ -156,6 +156,17 @@ final class MakeOptions {
|
|
|
156
156
|
else {
|
|
157
157
|
featureorder.addAll(names);
|
|
158
158
|
}
|
|
159
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
160
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
161
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
162
|
+
// or first, when no test entry exists. Without this the sorted
|
|
163
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
164
|
+
// features, turning its wire-truth events into fiction.
|
|
165
|
+
int si = featureorder.indexOf("station");
|
|
166
|
+
if (0 <= si) {
|
|
167
|
+
featureorder.remove(si);
|
|
168
|
+
featureorder.add(featureorder.indexOf("test") + 1, "station");
|
|
169
|
+
}
|
|
159
170
|
}
|
|
160
171
|
|
|
161
172
|
Map<String, Object> derived = new LinkedHashMap<>();
|
|
@@ -14,6 +14,31 @@ final class MakePoint {
|
|
|
14
14
|
|
|
15
15
|
private MakePoint() {}
|
|
16
16
|
|
|
17
|
+
// How many path segments a point has.
|
|
18
|
+
private static int partsLen(Map<String, Object> point) {
|
|
19
|
+
Object parts = Struct.getprop(point, "parts");
|
|
20
|
+
return parts instanceof List ? ((List<Object>) parts).size() : 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
24
|
+
// record's identifier (/boards/{id}); a cross-reference that also returns
|
|
25
|
+
// the entity ends in the relationship's name (/posts/{id}/author). That,
|
|
26
|
+
// then fewest segments, is what tells the entity's own route from a
|
|
27
|
+
// cross-reference. The same rule runs at generation time, in
|
|
28
|
+
// helpers/opShape.ts — both sides must move together.
|
|
29
|
+
private static boolean terminalParam(Map<String, Object> point) {
|
|
30
|
+
Object parts = Struct.getprop(point, "parts");
|
|
31
|
+
if (!(parts instanceof List)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
List<Object> list = (List<Object>) parts;
|
|
35
|
+
if (list.isEmpty()) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
Object last = list.get(list.size() - 1);
|
|
39
|
+
return last instanceof String && ((String) last).startsWith("{");
|
|
40
|
+
}
|
|
41
|
+
|
|
17
42
|
static Map<String, Object> makePoint(Context ctx) {
|
|
18
43
|
Object outPoint = ctx.out.get("point");
|
|
19
44
|
if (outPoint != null) {
|
|
@@ -62,10 +87,11 @@ final class MakePoint {
|
|
|
62
87
|
}
|
|
63
88
|
|
|
64
89
|
Map<String, Object> point = null;
|
|
90
|
+
boolean matched = false;
|
|
65
91
|
for (int i = 0; i < op.points.size(); i++) {
|
|
66
|
-
|
|
92
|
+
Map<String, Object> cand = op.points.get(i);
|
|
67
93
|
Map<String, Object> selectDef =
|
|
68
|
-
Helpers.toMapAny(Struct.getprop(
|
|
94
|
+
Helpers.toMapAny(Struct.getprop(cand, "select"));
|
|
69
95
|
boolean found = true;
|
|
70
96
|
|
|
71
97
|
if (selector != null && selectDef != null) {
|
|
@@ -92,10 +118,43 @@ final class MakePoint {
|
|
|
92
118
|
}
|
|
93
119
|
|
|
94
120
|
if (found) {
|
|
121
|
+
point = cand;
|
|
122
|
+
matched = true;
|
|
95
123
|
break;
|
|
96
124
|
}
|
|
97
125
|
}
|
|
98
126
|
|
|
127
|
+
// select.exist can list more than the params needed to pick a point, so
|
|
128
|
+
// nothing matches — fall back to the entity's own route rather than
|
|
129
|
+
// whichever point came last.
|
|
130
|
+
if (!matched) {
|
|
131
|
+
// A request naming an action reaches here only because that action's
|
|
132
|
+
// own point failed its exist test, so it is unbuildable whatever we
|
|
133
|
+
// pick. Refuse it BEFORE choosing a fallback: the guard below
|
|
134
|
+
// compares the chosen point's $action and would wave the request
|
|
135
|
+
// through whenever the fallback lands on the action point itself.
|
|
136
|
+
Object unmatchedAction =
|
|
137
|
+
reqselector != null ? Struct.getprop(reqselector, "$action", null) : null;
|
|
138
|
+
if (unmatchedAction != null) {
|
|
139
|
+
throw ctx.makeError("point_action_invalid",
|
|
140
|
+
"Operation \"" + op.name
|
|
141
|
+
+ "\" action \"" + Struct.stringify(unmatchedAction) + "\" is not valid.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
point = op.points.get(0);
|
|
145
|
+
for (Map<String, Object> cand : op.points) {
|
|
146
|
+
boolean candTerm = terminalParam(cand);
|
|
147
|
+
boolean bestTerm = terminalParam(point);
|
|
148
|
+
if (candTerm != bestTerm) {
|
|
149
|
+
if (candTerm) {
|
|
150
|
+
point = cand;
|
|
151
|
+
}
|
|
152
|
+
} else if (partsLen(cand) < partsLen(point)) {
|
|
153
|
+
point = cand;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
99
158
|
if (reqselector != null) {
|
|
100
159
|
Object reqAction = Struct.getprop(reqselector, "$action", null);
|
|
101
160
|
if (reqAction != null && point != null) {
|
|
@@ -251,9 +251,28 @@ class TestFeature extends BaseFeature {
|
|
|
251
251
|
const isempty = struct.isempty
|
|
252
252
|
|
|
253
253
|
const opname = getprop(op, 'name')
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
const points = getpath(ctx.config, [
|
|
255
|
+
'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']) || []
|
|
256
|
+
|
|
257
|
+
// Pick the entity's own endpoint, not a cross-reference from another
|
|
258
|
+
// resource that also returns it — the same rule makePoint falls back to,
|
|
259
|
+
// so the seed-data query is built from the endpoint the request will
|
|
260
|
+
// actually be sent to: a terminal `{id}` marks a record route, and
|
|
261
|
+
// failing that the shallower path wins.
|
|
262
|
+
const isterm = (pt) => {
|
|
263
|
+
const parts = pt.parts
|
|
264
|
+
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
265
|
+
return 'string' === typeof last && 0 === last.indexOf('{')
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let point = getelem(points, 0)
|
|
269
|
+
for (let i = 1; i < points.length; i++) {
|
|
270
|
+
const cand = getelem(points, i)
|
|
271
|
+
if (isterm(cand) !== isterm(point) ? isterm(cand) :
|
|
272
|
+
cand.parts.length < point.parts.length) {
|
|
273
|
+
point = cand
|
|
274
|
+
}
|
|
275
|
+
}
|
|
257
276
|
|
|
258
277
|
const reqd = transform(
|
|
259
278
|
select(getpath(point, ['args', 'params']), { reqd: true }),
|
|
@@ -68,6 +68,12 @@ function makeOptions(ctx) {
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
utility: {},
|
|
71
|
+
// Feature INSTANCES supplied at construction (the station adopt
|
|
72
|
+
// path): consumed by the constructor's featureAdd loop, so they are
|
|
73
|
+
// class instances, not data - `$ANY` accepts them verbatim. Without
|
|
74
|
+
// this entry the seam is dead: the constructor reads
|
|
75
|
+
// options.extend, but validate rejected the key.
|
|
76
|
+
extend: '`$ANY`',
|
|
71
77
|
system: {
|
|
72
78
|
fetch: undefined
|
|
73
79
|
},
|
|
@@ -131,10 +137,22 @@ function makeOptions(ctx) {
|
|
|
131
137
|
// otherwise order the map test-first, then the remaining names sorted, so
|
|
132
138
|
// the outcome is deterministic and `test` is always the base transport.
|
|
133
139
|
if (0 === featureorder.length) {
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
let names = Object.keys(opts.feature || {}).sort()
|
|
141
|
+
names = names.indexOf('test') < 0
|
|
136
142
|
? names
|
|
137
143
|
: ['test'].concat(names.filter((n) => 'test' !== n))
|
|
144
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
145
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
146
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
147
|
+
// or first, when no test entry exists. Without this the sorted
|
|
148
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
149
|
+
// features, turning its wire-truth events into fiction.
|
|
150
|
+
const si = names.indexOf('station')
|
|
151
|
+
if (0 <= si) {
|
|
152
|
+
names.splice(si, 1)
|
|
153
|
+
names.splice(names.indexOf('test') + 1, 0, 'station')
|
|
154
|
+
}
|
|
155
|
+
featureorder = names
|
|
138
156
|
}
|
|
139
157
|
|
|
140
158
|
opts.__derived__ = {
|