@voxgig/sdkgen 4.31.1 → 4.32.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 (76) 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/Response.js +14 -1
  27. package/project/.sdk/tm/js/src/utility/MakeUrlUtility.js +7 -0
  28. package/project/.sdk/tm/js/src/utility/PrepareHeadersUtility.js +18 -0
  29. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +3 -1
  30. package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +17 -3
  31. package/project/.sdk/tm/js/test/definition-runner.js +74 -13
  32. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +25 -0
  33. package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +8 -0
  34. package/project/.sdk/tm/kotlin/utility/Prepare.kt +34 -4
  35. package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -6
  36. package/project/.sdk/tm/lua/utility/make_url.lua +8 -0
  37. package/project/.sdk/tm/lua/utility/prepare_headers.lua +35 -6
  38. package/project/.sdk/tm/lua/utility/prepare_query.lua +10 -0
  39. package/project/.sdk/tm/lua/utility/transform_request.lua +37 -6
  40. package/project/.sdk/tm/ocaml/sdk_runtime.ml +63 -13
  41. package/project/.sdk/tm/perl/utility/make_url.pm +6 -0
  42. package/project/.sdk/tm/perl/utility/prepare_headers.pm +21 -3
  43. package/project/.sdk/tm/perl/utility/prepare_query.pm +8 -0
  44. package/project/.sdk/tm/perl/utility/transform_request.pm +19 -7
  45. package/project/.sdk/tm/php/utility/MakeUrl.php +7 -0
  46. package/project/.sdk/tm/php/utility/PrepareHeaders.php +33 -4
  47. package/project/.sdk/tm/php/utility/PrepareQuery.php +11 -0
  48. package/project/.sdk/tm/php/utility/TransformRequest.php +28 -5
  49. package/project/.sdk/tm/py/pkg/utility/make_url.py +7 -0
  50. package/project/.sdk/tm/py/pkg/utility/prepare_headers.py +27 -6
  51. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +8 -0
  52. package/project/.sdk/tm/py/pkg/utility/transform_request.py +21 -5
  53. package/project/.sdk/tm/rb/utility/make_url.rb +7 -0
  54. package/project/.sdk/tm/rb/utility/prepare_headers.rb +21 -3
  55. package/project/.sdk/tm/rb/utility/prepare_query.rb +8 -0
  56. package/project/.sdk/tm/rb/utility/transform_request.rb +18 -5
  57. package/project/.sdk/tm/rust/utility/make_url.rs +10 -1
  58. package/project/.sdk/tm/rust/utility/prepare_headers.rs +43 -7
  59. package/project/.sdk/tm/rust/utility/prepare_query.rs +18 -1
  60. package/project/.sdk/tm/rust/utility/transform_request.rs +29 -7
  61. package/project/.sdk/tm/scala/utility/Make.scala +9 -0
  62. package/project/.sdk/tm/scala/utility/Prepare.scala +42 -3
  63. package/project/.sdk/tm/scala/utility/Transform.scala +30 -6
  64. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +7 -0
  65. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +28 -2
  66. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +23 -5
  67. package/project/.sdk/tm/ts/src/Response.ts +14 -1
  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/ts/test/utility/PrimaryUtility.test.ts +25 -0
  74. package/project/.sdk/tm/zig/core/utility.zig +80 -20
  75. package/project/sdkgen-package.json +1 -1
  76. package/src/helpers/definitionPlan.ts +49 -7
@@ -17,24 +17,36 @@ our %REGISTRY;
17
17
 
18
18
  # `$action` selects the point (see make_point); it is never an API field, so
19
19
  # the body is a copy without it. The caller's hash is left untouched.
20
- my $strip_action = sub {
21
- my ($reqdata) = @_;
22
- return $reqdata unless 'HASH' eq ref $reqdata && exists $reqdata->{'$action'};
20
+ my $omit = sub {
21
+ my ($reqdata, @names) = @_;
22
+ return $reqdata unless 'HASH' eq ref $reqdata && grep { exists $reqdata->{$_} } @names;
23
23
  my %body = %$reqdata;
24
- delete $body{'$action'};
24
+ delete @body{@names};
25
25
  return \%body;
26
26
  };
27
27
 
28
+ my $strip_action = sub { $omit->($_[0], '$action') };
29
+
30
+ # A header argument travels as a header, which prepare_headers sends, so the
31
+ # body is built from the request data without it.
32
+ my $header_arg_names = sub {
33
+ my ($point) = @_;
34
+ my $hl = $point ? ProjectNameHelpers::gpath($point, 'args.header') : undef;
35
+ return () unless Voxgig::Struct::islist($hl);
36
+ return grep { defined $_ && !ref $_ && '' ne $_ } map { ProjectNameHelpers::gp($_, 'name') } @$hl;
37
+ };
38
+
28
39
  $REGISTRY{transform_request} = sub {
29
40
  my ($ctx) = @_;
30
41
  my $spec = $ctx->{spec};
31
42
  my $point = $ctx->{point};
32
43
  $spec->{step} = 'reqform' if $spec;
44
+ my $data = $omit->($ctx->{reqdata}, $header_arg_names->($point));
33
45
  my $transform = ProjectNameHelpers::to_map(ProjectNameHelpers::gp($point, 'transform'));
34
- return $strip_action->($ctx->{reqdata}) unless $transform;
46
+ return $strip_action->($data) unless $transform;
35
47
  my $reqform = ProjectNameHelpers::gp($transform, 'req');
36
- return $strip_action->($ctx->{reqdata}) unless ProjectNameHelpers::rb_truthy($reqform);
37
- return $strip_action->(Voxgig::Struct::transform({ 'reqdata' => $ctx->{reqdata} }, $reqform));
48
+ return $strip_action->($data) unless ProjectNameHelpers::rb_truthy($reqform);
49
+ return $strip_action->(Voxgig::Struct::transform({ 'reqdata' => $data }, $reqform));
38
50
  };
39
51
 
40
52
  1;
@@ -20,6 +20,13 @@ class ProjectNameMakeUrl
20
20
  $url = \Voxgig\Struct\Struct::join([$spec->base, $spec->prefix, $spec->path, $spec->suffix], '/', true);
21
21
  $resmatch = [];
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
+ $orig = $ctx->point ? \Voxgig\Struct\Struct::getprop($ctx->point, 'orig') : null;
26
+ if (is_string($orig) && str_ends_with($orig, '/') && '' === $spec->suffix && !str_ends_with($url, '/')) {
27
+ $url .= '/';
28
+ }
29
+
23
30
  $param_items = \Voxgig\Struct\Struct::items($spec->params);
24
31
  if ($param_items) {
25
32
  foreach ($param_items as $item) {
@@ -9,10 +9,39 @@ class ProjectNamePrepareHeaders
9
9
  {
10
10
  $options = $ctx->client->options_map();
11
11
  $headers = \Voxgig\Struct\Struct::getprop($options, 'headers');
12
- if (!$headers) {
13
- return [];
12
+ $out = $headers ? \Voxgig\Struct\Struct::clone($headers) : [];
13
+ if (!is_array($out)) {
14
+ $out = [];
14
15
  }
15
- $out = \Voxgig\Struct\Struct::clone($headers);
16
- return is_array($out) ? $out : [];
16
+ // A header parameter travels as a header, under the name the
17
+ // definition gives it, and only from this call's own arguments. It
18
+ // replaces a default of the same name, whatever its case.
19
+ $hl = $ctx->point ? \Voxgig\Struct\Struct::getpath($ctx->point, 'args.header') : null;
20
+ if (is_array($hl)) {
21
+ foreach ($hl as $hd) {
22
+ $name = \Voxgig\Struct\Struct::getprop($hd, 'name');
23
+ if (!is_string($name) || '' === $name) {
24
+ continue;
25
+ }
26
+ $orig = \Voxgig\Struct\Struct::getprop($hd, 'orig');
27
+ if (!is_string($orig) || '' === $orig) {
28
+ $orig = $name;
29
+ }
30
+ $val = \Voxgig\Struct\Struct::getprop($ctx->reqmatch ?? [], $name);
31
+ if (null === $val) {
32
+ $val = \Voxgig\Struct\Struct::getprop($ctx->reqdata ?? [], $name);
33
+ }
34
+ if (null !== $val) {
35
+ $wire = strtolower($orig);
36
+ foreach (array_keys($out) as $key) {
37
+ if (is_string($key) && strtolower($key) === $wire) {
38
+ unset($out[$key]);
39
+ }
40
+ }
41
+ $out[$wire] = \Voxgig\Struct\Struct::stringify($val);
42
+ }
43
+ }
44
+ }
45
+ return $out;
17
46
  }
18
47
  }
@@ -27,6 +27,17 @@ class ProjectNamePrepareQuery
27
27
  }
28
28
  }
29
29
  }
30
+ // A header parameter travels in the headers, which prepareHeaders
31
+ // fills.
32
+ $hl = \Voxgig\Struct\Struct::getpath($point, 'args.header');
33
+ if (is_array($hl)) {
34
+ foreach ($hl as $hd) {
35
+ $name = \Voxgig\Struct\Struct::getprop($hd, 'name');
36
+ if (is_string($name)) {
37
+ $params[] = $name;
38
+ }
39
+ }
40
+ }
30
41
  }
31
42
  // A query parameter travels under the name the definition gives it,
32
43
  // its orig, which the model may have renamed for the caller.
@@ -14,15 +14,16 @@ class ProjectNameTransformRequest
14
14
  if ($spec) {
15
15
  $spec->step = 'reqform';
16
16
  }
17
+ $data = self::omit($ctx->reqdata, self::header_arg_names($point));
17
18
  $transform = ProjectNameHelpers::to_map(\Voxgig\Struct\Struct::getprop($point, 'transform'));
18
19
  if (!$transform) {
19
- return self::strip_action($ctx->reqdata);
20
+ return self::strip_action($data);
20
21
  }
21
22
  $reqform = \Voxgig\Struct\Struct::getprop($transform, 'req');
22
23
  if (!$reqform) {
23
- return self::strip_action($ctx->reqdata);
24
+ return self::strip_action($data);
24
25
  }
25
- return self::strip_action(\Voxgig\Struct\Struct::transform(['reqdata' => $ctx->reqdata], $reqform));
26
+ return self::strip_action(\Voxgig\Struct\Struct::transform(['reqdata' => $data], $reqform));
26
27
  }
27
28
 
28
29
  // `$action` selects the point (see MakePoint); it is never an API field,
@@ -30,10 +31,32 @@ class ProjectNameTransformRequest
30
31
  // never reaches the caller's copy.
31
32
  private static function strip_action(mixed $reqdata): mixed
32
33
  {
33
- if (!is_array($reqdata) || !array_key_exists('$action', $reqdata)) {
34
+ return self::omit($reqdata, ['$action']);
35
+ }
36
+
37
+ // A header argument travels as a header, which PrepareHeaders sends, so
38
+ // the body is built from the request data without it.
39
+ private static function header_arg_names(mixed $point): array
40
+ {
41
+ $hl = $point ? \Voxgig\Struct\Struct::getpath($point, 'args.header') : null;
42
+ $names = [];
43
+ foreach (is_array($hl) ? $hl : [] as $hd) {
44
+ $name = \Voxgig\Struct\Struct::getprop($hd, 'name');
45
+ if (is_string($name) && '' !== $name) {
46
+ $names[] = $name;
47
+ }
48
+ }
49
+ return $names;
50
+ }
51
+
52
+ private static function omit(mixed $reqdata, array $names): mixed
53
+ {
54
+ if (!is_array($reqdata)) {
34
55
  return $reqdata;
35
56
  }
36
- unset($reqdata['$action']);
57
+ foreach ($names as $name) {
58
+ unset($reqdata[$name]);
59
+ }
37
60
  return $reqdata;
38
61
  }
39
62
  }
@@ -18,6 +18,13 @@ def make_url_util(ctx):
18
18
  url = vs.join([spec.base, spec.prefix, spec.path, spec.suffix], "/", True)
19
19
  resmatch = {}
20
20
 
21
+ # A route the definition ends with a slash keeps it: a server such as a
22
+ # Django REST one redirects or refuses the route without it.
23
+ orig = vs.getprop(ctx.point, "orig") if ctx.point is not None else None
24
+ if isinstance(orig, str) and orig.endswith("/") and not spec.suffix \
25
+ and not url.endswith("/"):
26
+ url += "/"
27
+
21
28
  param_items = vs.items(spec.params)
22
29
  if param_items is not None:
23
30
  for item in param_items:
@@ -8,10 +8,31 @@ def prepare_headers_util(ctx):
8
8
  options = ctx.client.options_map()
9
9
  headers = vs.getprop(options, "headers")
10
10
 
11
- if headers is None:
12
- return {}
11
+ out = {}
12
+ if headers is not None:
13
+ cloned = vs.clone(headers)
14
+ if isinstance(cloned, dict):
15
+ out = cloned
13
16
 
14
- out = vs.clone(headers)
15
- if isinstance(out, dict):
16
- return out
17
- return {}
17
+ # A header parameter travels as a header, under the name the definition
18
+ # gives it, and only from this call's own arguments. It replaces a default
19
+ # of the same name, whatever its case.
20
+ hl = vs.getpath(ctx.point, "args.header") if ctx.point is not None else None
21
+ if isinstance(hl, list):
22
+ for hd in hl:
23
+ name = vs.getprop(hd, "name")
24
+ if not isinstance(name, str) or name == "":
25
+ continue
26
+ orig = vs.getprop(hd, "orig")
27
+ if not isinstance(orig, str) or orig == "":
28
+ orig = name
29
+ val = vs.getprop(ctx.reqmatch or {}, name)
30
+ if val is None:
31
+ val = vs.getprop(ctx.reqdata or {}, name)
32
+ if val is not None:
33
+ wire = orig.lower()
34
+ for key in [k for k in out if isinstance(k, str) and k.lower() == wire]:
35
+ del out[key]
36
+ out[wire] = vs.stringify(val)
37
+
38
+ return out
@@ -29,6 +29,14 @@ def prepare_query_util(ctx):
29
29
  name = vs.getprop(pd, "name")
30
30
  if isinstance(name, str):
31
31
  params.append(name)
32
+ # A header parameter travels in the headers, which prepare_headers
33
+ # fills.
34
+ hl = vs.getpath(point, "args.header")
35
+ if isinstance(hl, list):
36
+ for hd in hl:
37
+ name = vs.getprop(hd, "name")
38
+ if isinstance(name, str):
39
+ params.append(name)
32
40
 
33
41
  # A query parameter travels under the name the definition gives it, its
34
42
  # orig, which the model may have renamed for the caller.
@@ -8,9 +8,23 @@ from projectname_sdk.core.helpers import to_map
8
8
  # `$action` selects the point (see make_point_util); it is never an API field,
9
9
  # so the body is a copy without it. The caller's dict is left untouched.
10
10
  def _strip_action(reqdata):
11
- if not isinstance(reqdata, dict) or "$action" not in reqdata:
11
+ return _omit(reqdata, ["$action"])
12
+
13
+
14
+ # A header argument travels as a header, which prepare_headers_util sends, so
15
+ # the body is built from the request data without it.
16
+ def _header_arg_names(point):
17
+ hl = vs.getpath(point, "args.header") if point is not None else None
18
+ if not isinstance(hl, list):
19
+ return []
20
+ names = [vs.getprop(hd, "name") for hd in hl]
21
+ return [n for n in names if isinstance(n, str) and n != ""]
22
+
23
+
24
+ def _omit(reqdata, names):
25
+ if not isinstance(reqdata, dict) or not any(n in reqdata for n in names):
12
26
  return reqdata
13
- return {k: v for k, v in reqdata.items() if k != "$action"}
27
+ return {k: v for k, v in reqdata.items() if k not in names}
14
28
 
15
29
 
16
30
  def transform_request_util(ctx):
@@ -20,16 +34,18 @@ def transform_request_util(ctx):
20
34
  if spec is not None:
21
35
  spec.step = "reqform"
22
36
 
37
+ data = _omit(ctx.reqdata, _header_arg_names(point))
38
+
23
39
  transform = to_map(vs.getprop(point, "transform"))
24
40
  if transform is None:
25
- return _strip_action(ctx.reqdata)
41
+ return _strip_action(data)
26
42
 
27
43
  reqform = vs.getprop(transform, "req")
28
44
  if reqform is None:
29
- return _strip_action(ctx.reqdata)
45
+ return _strip_action(data)
30
46
 
31
47
  reqdata = vs.transform({
32
- "reqdata": ctx.reqdata,
48
+ "reqdata": data,
33
49
  }, reqform)
34
50
 
35
51
  return _strip_action(reqdata)
@@ -11,6 +11,13 @@ module ProjectNameUtilities
11
11
  url = VoxgigStruct.join([spec.base, spec.prefix, spec.path, spec.suffix], "/", true)
12
12
  resmatch = {}
13
13
 
14
+ # A route the definition ends with a slash keeps it: a server such as a
15
+ # Django REST one redirects or refuses the route without it.
16
+ orig = ctx.point ? VoxgigStruct.getprop(ctx.point, "orig") : nil
17
+ if orig.is_a?(String) && orig.end_with?("/") && spec.suffix.to_s.empty? && !url.end_with?("/")
18
+ url += "/"
19
+ end
20
+
14
21
  param_items = VoxgigStruct.items(spec.params)
15
22
  if param_items
16
23
  param_items.each do |item|
@@ -4,8 +4,26 @@ module ProjectNameUtilities
4
4
  PrepareHeaders = ->(ctx) {
5
5
  options = ctx.client.options_map
6
6
  headers = VoxgigStruct.getprop(options, "headers")
7
- return {} unless headers
8
- out = VoxgigStruct.clone(headers)
9
- out.is_a?(Hash) ? out : {}
7
+ out = headers ? VoxgigStruct.clone(headers) : {}
8
+ out = {} unless out.is_a?(Hash)
9
+ # A header parameter travels as a header, under the name the definition
10
+ # gives it, and only from this call's own arguments. It replaces a default
11
+ # of the same name, whatever its case.
12
+ hl = ctx.point ? VoxgigStruct.getpath(ctx.point, "args.header") : nil
13
+ if hl.is_a?(Array)
14
+ hl.each do |hd|
15
+ name = VoxgigStruct.getprop(hd, "name")
16
+ next unless name.is_a?(String) && !name.empty?
17
+ orig = VoxgigStruct.getprop(hd, "orig")
18
+ orig = name unless orig.is_a?(String) && !orig.empty?
19
+ val = VoxgigStruct.getprop(ctx.reqmatch || {}, name)
20
+ val = VoxgigStruct.getprop(ctx.reqdata || {}, name) if val.nil?
21
+ next if val.nil?
22
+ wire = orig.downcase
23
+ out.delete_if { |k, _| k.is_a?(String) && k.downcase == wire }
24
+ out[wire] = VoxgigStruct.stringify(val)
25
+ end
26
+ end
27
+ out
10
28
  }
11
29
  end
@@ -17,6 +17,14 @@ module ProjectNameUtilities
17
17
  params << name if name.is_a?(String)
18
18
  end
19
19
  end
20
+ # A header parameter travels in the headers, which prepare_headers fills.
21
+ hl = VoxgigStruct.getpath(point, "args.header")
22
+ if hl.is_a?(Array)
23
+ hl.each do |hd|
24
+ name = VoxgigStruct.getprop(hd, "name")
25
+ params << name if name.is_a?(String)
26
+ end
27
+ end
20
28
  end
21
29
  # A query parameter travels under the name the definition gives it, its
22
30
  # orig, which the model may have renamed for the caller.
@@ -5,18 +5,31 @@ module ProjectNameUtilities
5
5
  # `$action` selects the point (see MakePoint); it is never an API field, so
6
6
  # the body is a copy without it. The caller's hash is left untouched.
7
7
  def self.strip_action(reqdata)
8
- return reqdata unless reqdata.is_a?(Hash) && reqdata.key?("$action")
9
- reqdata.reject { |k, _| k == "$action" }
8
+ omit_keys(reqdata, ["$action"])
9
+ end
10
+
11
+ # A header argument travels as a header, which PrepareHeaders sends, so the
12
+ # body is built from the request data without it.
13
+ def self.header_arg_names(point)
14
+ hl = point ? VoxgigStruct.getpath(point, "args.header") : nil
15
+ return [] unless hl.is_a?(Array)
16
+ hl.map { |hd| VoxgigStruct.getprop(hd, "name") }.select { |n| n.is_a?(String) && !n.empty? }
17
+ end
18
+
19
+ def self.omit_keys(reqdata, names)
20
+ return reqdata unless reqdata.is_a?(Hash) && names.any? { |n| reqdata.key?(n) }
21
+ reqdata.reject { |k, _| names.include?(k) }
10
22
  end
11
23
 
12
24
  TransformRequest = ->(ctx) {
13
25
  spec = ctx.spec
14
26
  point = ctx.point
15
27
  spec.step = "reqform" if spec
28
+ data = ProjectNameUtilities.omit_keys(ctx.reqdata, ProjectNameUtilities.header_arg_names(point))
16
29
  transform = ProjectNameHelpers.to_map(VoxgigStruct.getprop(point, "transform"))
17
- return ProjectNameUtilities.strip_action(ctx.reqdata) unless transform
30
+ return ProjectNameUtilities.strip_action(data) unless transform
18
31
  reqform = VoxgigStruct.getprop(transform, "req")
19
- return ProjectNameUtilities.strip_action(ctx.reqdata) unless reqform
20
- ProjectNameUtilities.strip_action(VoxgigStruct.transform({ "reqdata" => ctx.reqdata }, reqform))
32
+ return ProjectNameUtilities.strip_action(data) unless reqform
33
+ ProjectNameUtilities.strip_action(VoxgigStruct.transform({ "reqdata" => data }, reqform))
21
34
  }
22
35
  end
@@ -2,7 +2,7 @@ use std::rc::Rc;
2
2
 
3
3
  use crate::core::context::Context;
4
4
  use crate::core::error::ProjectNameError;
5
- use crate::core::helpers::setp;
5
+ use crate::core::helpers::{getp, setp};
6
6
  use crate::utility::voxgigstruct as vs;
7
7
  use crate::utility::voxgigstruct::Value;
8
8
 
@@ -26,6 +26,7 @@ pub fn make_url_util(ctx: &Rc<Context>) -> Result<String, ProjectNameError> {
26
26
  )
27
27
  };
28
28
 
29
+ let suffixless = suffix.is_empty();
29
30
  let mut url = vs::join(
30
31
  &Value::list(vec![
31
32
  Value::str(base),
@@ -37,6 +38,14 @@ pub fn make_url_util(ctx: &Rc<Context>) -> Result<String, ProjectNameError> {
37
38
  true,
38
39
  );
39
40
 
41
+ // A route the definition ends with a slash keeps it: a server such as a
42
+ // Django REST one redirects or refuses the route without it.
43
+ if let Value::Str(orig) = getp(&ctx.point.borrow().clone(), "orig") {
44
+ if orig.ends_with('/') && suffixless && !url.ends_with('/') {
45
+ url.push('/');
46
+ }
47
+ }
48
+
40
49
  let resmatch = Value::empty_map();
41
50
 
42
51
  if let Value::Map(pm) = &params {
@@ -1,7 +1,7 @@
1
1
  use std::rc::Rc;
2
2
 
3
3
  use crate::core::context::Context;
4
- use crate::core::helpers::getp;
4
+ use crate::core::helpers::{getp, setp};
5
5
  use crate::utility::voxgigstruct as vs;
6
6
  use crate::utility::voxgigstruct::Value;
7
7
 
@@ -12,12 +12,48 @@ pub fn prepare_headers_util(ctx: &Rc<Context>) -> Value {
12
12
  };
13
13
 
14
14
  let headers = getp(&options, "headers");
15
- if headers.is_noval() || headers.is_null() {
16
- return Value::empty_map();
17
- }
15
+ let out = if headers.is_noval() || headers.is_null() {
16
+ Value::empty_map()
17
+ } else {
18
+ match vs::clone(&headers) {
19
+ Value::Map(m) => Value::Map(m),
20
+ _ => Value::empty_map(),
21
+ }
22
+ };
18
23
 
19
- match vs::clone(&headers) {
20
- Value::Map(m) => Value::Map(m),
21
- _ => Value::empty_map(),
24
+ // A header parameter travels as a header, under the name the definition
25
+ // gives it, and only from this call's own arguments. It replaces a default
26
+ // of the same name, whatever its case.
27
+ let point = ctx.point.borrow().clone();
28
+ if let Value::List(hl) = getp(&getp(&point, "args"), "header") {
29
+ let reqmatch = ctx.reqmatch.borrow().clone();
30
+ let reqdata = ctx.reqdata.borrow().clone();
31
+ for hd in hl.borrow().iter() {
32
+ let name = match getp(hd, "name") {
33
+ Value::Str(n) if !n.is_empty() => n,
34
+ _ => continue,
35
+ };
36
+ let wire = match getp(hd, "orig") {
37
+ Value::Str(o) if !o.is_empty() => o,
38
+ _ => name.clone(),
39
+ };
40
+ let mut val = getp(&reqmatch, &name);
41
+ if val.is_noval() || val.is_null() {
42
+ val = getp(&reqdata, &name);
43
+ }
44
+ if !val.is_noval() && !val.is_null() {
45
+ let key = wire.to_lowercase();
46
+ if let Value::Map(m) = &out {
47
+ let same: Vec<String> =
48
+ m.borrow().keys().filter(|k| k.to_lowercase() == key).cloned().collect();
49
+ for k in same {
50
+ m.borrow_mut().shift_remove(&k);
51
+ }
52
+ }
53
+ setp(&out, &key, Value::Str(vs::stringify(&val, None, false)));
54
+ }
55
+ }
22
56
  }
57
+
58
+ out
23
59
  }
@@ -45,6 +45,21 @@ pub fn prepare_query_util(ctx: &Rc<Context>) -> Value {
45
45
  false
46
46
  };
47
47
 
48
+ // A header parameter travels in the headers, which prepare_headers fills.
49
+ let aheader = getp(&getp(&point, "args"), "header");
50
+ let in_header = |key: &str| -> bool {
51
+ if let Value::List(hl) = &aheader {
52
+ for hd in hl.borrow().iter() {
53
+ if let Value::Str(s) = getp(hd, "name") {
54
+ if s == key {
55
+ return true;
56
+ }
57
+ }
58
+ }
59
+ }
60
+ false
61
+ };
62
+
48
63
  // A query parameter travels under the name the definition gives it, its
49
64
  // orig, which the model may have renamed for the caller.
50
65
  let aquery = getp(&getp(&point, "args"), "query");
@@ -69,7 +84,9 @@ pub fn prepare_query_util(ctx: &Rc<Context>) -> Value {
69
84
  .map(|(k, v)| (k.clone(), v.clone()))
70
85
  .collect();
71
86
  for (key, val) in entries {
72
- if !val.is_noval() && !val.is_null() && "$action" != key && !contains(&key) && !in_args(&key) {
87
+ if !val.is_noval() && !val.is_null() && "$action" != key && !contains(&key) && !in_args(&key)
88
+ && !in_header(&key)
89
+ {
73
90
  setp(&out, &wire_name(&key), val);
74
91
  }
75
92
  }
@@ -8,8 +8,28 @@ use crate::utility::voxgigstruct::Value;
8
8
  // `$action` selects the point (see make_point_util); it is never an API
9
9
  // field, so the body is a copy without it. The caller's map is left untouched.
10
10
  fn strip_action(reqdata: Value) -> Value {
11
+ omit_keys(reqdata, &["$action".to_string()])
12
+ }
13
+
14
+ // A header argument travels as a header, which prepare_headers_util sends, so
15
+ // the body is built from the request data without it.
16
+ fn header_arg_names(point: &Value) -> Vec<String> {
17
+ match getp(&getp(point, "args"), "header") {
18
+ Value::List(hl) => hl
19
+ .borrow()
20
+ .iter()
21
+ .filter_map(|hd| match getp(hd, "name") {
22
+ Value::Str(n) if !n.is_empty() => Some(n),
23
+ _ => None,
24
+ })
25
+ .collect(),
26
+ _ => Vec::new(),
27
+ }
28
+ }
29
+
30
+ fn omit_keys(reqdata: Value, names: &[String]) -> Value {
11
31
  let has = match &reqdata {
12
- Value::Map(src) => src.borrow().contains_key("$action"),
32
+ Value::Map(src) => names.iter().any(|n| src.borrow().contains_key(n)),
13
33
  _ => false,
14
34
  };
15
35
  if !has {
@@ -23,7 +43,7 @@ fn strip_action(reqdata: Value) -> Value {
23
43
  .map(|(k, v)| (k.clone(), v.clone()))
24
44
  .collect();
25
45
  for (k, v) in entries {
26
- if "$action" != k {
46
+ if !names.contains(&k) {
27
47
  setp(&body, &k, v);
28
48
  }
29
49
  }
@@ -39,19 +59,21 @@ pub fn transform_request_util(ctx: &Rc<Context>) -> Value {
39
59
  sp.borrow_mut().step = "reqform".to_string();
40
60
  }
41
61
 
62
+ let reqdata = omit_keys(ctx.reqdata.borrow().clone(), &header_arg_names(&point));
63
+
42
64
  let transform = to_map(&getp(&point, "transform"));
43
65
  if transform.is_noval() {
44
- return strip_action(ctx.reqdata.borrow().clone());
66
+ return strip_action(reqdata);
45
67
  }
46
68
 
47
69
  let reqform = getp(&transform, "req");
48
70
  if reqform.is_noval() || reqform.is_null() {
49
- return strip_action(ctx.reqdata.borrow().clone());
71
+ return strip_action(reqdata);
50
72
  }
51
73
 
52
- let store = jo(vec![("reqdata", ctx.reqdata.borrow().clone())]);
74
+ let store = jo(vec![("reqdata", reqdata.clone())]);
53
75
  strip_action(match vs::transform(&store, &reqform, None) {
54
- Ok(reqdata) => reqdata,
55
- Err(_) => ctx.reqdata.borrow().clone(),
76
+ Ok(out) => out,
77
+ Err(_) => reqdata,
56
78
  })
57
79
  }
@@ -549,6 +549,15 @@ object MakeUrl {
549
549
  joinParts.add(spec.suffix)
550
550
  var url = Struct.join(joinParts, "/", true)
551
551
 
552
+ // A route the definition ends with a slash keeps it: a server such as a
553
+ // Django REST one redirects or refuses the route without it.
554
+ val orig = if (ctx.point == null) null else Struct.getprop(ctx.point, "orig", null)
555
+ orig match {
556
+ case o: String if o.endsWith("/") && (spec.suffix == null || spec.suffix.isEmpty) && !url.endsWith("/") =>
557
+ url = url + "/"
558
+ case _ =>
559
+ }
560
+
552
561
  val resmatch = new LinkedHashMap[String, Object]()
553
562
 
554
563
  val pit = Struct.items(spec.params).iterator()