@voxgig/sdkgen 4.3.0 → 4.4.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/package.json +1 -1
- package/project/.sdk/src/cmp/zig/Config_zig.ts +1 -1
- package/project/.sdk/tm/c/tests/primary_corpus_test.c +565 -0
- package/project/.sdk/tm/c/utility/prepare_method.c +3 -1
- package/project/.sdk/tm/c/utility/transform_request.c +4 -3
- package/project/.sdk/tm/clojure/src/sdk/core.clj +55 -5
- package/project/.sdk/tm/clojure/test/sdk/test/primary.clj +171 -1
- package/project/.sdk/tm/clojure/test/sdk/test/struct_corpus.clj +13 -2
- package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +2 -0
- package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +104 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +82 -2
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +50 -2
- package/project/.sdk/tm/elixir/test/primary_utility_test.exs +18 -200
- package/project/.sdk/tm/elixir/test/support/struct_corpus.ex +413 -2
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +56 -2
- package/project/.sdk/tm/lua/utility/make_options.lua +22 -2
- package/project/.sdk/tm/ocaml/Makefile +15 -4
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +8 -1
- package/project/.sdk/tm/ocaml/test/corpus_runner.ml +185 -0
- package/project/.sdk/tm/ocaml/test/primary_utility_test.ml +238 -0
- package/project/.sdk/tm/ocaml/test/struct_corpus.ml +5 -160
- package/project/.sdk/tm/scala/Makefile +1 -0
- package/project/.sdk/tm/scala/sdktest/PrimaryCorpus.scala +294 -0
- package/project/.sdk/tm/scala/sdktest/StructCorpus.scala +0 -0
- package/project/.sdk/tm/scala/utility/Make.scala +49 -2
- package/project/.sdk/tm/scala/utility/Prepare.scala +3 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +13 -2
- package/project/.sdk/tm/zig/core/utility.zig +8 -1
- package/project/.sdk/tm/zig/test/primary_utility_test.zig +445 -0
- package/project/.sdk/tm/zig/test/struct_runner.zig +238 -0
- package/project/sdkgen-package.json +1 -1
|
@@ -1,165 +1,10 @@
|
|
|
1
|
-
(*
|
|
2
|
-
*
|
|
3
|
-
*
|
|
1
|
+
(* Struct corpus: drives test.json -> "struct" through the vendored
|
|
2
|
+
* Voxgig_struct implementation. The harness itself lives in Corpus_runner so
|
|
3
|
+
* the primary-utility suite can share it.
|
|
4
|
+
*)
|
|
4
5
|
|
|
5
6
|
open Voxgig_struct
|
|
6
|
-
|
|
7
|
-
let nullmark = "__NULL__"
|
|
8
|
-
let undefmark = "__UNDEF__"
|
|
9
|
-
let existsmark = "__EXISTS__"
|
|
10
|
-
|
|
11
|
-
(* The JSON reader now lives in the runtime (sdk_json.ml) so the SDK's
|
|
12
|
-
* generated config can use it; the corpus runs against that same function
|
|
13
|
-
* rather than a second copy that could drift from it. *)
|
|
14
|
-
let json_read = Sdk_json.json_read
|
|
15
|
-
|
|
16
|
-
(* ---------------- fixJSON / equality ---------------- *)
|
|
17
|
-
|
|
18
|
-
let rec fix_json v flag_null =
|
|
19
|
-
match v with
|
|
20
|
-
| Noval | Null -> if flag_null then Str nullmark else v
|
|
21
|
-
| Map m -> let o = empty_map () in
|
|
22
|
-
List.iter (fun (k, x) -> ignore (setprop o (Str k) (fix_json x flag_null))) m.entries; o
|
|
23
|
-
| List r -> lst (List.map (fun x -> fix_json x flag_null) !r)
|
|
24
|
-
| _ -> v
|
|
25
|
-
|
|
26
|
-
(* Order-independent deep equality for maps; sequence equality for lists. *)
|
|
27
|
-
let rec eqv a b =
|
|
28
|
-
match a, b with
|
|
29
|
-
| (Noval | Null), (Noval | Null) -> true
|
|
30
|
-
| Bool x, Bool y -> x = y
|
|
31
|
-
| Num x, Num y -> x = y
|
|
32
|
-
| Str x, Str y -> x = y
|
|
33
|
-
| List x, List y -> List.length !x = List.length !y && List.for_all2 eqv !x !y
|
|
34
|
-
| Map x, Map y ->
|
|
35
|
-
omap_len x = omap_len y &&
|
|
36
|
-
List.for_all (fun (k, v) -> match omap_get y k with Some w -> eqv v w | None -> false) x.entries
|
|
37
|
-
| _ -> a == b
|
|
38
|
-
|
|
39
|
-
(* ---------------- match support ---------------- *)
|
|
40
|
-
|
|
41
|
-
let matchval check base =
|
|
42
|
-
let check = if check = Str undefmark || check = Str nullmark then Noval else check in
|
|
43
|
-
if eqv check base then true
|
|
44
|
-
else match check with
|
|
45
|
-
| Str cs ->
|
|
46
|
-
let basestr = stringify base in
|
|
47
|
-
if String.length cs >= 2 && cs.[0] = '/' && cs.[String.length cs - 1] = '/' then
|
|
48
|
-
Vregex.test_str (String.sub cs 1 (String.length cs - 2)) basestr
|
|
49
|
-
else
|
|
50
|
-
let low s = String.lowercase_ascii s in
|
|
51
|
-
let contains hay needle =
|
|
52
|
-
let hl = String.length hay and nl = String.length needle in
|
|
53
|
-
let rec go i = if i + nl > hl then false
|
|
54
|
-
else if String.sub hay i nl = needle then true else go (i + 1) in
|
|
55
|
-
nl = 0 || go 0 in
|
|
56
|
-
contains (low basestr) (low (stringify check))
|
|
57
|
-
| Func _ -> true
|
|
58
|
-
| _ -> false
|
|
59
|
-
|
|
60
|
-
let do_match check base =
|
|
61
|
-
let base = clone base in
|
|
62
|
-
ignore (walk ~before:(fun _k v _p path ->
|
|
63
|
-
(if not (isnode v) then begin
|
|
64
|
-
let baseval = getpath base path in
|
|
65
|
-
if eqv baseval v then ()
|
|
66
|
-
else if v = Str undefmark && is_nullish baseval then ()
|
|
67
|
-
else if v = Str existsmark && not (is_nullish baseval) then ()
|
|
68
|
-
else if not (matchval v baseval) then
|
|
69
|
-
raise (Struct_error (Printf.sprintf "MATCH: %s: [%s] <=> [%s]"
|
|
70
|
-
(String.concat "." (List.map js_string (match path with List r -> !r | _ -> [])))
|
|
71
|
-
(stringify v) (stringify baseval)))
|
|
72
|
-
end);
|
|
73
|
-
v) check)
|
|
74
|
-
|
|
75
|
-
(* ---------------- result tracking ---------------- *)
|
|
76
|
-
|
|
77
|
-
let npass = ref 0
|
|
78
|
-
let nfail = ref 0
|
|
79
|
-
let failures = ref []
|
|
80
|
-
|
|
81
|
-
let record group name ok msg =
|
|
82
|
-
if ok then incr npass
|
|
83
|
-
else (incr nfail; failures := Printf.sprintf "FAIL %s %s - %s" group name msg :: !failures)
|
|
84
|
-
|
|
85
|
-
(* ---------------- per-entry runner ---------------- *)
|
|
86
|
-
|
|
87
|
-
let omap_v kvs =
|
|
88
|
-
let m = empty_map () in
|
|
89
|
-
List.iter (fun (k, v) -> ignore (setprop m (Str k) v)) kvs; m
|
|
90
|
-
|
|
91
|
-
let getprop_raw_pub e k = (match e with Map m -> (match omap_get m k with Some x -> x | None -> Noval) | _ -> Noval)
|
|
92
|
-
let entry_get e k = getprop_raw_pub e k
|
|
93
|
-
let entry_has e k = match e with Map m -> omap_has m k | _ -> false
|
|
94
|
-
let default_injdef_pub () =
|
|
95
|
-
{ d_meta = Noval; d_extra = Noval; d_errs = Noval; d_modify = None; d_handler = None;
|
|
96
|
-
d_base = Noval; d_dparent = Noval; d_dpath = Noval; d_key = Noval }
|
|
97
|
-
|
|
98
|
-
let resolve_args entry =
|
|
99
|
-
if entry_has entry "ctx" then [entry_get entry "ctx"]
|
|
100
|
-
else if entry_has entry "args" then (match entry_get entry "args" with List r -> !r | _ -> [])
|
|
101
|
-
else if entry_has entry "in" then [clone (entry_get entry "in")]
|
|
102
|
-
else [Noval]
|
|
103
|
-
|
|
104
|
-
let check_result entry args res =
|
|
105
|
-
let matched = ref false in
|
|
106
|
-
(if entry_has entry "match" then begin
|
|
107
|
-
do_match (entry_get entry "match")
|
|
108
|
-
(omap_v ["in", entry_get entry "in"; "args", lst args;
|
|
109
|
-
"out", entry_get entry "res"; "ctx", entry_get entry "ctx"]);
|
|
110
|
-
matched := true
|
|
111
|
-
end);
|
|
112
|
-
let out = entry_get entry "out" in
|
|
113
|
-
if eqv out res then ()
|
|
114
|
-
else if !matched && (out = Str nullmark || is_nullish out) then ()
|
|
115
|
-
else raise (Struct_error (Printf.sprintf "Expected: %s, got: %s" (stringify out) (stringify res)))
|
|
116
|
-
|
|
117
|
-
let handle_error entry err =
|
|
118
|
-
let msg = (match err with Struct_error m -> m | e -> Printexc.to_string e) in
|
|
119
|
-
if entry_has entry "err" then begin
|
|
120
|
-
let entry_err = entry_get entry "err" in
|
|
121
|
-
if entry_err = Bool true || matchval entry_err (Str msg) then begin
|
|
122
|
-
if entry_has entry "match" then
|
|
123
|
-
do_match (entry_get entry "match")
|
|
124
|
-
(omap_v ["in", entry_get entry "in"; "out", entry_get entry "res";
|
|
125
|
-
"ctx", entry_get entry "ctx"; "err", Str msg])
|
|
126
|
-
end else
|
|
127
|
-
raise (Struct_error (Printf.sprintf "ERROR MATCH: [%s] <=> [%s]" (stringify entry_err) msg))
|
|
128
|
-
end else raise err
|
|
129
|
-
|
|
130
|
-
let run_set ?(flags = []) group node subject =
|
|
131
|
-
let flag_null = (match List.assoc_opt "null" flags with Some b -> b | None -> true) in
|
|
132
|
-
let fixed = fix_json node flag_null in
|
|
133
|
-
let testset = (match getprop fixed (Str "set") with List r -> !r | _ -> []) in
|
|
134
|
-
List.iter (fun entry ->
|
|
135
|
-
let name = js_string (entry_get entry "name") in
|
|
136
|
-
try
|
|
137
|
-
(if not (entry_has entry "out") && flag_null then ignore (setprop entry (Str "out") (Str nullmark)));
|
|
138
|
-
let args = resolve_args entry in
|
|
139
|
-
let res = fix_json (subject args) flag_null in
|
|
140
|
-
ignore (setprop entry (Str "res") res);
|
|
141
|
-
check_result entry args res;
|
|
142
|
-
record group name true ""
|
|
143
|
-
with
|
|
144
|
-
| e ->
|
|
145
|
-
(try handle_error entry e; record group name true ""
|
|
146
|
-
with e2 -> record group name false
|
|
147
|
-
(match e2 with Struct_error m -> m | _ -> Printexc.to_string e2)))
|
|
148
|
-
testset
|
|
149
|
-
|
|
150
|
-
let run_single group node actual_fn =
|
|
151
|
-
try
|
|
152
|
-
let expected = getprop_raw_pub node "out" in
|
|
153
|
-
let actual = actual_fn (getprop_raw_pub node "in") in
|
|
154
|
-
if eqv expected actual then record group "single" true ""
|
|
155
|
-
else record group "single" false (Printf.sprintf "Expected: %s, got: %s" (stringify expected) (stringify actual))
|
|
156
|
-
with e -> record group "single" false (match e with Struct_error m -> m | _ -> Printexc.to_string e)
|
|
157
|
-
|
|
158
|
-
(* ---------------- arg helpers ---------------- *)
|
|
159
|
-
|
|
160
|
-
let arg1 f = fun args -> f (match args with x :: _ -> x | [] -> Noval)
|
|
161
|
-
let vget vin k = match vin with Map m -> (match omap_get m k with Some x -> x | None -> Noval) | _ -> Noval
|
|
162
|
-
let vhas vin k = match vin with Map m -> omap_has m k | _ -> false
|
|
7
|
+
open Corpus_runner
|
|
163
8
|
|
|
164
9
|
(* ---------------- test groups ---------------- *)
|
|
165
10
|
|
|
@@ -12,6 +12,7 @@ test:
|
|
|
12
12
|
scala-cli run . --main-class SdkTestMain
|
|
13
13
|
scala-cli run . --main-class SdkEntityTestMain
|
|
14
14
|
scala-cli run . --main-class Runner -- ../.sdk/test/test.json
|
|
15
|
+
scala-cli run . --main-class PrimaryCorpusMain -- ../.sdk/test/test.json
|
|
15
16
|
|
|
16
17
|
corpus:
|
|
17
18
|
scala-cli run . --main-class Runner -- ../.sdk/test/test.json
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// Primary-utility corpus driver.
|
|
2
|
+
//
|
|
3
|
+
// Drives every `primary` section of the shared corpus (../.sdk/test/test.json)
|
|
4
|
+
// through this SDK's utilities, the way the ts reference harness does. The
|
|
5
|
+
// match/args/err machinery is reused from Runner (StructCorpus.scala); what
|
|
6
|
+
// this file adds is the bridge between the corpus Value ADT and the SDK's
|
|
7
|
+
// Java-collection world, plus a live Context built from each corpus entry.
|
|
8
|
+
|
|
9
|
+
import voxgig.struct.{clone as sclone, *}
|
|
10
|
+
import SCALAPACKAGE.core.*
|
|
11
|
+
|
|
12
|
+
import java.util.{ArrayList, LinkedHashMap, List as JList, Map as JMap}
|
|
13
|
+
import scala.collection.mutable.{ArrayBuffer, LinkedHashMap as MLinkedHashMap}
|
|
14
|
+
import scala.jdk.CollectionConverters.*
|
|
15
|
+
|
|
16
|
+
object PrimaryCorpus {
|
|
17
|
+
|
|
18
|
+
// ---- Value <-> Java bridge ---------------------------------------------
|
|
19
|
+
|
|
20
|
+
def toJava(v: Value): Object = v match {
|
|
21
|
+
case VMap(m) =>
|
|
22
|
+
val out = new LinkedHashMap[String, Object]()
|
|
23
|
+
m.foreach { case (k, x) => out.put(k, toJava(x)) }
|
|
24
|
+
out
|
|
25
|
+
case VList(b) =>
|
|
26
|
+
val out = new ArrayList[Object]()
|
|
27
|
+
b.foreach { x => out.add(toJava(x)) }
|
|
28
|
+
out
|
|
29
|
+
case VStr(s) => s
|
|
30
|
+
case VBool(b) => java.lang.Boolean.valueOf(b)
|
|
31
|
+
case VNum(n) =>
|
|
32
|
+
// The corpus writes whole numbers as JSON integers; handing the SDK a
|
|
33
|
+
// Double turned "1" into "1.0" everywhere it reached a URL or header.
|
|
34
|
+
if (n == Math.floor(n) && !n.isInfinite) java.lang.Long.valueOf(n.toLong)
|
|
35
|
+
else java.lang.Double.valueOf(n)
|
|
36
|
+
case _ => null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def toValue(o: Object): Value = o match {
|
|
40
|
+
case null => VNull
|
|
41
|
+
// Struct.UNDEF is the library's "no value" sentinel, not a value: an
|
|
42
|
+
// unresolved transform path returns it, and without this it reached the
|
|
43
|
+
// corpus as the string "java.lang.Object@..." instead of null.
|
|
44
|
+
case x if x eq SCALAPACKAGE.utility.struct.Struct.UNDEF => VNull
|
|
45
|
+
case m: JMap[_, _] =>
|
|
46
|
+
val out = MLinkedHashMap.empty[String, Value]
|
|
47
|
+
m.asInstanceOf[JMap[String, Object]].asScala.foreach { case (k, x) => out.put(k, toValue(x)) }
|
|
48
|
+
VMap(out)
|
|
49
|
+
case l: JList[_] =>
|
|
50
|
+
val out = ArrayBuffer.empty[Value]
|
|
51
|
+
l.asInstanceOf[JList[Object]].asScala.foreach { x => out += toValue(x) }
|
|
52
|
+
VList(out)
|
|
53
|
+
case s: String => VStr(s)
|
|
54
|
+
case b: java.lang.Boolean => VBool(b.booleanValue())
|
|
55
|
+
case n: java.lang.Number => VNum(n.doubleValue())
|
|
56
|
+
case e: RuntimeException =>
|
|
57
|
+
val m = MLinkedHashMap.empty[String, Value]
|
|
58
|
+
m.put("message", VStr(String.valueOf(e.getMessage)))
|
|
59
|
+
VMap(m)
|
|
60
|
+
case x => VStr(String.valueOf(x))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private def jmap(v: Value): JMap[String, Object] = toJava(v) match {
|
|
64
|
+
case m: JMap[_, _] => m.asInstanceOf[JMap[String, Object]]
|
|
65
|
+
case _ => new LinkedHashMap[String, Object]()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private def vget(v: Value, k: String): Value = v match {
|
|
69
|
+
case VMap(m) => m.getOrElse(k, Noval)
|
|
70
|
+
case _ => Noval
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---- corpus access -----------------------------------------------------
|
|
74
|
+
|
|
75
|
+
private var CORPUS: Value = Noval
|
|
76
|
+
|
|
77
|
+
private def sectionBasic(name: String): Value = vget(vget(vget(CORPUS, "primary"), name), "basic")
|
|
78
|
+
|
|
79
|
+
// makeSpec and prepareAuth read defaults off the CLIENT, as the ts reference
|
|
80
|
+
// does via client.options(), so a section's DEF.setup cannot reach them
|
|
81
|
+
// through ctx.options — those sections get their own client.
|
|
82
|
+
private def sectionSetup(name: String): Value =
|
|
83
|
+
vget(vget(vget(vget(vget(CORPUS, "primary"), name), "DEF"), "setup"), "a")
|
|
84
|
+
|
|
85
|
+
// ---- live context from a corpus map ------------------------------------
|
|
86
|
+
|
|
87
|
+
private def corpusCtx(client: ProjectNameSDK, ctxmap: Value): Context = {
|
|
88
|
+
val utility = client.getUtility()
|
|
89
|
+
val cm = new LinkedHashMap[String, Object]()
|
|
90
|
+
// Only when the corpus names one: defaulting to "load" made the SDK report
|
|
91
|
+
// the wrong operation in the error messages the corpus matches on.
|
|
92
|
+
vget(ctxmap, "opname") match {
|
|
93
|
+
case VStr(s) => cm.put("opname", s)
|
|
94
|
+
case _ =>
|
|
95
|
+
}
|
|
96
|
+
val ctx = utility.makeContext(cm, client.getRootCtx())
|
|
97
|
+
|
|
98
|
+
vget(ctxmap, "spec") match {
|
|
99
|
+
case m @ VMap(_) => ctx.spec = new Spec(jmap(m))
|
|
100
|
+
case _ =>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
vget(ctxmap, "result") match {
|
|
104
|
+
case m @ VMap(_) =>
|
|
105
|
+
val r = new Result(jmap(m))
|
|
106
|
+
// Result does not carry an err in from the map, so a corpus result
|
|
107
|
+
// holding one arrived empty and resultBasic had no previous message
|
|
108
|
+
// to prepend.
|
|
109
|
+
vget(vget(m, "err"), "message") match {
|
|
110
|
+
case VStr(msg) if msg.nonEmpty => r.err = new SdkError("", msg, null)
|
|
111
|
+
case _ =>
|
|
112
|
+
}
|
|
113
|
+
ctx.result = r
|
|
114
|
+
case _ =>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
vget(ctxmap, "response") match {
|
|
118
|
+
case m @ VMap(_) =>
|
|
119
|
+
val resp = new Response(jmap(m))
|
|
120
|
+
// resultBody reads response.json and requires it to be CALLABLE; the
|
|
121
|
+
// corpus supplies a plain `body`.
|
|
122
|
+
vget(m, "body") match {
|
|
123
|
+
case Noval => ()
|
|
124
|
+
case b =>
|
|
125
|
+
val bj = toJava(b)
|
|
126
|
+
resp.body = bj
|
|
127
|
+
resp.jsonFunc = new java.util.function.Supplier[Object] { def get(): Object = bj }
|
|
128
|
+
}
|
|
129
|
+
// Header names arrive from the wire in any case and the contract is
|
|
130
|
+
// lowercase; resultHeaders copies them verbatim.
|
|
131
|
+
vget(m, "headers") match {
|
|
132
|
+
case VMap(hm) =>
|
|
133
|
+
val low = new LinkedHashMap[String, Object]()
|
|
134
|
+
hm.foreach { case (k, x) => low.put(k.toLowerCase, toJava(x)) }
|
|
135
|
+
resp.headers = low
|
|
136
|
+
case _ =>
|
|
137
|
+
}
|
|
138
|
+
ctx.response = resp
|
|
139
|
+
case _ =>
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
vget(ctxmap, "point") match { case m @ VMap(_) => ctx.point = jmap(m); case _ => }
|
|
143
|
+
vget(ctxmap, "reqdata") match { case Noval => (); case v => ctx.reqdata = jmap(v) }
|
|
144
|
+
vget(ctxmap, "reqmatch") match { case Noval => (); case v => ctx.reqmatch = jmap(v) }
|
|
145
|
+
vget(ctxmap, "data") match { case Noval => (); case v => ctx.data = jmap(v) }
|
|
146
|
+
vget(ctxmap, "match") match { case Noval => (); case v => ctx.matchData = jmap(v) }
|
|
147
|
+
vget(ctxmap, "options") match { case m @ VMap(_) => ctx.options = jmap(m); case _ => }
|
|
148
|
+
vget(ctxmap, "config") match { case m @ VMap(_) => ctx.config = jmap(m); case _ => }
|
|
149
|
+
ctx
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// The match reads the corpus map while the utilities mutate the live objects
|
|
153
|
+
// hanging off the context, so without this every ctx.* assertion reads null.
|
|
154
|
+
private def publishCtx(ctxmap: Value, ctx: Context): Unit = ctxmap match {
|
|
155
|
+
case VMap(m) =>
|
|
156
|
+
if (ctx.spec != null) m.put("spec", specValue(ctx.spec))
|
|
157
|
+
if (ctx.result != null) m.put("result", resultValue(ctx.result))
|
|
158
|
+
if (ctx.response != null) m.put("response", VStr(Runner.EXISTSMARK))
|
|
159
|
+
case _ =>
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private def specValue(s: Spec): Value = {
|
|
163
|
+
val m = MLinkedHashMap.empty[String, Value]
|
|
164
|
+
m.put("base", VStr(s.base)); m.put("prefix", VStr(s.prefix)); m.put("suffix", VStr(s.suffix))
|
|
165
|
+
m.put("path", VStr(s.path)); m.put("url", VStr(s.url)); m.put("step", VStr(s.step))
|
|
166
|
+
m.put("method", VStr(s.method))
|
|
167
|
+
m.put("headers", toValue(s.headers)); m.put("params", toValue(s.params))
|
|
168
|
+
m.put("query", toValue(s.query)); m.put("alias", toValue(s.alias))
|
|
169
|
+
if (s.parts != null) m.put("parts", toValue(s.parts))
|
|
170
|
+
if (s.body != null) m.put("body", toValue(s.body))
|
|
171
|
+
VMap(m)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private def resultValue(r: Result): Value = {
|
|
175
|
+
val m = MLinkedHashMap.empty[String, Value]
|
|
176
|
+
m.put("ok", VBool(r.ok)); m.put("status", VNum(r.status.toDouble))
|
|
177
|
+
m.put("statusText", VStr(r.statusText))
|
|
178
|
+
m.put("headers", toValue(r.headers))
|
|
179
|
+
if (r.body != null) m.put("body", toValue(r.body))
|
|
180
|
+
if (r.resdata != null) m.put("resdata", toValue(r.resdata))
|
|
181
|
+
if (r.resmatch != null) m.put("resmatch", toValue(r.resmatch))
|
|
182
|
+
if (r.err != null) {
|
|
183
|
+
val e = MLinkedHashMap.empty[String, Value]
|
|
184
|
+
e.put("message", VStr(String.valueOf(r.err.getMessage)))
|
|
185
|
+
m.put("err", VMap(e))
|
|
186
|
+
}
|
|
187
|
+
VMap(m)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---- section runners ---------------------------------------------------
|
|
191
|
+
|
|
192
|
+
private def argAt(args: Seq[Value], i: Int): Value = if (i < args.length) args(i) else Noval
|
|
193
|
+
|
|
194
|
+
// A section driven with a ctx built from the corpus entry.
|
|
195
|
+
private def runset(name: String, client: ProjectNameSDK)(f: (Context, Seq[Value]) => Value): Unit =
|
|
196
|
+
Runner.runSet(name, sectionBasic(name), (args: Seq[Value]) => {
|
|
197
|
+
val ctxmap = argAt(args, 0)
|
|
198
|
+
val ctx = corpusCtx(client, ctxmap)
|
|
199
|
+
val out = f(ctx, args)
|
|
200
|
+
publishCtx(ctxmap, ctx)
|
|
201
|
+
out
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// A section that takes a bare map or explicit args rather than a ctx.
|
|
205
|
+
private def runsetArgs(name: String)(f: Seq[Value] => Value): Unit =
|
|
206
|
+
Runner.runSet(name, sectionBasic(name), f)
|
|
207
|
+
|
|
208
|
+
private def clientFor(name: String, shared: ProjectNameSDK): ProjectNameSDK =
|
|
209
|
+
sectionSetup(name) match {
|
|
210
|
+
case m @ VMap(_) => ProjectNameSDK.testSDK(null, jmap(m))
|
|
211
|
+
case _ => shared
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
def run(testfile: String): Int = {
|
|
215
|
+
val src = new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(testfile)), "UTF-8")
|
|
216
|
+
CORPUS = Runner.jsonRead(src)
|
|
217
|
+
|
|
218
|
+
val sdk = ProjectNameSDK.testSDK()
|
|
219
|
+
val u = sdk.getUtility()
|
|
220
|
+
|
|
221
|
+
runset("done", sdk) { (c, _) => toValue(u.done(c)) }
|
|
222
|
+
runset("makeUrl", sdk) { (c, _) => toValue(u.makeUrl(c)) }
|
|
223
|
+
runset("makeRequest", sdk) { (c, _) => u.makeRequest(c); resultValue(c.result) }
|
|
224
|
+
runset("makeResponse", sdk) { (c, _) => u.makeResponse(c); resultValue(c.result) }
|
|
225
|
+
runset("makeSpec", clientFor("makeSpec", sdk)) { (c, _) =>
|
|
226
|
+
val s = u.makeSpec(c); if (s != null) c.spec = s; specValue(c.spec)
|
|
227
|
+
}
|
|
228
|
+
runset("prepareAuth", clientFor("prepareAuth", sdk)) { (c, _) =>
|
|
229
|
+
u.prepareAuth(c); specValue(c.spec)
|
|
230
|
+
}
|
|
231
|
+
runset("prepareBody", sdk) { (c, _) => toValue(u.prepareBody(c)) }
|
|
232
|
+
runset("prepareHeaders", sdk) { (c, _) => toValue(u.prepareHeaders(c)) }
|
|
233
|
+
runset("prepareMethod", sdk) { (c, _) =>
|
|
234
|
+
val m = u.prepareMethod(c); if (m == null || m.isEmpty) VNull else VStr(m)
|
|
235
|
+
}
|
|
236
|
+
runset("prepareParams", sdk) { (c, _) => toValue(u.prepareParams(c)) }
|
|
237
|
+
runset("preparePath", sdk) { (c, _) => toValue(u.preparePath(c)) }
|
|
238
|
+
runset("prepareQuery", sdk) { (c, _) => toValue(u.prepareQuery(c)) }
|
|
239
|
+
runset("resultBasic", sdk) { (c, _) =>
|
|
240
|
+
val r = u.resultBasic(c); if (r != null) c.result = r; resultValue(c.result)
|
|
241
|
+
}
|
|
242
|
+
runset("resultBody", sdk) { (c, _) =>
|
|
243
|
+
val r = u.resultBody(c); if (r != null) c.result = r; resultValue(c.result)
|
|
244
|
+
}
|
|
245
|
+
runset("resultHeaders", sdk) { (c, _) =>
|
|
246
|
+
val r = u.resultHeaders(c); if (r != null) c.result = r; resultValue(c.result)
|
|
247
|
+
}
|
|
248
|
+
runset("transformRequest", sdk) { (c, _) => toValue(u.transformRequest(c)) }
|
|
249
|
+
runset("transformResponse", sdk) { (c, _) => toValue(u.transformResponse(c)) }
|
|
250
|
+
runset("param", sdk) { (c, args) => toValue(u.param(c, toJava(argAt(args, 1)))) }
|
|
251
|
+
runset("makeError", sdk) { (c, args) =>
|
|
252
|
+
val msg = vget(argAt(args, 1), "message") match { case VStr(s) => s; case _ => "" }
|
|
253
|
+
val in = if (msg.isEmpty) null else new SdkError("", msg, null)
|
|
254
|
+
toValue(u.makeError(c, in))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
runsetArgs("makeContext") { args =>
|
|
258
|
+
val c = corpusCtx(sdk, argAt(args, 0))
|
|
259
|
+
val op = MLinkedHashMap.empty[String, Value]
|
|
260
|
+
if (c.op != null) {
|
|
261
|
+
op.put("entity", VStr(c.op.entity)); op.put("name", VStr(c.op.name))
|
|
262
|
+
op.put("input", VStr(c.op.input)); op.put("points", toValue(c.op.points))
|
|
263
|
+
}
|
|
264
|
+
val out = MLinkedHashMap.empty[String, Value]
|
|
265
|
+
out.put("op", VMap(op))
|
|
266
|
+
VMap(out)
|
|
267
|
+
}
|
|
268
|
+
runsetArgs("makeOptions") { args =>
|
|
269
|
+
val in = argAt(args, 0)
|
|
270
|
+
val c = corpusCtx(sdk, VMap(MLinkedHashMap.empty[String, Value]))
|
|
271
|
+
vget(in, "config") match { case Noval => (); case v => c.config = jmap(v) }
|
|
272
|
+
vget(in, "options") match { case Noval => (); case v => c.options = jmap(v) }
|
|
273
|
+
toValue(u.makeOptions(c))
|
|
274
|
+
}
|
|
275
|
+
runsetArgs("operator") { args =>
|
|
276
|
+
val in = argAt(args, 0)
|
|
277
|
+
val m = MLinkedHashMap.empty[String, Value]
|
|
278
|
+
m.put("entity", vget(in, "entity") match { case Noval => VStr("_"); case v => v })
|
|
279
|
+
m.put("input", vget(in, "input") match { case Noval => VStr("_"); case v => v })
|
|
280
|
+
m.put("name", vget(in, "name") match { case Noval => VStr("_"); case v => v })
|
|
281
|
+
m.put("points", vget(in, "points") match { case l @ VList(_) => l; case _ => VList(ArrayBuffer.empty[Value]) })
|
|
282
|
+
VMap(m)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
Runner.reportPrimary()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
object PrimaryCorpusMain {
|
|
290
|
+
def main(args: Array[String]): Unit = {
|
|
291
|
+
val testfile = if (args.length > 0) args(0) else "../.sdk/test/test.json"
|
|
292
|
+
System.exit(PrimaryCorpus.run(testfile))
|
|
293
|
+
}
|
|
294
|
+
}
|
|
Binary file
|
|
@@ -199,13 +199,60 @@ object MakePoint {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
object MakeOptions {
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Replaces one utility member from `options.utility`, matching the ts
|
|
205
|
+
* reference: a key naming a real member REPLACES it, and any other key is
|
|
206
|
+
* attached as a custom extra. Returns false when the key names no member or
|
|
207
|
+
* the value is not that member's type, so the caller keeps it in `custom`.
|
|
208
|
+
*
|
|
209
|
+
* REFLECTION, NOT A KEYED SWITCH. The go and java ports list every member by
|
|
210
|
+
* hand and carry a "keep this in step with registerAll" warning, because a
|
|
211
|
+
* utility added to one list and not the other is overridable there and not
|
|
212
|
+
* here. The field set is readable off the class, so the list cannot drift.
|
|
213
|
+
*
|
|
214
|
+
* Scala `var` fields compile to a private field plus accessors, so the
|
|
215
|
+
* lookup is on the declared field and needs setAccessible. Function types
|
|
216
|
+
* erase on the JVM, so isInstance checks the FunctionN arity rather than the
|
|
217
|
+
* full signature - the same limit java's port documents.
|
|
218
|
+
*
|
|
219
|
+
* Only a PUBLIC name may replace a member: public utility names are
|
|
220
|
+
* camelCase and carry no underscore, so an underscore means the caller named
|
|
221
|
+
* something of their own rather than a member.
|
|
222
|
+
*/
|
|
223
|
+
def overrideUtil(utility: Utility, key: String, value: Object): Boolean = {
|
|
224
|
+
if (null == key || key.isEmpty || key.contains("_") || "custom" == key) return false
|
|
225
|
+
if (null == value) return false
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
val field = classOf[Utility].getDeclaredField(key)
|
|
229
|
+
if (!field.getType.isInstance(value)) return false
|
|
230
|
+
field.setAccessible(true)
|
|
231
|
+
field.set(utility, value)
|
|
232
|
+
true
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
case _: NoSuchFieldException => false
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
202
239
|
def makeOptions(ctx: Context): JMap[String, Object] = {
|
|
203
240
|
var options = ctx.options
|
|
204
241
|
if (options == null) options = new LinkedHashMap[String, Object]()
|
|
205
242
|
|
|
206
|
-
// Merge
|
|
243
|
+
// Merge utility overrides from options onto the utility object.
|
|
244
|
+
//
|
|
245
|
+
// A key naming a real utility member REPLACES it; anything else is
|
|
246
|
+
// attached as a custom extra. Shelving everything in `custom` - a map
|
|
247
|
+
// nothing reads - made `utility -> Map("fetcher" -> ...)`, the documented
|
|
248
|
+
// transport seam, a silent no-op here while ts honoured it.
|
|
207
249
|
val customUtils = Helpers.toMapAny(options.get("utility"))
|
|
208
|
-
if (customUtils != null && ctx.utility != null)
|
|
250
|
+
if (customUtils != null && ctx.utility != null) {
|
|
251
|
+
customUtils.entrySet().forEach { e =>
|
|
252
|
+
if (!overrideUtil(ctx.utility, e.getKey, e.getValue))
|
|
253
|
+
ctx.utility.custom.put(e.getKey, e.getValue)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
209
256
|
|
|
210
257
|
var opts = Struct.clone(options).asInstanceOf[JMap[String, Object]]
|
|
211
258
|
|
|
@@ -26,7 +26,9 @@ object PrepareMethod {
|
|
|
26
26
|
case "list" => "GET"
|
|
27
27
|
case "remove" => "DELETE"
|
|
28
28
|
case "patch" => "PATCH"
|
|
29
|
-
|
|
29
|
+
// No GET catch-all: an unrecognised op must fall through to the
|
|
30
|
+
// allow.method gate, not be silently issued as a GET.
|
|
31
|
+
case _ => ""
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -57,12 +57,30 @@ private func buildOptSpec() -> Value {
|
|
|
57
57
|
func makeOptionsUtil(_ ctx: Context) -> VMap {
|
|
58
58
|
let options = ctx.options ?? VMap()
|
|
59
59
|
|
|
60
|
-
// Merge
|
|
61
|
-
// original options before clone for safety.
|
|
60
|
+
// Merge utility overrides from options onto the utility object. Read from
|
|
61
|
+
// the original options before clone for safety.
|
|
62
|
+
//
|
|
63
|
+
// `fetcher` REPLACES the member; every other key is attached as a custom
|
|
64
|
+
// extra. Shelving the fetcher too made `utility: ["fetcher": ...]`, the
|
|
65
|
+
// documented transport seam, a silent no-op here while ts honoured it - and
|
|
66
|
+
// it is the seam the shared feature corpus scripts.
|
|
67
|
+
//
|
|
68
|
+
// PARTIAL, DELIBERATELY. The other members are replaceable in principle -
|
|
69
|
+
// Value.native carries any Swift value - but each has a distinct closure
|
|
70
|
+
// type, so honouring them means one `as?` arm per member with the signature
|
|
71
|
+
// written exactly. `fetcher` is the documented seam and the one the corpus
|
|
72
|
+
// needs; the rest stay in `custom` until a swift toolchain can compile the
|
|
73
|
+
// arms rather than have them written blind. See AGENTS.md.
|
|
62
74
|
if let customUtils = gp(options, "utility").asMap {
|
|
63
75
|
if let utility = ctx.utility {
|
|
64
76
|
for (k, v) in customUtils.entries {
|
|
65
|
-
|
|
77
|
+
let native = v.asNative ?? v
|
|
78
|
+
if "fetcher" == k, let fn = native as? FetcherFunc {
|
|
79
|
+
utility.fetcher = fn
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
utility.custom[k] = native
|
|
83
|
+
}
|
|
66
84
|
}
|
|
67
85
|
}
|
|
68
86
|
}
|
|
@@ -30,8 +30,19 @@ function ownIdField(config: any, getpath: any, entityName: string): string {
|
|
|
30
30
|
if (ptterm !== bestterm ? ptterm : pt.parts.length < best.parts.length) best = pt
|
|
31
31
|
}
|
|
32
32
|
const parts: string[] = (best && best.parts) || []
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// THE LAST PART, not the last param anywhere in the path. A record route
|
|
34
|
+
// ENDS in its key: /orgs/{org}/private-registries/{secret_name} does,
|
|
35
|
+
// /orgs/{org}/private-registries/public-key does not. Reading the last
|
|
36
|
+
// param wherever it fell returned `{org}` for that second path — a PARENT
|
|
37
|
+
// reference — and the seeding walk then stamped the record's own key over
|
|
38
|
+
// org_id, destroying the ORG01 the fixture set and the test looks up from
|
|
39
|
+
// idmap. github's private_registry failed its update with a 404 that named
|
|
40
|
+
// nothing to do with orgs.
|
|
41
|
+
//
|
|
42
|
+
// A point that does not end in a param says nothing about this entity's
|
|
43
|
+
// key, so move on to the next op rather than guess from it.
|
|
44
|
+
const lastPart = 0 < parts.length ? String(parts[parts.length - 1]) : ''
|
|
45
|
+
if (lastPart.startsWith('{')) return lastPart.slice(1, -1)
|
|
35
46
|
}
|
|
36
47
|
return 'id'
|
|
37
48
|
}
|
|
@@ -1019,7 +1019,14 @@ pub fn prepare_method_util(ctx: *Context) []const u8 {
|
|
|
1019
1019
|
if (std.mem.eql(u8, opname, "list")) return "GET";
|
|
1020
1020
|
if (std.mem.eql(u8, opname, "remove")) return "DELETE";
|
|
1021
1021
|
if (std.mem.eql(u8, opname, "patch")) return "PATCH";
|
|
1022
|
-
|
|
1022
|
+
|
|
1023
|
+
// NO CATCH-ALL GET. The ts reference returns methodMap[key], which is
|
|
1024
|
+
// undefined for an op the map does not name, so the request is refused
|
|
1025
|
+
// rather than issued. Returning "GET" here made every unrecognised op a
|
|
1026
|
+
// GET — a mistyped or unsupported op quietly fetched — and the method
|
|
1027
|
+
// feeds the allow.method gate, so it is not cosmetic. ocaml had the same
|
|
1028
|
+
// fallback; the shared corpus caught both.
|
|
1029
|
+
return "";
|
|
1023
1030
|
}
|
|
1024
1031
|
|
|
1025
1032
|
pub fn prepare_headers_util(ctx: *Context) Value {
|