@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
|
@@ -1,4 +1,50 @@
|
|
|
1
1
|
|
|
2
|
+
// The entity's OWN route among an op's points, as opposed to a
|
|
3
|
+
// CROSS-REFERENCE: another resource's route that happens to return this
|
|
4
|
+
// entity (`/notifications/{id}/board` for a board). Both are plain GETs
|
|
5
|
+
// with no `$action`, so the path itself is all there is to go on.
|
|
6
|
+
//
|
|
7
|
+
// Two signals, in order:
|
|
8
|
+
//
|
|
9
|
+
// 1. A record route ends in the record's identifier (`/boards/{id}`,
|
|
10
|
+
// `/accounts/{account_id}/users/{id}`); a cross-reference ends in the
|
|
11
|
+
// relationship's name (`/posts/{id}/author`). A terminal parameter is
|
|
12
|
+
// therefore the stronger signal, and unlike depth it survives an entity
|
|
13
|
+
// nested more deeply than the route pointing at it.
|
|
14
|
+
// 2. Failing that, fewest path segments — the shallower route is the one
|
|
15
|
+
// the entity is named for.
|
|
16
|
+
//
|
|
17
|
+
// Ties keep the earlier point, so the sorted-key order of the model decides
|
|
18
|
+
// and the output stays byte-stable.
|
|
19
|
+
//
|
|
20
|
+
// The same rule runs at GENERATION time, in `ownPoint` in
|
|
21
|
+
// helpers/opShape.ts. It cannot be shared with this file — a template ships
|
|
22
|
+
// standalone, outside that package — so it is written twice on purpose, and
|
|
23
|
+
// both sides must move together.
|
|
24
|
+
function terminalParam(point) {
|
|
25
|
+
const parts = point.parts
|
|
26
|
+
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
27
|
+
return 'string' === typeof last && 0 === last.indexOf('{')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
function ownPoint(points) {
|
|
32
|
+
let best = points[0]
|
|
33
|
+
|
|
34
|
+
for (const cand of points) {
|
|
35
|
+
const candterm = terminalParam(cand)
|
|
36
|
+
const bestterm = terminalParam(best)
|
|
37
|
+
|
|
38
|
+
if (candterm !== bestterm ? candterm :
|
|
39
|
+
cand.parts.length < best.parts.length) {
|
|
40
|
+
best = cand
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return best
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
2
48
|
function makePoint(ctx) {
|
|
3
49
|
if (ctx.out.point) {
|
|
4
50
|
return ctx.point = ctx.out.point
|
|
@@ -28,9 +74,10 @@ function makePoint(ctx) {
|
|
|
28
74
|
const selector = getprop(ctx, op.input)
|
|
29
75
|
|
|
30
76
|
let point
|
|
77
|
+
let matched = false
|
|
31
78
|
for (let i = 0; i < op.points.length; i++) {
|
|
32
|
-
|
|
33
|
-
const select =
|
|
79
|
+
const cand = op.points[i]
|
|
80
|
+
const select = cand.select
|
|
34
81
|
let found = true
|
|
35
82
|
|
|
36
83
|
if (selector && select.exist) {
|
|
@@ -53,10 +100,33 @@ function makePoint(ctx) {
|
|
|
53
100
|
}
|
|
54
101
|
|
|
55
102
|
if (found) {
|
|
103
|
+
point = cand
|
|
104
|
+
matched = true
|
|
56
105
|
break
|
|
57
106
|
}
|
|
58
107
|
}
|
|
59
108
|
|
|
109
|
+
// select.exist can list more than the params needed to pick a point (for
|
|
110
|
+
// /boards/{id} it is Trello's 17 optional query-includes), so a plain
|
|
111
|
+
// {id} call matches NOTHING. Fall back to the entity's OWN route rather
|
|
112
|
+
// than whichever point came last.
|
|
113
|
+
if (!matched) {
|
|
114
|
+
// A request naming an action reaches here only because that action's
|
|
115
|
+
// own point failed its exist test, so the request is unbuildable
|
|
116
|
+
// whatever we pick. Refuse it BEFORE choosing a fallback: the guard
|
|
117
|
+
// below compares the chosen point's $action, and would wave the
|
|
118
|
+
// request through whenever the action point is itself the one the
|
|
119
|
+
// fallback selects (a short action route folded into an entity whose
|
|
120
|
+
// canonical route is longer). An unbuildable action request is an
|
|
121
|
+
// error, never a silent request to some other endpoint.
|
|
122
|
+
if (null != reqselector.$action) {
|
|
123
|
+
return ctx.error('point_action_invalid', 'Operation "' + op.name +
|
|
124
|
+
'" action "' + reqselector.$action + '" is not valid.')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
point = ownPoint(op.points)
|
|
128
|
+
}
|
|
129
|
+
|
|
60
130
|
if (
|
|
61
131
|
null != reqselector.$action &&
|
|
62
132
|
null != point &&
|
|
@@ -262,12 +262,41 @@ class TestFeature : BaseFeature("test", "0.0.1", true) {
|
|
|
262
262
|
private fun buildArgs(ctx: Context, op: Operation, args: MutableMap<String, Any?>?): Any? {
|
|
263
263
|
val opname = op.name
|
|
264
264
|
|
|
265
|
-
//
|
|
265
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
266
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
267
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
268
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
269
|
+
// failing that the shallower path wins.
|
|
266
270
|
val points = Struct.getpath(
|
|
267
271
|
ctx.config,
|
|
268
272
|
listOf("entity", if (ctx.entity == null) "" else ctx.entity!!.name, "op", opname, "points"),
|
|
269
273
|
)
|
|
270
|
-
|
|
274
|
+
var point = Struct.getelem(points, 0)
|
|
275
|
+
if (points is List<*>) {
|
|
276
|
+
fun pointPartsLen(p: Any?): Int {
|
|
277
|
+
val parts = Struct.getprop(p, "parts")
|
|
278
|
+
return if (parts is List<*>) parts.size else 0
|
|
279
|
+
}
|
|
280
|
+
fun pointTerminalParam(p: Any?): Boolean {
|
|
281
|
+
val parts = Struct.getprop(p, "parts")
|
|
282
|
+
if (parts !is List<*> || parts.isEmpty()) {
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
val last = parts[parts.size - 1]
|
|
286
|
+
return last is String && last.startsWith("{")
|
|
287
|
+
}
|
|
288
|
+
for (cand in points) {
|
|
289
|
+
val candTerm = pointTerminalParam(cand)
|
|
290
|
+
val bestTerm = pointTerminalParam(point)
|
|
291
|
+
if (candTerm != bestTerm) {
|
|
292
|
+
if (candTerm) {
|
|
293
|
+
point = cand
|
|
294
|
+
}
|
|
295
|
+
} else if (pointPartsLen(cand) < pointPartsLen(point)) {
|
|
296
|
+
point = cand
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
271
300
|
|
|
272
301
|
// Get required params.
|
|
273
302
|
val paramsPath = Struct.getpath(point, listOf("args", "params"))
|
|
@@ -50,9 +50,18 @@ fun defaultHttpFetch(fullurl: String, fetchdef: MutableMap<String, Any?>): Mutab
|
|
|
50
50
|
reqb.setHeader("User-Agent", "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)")
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Honour a redirect annotation on the fetch definition (set by the
|
|
54
|
+
// station feature's middleware under a hosts policy): "manual" returns
|
|
55
|
+
// a 3xx like any other response instead of following it — the ts
|
|
56
|
+
// donor's fetch honours the same key natively, and an automatic follow
|
|
57
|
+
// would carry injected credentials to a host no policy approved.
|
|
58
|
+
val redirectPolicy =
|
|
59
|
+
if ("manual" == fetchdef["redirect"]) HttpClient.Redirect.NEVER
|
|
60
|
+
else HttpClient.Redirect.NORMAL
|
|
61
|
+
|
|
53
62
|
// Honour a proxy annotation on the fetch definition (set by the proxy
|
|
54
63
|
// feature): route the request through a proxied HttpClient.
|
|
55
|
-
val clientb = HttpClient.newBuilder().followRedirects(
|
|
64
|
+
val clientb = HttpClient.newBuilder().followRedirects(redirectPolicy)
|
|
56
65
|
val proxy = fetchdef["proxy"]
|
|
57
66
|
if (proxy is String && "" != proxy) {
|
|
58
67
|
try {
|
|
@@ -141,6 +141,17 @@ fun makeOptions(ctx: Context): MutableMap<String, Any?> {
|
|
|
141
141
|
} else {
|
|
142
142
|
featureorder.addAll(names)
|
|
143
143
|
}
|
|
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
|
+
val si = featureorder.indexOf("station")
|
|
151
|
+
if (0 <= si) {
|
|
152
|
+
featureorder.removeAt(si)
|
|
153
|
+
featureorder.add(featureorder.indexOf("test") + 1, "station")
|
|
154
|
+
}
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
val derived = linkedMapOf<String, Any?>()
|
|
@@ -4,6 +4,27 @@ import KOTLINPACKAGE.core.Context
|
|
|
4
4
|
import KOTLINPACKAGE.core.Helpers
|
|
5
5
|
import KOTLINPACKAGE.utility.struct.Struct
|
|
6
6
|
|
|
7
|
+
// How many path segments a point has.
|
|
8
|
+
private fun partsLen(point: Map<String, Any?>?): Int {
|
|
9
|
+
val parts = Struct.getprop(point, "parts")
|
|
10
|
+
return if (parts is List<*>) parts.size else 0
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
14
|
+
// record's identifier (/boards/{id}); a cross-reference that also returns the
|
|
15
|
+
// entity ends in the relationship's name (/posts/{id}/author). That, then
|
|
16
|
+
// fewest segments, is what tells the entity's own route from a
|
|
17
|
+
// cross-reference. The same rule runs at generation time, in
|
|
18
|
+
// helpers/opShape.ts — both sides must move together.
|
|
19
|
+
private fun terminalParam(point: Map<String, Any?>?): Boolean {
|
|
20
|
+
val parts = Struct.getprop(point, "parts")
|
|
21
|
+
if (parts !is List<*> || parts.isEmpty()) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
val last = parts[parts.size - 1]
|
|
25
|
+
return last is String && last.startsWith("{")
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
@Suppress("UNCHECKED_CAST")
|
|
8
29
|
fun makePoint(ctx: Context): Map<String, Any?> {
|
|
9
30
|
val outPoint = ctx.out["point"]
|
|
@@ -55,9 +76,10 @@ fun makePoint(ctx: Context): Map<String, Any?> {
|
|
|
55
76
|
}
|
|
56
77
|
|
|
57
78
|
var point: MutableMap<String, Any?>? = null
|
|
79
|
+
var matched = false
|
|
58
80
|
for (i in op.points.indices) {
|
|
59
|
-
|
|
60
|
-
val selectDef = Helpers.toMapAny(Struct.getprop(
|
|
81
|
+
val cand = op.points[i]
|
|
82
|
+
val selectDef = Helpers.toMapAny(Struct.getprop(cand, "select"))
|
|
61
83
|
var found = true
|
|
62
84
|
|
|
63
85
|
if (selectDef != null) {
|
|
@@ -84,10 +106,44 @@ fun makePoint(ctx: Context): Map<String, Any?> {
|
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
if (found) {
|
|
109
|
+
point = cand
|
|
110
|
+
matched = true
|
|
87
111
|
break
|
|
88
112
|
}
|
|
89
113
|
}
|
|
90
114
|
|
|
115
|
+
// select.exist can list more than the params needed to pick a point, so
|
|
116
|
+
// nothing matches — fall back to the entity's own route rather than
|
|
117
|
+
// whichever point came last.
|
|
118
|
+
if (!matched) {
|
|
119
|
+
// A request naming an action reaches here only because that action's
|
|
120
|
+
// own point failed its exist test, so it is unbuildable whatever we
|
|
121
|
+
// pick. Refuse it BEFORE choosing a fallback: the guard below compares
|
|
122
|
+
// the chosen point's $action and would wave the request through
|
|
123
|
+
// whenever the fallback lands on the action point itself.
|
|
124
|
+
val unmatchedAction = Struct.getprop(reqselector, "\$action", null)
|
|
125
|
+
if (unmatchedAction != null) {
|
|
126
|
+
throw ctx.makeError(
|
|
127
|
+
"point_action_invalid",
|
|
128
|
+
"Operation \"" + op.name +
|
|
129
|
+
"\" action \"" + Struct.stringify(unmatchedAction) + "\" is not valid.",
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
point = op.points[0]
|
|
134
|
+
for (cand in op.points) {
|
|
135
|
+
val candTerm = terminalParam(cand)
|
|
136
|
+
val bestTerm = terminalParam(point)
|
|
137
|
+
if (candTerm != bestTerm) {
|
|
138
|
+
if (candTerm) {
|
|
139
|
+
point = cand
|
|
140
|
+
}
|
|
141
|
+
} else if (partsLen(cand) < partsLen(point)) {
|
|
142
|
+
point = cand
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
91
147
|
val reqAction = Struct.getprop(reqselector, "\$action", null)
|
|
92
148
|
if (reqAction != null && point != null) {
|
|
93
149
|
val pointSelect = Helpers.toMapAny(Struct.getprop(point, "select"))
|
|
@@ -597,6 +597,20 @@ def makeResult (ctx : Value) : SIO Value := do
|
|
|
597
597
|
|
|
598
598
|
/-- Select the endpoint for this operation: the single point, else the first
|
|
599
599
|
whose `select.exist` keys are all present and whose `$action` agrees. -/
|
|
600
|
+
/-- How many path segments a point has, and whether its path ends in a
|
|
601
|
+
parameter. A record route ends in the record's identifier (/boards/{id});
|
|
602
|
+
a cross-reference that also returns the entity ends in the relationship's
|
|
603
|
+
name (/posts/{id}/author). That, then fewest segments, is what tells the
|
|
604
|
+
entity's own route from a cross-reference. The same rule runs at
|
|
605
|
+
generation time, in helpers/opShape.ts — both sides must move together. -/
|
|
606
|
+
def pointShape (pt : Value) : SIO (Nat × Bool) := do
|
|
607
|
+
match (← gp pt "parts") with
|
|
608
|
+
| .list i =>
|
|
609
|
+
let items ← listItems i
|
|
610
|
+
if items.size == 0 then pure (0, false)
|
|
611
|
+
else pure (items.size, (vs items[items.size - 1]!).startsWith "{")
|
|
612
|
+
| _ => pure (0, false)
|
|
613
|
+
|
|
600
614
|
def makePoint (ctx : Value) : SIO Value := do
|
|
601
615
|
let op ← gp ctx "op"
|
|
602
616
|
let matchV ← gp ctx "reqmatch"
|
|
@@ -622,6 +636,20 @@ def makePoint (ctx : Value) : SIO Value := do
|
|
|
622
636
|
| _ => pure ()
|
|
623
637
|
if ok && ((← gp sel "$action") == (← gp matchV "$action")) then
|
|
624
638
|
chosen := pt
|
|
639
|
+
-- select.exist can list more than the params needed to pick a point (for
|
|
640
|
+
-- /boards/{id} it is Trello's 17 optional query-includes), so a plain
|
|
641
|
+
-- {id} call matches NOTHING and this loop chose no point at all. Fall
|
|
642
|
+
-- back to the entity's own route rather than sending nowhere.
|
|
643
|
+
if isNov chosen then
|
|
644
|
+
let mut best := pts[0]!
|
|
645
|
+
for cand in pts do
|
|
646
|
+
let (candLen, candTerm) ← pointShape cand
|
|
647
|
+
let (bestLen, bestTerm) ← pointShape best
|
|
648
|
+
if candTerm && !bestTerm then
|
|
649
|
+
best := cand
|
|
650
|
+
else if candTerm == bestTerm && candLen < bestLen then
|
|
651
|
+
best := cand
|
|
652
|
+
chosen := best
|
|
625
653
|
if !(isNov chosen) then sp ctx "point" chosen
|
|
626
654
|
pure chosen
|
|
627
655
|
|
|
@@ -316,8 +316,19 @@ def main : IO UInt32 := do
|
|
|
316
316
|
runset "makeResult" (← getSpec primary #["makeResult", "basic"]) fun entry => do
|
|
317
317
|
pure ((← SdkUtility.makeResult (← entryCtx entry opts config)), none)
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
-- makePoint is NOT driven here, and this is deliberate.
|
|
320
|
+
--
|
|
321
|
+
-- The section used to be empty, so running it asserted nothing. It now
|
|
322
|
+
-- carries seven cases, and every one supplies its own `options` (for
|
|
323
|
+
-- allow.op) and `config` (for the operation's points) — but `entryCtx`
|
|
324
|
+
-- OVERWRITES both with the SDK's, so the cases would not run as authored.
|
|
325
|
+
-- Wiring it up needs entryCtx to prefer a fixture-supplied options/config,
|
|
326
|
+
-- which changes every section here and could not be compiled or run when
|
|
327
|
+
-- this change was made.
|
|
328
|
+
--
|
|
329
|
+
-- Left out rather than left in: an entry that runs with the wrong context
|
|
330
|
+
-- is worse than one that does not run, and no coverage is lost — this
|
|
331
|
+
-- section asserted nothing before either.
|
|
321
332
|
|
|
322
333
|
runset "makeFetchDef" (← getSpec primary #["makeFetchDef", "basic"]) fun entry => do
|
|
323
334
|
pure ((← SdkUtility.makeFetchDef (← entryCtx entry opts config)), none)
|
|
@@ -82,6 +82,12 @@ local function make_options_util(ctx)
|
|
|
82
82
|
},
|
|
83
83
|
},
|
|
84
84
|
utility = {},
|
|
85
|
+
-- Feature INSTANCES supplied at construction (the station adopt
|
|
86
|
+
-- path): consumed by the constructor's feature_add loop, so they are
|
|
87
|
+
-- class instances, not data -- `$ANY` accepts them verbatim. Without
|
|
88
|
+
-- this entry the seam is dead: the constructor reads
|
|
89
|
+
-- options.extend, but validate rejected the key.
|
|
90
|
+
extend = "`$ANY`",
|
|
85
91
|
system = {},
|
|
86
92
|
test = {
|
|
87
93
|
active = false,
|
|
@@ -104,6 +110,15 @@ local function make_options_util(ctx)
|
|
|
104
110
|
-- Preserve system.fetch before merge/validate.
|
|
105
111
|
local sys_fetch = vs.getpath(opts, "system.fetch")
|
|
106
112
|
|
|
113
|
+
-- Preserve extend feature INSTANCES before merge/validate, by ORIGINAL
|
|
114
|
+
-- reference (the ts makeOptions keeps the caller's instances too):
|
|
115
|
+
-- merge/validate are data-oriented and would mangle instance tables
|
|
116
|
+
-- carrying functions -- the same reason system.fetch is preserved.
|
|
117
|
+
local extend = nil
|
|
118
|
+
if type(options) == "table" and type(options["extend"]) == "table" then
|
|
119
|
+
extend = options["extend"]
|
|
120
|
+
end
|
|
121
|
+
|
|
107
122
|
-- Clone the config side before merging: `config` is a per-Lua-state
|
|
108
123
|
-- singleton (see config_shared), and merge would otherwise use its nested
|
|
109
124
|
-- tables as merge TARGETS — one instance's options (server, headers, ...)
|
|
@@ -156,6 +171,11 @@ local function make_options_util(ctx)
|
|
|
156
171
|
end
|
|
157
172
|
end
|
|
158
173
|
|
|
174
|
+
-- Restore extend feature instances.
|
|
175
|
+
if extend ~= nil then
|
|
176
|
+
opts["extend"] = extend
|
|
177
|
+
end
|
|
178
|
+
|
|
159
179
|
-- Derived clean config.
|
|
160
180
|
local clean_keys = "key,token,id"
|
|
161
181
|
local ck = vs.getpath(opts, "clean.keys")
|
|
@@ -202,6 +222,30 @@ local function make_options_util(ctx)
|
|
|
202
222
|
else
|
|
203
223
|
featureorder = names
|
|
204
224
|
end
|
|
225
|
+
-- Station special case, mirroring test's: its transport wrap must
|
|
226
|
+
-- sit immediately outside the base transport (inside retry/cache/
|
|
227
|
+
-- netsim), so map-form activation hoists it to just after test -
|
|
228
|
+
-- or first, when no test entry exists. Without this the sorted
|
|
229
|
+
-- default would init station last and wrap OUTSIDE the recording
|
|
230
|
+
-- features, turning its wire-truth events into fiction.
|
|
231
|
+
local si = nil
|
|
232
|
+
for i, n in ipairs(featureorder) do
|
|
233
|
+
if n == "station" then
|
|
234
|
+
si = i
|
|
235
|
+
break
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
if si ~= nil then
|
|
239
|
+
table.remove(featureorder, si)
|
|
240
|
+
local ti = 0
|
|
241
|
+
for i, n in ipairs(featureorder) do
|
|
242
|
+
if n == "test" then
|
|
243
|
+
ti = i
|
|
244
|
+
break
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
table.insert(featureorder, ti + 1, "station")
|
|
248
|
+
end
|
|
205
249
|
end
|
|
206
250
|
|
|
207
251
|
local derived = { clean = {} }
|
|
@@ -3,6 +3,32 @@
|
|
|
3
3
|
local vs = require("utility.struct.struct")
|
|
4
4
|
local helpers = require("core.helpers")
|
|
5
5
|
|
|
6
|
+
-- How many path segments a point has.
|
|
7
|
+
local function parts_len(point)
|
|
8
|
+
local parts = vs.getprop(point, "parts")
|
|
9
|
+
if type(parts) == "table" then
|
|
10
|
+
return #parts
|
|
11
|
+
end
|
|
12
|
+
return 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
-- Does this point's path end in a parameter? A record route ends in the
|
|
17
|
+
-- record's identifier (/boards/{id}); a cross-reference that also returns
|
|
18
|
+
-- the entity ends in the relationship's name (/posts/{id}/author). That,
|
|
19
|
+
-- then fewest segments, is what tells the entity's own route from a
|
|
20
|
+
-- cross-reference. The same rule runs at generation time, in
|
|
21
|
+
-- helpers/opShape.ts — both sides must move together.
|
|
22
|
+
local function terminal_param(point)
|
|
23
|
+
local parts = vs.getprop(point, "parts")
|
|
24
|
+
if type(parts) ~= "table" or #parts == 0 then
|
|
25
|
+
return false
|
|
26
|
+
end
|
|
27
|
+
local last = parts[#parts]
|
|
28
|
+
return type(last) == "string" and last:sub(1, 1) == "{"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
6
32
|
local function make_point_util(ctx)
|
|
7
33
|
if ctx.out["point"] ~= nil then
|
|
8
34
|
local preset = ctx.out["point"]
|
|
@@ -44,9 +70,10 @@ local function make_point_util(ctx)
|
|
|
44
70
|
end
|
|
45
71
|
|
|
46
72
|
local point = nil
|
|
73
|
+
local matched = false
|
|
47
74
|
for i = 1, #op.points do
|
|
48
|
-
|
|
49
|
-
local select_def = helpers.to_map(vs.getprop(
|
|
75
|
+
local cand = op.points[i]
|
|
76
|
+
local select_def = helpers.to_map(vs.getprop(cand, "select"))
|
|
50
77
|
local found = true
|
|
51
78
|
|
|
52
79
|
if selector ~= nil and select_def ~= nil then
|
|
@@ -73,10 +100,46 @@ local function make_point_util(ctx)
|
|
|
73
100
|
end
|
|
74
101
|
|
|
75
102
|
if found then
|
|
103
|
+
point = cand
|
|
104
|
+
matched = true
|
|
76
105
|
break
|
|
77
106
|
end
|
|
78
107
|
end
|
|
79
108
|
|
|
109
|
+
-- select.exist can list more than the params needed to pick a point, so
|
|
110
|
+
-- nothing matches — fall back to the entity's own route rather than
|
|
111
|
+
-- whichever point came last.
|
|
112
|
+
if not matched then
|
|
113
|
+
-- A request naming an action reaches here only because that action's
|
|
114
|
+
-- own point failed its exist test, so it is unbuildable whatever we
|
|
115
|
+
-- pick. Refuse it BEFORE choosing a fallback: the guard below compares
|
|
116
|
+
-- the chosen point's $action and would wave the request through
|
|
117
|
+
-- whenever the fallback lands on the action point itself.
|
|
118
|
+
local unmatched_action = nil
|
|
119
|
+
if reqselector ~= nil then
|
|
120
|
+
unmatched_action = vs.getprop(reqselector, "$action")
|
|
121
|
+
end
|
|
122
|
+
if unmatched_action ~= nil then
|
|
123
|
+
return nil, ctx:make_error("point_action_invalid",
|
|
124
|
+
'Operation "' .. op.name ..
|
|
125
|
+
'" action "' .. vs.stringify(unmatched_action) .. '" is not valid.')
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
point = op.points[1]
|
|
129
|
+
for i = 1, #op.points do
|
|
130
|
+
local cand = op.points[i]
|
|
131
|
+
local cand_term = terminal_param(cand)
|
|
132
|
+
local best_term = terminal_param(point)
|
|
133
|
+
if cand_term ~= best_term then
|
|
134
|
+
if cand_term then
|
|
135
|
+
point = cand
|
|
136
|
+
end
|
|
137
|
+
elseif parts_len(cand) < parts_len(point) then
|
|
138
|
+
point = cand
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
80
143
|
if reqselector ~= nil then
|
|
81
144
|
local req_action = vs.getprop(reqselector, "$action")
|
|
82
145
|
if req_action ~= nil and point ~= nil then
|
|
@@ -636,12 +636,12 @@ let make_point_util (ctx : ctx) : (value * sdk_error option) =
|
|
|
636
636
|
if op.op_input = "data" then ctx.c_reqdata, ctx.c_data
|
|
637
637
|
else ctx.c_reqmatch, ctx.c_match in
|
|
638
638
|
let chosen = ref Noval in
|
|
639
|
+
let matched = ref false in
|
|
639
640
|
let n = List.length points in
|
|
640
641
|
let arr = Array.of_list points in
|
|
641
642
|
let i = ref 0 and stop = ref false in
|
|
642
643
|
while not !stop && !i < n do
|
|
643
644
|
let point = arr.(!i) in
|
|
644
|
-
chosen := point;
|
|
645
645
|
let select_def = to_map (getp point "select") in
|
|
646
646
|
let found = ref true in
|
|
647
647
|
(match select_def with
|
|
@@ -661,8 +661,46 @@ let make_point_util (ctx : ctx) : (value * sdk_error option) =
|
|
|
661
661
|
let select_action = getp select_def "$action" in
|
|
662
662
|
if req_action <> select_action then found := false
|
|
663
663
|
end;
|
|
664
|
-
if !found then
|
|
664
|
+
if !found then begin chosen := point; matched := true; stop := true end
|
|
665
|
+
else incr i
|
|
665
666
|
done;
|
|
667
|
+
(* select.exist can list more than the params needed to pick a point
|
|
668
|
+
(for /boards/{id} it is Trello's 17 optional query-includes), so a
|
|
669
|
+
plain {id} call matches NOTHING. Fall back to the entity's own
|
|
670
|
+
route rather than whichever point came last. *)
|
|
671
|
+
if not !matched then begin
|
|
672
|
+
(* A request naming an action reaches here only because that
|
|
673
|
+
action's own point failed its exist test, so it is unbuildable
|
|
674
|
+
whatever we pick. Refuse it BEFORE choosing a fallback: the guard
|
|
675
|
+
below compares the chosen point's $action and would wave the
|
|
676
|
+
request through whenever the fallback lands on the action point
|
|
677
|
+
itself. *)
|
|
678
|
+
let unmatched_action = getp reqselector "$action" in
|
|
679
|
+
if not (is_noval unmatched_action) then
|
|
680
|
+
raise (Sdk_error_exc (ctx_make_error ctx "point_action_invalid"
|
|
681
|
+
("Operation \"" ^ op.op_name ^ "\" action \"" ^ (stringify unmatched_action) ^ "\" is not valid.")));
|
|
682
|
+
(* A terminal parameter marks a record route (/boards/{id}); a
|
|
683
|
+
cross-reference ends in the relationship's name
|
|
684
|
+
(/posts/{id}/author). Failing that, the shallower path wins. The
|
|
685
|
+
same rule runs at generation time, in helpers/opShape.ts — both
|
|
686
|
+
sides must move together. *)
|
|
687
|
+
let parts_len p =
|
|
688
|
+
match getp p "parts" with List r -> List.length !r | _ -> 0 in
|
|
689
|
+
let terminal_param p =
|
|
690
|
+
match getp p "parts" with
|
|
691
|
+
| List r ->
|
|
692
|
+
(match List.rev !r with
|
|
693
|
+
| last :: _ ->
|
|
694
|
+
let s = vstring last in
|
|
695
|
+
String.length s > 0 && s.[0] = '{'
|
|
696
|
+
| [] -> false)
|
|
697
|
+
| _ -> false in
|
|
698
|
+
chosen := arr.(0);
|
|
699
|
+
Array.iter (fun cand ->
|
|
700
|
+
let ct = terminal_param cand and bt = terminal_param !chosen in
|
|
701
|
+
if ct <> bt then (if ct then chosen := cand)
|
|
702
|
+
else if parts_len cand < parts_len !chosen then chosen := cand) arr
|
|
703
|
+
end;
|
|
666
704
|
let req_action = getp reqselector "$action" in
|
|
667
705
|
if not (is_noval req_action) && not (is_noval !chosen) then begin
|
|
668
706
|
let point_select = to_map (getp !chosen "select") in
|
|
@@ -39,7 +39,16 @@ our $DefaultHttpFetch = sub {
|
|
|
39
39
|
my $opts = { headers => \%hdrs };
|
|
40
40
|
$opts->{content} = "$body" if defined $body && !ref $body;
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
# fetchdef.redirect 'manual' (the ts fetch vocabulary): return a 3xx
|
|
43
|
+
# as-is instead of following it. HTTP::Tiny follows redirects by
|
|
44
|
+
# default; the station feature's hosts policy sets this so a Location
|
|
45
|
+
# off the allowlist can never pull an automatic credentialed follow-up.
|
|
46
|
+
my %new_args;
|
|
47
|
+
$new_args{max_redirect} = 0
|
|
48
|
+
if defined $fetchdef->{redirect} && !ref $fetchdef->{redirect}
|
|
49
|
+
&& 'manual' eq $fetchdef->{redirect};
|
|
50
|
+
|
|
51
|
+
my $res = eval { HTTP::Tiny->new(%new_args)->request($method, $fullurl, $opts) };
|
|
43
52
|
if (!$res) {
|
|
44
53
|
my $e = defined $@ ? "$@" : 'request failed';
|
|
45
54
|
$e =~ s/\s+\z//;
|
|
@@ -26,8 +26,20 @@ $REGISTRY{make_options} = sub {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
# Feature INSTANCES supplied at construction (the station adopt path):
|
|
30
|
+
# consumed by the constructor's extend loop, so they are blessed class
|
|
31
|
+
# instances, not data. This is the perl form of the ts/rb targets'
|
|
32
|
+
# `extend => $ANY` optspec entry: this port deep-clones its options, and
|
|
33
|
+
# Voxgig::Struct::clone would flatten a blessed feature to a plain
|
|
34
|
+
# unblessed hash (and validate rejects the key outright), so the RAW
|
|
35
|
+
# list is captured here and re-attached after validation - the
|
|
36
|
+
# system.fetch idiom. Without this the seam is dead: the constructor
|
|
37
|
+
# reads options.extend, but clone/validate dropped the instances.
|
|
38
|
+
my $extend_raw = ProjectNameHelpers::gp($options, 'extend');
|
|
39
|
+
|
|
29
40
|
my $opts = Voxgig::Struct::clone($options);
|
|
30
41
|
$opts = {} unless Voxgig::Struct::ismap($opts);
|
|
42
|
+
delete $opts->{extend};
|
|
31
43
|
|
|
32
44
|
# Feature add-order. options.feature may be given as an ordered ARRAY of
|
|
33
45
|
# { name, active, ...opts } entries (the array position IS the order in
|
|
@@ -78,6 +90,12 @@ $REGISTRY{make_options} = sub {
|
|
|
78
90
|
'system' => {},
|
|
79
91
|
'test' => { 'active' => $JF, 'entity' => { '`$OPEN`' => $JT } },
|
|
80
92
|
'clean' => { 'keys' => 'key,token,id' },
|
|
93
|
+
# Server-variable values for a templated base URL (OpenAPI server
|
|
94
|
+
# variables). The embedded config (configDefinition) carries the
|
|
95
|
+
# spec defaults; user values override them. This port does not yet
|
|
96
|
+
# substitute {name} placeholders into base - the entry keeps the
|
|
97
|
+
# config's server block valid under this optspec.
|
|
98
|
+
'server' => { '`$CHILD`' => '' },
|
|
81
99
|
};
|
|
82
100
|
|
|
83
101
|
my $sys_fetch = ProjectNameHelpers::gpath($opts, 'system.fetch');
|
|
@@ -95,6 +113,11 @@ $REGISTRY{make_options} = sub {
|
|
|
95
113
|
$opts->{system}{fetch} = $sys_fetch;
|
|
96
114
|
}
|
|
97
115
|
|
|
116
|
+
# Re-attach the raw extend instances captured above.
|
|
117
|
+
if (Voxgig::Struct::islist($extend_raw)) {
|
|
118
|
+
$opts->{extend} = $extend_raw;
|
|
119
|
+
}
|
|
120
|
+
|
|
98
121
|
my $clean_keys = ProjectNameHelpers::gpath($opts, 'clean.keys');
|
|
99
122
|
$clean_keys = 'key,token,id' unless defined $clean_keys && !ref $clean_keys;
|
|
100
123
|
my @parts;
|
|
@@ -114,6 +137,19 @@ $REGISTRY{make_options} = sub {
|
|
|
114
137
|
@featureorder = (grep { 'test' eq $_ } @names)
|
|
115
138
|
? ('test', grep { 'test' ne $_ } @names)
|
|
116
139
|
: @names;
|
|
140
|
+
|
|
141
|
+
# Station special case, mirroring test's: its transport wrap must
|
|
142
|
+
# sit immediately outside the base transport (inside retry/cache/
|
|
143
|
+
# netsim), so map-form activation hoists it to just after test -
|
|
144
|
+
# or first, when no test entry exists. Without this the sorted
|
|
145
|
+
# default would init station last and wrap OUTSIDE the recording
|
|
146
|
+
# features, turning its wire-truth events into fiction.
|
|
147
|
+
my ($si) = grep { 'station' eq $featureorder[$_] } 0 .. $#featureorder;
|
|
148
|
+
if (defined $si) {
|
|
149
|
+
splice @featureorder, $si, 1;
|
|
150
|
+
my ($ti) = grep { 'test' eq $featureorder[$_] } 0 .. $#featureorder;
|
|
151
|
+
splice @featureorder, (defined $ti ? $ti + 1 : 0), 0, 'station';
|
|
152
|
+
}
|
|
117
153
|
}
|
|
118
154
|
|
|
119
155
|
$opts->{__derived__} = {
|