@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
|
@@ -330,9 +330,10 @@ inline Value makePoint(CtxPtr ctx) {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
Value point;
|
|
333
|
+
bool matched = false;
|
|
333
334
|
for (size_t i = 0; i < op->points.size(); i++) {
|
|
334
|
-
|
|
335
|
-
Value selectDef = Helpers::toMapAny(getp(
|
|
335
|
+
Value cand = op->points[i];
|
|
336
|
+
Value selectDef = Helpers::toMapAny(getp(cand, "select"));
|
|
336
337
|
bool found = true;
|
|
337
338
|
|
|
338
339
|
if (selector.is_map() && selectDef.is_map()) {
|
|
@@ -356,7 +357,58 @@ inline Value makePoint(CtxPtr ctx) {
|
|
|
356
357
|
if (reqAction != selectAction) found = false;
|
|
357
358
|
}
|
|
358
359
|
|
|
359
|
-
if (found)
|
|
360
|
+
if (found) {
|
|
361
|
+
point = cand;
|
|
362
|
+
matched = true;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// select.exist can list more than the params needed to pick a point (for
|
|
368
|
+
// /boards/{id} it is Trello's 17 optional query-includes), so a plain
|
|
369
|
+
// {id} call matches NOTHING. Fall back to the entity's own route rather
|
|
370
|
+
// than whichever point came last.
|
|
371
|
+
if (!matched) {
|
|
372
|
+
// A request naming an action reaches here only because that action's
|
|
373
|
+
// own point failed its exist test, so it is unbuildable whatever we
|
|
374
|
+
// pick. Refuse it BEFORE choosing a fallback: the guard below compares
|
|
375
|
+
// the chosen point's $action and would wave the request through
|
|
376
|
+
// whenever the fallback lands on the action point itself.
|
|
377
|
+
Value unmatchedAction = getp(reqselector, "$action", Value(nullptr));
|
|
378
|
+
if (!unmatchedAction.is_null()) {
|
|
379
|
+
throw ctx->makeError("point_action_invalid",
|
|
380
|
+
"Operation \"" + op->name + "\" action \"" +
|
|
381
|
+
Struct::stringify(unmatchedAction) + "\" is not valid.");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// A terminal parameter marks a record route (/boards/{id}); a
|
|
385
|
+
// cross-reference ends in the relationship's name (/posts/{id}/author).
|
|
386
|
+
// Failing that, the shallower path wins. The same rule runs at
|
|
387
|
+
// generation time, in helpers/opShape.ts — both sides must move
|
|
388
|
+
// together.
|
|
389
|
+
auto partsLen = [](const Value& p) -> size_t {
|
|
390
|
+
Value parts = getp(p, "parts");
|
|
391
|
+
return parts.is_list() ? parts.as_list()->size() : 0;
|
|
392
|
+
};
|
|
393
|
+
auto terminalParam = [](const Value& p) -> bool {
|
|
394
|
+
Value parts = getp(p, "parts");
|
|
395
|
+
if (!parts.is_list() || parts.as_list()->empty()) return false;
|
|
396
|
+
const Value& last = parts.as_list()->back();
|
|
397
|
+
return last.is_string() && 0 == last.as_string().rfind("{", 0);
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
point = op->points[0];
|
|
401
|
+
for (size_t i = 0; i < op->points.size(); i++) {
|
|
402
|
+
const Value& cand = op->points[i];
|
|
403
|
+
bool candTerm = terminalParam(cand);
|
|
404
|
+
bool bestTerm = terminalParam(point);
|
|
405
|
+
if (candTerm != bestTerm) {
|
|
406
|
+
if (candTerm) point = cand;
|
|
407
|
+
}
|
|
408
|
+
else if (partsLen(cand) < partsLen(point)) {
|
|
409
|
+
point = cand;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
360
412
|
}
|
|
361
413
|
|
|
362
414
|
if (reqselector.is_map()) {
|
|
@@ -1159,8 +1211,16 @@ inline Value makeOptions(CtxPtr ctx) {
|
|
|
1159
1211
|
std::sort(names.begin(), names.end());
|
|
1160
1212
|
bool hasTest = std::find(names.begin(), names.end(), "test") != names.end();
|
|
1161
1213
|
if (hasTest) featureOrder.push_back("test");
|
|
1214
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
1215
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
1216
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
1217
|
+
// or first, when no test entry exists. Without this the sorted
|
|
1218
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
1219
|
+
// features, turning its wire-truth events into fiction.
|
|
1220
|
+
bool hasStation = std::find(names.begin(), names.end(), "station") != names.end();
|
|
1221
|
+
if (hasStation) featureOrder.push_back("station");
|
|
1162
1222
|
for (const auto& n : names) {
|
|
1163
|
-
if (n != "test") featureOrder.push_back(n);
|
|
1223
|
+
if (n != "test" && n != "station") featureOrder.push_back(n);
|
|
1164
1224
|
}
|
|
1165
1225
|
}
|
|
1166
1226
|
}
|
|
@@ -313,10 +313,44 @@ public class TestFeature : BaseFeature
|
|
|
313
313
|
{
|
|
314
314
|
var opname = op.Name;
|
|
315
315
|
|
|
316
|
-
//
|
|
316
|
+
// Pick the entity's own endpoint from config, not a cross-reference
|
|
317
|
+
// from another resource that also returns it — the same rule
|
|
318
|
+
// MakePoint falls back to, so the seed-data query is built from the
|
|
319
|
+
// endpoint the request will actually be sent to: a terminal `{id}`
|
|
320
|
+
// marks a record route, and failing that the shallower path wins.
|
|
317
321
|
var points = StructUtils.GetPath(ctx.Config,
|
|
318
322
|
StructUtils.Jt("entity", ctx.Entity!.GetName(), "op", opname, "points"));
|
|
319
|
-
var point = StructUtils.GetElem(points,
|
|
323
|
+
var point = StructUtils.GetElem(points, 0);
|
|
324
|
+
if (points is List<object?> pointList)
|
|
325
|
+
{
|
|
326
|
+
static int PointPartsLen(object? p) =>
|
|
327
|
+
StructUtils.GetProp(p, "parts") is List<object?> parts ? parts.Count : 0;
|
|
328
|
+
static bool PointTerminalParam(object? p)
|
|
329
|
+
{
|
|
330
|
+
if (StructUtils.GetProp(p, "parts") is not List<object?> parts || parts.Count == 0)
|
|
331
|
+
{
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
return parts[parts.Count - 1] is string last && last.StartsWith("{");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
foreach (var candidate in pointList)
|
|
338
|
+
{
|
|
339
|
+
var candTerm = PointTerminalParam(candidate);
|
|
340
|
+
var bestTerm = PointTerminalParam(point);
|
|
341
|
+
if (candTerm != bestTerm)
|
|
342
|
+
{
|
|
343
|
+
if (candTerm)
|
|
344
|
+
{
|
|
345
|
+
point = candidate;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else if (PointPartsLen(candidate) < PointPartsLen(point))
|
|
349
|
+
{
|
|
350
|
+
point = candidate;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
320
354
|
|
|
321
355
|
// Get required params.
|
|
322
356
|
var paramsPath = StructUtils.GetPath(point, StructUtils.Jt("args", "params"));
|
|
@@ -13,30 +13,48 @@ public static partial class SdkUtility
|
|
|
13
13
|
{
|
|
14
14
|
private static readonly HttpClient DefaultHttpClient = new();
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
16
|
+
// Non-following twin of DefaultHttpClient. The station feature's
|
|
17
|
+
// middleware sets `redirect: manual` on the fetch definition under a
|
|
18
|
+
// hosts egress policy: a 3xx must come back as a response like any
|
|
19
|
+
// other, because an automatic follow would carry injected credentials
|
|
20
|
+
// to a Location no policy approved (the ts donor's fetch honours the
|
|
21
|
+
// same key natively; redirect policy is per-handler in .NET, hence a
|
|
22
|
+
// second client).
|
|
23
|
+
private static readonly HttpClient ManualRedirectHttpClient = new(
|
|
24
|
+
new HttpClientHandler
|
|
25
|
+
{
|
|
26
|
+
AllowAutoRedirect = false,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Proxy-routed clients, cached per proxy URL and redirect policy (see
|
|
30
|
+
// the proxy feature's fetchdef annotation).
|
|
18
31
|
private static readonly Dictionary<string, HttpClient> ProxyClients = new();
|
|
19
32
|
private static readonly object ProxyClientsLock = new();
|
|
20
33
|
|
|
21
34
|
private static HttpClient ClientFor(Dictionary<string, object?> fetchdef)
|
|
22
35
|
{
|
|
36
|
+
var manual = fetchdef.TryGetValue("redirect", out var rraw) &&
|
|
37
|
+
rraw is string redirect && redirect == "manual";
|
|
38
|
+
|
|
23
39
|
if (fetchdef.TryGetValue("proxy", out var raw) && raw is string proxy && proxy != "")
|
|
24
40
|
{
|
|
25
41
|
lock (ProxyClientsLock)
|
|
26
42
|
{
|
|
27
|
-
|
|
43
|
+
var key = (manual ? "manual|" : "auto|") + proxy;
|
|
44
|
+
if (!ProxyClients.TryGetValue(key, out var client))
|
|
28
45
|
{
|
|
29
46
|
client = new HttpClient(new HttpClientHandler
|
|
30
47
|
{
|
|
31
48
|
Proxy = new System.Net.WebProxy(proxy),
|
|
32
49
|
UseProxy = true,
|
|
50
|
+
AllowAutoRedirect = !manual,
|
|
33
51
|
});
|
|
34
|
-
ProxyClients[
|
|
52
|
+
ProxyClients[key] = client;
|
|
35
53
|
}
|
|
36
54
|
return client;
|
|
37
55
|
}
|
|
38
56
|
}
|
|
39
|
-
return DefaultHttpClient;
|
|
57
|
+
return manual ? ManualRedirectHttpClient : DefaultHttpClient;
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
internal static Dictionary<string, object?> DefaultHttpFetch(
|
|
@@ -248,6 +248,19 @@ public static partial class SdkUtility
|
|
|
248
248
|
featureorder.Add(n);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
+
|
|
252
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
253
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
254
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
255
|
+
// or first, when no test entry exists. Without this the sorted
|
|
256
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
257
|
+
// features, turning its wire-truth events into fiction.
|
|
258
|
+
var si = featureorder.IndexOf("station");
|
|
259
|
+
if (si >= 0)
|
|
260
|
+
{
|
|
261
|
+
featureorder.RemoveAt(si);
|
|
262
|
+
featureorder.Insert(featureorder.IndexOf("test") + 1, "station");
|
|
263
|
+
}
|
|
251
264
|
}
|
|
252
265
|
|
|
253
266
|
var derived = new Dictionary<string, object?>
|
|
@@ -6,6 +6,27 @@ namespace ProjectNameSdk.Util;
|
|
|
6
6
|
|
|
7
7
|
public static partial class SdkUtility
|
|
8
8
|
{
|
|
9
|
+
// How many path segments a point has.
|
|
10
|
+
private static int PartsLen(Dictionary<string, object?>? point)
|
|
11
|
+
{
|
|
12
|
+
return StructUtils.GetProp(point, "parts") is List<object?> parts ? parts.Count : 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
16
|
+
// record's identifier (/boards/{id}); a cross-reference that also returns
|
|
17
|
+
// the entity ends in the relationship's name (/posts/{id}/author). That,
|
|
18
|
+
// then fewest segments, is what tells the entity's own route from a
|
|
19
|
+
// cross-reference. The same rule runs at generation time, in
|
|
20
|
+
// helpers/opShape.ts — both sides must move together.
|
|
21
|
+
private static bool TerminalParam(Dictionary<string, object?>? point)
|
|
22
|
+
{
|
|
23
|
+
if (StructUtils.GetProp(point, "parts") is not List<object?> parts || parts.Count == 0)
|
|
24
|
+
{
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return parts[parts.Count - 1] is string last && last.StartsWith("{");
|
|
28
|
+
}
|
|
29
|
+
|
|
9
30
|
internal static Dictionary<string, object?>? MakePointUtil(Context ctx)
|
|
10
31
|
{
|
|
11
32
|
if (ctx.Out.TryGetValue("point", out var outPoint) && outPoint != null)
|
|
@@ -62,10 +83,10 @@ public static partial class SdkUtility
|
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
Dictionary<string, object?>? point = null;
|
|
86
|
+
var matched = false;
|
|
65
87
|
foreach (var candidate in op.Points)
|
|
66
88
|
{
|
|
67
|
-
|
|
68
|
-
var selectDef = Helpers.ToMapAny(StructUtils.GetProp(point, "select"));
|
|
89
|
+
var selectDef = Helpers.ToMapAny(StructUtils.GetProp(candidate, "select"));
|
|
69
90
|
var found = true;
|
|
70
91
|
|
|
71
92
|
if (selector != null && selectDef != null)
|
|
@@ -98,10 +119,52 @@ public static partial class SdkUtility
|
|
|
98
119
|
|
|
99
120
|
if (found)
|
|
100
121
|
{
|
|
122
|
+
point = candidate;
|
|
123
|
+
matched = true;
|
|
101
124
|
break;
|
|
102
125
|
}
|
|
103
126
|
}
|
|
104
127
|
|
|
128
|
+
// select.exist can list more than the params needed to pick a
|
|
129
|
+
// point, so nothing matches — fall back to the entity's own
|
|
130
|
+
// route rather than the last point.
|
|
131
|
+
if (!matched)
|
|
132
|
+
{
|
|
133
|
+
// A request naming an action reaches here only because that
|
|
134
|
+
// action's own point failed its exist test, so it is
|
|
135
|
+
// unbuildable whatever we pick. Refuse it BEFORE choosing a
|
|
136
|
+
// fallback: the guard below compares the chosen point's
|
|
137
|
+
// $action and would wave the request through whenever the
|
|
138
|
+
// fallback lands on the action point itself.
|
|
139
|
+
var unmatchedAction = reqselector != null
|
|
140
|
+
? StructUtils.GetProp(reqselector, "$action") : null;
|
|
141
|
+
if (unmatchedAction != null)
|
|
142
|
+
{
|
|
143
|
+
throw ctx.MakeError("point_action_invalid",
|
|
144
|
+
"Operation \"" + op.Name +
|
|
145
|
+
"\" action \"" + StructUtils.Stringify(unmatchedAction) +
|
|
146
|
+
"\" is not valid.");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
point = op.Points[0];
|
|
150
|
+
foreach (var candidate in op.Points)
|
|
151
|
+
{
|
|
152
|
+
var candTerm = TerminalParam(candidate);
|
|
153
|
+
var bestTerm = TerminalParam(point);
|
|
154
|
+
if (candTerm != bestTerm)
|
|
155
|
+
{
|
|
156
|
+
if (candTerm)
|
|
157
|
+
{
|
|
158
|
+
point = candidate;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (PartsLen(candidate) < PartsLen(point))
|
|
162
|
+
{
|
|
163
|
+
point = candidate;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
105
168
|
if (reqselector != null)
|
|
106
169
|
{
|
|
107
170
|
var reqAction = StructUtils.GetProp(reqselector, "$action");
|
|
@@ -214,10 +214,40 @@ class TestFeature extends BaseFeature {
|
|
|
214
214
|
final param = ctx.utility.param;
|
|
215
215
|
|
|
216
216
|
final opname = op.name;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
223
|
+
final points = vs.getpath(
|
|
224
|
+
ctx.config, ['entity', _entname(ctx.entity), 'op', opname, 'points']);
|
|
225
|
+
var point = vs.getelem(points, 0);
|
|
226
|
+
if (points is List) {
|
|
227
|
+
int pointPartsLen(dynamic p) {
|
|
228
|
+
final parts = vs.getprop(p, 'parts');
|
|
229
|
+
return parts is List ? parts.length : 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
bool pointTerminalParam(dynamic p) {
|
|
233
|
+
final parts = vs.getprop(p, 'parts');
|
|
234
|
+
if (parts is! List || parts.isEmpty) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
final last = parts[parts.length - 1];
|
|
238
|
+
return last is String && last.startsWith('{');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (final cand in points) {
|
|
242
|
+
if (pointTerminalParam(cand) != pointTerminalParam(point)) {
|
|
243
|
+
if (pointTerminalParam(cand)) {
|
|
244
|
+
point = cand;
|
|
245
|
+
}
|
|
246
|
+
} else if (pointPartsLen(cand) < pointPartsLen(point)) {
|
|
247
|
+
point = cand;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
221
251
|
|
|
222
252
|
final reqd = vs.transform(
|
|
223
253
|
vs.select(vs.getpath(point, ['args', 'params']), {'reqd': true}),
|
|
@@ -46,6 +46,15 @@ Future<dynamic> httpFetch(dynamic fullurl, dynamic fetchdef) async {
|
|
|
46
46
|
|
|
47
47
|
final req = await client.openUrl(method, uri);
|
|
48
48
|
|
|
49
|
+
// Manual redirects: fetchdef['redirect'] == 'manual' surfaces a 3xx as
|
|
50
|
+
// an ordinary response instead of auto-following it — HttpClient would
|
|
51
|
+
// otherwise replay the request, headers included, against whatever host
|
|
52
|
+
// Location names. Set by the station feature's transport middleware
|
|
53
|
+
// under an egress hosts policy (mirrors the ts fetch option).
|
|
54
|
+
if ('manual' == vs.getprop(fetchdef, 'redirect')) {
|
|
55
|
+
req.followRedirects = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
49
58
|
final headers = vs.getprop(fetchdef, 'headers');
|
|
50
59
|
if (headers is Map) {
|
|
51
60
|
headers.forEach((k, v) {
|
|
@@ -47,6 +47,12 @@ dynamic makeOptions(dynamic ctx) {
|
|
|
47
47
|
},
|
|
48
48
|
},
|
|
49
49
|
'utility': {},
|
|
50
|
+
// Feature INSTANCES supplied at construction (the station adopt
|
|
51
|
+
// path): consumed by the constructor's featureAdd loop, so they are
|
|
52
|
+
// class instances, not data - `$ANY` accepts them verbatim. Without
|
|
53
|
+
// this entry the seam is dead: the constructor reads
|
|
54
|
+
// options.extend, but validate rejected the key.
|
|
55
|
+
'extend': '`\$ANY`',
|
|
50
56
|
'system': {},
|
|
51
57
|
'test': {
|
|
52
58
|
'active': false,
|
|
@@ -177,6 +183,17 @@ dynamic makeOptions(dynamic ctx) {
|
|
|
177
183
|
} else {
|
|
178
184
|
featureorder.addAll(names);
|
|
179
185
|
}
|
|
186
|
+
// Station special case, mirroring test's: its transport wrap must
|
|
187
|
+
// sit immediately outside the base transport (inside retry/cache/
|
|
188
|
+
// netsim), so map-form activation hoists it to just after test -
|
|
189
|
+
// or first, when no test entry exists. Without this the sorted
|
|
190
|
+
// default would init station last and wrap OUTSIDE the recording
|
|
191
|
+
// features, turning its wire-truth events into fiction.
|
|
192
|
+
if (featureorder.contains('station')) {
|
|
193
|
+
featureorder.remove('station');
|
|
194
|
+
final ti = featureorder.indexOf('test');
|
|
195
|
+
featureorder.insert(-1 == ti ? 0 : ti + 1, 'station');
|
|
196
|
+
}
|
|
180
197
|
}
|
|
181
198
|
|
|
182
199
|
opts['__derived__'] = {
|
|
@@ -34,9 +34,10 @@ dynamic makePoint(dynamic ctx) {
|
|
|
34
34
|
final selector = 'data' == op.input ? ctx.data : ctx.match;
|
|
35
35
|
|
|
36
36
|
dynamic point;
|
|
37
|
+
var matched = false;
|
|
37
38
|
for (var i = 0; i < op.points.length; i++) {
|
|
38
|
-
|
|
39
|
-
final select =
|
|
39
|
+
final cand = op.points[i];
|
|
40
|
+
final select = cand.select;
|
|
40
41
|
var found = true;
|
|
41
42
|
|
|
42
43
|
final exist = vs.getprop(select, 'exist');
|
|
@@ -60,10 +61,55 @@ dynamic makePoint(dynamic ctx) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
if (found) {
|
|
64
|
+
point = cand;
|
|
65
|
+
matched = true;
|
|
63
66
|
break;
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
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
|
+
if (!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 compares
|
|
77
|
+
// the chosen point's $action and would wave the request through
|
|
78
|
+
// whenever the fallback lands on the action point itself.
|
|
79
|
+
if (null != vs.getprop(reqselector, r'$action')) {
|
|
80
|
+
return ctx.error(
|
|
81
|
+
'point_action_invalid',
|
|
82
|
+
'Operation "' +
|
|
83
|
+
op.name.toString() +
|
|
84
|
+
'" action "' +
|
|
85
|
+
vs.getprop(reqselector, r'$action').toString() +
|
|
86
|
+
'" is not valid.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// A terminal parameter marks a record route (/boards/{id}); a
|
|
90
|
+
// cross-reference ends in the relationship's name
|
|
91
|
+
// (/posts/{id}/author). Failing that, the shallower path wins.
|
|
92
|
+
bool terminalParam(dynamic p) {
|
|
93
|
+
final parts = p.parts;
|
|
94
|
+
if (parts.isEmpty) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
final last = parts[parts.length - 1];
|
|
98
|
+
return last is String && last.startsWith('{');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
point = op.points[0];
|
|
102
|
+
for (final cand in op.points) {
|
|
103
|
+
if (terminalParam(cand) != terminalParam(point)) {
|
|
104
|
+
if (terminalParam(cand)) {
|
|
105
|
+
point = cand;
|
|
106
|
+
}
|
|
107
|
+
} else if (cand.parts.length < point.parts.length) {
|
|
108
|
+
point = cand;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
67
113
|
if (null != vs.getprop(reqselector, r'$action') &&
|
|
68
114
|
null != point &&
|
|
69
115
|
vs.getprop(reqselector, r'$action') !=
|
|
@@ -261,6 +261,12 @@ defmodule ProjectName.Utility do
|
|
|
261
261
|
"entity" => %{"`$CHILD`" => %{"`$OPEN`" => true, "active" => false, "alias" => %{}}},
|
|
262
262
|
"feature" => %{"`$CHILD`" => %{"`$OPEN`" => true, "active" => false}},
|
|
263
263
|
"utility" => %{},
|
|
264
|
+
# Feature INSTANCES supplied at construction (the station adopt
|
|
265
|
+
# path): consumed by the constructor's extend loop, so they are
|
|
266
|
+
# struct feature nodes, not data - `$ANY` accepts them verbatim.
|
|
267
|
+
# Without this entry the seam is dead: the constructor reads
|
|
268
|
+
# options.extend, but validate rejected the key.
|
|
269
|
+
"extend" => "`$ANY`",
|
|
264
270
|
"system" => %{},
|
|
265
271
|
"test" => %{"active" => false, "entity" => %{"`$OPEN`" => true}},
|
|
266
272
|
"clean" => %{"keys" => "key,token,id"},
|
|
@@ -359,8 +365,29 @@ defmodule ProjectName.Utility do
|
|
|
359
365
|
fmap = if S.ismap(fmap), do: fmap, else: S.jm([])
|
|
360
366
|
names = S.keysof(fmap)
|
|
361
367
|
|
|
362
|
-
|
|
363
|
-
|
|
368
|
+
names =
|
|
369
|
+
if Enum.member?(names, "test") do
|
|
370
|
+
["test" | Enum.reject(names, &(&1 == "test"))]
|
|
371
|
+
else
|
|
372
|
+
names
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Station special case, mirroring test's: its transport wrap must
|
|
376
|
+
# sit immediately outside the base transport (inside retry/cache/
|
|
377
|
+
# netsim), so map-form activation hoists it to just after test -
|
|
378
|
+
# or first, when no test entry exists. Without this the sorted
|
|
379
|
+
# default would init station last and wrap OUTSIDE the recording
|
|
380
|
+
# features, turning its wire-truth events into fiction.
|
|
381
|
+
if Enum.member?(names, "station") do
|
|
382
|
+
rest = Enum.reject(names, &(&1 == "station"))
|
|
383
|
+
|
|
384
|
+
at =
|
|
385
|
+
case Enum.find_index(rest, &(&1 == "test")) do
|
|
386
|
+
nil -> 0
|
|
387
|
+
ti -> ti + 1
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
List.insert_at(rest, at, "station")
|
|
364
391
|
else
|
|
365
392
|
names
|
|
366
393
|
end
|
|
@@ -465,21 +492,81 @@ defmodule ProjectName.Utility do
|
|
|
465
492
|
|
|
466
493
|
if found, do: {:halt, point}, else: {:cont, point}
|
|
467
494
|
end)
|
|
468
|
-
|> elem(1)
|
|
469
495
|
|
|
470
|
-
|
|
471
|
-
if reqselector != nil do
|
|
472
|
-
req_action = S.getprop(reqselector, "$action")
|
|
496
|
+
matched? = match?({:halt, _}, point)
|
|
473
497
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
498
|
+
point =
|
|
499
|
+
if matched? do
|
|
500
|
+
elem(point, 1)
|
|
501
|
+
else
|
|
502
|
+
# select.exist can list more than the params needed to pick a
|
|
503
|
+
# point, so nothing matched. Fall back to the entity's own
|
|
504
|
+
# route: a terminal parameter marks a record route
|
|
505
|
+
# (/boards/{id}) where a cross-reference ends in the
|
|
506
|
+
# relationship's name (/posts/{id}/author), and failing that
|
|
507
|
+
# the shallower path wins. The same rule runs at generation
|
|
508
|
+
# time, in helpers/opShape.ts — both sides must move together.
|
|
509
|
+
parts_len = fn p ->
|
|
510
|
+
parts = S.getprop(p, "parts")
|
|
511
|
+
if S.islist(parts), do: S.size(parts), else: 0
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
terminal_param? = fn p ->
|
|
515
|
+
parts = S.getprop(p, "parts")
|
|
477
516
|
|
|
478
|
-
if
|
|
479
|
-
|
|
480
|
-
|
|
517
|
+
if S.islist(parts) and S.size(parts) > 0 do
|
|
518
|
+
last = S.getelem(parts, S.size(parts) - 1)
|
|
519
|
+
is_binary(last) and String.starts_with?(last, "{")
|
|
520
|
+
else
|
|
521
|
+
false
|
|
481
522
|
end
|
|
482
523
|
end
|
|
524
|
+
|
|
525
|
+
Enum.reduce(0..(npoints - 1), S.getelem(points, 0), fn i, best ->
|
|
526
|
+
cand = S.getelem(points, i)
|
|
527
|
+
ct = terminal_param?.(cand)
|
|
528
|
+
bt = terminal_param?.(best)
|
|
529
|
+
|
|
530
|
+
cond do
|
|
531
|
+
ct != bt -> if ct, do: cand, else: best
|
|
532
|
+
parts_len.(cand) < parts_len.(best) -> cand
|
|
533
|
+
true -> best
|
|
534
|
+
end
|
|
535
|
+
end)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
unmatched_action =
|
|
539
|
+
if not matched? and reqselector != nil,
|
|
540
|
+
do: S.getprop(reqselector, "$action"),
|
|
541
|
+
else: nil
|
|
542
|
+
|
|
543
|
+
err =
|
|
544
|
+
cond do
|
|
545
|
+
# A request naming an action reaches the fallback only because
|
|
546
|
+
# that action's own point failed its exist test, so it is
|
|
547
|
+
# unbuildable whatever we pick. Refuse it BEFORE the guard
|
|
548
|
+
# below, which compares the chosen point's $action and would
|
|
549
|
+
# wave the request through whenever the fallback lands on the
|
|
550
|
+
# action point itself.
|
|
551
|
+
unmatched_action != nil ->
|
|
552
|
+
Context.make_error(ctx, "point_action_invalid",
|
|
553
|
+
"Operation \"" <> opname <> "\" action \"" <> S.stringify(unmatched_action) <> "\" is not valid.")
|
|
554
|
+
|
|
555
|
+
reqselector != nil ->
|
|
556
|
+
req_action = S.getprop(reqselector, "$action")
|
|
557
|
+
|
|
558
|
+
if req_action != nil and point != nil do
|
|
559
|
+
point_select = H.to_map(S.getprop(point, "select"))
|
|
560
|
+
point_action = S.getprop(point_select, "$action")
|
|
561
|
+
|
|
562
|
+
if req_action != point_action do
|
|
563
|
+
Context.make_error(ctx, "point_action_invalid",
|
|
564
|
+
"Operation \"" <> opname <> "\" action \"" <> S.stringify(req_action) <> "\" is not valid.")
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
true ->
|
|
569
|
+
nil
|
|
483
570
|
end
|
|
484
571
|
|
|
485
572
|
if err != nil do
|
|
@@ -1458,7 +1545,16 @@ defmodule ProjectName.Utility do
|
|
|
1458
1545
|
{url, hlist}
|
|
1459
1546
|
end
|
|
1460
1547
|
|
|
1461
|
-
|
|
1548
|
+
# A `redirect: "manual"` annotation (set by the station feature when a
|
|
1549
|
+
# hosts egress policy is active - the same in-band channel go and rust
|
|
1550
|
+
# use) disables :httpc's automatic redirect following, so a 3xx rides
|
|
1551
|
+
# back as a normal response: a Location pointing off an egress
|
|
1552
|
+
# allowlist must never pull an automatic credentialed follow-up
|
|
1553
|
+
# request.
|
|
1554
|
+
http_opts =
|
|
1555
|
+
if S.getprop(fetchdef, "redirect") == "manual", do: [autoredirect: false], else: []
|
|
1556
|
+
|
|
1557
|
+
case :httpc.request(method, request, http_opts, body_format: :binary) do
|
|
1462
1558
|
{:ok, {{_v, status, _reason}, resp_headers, resp_body}} ->
|
|
1463
1559
|
rh =
|
|
1464
1560
|
Enum.reduce(resp_headers, S.jm([]), fn {k, v}, acc ->
|
|
@@ -50,6 +50,25 @@ defmodule ProjectName.FeatureTest do
|
|
|
50
50
|
assert has?("test")
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# === station ===
|
|
54
|
+
|
|
55
|
+
# The station adapter (installed by @voxgig/sdkgen-station) with NO open
|
|
56
|
+
# station must be an inert no-op that emits nothing and fails nothing
|
|
57
|
+
# (station design 3.1). Driven through the generated factory - never a
|
|
58
|
+
# compile-time module reference - so this file stays green both with and
|
|
59
|
+
# without the feature installed (absent, the harness simply adds no
|
|
60
|
+
# feature). Bound behaviour (registration, injection, wire events) is
|
|
61
|
+
# exercised by the station library's own suite and the consumer-side
|
|
62
|
+
# station validation, not here.
|
|
63
|
+
test "station adapter with no open station is an inert no-op" do
|
|
64
|
+
h = harness([fspec("station")])
|
|
65
|
+
res = FH.op(h, %{})
|
|
66
|
+
assert res.ok == true
|
|
67
|
+
|
|
68
|
+
# Inert: the transport is neither wrapped nor marked.
|
|
69
|
+
assert S.getprop(h.utility, "__station_wrap__") == nil
|
|
70
|
+
end
|
|
71
|
+
|
|
53
72
|
# === netsim ===
|
|
54
73
|
|
|
55
74
|
test "netsim fixed latency then delegate" do
|