@voxgig/sdkgen 4.31.1 → 4.32.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 (72) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/helpers/definitionPlan.d.ts +5 -0
  3. package/dist/helpers/definitionPlan.js +44 -7
  4. package/dist/helpers/definitionPlan.js.map +1 -1
  5. package/dist/tsconfig.tsbuildinfo +1 -1
  6. package/package.json +1 -1
  7. package/project/.sdk/tm/c/utility/make_url.c +11 -0
  8. package/project/.sdk/tm/c/utility/prepare_headers.c +53 -3
  9. package/project/.sdk/tm/c/utility/prepare_query.c +4 -1
  10. package/project/.sdk/tm/c/utility/transform_request.c +31 -8
  11. package/project/.sdk/tm/clojure/src/sdk/core.clj +62 -15
  12. package/project/.sdk/tm/cpp/utility/pipeline.hpp +72 -15
  13. package/project/.sdk/tm/csharp/utility/MakeUrl.cs +8 -0
  14. package/project/.sdk/tm/csharp/utility/PrepareHeaders.cs +34 -4
  15. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +14 -0
  16. package/project/.sdk/tm/csharp/utility/TransformRequest.cs +31 -5
  17. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +86 -24
  18. package/project/.sdk/tm/go/utility/make_url.go +8 -0
  19. package/project/.sdk/tm/go/utility/prepare_headers.go +36 -7
  20. package/project/.sdk/tm/go/utility/prepare_query.go +11 -0
  21. package/project/.sdk/tm/go/utility/transform_request.go +31 -5
  22. package/project/.sdk/tm/java/utility/MakeUrl.java +8 -0
  23. package/project/.sdk/tm/java/utility/PrepareHeaders.java +30 -6
  24. package/project/.sdk/tm/java/utility/PrepareQuery.java +10 -0
  25. package/project/.sdk/tm/java/utility/TransformRequest.java +30 -5
  26. package/project/.sdk/tm/js/src/utility/MakeUrlUtility.js +7 -0
  27. package/project/.sdk/tm/js/src/utility/PrepareHeadersUtility.js +18 -0
  28. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +3 -1
  29. package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +17 -3
  30. package/project/.sdk/tm/js/test/definition-runner.js +74 -13
  31. package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +8 -0
  32. package/project/.sdk/tm/kotlin/utility/Prepare.kt +34 -4
  33. package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -6
  34. package/project/.sdk/tm/lua/utility/make_url.lua +8 -0
  35. package/project/.sdk/tm/lua/utility/prepare_headers.lua +35 -6
  36. package/project/.sdk/tm/lua/utility/prepare_query.lua +10 -0
  37. package/project/.sdk/tm/lua/utility/transform_request.lua +37 -6
  38. package/project/.sdk/tm/ocaml/sdk_runtime.ml +63 -13
  39. package/project/.sdk/tm/perl/utility/make_url.pm +6 -0
  40. package/project/.sdk/tm/perl/utility/prepare_headers.pm +21 -3
  41. package/project/.sdk/tm/perl/utility/prepare_query.pm +8 -0
  42. package/project/.sdk/tm/perl/utility/transform_request.pm +19 -7
  43. package/project/.sdk/tm/php/utility/MakeUrl.php +7 -0
  44. package/project/.sdk/tm/php/utility/PrepareHeaders.php +33 -4
  45. package/project/.sdk/tm/php/utility/PrepareQuery.php +11 -0
  46. package/project/.sdk/tm/php/utility/TransformRequest.php +28 -5
  47. package/project/.sdk/tm/py/pkg/utility/make_url.py +7 -0
  48. package/project/.sdk/tm/py/pkg/utility/prepare_headers.py +27 -6
  49. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +8 -0
  50. package/project/.sdk/tm/py/pkg/utility/transform_request.py +21 -5
  51. package/project/.sdk/tm/rb/utility/make_url.rb +7 -0
  52. package/project/.sdk/tm/rb/utility/prepare_headers.rb +21 -3
  53. package/project/.sdk/tm/rb/utility/prepare_query.rb +8 -0
  54. package/project/.sdk/tm/rb/utility/transform_request.rb +18 -5
  55. package/project/.sdk/tm/rust/utility/make_url.rs +10 -1
  56. package/project/.sdk/tm/rust/utility/prepare_headers.rs +43 -7
  57. package/project/.sdk/tm/rust/utility/prepare_query.rs +18 -1
  58. package/project/.sdk/tm/rust/utility/transform_request.rs +29 -7
  59. package/project/.sdk/tm/scala/utility/Make.scala +9 -0
  60. package/project/.sdk/tm/scala/utility/Prepare.scala +42 -3
  61. package/project/.sdk/tm/scala/utility/Transform.scala +30 -6
  62. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +7 -0
  63. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +28 -2
  64. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +23 -5
  65. package/project/.sdk/tm/ts/src/utility/MakeUrlUtility.ts +7 -0
  66. package/project/.sdk/tm/ts/src/utility/PrepareHeadersUtility.ts +16 -0
  67. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +3 -1
  68. package/project/.sdk/tm/ts/src/utility/TransformRequestUtility.ts +16 -3
  69. package/project/.sdk/tm/ts/test/definition-runner.ts +75 -13
  70. package/project/.sdk/tm/zig/core/utility.zig +80 -20
  71. package/project/sdkgen-package.json +1 -1
  72. package/src/helpers/definitionPlan.ts +49 -7
@@ -2,12 +2,62 @@
2
2
 
3
3
  #include "sdk.h"
4
4
 
5
+ #include <ctype.h>
6
+ #include <stdlib.h>
7
+ #include <string.h>
8
+
9
+ static bool same_name(const char* a, const char* b) {
10
+ for (; *a && *b; a++, b++) {
11
+ if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) return false;
12
+ }
13
+ return *a == *b;
14
+ }
15
+
5
16
  voxgig_value* prepare_headers_util(Context* ctx) {
6
17
  voxgig_value* options = ctx->client ? sdk_options_map(ctx->client) : ctx->options;
7
18
 
8
19
  voxgig_value* headers = getp(options, "headers");
9
- if (v_is_noval(headers) || v_is_null(headers)) return voxgig_new_map();
20
+ voxgig_value* out = NULL;
21
+ if (!v_is_noval(headers) && !v_is_null(headers)) {
22
+ voxgig_value* cloned = voxgig_clone(headers);
23
+ if (voxgig_is_map(cloned)) out = cloned;
24
+ }
25
+ if (NULL == out) out = voxgig_new_map();
26
+
27
+ // A header parameter travels as a header, under the name the definition
28
+ // gives it, and only from this call's own arguments. It replaces a default
29
+ // of the same name, whatever its case.
30
+ voxgig_value* aheader = getpath2(ctx->point, "args", "header");
31
+ if (voxgig_is_list(aheader)) {
32
+ voxgig_list* hl = voxgig_as_list(aheader);
33
+ for (size_t i = 0; i < hl->len; i++) {
34
+ voxgig_value* name = getp(hl->items[i], "name");
35
+ if (!voxgig_is_string(name) || voxgig_as_string(name)[0] == '\0') continue;
36
+ voxgig_value* orig = getp(hl->items[i], "orig");
37
+ const char* wire = voxgig_is_string(orig) && voxgig_as_string(orig)[0] != '\0'
38
+ ? voxgig_as_string(orig) : voxgig_as_string(name);
39
+
40
+ voxgig_value* val = getp(ctx->reqmatch, voxgig_as_string(name));
41
+ if (v_is_noval(val) || v_is_null(val)) val = getp(ctx->reqdata, voxgig_as_string(name));
42
+ if (v_is_noval(val) || v_is_null(val)) continue;
43
+
44
+ size_t n = strlen(wire);
45
+ char* key = (char*)malloc(n + 1);
46
+ for (size_t k = 0; k < n; k++) key[k] = (char)tolower((unsigned char)wire[k]);
47
+ key[n] = '\0';
48
+ voxgig_map* om = voxgig_as_map(out);
49
+ for (size_t j = om->len; j > 0; j--) {
50
+ if (same_name(om->entries[j - 1].key, key)) {
51
+ voxgig_value* dk = voxgig_new_string(om->entries[j - 1].key);
52
+ voxgig_delprop(out, dk);
53
+ voxgig_release(dk);
54
+ }
55
+ }
56
+ char* text = voxgig_stringify(val, -1);
57
+ setp(out, key, voxgig_new_string_take(text, strlen(text)));
58
+ free(key);
59
+ }
60
+ }
10
61
 
11
- voxgig_value* cloned = voxgig_clone(headers);
12
- return voxgig_is_map(cloned) ? cloned : voxgig_new_map();
62
+ return out;
13
63
  }
@@ -52,6 +52,8 @@ voxgig_value* prepare_query_util(Context* ctx) {
52
52
  voxgig_value* params = getp(point, "params");
53
53
  if (!voxgig_is_list(params)) params = voxgig_new_list();
54
54
  voxgig_value* aparams = getpath2(point, "args", "params");
55
+ // A header parameter travels in the headers, which prepare_headers fills.
56
+ voxgig_value* aheader = getpath2(point, "args", "header");
55
57
  voxgig_value* aquery = getpath2(point, "args", "query");
56
58
 
57
59
  voxgig_value* out = voxgig_new_map();
@@ -61,7 +63,8 @@ voxgig_value* prepare_query_util(Context* ctx) {
61
63
  const char* key = rm->entries[i].key;
62
64
  voxgig_value* val = rm->entries[i].value;
63
65
  if (!v_is_noval(val) && !v_is_null(val) && strcmp(key, "$action") != 0 &&
64
- !params_contains(params, key) && !args_params_contains(aparams, key)) {
66
+ !params_contains(params, key) && !args_params_contains(aparams, key) &&
67
+ !args_params_contains(aheader, key)) {
65
68
  setp(out, query_wire_name(aquery, key), v_share(val));
66
69
  }
67
70
  }
@@ -4,39 +4,62 @@
4
4
 
5
5
  #include <string.h>
6
6
 
7
+ /* A header argument travels as a header, which prepare_headers_util sends,
8
+ so the body is built from the request data without it. */
9
+ static bool header_arg(voxgig_value* point, const char* key) {
10
+ voxgig_value* aheader = getpath2(point, "args", "header");
11
+ if (!voxgig_is_list(aheader)) return false;
12
+ voxgig_list* hl = voxgig_as_list(aheader);
13
+ for (size_t i = 0; i < hl->len; i++) {
14
+ voxgig_value* name = getp(hl->items[i], "name");
15
+ if (voxgig_is_string(name) && strcmp(voxgig_as_string(name), key) == 0) return true;
16
+ }
17
+ return false;
18
+ }
19
+
7
20
  /* `$action` selects the point (see make_point_util); it is never an API
8
- field, so the body is a copy without it. The caller's map is left
9
- untouched. */
10
- static voxgig_value* strip_action(voxgig_value* reqdata) {
21
+ field, so the body is a copy without it, or, given a point, without its
22
+ header arguments. The caller's map is left untouched. */
23
+ static bool dropped(const char* key, voxgig_value* point) {
24
+ return NULL == point ? strcmp(key, "$action") == 0 : header_arg(point, key);
25
+ }
26
+
27
+ static voxgig_value* omit_keys(voxgig_value* reqdata, voxgig_value* point) {
11
28
  if (!voxgig_is_map(reqdata)) return reqdata;
12
29
  voxgig_map* rm = voxgig_as_map(reqdata);
13
30
  bool has = false;
14
31
  for (size_t i = 0; i < rm->len; i++) {
15
- if (strcmp(rm->entries[i].key, "$action") == 0) { has = true; break; }
32
+ if (dropped(rm->entries[i].key, point)) { has = true; break; }
16
33
  }
17
34
  if (!has) return reqdata;
18
35
  voxgig_value* body = voxgig_new_map();
19
36
  for (size_t i = 0; i < rm->len; i++) {
20
- if (strcmp(rm->entries[i].key, "$action") != 0) {
37
+ if (!dropped(rm->entries[i].key, point)) {
21
38
  setp(body, rm->entries[i].key, v_share(rm->entries[i].value));
22
39
  }
23
40
  }
24
41
  return body;
25
42
  }
26
43
 
44
+ static voxgig_value* strip_action(voxgig_value* reqdata) {
45
+ return omit_keys(reqdata, NULL);
46
+ }
47
+
27
48
  voxgig_value* transform_request_util(Context* ctx) {
28
49
  Spec* spec = ctx->spec;
29
50
  voxgig_value* point = ctx->point;
30
51
 
31
52
  if (spec) spec_set_step(spec, "reqform");
32
53
 
54
+ voxgig_value* data = NULL == point ? ctx->reqdata : omit_keys(ctx->reqdata, point);
55
+
33
56
  voxgig_value* transform = to_map(getp(point, "transform"));
34
- if (v_is_noval(transform)) return strip_action(ctx->reqdata);
57
+ if (v_is_noval(transform)) return strip_action(data);
35
58
 
36
59
  voxgig_value* reqform = getp(transform, "req");
37
- if (v_is_noval(reqform) || v_is_null(reqform)) return strip_action(ctx->reqdata);
60
+ if (v_is_noval(reqform) || v_is_null(reqform)) return strip_action(data);
38
61
 
39
- voxgig_value* store = cmap(1, "reqdata", v_share(ctx->reqdata));
62
+ voxgig_value* store = cmap(1, "reqdata", v_share(data));
40
63
  /* Return the transform result verbatim, as the ts reference does. Falling
41
64
  back to ctx->reqdata meant a reqform naming a path that does not resolve
42
65
  sent the WHOLE request data instead of nothing. */
@@ -450,9 +450,30 @@
450
450
 
451
451
  (defn u-prepare-headers [ctx]
452
452
  (let [options (client-options-map (oget ctx :client))
453
- headers (vs/getprop options "headers")]
454
- (if (nil? headers) (vs/jm)
455
- (let [o (vs/clone headers)] (if (vs/ismap o) o (vs/jm))))))
453
+ headers (vs/getprop options "headers")
454
+ out (if (nil? headers) (vs/jm)
455
+ (let [o (vs/clone headers)] (if (vs/ismap o) o (vs/jm))))
456
+ point (oget ctx :point)
457
+ args (when point (vs/getprop point "args"))
458
+ aheader (let [h (when (vs/ismap args) (vs/getprop args "header"))]
459
+ (if (vs/islist h) h (vs/jt)))]
460
+ ;; A header parameter travels as a header, under the name the definition
461
+ ;; gives it, and only from this call's own arguments. It replaces a default
462
+ ;; of the same name, whatever its case.
463
+ (doseq [hd (vec aheader)]
464
+ (let [name (when (vs/ismap hd) (vs/getprop hd "name"))]
465
+ (when (and (string? name) (seq name))
466
+ (let [orig (vs/getprop hd "orig")
467
+ wire (if (and (string? orig) (seq orig)) orig name)
468
+ v (vs/getprop (oget ctx :reqmatch) name)
469
+ v (if (nil? v) (vs/getprop (oget ctx :reqdata) name) v)]
470
+ (when (some? v)
471
+ (let [key (str/lower-case wire)]
472
+ (doseq [k (vec (.keySet ^java.util.Map out))]
473
+ (when (and (string? k) (= key (str/lower-case k)))
474
+ (.remove ^java.util.Map out k)))
475
+ (.put ^java.util.Map out key (vs/stringify v))))))))
476
+ out))
456
477
 
457
478
  (defn u-param [ctx paramdef]
458
479
  (let [point (oget ctx :point)
@@ -506,8 +527,14 @@
506
527
  aparams (let [args (when point (vs/getprop point "args"))
507
528
  p (when (vs/ismap args) (vs/getprop args "params"))]
508
529
  (if (vs/islist p) p (vs/jt)))
509
- pset (into (into #{} (vec params))
510
- (keep (fn [pd] (when (vs/ismap pd) (vs/getprop pd "name"))) (vec aparams)))
530
+ ;; A header parameter travels in the headers, which u-prepare-headers
531
+ ;; fills.
532
+ aheader (let [args (when point (vs/getprop point "args"))
533
+ h (when (vs/ismap args) (vs/getprop args "header"))]
534
+ (if (vs/islist h) h (vs/jt)))
535
+ pset (into (into (into #{} (vec params))
536
+ (keep (fn [pd] (when (vs/ismap pd) (vs/getprop pd "name"))) (vec aparams)))
537
+ (keep (fn [hd] (when (vs/ismap hd) (vs/getprop hd "name"))) (vec aheader)))
511
538
  ;; A query parameter travels under the name the definition gives it,
512
539
  ;; its orig, which the model may have renamed for the caller.
513
540
  aquery (let [args (when point (vs/getprop point "args"))
@@ -526,26 +553,39 @@
526
553
  (.put ^java.util.Map out (get wire k k) v))))
527
554
  out))
528
555
 
529
- ;; `$action` selects the point (see u-make-point); it is never an API field,
530
- ;; so the body is a copy without it. The caller's map is left untouched.
531
- (defn- strip-action [reqdata]
532
- (if (and (vs/ismap reqdata) (.containsKey ^java.util.Map reqdata "$action"))
556
+ (defn- omit-keys [reqdata names]
557
+ (if (and (vs/ismap reqdata) (some #(.containsKey ^java.util.Map reqdata %) names))
533
558
  (let [body (vs/jm)]
534
559
  (doseq [item (or (vs/items reqdata) [])]
535
560
  (let [k (vs/getprop item 0) v (vs/getprop item 1)]
536
- (when (not= "$action" k) (.put ^java.util.Map body k v))))
561
+ (when-not (some #(= k %) names) (.put ^java.util.Map body k v))))
537
562
  body)
538
563
  reqdata))
539
564
 
565
+ ;; `$action` selects the point (see u-make-point); it is never an API field,
566
+ ;; so the body is a copy without it. The caller's map is left untouched.
567
+ (defn- strip-action [reqdata] (omit-keys reqdata ["$action"]))
568
+
569
+ ;; A header argument travels as a header, which u-prepare-headers sends, so the
570
+ ;; body is built from the request data without it.
571
+ (defn- header-arg-names [point]
572
+ (let [args (when point (vs/getprop point "args"))
573
+ h (when (vs/ismap args) (vs/getprop args "header"))]
574
+ (if (vs/islist h)
575
+ (filterv #(and (string? %) (seq %))
576
+ (map #(when (vs/ismap %) (vs/getprop % "name")) (vec h)))
577
+ [])))
578
+
540
579
  (defn u-transform-request [ctx]
541
- (let [spec (oget ctx :spec) point (oget ctx :point)]
580
+ (let [spec (oget ctx :spec) point (oget ctx :point)
581
+ data (omit-keys (oget ctx :reqdata) (header-arg-names point))]
542
582
  (when spec (oset! spec :step "reqform"))
543
583
  (let [transform (to-map (vs/getprop point "transform"))]
544
584
  (strip-action
545
- (if (nil? transform) (oget ctx :reqdata)
585
+ (if (nil? transform) data
546
586
  (let [reqform (vs/getprop transform "req")]
547
- (if (nil? reqform) (oget ctx :reqdata)
548
- (vs/transform (vs/jm "reqdata" (oget ctx :reqdata)) reqform))))))))
587
+ (if (nil? reqform) data
588
+ (vs/transform (vs/jm "reqdata" data) reqform))))))))
549
589
 
550
590
  (defn u-prepare-body [ctx]
551
591
  (if (= "data" (op-input (oget ctx :op))) (ucall ctx :transform-request) nil))
@@ -817,7 +857,14 @@
817
857
  (nil? result) ["" (ctx-error ctx "url_no_result" "Expected context result property to be defined.")]
818
858
  :else
819
859
  (let [url (atom (vs/join (vs/jt (oget spec :base) (oget spec :prefix) (oget spec :path) (oget spec :suffix)) "/" true))
820
- resmatch (vs/jm)]
860
+ resmatch (vs/jm)
861
+ point (oget ctx :point)
862
+ orig (when point (vs/getprop point "orig"))]
863
+ ;; A route the definition ends with a slash keeps it: a server such as a
864
+ ;; Django REST one redirects or refuses the route without it.
865
+ (when (and (string? orig) (str/ends-with? orig "/") (empty? (str (oget spec :suffix)))
866
+ (not (str/ends-with? @url "/")))
867
+ (swap! url str "/"))
821
868
  (doseq [item (or (vs/items (oget spec :params)) [])]
822
869
  (let [k (vs/getprop item 0) v (vs/getprop item 1)]
823
870
  (when (and (some? v) (string? k))
@@ -281,6 +281,14 @@ inline std::string makeUrl(CtxPtr ctx) {
281
281
  Value joinParts = vlist({Value(spec->base), Value(spec->prefix), Value(spec->path), Value(spec->suffix)});
282
282
  std::string url = Struct::join(joinParts, "/", true);
283
283
 
284
+ // A route the definition ends with a slash keeps it: a server such as a
285
+ // Django REST one redirects or refuses the route without it.
286
+ Value orig = ctx->point.is_map() ? getp(ctx->point, "orig") : Value::undef();
287
+ if (orig.is_string() && !orig.as_string().empty() && '/' == orig.as_string().back() &&
288
+ spec->suffix.empty() && (url.empty() || '/' != url.back())) {
289
+ url += "/";
290
+ }
291
+
284
292
  Value resmatch = vmap();
285
293
 
286
294
  for (const auto& item : Struct::items(spec->params)) {
@@ -921,9 +929,35 @@ inline Value prepareBody(CtxPtr ctx) {
921
929
  inline Value prepareHeaders(CtxPtr ctx) {
922
930
  Value options = ctx->client->optionsMap();
923
931
  Value headers = getp(options, "headers");
924
- if (is_nullish(headers)) return vmap();
925
- Value out = Helpers::toMapAny(Struct::clone(headers));
926
- return out.is_map() ? out : vmap();
932
+ Value out = is_nullish(headers) ? vmap() : Helpers::toMapAny(Struct::clone(headers));
933
+ if (!out.is_map()) out = vmap();
934
+
935
+ // A header parameter travels as a header, under the name the definition
936
+ // gives it, and only from this call's own arguments. It replaces a default
937
+ // of the same name, whatever its case.
938
+ auto lower = [](std::string s) {
939
+ for (auto& ch : s) ch = (char)std::tolower((unsigned char)ch);
940
+ return s;
941
+ };
942
+ Value aheader = ctx->point.is_map() ? getp(getp(ctx->point, "args"), "header") : Value::undef();
943
+ if (aheader.is_list()) {
944
+ for (const auto& hd : *aheader.as_list()) {
945
+ Value name = getp(hd, "name");
946
+ if (!name.is_string() || name.as_string().empty()) continue;
947
+ Value orig = getp(hd, "orig");
948
+ std::string wire = lower(orig.is_string() && !orig.as_string().empty() ?
949
+ orig.as_string() : name.as_string());
950
+ Value val = getp(ctx->reqmatch, name.as_string(), Value(nullptr));
951
+ if (val.is_null()) val = getp(ctx->reqdata, name.as_string(), Value(nullptr));
952
+ if (is_nullish(val)) continue;
953
+ for (const auto& item : Struct::items(out)) {
954
+ std::string key = as_str(pair_key(item));
955
+ if (lower(key) == wire) out.as_map()->erase(key);
956
+ }
957
+ map_put(out, wire, Value(Struct::stringify(val)));
958
+ }
959
+ }
960
+ return out;
927
961
  }
928
962
 
929
963
  // ---- param ------------------------------------------------------------
@@ -1024,10 +1058,12 @@ inline Value prepareQuery(CtxPtr ctx) {
1024
1058
 
1025
1059
  // A path parameter travels in the path. The generated config lists them as
1026
1060
  // args.params, which prepareParams reads; params is the older list of names.
1061
+ // A header parameter travels in the headers, which prepareHeaders fills.
1027
1062
  Value aparams = point.is_map() ? getp(getp(point, "args"), "params") : Value::undef();
1028
- auto in_args = [&](const std::string& s) {
1029
- if (!aparams.is_list()) return false;
1030
- for (const auto& pd : *aparams.as_list()) {
1063
+ Value aheader = point.is_map() ? getp(getp(point, "args"), "header") : Value::undef();
1064
+ auto named = [&](const Value& defs, const std::string& s) {
1065
+ if (!defs.is_list()) return false;
1066
+ for (const auto& pd : *defs.as_list()) {
1031
1067
  Value name = getp(pd, "name");
1032
1068
  if (name.is_string() && name.as_string() == s) return true;
1033
1069
  }
@@ -1055,7 +1091,8 @@ inline Value prepareQuery(CtxPtr ctx) {
1055
1091
  for (const auto& item : Struct::items(reqmatch)) {
1056
1092
  std::string key = as_str(pair_key(item));
1057
1093
  Value val = pair_val(item);
1058
- if (!is_nullish(val) && "$action" != key && !contains_str(params, key) && !in_args(key)) {
1094
+ if (!is_nullish(val) && "$action" != key && !contains_str(params, key) &&
1095
+ !named(aparams, key) && !named(aheader, key)) {
1059
1096
  map_put(out, wire_name(key), val);
1060
1097
  }
1061
1098
  }
@@ -1079,29 +1116,49 @@ inline std::string preparePath(CtxPtr ctx) {
1079
1116
 
1080
1117
  // ---- transformRequest -------------------------------------------------
1081
1118
 
1082
- // `$action` selects the point (see makePoint); it is never an API field, so
1083
- // the body is a copy without it. The caller's map is left untouched.
1084
- inline Value stripAction(const Value& reqdata) {
1085
- if (!map_contains(reqdata, "$action")) return reqdata;
1119
+ inline Value omitKeys(const Value& reqdata, const std::vector<std::string>& names) {
1120
+ bool has = false;
1121
+ for (const auto& name : names) has = has || map_contains(reqdata, name);
1122
+ if (!has) return reqdata;
1086
1123
  Value body = vmap();
1087
1124
  for (const auto& item : Struct::items(reqdata)) {
1088
1125
  std::string key = as_str(pair_key(item));
1089
- if ("$action" != key) map_put(body, key, pair_val(item));
1126
+ if (std::find(names.begin(), names.end(), key) == names.end()) map_put(body, key, pair_val(item));
1090
1127
  }
1091
1128
  return body;
1092
1129
  }
1093
1130
 
1131
+ // `$action` selects the point (see makePoint); it is never an API field, so
1132
+ // the body is a copy without it. The caller's map is left untouched.
1133
+ inline Value stripAction(const Value& reqdata) { return omitKeys(reqdata, {"$action"}); }
1134
+
1135
+ // A header argument travels as a header, which prepareHeaders sends, so the
1136
+ // body is built from the request data without it.
1137
+ inline std::vector<std::string> headerArgNames(CtxPtr ctx) {
1138
+ std::vector<std::string> names;
1139
+ Value aheader = ctx->point.is_map() ? getp(getp(ctx->point, "args"), "header") : Value::undef();
1140
+ if (aheader.is_list()) {
1141
+ for (const auto& hd : *aheader.as_list()) {
1142
+ Value name = getp(hd, "name");
1143
+ if (name.is_string() && !name.as_string().empty()) names.push_back(name.as_string());
1144
+ }
1145
+ }
1146
+ return names;
1147
+ }
1148
+
1094
1149
  inline Value transformRequest(CtxPtr ctx) {
1095
1150
  if (ctx->spec) ctx->spec->step = "reqform";
1096
1151
 
1152
+ Value reqdata = omitKeys(ctx->reqdata, headerArgNames(ctx));
1153
+
1097
1154
  Value transform = Helpers::toMapAny(getp(ctx->point, "transform"));
1098
- if (!transform.is_map()) return stripAction(ctx->reqdata);
1155
+ if (!transform.is_map()) return stripAction(reqdata);
1099
1156
 
1100
1157
  Value reqform = getp(transform, "req");
1101
- if (is_nullish(reqform)) return stripAction(ctx->reqdata);
1158
+ if (is_nullish(reqform)) return stripAction(reqdata);
1102
1159
 
1103
1160
  Value data = vmap();
1104
- map_put(data, "reqdata", ctx->reqdata);
1161
+ map_put(data, "reqdata", reqdata);
1105
1162
  return stripAction(Struct::transform(data, reqform));
1106
1163
  }
1107
1164
 
@@ -20,6 +20,14 @@ public static partial class SdkUtility
20
20
  StructUtils.Jt(spec.Base, spec.Prefix, spec.Path, spec.Suffix), "/", true);
21
21
  var resmatch = new Dictionary<string, object?>();
22
22
 
23
+ // A route the definition ends with a slash keeps it: a server such as
24
+ // a Django REST one redirects or refuses the route without it.
25
+ if (ctx.Point != null && StructUtils.GetProp(ctx.Point, "orig") is string orig &&
26
+ orig.EndsWith("/") && string.IsNullOrEmpty(spec.Suffix) && !url.EndsWith("/"))
27
+ {
28
+ url += "/";
29
+ }
30
+
23
31
  foreach (var item in StructUtils.Items(spec.Params))
24
32
  {
25
33
  var key = item[0] as string ?? "";
@@ -11,12 +11,42 @@ public static partial class SdkUtility
11
11
  var options = ctx.Client!.OptionsMap();
12
12
 
13
13
  var headers = StructUtils.GetProp(options, "headers");
14
- if (headers == null)
14
+ var result = (headers == null ? null : StructUtils.Clone(headers) as Dictionary<string, object?>)
15
+ ?? new Dictionary<string, object?>();
16
+
17
+ // A header parameter travels as a header, under the name the
18
+ // definition gives it, and only from this call's own arguments. It
19
+ // replaces a default of the same name, whatever its case.
20
+ if (ctx.Point != null &&
21
+ StructUtils.GetPath(ctx.Point, StructUtils.Jt("args", "header")) is List<object?> hl)
15
22
  {
16
- return new Dictionary<string, object?>();
23
+ foreach (var hd in hl)
24
+ {
25
+ if (StructUtils.GetProp(hd, "name") is not string name || name == "")
26
+ {
27
+ continue;
28
+ }
29
+ var wire = StructUtils.GetProp(hd, "orig") is string orig && orig != "" ? orig : name;
30
+ var val = ctx.Reqmatch == null ? null : StructUtils.GetProp(ctx.Reqmatch, name);
31
+ if (val == null && ctx.Reqdata != null)
32
+ {
33
+ val = StructUtils.GetProp(ctx.Reqdata, name);
34
+ }
35
+ if (val != null)
36
+ {
37
+ var key = wire.ToLowerInvariant();
38
+ foreach (var k in new List<string>(result.Keys))
39
+ {
40
+ if (k.ToLowerInvariant() == key)
41
+ {
42
+ result.Remove(k);
43
+ }
44
+ }
45
+ result[key] = StructUtils.Stringify(val);
46
+ }
47
+ }
17
48
  }
18
49
 
19
- return StructUtils.Clone(headers) as Dictionary<string, object?>
20
- ?? new Dictionary<string, object?>();
50
+ return result;
21
51
  }
22
52
  }
@@ -31,6 +31,20 @@ public static partial class SdkUtility
31
31
  }
32
32
  }
33
33
 
34
+ // A header parameter travels in the headers, which PrepareHeaders
35
+ // fills.
36
+ if (point != null &&
37
+ StructUtils.GetPath(point, StructUtils.Jt("args", "header")) is List<object?> ahl)
38
+ {
39
+ foreach (var hd in ahl)
40
+ {
41
+ if (StructUtils.GetProp(hd, "name") is string hname)
42
+ {
43
+ paramnames.Add(hname);
44
+ }
45
+ }
46
+ }
47
+
34
48
  // A query parameter travels under the name the definition gives it,
35
49
  // its orig, which the model may have renamed for the caller.
36
50
  var wire = new Dictionary<string, string>();
@@ -17,21 +17,23 @@ public static partial class SdkUtility
17
17
  spec.Step = "reqform";
18
18
  }
19
19
 
20
+ var data = OmitKeys(ctx.Reqdata, HeaderArgNames(point));
21
+
20
22
  var transform = Helpers.ToMapAny(StructUtils.GetProp(point, "transform"));
21
23
  if (transform == null)
22
24
  {
23
- return StripAction(ctx.Reqdata);
25
+ return StripAction(data);
24
26
  }
25
27
 
26
28
  var reqform = StructUtils.GetProp(transform, "req");
27
29
  if (reqform == null)
28
30
  {
29
- return StripAction(ctx.Reqdata);
31
+ return StripAction(data);
30
32
  }
31
33
 
32
34
  var reqdata = StructUtils.Transform(new Dictionary<string, object?>
33
35
  {
34
- ["reqdata"] = ctx.Reqdata,
36
+ ["reqdata"] = data,
35
37
  }, reqform);
36
38
 
37
39
  return StripAction(reqdata);
@@ -42,14 +44,38 @@ public static partial class SdkUtility
42
44
  // untouched.
43
45
  private static object? StripAction(object? reqdata)
44
46
  {
45
- if (reqdata is not IDictionary<string, object?> src || !src.ContainsKey("$action"))
47
+ return OmitKeys(reqdata, new List<string> { "$action" });
48
+ }
49
+
50
+ // A header argument travels as a header, which PrepareHeadersUtil sends,
51
+ // so the body is built from the request data without it.
52
+ private static List<string> HeaderArgNames(object? point)
53
+ {
54
+ var names = new List<string>();
55
+ if (point != null &&
56
+ StructUtils.GetPath(point, StructUtils.Jt("args", "header")) is List<object?> hl)
57
+ {
58
+ foreach (var hd in hl)
59
+ {
60
+ if (StructUtils.GetProp(hd, "name") is string name && name != "")
61
+ {
62
+ names.Add(name);
63
+ }
64
+ }
65
+ }
66
+ return names;
67
+ }
68
+
69
+ private static object? OmitKeys(object? reqdata, List<string> names)
70
+ {
71
+ if (reqdata is not IDictionary<string, object?> src || !names.Exists(src.ContainsKey))
46
72
  {
47
73
  return reqdata;
48
74
  }
49
75
  var body = new Dictionary<string, object?>();
50
76
  foreach (var kv in src)
51
77
  {
52
- if ("$action" != kv.Key)
78
+ if (!names.Contains(kv.Key))
53
79
  {
54
80
  body[kv.Key] = kv.Value;
55
81
  }