@voxgig/sdkgen 4.31.1 → 4.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/helpers/definitionPlan.d.ts +5 -0
- package/dist/helpers/definitionPlan.js +44 -7
- package/dist/helpers/definitionPlan.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/project/.sdk/tm/c/utility/make_url.c +11 -0
- package/project/.sdk/tm/c/utility/prepare_headers.c +53 -3
- package/project/.sdk/tm/c/utility/prepare_query.c +4 -1
- package/project/.sdk/tm/c/utility/transform_request.c +31 -8
- package/project/.sdk/tm/clojure/src/sdk/core.clj +62 -15
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +72 -15
- package/project/.sdk/tm/csharp/utility/MakeUrl.cs +8 -0
- package/project/.sdk/tm/csharp/utility/PrepareHeaders.cs +34 -4
- package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +14 -0
- package/project/.sdk/tm/csharp/utility/TransformRequest.cs +31 -5
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +86 -24
- package/project/.sdk/tm/go/utility/make_url.go +8 -0
- package/project/.sdk/tm/go/utility/prepare_headers.go +36 -7
- package/project/.sdk/tm/go/utility/prepare_query.go +11 -0
- package/project/.sdk/tm/go/utility/transform_request.go +31 -5
- package/project/.sdk/tm/java/utility/MakeUrl.java +8 -0
- package/project/.sdk/tm/java/utility/PrepareHeaders.java +30 -6
- package/project/.sdk/tm/java/utility/PrepareQuery.java +10 -0
- package/project/.sdk/tm/java/utility/TransformRequest.java +30 -5
- package/project/.sdk/tm/js/src/utility/MakeUrlUtility.js +7 -0
- package/project/.sdk/tm/js/src/utility/PrepareHeadersUtility.js +18 -0
- package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +3 -1
- package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +17 -3
- package/project/.sdk/tm/js/test/definition-runner.js +74 -13
- package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +8 -0
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +34 -4
- package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -6
- package/project/.sdk/tm/lua/utility/make_url.lua +8 -0
- package/project/.sdk/tm/lua/utility/prepare_headers.lua +35 -6
- package/project/.sdk/tm/lua/utility/prepare_query.lua +10 -0
- package/project/.sdk/tm/lua/utility/transform_request.lua +37 -6
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +63 -13
- package/project/.sdk/tm/perl/utility/make_url.pm +6 -0
- package/project/.sdk/tm/perl/utility/prepare_headers.pm +21 -3
- package/project/.sdk/tm/perl/utility/prepare_query.pm +8 -0
- package/project/.sdk/tm/perl/utility/transform_request.pm +19 -7
- package/project/.sdk/tm/php/utility/MakeUrl.php +7 -0
- package/project/.sdk/tm/php/utility/PrepareHeaders.php +33 -4
- package/project/.sdk/tm/php/utility/PrepareQuery.php +11 -0
- package/project/.sdk/tm/php/utility/TransformRequest.php +28 -5
- package/project/.sdk/tm/py/pkg/utility/make_url.py +7 -0
- package/project/.sdk/tm/py/pkg/utility/prepare_headers.py +27 -6
- package/project/.sdk/tm/py/pkg/utility/prepare_query.py +8 -0
- package/project/.sdk/tm/py/pkg/utility/transform_request.py +21 -5
- package/project/.sdk/tm/rb/utility/make_url.rb +7 -0
- package/project/.sdk/tm/rb/utility/prepare_headers.rb +21 -3
- package/project/.sdk/tm/rb/utility/prepare_query.rb +8 -0
- package/project/.sdk/tm/rb/utility/transform_request.rb +18 -5
- package/project/.sdk/tm/rust/utility/make_url.rs +10 -1
- package/project/.sdk/tm/rust/utility/prepare_headers.rs +43 -7
- package/project/.sdk/tm/rust/utility/prepare_query.rs +18 -1
- package/project/.sdk/tm/rust/utility/transform_request.rs +29 -7
- package/project/.sdk/tm/scala/utility/Make.scala +9 -0
- package/project/.sdk/tm/scala/utility/Prepare.scala +42 -3
- package/project/.sdk/tm/scala/utility/Transform.scala +30 -6
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +7 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +28 -2
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +23 -5
- package/project/.sdk/tm/ts/src/utility/MakeUrlUtility.ts +7 -0
- package/project/.sdk/tm/ts/src/utility/PrepareHeadersUtility.ts +16 -0
- package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +3 -1
- package/project/.sdk/tm/ts/src/utility/TransformRequestUtility.ts +16 -3
- package/project/.sdk/tm/ts/test/definition-runner.ts +75 -13
- package/project/.sdk/tm/zig/core/utility.zig +80 -20
- package/project/sdkgen-package.json +1 -1
- package/src/helpers/definitionPlan.ts +49 -7
|
@@ -32,6 +32,7 @@ async function runDefinitionPoint(SDK, point) {
|
|
|
32
32
|
|
|
33
33
|
const input = { ...point.select }
|
|
34
34
|
for (const arg of point.args) input[arg.name] = arg.value
|
|
35
|
+
for (const h of point.headers || []) input[h.name] = h.value
|
|
35
36
|
if (null != point.action) input.$action = point.action
|
|
36
37
|
|
|
37
38
|
let result
|
|
@@ -53,7 +54,9 @@ async function runDefinitionPoint(SDK, point) {
|
|
|
53
54
|
const arg = point.args.find((a) => a.wire === name)
|
|
54
55
|
return null == arg ? '{' + name + '}' : encodeURIComponent(String(arg.value))
|
|
55
56
|
})
|
|
56
|
-
|
|
57
|
+
// Read as the request was, so a backslash in the definition's path, as in
|
|
58
|
+
// GitLab's `Packages\(\)`, is the slash the URL parser makes it.
|
|
59
|
+
assert.equal(url.pathname, new URL(BASE + route).pathname, 'route')
|
|
57
60
|
|
|
58
61
|
// Only what the definition declares, so never a path parameter again.
|
|
59
62
|
const credentialQuery = (point.auth || []).flat()
|
|
@@ -70,6 +73,18 @@ async function runDefinitionPoint(SDK, point) {
|
|
|
70
73
|
'credential not sent as the definition declares it: ' + JSON.stringify(point.auth))
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
// A header parameter goes out as a header. The credential check above owns
|
|
77
|
+
// any header the security scheme names, and the SDK sets the content type
|
|
78
|
+
// from the body it sends.
|
|
79
|
+
const credentialHeaders = (point.auth || []).flat()
|
|
80
|
+
.filter((c) => 'header' === c.in).map((c) => c.name.toLowerCase())
|
|
81
|
+
for (const h of point.headers || []) {
|
|
82
|
+
const wire = h.wire.toLowerCase()
|
|
83
|
+
if (credentialHeaders.includes(wire) || 'content-type' === wire) continue
|
|
84
|
+
assert.equal(new Headers(init.headers).get(wire), String(h.value),
|
|
85
|
+
'header parameter not sent as a header: ' + h.wire)
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
if (null != error) {
|
|
74
89
|
throw error
|
|
75
90
|
}
|
|
@@ -88,7 +103,7 @@ async function runDefinitionPoint(SDK, point) {
|
|
|
88
103
|
}
|
|
89
104
|
}
|
|
90
105
|
else if ('load' === point.op || 'create' === point.op || 'update' === point.op) {
|
|
91
|
-
const record = recordOf(point.sample, point.idField)
|
|
106
|
+
const record = recordOf(point.sample, point.idField, point.entity)
|
|
92
107
|
if (null != record) {
|
|
93
108
|
assert.equal(result?.data?.()?.[point.idField], record[point.idField],
|
|
94
109
|
'the entity does not hold the record the definition example returns')
|
|
@@ -128,33 +143,79 @@ function placed(cred, headers, url) {
|
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
|
|
131
|
-
//
|
|
132
|
-
//
|
|
146
|
+
// What an envelope may hold beside what it carries, compared without case,
|
|
147
|
+
// `_` or `-`: status, paging and the page's own metadata, each by its whole
|
|
148
|
+
// name, so a record's homepage or preview is data of its own.
|
|
149
|
+
const ENVELOPE_KEYS = new Set([
|
|
150
|
+
'success', 'status', 'ok', 'message', 'code', 'error', 'errorcode', 'errormessage',
|
|
151
|
+
'requestid', 'timestamp', 'took', 'version', 'apiversion', 'object', 'url',
|
|
152
|
+
'count', 'total', 'totalcount', 'totalhits', 'totalitems', 'totalpages', 'totalresults',
|
|
153
|
+
'totalrecords', 'totalrowcount', 'totalentries', 'itemcount', 'resultcount', 'rowcount',
|
|
154
|
+
'page', 'pages', 'pagecount', 'pagenumber', 'pageindex', 'pagesize', 'perpage',
|
|
155
|
+
'currentpage', 'lastpage', 'limit', 'offset', 'cursor', 'nextcursor', 'prevcursor',
|
|
156
|
+
'previouscursor', 'next', 'nextpage', 'nexturl', 'nextlink', 'nextpagetoken', 'nexttoken',
|
|
157
|
+
'pagetoken', 'continuationtoken', 'prev', 'previous', 'prevpage', 'previouspage',
|
|
158
|
+
'prevurl', 'previousurl', 'prevlink', 'hasmore', 'hasnext', 'hasnextpage', 'hasprevious',
|
|
159
|
+
'haspreviouspage', 'more', 'meta', 'metadata', 'pagination', 'paging', 'pageinfo', 'links',
|
|
160
|
+
])
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
function envelopeKey(key) {
|
|
164
|
+
return ENVELOPE_KEYS.has(squash(key))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
function ownData(sample) {
|
|
169
|
+
return Object.entries(sample).some(([key, value]) =>
|
|
170
|
+
null != value && 'object' !== typeof value && !envelopeKey(key))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
function isRecord(value) {
|
|
175
|
+
return null != value && 'object' === typeof value && !Array.isArray(value)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// The records a list response holds: the body itself, or the one non-empty
|
|
180
|
+
// list of objects in a page. Anything else, such as a record that happens to
|
|
181
|
+
// hold a list, proves nothing.
|
|
133
182
|
function recordsOf(sample) {
|
|
134
183
|
if (Array.isArray(sample)) {
|
|
135
184
|
return sample
|
|
136
185
|
}
|
|
137
|
-
if (
|
|
186
|
+
if (!isRecord(sample) || ownData(sample)) {
|
|
138
187
|
return null
|
|
139
188
|
}
|
|
140
189
|
const lists = Object.values(sample).filter((v) => Array.isArray(v) && 0 < v.length &&
|
|
141
|
-
v.every(
|
|
190
|
+
v.every(isRecord))
|
|
142
191
|
return 1 === lists.length ? lists[0] : null
|
|
143
192
|
}
|
|
144
193
|
|
|
145
194
|
|
|
146
|
-
// The record a single-item response returns: the body, or
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
195
|
+
// The record a single-item response returns: the body, or the one object
|
|
196
|
+
// with the identity field that an envelope carries, under the entity's name
|
|
197
|
+
// or beside envelope keys alone. Otherwise the body is the record itself,
|
|
198
|
+
// such as GitHub's check suite preferences beside their repository.
|
|
199
|
+
function recordOf(sample, idField, entity) {
|
|
200
|
+
if (!isRecord(sample)) {
|
|
150
201
|
return null
|
|
151
202
|
}
|
|
152
203
|
if (null != sample[idField]) {
|
|
153
204
|
return sample
|
|
154
205
|
}
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
206
|
+
const keys = Object.keys(sample)
|
|
207
|
+
const inner = keys.filter((key) => isRecord(sample[key]) && null != sample[key][idField])
|
|
208
|
+
if (1 !== inner.length) {
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
const named = null != entity && squash(inner[0]) === squash(entity) && !ownData(sample)
|
|
212
|
+
const alone = keys.every((key) => key === inner[0] || envelopeKey(key))
|
|
213
|
+
return named || alone ? sample[inner[0]] : null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
function squash(name) {
|
|
218
|
+
return name.toLowerCase().replace(/[_-]/g, '')
|
|
158
219
|
}
|
|
159
220
|
|
|
160
221
|
|
|
@@ -104,6 +104,14 @@ fun makeUrl(ctx: Context): String {
|
|
|
104
104
|
joinParts.add(spec.suffix)
|
|
105
105
|
var url = Struct.join(joinParts, "/", true)
|
|
106
106
|
|
|
107
|
+
// A route the definition ends with a slash keeps it: a server such as a
|
|
108
|
+
// Django REST one redirects or refuses the route without it.
|
|
109
|
+
val point = ctx.point
|
|
110
|
+
val orig = if (point == null) null else Struct.getprop(point, "orig", null)
|
|
111
|
+
if (orig is String && orig.endsWith("/") && spec.suffix.isEmpty() && !url.endsWith("/")) {
|
|
112
|
+
url += "/"
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
val resmatch = linkedMapOf<String, Any?>()
|
|
108
116
|
|
|
109
117
|
val params = spec.params
|
|
@@ -100,12 +100,32 @@ fun prepareHeaders(ctx: Context): MutableMap<String, Any?> {
|
|
|
100
100
|
val options = ctx.client!!.optionsMap()
|
|
101
101
|
|
|
102
102
|
val headers = Struct.getprop(options, "headers", null)
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
val out: MutableMap<String, Any?> =
|
|
104
|
+
(if (headers == null) null else Helpers.toMapAny(Struct.clone(headers))) ?: linkedMapOf()
|
|
105
|
+
|
|
106
|
+
// A header parameter travels as a header, under the name the definition
|
|
107
|
+
// gives it, and only from this call's own arguments. It replaces a default
|
|
108
|
+
// of the same name, whatever its case.
|
|
109
|
+
val point = ctx.point
|
|
110
|
+
val hl = if (point == null) null else Struct.getpath(point, listOf("args", "header"))
|
|
111
|
+
if (hl is List<*>) {
|
|
112
|
+
for (hd in hl) {
|
|
113
|
+
val name = Struct.getprop(hd, "name")
|
|
114
|
+
if (name !is String || name.isEmpty()) {
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
val orig = Struct.getprop(hd, "orig")
|
|
118
|
+
val wire = if (orig is String && orig.isNotEmpty()) orig else name
|
|
119
|
+
val v = Struct.getprop(ctx.reqmatch, name, null) ?: Struct.getprop(ctx.reqdata, name, null)
|
|
120
|
+
if (v != null) {
|
|
121
|
+
val key = wire.lowercase()
|
|
122
|
+
out.keys.removeAll { it.lowercase() == key }
|
|
123
|
+
out[key] = Struct.stringify(v)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
105
126
|
}
|
|
106
127
|
|
|
107
|
-
|
|
108
|
-
return out ?: linkedMapOf()
|
|
128
|
+
return out
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
fun prepareMethod(ctx: Context): String? {
|
|
@@ -196,6 +216,16 @@ fun prepareQuery(ctx: Context): MutableMap<String, Any?> {
|
|
|
196
216
|
}
|
|
197
217
|
}
|
|
198
218
|
}
|
|
219
|
+
// A header parameter travels in the headers, which prepareHeaders fills.
|
|
220
|
+
val hl = Struct.getpath(point, listOf("args", "header"))
|
|
221
|
+
if (hl is List<*>) {
|
|
222
|
+
for (hd in hl) {
|
|
223
|
+
val name = Struct.getprop(hd, "name")
|
|
224
|
+
if (name is String) {
|
|
225
|
+
params.add(name)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
199
229
|
}
|
|
200
230
|
|
|
201
231
|
// A query parameter travels under the name the definition gives it, its
|
|
@@ -60,13 +60,23 @@ fun resultHeaders(ctx: Context): KOTLINPACKAGE.core.Result {
|
|
|
60
60
|
|
|
61
61
|
// `$action` selects the point (see makePoint); it is never an API field, so
|
|
62
62
|
// the body is a copy without it. The caller's map is left untouched.
|
|
63
|
-
private fun stripAction(reqdata: Any?): Any?
|
|
64
|
-
|
|
63
|
+
private fun stripAction(reqdata: Any?): Any? = omitKeys(reqdata, listOf("\$action"))
|
|
64
|
+
|
|
65
|
+
// A header argument travels as a header, which prepareHeaders sends, so the
|
|
66
|
+
// body is built from the request data without it.
|
|
67
|
+
private fun headerArgNames(ctx: Context): List<String> {
|
|
68
|
+
val point = ctx.point ?: return emptyList()
|
|
69
|
+
val hl = Struct.getpath(point, listOf("args", "header")) as? List<*> ?: return emptyList()
|
|
70
|
+
return hl.mapNotNull { hd -> (Struct.getprop(hd, "name", null) as? String)?.takeIf { it.isNotEmpty() } }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private fun omitKeys(reqdata: Any?, names: List<String>): Any? {
|
|
74
|
+
if (reqdata !is Map<*, *> || names.none { reqdata.containsKey(it) }) {
|
|
65
75
|
return reqdata
|
|
66
76
|
}
|
|
67
77
|
val body = linkedMapOf<String, Any?>()
|
|
68
78
|
for ((k, v) in reqdata) {
|
|
69
|
-
if (
|
|
79
|
+
if (k.toString() !in names) {
|
|
70
80
|
body[k.toString()] = v
|
|
71
81
|
}
|
|
72
82
|
}
|
|
@@ -78,14 +88,16 @@ fun transformRequest(ctx: Context): Any? {
|
|
|
78
88
|
ctx.spec!!.step = "reqform"
|
|
79
89
|
}
|
|
80
90
|
|
|
91
|
+
val reqdata = omitKeys(ctx.reqdata, headerArgNames(ctx))
|
|
92
|
+
|
|
81
93
|
val transform = Helpers.toMapAny(Struct.getprop(ctx.point, "transform"))
|
|
82
|
-
?: return stripAction(
|
|
94
|
+
?: return stripAction(reqdata)
|
|
83
95
|
|
|
84
96
|
val reqform = Struct.getprop(transform, "req", null)
|
|
85
|
-
?: return stripAction(
|
|
97
|
+
?: return stripAction(reqdata)
|
|
86
98
|
|
|
87
99
|
val data = linkedMapOf<String, Any?>()
|
|
88
|
-
data["reqdata"] =
|
|
100
|
+
data["reqdata"] = reqdata
|
|
89
101
|
|
|
90
102
|
return stripAction(Struct.transform(data, reqform))
|
|
91
103
|
}
|
|
@@ -18,6 +18,14 @@ local function make_url_util(ctx)
|
|
|
18
18
|
local url = vs.join({ spec.base, spec.prefix, spec.path, spec.suffix }, "/", true)
|
|
19
19
|
local 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
|
+
local orig = ctx.point ~= nil and vs.getprop(ctx.point, "orig") or nil
|
|
24
|
+
if type(orig) == "string" and orig:sub(-1) == "/" and (spec.suffix == nil or spec.suffix == "")
|
|
25
|
+
and url:sub(-1) ~= "/" then
|
|
26
|
+
url = url .. "/"
|
|
27
|
+
end
|
|
28
|
+
|
|
21
29
|
local param_items = vs.items(spec.params)
|
|
22
30
|
if param_items ~= nil then
|
|
23
31
|
for _, item in ipairs(param_items) do
|
|
@@ -6,15 +6,44 @@ local function prepare_headers_util(ctx)
|
|
|
6
6
|
local options = ctx.client:options_map()
|
|
7
7
|
local headers = vs.getprop(options, "headers")
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
local out = {}
|
|
10
|
+
if headers ~= nil then
|
|
11
|
+
local cloned = vs.clone(headers)
|
|
12
|
+
if type(cloned) == "table" then
|
|
13
|
+
out = cloned
|
|
14
|
+
end
|
|
11
15
|
end
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+
local hl = ctx.point ~= nil and vs.getpath(ctx.point, "args.header") or nil
|
|
21
|
+
if type(hl) == "table" then
|
|
22
|
+
for _, hd in ipairs(hl) do
|
|
23
|
+
local name = vs.getprop(hd, "name")
|
|
24
|
+
if type(name) == "string" and name ~= "" then
|
|
25
|
+
local orig = vs.getprop(hd, "orig")
|
|
26
|
+
if type(orig) ~= "string" or orig == "" then
|
|
27
|
+
orig = name
|
|
28
|
+
end
|
|
29
|
+
local val = vs.getprop(ctx.reqmatch or {}, name)
|
|
30
|
+
if val == nil then
|
|
31
|
+
val = vs.getprop(ctx.reqdata or {}, name)
|
|
32
|
+
end
|
|
33
|
+
if val ~= nil then
|
|
34
|
+
local wire = string.lower(orig)
|
|
35
|
+
for key in pairs(out) do
|
|
36
|
+
if type(key) == "string" and string.lower(key) == wire then
|
|
37
|
+
out[key] = nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
out[wire] = vs.stringify(val)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
16
44
|
end
|
|
17
|
-
|
|
45
|
+
|
|
46
|
+
return out
|
|
18
47
|
end
|
|
19
48
|
|
|
20
49
|
return prepare_headers_util
|
|
@@ -34,6 +34,16 @@ local function prepare_query_util(ctx)
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
|
+
-- A header parameter travels in the headers, which prepare_headers fills.
|
|
38
|
+
local hl = vs.getpath(point, "args.header")
|
|
39
|
+
if type(hl) == "table" then
|
|
40
|
+
for _, hd in ipairs(hl) do
|
|
41
|
+
local name = vs.getprop(hd, "name")
|
|
42
|
+
if type(name) == "string" then
|
|
43
|
+
table.insert(params, name)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
37
47
|
end
|
|
38
48
|
|
|
39
49
|
-- A query parameter travels under the name the definition gives it, its
|
|
@@ -6,19 +6,48 @@ local helpers = require("core.helpers")
|
|
|
6
6
|
-- `$action` selects the point (see make_point_util); it is never an API
|
|
7
7
|
-- field, so the body is a copy without it. The caller's table is left
|
|
8
8
|
-- untouched.
|
|
9
|
-
local function
|
|
10
|
-
if not vs.ismap(reqdata)
|
|
9
|
+
local function omit(reqdata, names)
|
|
10
|
+
if not vs.ismap(reqdata) then
|
|
11
|
+
return reqdata
|
|
12
|
+
end
|
|
13
|
+
local skip = {}
|
|
14
|
+
local found = false
|
|
15
|
+
for _, name in ipairs(names) do
|
|
16
|
+
skip[name] = true
|
|
17
|
+
found = found or reqdata[name] ~= nil
|
|
18
|
+
end
|
|
19
|
+
if not found then
|
|
11
20
|
return reqdata
|
|
12
21
|
end
|
|
13
22
|
local body = {}
|
|
14
23
|
for k, v in pairs(reqdata) do
|
|
15
|
-
if k
|
|
24
|
+
if not skip[k] then
|
|
16
25
|
body[k] = v
|
|
17
26
|
end
|
|
18
27
|
end
|
|
19
28
|
return body
|
|
20
29
|
end
|
|
21
30
|
|
|
31
|
+
local function strip_action(reqdata)
|
|
32
|
+
return omit(reqdata, { "$action" })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
-- A header argument travels as a header, which prepare_headers_util sends, so
|
|
36
|
+
-- the body is built from the request data without it.
|
|
37
|
+
local function header_arg_names(point)
|
|
38
|
+
local names = {}
|
|
39
|
+
local hl = point ~= nil and vs.getpath(point, "args.header") or nil
|
|
40
|
+
if type(hl) == "table" then
|
|
41
|
+
for _, hd in ipairs(hl) do
|
|
42
|
+
local name = vs.getprop(hd, "name")
|
|
43
|
+
if type(name) == "string" and name ~= "" then
|
|
44
|
+
names[#names + 1] = name
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
return names
|
|
49
|
+
end
|
|
50
|
+
|
|
22
51
|
local function transform_request_util(ctx)
|
|
23
52
|
local spec = ctx.spec
|
|
24
53
|
local point = ctx.point
|
|
@@ -27,18 +56,20 @@ local function transform_request_util(ctx)
|
|
|
27
56
|
spec.step = "reqform"
|
|
28
57
|
end
|
|
29
58
|
|
|
59
|
+
local data = omit(ctx.reqdata, header_arg_names(point))
|
|
60
|
+
|
|
30
61
|
local transform = helpers.to_map(vs.getprop(point, "transform"))
|
|
31
62
|
if transform == nil then
|
|
32
|
-
return strip_action(
|
|
63
|
+
return strip_action(data)
|
|
33
64
|
end
|
|
34
65
|
|
|
35
66
|
local reqform = vs.getprop(transform, "req")
|
|
36
67
|
if reqform == nil then
|
|
37
|
-
return strip_action(
|
|
68
|
+
return strip_action(data)
|
|
38
69
|
end
|
|
39
70
|
|
|
40
71
|
local reqdata = vs.transform({
|
|
41
|
-
reqdata =
|
|
72
|
+
reqdata = data,
|
|
42
73
|
}, reqform)
|
|
43
74
|
|
|
44
75
|
return strip_action(reqdata)
|
|
@@ -355,9 +355,35 @@ let prepare_method_util (ctx : ctx) : string =
|
|
|
355
355
|
|
|
356
356
|
let prepare_headers_util (ctx : ctx) : value =
|
|
357
357
|
let options = client_options_map (cc ctx) in
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
358
|
+
let out =
|
|
359
|
+
match getp options "headers" with
|
|
360
|
+
| Noval -> empty_map ()
|
|
361
|
+
| h -> (match clone h with Map _ as m -> m | _ -> empty_map ())
|
|
362
|
+
in
|
|
363
|
+
(* A header parameter travels as a header, under the name the definition
|
|
364
|
+
* gives it, and only from this call's own arguments. It replaces a default
|
|
365
|
+
* of the same name, whatever its case. *)
|
|
366
|
+
(match getp (getp ctx.c_point "args") "header" with
|
|
367
|
+
| List r ->
|
|
368
|
+
List.iter (fun hd ->
|
|
369
|
+
match getp hd "name" with
|
|
370
|
+
| Str name when name <> "" ->
|
|
371
|
+
let wire = match getp hd "orig" with Str o when o <> "" -> o | _ -> name in
|
|
372
|
+
let v = match getp ctx.c_reqmatch name with
|
|
373
|
+
| Noval | Null -> getp ctx.c_reqdata name
|
|
374
|
+
| v -> v
|
|
375
|
+
in
|
|
376
|
+
(match v with
|
|
377
|
+
| Noval | Null -> ()
|
|
378
|
+
| v ->
|
|
379
|
+
let key = String.lowercase_ascii wire in
|
|
380
|
+
List.iter (fun k ->
|
|
381
|
+
if String.lowercase_ascii k = key then ignore (delprop out (Str k)))
|
|
382
|
+
(keysof out);
|
|
383
|
+
setp out key (Str (stringify v)))
|
|
384
|
+
| _ -> ()) !r
|
|
385
|
+
| _ -> ());
|
|
386
|
+
out
|
|
361
387
|
|
|
362
388
|
let param_util (ctx : ctx) (paramdef : value) : value =
|
|
363
389
|
let point = ctx.c_point and spec = ctx.c_spec in
|
|
@@ -418,7 +444,13 @@ let prepare_query_util (ctx : ctx) : value =
|
|
|
418
444
|
| List r -> List.map (fun pd -> getp pd "name") !r
|
|
419
445
|
| _ -> []
|
|
420
446
|
in
|
|
421
|
-
|
|
447
|
+
(* A header parameter travels in the headers, which prepare_headers fills. *)
|
|
448
|
+
let header_names =
|
|
449
|
+
match getp (getp ctx.c_point "args") "header" with
|
|
450
|
+
| List r -> List.map (fun hd -> getp hd "name") !r
|
|
451
|
+
| _ -> []
|
|
452
|
+
in
|
|
453
|
+
let params = params @ arg_names @ header_names in
|
|
422
454
|
let contains_param s = List.exists (fun v -> match v with Str x -> x = s | _ -> false) params in
|
|
423
455
|
(* A query parameter travels under the name the definition gives it, its
|
|
424
456
|
* orig, which the model may have renamed for the caller. *)
|
|
@@ -578,27 +610,39 @@ let prepare_auth_util = Sdk_prepare_auth.prepare_auth_util
|
|
|
578
610
|
|
|
579
611
|
(* ----- transforms / result helpers ----- *)
|
|
580
612
|
|
|
581
|
-
|
|
582
|
-
field, so the body is a copy without it. The caller's map is left
|
|
583
|
-
untouched. *)
|
|
584
|
-
let strip_action (reqdata : value) : value =
|
|
613
|
+
let omit_keys (reqdata : value) (names : string list) : value =
|
|
585
614
|
match reqdata with
|
|
586
|
-
| Map _ when List.mem
|
|
615
|
+
| Map _ when List.exists (fun n -> List.mem n (keysof reqdata)) names ->
|
|
587
616
|
let body = empty_map () in
|
|
588
|
-
List.iter (fun k -> if k
|
|
617
|
+
List.iter (fun k -> if not (List.mem k names) then setp body k (getp reqdata k))
|
|
589
618
|
(keysof reqdata);
|
|
590
619
|
body
|
|
591
620
|
| _ -> reqdata
|
|
592
621
|
|
|
622
|
+
(* `$action` selects the point (see make_point_util); it is never an API
|
|
623
|
+
field, so the body is a copy without it. The caller's map is left
|
|
624
|
+
untouched. *)
|
|
625
|
+
let strip_action (reqdata : value) : value = omit_keys reqdata ["$action"]
|
|
626
|
+
|
|
627
|
+
(* A header argument travels as a header, which prepare_headers_util sends,
|
|
628
|
+
so the body is built from the request data without it. *)
|
|
629
|
+
let header_arg_names (point : value) : string list =
|
|
630
|
+
match getp (getp point "args") "header" with
|
|
631
|
+
| List r ->
|
|
632
|
+
List.filter_map (fun hd ->
|
|
633
|
+
match getp hd "name" with Str n when n <> "" -> Some n | _ -> None) !r
|
|
634
|
+
| _ -> []
|
|
635
|
+
|
|
593
636
|
let transform_request_util (ctx : ctx) : value =
|
|
594
637
|
(match ctx.c_spec with Some s -> s.sp_step <- "reqform" | None -> ());
|
|
638
|
+
let data = omit_keys ctx.c_reqdata (header_arg_names ctx.c_point) in
|
|
595
639
|
strip_action
|
|
596
640
|
(match to_map (getp ctx.c_point "transform") with
|
|
597
641
|
| Map _ as tr ->
|
|
598
642
|
(match getp tr "req" with
|
|
599
|
-
| Noval ->
|
|
600
|
-
| reqform -> transform (jo [("reqdata",
|
|
601
|
-
| _ ->
|
|
643
|
+
| Noval -> data
|
|
644
|
+
| reqform -> transform (jo [("reqdata", data)]) reqform)
|
|
645
|
+
| _ -> data)
|
|
602
646
|
|
|
603
647
|
let transform_response_util (ctx : ctx) : value =
|
|
604
648
|
(match ctx.c_spec with Some s -> s.sp_step <- "resform" | None -> ());
|
|
@@ -804,6 +848,12 @@ let make_url_util (ctx : ctx) : (string * sdk_error option) =
|
|
|
804
848
|
| _, None -> ("", Some (ctx_make_error ctx "url_no_result" "Expected context result property to be defined."))
|
|
805
849
|
| Some spec, Some result ->
|
|
806
850
|
let url = ref (join ~sep:(Str "/") ~url:true (ja [Str spec.sp_base; Str spec.sp_prefix; Str spec.sp_path; Str spec.sp_suffix])) in
|
|
851
|
+
(* A route the definition ends with a slash keeps it: a server such as a
|
|
852
|
+
* Django REST one redirects or refuses the route without it. *)
|
|
853
|
+
let ends_slash s = String.length s > 0 && '/' = s.[String.length s - 1] in
|
|
854
|
+
(match getp ctx.c_point "orig" with
|
|
855
|
+
| Str o when ends_slash o && spec.sp_suffix = "" && not (ends_slash !url) -> url := !url ^ "/"
|
|
856
|
+
| _ -> ());
|
|
807
857
|
let resmatch = empty_map () in
|
|
808
858
|
List.iter (fun key ->
|
|
809
859
|
let v = getp spec.sp_params key in
|
|
@@ -29,6 +29,12 @@ $REGISTRY{make_url} = sub {
|
|
|
29
29
|
[$spec->{base}, $spec->{prefix}, $spec->{path}, $spec->{suffix}], '/', 1);
|
|
30
30
|
my $resmatch = {};
|
|
31
31
|
|
|
32
|
+
# A route the definition ends with a slash keeps it: a server such as a
|
|
33
|
+
# Django REST one redirects or refuses the route without it.
|
|
34
|
+
my $orig = $ctx->{point} ? ProjectNameHelpers::gp($ctx->{point}, 'orig') : undef;
|
|
35
|
+
$url .= '/' if defined $orig && !ref $orig && $orig =~ m{/\z}
|
|
36
|
+
&& !(defined $spec->{suffix} && '' ne $spec->{suffix}) && $url !~ m{/\z};
|
|
37
|
+
|
|
32
38
|
my $param_items = Voxgig::Struct::items($spec->{params});
|
|
33
39
|
if ($param_items) {
|
|
34
40
|
for my $item (@$param_items) {
|
|
@@ -19,9 +19,27 @@ $REGISTRY{prepare_headers} = sub {
|
|
|
19
19
|
my ($ctx) = @_;
|
|
20
20
|
my $options = $ctx->{client}->options_map;
|
|
21
21
|
my $headers = ProjectNameHelpers::gp($options, 'headers');
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
my $out = ProjectNameHelpers::rb_truthy($headers) ? Voxgig::Struct::clone($headers) : {};
|
|
23
|
+
$out = {} unless Voxgig::Struct::ismap($out);
|
|
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
|
+
my $hl = $ctx->{point} ? ProjectNameHelpers::gpath($ctx->{point}, 'args.header') : undef;
|
|
28
|
+
if (Voxgig::Struct::islist($hl)) {
|
|
29
|
+
for my $hd (@$hl) {
|
|
30
|
+
my $name = ProjectNameHelpers::gp($hd, 'name');
|
|
31
|
+
next unless defined $name && !ref $name && '' ne $name;
|
|
32
|
+
my $orig = ProjectNameHelpers::gp($hd, 'orig');
|
|
33
|
+
$orig = $name unless defined $orig && !ref $orig && '' ne $orig;
|
|
34
|
+
my $val = ProjectNameHelpers::gp($ctx->{reqmatch} || {}, $name);
|
|
35
|
+
$val = ProjectNameHelpers::gp($ctx->{reqdata} || {}, $name) unless defined $val;
|
|
36
|
+
next unless defined $val;
|
|
37
|
+
my $wire = lc $orig;
|
|
38
|
+
delete $out->{$_} for grep { lc $_ eq $wire } keys %$out;
|
|
39
|
+
$out->{$wire} = Voxgig::Struct::stringify($val);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return $out;
|
|
25
43
|
};
|
|
26
44
|
|
|
27
45
|
1;
|
|
@@ -32,6 +32,14 @@ $REGISTRY{prepare_query} = sub {
|
|
|
32
32
|
push @$params, $name if defined $name && !ref $name;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
# A header parameter travels in the headers, which prepare_headers fills.
|
|
36
|
+
my $hl = ProjectNameHelpers::gpath($point, 'args.header');
|
|
37
|
+
if (Voxgig::Struct::islist($hl)) {
|
|
38
|
+
for my $hd (@$hl) {
|
|
39
|
+
my $name = ProjectNameHelpers::gp($hd, 'name');
|
|
40
|
+
push @$params, $name if defined $name && !ref $name;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
35
43
|
}
|
|
36
44
|
# A query parameter travels under the name the definition gives it, its
|
|
37
45
|
# orig, which the model may have renamed for the caller.
|
|
@@ -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 $
|
|
21
|
-
my ($reqdata) = @_;
|
|
22
|
-
return $reqdata unless 'HASH' eq ref $reqdata && exists $reqdata->{
|
|
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
|
|
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->($
|
|
46
|
+
return $strip_action->($data) unless $transform;
|
|
35
47
|
my $reqform = ProjectNameHelpers::gp($transform, 'req');
|
|
36
|
-
return $strip_action->($
|
|
37
|
-
return $strip_action->(Voxgig::Struct::transform({ 'reqdata' => $
|
|
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) {
|