@voxgig/sdkgen 4.3.0 → 4.4.1

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 (46) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/tsconfig.tsbuildinfo +1 -1
  3. package/package.json +1 -1
  4. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +13 -1
  5. package/project/.sdk/src/cmp/zig/Config_zig.ts +1 -1
  6. package/project/.sdk/tm/c/tests/primary_corpus_test.c +565 -0
  7. package/project/.sdk/tm/c/utility/prepare_method.c +3 -1
  8. package/project/.sdk/tm/c/utility/transform_request.c +4 -3
  9. package/project/.sdk/tm/clojure/src/sdk/core.clj +55 -5
  10. package/project/.sdk/tm/clojure/test/sdk/test/primary.clj +171 -1
  11. package/project/.sdk/tm/clojure/test/sdk/test/struct_corpus.clj +13 -2
  12. package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +2 -0
  13. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +104 -0
  14. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +82 -2
  15. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +50 -2
  16. package/project/.sdk/tm/elixir/test/primary_utility_test.exs +18 -200
  17. package/project/.sdk/tm/elixir/test/support/struct_corpus.ex +413 -2
  18. package/project/.sdk/tm/go/test/feature_corpus_test.go +18 -0
  19. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +8 -0
  20. package/project/.sdk/tm/js/test/feature/Corpus.test.js +6 -1
  21. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +56 -2
  22. package/project/.sdk/tm/lua/utility/make_options.lua +22 -2
  23. package/project/.sdk/tm/ocaml/Makefile +15 -4
  24. package/project/.sdk/tm/ocaml/sdk_features.ml +224 -0
  25. package/project/.sdk/tm/ocaml/sdk_runtime.ml +8 -1
  26. package/project/.sdk/tm/ocaml/test/corpus_runner.ml +185 -0
  27. package/project/.sdk/tm/ocaml/test/primary_utility_test.ml +238 -0
  28. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +5 -160
  29. package/project/.sdk/tm/perl/t/feature_corpus.t +9 -1
  30. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +11 -0
  31. package/project/.sdk/tm/py/test/feature_harness.py +6 -0
  32. package/project/.sdk/tm/py/test/test_feature_corpus.py +6 -0
  33. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +6 -1
  34. package/project/.sdk/tm/rust/feature/cost.rs +25 -1
  35. package/project/.sdk/tm/scala/Makefile +1 -0
  36. package/project/.sdk/tm/scala/sdktest/PrimaryCorpus.scala +294 -0
  37. package/project/.sdk/tm/scala/sdktest/StructCorpus.scala +0 -0
  38. package/project/.sdk/tm/scala/utility/Make.scala +49 -2
  39. package/project/.sdk/tm/scala/utility/Prepare.scala +3 -1
  40. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -3
  41. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +13 -2
  42. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +12 -1
  43. package/project/.sdk/tm/zig/core/utility.zig +8 -1
  44. package/project/.sdk/tm/zig/test/primary_utility_test.zig +445 -0
  45. package/project/.sdk/tm/zig/test/struct_runner.zig +238 -0
  46. package/project/sdkgen-package.json +1 -1
@@ -1,211 +1,29 @@
1
1
  # ProjectName SDK primary-utility test
2
2
  #
3
- # Directly exercises the request-shaping utilities (make_url, param,
4
- # prepare_*, make_options) through the client's utility object. API-agnostic.
3
+ # Drives the SHARED language-neutral corpus (.sdk/test/test.json -> "primary")
4
+ # through this SDK's request-shaping utilities, so the cases cannot drift from
5
+ # the reference implementation. Each section is looked up with getSpec and
6
+ # executed with runset, as the ts/js reference harness does.
7
+ #
8
+ # This suite used to MIRROR the corpus by hand: it asserted a handful of
9
+ # behaviours it had transcribed, so a corpus case that changed — or one that
10
+ # was added — went unnoticed here. Driving the corpus is what makes elixir a
11
+ # FULL parity target rather than a mirrored one.
5
12
 
6
13
  defmodule ProjectName.PrimaryUtilityTest do
7
14
  use ExUnit.Case
8
15
 
9
- alias Voxgig.Struct, as: S
10
- alias ProjectName.Helpers, as: H
11
- alias ProjectName.{Context, Utility, Spec, Result}
12
-
13
- defp client, do: ProjectName.test()
14
-
15
- defp ctx(client, opname \\ "load") do
16
- Context.new(S.jm(["opname", opname]), ProjectName.get_root_ctx(client))
17
- end
18
-
19
- test "make_url substitutes path params and appends query" do
20
- c = client()
21
- ctx = ctx(c)
22
-
23
- S.setprop(ctx, "spec",
24
- Spec.new(H.deep(%{
25
- "base" => "http://h",
26
- "path" => "planet/{id}",
27
- "params" => %{"id" => "P1"},
28
- "query" => %{"q" => "x"}
29
- })))
30
-
31
- S.setprop(ctx, "result", Result.new(nil))
32
-
33
- {url, err} = Utility.make_url(ctx)
34
- assert err == nil
35
- assert String.contains?(url, "planet/P1")
36
- assert String.contains?(url, "q=x")
37
- assert S.getprop(S.getprop(ctx, "result"), "resmatch") != nil
38
- end
39
-
40
- test "param resolves reqmatch over match, then data" do
41
- c = client()
42
- ctx = ctx(c)
43
- S.setprop(ctx, "reqmatch", H.deep(%{"id" => "R1"}))
44
- S.setprop(ctx, "match", H.deep(%{"id" => "M1", "k" => "mk"}))
45
- S.setprop(ctx, "reqdata", H.deep(%{"d" => "rd"}))
46
- S.setprop(ctx, "data", H.deep(%{"d" => "dd", "only" => "od"}))
47
-
48
- assert Utility.param(ctx, "id") == "R1"
49
- assert Utility.param(ctx, "k") == "mk"
50
- assert Utility.param(ctx, "d") == "rd"
51
- assert Utility.param(ctx, "only") == "od"
52
- assert Utility.param(ctx, "missing") == nil
53
- end
54
-
55
- test "prepare_method maps op names to HTTP verbs" do
56
- c = client()
57
- assert Utility.prepare_method(ctx(c, "create")) == "POST"
58
- assert Utility.prepare_method(ctx(c, "update")) == "PUT"
59
- assert Utility.prepare_method(ctx(c, "load")) == "GET"
60
- assert Utility.prepare_method(ctx(c, "list")) == "GET"
61
- assert Utility.prepare_method(ctx(c, "remove")) == "DELETE"
62
- end
63
-
64
- test "prepare_query keeps reqmatch keys not declared as point params" do
65
- c = client()
66
- ctx = ctx(c)
67
- S.setprop(ctx, "point", H.deep(%{"params" => ["id"]}))
68
- S.setprop(ctx, "reqmatch", H.deep(%{"id" => "x", "extra" => "y"}))
69
- q = Utility.prepare_query(ctx)
70
- assert S.getprop(q, "extra") == "y"
71
- assert S.getprop(q, "id") == nil
72
- end
73
-
74
- test "prepare_params resolves declared params" do
75
- c = client()
76
- ctx = ctx(c)
77
- S.setprop(ctx, "point", H.deep(%{"args" => %{"params" => [%{"name" => "id"}]}}))
78
- S.setprop(ctx, "reqmatch", H.deep(%{"id" => "P9"}))
79
- p = Utility.prepare_params(ctx)
80
- assert S.getprop(p, "id") == "P9"
81
- end
82
-
83
- test "prepare_auth sets an authorization header when an apikey is present" do
84
- c = ProjectName.new(H.deep(%{"apikey" => "secret"}))
85
- ctx = ctx(c)
86
- spec = Spec.new(H.deep(%{"headers" => %{}}))
87
- S.setprop(ctx, "spec", spec)
88
- {_spec, err} = Utility.prepare_auth(ctx)
89
- assert err == nil
90
- # The apikey must reach the authorization header; the exact value carries
91
- # the API's configured auth prefix (e.g. "Basic secret" for HTTP basic, or
92
- # just "secret" for an empty prefix), so assert on the apikey being present
93
- # rather than an exact string that varies per API.
94
- header = S.getprop(S.getprop(spec, "headers"), "authorization")
95
- assert header != nil
96
- assert String.contains?(header, "secret")
97
- end
98
-
99
- test "prepare_auth omits the header when no apikey" do
100
- c = ProjectName.new()
101
- ctx = ctx(c)
102
- spec = Spec.new(H.deep(%{"headers" => %{"authorization" => "stale"}}))
103
- S.setprop(ctx, "spec", spec)
104
- {_spec, err} = Utility.prepare_auth(ctx)
105
- assert err == nil
106
- assert S.getprop(S.getprop(spec, "headers"), "authorization") == nil
107
- end
108
-
109
- test "make_options derives a clean key regex" do
110
- c = ProjectName.test()
111
- opts = ProjectName.options_map(c)
112
- assert S.getpath(opts, "__derived__.clean.keyre") == "key|token|id"
113
- end
16
+ test "primary utilities pass the shared corpus" do
17
+ {pass, fail, failures} = ProjectName.StructCorpus.run_primary()
114
18
 
115
- # --- feature add-order ----------------------------------------------------
116
- # options.feature accepts an ordered LIST (developer add-order) or a map
117
- # (defaults test-first); make_options records the resolved order in
118
- # __derived__.featureorder. Driven with a synthetic ctx (empty config
119
- # options) so the assertions do not depend on this SDK's own features.
120
-
121
- defp resolve_order(feature) do
122
- ctx =
123
- S.jm([
124
- "utility", Utility.new(),
125
- "options", H.deep(%{"feature" => feature}),
126
- "config", H.deep(%{"options" => %{}})
127
- ])
128
-
129
- fo = S.getpath(Utility.make_options(ctx), "__derived__.featureorder")
130
- n = S.size(fo)
131
- order = if n == 0, do: [], else: Enum.map(0..(n - 1), fn i -> S.getelem(fo, i) end)
132
- Enum.join(order, ",")
133
- end
19
+ if fail > 0 do
20
+ IO.puts("\n" <> Enum.join(Enum.take(failures, 40), "\n"))
21
+ end
134
22
 
135
- test "feature order: map form is test-first" do
136
- assert "test,metrics" ==
137
- resolve_order(%{"metrics" => %{"active" => true}, "test" => %{"active" => true}})
138
- end
139
-
140
- test "feature order: an explicit list preserves its order" do
141
- assert "metrics,test" ==
142
- resolve_order([
143
- %{"name" => "metrics", "active" => true},
144
- %{"name" => "test", "active" => true}
145
- ])
146
- end
147
-
148
- # Station special case, mirroring test's: its transport wrap must sit
149
- # immediately outside the base transport, so map-form activation hoists
150
- # it to just after test - or first, when no test entry exists.
151
- test "feature order: map form hoists station after test" do
152
- assert "test,station,metrics" ==
153
- resolve_order(%{
154
- "metrics" => %{"active" => true},
155
- "station" => %{"active" => true},
156
- "test" => %{"active" => true}
157
- })
158
- end
159
-
160
- test "feature order: station first when no test entry exists" do
161
- assert "station,metrics" ==
162
- resolve_order(%{
163
- "metrics" => %{"active" => true},
164
- "station" => %{"active" => true}
165
- })
166
- end
167
-
168
- # The extend seam (feature INSTANCES supplied at construction - the
169
- # station adopt path): validate must accept the key verbatim, or the
170
- # constructor's extend loop reads a stripped option and the seam is dead.
171
- test "make_options preserves options.extend through validation" do
172
- marker = S.jm(["name", "station"])
173
-
174
- ctx =
175
- S.jm([
176
- "utility", Utility.new(),
177
- "options", S.jm(["extend", S.jt([marker])]),
178
- "config", H.deep(%{"options" => %{}})
179
- ])
180
-
181
- extend = S.getprop(Utility.make_options(ctx), "extend")
182
- assert S.islist(extend)
183
- assert S.size(extend) == 1
184
- assert S.getprop(S.getelem(extend, 0), "name") == "station"
185
- end
186
-
187
- test "make_error formats a namespaced message and raises by default" do
188
- c = client()
189
- ctx = ctx(c, "load")
190
-
191
- err =
192
- assert_raise ProjectName.Error, fn ->
193
- Utility.make_error(ctx, Context.make_error(ctx, "boom", "kaboom"))
194
- end
195
-
196
- assert String.contains?(err.msg, "load")
197
- assert String.contains?(err.msg, "kaboom")
198
- assert err.code == "boom"
199
- end
23
+ IO.puts("\nPRIMARY CORPUS: PASS #{pass} FAIL #{fail}")
200
24
 
201
- test "make_error returns bare resdata when throw_err is disabled" do
202
- c = client()
203
- ctx = ctx(c, "load")
204
- S.setprop(S.getprop(ctx, "ctrl"), "throw_err", false)
205
- result = Result.new(H.deep(%{"resdata" => %{"x" => 1}}))
206
- S.setprop(ctx, "result", result)
207
- out = Utility.make_error(ctx, Context.make_error(ctx, "boom", "kaboom"))
208
- assert S.ismap(out)
209
- assert S.getprop(out, "x") == 1
25
+ # A run that executes nothing is not a pass. The corpus is the point.
26
+ assert pass > 0, "the primary corpus executed no cases"
27
+ assert fail == 0
210
28
  end
211
29
  end
@@ -8,6 +8,7 @@
8
8
  defmodule ProjectName.StructCorpus do
9
9
  alias Voxgig.Struct, as: S
10
10
  alias Voxgig.Struct.Error, as: SE
11
+ alias ProjectName.Utility, as: U
11
12
 
12
13
  @nullmark "__NULL__"
13
14
  @undefmark "__UNDEF__"
@@ -263,7 +264,23 @@ defmodule ProjectName.StructCorpus do
263
264
  defp resolve_args(entry) do
264
265
  cond do
265
266
  ehas(entry, "ctx") -> [eget(entry, "ctx")]
266
- ehas(entry, "args") -> (a = eget(entry, "args")); if(S.islist(a), do: velems(a), else: [])
267
+ ehas(entry, "args") ->
268
+ a = eget(entry, "args")
269
+
270
+ if S.islist(a) do
271
+ args = velems(a)
272
+ # ts's resolveArgs writes the live first arg back as entry.ctx, so a
273
+ # `match: {ctx: ...}` resolves for args-style entries too. Without it
274
+ # every such assertion reads null and asserts nothing.
275
+ case args do
276
+ [first | _] -> if S.ismap(first), do: S.setprop(entry, "ctx", first)
277
+ _ -> :ok
278
+ end
279
+
280
+ args
281
+ else
282
+ []
283
+ end
267
284
  ehas(entry, "in") -> [S.clone(eget(entry, "in"))]
268
285
  true -> []
269
286
  end
@@ -301,7 +318,13 @@ defmodule ProjectName.StructCorpus do
301
318
  if ehas(entry, "match") do
302
319
  do_match(
303
320
  eget(entry, "match"),
304
- S.jm(["in", eget(entry, "in"), "out", eget(entry, "res"), "ctx", eget(entry, "ctx"), "err", msg])
321
+ # ts hands do_match the ERROR OBJECT, so a corpus
322
+ # `match: {err: {message: ...}}` resolves; a bare string leaves
323
+ # err.message reading null.
324
+ S.jm([
325
+ "in", eget(entry, "in"), "out", eget(entry, "res"),
326
+ "ctx", eget(entry, "ctx"), "err", S.jm(["message", msg])
327
+ ])
305
328
  )
306
329
  end
307
330
 
@@ -674,4 +697,392 @@ defmodule ProjectName.StructCorpus do
674
697
 
675
698
  {Process.get(:npass, 0), Process.get(:nfail, 0), Process.get(:failures, [])}
676
699
  end
700
+
701
+ # --- PRIMARY corpus ------------------------------------------------------
702
+ #
703
+ # Drives .sdk/test/test.json -> "primary", the same language-neutral corpus
704
+ # the ts/js/lua/lean suites execute, through THIS SDK's request-shaping
705
+ # utilities. Before this, elixir mirrored the corpus by hand, so its cases
706
+ # could drift from the reference without anything noticing.
707
+ #
708
+ # The harness below is run_set's, with one difference: the subject receives
709
+ # the ENTRY rather than resolved args, because the args-style sections have
710
+ # to publish the context they build back onto the entry — a `match: ctx:`
711
+ # assertion reads it from there.
712
+
713
+ # A struct VALUE is itself a 2-tuple — {:vmap, id}, {:vlist, id}, {:vinj, id}
714
+ # — so a bare `{res, err}` clause destructures a perfectly good map into
715
+ # res=:vmap, err=<heap id>, and the run then fails with the heap id as its
716
+ # error message. Match the value shapes first.
717
+ defp pnorm({:vmap, _} = v), do: {v, nil}
718
+ defp pnorm({:vlist, _} = v), do: {v, nil}
719
+ defp pnorm({:vinj, _} = v), do: {v, nil}
720
+ defp pnorm({res, err}), do: {res, err}
721
+ defp pnorm(res), do: {res, nil}
722
+
723
+ # A ctx for a ctx-style entry: the corpus supplies it, the client supplies
724
+ # what makes it live (utility dispatch, options, config).
725
+ defp pctx(entry, util, opts, config) do
726
+ ctxmap = if ehas(entry, "ctx") and S.ismap(eget(entry, "ctx")), do: eget(entry, "ctx"), else: S.jm([])
727
+ ctx = pbuild(ctxmap, util, opts, config)
728
+ S.setprop(entry, "ctx", ctx)
729
+ ctx
730
+ end
731
+
732
+ # A LIVE context from a corpus map. The corpus carries spec/result/response
733
+ # as plain JSON; the utilities read and MUTATE them through Spec/Result/
734
+ # Response, so a bare map leaves them with nothing to work on — every
735
+ # `match: ctx.result.*` assertion then reads null, and prepare_* returns
736
+ # nothing. Mirrors make_ctx_from_map in the lua/js reference drivers.
737
+ defp pbuild(ctxmap, util, opts, config) do
738
+ ctx = ProjectName.Context.new(ctxmap, nil)
739
+ S.setprop(ctx, "utility", util)
740
+ S.setprop(ctx, "config", config)
741
+ # THE CLIENT IS OPT-IN. Some utilities read their defaults off it —
742
+ # prepare_headers reads opts_map(ctx.client) — but the client holds the
743
+ # root ctx, which holds the client, so anything that WALKS a ctx carrying
744
+ # one never terminates: makeContext returns a ctx, and make_error raises an
745
+ # error whose `ctx:` field is the ctx. Both hung the run until ExUnit
746
+ # killed it. Only the sections that need client defaults ask for it.
747
+ if Process.get(:pwantclient) == true and Process.get(:pclient) != nil,
748
+ do: S.setprop(ctx, "client", Process.get(:pclient))
749
+ if S.getprop(ctx, "options") == nil, do: S.setprop(ctx, "options", opts)
750
+
751
+ specmap = S.getprop(ctxmap, "spec")
752
+ if S.ismap(specmap), do: S.setprop(ctx, "spec", ProjectName.Spec.new(specmap))
753
+
754
+ resmap = S.getprop(ctxmap, "result")
755
+
756
+ if S.ismap(resmap) do
757
+ result = ProjectName.Result.new(resmap)
758
+ errmap = S.getprop(resmap, "err")
759
+
760
+ if S.ismap(errmap) do
761
+ msg = jss(S.getprop(errmap, "message"))
762
+ if is_binary(msg) and msg != "",
763
+ do: S.setprop(result, "err", ProjectName.Error.new("", msg, nil))
764
+ end
765
+
766
+ S.setprop(ctx, "result", result)
767
+ end
768
+
769
+ respmap = S.getprop(ctxmap, "response")
770
+
771
+ if S.ismap(respmap) do
772
+ response = ProjectName.Response.new(respmap)
773
+ body = S.getprop(respmap, "body")
774
+ if body != nil, do: S.setprop(response, "json_func", fn -> body end)
775
+
776
+ hdrs = S.getprop(respmap, "headers")
777
+
778
+ if S.ismap(hdrs) do
779
+ lower = S.jm([])
780
+ Enum.each(S.keysof(hdrs), fn k ->
781
+ S.setprop(lower, String.downcase(k), S.getprop(hdrs, k))
782
+ end)
783
+ S.setprop(response, "headers", lower)
784
+ end
785
+
786
+ S.setprop(ctx, "response", response)
787
+ end
788
+
789
+ ctx
790
+ end
791
+
792
+ # A ctx from a bare map (args-style sections), published back onto the entry.
793
+ defp pmapctx(entry, v, util, opts, config) do
794
+ ctx = pbuild(if(S.ismap(v), do: v, else: S.jm([])), util, opts, config)
795
+ S.setprop(entry, "ctx", ctx)
796
+ ctx
797
+ end
798
+
799
+ # A ctx that carries the client, for the sections whose utilities read their
800
+ # defaults off it. Scoped so nothing else inherits the cycle.
801
+ defp pctx_client(entry, util, opts, config) do
802
+ Process.put(:pwantclient, true)
803
+ ctx = pctx(entry, util, opts, config)
804
+ Process.put(:pwantclient, false)
805
+ ctx
806
+ end
807
+
808
+ defp parg(entry, i) do
809
+ a = eget(entry, "args")
810
+ if S.islist(a), do: Enum.at(velems(a), i), else: nil
811
+ end
812
+
813
+ defp run_primary_set(group, node, subject) do
814
+ if System.get_env("VOX_DBG"), do: IO.puts("SECTION #{group}")
815
+ fixed = fixj(S.clone(node), true)
816
+ testset = S.getprop(fixed, "set")
817
+
818
+ if S.islist(testset) do
819
+ Enum.each(velems(testset), fn entry ->
820
+ name = jss(eget(entry, "name"))
821
+
822
+ try do
823
+ if not ehas(entry, "out"), do: S.setprop(entry, "out", @nullmark)
824
+ {res0, err} = pnorm(subject.(entry))
825
+
826
+ if err != nil do
827
+ raise SE, message: perrmsg(err)
828
+ end
829
+
830
+ # The RESULT can carry them too — makeContext returns a context — and
831
+ # fixj walks it before anything else gets the chance to strip.
832
+ if S.ismap(res0) do
833
+ S.delprop(res0, "client")
834
+ S.delprop(res0, "utility")
835
+ end
836
+
837
+ res = fixj(res0, true)
838
+ S.setprop(entry, "res", res)
839
+
840
+ # STRIP THE LIVE REFERENCES BEFORE MATCHING. check_result matches
841
+ # against entry.ctx, and the ctx carries the client so utilities like
842
+ # prepare_headers can read their defaults off it. The client holds the
843
+ # root ctx, which holds the client — walking that is a cycle, and the
844
+ # run hangs until ExUnit kills it at 60s. The utilities have already
845
+ # run by here, so nothing needs them any more.
846
+ ectx = eget(entry, "ctx")
847
+
848
+ if S.ismap(ectx) do
849
+ S.delprop(ectx, "client")
850
+ S.delprop(ectx, "utility")
851
+ end
852
+
853
+ check_result(entry, resolve_args(entry), res)
854
+ record(group, name, true, "")
855
+ rescue
856
+ e ->
857
+ try do
858
+ handle_error(entry, e)
859
+ record(group, name, true, "")
860
+ rescue
861
+ e2 -> record(group, name, false, errmsg(e2))
862
+ end
863
+ end
864
+ end)
865
+ end
866
+ end
867
+
868
+ # The corpus speaks camelCase; snake_case languages do not. lua, py and
869
+ # elixir all store `status_text` internally while reading `statusText` off
870
+ # the wire, so a `match: ctx.result.statusText` assertion reads null unless
871
+ # the driver publishes a neutral-named view back. The lua reference driver
872
+ # does exactly this.
873
+ defp pneutral_result(result) do
874
+ if S.ismap(result) do
875
+ err = S.getprop(result, "err")
876
+
877
+ S.jm([
878
+ "ok", S.getprop(result, "ok"),
879
+ "status", S.getprop(result, "status"),
880
+ "statusText", S.getprop(result, "status_text"),
881
+ "headers", S.getprop(result, "headers"),
882
+ "body", S.getprop(result, "body"),
883
+ "err", if(err == nil, do: nil, else: S.jm(["message", perrmsg(err)]))
884
+ ])
885
+ end
886
+ end
887
+
888
+ # Publish the neutral view of ctx.result onto the entry's ctx, so
889
+ # `match: ctx.result.*` sees corpus-shaped data.
890
+ defp ppub(entry, ctx) do
891
+ ectx = eget(entry, "ctx")
892
+ result = if S.ismap(ctx), do: S.getprop(ctx, "result")
893
+ if S.ismap(ectx) and S.ismap(result) do
894
+ S.setprop(ectx, "result", pneutral_result(result))
895
+ end
896
+ :ok
897
+ end
898
+
899
+ defp perrmsg(err) do
900
+ cond do
901
+ is_binary(err) -> err
902
+ S.ismap(err) -> jss(S.getprop(err, "message")) || S.stringify(err)
903
+ true -> inspect(err)
904
+ end
905
+ end
906
+
907
+ defp psec(primary, name, sub \\ "basic"), do: S.getprop(S.getprop(primary, name), sub)
908
+
909
+ # A CLIENT built from a section's DEF.setup.a block.
910
+ #
911
+ # prepare_auth reads opts_map(ctx.client) — as the ts reference does, via
912
+ # client.options() — so a section's setup options have to reach it through a
913
+ # client, not through ctx.options. Passing them as options alone left the
914
+ # apikey unreachable and the authorization header never set.
915
+ defp pclient_for(primary, name) do
916
+ setup = S.getprop(S.getprop(S.getprop(S.getprop(primary, name), "DEF"), "setup"), "a")
917
+ if S.ismap(setup), do: ProjectName.test(nil, S.clone(setup)), else: ProjectName.test()
918
+ end
919
+
920
+ # Options built from a section's DEF.setup.a block, the way the reference
921
+ # harness does. Falls back to the client's options when a section has none.
922
+ defp psetup(primary, name, util, config) do
923
+ setup =
924
+ S.getprop(S.getprop(S.getprop(S.getprop(primary, name), "DEF"), "setup"), "a")
925
+
926
+ ctx = S.jm([])
927
+ S.setprop(ctx, "utility", util)
928
+ S.setprop(ctx, "config", config)
929
+ S.setprop(ctx, "options", if(S.ismap(setup), do: S.clone(setup), else: S.jm([])))
930
+ U.make_options(ctx)
931
+ end
932
+
933
+ def run_primary(testfile \\ nil) do
934
+ Process.put(:npass, 0)
935
+ Process.put(:nfail, 0)
936
+ Process.put(:failures, [])
937
+
938
+ testfile = testfile || Path.join(File.cwd!(), "../.sdk/test/test.json")
939
+ root = parse(File.read!(testfile))
940
+ primary = S.getprop(root, "primary")
941
+
942
+ client = ProjectName.test()
943
+ Process.put(:pclient, client)
944
+ Process.put(:pbaseclient, client)
945
+ rootctx = ProjectName.get_root_ctx(client)
946
+ util = S.getprop(rootctx, "utility")
947
+ config = S.getprop(rootctx, "config")
948
+ opts = S.getprop(rootctx, "options")
949
+
950
+ run_primary_set("done", psec(primary, "done"), fn e ->
951
+ U.done(pctx(e, util, opts, config))
952
+ end)
953
+
954
+ # makeContext takes a PLAIN map and returns a context; it needs no client
955
+ # and no utility dispatch. Handing it a live ctx made Context.new copy the
956
+ # client through, and fixj then walked client -> rootctx -> client.
957
+ run_primary_set("makeContext", psec(primary, "makeContext"), fn e ->
958
+ inv = eget(e, "in")
959
+ ctxmap = if S.ismap(inv), do: inv, else: S.jm([])
960
+ S.setprop(e, "ctx", ctxmap)
961
+ ProjectName.Context.new(ctxmap, nil)
962
+ end)
963
+
964
+ run_primary_set("makeError", psec(primary, "makeError"), fn e ->
965
+ ctx = pmapctx(e, parg(e, 0), util, opts, config)
966
+ {nil, U.make_error(ctx, parg(e, 1))}
967
+ end)
968
+
969
+ run_primary_set("makeOptions", psec(primary, "makeOptions"), fn e ->
970
+ inv = eget(e, "in")
971
+ ctx = pmapctx(e, S.jm([]), util, opts, config)
972
+ S.setprop(ctx, "config", S.getprop(inv, "config"))
973
+ S.setprop(ctx, "options", S.getprop(inv, "options"))
974
+ U.make_options(ctx)
975
+ end)
976
+
977
+ run_primary_set("makeRequest", psec(primary, "makeRequest"), fn e ->
978
+ U.make_request(pctx(e, util, opts, config))
979
+ end)
980
+
981
+ run_primary_set("makeResponse", psec(primary, "makeResponse"), fn e ->
982
+ ctx = pctx(e, util, opts, config)
983
+ out = U.make_response(ctx)
984
+ ppub(e, ctx)
985
+ out
986
+ end)
987
+
988
+ # makeSpec and prepareAuth are configured by their own DEF.setup.a block,
989
+ # not by the client's default options — the corpus supplies the base URL
990
+ # and the api key the cases assert on.
991
+ spec_opts = psetup(primary, "makeSpec", util, config)
992
+
993
+ run_primary_set("makeSpec", psec(primary, "makeSpec"), fn e ->
994
+ U.make_spec(pctx_client(e, util, spec_opts, config))
995
+ end)
996
+
997
+ run_primary_set("makeUrl", psec(primary, "makeUrl"), fn e ->
998
+ U.make_url(pctx(e, util, opts, config))
999
+ end)
1000
+
1001
+ run_primary_set("operator", psec(primary, "operator"), fn e ->
1002
+ inv = eget(e, "in")
1003
+ op = ProjectName.Operation.new(if(S.ismap(inv), do: inv, else: S.jm([])))
1004
+ S.jm([
1005
+ "entity", S.getprop(op, "entity"),
1006
+ "input", S.getprop(op, "input"),
1007
+ "name", S.getprop(op, "name"),
1008
+ "points", S.getprop(op, "points")
1009
+ ])
1010
+ end)
1011
+
1012
+ run_primary_set("param", psec(primary, "param"), fn e ->
1013
+ ctx = pmapctx(e, parg(e, 0), util, opts, config)
1014
+ U.param(ctx, parg(e, 1))
1015
+ end)
1016
+
1017
+ auth_opts = psetup(primary, "prepareAuth", util, config)
1018
+ auth_client = pclient_for(primary, "prepareAuth")
1019
+
1020
+ run_primary_set("prepareAuth", psec(primary, "prepareAuth"), fn e ->
1021
+ Process.put(:pclient, auth_client)
1022
+ out = U.prepare_auth(pctx_client(e, util, auth_opts, config))
1023
+ Process.put(:pclient, Process.get(:pbaseclient))
1024
+ out
1025
+ end)
1026
+
1027
+ run_primary_set("prepareBody", psec(primary, "prepareBody"), fn e ->
1028
+ U.prepare_body(pctx(e, util, opts, config))
1029
+ end)
1030
+
1031
+ run_primary_set("prepareHeaders", psec(primary, "prepareHeaders"), fn e ->
1032
+ U.prepare_headers(pctx_client(e, util, opts, config))
1033
+ end)
1034
+
1035
+ run_primary_set("prepareMethod", psec(primary, "prepareMethod"), fn e ->
1036
+ U.prepare_method(pctx(e, util, opts, config))
1037
+ end)
1038
+
1039
+ run_primary_set("prepareParams", psec(primary, "prepareParams"), fn e ->
1040
+ U.prepare_params(pctx(e, util, opts, config))
1041
+ end)
1042
+
1043
+ run_primary_set("preparePath", psec(primary, "preparePath"), fn e ->
1044
+ U.prepare_path(pctx(e, util, opts, config))
1045
+ end)
1046
+
1047
+ run_primary_set("prepareQuery", psec(primary, "prepareQuery"), fn e ->
1048
+ U.prepare_query(pctx(e, util, opts, config))
1049
+ end)
1050
+
1051
+ run_primary_set("resultBasic", psec(primary, "resultBasic"), fn e ->
1052
+ ctx = pctx(e, util, opts, config)
1053
+ out = U.result_basic(ctx)
1054
+ ppub(e, ctx)
1055
+ if S.ismap(out) and S.getprop(out, "status_text") != nil,
1056
+ do: pneutral_result(out),
1057
+ else: out
1058
+ end)
1059
+
1060
+ run_primary_set("resultBody", psec(primary, "resultBody"), fn e ->
1061
+ ctx = pctx(e, util, opts, config)
1062
+ out = U.result_body(ctx)
1063
+ ppub(e, ctx)
1064
+ if S.ismap(out) and S.getprop(out, "status_text") != nil,
1065
+ do: pneutral_result(out),
1066
+ else: out
1067
+ end)
1068
+
1069
+ run_primary_set("resultHeaders", psec(primary, "resultHeaders"), fn e ->
1070
+ ctx = pctx(e, util, opts, config)
1071
+ out = U.result_headers(ctx)
1072
+ ppub(e, ctx)
1073
+ if S.ismap(out) and S.getprop(out, "status_text") != nil,
1074
+ do: pneutral_result(out),
1075
+ else: out
1076
+ end)
1077
+
1078
+ run_primary_set("transformRequest", psec(primary, "transformRequest"), fn e ->
1079
+ U.transform_request(pctx(e, util, opts, config))
1080
+ end)
1081
+
1082
+ run_primary_set("transformResponse", psec(primary, "transformResponse"), fn e ->
1083
+ U.transform_response(pctx(e, util, opts, config))
1084
+ end)
1085
+
1086
+ {Process.get(:npass, 0), Process.get(:nfail, 0), Process.get(:failures, [])}
1087
+ end
677
1088
  end