@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
@@ -0,0 +1,565 @@
1
+ /* Primary-utility corpus driver — C port.
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
+ * hand-written primary_utility_test.c beside this file covers the same
6
+ * utilities with directly-constructed contexts; that suite can drift from the
7
+ * contract, this one cannot — the cases ARE the contract.
8
+ *
9
+ * The corpus entry shapes are ctx+out, ctx+match, ctx+mark+out, args+mark+out,
10
+ * args+err and in+match. `mark` is a case label, not an assertion.
11
+ */
12
+
13
+ #include "feature_harness.h" /* test_sdk + Fetcher helpers + ctest.h */
14
+ #include "runner.h" /* normalize + deep_equal, shared with the struct corpus */
15
+ #include "voxgig_struct.h"
16
+
17
+ #include <ctype.h>
18
+ #include <stdio.h>
19
+ #include <stdlib.h>
20
+ #include <string.h>
21
+
22
+ static voxgig_value* CORPUS = NULL;
23
+ static int NPASS = 0;
24
+ static int NFAIL = 0;
25
+
26
+ /* ---- corpus access ------------------------------------------------------ */
27
+
28
+ static voxgig_value* mget(voxgig_value* m, const char* k) {
29
+ if (!voxgig_is_map(m)) return NULL;
30
+ return voxgig_map_get(voxgig_as_map(m), k);
31
+ }
32
+
33
+ static voxgig_value* primary_root(void) {
34
+ if (!CORPUS) CORPUS = voxgig_parse_json_file("../.sdk/test/test.json");
35
+ voxgig_value* p = mget(CORPUS, "primary");
36
+ return p ? p : v_map();
37
+ }
38
+
39
+ /* section(name) -> the `basic` node, which carries `set`. */
40
+ static voxgig_value* section_basic(const char* name) {
41
+ voxgig_value* sec = mget(primary_root(), name);
42
+ voxgig_value* basic = mget(sec, "basic");
43
+ return basic ? basic : v_map();
44
+ }
45
+
46
+ /* A section may carry its own client setup at DEF.setup.a — makeSpec and
47
+ * prepareAuth read defaults off the CLIENT, not off ctx.options, so those two
48
+ * cannot be driven with the shared client. */
49
+ static voxgig_value* section_setup(const char* name) {
50
+ voxgig_value* sec = mget(primary_root(), name);
51
+ voxgig_value* def = mget(sec, "DEF");
52
+ voxgig_value* setup = mget(def, "setup");
53
+ voxgig_value* a = mget(setup, "a");
54
+ return a;
55
+ }
56
+
57
+ /* ---- match machinery ---------------------------------------------------- */
58
+
59
+ static char* vstr(voxgig_value* v) {
60
+ if (!v) return strdup("");
61
+ char* s = voxgig_stringify(v, -1);
62
+ return s ? s : strdup("");
63
+ }
64
+
65
+ static char* lower_dup(const char* s) {
66
+ char* o = strdup(s ? s : "");
67
+ for (char* p = o; *p; p++) *p = (char)tolower((unsigned char)*p);
68
+ return o;
69
+ }
70
+
71
+ static bool is_nullish(voxgig_value* v) {
72
+ return v == NULL || voxgig_is_undef(v) || voxgig_is_null(v);
73
+ }
74
+
75
+ static bool matchval(voxgig_value* check, voxgig_value* base) {
76
+ voxgig_value* nc = normalize(check);
77
+ voxgig_value* nb = normalize(base);
78
+ bool eq = deep_equal(nc, nb);
79
+ voxgig_release(nc);
80
+ voxgig_release(nb);
81
+ if (eq) return true;
82
+
83
+ if (!check || !v_is_str(check)) return false;
84
+ const char* c = voxgig_as_string(check);
85
+ if (strcmp(c, "__UNDEF__") == 0) return is_nullish(base);
86
+ if (strcmp(c, "__EXISTS__") == 0) return !is_nullish(base);
87
+
88
+ char* bs = vstr(base);
89
+ size_t cl = strlen(c);
90
+
91
+ /* A /pattern/ is a regex over the stringified base. */
92
+ if (2 <= cl && c[0] == '/' && c[cl - 1] == '/') {
93
+ char* pat = (char*)malloc(cl - 1);
94
+ memcpy(pat, c + 1, cl - 2);
95
+ pat[cl - 2] = '\0';
96
+ bool ok = voxgig_re_test(pat, bs);
97
+ free(pat);
98
+ free(bs);
99
+ return ok;
100
+ }
101
+
102
+ /* Otherwise a case-insensitive substring, as the reference does. */
103
+ char* lb = lower_dup(bs);
104
+ char* lc = lower_dup(c);
105
+ bool ok = strstr(lb, lc) != NULL;
106
+ free(lb);
107
+ free(lc);
108
+ free(bs);
109
+ return ok;
110
+ }
111
+
112
+ /* Walk the CHECK structure; every leaf must matchval at the same path in
113
+ * base. A missing path in base reads as null, which only __UNDEF__ matches. */
114
+ static bool do_match(voxgig_value* check, voxgig_value* base,
115
+ const char* path, char** fail) {
116
+ if (voxgig_is_map(check)) {
117
+ voxgig_map* m = voxgig_as_map(check);
118
+ for (size_t i = 0; i < m->len; i++) {
119
+ const char* k = m->entries[i].key;
120
+ char sub[512];
121
+ snprintf(sub, sizeof(sub), "%s%s%s", path, path[0] ? "." : "", k);
122
+ if (!do_match(m->entries[i].value, mget(base, k), sub, fail)) return false;
123
+ }
124
+ return true;
125
+ }
126
+ if (voxgig_is_list(check)) {
127
+ voxgig_list* l = voxgig_as_list(check);
128
+ for (size_t i = 0; i < l->len; i++) {
129
+ char sub[512];
130
+ snprintf(sub, sizeof(sub), "%s.%zu", path, i);
131
+ voxgig_value* be = NULL;
132
+ if (voxgig_is_list(base) && i < voxgig_as_list(base)->len) {
133
+ be = voxgig_as_list(base)->items[i];
134
+ }
135
+ if (!do_match(l->items[i], be, sub, fail)) return false;
136
+ }
137
+ return true;
138
+ }
139
+ if (matchval(check, base)) return true;
140
+
141
+ char* cs = vstr(check);
142
+ char* bs = vstr(base);
143
+ char buf[1024];
144
+ snprintf(buf, sizeof(buf), "MATCH: %s: [%s] <=> [%s]", path, cs, bs);
145
+ free(cs);
146
+ free(bs);
147
+ *fail = strdup(buf);
148
+ return false;
149
+ }
150
+
151
+ /* ---- live context from a corpus map ------------------------------------- */
152
+
153
+ static voxgig_value* corpus_json_thunk(void* ud, voxgig_value* args) {
154
+ (void)args;
155
+ return ud ? (voxgig_value*)ud : v_undef();
156
+ }
157
+
158
+ static Context* corpus_ctx(ProjectNameSDK* cl, voxgig_value* ctxmap) {
159
+ CtxSpec cs;
160
+ memset(&cs, 0, sizeof(cs));
161
+ /* Only when the corpus names one: defaulting to "load" made the SDK report
162
+ * the wrong operation in the error messages the corpus matches on. */
163
+ voxgig_value* opn = mget(ctxmap, "opname");
164
+ if (opn && v_is_str(opn)) cs.opname = voxgig_as_string(opn);
165
+ cs.client = cl;
166
+ cs.utility = sdk_get_utility(cl);
167
+ Context* ctx = make_context_util(cs, sdk_get_root_ctx(cl));
168
+
169
+ voxgig_value* sp = mget(ctxmap, "spec");
170
+ if (voxgig_is_map(sp)) ctx->spec = spec_new(voxgig_clone(sp));
171
+
172
+ voxgig_value* rs = mget(ctxmap, "result");
173
+ if (voxgig_is_map(rs)) {
174
+ ctx->result = result_new(voxgig_clone(rs));
175
+ /* result_new does not carry an err, so a corpus result holding one
176
+ * arrived empty and result_basic had no previous message to prepend. */
177
+ voxgig_value* re = mget(rs, "err");
178
+ voxgig_value* rm = mget(re, "message");
179
+ if (rm && v_is_str(rm) && voxgig_as_string(rm)[0]) {
180
+ ctx->result->err = pn_error_new("", voxgig_as_string(rm));
181
+ }
182
+ }
183
+
184
+ voxgig_value* rp = mget(ctxmap, "response");
185
+ if (voxgig_is_map(rp)) {
186
+ ctx->response = response_new(voxgig_clone(rp));
187
+ /* result_body reads response.json and requires it to be CALLABLE; the
188
+ * corpus supplies a plain `body`. */
189
+ voxgig_value* body = mget(rp, "body");
190
+ if (body && !v_is_noval(body)) {
191
+ ctx->response->body = voxgig_retain(body);
192
+ ctx->response->json = vfn(corpus_json_thunk, voxgig_retain(body));
193
+ }
194
+ /* Header names arrive from the wire in any case; the contract is
195
+ * lowercase and result_headers copies them verbatim. */
196
+ voxgig_value* hs = mget(rp, "headers");
197
+ if (voxgig_is_map(hs)) {
198
+ voxgig_map* hm = voxgig_as_map(hs);
199
+ voxgig_value* low = v_map();
200
+ for (size_t i = 0; i < hm->len; i++) {
201
+ char* lk = lower_dup(hm->entries[i].key);
202
+ setp(low, lk, voxgig_retain(hm->entries[i].value));
203
+ free(lk);
204
+ }
205
+ ctx->response->headers = low;
206
+ }
207
+ }
208
+
209
+ voxgig_value* pt = mget(ctxmap, "point");
210
+ if (voxgig_is_map(pt)) ctx->point = voxgig_clone(pt);
211
+ voxgig_value* rd = mget(ctxmap, "reqdata");
212
+ if (rd && !v_is_noval(rd)) ctx->reqdata = voxgig_retain(rd);
213
+ voxgig_value* rmt = mget(ctxmap, "reqmatch");
214
+ if (rmt && !v_is_noval(rmt)) ctx->reqmatch = voxgig_retain(rmt);
215
+ voxgig_value* dt = mget(ctxmap, "data");
216
+ if (dt && !v_is_noval(dt)) ctx->data = voxgig_retain(dt);
217
+ voxgig_value* mt = mget(ctxmap, "match");
218
+ if (mt && !v_is_noval(mt)) ctx->mtch = voxgig_retain(mt);
219
+ voxgig_value* op = mget(ctxmap, "options");
220
+ if (voxgig_is_map(op)) ctx->options = voxgig_clone(op);
221
+ voxgig_value* cf = mget(ctxmap, "config");
222
+ if (voxgig_is_map(cf)) ctx->config = voxgig_clone(cf);
223
+ return ctx;
224
+ }
225
+
226
+ /* The match reads the corpus map while the utilities mutate the live objects
227
+ * hanging off the context, so without this every ctx.* assertion reads null. */
228
+ static void publish_ctx(voxgig_value* ctxmap, Context* ctx) {
229
+ if (!voxgig_is_map(ctxmap)) return;
230
+ if (ctx->spec) setp(ctxmap, "spec", spec_to_value(ctx->spec));
231
+ if (ctx->result) setp(ctxmap, "result", result_to_value(ctx->result));
232
+ if (ctx->response) setp(ctxmap, "response", v_str("__EXISTS__"));
233
+ }
234
+
235
+ /* ---- the section runner ------------------------------------------------- */
236
+
237
+ typedef voxgig_value* (*ctxfn)(Context* ctx, voxgig_value* args, char** err);
238
+ typedef voxgig_value* (*argfn)(voxgig_value* args, char** err);
239
+
240
+ static void record(const char* section, bool ok, const char* msg) {
241
+ if (ok) {
242
+ NPASS++;
243
+ } else {
244
+ NFAIL++;
245
+ printf("PRIMARY-FAIL %s - %s\n", section, msg ? msg : "?");
246
+ }
247
+ }
248
+
249
+ static voxgig_value* arg_at(voxgig_value* args, size_t i) {
250
+ if (!voxgig_is_list(args)) return NULL;
251
+ voxgig_list* l = voxgig_as_list(args);
252
+ return i < l->len ? l->items[i] : NULL;
253
+ }
254
+
255
+ static void runset(const char* name, ProjectNameSDK* cl,
256
+ ctxfn cf, argfn af) {
257
+ voxgig_value* basic = section_basic(name);
258
+ voxgig_value* set = mget(basic, "set");
259
+ if (!voxgig_is_list(set)) return;
260
+ voxgig_list* sl = voxgig_as_list(set);
261
+
262
+ for (size_t i = 0; i < sl->len; i++) {
263
+ voxgig_value* entry = sl->items[i];
264
+ if (!voxgig_is_map(entry)) continue;
265
+
266
+ /* resolve_args, mirroring the ts runner. */
267
+ voxgig_value* args = v_list();
268
+ voxgig_value* ectx = mget(entry, "ctx");
269
+ voxgig_value* eargs = mget(entry, "args");
270
+ voxgig_value* ein = mget(entry, "in");
271
+ if (ectx) {
272
+ voxgig_list_push(voxgig_as_list(args), voxgig_clone(ectx));
273
+ } else if (voxgig_is_list(eargs)) {
274
+ voxgig_list* al = voxgig_as_list(eargs);
275
+ for (size_t k = 0; k < al->len; k++) {
276
+ voxgig_list_push(voxgig_as_list(args), voxgig_retain(al->items[k]));
277
+ }
278
+ } else if (ein) {
279
+ voxgig_list_push(voxgig_as_list(args), voxgig_clone(ein));
280
+ }
281
+
282
+ /* ts's resolveArgs writes the live first arg back as entry.ctx so a
283
+ * `match: {ctx: ...}` resolves for args-style entries too. */
284
+ voxgig_value* first = arg_at(args, 0);
285
+ if (voxgig_is_map(first)) setp(entry, "ctx", voxgig_retain(first));
286
+
287
+ char* err = NULL;
288
+ voxgig_value* got = NULL;
289
+ if (cf) {
290
+ Context* ctx = corpus_ctx(cl, first);
291
+ got = cf(ctx, args, &err);
292
+ publish_ctx(first, ctx);
293
+ } else {
294
+ got = af(args, &err);
295
+ }
296
+
297
+ voxgig_value* eerr = mget(entry, "err");
298
+ if (eerr && !v_is_noval(eerr)) {
299
+ /* The case expects a failure. */
300
+ if (!err) {
301
+ record(name, false, "expected an error, got none");
302
+ continue;
303
+ }
304
+ voxgig_value* errv = v_str(err);
305
+ bool ok = (voxgig_is_bool(eerr) && voxgig_as_bool(eerr)) || matchval(eerr, errv);
306
+ if (!ok) {
307
+ char* es = vstr(eerr);
308
+ char buf[1024];
309
+ snprintf(buf, sizeof(buf), "ERROR MATCH: [%s] <=> [%s]", es, err);
310
+ free(es);
311
+ record(name, false, buf);
312
+ continue;
313
+ }
314
+ voxgig_value* emat = mget(entry, "match");
315
+ if (voxgig_is_map(emat)) {
316
+ /* ts hands do_match the ERROR OBJECT, so `match: {err: {message}}`
317
+ * resolves; a bare string leaves err.message reading null. */
318
+ voxgig_value* base = cmap(4, "in", ein ? voxgig_retain(ein) : v_undef(),
319
+ "out", got ? voxgig_retain(got) : v_undef(),
320
+ "ctx", voxgig_retain(mget(entry, "ctx")),
321
+ "err", cmap(1, "message", v_str(err)));
322
+ char* fail = NULL;
323
+ if (!do_match(emat, base, "", &fail)) {
324
+ record(name, false, fail);
325
+ continue;
326
+ }
327
+ }
328
+ record(name, true, NULL);
329
+ continue;
330
+ }
331
+
332
+ if (err) {
333
+ record(name, false, err);
334
+ continue;
335
+ }
336
+
337
+ /* check_result: `match` first, then `out`. */
338
+ bool matched = false;
339
+ voxgig_value* emat = mget(entry, "match");
340
+ if (voxgig_is_map(emat)) {
341
+ voxgig_value* base = cmap(4, "in", ein ? voxgig_retain(ein) : v_undef(),
342
+ "args", voxgig_retain(args),
343
+ "out", got ? voxgig_retain(got) : v_undef(),
344
+ "ctx", voxgig_retain(mget(entry, "ctx")));
345
+ char* fail = NULL;
346
+ if (!do_match(emat, base, "", &fail)) {
347
+ record(name, false, fail);
348
+ continue;
349
+ }
350
+ matched = true;
351
+ }
352
+
353
+ voxgig_value* eout = mget(entry, "out");
354
+ voxgig_value* expected = eout ? eout : v_null();
355
+ voxgig_value* ne = normalize(expected);
356
+ voxgig_value* ng = normalize(got);
357
+ bool eq = deep_equal(ne, ng);
358
+ voxgig_release(ne);
359
+ voxgig_release(ng);
360
+ if (!eq && !(matched && !eout)) {
361
+ char* es = vstr(expected);
362
+ char* gs = vstr(got);
363
+ char buf[1024];
364
+ snprintf(buf, sizeof(buf), "Expected: %s, got: %s", es, gs);
365
+ free(es);
366
+ free(gs);
367
+ record(name, false, buf);
368
+ continue;
369
+ }
370
+ record(name, true, NULL);
371
+ }
372
+ }
373
+
374
+ /* ---- per-section subjects ----------------------------------------------- */
375
+
376
+ #define ERRSET(e, p) do { if (p) { *(e) = strdup((p)->msg ? (p)->msg : "error"); } } while (0)
377
+
378
+ static voxgig_value* s_done(Context* c, voxgig_value* a, char** e) {
379
+ (void)a;
380
+ PNError* pe = NULL;
381
+ voxgig_value* r = done_util(c, &pe);
382
+ ERRSET(e, pe);
383
+ return r;
384
+ }
385
+ static voxgig_value* s_make_url(Context* c, voxgig_value* a, char** e) {
386
+ (void)a;
387
+ PNError* pe = NULL;
388
+ char* u = make_url_util(c, &pe);
389
+ ERRSET(e, pe);
390
+ return u ? v_str(u) : v_undef();
391
+ }
392
+ static voxgig_value* s_make_request(Context* c, voxgig_value* a, char** e) {
393
+ (void)a;
394
+ PNError* pe = NULL;
395
+ make_request_util(c, &pe);
396
+ ERRSET(e, pe);
397
+ return c->result ? result_to_value(c->result) : v_undef();
398
+ }
399
+ static voxgig_value* s_make_response(Context* c, voxgig_value* a, char** e) {
400
+ (void)a;
401
+ PNError* pe = NULL;
402
+ make_response_util(c, &pe);
403
+ ERRSET(e, pe);
404
+ return c->result ? result_to_value(c->result) : v_undef();
405
+ }
406
+ static voxgig_value* s_make_spec(Context* c, voxgig_value* a, char** e) {
407
+ (void)a;
408
+ PNError* pe = NULL;
409
+ Spec* s = make_spec_util(c, &pe);
410
+ ERRSET(e, pe);
411
+ if (s) c->spec = s;
412
+ return s ? spec_to_value(s) : v_undef();
413
+ }
414
+ static voxgig_value* s_prepare_auth(Context* c, voxgig_value* a, char** e) {
415
+ (void)a;
416
+ PNError* pe = NULL;
417
+ prepare_auth_util(c, &pe);
418
+ ERRSET(e, pe);
419
+ return c->spec ? spec_to_value(c->spec) : v_undef();
420
+ }
421
+ static voxgig_value* s_prepare_body(Context* c, voxgig_value* a, char** e) {
422
+ (void)a; (void)e;
423
+ return prepare_body_util(c);
424
+ }
425
+ static voxgig_value* s_prepare_headers(Context* c, voxgig_value* a, char** e) {
426
+ (void)a; (void)e;
427
+ return prepare_headers_util(c);
428
+ }
429
+ static voxgig_value* s_prepare_method(Context* c, voxgig_value* a, char** e) {
430
+ (void)a; (void)e;
431
+ const char* m = prepare_method_util(c);
432
+ return (m && m[0]) ? v_str(m) : v_undef();
433
+ }
434
+ static voxgig_value* s_prepare_params(Context* c, voxgig_value* a, char** e) {
435
+ (void)a; (void)e;
436
+ return prepare_params_util(c);
437
+ }
438
+ static voxgig_value* s_prepare_path(Context* c, voxgig_value* a, char** e) {
439
+ (void)a; (void)e;
440
+ char* p = prepare_path_util(c);
441
+ return p ? v_str(p) : v_undef();
442
+ }
443
+ static voxgig_value* s_prepare_query(Context* c, voxgig_value* a, char** e) {
444
+ (void)a; (void)e;
445
+ return prepare_query_util(c);
446
+ }
447
+ static voxgig_value* s_result_basic(Context* c, voxgig_value* a, char** e) {
448
+ (void)a; (void)e;
449
+ SdkResult* r = result_basic_util(c);
450
+ if (r) c->result = r;
451
+ return c->result ? result_to_value(c->result) : v_undef();
452
+ }
453
+ static voxgig_value* s_result_body(Context* c, voxgig_value* a, char** e) {
454
+ (void)a; (void)e;
455
+ SdkResult* r = result_body_util(c);
456
+ if (r) c->result = r;
457
+ return c->result ? result_to_value(c->result) : v_undef();
458
+ }
459
+ static voxgig_value* s_result_headers(Context* c, voxgig_value* a, char** e) {
460
+ (void)a; (void)e;
461
+ SdkResult* r = result_headers_util(c);
462
+ if (r) c->result = r;
463
+ return c->result ? result_to_value(c->result) : v_undef();
464
+ }
465
+ static voxgig_value* s_transform_request(Context* c, voxgig_value* a, char** e) {
466
+ (void)a; (void)e;
467
+ return transform_request_util(c);
468
+ }
469
+ static voxgig_value* s_transform_response(Context* c, voxgig_value* a, char** e) {
470
+ (void)a; (void)e;
471
+ return transform_response_util(c);
472
+ }
473
+ static voxgig_value* s_param(Context* c, voxgig_value* a, char** e) {
474
+ (void)e;
475
+ return param_util(c, arg_at(a, 1));
476
+ }
477
+ static voxgig_value* s_make_error(Context* c, voxgig_value* a, char** e) {
478
+ voxgig_value* a1 = arg_at(a, 1);
479
+ voxgig_value* msgv = mget(a1, "message");
480
+ PNError* in = NULL;
481
+ if (msgv && v_is_str(msgv) && voxgig_as_string(msgv)[0]) {
482
+ in = pn_error_new("", voxgig_as_string(msgv));
483
+ }
484
+ PNError* out = NULL;
485
+ voxgig_value* r = make_error_util(c, in, &out);
486
+ ERRSET(e, out);
487
+ return r;
488
+ }
489
+
490
+ /* Sections that take a bare map rather than a ctx. */
491
+ static ProjectNameSDK* SHARED = NULL;
492
+
493
+ static voxgig_value* s_make_context(voxgig_value* a, char** e) {
494
+ (void)e;
495
+ Context* c = corpus_ctx(SHARED, arg_at(a, 0));
496
+ voxgig_value* op = v_map();
497
+ if (c->op) {
498
+ setp(op, "entity", v_str(c->op->entity ? c->op->entity : ""));
499
+ setp(op, "name", v_str(c->op->name ? c->op->name : ""));
500
+ setp(op, "input", v_str(c->op->input ? c->op->input : ""));
501
+ setp(op, "points", c->op->points ? voxgig_retain(c->op->points) : v_list());
502
+ }
503
+ return cmap(1, "op", op);
504
+ }
505
+ static voxgig_value* s_make_options(voxgig_value* a, char** e) {
506
+ (void)e;
507
+ voxgig_value* in = arg_at(a, 0);
508
+ Context* c = corpus_ctx(SHARED, v_map());
509
+ voxgig_value* cf = mget(in, "config");
510
+ if (cf) c->config = voxgig_retain(cf);
511
+ voxgig_value* op = mget(in, "options");
512
+ if (op) c->options = voxgig_retain(op);
513
+ return make_options_util(c);
514
+ }
515
+ static voxgig_value* s_operator(voxgig_value* a, char** e) {
516
+ (void)e;
517
+ voxgig_value* in = arg_at(a, 0);
518
+ voxgig_value* en = mget(in, "entity");
519
+ voxgig_value* nm = mget(in, "name");
520
+ voxgig_value* ip = mget(in, "input");
521
+ voxgig_value* pts = mget(in, "points");
522
+ return cmap(4,
523
+ "entity", en ? voxgig_retain(en) : v_str("_"),
524
+ "input", ip ? voxgig_retain(ip) : v_str("_"),
525
+ "name", nm ? voxgig_retain(nm) : v_str("_"),
526
+ "points", voxgig_is_list(pts) ? voxgig_retain(pts) : v_list());
527
+ }
528
+
529
+ /* ---- main --------------------------------------------------------------- */
530
+
531
+ static ProjectNameSDK* client_for(const char* section) {
532
+ voxgig_value* setup = section_setup(section);
533
+ if (voxgig_is_map(setup)) return test_sdk(v_undef(), voxgig_clone(setup));
534
+ return SHARED;
535
+ }
536
+
537
+ int main(void) {
538
+ SHARED = test_sdk(v_undef(), v_undef());
539
+
540
+ runset("done", SHARED, s_done, NULL);
541
+ runset("makeUrl", SHARED, s_make_url, NULL);
542
+ runset("makeRequest", SHARED, s_make_request, NULL);
543
+ runset("makeResponse", SHARED, s_make_response, NULL);
544
+ runset("makeSpec", client_for("makeSpec"), s_make_spec, NULL);
545
+ runset("prepareAuth", client_for("prepareAuth"), s_prepare_auth, NULL);
546
+ runset("prepareBody", SHARED, s_prepare_body, NULL);
547
+ runset("prepareHeaders", SHARED, s_prepare_headers, NULL);
548
+ runset("prepareMethod", SHARED, s_prepare_method, NULL);
549
+ runset("prepareParams", SHARED, s_prepare_params, NULL);
550
+ runset("preparePath", SHARED, s_prepare_path, NULL);
551
+ runset("prepareQuery", SHARED, s_prepare_query, NULL);
552
+ runset("resultBasic", SHARED, s_result_basic, NULL);
553
+ runset("resultBody", SHARED, s_result_body, NULL);
554
+ runset("resultHeaders", SHARED, s_result_headers, NULL);
555
+ runset("transformRequest", SHARED, s_transform_request, NULL);
556
+ runset("transformResponse", SHARED, s_transform_response, NULL);
557
+ runset("param", SHARED, s_param, NULL);
558
+ runset("makeError", SHARED, s_make_error, NULL);
559
+ runset("makeContext", SHARED, NULL, s_make_context);
560
+ runset("makeOptions", SHARED, NULL, s_make_options);
561
+ runset("operator", SHARED, NULL, s_operator);
562
+
563
+ printf("\nPRIMARY CORPUS: PASS %d FAIL %d\n", NPASS, NFAIL);
564
+ return NFAIL == 0 ? 0 : 1;
565
+ }
@@ -30,5 +30,7 @@ const char* prepare_method_util(Context* ctx) {
30
30
  if (strcmp(op, "list") == 0) return "GET";
31
31
  if (strcmp(op, "remove") == 0) return "DELETE";
32
32
  if (strcmp(op, "patch") == 0) return "PATCH";
33
- return "GET";
33
+ /* No GET catch-all: an unrecognised op must fall through to the
34
+ allow.method gate, not be silently issued as a GET. */
35
+ return "";
34
36
  }
@@ -15,7 +15,8 @@ voxgig_value* transform_request_util(Context* ctx) {
15
15
  if (v_is_noval(reqform) || v_is_null(reqform)) return ctx->reqdata;
16
16
 
17
17
  voxgig_value* store = cmap(1, "reqdata", v_share(ctx->reqdata));
18
- voxgig_value* out = voxgig_transform(store, reqform, NULL);
19
- if (out && !v_is_noval(out)) return out;
20
- return ctx->reqdata;
18
+ /* Return the transform result verbatim, as the ts reference does. Falling
19
+ back to ctx->reqdata meant a reqform naming a path that does not resolve
20
+ sent the WHOLE request data instead of nothing. */
21
+ return voxgig_transform(store, reqform, NULL);
21
22
  }
@@ -125,7 +125,23 @@
125
125
  :ctx ctx :result nil :spec nil}))
126
126
 
127
127
  (defn sdk-error? [x] (and (map? x) (true? (:sdk-error x))))
128
- (defn err-msg [e] (cond (sdk-error? e) (:msg e) (instance? Throwable e) (.getMessage ^Throwable e) :else (str e)))
128
+ (defn err-msg [e]
129
+ (cond
130
+ (sdk-error? e) (:msg e)
131
+ (instance? Throwable e) (.getMessage ^Throwable e)
132
+ ;; A plain error map carrying a message — what a response body or a
133
+ ;; caller-supplied err looks like. The neutral contract spells it
134
+ ;; "message"; without this it fell to (str e) and result-basic reported
135
+ ;; "{message=Foo}: request: 400: BAD" where ts reports "Foo: request: ...".
136
+ ;; java.util.Map, not just clojure's — a struct map is a LinkedHashMap, so
137
+ ;; `map?` is false for exactly the values the corpus supplies.
138
+ (instance? java.util.Map e)
139
+ (let [m (or (.get ^java.util.Map e "message") (.get ^java.util.Map e "msg")
140
+ (get e :message) (get e :msg))]
141
+ ;; An error map with no message IS an unknown error; stringifying it gave
142
+ ;; "{}" where every other target reports "unknown error".
143
+ (if (and (some? m) (not= "" (str m))) (str m) "unknown error"))
144
+ :else (str e)))
129
145
  (defn err-code [e] (when (sdk-error? e) (:code e)))
130
146
  (defn sdk-throw [e] (throw (ex-info (err-msg e) {::sdk-error (if (sdk-error? e) e (make-error-obj "" (str e)))})))
131
147
  (defn ex->sdk [t] (::sdk-error (ex-data t)))
@@ -406,7 +422,13 @@
406
422
  m (when point (vs/getprop point "method"))]
407
423
  (if (and (string? m) (not= "" m))
408
424
  (str/upper-case m)
409
- (get METHOD-MAP (op-name (oget ctx :op)) "GET"))))
425
+ ;; NO CATCH-ALL "GET". The ts reference returns methodMap[key], which is
426
+ ;; undefined for an op the map does not name, so the request is refused
427
+ ;; rather than issued. Defaulting to GET made every unrecognised op — a
428
+ ;; typo, an unsupported operation — a quiet fetch, and the method feeds
429
+ ;; the allow.method gate. ocaml and zig had the identical fallback; the
430
+ ;; shared corpus caught all three.
431
+ (get METHOD-MAP (op-name (oget ctx :op))))))
410
432
 
411
433
  (defn u-prepare-headers [ctx]
412
434
  (let [options (client-options-map (oget ctx :client))
@@ -932,12 +954,40 @@
932
954
  (oget result :resdata)
933
955
  (ucall ctx :make-error nil)))))
934
956
 
957
+ ;; Public camelCase option key -> the kebab-case keyword naming a utility
958
+ ;; member, or nil when the key is not a public name.
959
+ ;;
960
+ ;; Public utility names are camelCase and carry neither a hyphen nor an
961
+ ;; underscore, so either means the caller named something of their own -
962
+ ;; possibly the INTERNAL spelling of a real member. `make-error` must stay an
963
+ ;; extension in :custom; replacing the pipeline function with a non-callable
964
+ ;; would break the error path on the next request, silently.
965
+ ;; `str` is the clojure.string ALIAS in this namespace, so core's str is
966
+ ;; qualified here rather than shadowed.
967
+ (defn- util-member [k]
968
+ (let [n (name k)]
969
+ (when-not (or (str/includes? n "-") (str/includes? n "_"))
970
+ (keyword (str/replace n #"([A-Z])"
971
+ (fn [[_ c]]
972
+ (clojure.core/str "-" (str/lower-case c))))))))
973
+
935
974
  (defn u-make-options [ctx]
936
975
  (let [options (or (oget ctx :options) (vs/jm))
937
976
  custom-utils (vs/getprop options "utility")]
977
+ ;; A key naming a real utility member REPLACES it; anything else is
978
+ ;; attached as a custom extra. Shelving everything in :custom - a map
979
+ ;; nothing reads - made `{"utility" {"fetcher" ...}}`, the documented
980
+ ;; transport seam, a silent no-op here while ts honoured it.
938
981
  (when (and (vs/ismap custom-utils) (oget ctx :utility))
939
- (doseq [item (or (vs/items custom-utils) [])]
940
- (.put ^java.util.Map (oget (oget ctx :utility) :custom) (vs/getprop item 0) (vs/getprop item 1))))
982
+ (let [util (oget ctx :utility)
983
+ members (deref util)]
984
+ (doseq [item (or (vs/items custom-utils) [])]
985
+ (let [k (vs/getprop item 0)
986
+ v (vs/getprop item 1)
987
+ member (util-member k)]
988
+ (if (and member (not= member :custom) (contains? members member))
989
+ (oset! util member v)
990
+ (.put ^java.util.Map (oget util :custom) k v))))))
941
991
  (let [opts0 (let [c (vs/clone options)] (if (vs/ismap c) c (vs/jm)))
942
992
  ;; Feature add-order. options.feature may be given as an ordered
943
993
  ;; ARRAY of {name active ...opts} entries (array position = add
@@ -1001,7 +1051,7 @@
1001
1051
  (if (vs/ismap s) s (vs/jm)))
1002
1052
  sdkname (let [n (vs/getpath config "main.name")]
1003
1053
  (if (and (string? n) (seq n)) n "SDK"))
1004
- resolved (clojure.string/replace
1054
+ resolved (str/replace
1005
1055
  base #"\{([A-Za-z0-9_]+)\}"
1006
1056
  (fn [[_ name]]
1007
1057
  (let [v (vs/getprop server name)