@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.
Files changed (33) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/package.json +1 -1
  3. package/project/.sdk/src/cmp/zig/Config_zig.ts +1 -1
  4. package/project/.sdk/tm/c/tests/primary_corpus_test.c +565 -0
  5. package/project/.sdk/tm/c/utility/prepare_method.c +3 -1
  6. package/project/.sdk/tm/c/utility/transform_request.c +4 -3
  7. package/project/.sdk/tm/clojure/src/sdk/core.clj +55 -5
  8. package/project/.sdk/tm/clojure/test/sdk/test/primary.clj +171 -1
  9. package/project/.sdk/tm/clojure/test/sdk/test/struct_corpus.clj +13 -2
  10. package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +2 -0
  11. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +104 -0
  12. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +82 -2
  13. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +50 -2
  14. package/project/.sdk/tm/elixir/test/primary_utility_test.exs +18 -200
  15. package/project/.sdk/tm/elixir/test/support/struct_corpus.ex +413 -2
  16. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +56 -2
  17. package/project/.sdk/tm/lua/utility/make_options.lua +22 -2
  18. package/project/.sdk/tm/ocaml/Makefile +15 -4
  19. package/project/.sdk/tm/ocaml/sdk_runtime.ml +8 -1
  20. package/project/.sdk/tm/ocaml/test/corpus_runner.ml +185 -0
  21. package/project/.sdk/tm/ocaml/test/primary_utility_test.ml +238 -0
  22. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +5 -160
  23. package/project/.sdk/tm/scala/Makefile +1 -0
  24. package/project/.sdk/tm/scala/sdktest/PrimaryCorpus.scala +294 -0
  25. package/project/.sdk/tm/scala/sdktest/StructCorpus.scala +0 -0
  26. package/project/.sdk/tm/scala/utility/Make.scala +49 -2
  27. package/project/.sdk/tm/scala/utility/Prepare.scala +3 -1
  28. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -3
  29. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +13 -2
  30. package/project/.sdk/tm/zig/core/utility.zig +8 -1
  31. package/project/.sdk/tm/zig/test/primary_utility_test.zig +445 -0
  32. package/project/.sdk/tm/zig/test/struct_runner.zig +238 -0
  33. package/project/sdkgen-package.json +1 -1
@@ -1,165 +1,10 @@
1
- (* Test runner for the shared JSON corpus (build/test/test.json).
2
- * Self-contained: an in-tree JSON reader builds the library's `value` type
3
- * directly, so the OCaml port is exercised exactly as in production. *)
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
+ }
@@ -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 custom utility overrides onto the utility object.
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) ctx.utility.custom.putAll(customUtils)
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
- case _ => "GET"
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 custom utility overrides onto the utility object. Read from the
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
- utility.custom[k] = v.asNative ?? v
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
- const last = [...parts].reverse().find((p: string) => p.startsWith('{'))
34
- if (null != last) return last.slice(1, -1)
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
- return "GET";
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 {