@voxgig/sdkgen 3.5.0 → 3.6.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/ExternalTarget.js +1 -1
- package/dist/cmp/ExternalTarget.js.map +1 -1
- package/dist/helpers/naming.d.ts +4 -1
- package/dist/helpers/naming.js +119 -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 +61 -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/package.json +32 -8
- 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/Test_cpp.ts +4 -7
- 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/Test_dart.ts +3 -4
- package/project/.sdk/src/cmp/elixir/Package_elixir.ts +1 -1
- package/project/.sdk/src/cmp/elixir/Test_elixir.ts +5 -6
- 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/Test_java.ts +4 -7
- 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/kotlin/Test_kotlin.ts +4 -7
- package/project/.sdk/src/cmp/lean/Test_lean.ts +3 -3
- package/project/.sdk/src/cmp/lua/Package_lua.ts +2 -2
- 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/Package_perl.ts +1 -1
- package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -7
- package/project/.sdk/src/cmp/php/EntityTypes_php.ts +13 -2
- 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/py/Package_py.ts +2 -2
- package/project/.sdk/src/cmp/py/Test_py.ts +5 -7
- package/project/.sdk/src/cmp/py-data/Main_py-data.ts +14 -19
- 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/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/Test_swift.ts +5 -7
- 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/zig/Test_zig.ts +3 -7
- 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 +55 -3
- package/project/.sdk/tm/csharp/feature/TestFeature.cs +36 -2
- 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/MakePointUtility.dart +48 -2
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +70 -10
- package/project/.sdk/tm/go/feature/test_feature.go +34 -2
- 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/MakePoint.java +61 -2
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +22 -3
- 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/MakePoint.kt +58 -2
- package/project/.sdk/tm/lean/src/SdkUtility.lean +28 -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/make_point.pm +55 -2
- package/project/.sdk/tm/php/feature/TestFeature.php +30 -1
- package/project/.sdk/tm/php/utility/MakePoint.php +49 -1
- package/project/.sdk/tm/py/pkg/utility/make_point.py +56 -2
- package/project/.sdk/tm/rb/utility/make_point.rb +42 -2
- 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/Make.swift +51 -2
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +22 -3
- package/project/.sdk/tm/ts/src/utility/MakePointUtility.ts +72 -2
- package/project/.sdk/tm/zig/core/utility.zig +66 -3
- package/project/sdkgen-package.json +1 -2
- package/src/cmp/ExternalTarget.ts +1 -1
- package/src/helpers/naming.ts +129 -5
- package/src/helpers/opShape.ts +85 -0
- package/src/helpers/packageMeta.ts +59 -20
- package/src/sdkgen.ts +68 -20
- package/src/testkit.ts +863 -0
- package/testkit.d.ts +3 -0
- package/testkit.js +12 -0
- package/project/.sdk/model/target/haskell.aontu +0 -72
- package/project/.sdk/src/cmp/haskell/Config_haskell.ts +0 -98
- package/project/.sdk/src/cmp/haskell/Entity_haskell.ts +0 -13
- package/project/.sdk/src/cmp/haskell/Gitignore_haskell.ts +0 -27
- package/project/.sdk/src/cmp/haskell/Main_haskell.ts +0 -123
- package/project/.sdk/src/cmp/haskell/Package_haskell.ts +0 -60
- package/project/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +0 -186
- package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +0 -135
- package/project/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +0 -48
- package/project/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +0 -168
- package/project/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +0 -51
- package/project/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +0 -67
- package/project/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +0 -162
- package/project/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +0 -74
- package/project/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +0 -241
- package/project/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +0 -408
- package/project/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +0 -31
- package/project/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +0 -104
- package/project/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +0 -80
- package/project/.sdk/src/cmp/haskell/Test_haskell.ts +0 -300
- package/project/.sdk/src/cmp/haskell/tsconfig.json +0 -15
- package/project/.sdk/src/cmp/haskell/utility_haskell.ts +0 -166
- package/project/.sdk/tm/haskell/LICENSE +0 -21
- package/project/.sdk/tm/haskell/Makefile +0 -22
- package/project/.sdk/tm/haskell/VERSION +0 -1
- package/project/.sdk/tm/haskell/src/SdkFeatures.hs +0 -1440
- package/project/.sdk/tm/haskell/src/SdkHelpers.hs +0 -337
- package/project/.sdk/tm/haskell/src/SdkJson.hs +0 -111
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +0 -1219
- package/project/.sdk/tm/haskell/src/SdkTypes.hs +0 -195
- package/project/.sdk/tm/haskell/src/VoxgigStruct.hs +0 -2299
- package/project/.sdk/tm/haskell/src/Vregex.hs +0 -237
- package/project/.sdk/tm/haskell/src/feature/README.md +0 -5
- package/project/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
- package/project/.sdk/tm/haskell/test/Harness.hs +0 -281
- package/project/.sdk/tm/haskell/test/Runner.hs +0 -47
- package/project/.sdk/tm/haskell/test/StructCorpus.hs +0 -449
- package/project/.sdk/tm/haskell/test/TCustomUtility.hs +0 -38
- package/project/.sdk/tm/haskell/test/TFeature.hs +0 -794
- package/project/.sdk/tm/haskell/test/TNetsim.hs +0 -50
- package/project/.sdk/tm/haskell/test/TPipeline.hs +0 -334
- package/project/.sdk/tm/haskell/test/TPrimaryUtility.hs +0 -386
- package/project/.sdk/tm/haskell/test/Testutil.hs +0 -59
|
@@ -7,6 +7,33 @@ use crate::core::types::OutVal;
|
|
|
7
7
|
use crate::utility::voxgigstruct as vs;
|
|
8
8
|
use crate::utility::voxgigstruct::Value;
|
|
9
9
|
|
|
10
|
+
// How many path segments a point has.
|
|
11
|
+
fn parts_len(point: &Value) -> usize {
|
|
12
|
+
match getp(point, "parts") {
|
|
13
|
+
Value::List(l) => l.borrow().len(),
|
|
14
|
+
_ => 0,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
19
|
+
// record's identifier (/boards/{id}); a cross-reference that also returns
|
|
20
|
+
// the entity ends in the relationship's name (/posts/{id}/author). That,
|
|
21
|
+
// then fewest segments, is what tells the entity's own route from a
|
|
22
|
+
// cross-reference. The same rule runs at generation time, in
|
|
23
|
+
// helpers/opShape.ts — both sides must move together.
|
|
24
|
+
fn terminal_param(point: &Value) -> bool {
|
|
25
|
+
match getp(point, "parts") {
|
|
26
|
+
Value::List(l) => {
|
|
27
|
+
let parts = l.borrow();
|
|
28
|
+
match parts.last() {
|
|
29
|
+
Some(Value::Str(s)) => s.starts_with('{'),
|
|
30
|
+
_ => false,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
_ => false,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
10
37
|
pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
|
|
11
38
|
match ctx.out_get("point") {
|
|
12
39
|
// A PrePoint feature hook (e.g. rbac) may short-circuit the
|
|
@@ -60,9 +87,10 @@ pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
|
|
|
60
87
|
};
|
|
61
88
|
|
|
62
89
|
let mut point = Value::Noval;
|
|
90
|
+
let mut matched = false;
|
|
63
91
|
for i in 0..plen {
|
|
64
|
-
|
|
65
|
-
let select_def = to_map(&getp(&
|
|
92
|
+
let cand = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
|
|
93
|
+
let select_def = to_map(&getp(&cand, "select"));
|
|
66
94
|
let mut found = true;
|
|
67
95
|
|
|
68
96
|
if !selector.is_noval() && !select_def.is_noval() {
|
|
@@ -89,10 +117,49 @@ pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
|
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
if found {
|
|
120
|
+
point = cand;
|
|
121
|
+
matched = true;
|
|
92
122
|
break;
|
|
93
123
|
}
|
|
94
124
|
}
|
|
95
125
|
|
|
126
|
+
// select.exist can list more than the params needed to pick a point,
|
|
127
|
+
// so nothing matches — fall back to the entity's own route rather
|
|
128
|
+
// than whichever point came last.
|
|
129
|
+
if !matched {
|
|
130
|
+
// A request naming an action reaches here only because that
|
|
131
|
+
// action's own point failed its exist test, so it is unbuildable
|
|
132
|
+
// whatever we pick. Refuse it BEFORE choosing a fallback: the
|
|
133
|
+
// guard below compares the chosen point's $action and would wave
|
|
134
|
+
// the request through whenever the fallback lands on the action
|
|
135
|
+
// point itself.
|
|
136
|
+
let unmatched_action = getp(&reqselector, "$action");
|
|
137
|
+
if !unmatched_action.is_noval() {
|
|
138
|
+
return Err(ctx.make_error(
|
|
139
|
+
"point_action_invalid",
|
|
140
|
+
&format!(
|
|
141
|
+
"Operation \"{}\" action \"{}\" is not valid.",
|
|
142
|
+
op.name,
|
|
143
|
+
vs::stringify(&unmatched_action, None, false)
|
|
144
|
+
),
|
|
145
|
+
));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
point = vs::get_elem(&points, &Value::Num(0.0), Value::Noval);
|
|
149
|
+
for i in 0..plen {
|
|
150
|
+
let cand = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
|
|
151
|
+
let cand_term = terminal_param(&cand);
|
|
152
|
+
let best_term = terminal_param(&point);
|
|
153
|
+
if cand_term != best_term {
|
|
154
|
+
if cand_term {
|
|
155
|
+
point = cand;
|
|
156
|
+
}
|
|
157
|
+
} else if parts_len(&cand) < parts_len(&point) {
|
|
158
|
+
point = cand;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
96
163
|
let req_action = getp(&reqselector, "$action");
|
|
97
164
|
if !req_action.is_noval() && !point.is_noval() {
|
|
98
165
|
let point_select = to_map(&getp(&point, "select"));
|
|
@@ -215,9 +215,44 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
|
|
|
215
215
|
private def buildArgs(ctx: Context, op: Operation, args: JMap[String, Object]): Object = {
|
|
216
216
|
val opname = op.name
|
|
217
217
|
|
|
218
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
219
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
220
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
221
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
222
|
+
// failing that the shallower path wins.
|
|
218
223
|
val points = Struct.getpath(ctx.config,
|
|
219
224
|
java.util.List.of("entity", if (ctx.entity == null) "" else ctx.entity.getName(), "op", opname, "points"))
|
|
220
|
-
|
|
225
|
+
var point = Struct.getelem(points, java.lang.Integer.valueOf(0))
|
|
226
|
+
points match {
|
|
227
|
+
case plist: java.util.List[_] =>
|
|
228
|
+
def pointPartsLen(p: Object): Int =
|
|
229
|
+
Struct.getprop(p, "parts") match {
|
|
230
|
+
case parts: java.util.List[_] => parts.size()
|
|
231
|
+
case _ => 0
|
|
232
|
+
}
|
|
233
|
+
def pointTerminalParam(p: Object): Boolean =
|
|
234
|
+
Struct.getprop(p, "parts") match {
|
|
235
|
+
case parts: java.util.List[_] if parts.size() > 0 =>
|
|
236
|
+
parts.get(parts.size() - 1) match {
|
|
237
|
+
case s: String => s.startsWith("{")
|
|
238
|
+
case _ => false
|
|
239
|
+
}
|
|
240
|
+
case _ => false
|
|
241
|
+
}
|
|
242
|
+
var k = 0
|
|
243
|
+
while (k < plist.size()) {
|
|
244
|
+
val cand = plist.get(k).asInstanceOf[Object]
|
|
245
|
+
val candTerm = pointTerminalParam(cand)
|
|
246
|
+
val bestTerm = pointTerminalParam(point)
|
|
247
|
+
if (candTerm != bestTerm) {
|
|
248
|
+
if (candTerm) point = cand
|
|
249
|
+
} else if (pointPartsLen(cand) < pointPartsLen(point)) {
|
|
250
|
+
point = cand
|
|
251
|
+
}
|
|
252
|
+
k += 1
|
|
253
|
+
}
|
|
254
|
+
case _ =>
|
|
255
|
+
}
|
|
221
256
|
|
|
222
257
|
val paramsPath = Struct.getpath(point, java.util.List.of("args", "params"))
|
|
223
258
|
val reqdParams = Struct.select(paramsPath, Struct.jm("reqd", java.lang.Boolean.TRUE))
|
|
@@ -94,11 +94,12 @@ object MakePoint {
|
|
|
94
94
|
val selector: JMap[String, Object] = if ("data" == op.input) ctx.data else ctx.matchData
|
|
95
95
|
|
|
96
96
|
var point: JMap[String, Object] = null
|
|
97
|
+
var matched = false
|
|
97
98
|
var i = 0
|
|
98
99
|
var break = false
|
|
99
100
|
while (i < op.points.size() && !break) {
|
|
100
|
-
|
|
101
|
-
val selectDef = Helpers.toMapAny(Struct.getprop(
|
|
101
|
+
val cand = op.points.get(i)
|
|
102
|
+
val selectDef = Helpers.toMapAny(Struct.getprop(cand, "select"))
|
|
102
103
|
var found = true
|
|
103
104
|
|
|
104
105
|
if (selector != null && selectDef != null) {
|
|
@@ -122,7 +123,60 @@ object MakePoint {
|
|
|
122
123
|
if (!Objects.equals(reqAction, selectAction)) found = false
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
if (found) break = true else i += 1
|
|
126
|
+
if (found) { point = cand; matched = true; break = true } else i += 1
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// select.exist can list more than the params needed to pick a point
|
|
130
|
+
// (for /boards/{id} it is Trello's 17 optional query-includes), so a
|
|
131
|
+
// plain {id} call matches NOTHING. Fall back to the entity's own route
|
|
132
|
+
// rather than whichever point came last.
|
|
133
|
+
if (!matched) {
|
|
134
|
+
// A request naming an action reaches here only because that action's
|
|
135
|
+
// own point failed its exist test, so it is unbuildable whatever we
|
|
136
|
+
// pick. Refuse it BEFORE choosing a fallback: the guard below
|
|
137
|
+
// compares the chosen point's $action and would wave the request
|
|
138
|
+
// through whenever the fallback lands on the action point itself.
|
|
139
|
+
val unmatchedAction =
|
|
140
|
+
if (reqselector != null) Struct.getprop(reqselector, "$action", null) else null
|
|
141
|
+
if (unmatchedAction != null) {
|
|
142
|
+
throw ctx.makeError("point_action_invalid",
|
|
143
|
+
"Operation \"" + op.name + "\" action \"" +
|
|
144
|
+
Struct.stringify(unmatchedAction) + "\" is not valid.")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// A terminal parameter marks a record route (/boards/{id}); a
|
|
148
|
+
// cross-reference ends in the relationship's name
|
|
149
|
+
// (/posts/{id}/author). Failing that, the shallower path wins. The
|
|
150
|
+
// same rule runs at generation time, in helpers/opShape.ts — both
|
|
151
|
+
// sides must move together.
|
|
152
|
+
def partsLen(p: JMap[String, Object]): Int =
|
|
153
|
+
Struct.getprop(p, "parts") match {
|
|
154
|
+
case parts: JList[_] => parts.size()
|
|
155
|
+
case _ => 0
|
|
156
|
+
}
|
|
157
|
+
def terminalParam(p: JMap[String, Object]): Boolean =
|
|
158
|
+
Struct.getprop(p, "parts") match {
|
|
159
|
+
case parts: JList[_] if parts.size() > 0 =>
|
|
160
|
+
parts.get(parts.size() - 1) match {
|
|
161
|
+
case s: String => s.startsWith("{")
|
|
162
|
+
case _ => false
|
|
163
|
+
}
|
|
164
|
+
case _ => false
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
point = op.points.get(0)
|
|
168
|
+
var j = 0
|
|
169
|
+
while (j < op.points.size()) {
|
|
170
|
+
val cand = op.points.get(j)
|
|
171
|
+
val candTerm = terminalParam(cand)
|
|
172
|
+
val bestTerm = terminalParam(point)
|
|
173
|
+
if (candTerm != bestTerm) {
|
|
174
|
+
if (candTerm) point = cand
|
|
175
|
+
} else if (partsLen(cand) < partsLen(point)) {
|
|
176
|
+
point = cand
|
|
177
|
+
}
|
|
178
|
+
j += 1
|
|
179
|
+
}
|
|
126
180
|
}
|
|
127
181
|
|
|
128
182
|
if (reqselector != null) {
|
|
@@ -38,7 +38,35 @@ private func testBuildArgs(_ ctx: Context, _ op: Operation, _ args: VMap?) -> Va
|
|
|
38
38
|
|
|
39
39
|
let cfg: Value = ctx.config == nil ? .noval : .map(ctx.config!)
|
|
40
40
|
let points = getpath(cfg, jtp("entity", ctx.entity!.getName(), "op", opname, "points"))
|
|
41
|
-
|
|
41
|
+
|
|
42
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
43
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
44
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
45
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
46
|
+
// failing that the shallower path wins.
|
|
47
|
+
func pointPartsLen(_ p: Value) -> Int {
|
|
48
|
+
return getprop(p, .string("parts")).asList?.items.count ?? 0
|
|
49
|
+
}
|
|
50
|
+
func pointTerminalParam(_ p: Value) -> Bool {
|
|
51
|
+
guard let items = getprop(p, .string("parts")).asList?.items, let last = items.last else {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
return (last.asString ?? "").hasPrefix("{")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var point = getelem(points, .int(0))
|
|
58
|
+
if let candidates = points.asList?.items {
|
|
59
|
+
for cand in candidates {
|
|
60
|
+
let candTerm = pointTerminalParam(cand)
|
|
61
|
+
let bestTerm = pointTerminalParam(point)
|
|
62
|
+
if candTerm != bestTerm {
|
|
63
|
+
if candTerm { point = cand }
|
|
64
|
+
}
|
|
65
|
+
else if pointPartsLen(cand) < pointPartsLen(point) {
|
|
66
|
+
point = cand
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
42
70
|
|
|
43
71
|
let paramsPath = getpath(point, jtp("args", "params"))
|
|
44
72
|
let reqdParams = select(paramsPath, .map(vm(("reqd", .bool(true)))))
|
|
@@ -40,8 +40,8 @@ func makePointUtil(_ ctx: Context) throws -> VMap? {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
var point: VMap? = nil
|
|
43
|
+
var matched = false
|
|
43
44
|
for candidate in op.points {
|
|
44
|
-
point = candidate
|
|
45
45
|
let selectDef = gp(candidate, "select").asMap
|
|
46
46
|
var found = true
|
|
47
47
|
|
|
@@ -62,7 +62,56 @@ func makePointUtil(_ ctx: Context) throws -> VMap? {
|
|
|
62
62
|
if reqAction != selectAction { found = false }
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
if found {
|
|
65
|
+
if found {
|
|
66
|
+
point = candidate
|
|
67
|
+
matched = true
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// select.exist can list more than the params needed to pick a point (for
|
|
73
|
+
// /boards/{id} it is Trello's 17 optional query-includes), so a plain
|
|
74
|
+
// {id} call matches NOTHING. Fall back to the entity's own route rather
|
|
75
|
+
// than whichever point came last.
|
|
76
|
+
if !matched {
|
|
77
|
+
// A request naming an action reaches here only because that action's
|
|
78
|
+
// own point failed its exist test, so it is unbuildable whatever we
|
|
79
|
+
// pick. Refuse it BEFORE choosing a fallback: the guard below compares
|
|
80
|
+
// the chosen point's $action and would wave the request through
|
|
81
|
+
// whenever the fallback lands on the action point itself.
|
|
82
|
+
let unmatchedAction = gp(reqselector, "$action")
|
|
83
|
+
if !isNil(unmatchedAction) {
|
|
84
|
+
throw ctx.makeError("point_action_invalid",
|
|
85
|
+
"Operation \"\(op.name)\" action \"\(stringify(unmatchedAction))\" is not valid.")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A terminal parameter marks a record route (/boards/{id}); a
|
|
89
|
+
// cross-reference ends in the relationship's name (/posts/{id}/author).
|
|
90
|
+
// Failing that, the shallower path wins. The same rule runs at
|
|
91
|
+
// generation time, in helpers/opShape.ts — both sides must move
|
|
92
|
+
// together.
|
|
93
|
+
func partsLen(_ p: VMap) -> Int {
|
|
94
|
+
return gp(p, "parts").asList?.items.count ?? 0
|
|
95
|
+
}
|
|
96
|
+
func terminalParam(_ p: VMap) -> Bool {
|
|
97
|
+
guard let items = gp(p, "parts").asList?.items, let last = items.last else {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
return (last.asString ?? "").hasPrefix("{")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
point = op.points.first
|
|
104
|
+
for candidate in op.points {
|
|
105
|
+
guard let best = point else { break }
|
|
106
|
+
let candTerm = terminalParam(candidate)
|
|
107
|
+
let bestTerm = terminalParam(best)
|
|
108
|
+
if candTerm != bestTerm {
|
|
109
|
+
if candTerm { point = candidate }
|
|
110
|
+
}
|
|
111
|
+
else if partsLen(candidate) < partsLen(best) {
|
|
112
|
+
point = candidate
|
|
113
|
+
}
|
|
114
|
+
}
|
|
66
115
|
}
|
|
67
116
|
|
|
68
117
|
let reqAction = gp(reqselector, "$action")
|
|
@@ -253,9 +253,28 @@ class TestFeature extends BaseFeature {
|
|
|
253
253
|
const isempty = struct.isempty
|
|
254
254
|
|
|
255
255
|
const opname = getprop(op, 'name')
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
const points = getpath(ctx.config, [
|
|
257
|
+
'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']) || []
|
|
258
|
+
|
|
259
|
+
// Pick the entity's own endpoint, not a cross-reference from another
|
|
260
|
+
// resource that also returns it — the same rule makePoint falls back to,
|
|
261
|
+
// so the seed-data query is built from the endpoint the request will
|
|
262
|
+
// actually be sent to: a terminal `{id}` marks a record route, and
|
|
263
|
+
// failing that the shallower path wins.
|
|
264
|
+
const isterm = (pt: any) => {
|
|
265
|
+
const parts = pt.parts
|
|
266
|
+
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
267
|
+
return 'string' === typeof last && 0 === last.indexOf('{')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let point = getelem(points, 0)
|
|
271
|
+
for (let i = 1; i < points.length; i++) {
|
|
272
|
+
const cand = getelem(points, i)
|
|
273
|
+
if (isterm(cand) !== isterm(point) ? isterm(cand) :
|
|
274
|
+
cand.parts.length < point.parts.length) {
|
|
275
|
+
point = cand
|
|
276
|
+
}
|
|
277
|
+
}
|
|
259
278
|
|
|
260
279
|
const reqd = transform(
|
|
261
280
|
select(getpath(point, ['args', 'params']), { reqd: true }),
|
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
import { Context, Point } from '../types'
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
// The entity's OWN route among an op's points, as opposed to a
|
|
6
|
+
// CROSS-REFERENCE: another resource's route that happens to return this
|
|
7
|
+
// entity (`/notifications/{id}/board` for a board). Both are plain GETs
|
|
8
|
+
// with no `$action`, so the path itself is all there is to go on.
|
|
9
|
+
//
|
|
10
|
+
// Two signals, in order:
|
|
11
|
+
//
|
|
12
|
+
// 1. A record route ends in the record's identifier (`/boards/{id}`,
|
|
13
|
+
// `/accounts/{account_id}/users/{id}`); a cross-reference ends in the
|
|
14
|
+
// relationship's name (`/posts/{id}/author`). A terminal parameter is
|
|
15
|
+
// therefore the stronger signal, and unlike depth it survives an entity
|
|
16
|
+
// nested more deeply than the route pointing at it.
|
|
17
|
+
// 2. Failing that, fewest path segments — the shallower route is the one
|
|
18
|
+
// the entity is named for.
|
|
19
|
+
//
|
|
20
|
+
// Ties keep the earlier point, so the sorted-key order of the model decides
|
|
21
|
+
// and the output stays byte-stable.
|
|
22
|
+
//
|
|
23
|
+
// The same rule runs at GENERATION time, in `shortestPoint`/`ownPoint` in
|
|
24
|
+
// helpers/opShape.ts. It cannot be shared with this file — a template ships
|
|
25
|
+
// standalone, outside that package — so it is written twice on purpose, and
|
|
26
|
+
// both sides must move together.
|
|
27
|
+
function terminalParam(point: any): boolean {
|
|
28
|
+
const parts = point.parts
|
|
29
|
+
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
30
|
+
return 'string' === typeof last && 0 === last.indexOf('{')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
function ownPoint(points: any[]): any {
|
|
35
|
+
let best = points[0]
|
|
36
|
+
|
|
37
|
+
for (const cand of points) {
|
|
38
|
+
const candterm = terminalParam(cand)
|
|
39
|
+
const bestterm = terminalParam(best)
|
|
40
|
+
|
|
41
|
+
if (candterm !== bestterm ? candterm :
|
|
42
|
+
cand.parts.length < best.parts.length) {
|
|
43
|
+
best = cand
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return best
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
5
51
|
function makePoint(ctx: Context): Point | Error {
|
|
6
52
|
if (ctx.out.point) {
|
|
7
53
|
return ctx.point = ctx.out.point
|
|
@@ -31,9 +77,10 @@ function makePoint(ctx: Context): Point | Error {
|
|
|
31
77
|
const selector = getprop(ctx, op.input)
|
|
32
78
|
|
|
33
79
|
let point
|
|
80
|
+
let matched = false
|
|
34
81
|
for (let i = 0; i < op.points.length; i++) {
|
|
35
|
-
|
|
36
|
-
const select =
|
|
82
|
+
const cand = op.points[i]
|
|
83
|
+
const select = cand.select
|
|
37
84
|
let found = true
|
|
38
85
|
|
|
39
86
|
if (selector && select.exist) {
|
|
@@ -56,10 +103,33 @@ function makePoint(ctx: Context): Point | Error {
|
|
|
56
103
|
}
|
|
57
104
|
|
|
58
105
|
if (found) {
|
|
106
|
+
point = cand
|
|
107
|
+
matched = true
|
|
59
108
|
break
|
|
60
109
|
}
|
|
61
110
|
}
|
|
62
111
|
|
|
112
|
+
// select.exist can list more than the params needed to pick a point (for
|
|
113
|
+
// /boards/{id} it is Trello's 17 optional query-includes), so a plain
|
|
114
|
+
// {id} call matches NOTHING. Fall back to the entity's OWN route rather
|
|
115
|
+
// than whichever point came last.
|
|
116
|
+
if (!matched) {
|
|
117
|
+
// A request naming an action reaches here only because that action's
|
|
118
|
+
// own point failed its exist test, so the request is unbuildable
|
|
119
|
+
// whatever we pick. Refuse it BEFORE choosing a fallback: the guard
|
|
120
|
+
// below compares the chosen point's $action, and would wave the
|
|
121
|
+
// request through whenever the action point is itself the one the
|
|
122
|
+
// fallback selects (a short action route folded into an entity whose
|
|
123
|
+
// canonical route is longer). An unbuildable action request is an
|
|
124
|
+
// error, never a silent request to some other endpoint.
|
|
125
|
+
if (null != reqselector.$action) {
|
|
126
|
+
return ctx.error('point_action_invalid', 'Operation "' + op.name +
|
|
127
|
+
'" action "' + reqselector.$action + '" is not valid.')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
point = ownPoint(op.points)
|
|
131
|
+
}
|
|
132
|
+
|
|
63
133
|
if (
|
|
64
134
|
null != reqselector.$action &&
|
|
65
135
|
null != point &&
|
|
@@ -521,6 +521,29 @@ pub fn make_options_util(ctx: *Context) Value {
|
|
|
521
521
|
// make_point (gotcha #2: rbac PrePoint short-circuit)
|
|
522
522
|
// ============================================================================
|
|
523
523
|
|
|
524
|
+
// How many path segments a point has.
|
|
525
|
+
fn point_parts_len(point: Value) i64 {
|
|
526
|
+
const parts = h.getp(point, "parts");
|
|
527
|
+
if (parts != .array) return 0;
|
|
528
|
+
return @intCast(parts.array.data.items.len);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
532
|
+
// record's identifier (/boards/{id}); a cross-reference that also returns the
|
|
533
|
+
// entity ends in the relationship's name (/posts/{id}/author). That, then
|
|
534
|
+
// fewest segments, is what tells the entity's own route from a
|
|
535
|
+
// cross-reference. The same rule runs at generation time, in
|
|
536
|
+
// helpers/opShape.ts — both sides must move together.
|
|
537
|
+
fn point_terminal_param(point: Value) bool {
|
|
538
|
+
const parts = h.getp(point, "parts");
|
|
539
|
+
if (parts != .array) return false;
|
|
540
|
+
const items = parts.array.data.items;
|
|
541
|
+
if (0 == items.len) return false;
|
|
542
|
+
const last = items[items.len - 1];
|
|
543
|
+
if (last != .string) return false;
|
|
544
|
+
return 0 < last.string.len and '{' == last.string[0];
|
|
545
|
+
}
|
|
546
|
+
|
|
524
547
|
pub fn make_point_util(ctx: *Context) E!Value {
|
|
525
548
|
if (ctx.out_get("point")) |ov| {
|
|
526
549
|
switch (ov) {
|
|
@@ -560,10 +583,11 @@ pub fn make_point_util(ctx: *Context) E!Value {
|
|
|
560
583
|
const selector: Value = if (std.mem.eql(u8, op.input, "data")) ctx.data else ctx.mtch;
|
|
561
584
|
|
|
562
585
|
var point: Value = h.vnull();
|
|
586
|
+
var matched = false;
|
|
563
587
|
var i: i64 = 0;
|
|
564
588
|
while (i < plen) : (i += 1) {
|
|
565
|
-
|
|
566
|
-
const select_def = h.to_map(h.getp(
|
|
589
|
+
const cand = h.get_elem(points, h.vnum(i), h.vnull());
|
|
590
|
+
const select_def = h.to_map(h.getp(cand, "select"));
|
|
567
591
|
var found = true;
|
|
568
592
|
|
|
569
593
|
if (!h.is_noval(selector) and !h.is_noval(select_def)) {
|
|
@@ -589,7 +613,46 @@ pub fn make_point_util(ctx: *Context) E!Value {
|
|
|
589
613
|
if (!h.veq(req_action, select_action)) found = false;
|
|
590
614
|
}
|
|
591
615
|
|
|
592
|
-
if (found)
|
|
616
|
+
if (found) {
|
|
617
|
+
point = cand;
|
|
618
|
+
matched = true;
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// select.exist can list more than the params needed to pick a point
|
|
624
|
+
// (for /boards/{id} it is Trello's 17 optional query-includes), so a
|
|
625
|
+
// plain {id} call matches NOTHING. Fall back to the entity's own
|
|
626
|
+
// route rather than whichever point came last.
|
|
627
|
+
if (!matched) {
|
|
628
|
+
// A request naming an action reaches here only because that
|
|
629
|
+
// action's own point failed its exist test, so it is unbuildable
|
|
630
|
+
// whatever we pick. Refuse it BEFORE choosing a fallback: the
|
|
631
|
+
// guard below compares the chosen point's $action and would wave
|
|
632
|
+
// the request through whenever the fallback lands on the action
|
|
633
|
+
// point itself.
|
|
634
|
+
const unmatched_action = h.getp(reqselector, "$action");
|
|
635
|
+
if (!h.is_noval(unmatched_action)) {
|
|
636
|
+
return ctx.fail("point_action_invalid", fmt("Operation \"{s}\" action \"{s}\" is not valid.", .{ op.name, h.stringify(unmatched_action) }));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// A terminal parameter marks a record route (/boards/{id}); a
|
|
640
|
+
// cross-reference ends in the relationship's name
|
|
641
|
+
// (/posts/{id}/author). Failing that, the shallower path wins.
|
|
642
|
+
// The same rule runs at generation time, in helpers/opShape.ts —
|
|
643
|
+
// both sides must move together.
|
|
644
|
+
point = h.get_elem(points, h.vnum(0), h.vnull());
|
|
645
|
+
var j: i64 = 0;
|
|
646
|
+
while (j < plen) : (j += 1) {
|
|
647
|
+
const cand = h.get_elem(points, h.vnum(j), h.vnull());
|
|
648
|
+
const cand_term = point_terminal_param(cand);
|
|
649
|
+
const best_term = point_terminal_param(point);
|
|
650
|
+
if (cand_term != best_term) {
|
|
651
|
+
if (cand_term) point = cand;
|
|
652
|
+
} else if (point_parts_len(cand) < point_parts_len(point)) {
|
|
653
|
+
point = cand;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
593
656
|
}
|
|
594
657
|
|
|
595
658
|
const req_action = h.getp(reqselector, "$action");
|
|
@@ -88,7 +88,7 @@ const ExternalTarget = cmp(function ExternalTarget(props: any) {
|
|
|
88
88
|
names(target, target.name)
|
|
89
89
|
|
|
90
90
|
if (phaseActive('entity')) {
|
|
91
|
-
each(entity
|
|
91
|
+
each(entity).filter((entity: any) => entity.active).map((entity: any) => {
|
|
92
92
|
names(entity, entity.name)
|
|
93
93
|
Entity({ target, entity })
|
|
94
94
|
})
|