@voxgig/sdkgen 3.5.1 → 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
|
@@ -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') !=
|
|
@@ -465,21 +465,81 @@ defmodule ProjectName.Utility do
|
|
|
465
465
|
|
|
466
466
|
if found, do: {:halt, point}, else: {:cont, point}
|
|
467
467
|
end)
|
|
468
|
-
|> elem(1)
|
|
469
468
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
469
|
+
matched? = match?({:halt, _}, point)
|
|
470
|
+
|
|
471
|
+
point =
|
|
472
|
+
if matched? do
|
|
473
|
+
elem(point, 1)
|
|
474
|
+
else
|
|
475
|
+
# select.exist can list more than the params needed to pick a
|
|
476
|
+
# point, so nothing matched. Fall back to the entity's own
|
|
477
|
+
# route: a terminal parameter marks a record route
|
|
478
|
+
# (/boards/{id}) where a cross-reference ends in the
|
|
479
|
+
# relationship's name (/posts/{id}/author), and failing that
|
|
480
|
+
# the shallower path wins. The same rule runs at generation
|
|
481
|
+
# time, in helpers/opShape.ts — both sides must move together.
|
|
482
|
+
parts_len = fn p ->
|
|
483
|
+
parts = S.getprop(p, "parts")
|
|
484
|
+
if S.islist(parts), do: S.size(parts), else: 0
|
|
485
|
+
end
|
|
473
486
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
point_action = S.getprop(point_select, "$action")
|
|
487
|
+
terminal_param? = fn p ->
|
|
488
|
+
parts = S.getprop(p, "parts")
|
|
477
489
|
|
|
478
|
-
if
|
|
479
|
-
|
|
480
|
-
|
|
490
|
+
if S.islist(parts) and S.size(parts) > 0 do
|
|
491
|
+
last = S.getelem(parts, S.size(parts) - 1)
|
|
492
|
+
is_binary(last) and String.starts_with?(last, "{")
|
|
493
|
+
else
|
|
494
|
+
false
|
|
481
495
|
end
|
|
482
496
|
end
|
|
497
|
+
|
|
498
|
+
Enum.reduce(0..(npoints - 1), S.getelem(points, 0), fn i, best ->
|
|
499
|
+
cand = S.getelem(points, i)
|
|
500
|
+
ct = terminal_param?.(cand)
|
|
501
|
+
bt = terminal_param?.(best)
|
|
502
|
+
|
|
503
|
+
cond do
|
|
504
|
+
ct != bt -> if ct, do: cand, else: best
|
|
505
|
+
parts_len.(cand) < parts_len.(best) -> cand
|
|
506
|
+
true -> best
|
|
507
|
+
end
|
|
508
|
+
end)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
unmatched_action =
|
|
512
|
+
if not matched? and reqselector != nil,
|
|
513
|
+
do: S.getprop(reqselector, "$action"),
|
|
514
|
+
else: nil
|
|
515
|
+
|
|
516
|
+
err =
|
|
517
|
+
cond do
|
|
518
|
+
# A request naming an action reaches the fallback only because
|
|
519
|
+
# that action's own point failed its exist test, so it is
|
|
520
|
+
# unbuildable whatever we pick. Refuse it BEFORE the guard
|
|
521
|
+
# below, which compares the chosen point's $action and would
|
|
522
|
+
# wave the request through whenever the fallback lands on the
|
|
523
|
+
# action point itself.
|
|
524
|
+
unmatched_action != nil ->
|
|
525
|
+
Context.make_error(ctx, "point_action_invalid",
|
|
526
|
+
"Operation \"" <> opname <> "\" action \"" <> S.stringify(unmatched_action) <> "\" is not valid.")
|
|
527
|
+
|
|
528
|
+
reqselector != nil ->
|
|
529
|
+
req_action = S.getprop(reqselector, "$action")
|
|
530
|
+
|
|
531
|
+
if req_action != nil and point != nil do
|
|
532
|
+
point_select = H.to_map(S.getprop(point, "select"))
|
|
533
|
+
point_action = S.getprop(point_select, "$action")
|
|
534
|
+
|
|
535
|
+
if req_action != point_action do
|
|
536
|
+
Context.make_error(ctx, "point_action_invalid",
|
|
537
|
+
"Operation \"" <> opname <> "\" action \"" <> S.stringify(req_action) <> "\" is not valid.")
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
true ->
|
|
542
|
+
nil
|
|
483
543
|
end
|
|
484
544
|
|
|
485
545
|
if err != nil do
|
|
@@ -4,6 +4,7 @@ import (
|
|
|
4
4
|
"fmt"
|
|
5
5
|
"math/rand"
|
|
6
6
|
"regexp"
|
|
7
|
+
"strings"
|
|
7
8
|
|
|
8
9
|
vs "github.com/voxgig/struct"
|
|
9
10
|
|
|
@@ -310,9 +311,40 @@ func (f *TestFeature) makeNetsim(net map[string]any, inner core.FetcherFunc) cor
|
|
|
310
311
|
func (f *TestFeature) buildArgs(ctx *core.Context, op *core.Operation, args map[string]any) any {
|
|
311
312
|
opname := op.Name
|
|
312
313
|
|
|
313
|
-
//
|
|
314
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
315
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
316
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
317
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
318
|
+
// failing that the shallower path wins.
|
|
314
319
|
points := vs.GetPath([]any{"entity", ctx.Entity.GetName(), "op", opname, "points"}, ctx.Config)
|
|
315
|
-
point := vs.GetElem(points,
|
|
320
|
+
point := vs.GetElem(points, 0)
|
|
321
|
+
if plist, ok := points.([]any); ok {
|
|
322
|
+
partsLen := func(p any) int {
|
|
323
|
+
if parts, ok := vs.GetProp(p, "parts").([]any); ok {
|
|
324
|
+
return len(parts)
|
|
325
|
+
}
|
|
326
|
+
return 0
|
|
327
|
+
}
|
|
328
|
+
terminalParam := func(p any) bool {
|
|
329
|
+
parts, ok := vs.GetProp(p, "parts").([]any)
|
|
330
|
+
if !ok || 0 == len(parts) {
|
|
331
|
+
return false
|
|
332
|
+
}
|
|
333
|
+
last, _ := parts[len(parts)-1].(string)
|
|
334
|
+
return strings.HasPrefix(last, "{")
|
|
335
|
+
}
|
|
336
|
+
for _, cand := range plist {
|
|
337
|
+
candTerm := terminalParam(cand)
|
|
338
|
+
bestTerm := terminalParam(point)
|
|
339
|
+
if candTerm != bestTerm {
|
|
340
|
+
if candTerm {
|
|
341
|
+
point = cand
|
|
342
|
+
}
|
|
343
|
+
} else if partsLen(cand) < partsLen(point) {
|
|
344
|
+
point = cand
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
316
348
|
|
|
317
349
|
// Get required params.
|
|
318
350
|
paramsPath := vs.GetPath([]any{"args", "params"}, point)
|
|
@@ -8,6 +8,47 @@ import (
|
|
|
8
8
|
"GOMODULE/core"
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
// How many path segments a point has.
|
|
12
|
+
func partsLen(point map[string]any) int {
|
|
13
|
+
if parts, ok := vs.GetProp(point, "parts").([]any); ok {
|
|
14
|
+
return len(parts)
|
|
15
|
+
}
|
|
16
|
+
return 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
20
|
+
// record's identifier (`/boards/{id}`); a cross-reference that also returns
|
|
21
|
+
// the entity ends in the relationship's name (`/posts/{id}/author`).
|
|
22
|
+
func terminalParam(point map[string]any) bool {
|
|
23
|
+
parts, ok := vs.GetProp(point, "parts").([]any)
|
|
24
|
+
if !ok || 0 == len(parts) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
last, _ := parts[len(parts)-1].(string)
|
|
28
|
+
return strings.HasPrefix(last, "{")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// The entity's OWN route among an op's points: a terminal parameter first,
|
|
32
|
+
// then the fewest path segments. Ties keep the earlier point, so the model's
|
|
33
|
+
// sorted-key order decides. The same rule runs at generation time, in
|
|
34
|
+
// helpers/opShape.ts — a template ships standalone, so both sides must move
|
|
35
|
+
// together.
|
|
36
|
+
func ownPoint(points []map[string]any) map[string]any {
|
|
37
|
+
best := points[0]
|
|
38
|
+
for _, cand := range points {
|
|
39
|
+
candTerm := terminalParam(cand)
|
|
40
|
+
bestTerm := terminalParam(best)
|
|
41
|
+
if candTerm != bestTerm {
|
|
42
|
+
if candTerm {
|
|
43
|
+
best = cand
|
|
44
|
+
}
|
|
45
|
+
} else if partsLen(cand) < partsLen(best) {
|
|
46
|
+
best = cand
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return best
|
|
50
|
+
}
|
|
51
|
+
|
|
11
52
|
func makePointUtil(ctx *core.Context) (map[string]any, error) {
|
|
12
53
|
if ctx.Out["point"] != nil {
|
|
13
54
|
// A PrePoint feature hook (e.g. rbac) may short-circuit the
|
|
@@ -52,9 +93,10 @@ func makePointUtil(ctx *core.Context) (map[string]any, error) {
|
|
|
52
93
|
}
|
|
53
94
|
|
|
54
95
|
var point map[string]any
|
|
96
|
+
matched := false
|
|
55
97
|
for i := 0; i < len(op.Points); i++ {
|
|
56
|
-
|
|
57
|
-
selectDef := core.ToMapAny(vs.GetProp(
|
|
98
|
+
cand := op.Points[i]
|
|
99
|
+
selectDef := core.ToMapAny(vs.GetProp(cand, "select"))
|
|
58
100
|
found := true
|
|
59
101
|
|
|
60
102
|
if selector != nil && selectDef != nil {
|
|
@@ -82,10 +124,32 @@ func makePointUtil(ctx *core.Context) (map[string]any, error) {
|
|
|
82
124
|
}
|
|
83
125
|
|
|
84
126
|
if found {
|
|
127
|
+
point = cand
|
|
128
|
+
matched = true
|
|
85
129
|
break
|
|
86
130
|
}
|
|
87
131
|
}
|
|
88
132
|
|
|
133
|
+
// select.exist can list more than the params needed to pick a point,
|
|
134
|
+
// so nothing matches — fall back to the entity's own route rather
|
|
135
|
+
// than whichever point came last.
|
|
136
|
+
if !matched {
|
|
137
|
+
// A request naming an action reaches here only because that
|
|
138
|
+
// action's own point failed its exist test, so it is unbuildable
|
|
139
|
+
// whatever we pick. Refuse it BEFORE choosing a fallback: the
|
|
140
|
+
// guard below compares the chosen point's $action and would wave
|
|
141
|
+
// the request through whenever the fallback lands on the action
|
|
142
|
+
// point itself.
|
|
143
|
+
if reqselector != nil && vs.GetProp(reqselector, "$action") != nil {
|
|
144
|
+
return nil, ctx.MakeError("point_action_invalid",
|
|
145
|
+
"Operation \""+op.Name+
|
|
146
|
+
"\" action \""+vs.Stringify(vs.GetProp(reqselector, "$action"))+
|
|
147
|
+
"\" is not valid.")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
point = ownPoint(op.Points)
|
|
151
|
+
}
|
|
152
|
+
|
|
89
153
|
if reqselector != nil {
|
|
90
154
|
reqAction := vs.GetProp(reqselector, "$action")
|
|
91
155
|
if reqAction != nil && point != nil {
|
|
@@ -26,6 +26,29 @@ public class TestFeature extends BaseFeature {
|
|
|
26
26
|
private Map<String, Object> options;
|
|
27
27
|
private int netcalls = 0;
|
|
28
28
|
|
|
29
|
+
// How many path segments a point has, and whether its path ends in a
|
|
30
|
+
// parameter — the rule makePoint uses to tell the entity's own route from a
|
|
31
|
+
// cross-reference that also returns it.
|
|
32
|
+
@SuppressWarnings("unchecked")
|
|
33
|
+
private static int pointPartsLen(Object point) {
|
|
34
|
+
Object parts = Struct.getprop(point, "parts");
|
|
35
|
+
return parts instanceof List ? ((List<Object>) parts).size() : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@SuppressWarnings("unchecked")
|
|
39
|
+
private static boolean pointTerminalParam(Object point) {
|
|
40
|
+
Object parts = Struct.getprop(point, "parts");
|
|
41
|
+
if (!(parts instanceof List)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
List<Object> list = (List<Object>) parts;
|
|
45
|
+
if (list.isEmpty()) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
Object last = list.get(list.size() - 1);
|
|
49
|
+
return last instanceof String && ((String) last).startsWith("{");
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
public TestFeature() {
|
|
30
53
|
super("test", "0.0.1", true);
|
|
31
54
|
}
|
|
@@ -289,11 +312,28 @@ public class TestFeature extends BaseFeature {
|
|
|
289
312
|
private Object buildArgs(Context ctx, Operation op, Map<String, Object> args) {
|
|
290
313
|
String opname = op.name;
|
|
291
314
|
|
|
292
|
-
//
|
|
315
|
+
// Pick the entity's own endpoint from config, not a cross-reference from
|
|
316
|
+
// another resource that also returns it — the same rule makePoint falls
|
|
317
|
+
// back to, so the seed-data query is built from the endpoint the request
|
|
318
|
+
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
319
|
+
// failing that the shallower path wins.
|
|
293
320
|
Object points = Struct.getpath(ctx.config,
|
|
294
321
|
List.of("entity", ctx.entity == null ? "" : ctx.entity.getName(),
|
|
295
322
|
"op", opname, "points"));
|
|
296
|
-
Object point = Struct.getelem(points,
|
|
323
|
+
Object point = Struct.getelem(points, 0);
|
|
324
|
+
if (points instanceof List) {
|
|
325
|
+
for (Object cand : (List<Object>) points) {
|
|
326
|
+
boolean candTerm = pointTerminalParam(cand);
|
|
327
|
+
boolean bestTerm = pointTerminalParam(point);
|
|
328
|
+
if (candTerm != bestTerm) {
|
|
329
|
+
if (candTerm) {
|
|
330
|
+
point = cand;
|
|
331
|
+
}
|
|
332
|
+
} else if (pointPartsLen(cand) < pointPartsLen(point)) {
|
|
333
|
+
point = cand;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
297
337
|
|
|
298
338
|
// Get required params.
|
|
299
339
|
Object paramsPath = Struct.getpath(point, List.of("args", "params"));
|
|
@@ -14,6 +14,31 @@ final class MakePoint {
|
|
|
14
14
|
|
|
15
15
|
private MakePoint() {}
|
|
16
16
|
|
|
17
|
+
// How many path segments a point has.
|
|
18
|
+
private static int partsLen(Map<String, Object> point) {
|
|
19
|
+
Object parts = Struct.getprop(point, "parts");
|
|
20
|
+
return parts instanceof List ? ((List<Object>) parts).size() : 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Does this point's path end in a parameter? A record route ends in the
|
|
24
|
+
// record's identifier (/boards/{id}); a cross-reference that also returns
|
|
25
|
+
// the entity ends in the relationship's name (/posts/{id}/author). That,
|
|
26
|
+
// then fewest segments, is what tells the entity's own route from a
|
|
27
|
+
// cross-reference. The same rule runs at generation time, in
|
|
28
|
+
// helpers/opShape.ts — both sides must move together.
|
|
29
|
+
private static boolean terminalParam(Map<String, Object> point) {
|
|
30
|
+
Object parts = Struct.getprop(point, "parts");
|
|
31
|
+
if (!(parts instanceof List)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
List<Object> list = (List<Object>) parts;
|
|
35
|
+
if (list.isEmpty()) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
Object last = list.get(list.size() - 1);
|
|
39
|
+
return last instanceof String && ((String) last).startsWith("{");
|
|
40
|
+
}
|
|
41
|
+
|
|
17
42
|
static Map<String, Object> makePoint(Context ctx) {
|
|
18
43
|
Object outPoint = ctx.out.get("point");
|
|
19
44
|
if (outPoint != null) {
|
|
@@ -62,10 +87,11 @@ final class MakePoint {
|
|
|
62
87
|
}
|
|
63
88
|
|
|
64
89
|
Map<String, Object> point = null;
|
|
90
|
+
boolean matched = false;
|
|
65
91
|
for (int i = 0; i < op.points.size(); i++) {
|
|
66
|
-
|
|
92
|
+
Map<String, Object> cand = op.points.get(i);
|
|
67
93
|
Map<String, Object> selectDef =
|
|
68
|
-
Helpers.toMapAny(Struct.getprop(
|
|
94
|
+
Helpers.toMapAny(Struct.getprop(cand, "select"));
|
|
69
95
|
boolean found = true;
|
|
70
96
|
|
|
71
97
|
if (selector != null && selectDef != null) {
|
|
@@ -92,10 +118,43 @@ final class MakePoint {
|
|
|
92
118
|
}
|
|
93
119
|
|
|
94
120
|
if (found) {
|
|
121
|
+
point = cand;
|
|
122
|
+
matched = true;
|
|
95
123
|
break;
|
|
96
124
|
}
|
|
97
125
|
}
|
|
98
126
|
|
|
127
|
+
// select.exist can list more than the params needed to pick a point, so
|
|
128
|
+
// nothing matches — fall back to the entity's own route rather than
|
|
129
|
+
// whichever point came last.
|
|
130
|
+
if (!matched) {
|
|
131
|
+
// A request naming an action reaches here only because that action's
|
|
132
|
+
// own point failed its exist test, so it is unbuildable whatever we
|
|
133
|
+
// pick. Refuse it BEFORE choosing a fallback: the guard below
|
|
134
|
+
// compares the chosen point's $action and would wave the request
|
|
135
|
+
// through whenever the fallback lands on the action point itself.
|
|
136
|
+
Object unmatchedAction =
|
|
137
|
+
reqselector != null ? Struct.getprop(reqselector, "$action", null) : null;
|
|
138
|
+
if (unmatchedAction != null) {
|
|
139
|
+
throw ctx.makeError("point_action_invalid",
|
|
140
|
+
"Operation \"" + op.name
|
|
141
|
+
+ "\" action \"" + Struct.stringify(unmatchedAction) + "\" is not valid.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
point = op.points.get(0);
|
|
145
|
+
for (Map<String, Object> cand : op.points) {
|
|
146
|
+
boolean candTerm = terminalParam(cand);
|
|
147
|
+
boolean bestTerm = terminalParam(point);
|
|
148
|
+
if (candTerm != bestTerm) {
|
|
149
|
+
if (candTerm) {
|
|
150
|
+
point = cand;
|
|
151
|
+
}
|
|
152
|
+
} else if (partsLen(cand) < partsLen(point)) {
|
|
153
|
+
point = cand;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
99
158
|
if (reqselector != null) {
|
|
100
159
|
Object reqAction = Struct.getprop(reqselector, "$action", null);
|
|
101
160
|
if (reqAction != null && point != null) {
|
|
@@ -251,9 +251,28 @@ class TestFeature extends BaseFeature {
|
|
|
251
251
|
const isempty = struct.isempty
|
|
252
252
|
|
|
253
253
|
const opname = getprop(op, 'name')
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
const points = getpath(ctx.config, [
|
|
255
|
+
'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']) || []
|
|
256
|
+
|
|
257
|
+
// Pick the entity's own endpoint, not a cross-reference from another
|
|
258
|
+
// resource that also returns it — the same rule makePoint falls back to,
|
|
259
|
+
// so the seed-data query is built from the endpoint the request will
|
|
260
|
+
// actually be sent to: a terminal `{id}` marks a record route, and
|
|
261
|
+
// failing that the shallower path wins.
|
|
262
|
+
const isterm = (pt) => {
|
|
263
|
+
const parts = pt.parts
|
|
264
|
+
const last = 0 < parts.length ? parts[parts.length - 1] : ''
|
|
265
|
+
return 'string' === typeof last && 0 === last.indexOf('{')
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let point = getelem(points, 0)
|
|
269
|
+
for (let i = 1; i < points.length; i++) {
|
|
270
|
+
const cand = getelem(points, i)
|
|
271
|
+
if (isterm(cand) !== isterm(point) ? isterm(cand) :
|
|
272
|
+
cand.parts.length < point.parts.length) {
|
|
273
|
+
point = cand
|
|
274
|
+
}
|
|
275
|
+
}
|
|
257
276
|
|
|
258
277
|
const reqd = transform(
|
|
259
278
|
select(getpath(point, ['args', 'params']), { reqd: true }),
|
|
@@ -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"))
|