@voxgig/sdkgen 4.30.3 → 4.31.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 (70) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/ReadmeTop.js +6 -5
  3. package/dist/cmp/ReadmeTop.js.map +1 -1
  4. package/dist/helpers/definitionPlan.d.ts +27 -0
  5. package/dist/helpers/definitionPlan.js +219 -0
  6. package/dist/helpers/definitionPlan.js.map +1 -0
  7. package/dist/sdkgen.d.ts +2 -1
  8. package/dist/sdkgen.js +5 -3
  9. package/dist/sdkgen.js.map +1 -1
  10. package/dist/tsconfig.tsbuildinfo +1 -1
  11. package/package.json +2 -2
  12. package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +5 -3
  13. package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +7 -5
  14. package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +5 -3
  15. package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +6 -4
  16. package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +8 -6
  17. package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +7 -9
  18. package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +5 -3
  19. package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +6 -4
  20. package/project/.sdk/src/cmp/js/ReadmeInstall_js.ts +22 -5
  21. package/project/.sdk/src/cmp/js/TestDefinition_js.ts +40 -0
  22. package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
  23. package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +5 -3
  24. package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +7 -3
  25. package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +7 -5
  26. package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +6 -3
  27. package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +5 -3
  28. package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +11 -5
  29. package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +5 -3
  30. package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +5 -3
  31. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +14 -4
  32. package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +5 -3
  33. package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +5 -3
  34. package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +5 -3
  35. package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +6 -4
  36. package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +10 -4
  37. package/project/.sdk/src/cmp/ts/TestDefinition_ts.ts +40 -0
  38. package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
  39. package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +5 -3
  40. package/project/.sdk/tm/c/utility/prepare_query.c +34 -2
  41. package/project/.sdk/tm/clojure/src/sdk/core.clj +20 -2
  42. package/project/.sdk/tm/cpp/utility/pipeline.hpp +31 -2
  43. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +32 -4
  44. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +35 -1
  45. package/project/.sdk/tm/go/utility/prepare_query.go +31 -1
  46. package/project/.sdk/tm/java/utility/PrepareQuery.java +29 -5
  47. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +16 -2
  48. package/project/.sdk/tm/js/test/definition-runner.js +161 -0
  49. package/project/.sdk/tm/js/test/pipeline.test.js +43 -3
  50. package/project/.sdk/tm/js/test/sdk-test-control.json +4 -1
  51. package/project/.sdk/tm/kotlin/utility/Prepare.kt +29 -5
  52. package/project/.sdk/tm/lua/utility/prepare_query.lua +31 -2
  53. package/project/.sdk/tm/ocaml/sdk_runtime.ml +22 -1
  54. package/project/.sdk/tm/perl/utility/prepare_query.pm +25 -2
  55. package/project/.sdk/tm/php/utility/PrepareQuery.php +28 -1
  56. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +23 -2
  57. package/project/.sdk/tm/rb/utility/prepare_query.rb +24 -2
  58. package/project/.sdk/tm/rust/utility/prepare_query.rs +34 -2
  59. package/project/.sdk/tm/scala/utility/Prepare.scala +31 -4
  60. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +23 -5
  61. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +16 -2
  62. package/project/.sdk/tm/ts/test/definition-runner.ts +179 -0
  63. package/project/.sdk/tm/ts/test/pipeline.test.ts +43 -3
  64. package/project/.sdk/tm/ts/test/sdk-test-control.json +4 -1
  65. package/project/.sdk/tm/ts/test/utility.ts +3 -2
  66. package/project/.sdk/tm/zig/core/utility.zig +29 -1
  67. package/project/sdkgen-package.json +1 -1
  68. package/src/cmp/ReadmeTop.ts +7 -6
  69. package/src/helpers/definitionPlan.ts +265 -0
  70. package/src/sdkgen.ts +2 -0
@@ -148,9 +148,11 @@ function basicBlock(spec: AuthSpec): string {
148
148
  if (!spec.basic) return ''
149
149
 
150
150
  return `
151
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
151
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
152
152
  // token in the header (the branch below) can never authenticate against
153
153
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
154
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
155
+ // the user with a blank password (\`curl -u key:\`).
154
156
  if true == gpath(options, "auth", "basic").asBool {
155
157
  let secret = getprop(.map(options), .string(optionSecret), .string(notFound))
156
158
 
@@ -159,12 +161,12 @@ function basicBlock(spec: AuthSpec): string {
159
161
  noSecret = true
160
162
  }
161
163
 
162
- if skip || noSecret {
164
+ if skip {
163
165
  headers.entries.removeValue(forKey: headerAuth)
164
166
  } else {
165
167
  var authPrefix = ""
166
168
  if let ap = gpath(options, "auth", "prefix").asString { authPrefix = ap }
167
- let joined = (apikey.asString ?? "") + ":" + (secret.asString ?? "")
169
+ let joined = (apikey.asString ?? "") + ":" + (noSecret ? "" : (secret.asString ?? ""))
168
170
  let b64 = Data(joined.utf8).base64EncodedString()
169
171
  // Empty prefix (raw credential) must not add a leading space.
170
172
  headers.entries[headerAuth] = .string(authPrefix == "" ? b64 : authPrefix + " " + b64)
@@ -108,19 +108,21 @@ ${cookieHelper(spec.where)}
108
108
  // query parameter or a cookie, so the branch is emitted only where it
109
109
  // can mean something.
110
110
  const basicBlock = (spec.basic && 'header' === spec.where) ? `
111
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
111
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
112
112
  // token in the header (the branch below) can never authenticate against
113
113
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
114
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
115
+ // the user with a blank password (\`curl -u key:\`).
114
116
  if (true === options.auth.basic) {
115
117
  const secret = getprop(options, OPTION_secret, NOTFOUND)
116
118
  const noApikey = NOTFOUND === apikey || null == apikey || '' === apikey
117
- const noSecret = NOTFOUND === secret || null == secret || '' === secret
119
+ const pass = NOTFOUND === secret || null == secret ? '' : secret
118
120
 
119
- if (noApikey || noSecret) {
121
+ if (noApikey) {
120
122
  delprop(headers, CRED_name)
121
123
  }
122
124
  else {
123
- const b64 = Buffer.from(apikey + ':' + secret).toString('base64')
125
+ const b64 = Buffer.from(apikey + ':' + pass).toString('base64')
124
126
  setprop(headers, CRED_name, prefix ? prefix + ' ' + b64 : b64)
125
127
  }
126
128
 
@@ -16,12 +16,18 @@ ${installCommand(model, target.name)}
16
16
  }
17
17
 
18
18
  // Publish pending: the package is not yet on npm, so install from the
19
- // git release tag instead of a `npm install` that would 404.
20
- const { releasesUrl } = repoInfo(model)
19
+ // git release tag instead of a `npm install` that would 404. A repository
20
+ // may have no tag at all, and npm cannot install a subdirectory of a git
21
+ // URL, so a clone is always offered too. It carries the compiled dist/.
22
+ const { releasesUrl, repoUrl, repo } = repoInfo(model)
21
23
  Content(`This package is not yet published to npm. Install it from the GitHub
22
- release tag (\`${target.name}/vX.Y.Z\`):
24
+ release tag (\`${target.name}/vX.Y.Z\`, see [Releases](${releasesUrl})), or from a
25
+ clone, which carries the compiled \`dist/\`:
23
26
 
24
- - Releases: [${releasesUrl}](${releasesUrl})
27
+ \`\`\`bash
28
+ git clone ${repoUrl}
29
+ npm install ./${repo}/${target.name}
30
+ \`\`\`
25
31
 
26
32
  `)
27
33
  })
@@ -0,0 +1,40 @@
1
+ import { cmp, File, Content, definitionPlan } from '@voxgig/sdkgen'
2
+
3
+
4
+ // Every operation checked against the API definition, offline, in the SDK's
5
+ // own suite: see test/definition-runner.
6
+ const TestDefinition = cmp(function TestDefinition(props: any) {
7
+ const plan = definitionPlan(props.ctx$)
8
+ if (0 === plan.length) return
9
+
10
+ File({ name: 'definition.test.ts' }, () => Content(`import { describe, test } from 'node:test'
11
+ import { SDK } from '..'
12
+ import { runDefinitionPoint } from './definition-runner'
13
+ import { isControlSkipped } from './utility'
14
+
15
+
16
+ // Generated from the API definition, not from the model this SDK was built
17
+ // from: the route, the declared query parameters, the credential the security
18
+ // scheme names, and the definition's own response example.
19
+ const PLAN: any[] = ${JSON.stringify(plan, null, 2)}
20
+
21
+
22
+ describe('definition', () => {
23
+ for (const point of PLAN) {
24
+ test(point.entity + '.' + point.op + ' ' + point.method + ' ' + point.path, async (t) => {
25
+ const control = isControlSkipped('entityOp', point.entity + '.' + point.op, 'definition')
26
+ if (control.skip) {
27
+ t.skip(control.reason || 'skipped via sdk-test-control.json')
28
+ return
29
+ }
30
+ await runDefinitionPoint(SDK, point)
31
+ })
32
+ }
33
+ })
34
+ `))
35
+ })
36
+
37
+
38
+ export {
39
+ TestDefinition
40
+ }
@@ -8,6 +8,7 @@ import { cmp, each, Folder, entityCollection,
8
8
 
9
9
 
10
10
  import { TestLive } from './TestLive_ts'
11
+ import { TestDefinition } from './TestDefinition_ts'
11
12
  import { TestDirect } from './TestDirect_ts'
12
13
  import { TestEntity } from './TestEntity_ts'
13
14
  import { ReadmeExampleTest } from './ReadmeExampleTest_ts'
@@ -23,6 +24,7 @@ const Test = cmp(function Test(props: any) {
23
24
  // Write-once: a project's edited control file survives regeneration.
24
25
  TestControl({ target, dir: 'test' })
25
26
  TestLive({ target })
27
+ TestDefinition({ target })
26
28
 
27
29
  ReadmeExampleTest({ target })
28
30
  ReadmeExamplesTest({ target })
@@ -330,9 +330,11 @@ fn base64_std(text: []const u8) []const u8 {
330
330
  // something — and only when the model says the scheme IS basic.
331
331
  function basicBlock(): string {
332
332
  return `
333
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
333
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
334
334
  // token in the header (the branch below) can never authenticate against
335
335
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
336
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
337
+ // the user with a blank password (\`curl -u key:\`).
336
338
  if (is_true(h.getpath(&.{ "auth", "basic" }, options))) {
337
339
  const secret = vs.getprop(h.A(), options, h.vstr(OPTION_SECRET), h.vstr(NOT_FOUND)) catch h.vstr(NOT_FOUND);
338
340
 
@@ -342,7 +344,7 @@ function basicBlock(): string {
342
344
  else => false,
343
345
  };
344
346
 
345
- if (skip or no_secret) {
347
+ if (skip) {
346
348
  h.del_prop(headers, h.vstr(CRED_NAME));
347
349
  } else {
348
350
  const basic_prefix: []const u8 = switch (h.getpath(&.{ "auth", "prefix" }, options)) {
@@ -353,7 +355,7 @@ function basicBlock(): string {
353
355
  .string => |s| s,
354
356
  else => "",
355
357
  };
356
- const secret_val: []const u8 = switch (secret) {
358
+ const secret_val: []const u8 = if (no_secret) "" else switch (secret) {
357
359
  .string => |s| s,
358
360
  else => "",
359
361
  };
@@ -15,12 +15,44 @@ static bool params_contains(voxgig_value* params, const char* key) {
15
15
  return false;
16
16
  }
17
17
 
18
+ // A path parameter travels in the path. The generated config lists them as
19
+ // args.params, which prepare_params reads; params is the older list of names.
20
+ static bool args_params_contains(voxgig_value* aparams, const char* key) {
21
+ if (voxgig_is_list(aparams)) {
22
+ voxgig_list* pl = voxgig_as_list(aparams);
23
+ for (size_t i = 0; i < pl->len; i++) {
24
+ voxgig_value* name = getp(pl->items[i], "name");
25
+ if (voxgig_is_string(name) && strcmp(voxgig_as_string(name), key) == 0) return true;
26
+ }
27
+ }
28
+ return false;
29
+ }
30
+
31
+ // A query parameter travels under the name the definition gives it, its orig,
32
+ // which the model may have renamed for the caller.
33
+ static const char* query_wire_name(voxgig_value* aquery, const char* key) {
34
+ if (voxgig_is_list(aquery)) {
35
+ voxgig_list* ql = voxgig_as_list(aquery);
36
+ for (size_t i = 0; i < ql->len; i++) {
37
+ voxgig_value* name = getp(ql->items[i], "name");
38
+ voxgig_value* orig = getp(ql->items[i], "orig");
39
+ if (voxgig_is_string(name) && voxgig_is_string(orig) &&
40
+ strcmp(voxgig_as_string(name), key) == 0 && voxgig_as_string(orig)[0] != '\0') {
41
+ return voxgig_as_string(orig);
42
+ }
43
+ }
44
+ }
45
+ return key;
46
+ }
47
+
18
48
  voxgig_value* prepare_query_util(Context* ctx) {
19
49
  voxgig_value* point = ctx->point;
20
50
  voxgig_value* reqmatch = voxgig_is_map(ctx->reqmatch) ? ctx->reqmatch : voxgig_new_map();
21
51
 
22
52
  voxgig_value* params = getp(point, "params");
23
53
  if (!voxgig_is_list(params)) params = voxgig_new_list();
54
+ voxgig_value* aparams = getpath2(point, "args", "params");
55
+ voxgig_value* aquery = getpath2(point, "args", "query");
24
56
 
25
57
  voxgig_value* out = voxgig_new_map();
26
58
  if (voxgig_is_map(reqmatch)) {
@@ -29,8 +61,8 @@ voxgig_value* prepare_query_util(Context* ctx) {
29
61
  const char* key = rm->entries[i].key;
30
62
  voxgig_value* val = rm->entries[i].value;
31
63
  if (!v_is_noval(val) && !v_is_null(val) && strcmp(key, "$action") != 0 &&
32
- !params_contains(params, key)) {
33
- setp(out, key, v_share(val));
64
+ !params_contains(params, key) && !args_params_contains(aparams, key)) {
65
+ setp(out, query_wire_name(aquery, key), v_share(val));
34
66
  }
35
67
  }
36
68
  }
@@ -500,12 +500,30 @@
500
500
  (let [point (oget ctx :point)
501
501
  reqmatch (or (oget ctx :reqmatch) (vs/jm))
502
502
  params (let [p (when point (vs/getprop point "params"))] (if (vs/islist p) p (vs/jt)))
503
- pset (into #{} (vec params))
503
+ ;; A path parameter travels in the path. The generated config lists
504
+ ;; them as args.params, which u-prepare-params reads; params is the
505
+ ;; older list of names.
506
+ aparams (let [args (when point (vs/getprop point "args"))
507
+ p (when (vs/ismap args) (vs/getprop args "params"))]
508
+ (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)))
511
+ ;; A query parameter travels under the name the definition gives it,
512
+ ;; its orig, which the model may have renamed for the caller.
513
+ aquery (let [args (when point (vs/getprop point "args"))
514
+ q (when (vs/ismap args) (vs/getprop args "query"))]
515
+ (if (vs/islist q) q (vs/jt)))
516
+ wire (into {}
517
+ (keep (fn [qd]
518
+ (when (vs/ismap qd)
519
+ (let [n (vs/getprop qd "name") o (vs/getprop qd "orig")]
520
+ (when (and (string? n) (string? o) (not= "" o)) [n o]))))
521
+ (vec aquery)))
504
522
  out (vs/jm)]
505
523
  (doseq [item (or (vs/items reqmatch) [])]
506
524
  (let [k (vs/getprop item 0) v (vs/getprop item 1)]
507
525
  (when (and (some? v) (string? k) (not= "$action" k) (not (contains? pset k)))
508
- (.put ^java.util.Map out k v))))
526
+ (.put ^java.util.Map out (get wire k k) v))))
509
527
  out))
510
528
 
511
529
  ;; `$action` selects the point (see u-make-point); it is never an API field,
@@ -1022,12 +1022,41 @@ inline Value prepareQuery(CtxPtr ctx) {
1022
1022
  return false;
1023
1023
  };
1024
1024
 
1025
+ // A path parameter travels in the path. The generated config lists them as
1026
+ // args.params, which prepareParams reads; params is the older list of names.
1027
+ 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()) {
1031
+ Value name = getp(pd, "name");
1032
+ if (name.is_string() && name.as_string() == s) return true;
1033
+ }
1034
+ return false;
1035
+ };
1036
+
1037
+ // A query parameter travels under the name the definition gives it, its
1038
+ // orig, which the model may have renamed for the caller.
1039
+ Value aquery = point.is_map() ? getp(getp(point, "args"), "query") : Value::undef();
1040
+ auto wire_name = [&](const std::string& s) {
1041
+ if (aquery.is_list()) {
1042
+ for (const auto& qd : *aquery.as_list()) {
1043
+ Value name = getp(qd, "name");
1044
+ Value orig = getp(qd, "orig");
1045
+ if (name.is_string() && orig.is_string() && name.as_string() == s &&
1046
+ !orig.as_string().empty()) {
1047
+ return orig.as_string();
1048
+ }
1049
+ }
1050
+ }
1051
+ return s;
1052
+ };
1053
+
1025
1054
  Value out = vmap();
1026
1055
  for (const auto& item : Struct::items(reqmatch)) {
1027
1056
  std::string key = as_str(pair_key(item));
1028
1057
  Value val = pair_val(item);
1029
- if (!is_nullish(val) && "$action" != key && !contains_str(params, key)) {
1030
- map_put(out, key, val);
1058
+ if (!is_nullish(val) && "$action" != key && !contains_str(params, key) && !in_args(key)) {
1059
+ map_put(out, wire_name(key), val);
1031
1060
  }
1032
1061
  }
1033
1062
  return out;
@@ -12,12 +12,40 @@ public static partial class SdkUtility
12
12
  var point = ctx.Point;
13
13
  var reqmatch = ctx.Reqmatch ?? new Dictionary<string, object?>();
14
14
 
15
- List<object?>? paramnames = null;
15
+ var paramnames = new List<object?>();
16
16
  if (point != null && StructUtils.GetProp(point, "params") is List<object?> pl)
17
17
  {
18
- paramnames = pl;
18
+ paramnames.AddRange(pl);
19
+ }
20
+ // A path parameter travels in the path. The generated config lists them
21
+ // as args.params, which PrepareParams reads; params is the older list.
22
+ if (point != null &&
23
+ StructUtils.GetPath(point, StructUtils.Jt("args", "params")) is List<object?> apl)
24
+ {
25
+ foreach (var pd in apl)
26
+ {
27
+ if (StructUtils.GetProp(pd, "name") is string name)
28
+ {
29
+ paramnames.Add(name);
30
+ }
31
+ }
32
+ }
33
+
34
+ // A query parameter travels under the name the definition gives it,
35
+ // its orig, which the model may have renamed for the caller.
36
+ var wire = new Dictionary<string, string>();
37
+ if (point != null &&
38
+ StructUtils.GetPath(point, StructUtils.Jt("args", "query")) is List<object?> aql)
39
+ {
40
+ foreach (var qd in aql)
41
+ {
42
+ if (StructUtils.GetProp(qd, "name") is string qname &&
43
+ StructUtils.GetProp(qd, "orig") is string qorig && qorig != "")
44
+ {
45
+ wire[qname] = qorig;
46
+ }
47
+ }
19
48
  }
20
- paramnames ??= new List<object?>();
21
49
 
22
50
  var query = new Dictionary<string, object?>();
23
51
  foreach (var item in StructUtils.Items(reqmatch))
@@ -26,7 +54,7 @@ public static partial class SdkUtility
26
54
  var val = item[1];
27
55
  if (val != null && "$action" != key && !ContainsStr(paramnames, key))
28
56
  {
29
- query[key] = val;
57
+ query[wire.TryGetValue(key, out var wkey) ? wkey : key] = val;
30
58
  }
31
59
  }
32
60
 
@@ -1206,12 +1206,46 @@ defmodule ProjectName.Utility do
1206
1206
  Enum.map(0..(S.size(params) - 1), fn i -> S.getelem(params, i) end)
1207
1207
  end
1208
1208
 
1209
+ # A path parameter travels in the path. The generated config lists them
1210
+ # 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
1223
+
1224
+ # A query parameter travels under the name the definition gives it, its
1225
+ # orig, which the model may have renamed for the caller.
1226
+ aquery = if point != nil, do: S.getpath(point, "args.query"), else: nil
1227
+
1228
+ wire =
1229
+ if S.islist(aquery) and S.size(aquery) > 0 do
1230
+ Enum.reduce(0..(S.size(aquery) - 1), %{}, fn i, acc ->
1231
+ qd = S.getelem(aquery, i)
1232
+ name = S.getprop(qd, "name")
1233
+ orig = S.getprop(qd, "orig")
1234
+
1235
+ if is_binary(name) and is_binary(orig) and orig != "",
1236
+ do: Map.put(acc, name, orig),
1237
+ else: acc
1238
+ end)
1239
+ else
1240
+ %{}
1241
+ end
1242
+
1209
1243
  out = S.jm([])
1210
1244
 
1211
1245
  Enum.each(H.entries(reqmatch), fn {key, val} ->
1212
1246
  if val != nil and is_binary(key) and key != "$action" and
1213
1247
  not Enum.member?(param_strs, key) do
1214
- S.setprop(out, key, val)
1248
+ S.setprop(out, Map.get(wire, key, key), val)
1215
1249
  end
1216
1250
  end)
1217
1251
 
@@ -17,7 +17,7 @@ func prepareQueryUtil(ctx *core.Context) map[string]any {
17
17
  if point != nil {
18
18
  if p := vs.GetProp(point, "params"); p != nil {
19
19
  if pl, ok := p.([]any); ok {
20
- params = pl
20
+ params = append([]any{}, pl...)
21
21
  }
22
22
  }
23
23
  }
@@ -25,11 +25,41 @@ func prepareQueryUtil(ctx *core.Context) map[string]any {
25
25
  params = []any{}
26
26
  }
27
27
 
28
+ // A path parameter travels in the path. The generated config lists them as
29
+ // args.params, which prepareParams reads; params is the older list of names.
30
+ if point != nil {
31
+ if pl, ok := vs.GetPath(point, []any{"args", "params"}).([]any); ok {
32
+ for _, pd := range pl {
33
+ if name, ok := vs.GetProp(pd, "name").(string); ok {
34
+ params = append(params, name)
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ // A query parameter travels under the name the definition gives it, its
41
+ // orig, which the model may have renamed for the caller.
42
+ wire := map[string]string{}
43
+ if point != nil {
44
+ if ql, ok := vs.GetPath(point, []any{"args", "query"}).([]any); ok {
45
+ for _, qd := range ql {
46
+ name, _ := vs.GetProp(qd, "name").(string)
47
+ orig, _ := vs.GetProp(qd, "orig").(string)
48
+ if "" != name && "" != orig {
49
+ wire[name] = orig
50
+ }
51
+ }
52
+ }
53
+ }
54
+
28
55
  out := map[string]any{}
29
56
  for _, item := range vs.Items(reqmatch) {
30
57
  key, _ := item[0].(string)
31
58
  val := item[1]
32
59
  if val != nil && key != "$action" && !containsStr(params, key) {
60
+ if orig, ok := wire[key]; ok {
61
+ key = orig
62
+ }
33
63
  out[key] = val
34
64
  }
35
65
  }
@@ -20,15 +20,39 @@ final class PrepareQuery {
20
20
  reqmatch = new LinkedHashMap<>();
21
21
  }
22
22
 
23
- List<Object> params = null;
23
+ List<Object> params = new ArrayList<>();
24
24
  if (point != null) {
25
25
  Object p = Struct.getprop(point, "params");
26
26
  if (p instanceof List) {
27
- params = (List<Object>) p;
27
+ params.addAll((List<Object>) p);
28
+ }
29
+ // A path parameter travels in the path. The generated config lists them
30
+ // as args.params, which prepareParams reads; params is the older list.
31
+ Object pl = Struct.getpath(point, List.of("args", "params"));
32
+ if (pl instanceof List) {
33
+ for (Object pd : (List<Object>) pl) {
34
+ Object name = Struct.getprop(pd, "name");
35
+ if (name instanceof String) {
36
+ params.add(name);
37
+ }
38
+ }
28
39
  }
29
40
  }
30
- if (params == null) {
31
- params = new ArrayList<>();
41
+
42
+ // A query parameter travels under the name the definition gives it, its
43
+ // orig, which the model may have renamed for the caller.
44
+ Map<String, String> wire = new LinkedHashMap<>();
45
+ if (point != null) {
46
+ Object ql = Struct.getpath(point, List.of("args", "query"));
47
+ if (ql instanceof List) {
48
+ for (Object qd : (List<Object>) ql) {
49
+ Object name = Struct.getprop(qd, "name");
50
+ Object orig = Struct.getprop(qd, "orig");
51
+ if (name instanceof String && orig instanceof String && !((String) orig).isEmpty()) {
52
+ wire.put((String) name, (String) orig);
53
+ }
54
+ }
55
+ }
32
56
  }
33
57
 
34
58
  Map<String, Object> out = new LinkedHashMap<>();
@@ -36,7 +60,7 @@ final class PrepareQuery {
36
60
  String key = item.get(0) instanceof String ? (String) item.get(0) : "";
37
61
  Object val = item.get(1);
38
62
  if (val != null && !"$action".equals(key) && !containsStr(params, key)) {
39
- out.put(key, val);
63
+ out.put(wire.getOrDefault(key, key), val);
40
64
  }
41
65
  }
42
66
 
@@ -11,10 +11,24 @@ function prepareQuery(ctx) {
11
11
  params = params || []
12
12
  reqmatch = reqmatch || {}
13
13
 
14
+ // A path parameter travels in the path. The generated config lists them as
15
+ // args.params, which prepareParams reads; params is the older list of names.
16
+ const inpath = params.concat(
17
+ ((point.args && point.args.params) || []).map((p) => p && p.name))
18
+
19
+ // A query parameter travels under the name the definition gives it, its
20
+ // orig, which the model may have renamed for the caller.
21
+ const wire = Object.create(null)
22
+ for (const q of ((point.args && point.args.query) || [])) {
23
+ if (q && 'string' === typeof q.name && 'string' === typeof q.orig && '' !== q.orig) {
24
+ wire[q.name] = q.orig
25
+ }
26
+ }
27
+
14
28
  const out = {}
15
29
  for (let [key, val] of items(reqmatch)) {
16
- if (null != val && '$action' !== key && !params.includes(key)) {
17
- out[key] = val
30
+ if (null != val && '$action' !== key && !inpath.includes(key)) {
31
+ out[null == wire[key] ? key : wire[key]] = val
18
32
  }
19
33
  }
20
34