@voxgig/sdkgen 4.31.0 → 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 (76) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/package.d.ts +2 -1
  3. package/dist/action/package.js +8 -1
  4. package/dist/action/package.js.map +1 -1
  5. package/dist/helpers/definitionPlan.d.ts +5 -0
  6. package/dist/helpers/definitionPlan.js +44 -7
  7. package/dist/helpers/definitionPlan.js.map +1 -1
  8. package/dist/tsconfig.tsbuildinfo +1 -1
  9. package/package.json +1 -1
  10. package/project/.sdk/tm/c/utility/make_url.c +11 -0
  11. package/project/.sdk/tm/c/utility/prepare_headers.c +53 -3
  12. package/project/.sdk/tm/c/utility/prepare_query.c +4 -1
  13. package/project/.sdk/tm/c/utility/transform_request.c +31 -8
  14. package/project/.sdk/tm/clojure/src/sdk/core.clj +62 -15
  15. package/project/.sdk/tm/cpp/utility/pipeline.hpp +72 -15
  16. package/project/.sdk/tm/csharp/utility/MakeUrl.cs +8 -0
  17. package/project/.sdk/tm/csharp/utility/PrepareHeaders.cs +34 -4
  18. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +14 -0
  19. package/project/.sdk/tm/csharp/utility/TransformRequest.cs +31 -5
  20. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +86 -24
  21. package/project/.sdk/tm/go/utility/make_url.go +8 -0
  22. package/project/.sdk/tm/go/utility/prepare_headers.go +36 -7
  23. package/project/.sdk/tm/go/utility/prepare_query.go +11 -0
  24. package/project/.sdk/tm/go/utility/transform_request.go +31 -5
  25. package/project/.sdk/tm/java/utility/MakeUrl.java +8 -0
  26. package/project/.sdk/tm/java/utility/PrepareHeaders.java +30 -6
  27. package/project/.sdk/tm/java/utility/PrepareQuery.java +10 -0
  28. package/project/.sdk/tm/java/utility/TransformRequest.java +30 -5
  29. package/project/.sdk/tm/js/src/utility/MakeUrlUtility.js +7 -0
  30. package/project/.sdk/tm/js/src/utility/PrepareHeadersUtility.js +18 -0
  31. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +3 -1
  32. package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +17 -3
  33. package/project/.sdk/tm/js/test/definition-runner.js +74 -13
  34. package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +8 -0
  35. package/project/.sdk/tm/kotlin/utility/Prepare.kt +34 -4
  36. package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -6
  37. package/project/.sdk/tm/lua/utility/make_url.lua +8 -0
  38. package/project/.sdk/tm/lua/utility/prepare_headers.lua +35 -6
  39. package/project/.sdk/tm/lua/utility/prepare_query.lua +10 -0
  40. package/project/.sdk/tm/lua/utility/transform_request.lua +37 -6
  41. package/project/.sdk/tm/ocaml/sdk_runtime.ml +63 -13
  42. package/project/.sdk/tm/perl/utility/make_url.pm +6 -0
  43. package/project/.sdk/tm/perl/utility/prepare_headers.pm +21 -3
  44. package/project/.sdk/tm/perl/utility/prepare_query.pm +8 -0
  45. package/project/.sdk/tm/perl/utility/transform_request.pm +19 -7
  46. package/project/.sdk/tm/php/utility/MakeUrl.php +7 -0
  47. package/project/.sdk/tm/php/utility/PrepareHeaders.php +33 -4
  48. package/project/.sdk/tm/php/utility/PrepareQuery.php +11 -0
  49. package/project/.sdk/tm/php/utility/TransformRequest.php +28 -5
  50. package/project/.sdk/tm/py/pkg/utility/make_url.py +7 -0
  51. package/project/.sdk/tm/py/pkg/utility/prepare_headers.py +27 -6
  52. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +8 -0
  53. package/project/.sdk/tm/py/pkg/utility/transform_request.py +21 -5
  54. package/project/.sdk/tm/rb/utility/make_url.rb +7 -0
  55. package/project/.sdk/tm/rb/utility/prepare_headers.rb +21 -3
  56. package/project/.sdk/tm/rb/utility/prepare_query.rb +8 -0
  57. package/project/.sdk/tm/rb/utility/transform_request.rb +18 -5
  58. package/project/.sdk/tm/rust/utility/make_url.rs +10 -1
  59. package/project/.sdk/tm/rust/utility/prepare_headers.rs +43 -7
  60. package/project/.sdk/tm/rust/utility/prepare_query.rs +18 -1
  61. package/project/.sdk/tm/rust/utility/transform_request.rs +29 -7
  62. package/project/.sdk/tm/scala/utility/Make.scala +9 -0
  63. package/project/.sdk/tm/scala/utility/Prepare.scala +42 -3
  64. package/project/.sdk/tm/scala/utility/Transform.scala +30 -6
  65. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +7 -0
  66. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +28 -2
  67. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +23 -5
  68. package/project/.sdk/tm/ts/src/utility/MakeUrlUtility.ts +7 -0
  69. package/project/.sdk/tm/ts/src/utility/PrepareHeadersUtility.ts +16 -0
  70. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +3 -1
  71. package/project/.sdk/tm/ts/src/utility/TransformRequestUtility.ts +16 -3
  72. package/project/.sdk/tm/ts/test/definition-runner.ts +75 -13
  73. package/project/.sdk/tm/zig/core/utility.zig +80 -20
  74. package/project/sdkgen-package.json +1 -1
  75. package/src/action/package.ts +10 -1
  76. package/src/helpers/definitionPlan.ts +49 -7
@@ -1003,13 +1003,24 @@ defmodule ProjectName.Utility do
1003
1003
  {"", Context.make_error(ctx, "url_no_result", "Expected context result property to be defined.")}
1004
1004
 
1005
1005
  true ->
1006
- url0 =
1006
+ joined =
1007
1007
  S.join(
1008
1008
  S.jt([S.getprop(spec, "base"), S.getprop(spec, "prefix"), S.getprop(spec, "path"), S.getprop(spec, "suffix")]),
1009
1009
  "/",
1010
1010
  true
1011
1011
  )
1012
1012
 
1013
+ # A route the definition ends with a slash keeps it: a server such as a
1014
+ # Django REST one redirects or refuses the route without it.
1015
+ orig = S.getprop(S.getprop(ctx, "point"), "orig")
1016
+ suffix = S.getprop(spec, "suffix")
1017
+
1018
+ url0 =
1019
+ if is_binary(orig) and String.ends_with?(orig, "/") and (suffix == nil or suffix == "") and
1020
+ not String.ends_with?(joined, "/"),
1021
+ do: joined <> "/",
1022
+ else: joined
1023
+
1013
1024
  resmatch = S.jm([])
1014
1025
 
1015
1026
  url1 =
@@ -1125,12 +1136,44 @@ defmodule ProjectName.Utility do
1125
1136
  options = opts_map(S.getprop(ctx, "client"))
1126
1137
  headers = S.getprop(options, "headers")
1127
1138
 
1128
- if headers == nil do
1129
- S.jm([])
1130
- else
1131
- out = S.clone(headers)
1132
- if S.ismap(out), do: out, else: S.jm([])
1139
+ out =
1140
+ if headers == nil do
1141
+ S.jm([])
1142
+ else
1143
+ cloned = S.clone(headers)
1144
+ if S.ismap(cloned), do: cloned, else: S.jm([])
1145
+ end
1146
+
1147
+ # A header parameter travels as a header, under the name the definition
1148
+ # gives it, and only from this call's own arguments. It replaces a default
1149
+ # of the same name, whatever its case.
1150
+ point = S.getprop(ctx, "point")
1151
+ aheader = if point != nil, do: S.getpath(point, "args.header"), else: nil
1152
+
1153
+ if S.islist(aheader) and S.size(aheader) > 0 do
1154
+ Enum.each(0..(S.size(aheader) - 1), fn i ->
1155
+ hd = S.getelem(aheader, i)
1156
+ name = S.getprop(hd, "name")
1157
+
1158
+ if is_binary(name) and name != "" do
1159
+ orig = S.getprop(hd, "orig")
1160
+ wire = if is_binary(orig) and orig != "", do: orig, else: name
1161
+ val = S.getprop(S.getprop(ctx, "reqmatch"), name)
1162
+ val = if val == nil, do: S.getprop(S.getprop(ctx, "reqdata"), name), else: val
1163
+ if val != nil do
1164
+ key = String.downcase(wire)
1165
+
1166
+ Enum.each(H.entries(out), fn {k, _} ->
1167
+ if is_binary(k) and String.downcase(k) == key, do: S.delprop(out, k)
1168
+ end)
1169
+
1170
+ S.setprop(out, key, S.stringify(val))
1171
+ end
1172
+ end
1173
+ end)
1133
1174
  end
1175
+
1176
+ out
1134
1177
  end
1135
1178
 
1136
1179
  def prepare_body_impl(ctx) do
@@ -1187,6 +1230,19 @@ defmodule ProjectName.Utility do
1187
1230
  S.join(parts, "/", true)
1188
1231
  end
1189
1232
 
1233
+ # The names in one of a point's lists of argument definitions.
1234
+ defp arg_names(nil, _path), do: []
1235
+
1236
+ defp arg_names(point, path) do
1237
+ defs = S.getpath(point, path)
1238
+
1239
+ if S.islist(defs) and S.size(defs) > 0 do
1240
+ Enum.map(0..(S.size(defs) - 1), fn i -> S.getprop(S.getelem(defs, i), "name") end)
1241
+ else
1242
+ []
1243
+ end
1244
+ end
1245
+
1190
1246
  def prepare_query_impl(ctx) do
1191
1247
  point = S.getprop(ctx, "point")
1192
1248
  reqmatch = H.or_(S.getprop(ctx, "reqmatch"), S.jm([]))
@@ -1208,18 +1264,8 @@ defmodule ProjectName.Utility do
1208
1264
 
1209
1265
  # A path parameter travels in the path. The generated config lists them
1210
1266
  # as args.params, which prepare_params reads; params is the older list.
1211
- aparams = if point != nil, do: S.getpath(point, "args.params"), else: nil
1212
-
1213
- arg_strs =
1214
- if S.islist(aparams) and S.size(aparams) > 0 do
1215
- Enum.map(0..(S.size(aparams) - 1), fn i ->
1216
- S.getprop(S.getelem(aparams, i), "name")
1217
- end)
1218
- else
1219
- []
1220
- end
1221
-
1222
- param_strs = param_strs ++ arg_strs
1267
+ # A header parameter travels in the headers, which prepare_headers fills.
1268
+ param_strs = param_strs ++ arg_names(point, "args.params") ++ arg_names(point, "args.header")
1223
1269
 
1224
1270
  # A query parameter travels under the name the definition gives it, its
1225
1271
  # orig, which the model may have renamed for the caller.
@@ -1484,12 +1530,27 @@ defmodule ProjectName.Utility do
1484
1530
  # `$action` selects the point (see make_point_impl); it is never an API
1485
1531
  # field, so the body is a copy without it. The caller's map is left
1486
1532
  # untouched.
1487
- defp strip_action(reqdata) do
1488
- if S.ismap(reqdata) and S.haskey(reqdata, "$action") do
1533
+ defp strip_action(reqdata), do: omit_keys(reqdata, ["$action"])
1534
+
1535
+ # A header argument travels as a header, which prepare_headers_impl sends, so
1536
+ # the body is built from the request data without it.
1537
+ defp header_arg_names(point) do
1538
+ aheader = if point != nil, do: S.getpath(point, "args.header"), else: nil
1539
+
1540
+ if S.islist(aheader) and S.size(aheader) > 0 do
1541
+ Enum.map(0..(S.size(aheader) - 1), &S.getprop(S.getelem(aheader, &1), "name"))
1542
+ |> Enum.filter(&(is_binary(&1) and &1 != ""))
1543
+ else
1544
+ []
1545
+ end
1546
+ end
1547
+
1548
+ defp omit_keys(reqdata, names) do
1549
+ if S.ismap(reqdata) and Enum.any?(names, &S.haskey(reqdata, &1)) do
1489
1550
  body = S.jm([])
1490
1551
 
1491
1552
  Enum.each(H.entries(reqdata), fn {key, val} ->
1492
- if key != "$action", do: S.setprop(body, key, val)
1553
+ if key not in names, do: S.setprop(body, key, val)
1493
1554
  end)
1494
1555
 
1495
1556
  body
@@ -1503,18 +1564,19 @@ defmodule ProjectName.Utility do
1503
1564
  point = S.getprop(ctx, "point")
1504
1565
  if spec != nil, do: S.setprop(spec, "step", "reqform")
1505
1566
 
1567
+ data = omit_keys(S.getprop(ctx, "reqdata"), header_arg_names(point))
1506
1568
  transform = H.to_map(S.getprop(point, "transform"))
1507
1569
 
1508
1570
  reqdata =
1509
1571
  if transform == nil do
1510
- S.getprop(ctx, "reqdata")
1572
+ data
1511
1573
  else
1512
1574
  reqform = S.getprop(transform, "req")
1513
1575
 
1514
1576
  if reqform == nil do
1515
- S.getprop(ctx, "reqdata")
1577
+ data
1516
1578
  else
1517
- S.transform(S.jm(["reqdata", S.getprop(ctx, "reqdata")]), reqform)
1579
+ S.transform(S.jm(["reqdata", data]), reqform)
1518
1580
  end
1519
1581
  end
1520
1582
 
@@ -2,6 +2,7 @@ package utility
2
2
 
3
3
  import (
4
4
  "regexp"
5
+ "strings"
5
6
 
6
7
  vs "github.com/voxgig/struct"
7
8
 
@@ -24,6 +25,13 @@ func makeUrlUtil(ctx *core.Context) (string, error) {
24
25
  url := vs.Join([]any{spec.Base, spec.Prefix, spec.Path, spec.Suffix}, "/", true)
25
26
  resmatch := map[string]any{}
26
27
 
28
+ // A route the definition ends with a slash keeps it: a server such as a
29
+ // Django REST one redirects or refuses the route without it.
30
+ if orig, _ := vs.GetProp(ctx.Point, "orig").(string); strings.HasSuffix(orig, "/") &&
31
+ spec.Suffix == "" && !strings.HasSuffix(url, "/") {
32
+ url += "/"
33
+ }
34
+
27
35
  params := spec.Params
28
36
  for _, item := range vs.Items(params) {
29
37
  key, _ := item[0].(string)
@@ -1,6 +1,8 @@
1
1
  package utility
2
2
 
3
3
  import (
4
+ "strings"
5
+
4
6
  vs "github.com/voxgig/struct"
5
7
 
6
8
  "GOMODULE/core"
@@ -9,14 +11,41 @@ import (
9
11
  func prepareHeadersUtil(ctx *core.Context) map[string]any {
10
12
  options := ctx.Client.OptionsMap()
11
13
 
12
- headers := vs.GetProp(options, "headers")
13
- if headers == nil {
14
- return map[string]any{}
14
+ out := map[string]any{}
15
+ if headers := vs.GetProp(options, "headers"); headers != nil {
16
+ if om, ok := vs.Clone(headers).(map[string]any); ok {
17
+ out = om
18
+ }
15
19
  }
16
20
 
17
- out := vs.Clone(headers)
18
- if om, ok := out.(map[string]any); ok {
19
- return om
21
+ // A header parameter travels as a header, under the name the definition
22
+ // gives it, and only from this call's own arguments. It replaces a default
23
+ // of the same name, whatever its case.
24
+ if hl, ok := vs.GetPath(ctx.Point, []any{"args", "header"}).([]any); ok {
25
+ for _, hd := range hl {
26
+ name, _ := vs.GetProp(hd, "name").(string)
27
+ orig, _ := vs.GetProp(hd, "orig").(string)
28
+ if "" == name {
29
+ continue
30
+ }
31
+ if "" == orig {
32
+ orig = name
33
+ }
34
+ val := vs.GetProp(ctx.Reqmatch, name)
35
+ if val == nil {
36
+ val = vs.GetProp(ctx.Reqdata, name)
37
+ }
38
+ if val != nil {
39
+ wire := strings.ToLower(orig)
40
+ for key := range out {
41
+ if strings.ToLower(key) == wire {
42
+ delete(out, key)
43
+ }
44
+ }
45
+ out[wire] = vs.Stringify(val)
46
+ }
47
+ }
20
48
  }
21
- return map[string]any{}
49
+
50
+ return out
22
51
  }
@@ -37,6 +37,17 @@ func prepareQueryUtil(ctx *core.Context) map[string]any {
37
37
  }
38
38
  }
39
39
 
40
+ // A header parameter travels in the headers, which prepareHeaders fills.
41
+ if point != nil {
42
+ if hl, ok := vs.GetPath(point, []any{"args", "header"}).([]any); ok {
43
+ for _, hd := range hl {
44
+ if name, ok := vs.GetProp(hd, "name").(string); ok {
45
+ params = append(params, name)
46
+ }
47
+ }
48
+ }
49
+ }
50
+
40
51
  // A query parameter travels under the name the definition gives it, its
41
52
  // orig, which the model may have renamed for the caller.
42
53
  wire := map[string]string{}
@@ -14,18 +14,20 @@ func transformRequestUtil(ctx *core.Context) any {
14
14
  spec.Step = "reqform"
15
15
  }
16
16
 
17
+ data := omitKeys(ctx.Reqdata, headerArgNames(point))
18
+
17
19
  transform := core.ToMapAny(vs.GetProp(point, "transform"))
18
20
  if transform == nil {
19
- return stripAction(ctx.Reqdata)
21
+ return stripAction(data)
20
22
  }
21
23
 
22
24
  reqform := vs.GetProp(transform, "req")
23
25
  if reqform == nil {
24
- return stripAction(ctx.Reqdata)
26
+ return stripAction(data)
25
27
  }
26
28
 
27
29
  reqdata, terr := vs.Transform(map[string]any{
28
- "reqdata": ctx.Reqdata,
30
+ "reqdata": data,
29
31
  }, reqform)
30
32
 
31
33
  if terr != nil {
@@ -42,16 +44,40 @@ func transformRequestUtil(ctx *core.Context) any {
42
44
  // `$action` selects the point (see makePointUtil); it is never an API field,
43
45
  // so the body is a copy without it. The caller's map is left untouched.
44
46
  func stripAction(reqdata any) any {
47
+ return omitKeys(reqdata, []string{"$action"})
48
+ }
49
+
50
+ // A header argument travels as a header, which prepareHeadersUtil sends, so
51
+ // the body is built from the request data without it.
52
+ func headerArgNames(point any) []string {
53
+ names := []string{}
54
+ if hl, ok := vs.GetPath(point, []any{"args", "header"}).([]any); ok {
55
+ for _, hd := range hl {
56
+ if name, _ := vs.GetProp(hd, "name").(string); "" != name {
57
+ names = append(names, name)
58
+ }
59
+ }
60
+ }
61
+ return names
62
+ }
63
+
64
+ func omitKeys(reqdata any, names []string) any {
45
65
  src, ok := reqdata.(map[string]any)
46
66
  if !ok {
47
67
  return reqdata
48
68
  }
49
- if _, has := src["$action"]; !has {
69
+ skip := map[string]bool{}
70
+ for _, name := range names {
71
+ if _, has := src[name]; has {
72
+ skip[name] = true
73
+ }
74
+ }
75
+ if 0 == len(skip) {
50
76
  return reqdata
51
77
  }
52
78
  body := make(map[string]any, len(src))
53
79
  for k, v := range src {
54
- if k != "$action" {
80
+ if !skip[k] {
55
81
  body[k] = v
56
82
  }
57
83
  }
@@ -34,6 +34,14 @@ final class MakeUrl {
34
34
  joinParts.add(spec.suffix);
35
35
  String url = Struct.join(joinParts, "/", true);
36
36
 
37
+ // A route the definition ends with a slash keeps it: a server such as a
38
+ // Django REST one redirects or refuses the route without it.
39
+ Object orig = ctx.point == null ? null : Struct.getprop(ctx.point, "orig", null);
40
+ if (orig instanceof String && ((String) orig).endsWith("/") &&
41
+ (spec.suffix == null || spec.suffix.isEmpty()) && !url.endsWith("/")) {
42
+ url = url + "/";
43
+ }
44
+
37
45
  Map<String, Object> resmatch = new LinkedHashMap<>();
38
46
 
39
47
  Map<String, Object> params = spec.params;
@@ -1,12 +1,15 @@
1
1
  package JAVAPACKAGE.utility;
2
2
 
3
3
  import java.util.LinkedHashMap;
4
+ import java.util.List;
5
+ import java.util.Locale;
4
6
  import java.util.Map;
5
7
 
6
8
  import JAVAPACKAGE.core.Context;
7
9
  import JAVAPACKAGE.core.Helpers;
8
10
  import JAVAPACKAGE.utility.struct.Struct;
9
11
 
12
+ @SuppressWarnings({"unchecked"})
10
13
  final class PrepareHeaders {
11
14
 
12
15
  private PrepareHeaders() {}
@@ -15,14 +18,35 @@ final class PrepareHeaders {
15
18
  Map<String, Object> options = ctx.client.optionsMap();
16
19
 
17
20
  Object headers = Struct.getprop(options, "headers");
18
- if (headers == null) {
19
- return new LinkedHashMap<>();
21
+ Map<String, Object> out = headers == null ? null : Helpers.toMapAny(Struct.clone(headers));
22
+ if (out == null) {
23
+ out = new LinkedHashMap<>();
20
24
  }
21
25
 
22
- Map<String, Object> out = Helpers.toMapAny(Struct.clone(headers));
23
- if (out != null) {
24
- return out;
26
+ // A header parameter travels as a header, under the name the definition
27
+ // gives it, and only from this call's own arguments. It replaces a default
28
+ // of the same name, whatever its case.
29
+ Object hl = ctx.point == null ? null : Struct.getpath(ctx.point, List.of("args", "header"));
30
+ if (hl instanceof List) {
31
+ for (Object hd : (List<Object>) hl) {
32
+ Object name = Struct.getprop(hd, "name");
33
+ if (!(name instanceof String) || ((String) name).isEmpty()) {
34
+ continue;
35
+ }
36
+ Object orig = Struct.getprop(hd, "orig");
37
+ String wire = orig instanceof String && !((String) orig).isEmpty() ? (String) orig : (String) name;
38
+ Object val = ctx.reqmatch == null ? null : Struct.getprop(ctx.reqmatch, name, null);
39
+ if (val == null && ctx.reqdata != null) {
40
+ val = Struct.getprop(ctx.reqdata, name, null);
41
+ }
42
+ if (val != null) {
43
+ String key = wire.toLowerCase(Locale.ROOT);
44
+ out.keySet().removeIf(k -> k != null && k.toLowerCase(Locale.ROOT).equals(key));
45
+ out.put(key, Struct.stringify(val));
46
+ }
47
+ }
25
48
  }
26
- return new LinkedHashMap<>();
49
+
50
+ return out;
27
51
  }
28
52
  }
@@ -37,6 +37,16 @@ final class PrepareQuery {
37
37
  }
38
38
  }
39
39
  }
40
+ // A header parameter travels in the headers, which prepareHeaders fills.
41
+ Object hl = Struct.getpath(point, List.of("args", "header"));
42
+ if (hl instanceof List) {
43
+ for (Object hd : (List<Object>) hl) {
44
+ Object name = Struct.getprop(hd, "name");
45
+ if (name instanceof String) {
46
+ params.add(name);
47
+ }
48
+ }
49
+ }
40
50
  }
41
51
 
42
52
  // A query parameter travels under the name the definition gives it, its
@@ -1,6 +1,8 @@
1
1
  package JAVAPACKAGE.utility;
2
2
 
3
+ import java.util.ArrayList;
3
4
  import java.util.LinkedHashMap;
5
+ import java.util.List;
4
6
  import java.util.Map;
5
7
 
6
8
  import JAVAPACKAGE.core.Context;
@@ -16,19 +18,21 @@ final class TransformRequest {
16
18
  ctx.spec.step = "reqform";
17
19
  }
18
20
 
21
+ Object reqdata = omit(ctx.reqdata, headerArgNames(ctx));
22
+
19
23
  Map<String, Object> transform =
20
24
  Helpers.toMapAny(Struct.getprop(ctx.point, "transform"));
21
25
  if (transform == null) {
22
- return stripAction(ctx.reqdata);
26
+ return stripAction(reqdata);
23
27
  }
24
28
 
25
29
  Object reqform = Struct.getprop(transform, "req", null);
26
30
  if (reqform == null) {
27
- return stripAction(ctx.reqdata);
31
+ return stripAction(reqdata);
28
32
  }
29
33
 
30
34
  Map<String, Object> data = new LinkedHashMap<>();
31
- data.put("reqdata", ctx.reqdata);
35
+ data.put("reqdata", reqdata);
32
36
 
33
37
  return stripAction(Struct.transform(data, reqform));
34
38
  }
@@ -36,12 +40,33 @@ final class TransformRequest {
36
40
  // `$action` selects the point (see MakePoint); it is never an API field, so
37
41
  // the body is a copy without it. The caller's map is left untouched.
38
42
  private static Object stripAction(Object reqdata) {
39
- if (!(reqdata instanceof Map) || !((Map<?, ?>) reqdata).containsKey("$action")) {
43
+ return omit(reqdata, List.of("$action"));
44
+ }
45
+
46
+ // A header argument travels as a header, which PrepareHeaders sends, so the
47
+ // body is built from the request data without it.
48
+ @SuppressWarnings("unchecked")
49
+ private static List<String> headerArgNames(Context ctx) {
50
+ List<String> names = new ArrayList<>();
51
+ Object hl = ctx.point == null ? null : Struct.getpath(ctx.point, List.of("args", "header"));
52
+ if (hl instanceof List) {
53
+ for (Object hd : (List<Object>) hl) {
54
+ Object name = Struct.getprop(hd, "name", null);
55
+ if (name instanceof String && !((String) name).isEmpty()) {
56
+ names.add((String) name);
57
+ }
58
+ }
59
+ }
60
+ return names;
61
+ }
62
+
63
+ private static Object omit(Object reqdata, List<String> names) {
64
+ if (!(reqdata instanceof Map) || names.stream().noneMatch(((Map<?, ?>) reqdata)::containsKey)) {
40
65
  return reqdata;
41
66
  }
42
67
  Map<String, Object> body = new LinkedHashMap<>();
43
68
  for (Map.Entry<?, ?> e : ((Map<?, ?>) reqdata).entrySet()) {
44
- if (!"$action".equals(e.getKey())) {
69
+ if (!names.contains(e.getKey())) {
45
70
  body.put(String.valueOf(e.getKey()), e.getValue());
46
71
  }
47
72
  }
@@ -19,6 +19,13 @@ function makeUrl(ctx) {
19
19
  }
20
20
 
21
21
  let url = join([spec.base, spec.prefix, spec.path, spec.suffix], '/', true)
22
+
23
+ // A route the definition ends with a slash keeps it: a server such as a
24
+ // Django REST one redirects or refuses the route without it.
25
+ const orig = ctx.point && ctx.point.orig
26
+ if ('string' === typeof orig && orig.endsWith('/') && !spec.suffix && !url.endsWith('/')) {
27
+ url += '/'
28
+ }
22
29
  let resmatch = {}
23
30
 
24
31
  const params = spec.params
@@ -3,6 +3,7 @@ function prepareHeaders(ctx) {
3
3
  const struct = ctx.utility.struct
4
4
  const clone = struct.clone
5
5
  const getprop = struct.getprop
6
+ const stringify = struct.stringify
6
7
 
7
8
  const client = ctx.client
8
9
 
@@ -10,6 +11,23 @@ function prepareHeaders(ctx) {
10
11
 
11
12
  let out = clone(getprop(options, 'headers', {}))
12
13
 
14
+ // A header parameter travels as a header, under the name the definition
15
+ // gives it, and only from this call's own arguments. It replaces a default
16
+ // of the same name, whatever its case.
17
+ const hargs = (ctx.point && ctx.point.args && ctx.point.args.header) || []
18
+ for (const h of hargs) {
19
+ if (null == h || 'string' !== typeof h.name || '' === h.name) continue
20
+ let val = getprop(ctx.reqmatch, h.name)
21
+ if (null == val) val = getprop(ctx.reqdata, h.name)
22
+ if (null != val) {
23
+ const wire = String(h.orig || h.name).toLowerCase()
24
+ for (const key of Object.keys(out)) {
25
+ if (wire === key.toLowerCase()) delete out[key]
26
+ }
27
+ out[wire] = stringify(val)
28
+ }
29
+ }
30
+
13
31
  return out
14
32
  }
15
33
 
@@ -13,8 +13,10 @@ function prepareQuery(ctx) {
13
13
 
14
14
  // A path parameter travels in the path. The generated config lists them as
15
15
  // args.params, which prepareParams reads; params is the older list of names.
16
+ // A header parameter travels in the headers, which prepareHeaders fills.
16
17
  const inpath = params.concat(
17
- ((point.args && point.args.params) || []).map((p) => p && p.name))
18
+ ((point.args && point.args.params) || []).map((p) => p && p.name),
19
+ ((point.args && point.args.header) || []).map((h) => h && h.name))
18
20
 
19
21
  // A query parameter travels under the name the definition gives it, its
20
22
  // orig, which the model may have renamed for the caller.
@@ -13,7 +13,7 @@ function transformRequest(ctx) {
13
13
  try {
14
14
  const reqform = point.transform.req
15
15
  const reqdata = isfunc(reqform) ? reqform(ctx) : transform({
16
- reqdata: ctx.reqdata
16
+ reqdata: omit(ctx.reqdata, headerArgNames(ctx))
17
17
  }, reqform)
18
18
 
19
19
  return stripAction(reqdata)
@@ -42,17 +42,31 @@ function transformRequest(ctx) {
42
42
  // as StructUtility's condenseSet, for the same reason.
43
43
 
44
44
  function stripAction(reqdata) {
45
+ return omit(reqdata, ['$action'])
46
+ }
47
+
48
+
49
+ // A header argument travels as a header, which prepareHeaders sends, so the
50
+ // body is built from the request data without it.
51
+ function headerArgNames(ctx) {
52
+ const hargs = (ctx.point && ctx.point.args && ctx.point.args.header) || []
53
+ return hargs.map((h) => h && h.name)
54
+ .filter((name) => 'string' === typeof name && '' !== name)
55
+ }
56
+
57
+
58
+ function omit(reqdata, names) {
45
59
  if (null == reqdata || 'object' !== typeof reqdata || Array.isArray(reqdata)) {
46
60
  return reqdata
47
61
  }
48
62
 
49
- if (!Object.prototype.hasOwnProperty.call(reqdata, '$action')) {
63
+ if (!names.some((name) => Object.prototype.hasOwnProperty.call(reqdata, name))) {
50
64
  return reqdata
51
65
  }
52
66
 
53
67
  const body = {}
54
68
  for (const key of Object.keys(reqdata)) {
55
- if ('$action' === key) {
69
+ if (names.includes(key)) {
56
70
  continue
57
71
  }
58
72
  if ('__proto__' === key) {