@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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/action/package.d.ts +2 -1
- package/dist/action/package.js +8 -1
- package/dist/action/package.js.map +1 -1
- package/dist/cmp/ReadmeTop.js +6 -5
- package/dist/cmp/ReadmeTop.js.map +1 -1
- package/dist/helpers/definitionPlan.d.ts +27 -0
- package/dist/helpers/definitionPlan.js +219 -0
- package/dist/helpers/definitionPlan.js.map +1 -0
- package/dist/sdkgen.d.ts +2 -1
- package/dist/sdkgen.js +5 -3
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +5 -3
- package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +7 -5
- package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +5 -3
- package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +6 -4
- package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +8 -6
- package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +7 -9
- package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +5 -3
- package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +6 -4
- package/project/.sdk/src/cmp/js/ReadmeInstall_js.ts +22 -5
- package/project/.sdk/src/cmp/js/TestDefinition_js.ts +40 -0
- package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
- package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +5 -3
- package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +7 -3
- package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +7 -5
- package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +6 -3
- package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +5 -3
- package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +11 -5
- package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +5 -3
- package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +5 -3
- package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +14 -4
- package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +5 -3
- package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +5 -3
- package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +5 -3
- package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +6 -4
- package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +10 -4
- package/project/.sdk/src/cmp/ts/TestDefinition_ts.ts +40 -0
- package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
- package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +5 -3
- package/project/.sdk/tm/c/utility/prepare_query.c +34 -2
- package/project/.sdk/tm/clojure/src/sdk/core.clj +20 -2
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +31 -2
- package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +32 -4
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +35 -1
- package/project/.sdk/tm/go/utility/prepare_query.go +31 -1
- package/project/.sdk/tm/java/utility/PrepareQuery.java +29 -5
- package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +16 -2
- package/project/.sdk/tm/js/test/definition-runner.js +161 -0
- package/project/.sdk/tm/js/test/pipeline.test.js +43 -3
- package/project/.sdk/tm/js/test/sdk-test-control.json +4 -1
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +29 -5
- package/project/.sdk/tm/lua/utility/prepare_query.lua +31 -2
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +22 -1
- package/project/.sdk/tm/perl/utility/prepare_query.pm +25 -2
- package/project/.sdk/tm/php/utility/PrepareQuery.php +28 -1
- package/project/.sdk/tm/py/pkg/utility/prepare_query.py +23 -2
- package/project/.sdk/tm/rb/utility/prepare_query.rb +24 -2
- package/project/.sdk/tm/rust/utility/prepare_query.rs +34 -2
- package/project/.sdk/tm/scala/utility/Prepare.scala +31 -4
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +23 -5
- package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +16 -2
- package/project/.sdk/tm/ts/test/definition-runner.ts +179 -0
- package/project/.sdk/tm/ts/test/pipeline.test.ts +43 -3
- package/project/.sdk/tm/ts/test/sdk-test-control.json +4 -1
- package/project/.sdk/tm/ts/test/utility.ts +3 -2
- package/project/.sdk/tm/zig/core/utility.zig +29 -1
- package/project/sdkgen-package.json +1 -1
- package/src/action/package.ts +10 -1
- package/src/cmp/ReadmeTop.ts +7 -6
- package/src/helpers/definitionPlan.ts +265 -0
- 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
|
|
344
|
-
//
|
|
345
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
70
|
+
val params: JList[Object] = new ArrayList[Object]()
|
|
71
71
|
if (point != null) {
|
|
72
|
-
Struct.getprop(point, "params") match { case l: JList[_] => params
|
|
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
|
}
|