@voxgig/sdkgen 4.30.3 → 4.31.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 (74) 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/cmp/ReadmeTop.js +6 -5
  6. package/dist/cmp/ReadmeTop.js.map +1 -1
  7. package/dist/helpers/definitionPlan.d.ts +27 -0
  8. package/dist/helpers/definitionPlan.js +219 -0
  9. package/dist/helpers/definitionPlan.js.map +1 -0
  10. package/dist/sdkgen.d.ts +2 -1
  11. package/dist/sdkgen.js +5 -3
  12. package/dist/sdkgen.js.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +2 -2
  15. package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +5 -3
  16. package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +7 -5
  17. package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +5 -3
  18. package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +6 -4
  19. package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +8 -6
  20. package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +7 -9
  21. package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +5 -3
  22. package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +6 -4
  23. package/project/.sdk/src/cmp/js/ReadmeInstall_js.ts +22 -5
  24. package/project/.sdk/src/cmp/js/TestDefinition_js.ts +40 -0
  25. package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
  26. package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +5 -3
  27. package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +7 -3
  28. package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +7 -5
  29. package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +6 -3
  30. package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +5 -3
  31. package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +11 -5
  32. package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +5 -3
  33. package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +5 -3
  34. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +14 -4
  35. package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +5 -3
  36. package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +5 -3
  37. package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +5 -3
  38. package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +6 -4
  39. package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +10 -4
  40. package/project/.sdk/src/cmp/ts/TestDefinition_ts.ts +40 -0
  41. package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
  42. package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +5 -3
  43. package/project/.sdk/tm/c/utility/prepare_query.c +34 -2
  44. package/project/.sdk/tm/clojure/src/sdk/core.clj +20 -2
  45. package/project/.sdk/tm/cpp/utility/pipeline.hpp +31 -2
  46. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +32 -4
  47. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +35 -1
  48. package/project/.sdk/tm/go/utility/prepare_query.go +31 -1
  49. package/project/.sdk/tm/java/utility/PrepareQuery.java +29 -5
  50. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +16 -2
  51. package/project/.sdk/tm/js/test/definition-runner.js +161 -0
  52. package/project/.sdk/tm/js/test/pipeline.test.js +43 -3
  53. package/project/.sdk/tm/js/test/sdk-test-control.json +4 -1
  54. package/project/.sdk/tm/kotlin/utility/Prepare.kt +29 -5
  55. package/project/.sdk/tm/lua/utility/prepare_query.lua +31 -2
  56. package/project/.sdk/tm/ocaml/sdk_runtime.ml +22 -1
  57. package/project/.sdk/tm/perl/utility/prepare_query.pm +25 -2
  58. package/project/.sdk/tm/php/utility/PrepareQuery.php +28 -1
  59. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +23 -2
  60. package/project/.sdk/tm/rb/utility/prepare_query.rb +24 -2
  61. package/project/.sdk/tm/rust/utility/prepare_query.rs +34 -2
  62. package/project/.sdk/tm/scala/utility/Prepare.scala +31 -4
  63. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +23 -5
  64. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +16 -2
  65. package/project/.sdk/tm/ts/test/definition-runner.ts +179 -0
  66. package/project/.sdk/tm/ts/test/pipeline.test.ts +43 -3
  67. package/project/.sdk/tm/ts/test/sdk-test-control.json +4 -1
  68. package/project/.sdk/tm/ts/test/utility.ts +3 -2
  69. package/project/.sdk/tm/zig/core/utility.zig +29 -1
  70. package/project/sdkgen-package.json +1 -1
  71. package/src/action/package.ts +10 -1
  72. package/src/cmp/ReadmeTop.ts +7 -6
  73. package/src/helpers/definitionPlan.ts +265 -0
  74. package/src/sdkgen.ts +2 -0
@@ -0,0 +1,161 @@
1
+ // Checks one operation against the API definition rather than the model the
2
+ // SDK was generated from. A mock transport answers with the definition's own
3
+ // response example, so nothing here depends on how the model reads it.
4
+
5
+ const assert = require('node:assert/strict')
6
+
7
+
8
+
9
+
10
+ const KEY = 'definition-test-key'
11
+ const BASE = 'http://definition.test'
12
+
13
+
14
+ async function runDefinitionPoint(SDK, point) {
15
+ const sent = []
16
+ const body = null == point.sample ? null : JSON.stringify(point.sample)
17
+
18
+ const client = new SDK({
19
+ apikey: KEY,
20
+ base: BASE,
21
+ feature: { test: { active: false } },
22
+ system: {
23
+ fetch: async (url, init) => {
24
+ sent.push({ url: new URL(String(url)), init: init || {} })
25
+ return new Response(204 === point.status ? null : body, {
26
+ status: point.status,
27
+ headers: { 'content-type': 'application/json' },
28
+ })
29
+ },
30
+ },
31
+ })
32
+
33
+ const input = { ...point.select }
34
+ for (const arg of point.args) input[arg.name] = arg.value
35
+ if (null != point.action) input.$action = point.action
36
+
37
+ let result
38
+ let error
39
+ try {
40
+ result = await client[point.accessor]()[point.op](input)
41
+ }
42
+ catch (err) {
43
+ error = err
44
+ }
45
+
46
+ assert.equal(sent.length, 1, 'expected one request, sent ' + sent.length +
47
+ (error ? ': ' + error.message : ''))
48
+ const { url, init } = sent[0]
49
+
50
+ assert.equal(String(init.method || 'GET').toUpperCase(), point.method, 'method')
51
+
52
+ const route = point.path.replace(/\{([^}]+)\}/g, (_, name) => {
53
+ const arg = point.args.find((a) => a.wire === name)
54
+ return null == arg ? '{' + name + '}' : encodeURIComponent(String(arg.value))
55
+ })
56
+ assert.equal(url.pathname, route, 'route')
57
+
58
+ // Only what the definition declares, so never a path parameter again.
59
+ const credentialQuery = (point.auth || []).flat()
60
+ .filter((c) => 'query' === c.in).map((c) => c.name)
61
+ for (const key of url.searchParams.keys()) {
62
+ assert(point.query.includes(key) || credentialQuery.includes(key),
63
+ 'query parameter not in the definition: ' + key)
64
+ }
65
+
66
+ // From the apikey alone, placed as the definition's security scheme says.
67
+ if (null != point.auth && 0 < point.auth.length) {
68
+ const headers = new Headers(init.headers)
69
+ assert(point.auth.some((set) => set.every((c) => placed(c, headers, url))),
70
+ 'credential not sent as the definition declares it: ' + JSON.stringify(point.auth))
71
+ }
72
+
73
+ if (null != error) {
74
+ throw error
75
+ }
76
+
77
+ if (null == point.sample) {
78
+ return
79
+ }
80
+
81
+ if ('list' === point.op) {
82
+ const records = recordsOf(point.sample)
83
+ if (null != records) {
84
+ assert(Array.isArray(result), 'list did not return a list')
85
+ assert.equal(result.length, records.length,
86
+ 'list read ' + result.length + ' records where the definition example holds ' +
87
+ records.length)
88
+ }
89
+ }
90
+ else if ('load' === point.op || 'create' === point.op || 'update' === point.op) {
91
+ const record = recordOf(point.sample, point.idField)
92
+ if (null != record) {
93
+ assert.equal(result?.data?.()?.[point.idField], record[point.idField],
94
+ 'the entity does not hold the record the definition example returns')
95
+ }
96
+ }
97
+ }
98
+
99
+
100
+ function placed(cred, headers, url) {
101
+ if ('query' === cred.in) {
102
+ return KEY === url.searchParams.get(cred.name)
103
+ }
104
+
105
+ if ('cookie' === cred.in) {
106
+ return String(headers.get('cookie') || '').split(';')
107
+ .some((part) => cred.name + '=' + KEY === part.trim())
108
+ }
109
+
110
+ const value = headers.get(cred.name)
111
+ if (null == value) {
112
+ return false
113
+ }
114
+
115
+ const [kind, token] = value.split(' ')
116
+
117
+ // The client carries no secret, so the password must be empty.
118
+ if ('basic' === cred.scheme) {
119
+ return 'basic' === String(kind).toLowerCase() &&
120
+ Buffer.from(String(token), 'base64').toString('utf8') === KEY + ':'
121
+ }
122
+
123
+ if ('bearer' === cred.scheme) {
124
+ return 'bearer' === String(kind).toLowerCase() && KEY === token
125
+ }
126
+
127
+ return value.includes(KEY)
128
+ }
129
+
130
+
131
+ // The records a list response holds: the body itself, or its one property
132
+ // that is a non-empty list of objects. Anything else proves nothing.
133
+ function recordsOf(sample) {
134
+ if (Array.isArray(sample)) {
135
+ return sample
136
+ }
137
+ if (null == sample || 'object' !== typeof sample) {
138
+ return null
139
+ }
140
+ const lists = Object.values(sample).filter((v) => Array.isArray(v) && 0 < v.length &&
141
+ v.every((x) => null != x && 'object' === typeof x && !Array.isArray(x)))
142
+ return 1 === lists.length ? lists[0] : null
143
+ }
144
+
145
+
146
+ // The record a single-item response returns: the body, or its one property
147
+ // holding an object with the identity field.
148
+ function recordOf(sample, idField) {
149
+ if (null == sample || 'object' !== typeof sample || Array.isArray(sample)) {
150
+ return null
151
+ }
152
+ if (null != sample[idField]) {
153
+ return sample
154
+ }
155
+ const inner = Object.values(sample).filter((v) => null != v && 'object' === typeof v &&
156
+ !Array.isArray(v) && null != v[idField])
157
+ return 1 === inner.length ? inner[0] : null
158
+ }
159
+
160
+
161
+ module.exports = { runDefinitionPoint, recordsOf, recordOf }
@@ -340,9 +340,9 @@ describe('pipeline:prepareAuth', () => {
340
340
  }
341
341
 
342
342
  // `basic: false` is explicit: an HTTP Basic API's generated config carries
343
- // `auth.basic: true`, and a client that merges it in takes a branch that
344
- // needs a secret as well. With none supplied that branch deliberately
345
- // writes nothing, which the probe below would read as a public API.
343
+ // `auth.basic: true`, and a client that merges it in takes the Basic
344
+ // branch, which base64-joins the key with a password rather than placing
345
+ // the key as it is.
346
346
  function auth(prefix) {
347
347
  return { prefix, basic: false }
348
348
  }
@@ -437,6 +437,46 @@ describe('pipeline:prepareAuth', () => {
437
437
  test('a missing apikey option drops the credential', () => {
438
438
  strictEqual(placed({ auth: auth('Bearer') }, 'stale'), undefined)
439
439
  })
440
+
441
+ // HTTP Basic joins the key with a password, which may be empty (RFC 7617):
442
+ // Lob documents its key as the user with a blank password. An SDK for an
443
+ // API that is not HTTP Basic has no such branch, and the first placement
444
+ // below tells the two apart.
445
+ test('HTTP Basic sends the key with a blank password', () => {
446
+ if (null == CRED || 'headers' !== CRED.where || '' !== CRED.pair) return
447
+ const basic = { prefix: 'Basic', basic: true }
448
+ const b64 = (s) => Buffer.from(s).toString('base64')
449
+ if (placed({ apikey: 'K', secret: 'S', auth: basic }) !== 'Basic ' + b64('K:S')) return
450
+ strictEqual(placed({ apikey: 'K', auth: basic }), 'Basic ' + b64('K:'))
451
+ strictEqual(placed({ apikey: 'K', secret: '', auth: basic }), 'Basic ' + b64('K:'))
452
+ strictEqual(placed({ apikey: '', secret: 'S', auth: basic }, 'stale'), undefined)
453
+ })
454
+ })
455
+
456
+
457
+ describe('pipeline:prepareQuery', () => {
458
+
459
+ // The generated config lists path parameters as args.params. A match field
460
+ // that fills the path must not be sent again as a query parameter, which a
461
+ // strict server rejects.
462
+ test('a path parameter stays out of the query', () => {
463
+ const point = { args: { params: [{ name: 'id', kind: 'param' }] } }
464
+ const ctx = base({ point, reqmatch: { id: 'i1', q: 'x' } })
465
+ deepStrictEqual(stdutil.prepareQuery(ctx), { q: 'x' })
466
+ })
467
+
468
+ test('an action is never a query parameter', () => {
469
+ const ctx = base({ point: { args: { params: [] } }, reqmatch: { $action: 'go', q: 'x' } })
470
+ deepStrictEqual(stdutil.prepareQuery(ctx), { q: 'x' })
471
+ })
472
+
473
+ // A query argument's orig is the name the API definition gives it, which
474
+ // the model may have renamed for the caller.
475
+ test('a query argument goes out under its orig', () => {
476
+ const point = { args: { query: [{ name: 'item_id', orig: 'itemIds', kind: 'query' }] } }
477
+ const ctx = base({ point, reqmatch: { item_id: ['i1'], q: 'x' } })
478
+ deepStrictEqual(stdutil.prepareQuery(ctx), { itemIds: ['i1'], q: 'x' })
479
+ })
440
480
  })
441
481
 
442
482
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "_doc": "Per-SDK test control. Lists tests/operations to skip in unit and live modes; tunes per-test pacing. Edit by hand. Loaded by js/test/utility.js.",
3
+ "_doc": "Per-SDK test control. Lists tests/operations to skip in unit, live and definition modes; tunes per-test pacing. Edit by hand. Loaded by js/test/utility.js.",
4
4
  "test": {
5
5
  "skip": {
6
6
  "live": {
@@ -10,6 +10,9 @@
10
10
  "unit": {
11
11
  "direct": [],
12
12
  "entityOp": []
13
+ },
14
+ "definition": {
15
+ "entityOp": []
13
16
  }
14
17
  },
15
18
  "live": {
@@ -179,15 +179,39 @@ fun prepareQuery(ctx: Context): MutableMap<String, Any?> {
179
179
  val point = ctx.point
180
180
  val reqmatch = ctx.reqmatch
181
181
 
182
- var params: MutableList<Any?>? = null
182
+ val params: MutableList<Any?> = mutableListOf()
183
183
  if (point != null) {
184
184
  val p = Struct.getprop(point, "params")
185
185
  if (p is MutableList<*>) {
186
- params = p as MutableList<Any?>
186
+ params.addAll(p as MutableList<Any?>)
187
+ }
188
+ // A path parameter travels in the path. The generated config lists them
189
+ // as args.params, which prepareParams reads; params is the older list.
190
+ val pl = Struct.getpath(point, listOf("args", "params"))
191
+ if (pl is List<*>) {
192
+ for (pd in pl) {
193
+ val name = Struct.getprop(pd, "name")
194
+ if (name is String) {
195
+ params.add(name)
196
+ }
197
+ }
187
198
  }
188
199
  }
189
- if (params == null) {
190
- params = mutableListOf()
200
+
201
+ // A query parameter travels under the name the definition gives it, its
202
+ // orig, which the model may have renamed for the caller.
203
+ val wire = mutableMapOf<String, String>()
204
+ if (point != null) {
205
+ val ql = Struct.getpath(point, listOf("args", "query"))
206
+ if (ql is List<*>) {
207
+ for (qd in ql) {
208
+ val name = Struct.getprop(qd, "name")
209
+ val orig = Struct.getprop(qd, "orig")
210
+ if (name is String && orig is String && orig.isNotEmpty()) {
211
+ wire[name] = orig
212
+ }
213
+ }
214
+ }
191
215
  }
192
216
 
193
217
  val out = linkedMapOf<String, Any?>()
@@ -195,7 +219,7 @@ fun prepareQuery(ctx: Context): MutableMap<String, Any?> {
195
219
  val key = if (item[0] is String) item[0] as String else ""
196
220
  val v = item[1]
197
221
  if (v != null && "\$action" != key && !containsStr(params, key)) {
198
- out[key] = v
222
+ out[wire[key] ?: key] = v
199
223
  }
200
224
  }
201
225
 
@@ -19,7 +19,36 @@ local function prepare_query_util(ctx)
19
19
  if point ~= nil then
20
20
  local p = vs.getprop(point, "params")
21
21
  if type(p) == "table" then
22
- params = p
22
+ for _, v in ipairs(p) do
23
+ table.insert(params, v)
24
+ end
25
+ end
26
+ -- A path parameter travels in the path. The generated config lists them
27
+ -- as args.params, which prepare_params reads; params is the older list.
28
+ local pl = vs.getpath(point, "args.params")
29
+ if type(pl) == "table" then
30
+ for _, pd in ipairs(pl) do
31
+ local name = vs.getprop(pd, "name")
32
+ if type(name) == "string" then
33
+ table.insert(params, name)
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ -- A query parameter travels under the name the definition gives it, its
40
+ -- orig, which the model may have renamed for the caller.
41
+ local wire = {}
42
+ if point ~= nil then
43
+ local ql = vs.getpath(point, "args.query")
44
+ if type(ql) == "table" then
45
+ for _, qd in ipairs(ql) do
46
+ local name = vs.getprop(qd, "name")
47
+ local orig = vs.getprop(qd, "orig")
48
+ if type(name) == "string" and type(orig) == "string" and orig ~= "" then
49
+ wire[name] = orig
50
+ end
51
+ end
23
52
  end
24
53
  end
25
54
 
@@ -31,7 +60,7 @@ local function prepare_query_util(ctx)
31
60
  local val = item[2]
32
61
  if val ~= nil and type(key) == "string" and key ~= "$action"
33
62
  and not contains_param(params, key) then
34
- out[key] = val
63
+ out[wire[key] or key] = val
35
64
  end
36
65
  end
37
66
  end
@@ -411,11 +411,32 @@ let prepare_query_util (ctx : ctx) : value =
411
411
  let params =
412
412
  match getp ctx.c_point "params" with List r -> !r | _ -> []
413
413
  in
414
+ (* A path parameter travels in the path. The generated config lists them as
415
+ * args.params, which prepare_params reads; params is the older list. *)
416
+ let arg_names =
417
+ match getp (getp ctx.c_point "args") "params" with
418
+ | List r -> List.map (fun pd -> getp pd "name") !r
419
+ | _ -> []
420
+ in
421
+ let params = params @ arg_names in
414
422
  let contains_param s = List.exists (fun v -> match v with Str x -> x = s | _ -> false) params in
423
+ (* A query parameter travels under the name the definition gives it, its
424
+ * orig, which the model may have renamed for the caller. *)
425
+ let wire =
426
+ match getp (getp ctx.c_point "args") "query" with
427
+ | List r ->
428
+ List.filter_map (fun qd ->
429
+ match getp qd "name", getp qd "orig" with
430
+ | Str n, Str o when o <> "" -> Some (n, o)
431
+ | _ -> None) !r
432
+ | _ -> []
433
+ in
434
+ let wire_name k = match List.assoc_opt k wire with Some o -> o | None -> k in
415
435
  let out = empty_map () in
416
436
  List.iter (fun k ->
417
437
  let v = getp reqmatch k in
418
- if not (is_noval v) && k <> "$action" && not (contains_param k) then setp out k v)
438
+ if not (is_noval v) && k <> "$action" && not (contains_param k) then
439
+ setp out (wire_name k) v)
419
440
  (keysof reqmatch);
420
441
  out
421
442
 
@@ -22,7 +22,30 @@ $REGISTRY{prepare_query} = sub {
22
22
  my $params = [];
23
23
  if ($point) {
24
24
  my $p = ProjectNameHelpers::gp($point, 'params');
25
- $params = $p if Voxgig::Struct::islist($p);
25
+ $params = [@$p] if Voxgig::Struct::islist($p);
26
+ # A path parameter travels in the path. The generated config lists them
27
+ # as args.params, which prepare_params reads; params is the older list.
28
+ my $pl = ProjectNameHelpers::gpath($point, 'args.params');
29
+ if (Voxgig::Struct::islist($pl)) {
30
+ for my $pd (@$pl) {
31
+ my $name = ProjectNameHelpers::gp($pd, 'name');
32
+ push @$params, $name if defined $name && !ref $name;
33
+ }
34
+ }
35
+ }
36
+ # A query parameter travels under the name the definition gives it, its
37
+ # orig, which the model may have renamed for the caller.
38
+ my %wire;
39
+ if ($point) {
40
+ my $ql = ProjectNameHelpers::gpath($point, 'args.query');
41
+ if (Voxgig::Struct::islist($ql)) {
42
+ for my $qd (@$ql) {
43
+ my $name = ProjectNameHelpers::gp($qd, 'name');
44
+ my $orig = ProjectNameHelpers::gp($qd, 'orig');
45
+ $wire{$name} = $orig
46
+ if defined $name && !ref $name && defined $orig && !ref $orig && '' ne $orig;
47
+ }
48
+ }
26
49
  }
27
50
  my $out = {};
28
51
  my $items = Voxgig::Struct::items($reqmatch);
@@ -32,7 +55,7 @@ $REGISTRY{prepare_query} = sub {
32
55
  next unless ProjectNameHelpers::rb_truthy($val) && defined $key && !ref $key;
33
56
  next if '$action' eq $key;
34
57
  next if grep { defined $_ && !ref $_ && $_ eq $key } @$params;
35
- $out->{$key} = $val;
58
+ $out->{exists $wire{$key} ? $wire{$key} : $key} = $val;
36
59
  }
37
60
  }
38
61
  return $out;
@@ -15,6 +15,33 @@ class ProjectNamePrepareQuery
15
15
  if (is_array($p)) {
16
16
  $params = $p;
17
17
  }
18
+ // A path parameter travels in the path. The generated config lists
19
+ // them as args.params, which prepareParams reads; params is the
20
+ // older list of names.
21
+ $pl = \Voxgig\Struct\Struct::getpath($point, 'args.params');
22
+ if (is_array($pl)) {
23
+ foreach ($pl as $pd) {
24
+ $name = \Voxgig\Struct\Struct::getprop($pd, 'name');
25
+ if (is_string($name)) {
26
+ $params[] = $name;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ // A query parameter travels under the name the definition gives it,
32
+ // its orig, which the model may have renamed for the caller.
33
+ $wire = [];
34
+ if ($point) {
35
+ $ql = \Voxgig\Struct\Struct::getpath($point, 'args.query');
36
+ if (is_array($ql)) {
37
+ foreach ($ql as $qd) {
38
+ $name = \Voxgig\Struct\Struct::getprop($qd, 'name');
39
+ $orig = \Voxgig\Struct\Struct::getprop($qd, 'orig');
40
+ if (is_string($name) && is_string($orig) && '' !== $orig) {
41
+ $wire[$name] = $orig;
42
+ }
43
+ }
44
+ }
18
45
  }
19
46
  $out = [];
20
47
  $items = \Voxgig\Struct\Struct::items($reqmatch);
@@ -23,7 +50,7 @@ class ProjectNamePrepareQuery
23
50
  $key = $item[0];
24
51
  $val = $item[1];
25
52
  if ($val !== null && is_string($key) && '$action' !== $key && !in_array($key, $params, true)) {
26
- $out[$key] = $val;
53
+ $out[$wire[$key] ?? $key] = $val;
27
54
  }
28
55
  }
29
56
  }
@@ -19,7 +19,28 @@ def prepare_query_util(ctx):
19
19
  if point is not None:
20
20
  p = vs.getprop(point, "params")
21
21
  if isinstance(p, list):
22
- params = p
22
+ params = list(p)
23
+ # A path parameter travels in the path. The generated config lists
24
+ # them as args.params, which prepare_params reads; params is the
25
+ # older list of names.
26
+ pl = vs.getpath(point, "args.params")
27
+ if isinstance(pl, list):
28
+ for pd in pl:
29
+ name = vs.getprop(pd, "name")
30
+ if isinstance(name, str):
31
+ params.append(name)
32
+
33
+ # A query parameter travels under the name the definition gives it, its
34
+ # orig, which the model may have renamed for the caller.
35
+ wire = {}
36
+ if point is not None:
37
+ ql = vs.getpath(point, "args.query")
38
+ if isinstance(ql, list):
39
+ for qd in ql:
40
+ name = vs.getprop(qd, "name")
41
+ orig = vs.getprop(qd, "orig")
42
+ if isinstance(name, str) and isinstance(orig, str) and orig != "":
43
+ wire[name] = orig
23
44
 
24
45
  out = {}
25
46
  reqmatch_items = vs.items(reqmatch)
@@ -29,6 +50,6 @@ def prepare_query_util(ctx):
29
50
  val = item[1]
30
51
  if val is not None and isinstance(key, str) and key != "$action" \
31
52
  and not _contains_param(params, key):
32
- out[key] = val
53
+ out[wire.get(key, key)] = val
33
54
 
34
55
  return out
@@ -7,14 +7,36 @@ module ProjectNameUtilities
7
7
  params = []
8
8
  if point
9
9
  p = VoxgigStruct.getprop(point, "params")
10
- params = p if p.is_a?(Array)
10
+ params = p.dup if p.is_a?(Array)
11
+ # A path parameter travels in the path. The generated config lists them
12
+ # as args.params, which prepare_params reads; params is the older list.
13
+ pl = VoxgigStruct.getpath(point, "args.params")
14
+ if pl.is_a?(Array)
15
+ pl.each do |pd|
16
+ name = VoxgigStruct.getprop(pd, "name")
17
+ params << name if name.is_a?(String)
18
+ end
19
+ end
20
+ end
21
+ # A query parameter travels under the name the definition gives it, its
22
+ # orig, which the model may have renamed for the caller.
23
+ wire = {}
24
+ if point
25
+ ql = VoxgigStruct.getpath(point, "args.query")
26
+ if ql.is_a?(Array)
27
+ ql.each do |qd|
28
+ name = VoxgigStruct.getprop(qd, "name")
29
+ orig = VoxgigStruct.getprop(qd, "orig")
30
+ wire[name] = orig if name.is_a?(String) && orig.is_a?(String) && !orig.empty?
31
+ end
32
+ end
11
33
  end
12
34
  out = {}
13
35
  items = VoxgigStruct.items(reqmatch)
14
36
  if items
15
37
  items.each do |item|
16
38
  key, val = item[0], item[1]
17
- out[key] = val if val && key.is_a?(String) && key != "$action" && !params.include?(key)
39
+ out[wire.fetch(key, key)] = val if val && key.is_a?(String) && key != "$action" && !params.include?(key)
18
40
  end
19
41
  end
20
42
  out
@@ -29,6 +29,38 @@ pub fn prepare_query_util(ctx: &Rc<Context>) -> Value {
29
29
  false
30
30
  };
31
31
 
32
+ // A path parameter travels in the path. The generated config lists them as
33
+ // args.params, which prepare_params reads; params is the older list of names.
34
+ let aparams = getp(&getp(&point, "args"), "params");
35
+ let in_args = |key: &str| -> bool {
36
+ if let Value::List(pl) = &aparams {
37
+ for pd in pl.borrow().iter() {
38
+ if let Value::Str(s) = getp(pd, "name") {
39
+ if s == key {
40
+ return true;
41
+ }
42
+ }
43
+ }
44
+ }
45
+ false
46
+ };
47
+
48
+ // A query parameter travels under the name the definition gives it, its
49
+ // orig, which the model may have renamed for the caller.
50
+ let aquery = getp(&getp(&point, "args"), "query");
51
+ let wire_name = |key: &str| -> String {
52
+ if let Value::List(ql) = &aquery {
53
+ for qd in ql.borrow().iter() {
54
+ if let (Value::Str(n), Value::Str(o)) = (getp(qd, "name"), getp(qd, "orig")) {
55
+ if n == key && !o.is_empty() {
56
+ return o;
57
+ }
58
+ }
59
+ }
60
+ }
61
+ key.to_string()
62
+ };
63
+
32
64
  let out = Value::empty_map();
33
65
  if let Value::Map(rm) = &reqmatch {
34
66
  let entries: Vec<(String, Value)> = rm
@@ -37,8 +69,8 @@ pub fn prepare_query_util(ctx: &Rc<Context>) -> Value {
37
69
  .map(|(k, v)| (k.clone(), v.clone()))
38
70
  .collect();
39
71
  for (key, val) in entries {
40
- if !val.is_noval() && !val.is_null() && "$action" != key && !contains(&key) {
41
- setp(&out, &key, val);
72
+ if !val.is_noval() && !val.is_null() && "$action" != key && !contains(&key) && !in_args(&key) {
73
+ setp(&out, &wire_name(&key), val);
42
74
  }
43
75
  }
44
76
  }
@@ -67,11 +67,38 @@ object PrepareQuery {
67
67
  var reqmatch = ctx.reqmatch
68
68
  if (reqmatch == null) reqmatch = new LinkedHashMap[String, Object]()
69
69
 
70
- var params: JList[Object] = null
70
+ val params: JList[Object] = new ArrayList[Object]()
71
71
  if (point != null) {
72
- Struct.getprop(point, "params") match { case l: JList[_] => params = l.asInstanceOf[JList[Object]]; case _ => }
72
+ Struct.getprop(point, "params") match { case l: JList[_] => params.addAll(l.asInstanceOf[JList[Object]]); case _ => }
73
+ // A path parameter travels in the path. The generated config lists them
74
+ // as args.params, which prepareParams reads; params is the older list.
75
+ Struct.getpath(point, java.util.List.of("args", "params")) match {
76
+ case l: JList[_] =>
77
+ val pit = l.iterator()
78
+ while (pit.hasNext) {
79
+ Struct.getprop(pit.next(), "name") match { case s: String => params.add(s); case _ => }
80
+ }
81
+ case _ =>
82
+ }
83
+ }
84
+
85
+ // A query parameter travels under the name the definition gives it, its
86
+ // orig, which the model may have renamed for the caller.
87
+ val wire = new LinkedHashMap[String, String]()
88
+ if (point != null) {
89
+ Struct.getpath(point, java.util.List.of("args", "query")) match {
90
+ case l: JList[_] =>
91
+ val qit = l.iterator()
92
+ while (qit.hasNext) {
93
+ val qd = qit.next()
94
+ (Struct.getprop(qd, "name"), Struct.getprop(qd, "orig")) match {
95
+ case (n: String, o: String) if o.nonEmpty => wire.put(n, o)
96
+ case _ =>
97
+ }
98
+ }
99
+ case _ =>
100
+ }
73
101
  }
74
- if (params == null) params = new ArrayList[Object]()
75
102
 
76
103
  val out = new LinkedHashMap[String, Object]()
77
104
  val it = Struct.items(reqmatch).iterator()
@@ -79,7 +106,7 @@ object PrepareQuery {
79
106
  val item = it.next()
80
107
  val key = item.get(0) match { case s: String => s; case _ => "" }
81
108
  val v = item.get(1)
82
- if (v != null && "$action" != key && !containsStr(params, key)) out.put(key, v)
109
+ if (v != null && "$action" != key && !containsStr(params, key)) out.put(wire.getOrDefault(key, key), v)
83
110
  }
84
111
  out
85
112
  }