@voxgig/sdkgen 4.23.0 → 4.24.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 +18 -2
- package/dist/action/doctor.d.ts +1 -1
- package/dist/action/doctor.js +12 -10
- package/dist/action/doctor.js.map +1 -1
- package/dist/action/edition.js +4 -0
- package/dist/action/edition.js.map +1 -1
- package/dist/action/feature.js +5 -1
- package/dist/action/feature.js.map +1 -1
- package/dist/action/remove.d.ts +16 -0
- package/dist/action/remove.js +345 -0
- package/dist/action/remove.js.map +1 -0
- package/dist/action/target.js +6 -1
- package/dist/action/target.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/project/.sdk/tm/c/core/context.c +3 -1
- package/project/.sdk/tm/c/feature/paging.c +37 -3
- package/project/.sdk/tm/c/feature/test.c +51 -27
- package/project/.sdk/tm/c/utility/prepare_query.c +2 -1
- package/project/.sdk/tm/c/utility/transform_request.c +25 -3
- package/project/.sdk/tm/clojure/src/sdk/core.clj +21 -7
- package/project/.sdk/tm/clojure/src/sdk/features.clj +68 -18
- package/project/.sdk/tm/cpp/core/types.hpp +1 -1
- package/project/.sdk/tm/cpp/feature/paging.hpp +36 -3
- package/project/.sdk/tm/cpp/feature/test.hpp +41 -13
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +16 -4
- package/project/.sdk/tm/csharp/core/Context.cs +1 -1
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +58 -4
- package/project/.sdk/tm/csharp/feature/TestFeature.cs +13 -21
- package/project/.sdk/tm/csharp/utility/Fetcher.cs +19 -12
- package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +1 -1
- package/project/.sdk/tm/csharp/utility/TransformRequest.cs +23 -3
- package/project/.sdk/tm/elixir/lib/projectname/context.ex +1 -1
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +52 -10
- package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +47 -17
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +30 -9
- package/project/.sdk/tm/go/core/context.go +13 -13
- package/project/.sdk/tm/go/feature/paging_feature.go +40 -2
- package/project/.sdk/tm/go/feature/test_feature.go +3 -15
- package/project/.sdk/tm/go/test/feature_corpus_test.go +1 -0
- package/project/.sdk/tm/go/test/feature_test.go +54 -0
- package/project/.sdk/tm/go/utility/fetcher.go +41 -20
- package/project/.sdk/tm/go/utility/prepare_query.go +1 -1
- package/project/.sdk/tm/go/utility/transform_request.go +22 -3
- package/project/.sdk/tm/java/core/Context.java +2 -1
- package/project/.sdk/tm/java/feature/PagingFeature.java +46 -4
- package/project/.sdk/tm/java/feature/TestFeature.java +10 -13
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +3 -0
- package/project/.sdk/tm/java/test/FeatureTest.java +42 -0
- package/project/.sdk/tm/java/utility/Fetcher.java +36 -28
- package/project/.sdk/tm/java/utility/PrepareQuery.java +1 -1
- package/project/.sdk/tm/java/utility/TransformRequest.java +18 -3
- package/project/.sdk/tm/js/src/Context.js +7 -2
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +24 -6
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +7 -5
- package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +1 -1
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +4 -0
- package/project/.sdk/tm/kotlin/core/Context.kt +1 -1
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +36 -4
- package/project/.sdk/tm/kotlin/feature/TestFeature.kt +11 -14
- package/project/.sdk/tm/kotlin/utility/Fetcher.kt +33 -24
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -3
- package/project/.sdk/tm/lua/core/context.lua +8 -1
- package/project/.sdk/tm/lua/core/control.lua +3 -0
- package/project/.sdk/tm/lua/feature/paging_feature.lua +44 -5
- package/project/.sdk/tm/lua/feature/test_feature.lua +32 -14
- package/project/.sdk/tm/lua/utility/prepare_query.lua +2 -1
- package/project/.sdk/tm/lua/utility/transform_request.lua +19 -3
- package/project/.sdk/tm/ocaml/sdk_features.ml +72 -21
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +23 -8
- package/project/.sdk/tm/perl/core/context.pm +2 -1
- package/project/.sdk/tm/perl/feature/paging_feature.pm +31 -5
- package/project/.sdk/tm/perl/feature/test_feature.pm +46 -16
- package/project/.sdk/tm/perl/feature/timeout_feature.pm +17 -13
- package/project/.sdk/tm/perl/t/feature_corpus.t +14 -1
- package/project/.sdk/tm/perl/utility/fetcher.pm +15 -1
- package/project/.sdk/tm/perl/utility/prepare_query.pm +1 -0
- package/project/.sdk/tm/perl/utility/transform_request.pm +13 -3
- package/project/.sdk/tm/php/core/Context.php +5 -1
- package/project/.sdk/tm/php/core/Control.php +5 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +26 -5
- package/project/.sdk/tm/php/feature/TestFeature.php +27 -42
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +17 -1
- package/project/.sdk/tm/php/test/FeatureTest.php +44 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +17 -3
- package/project/.sdk/tm/php/utility/PrepareQuery.php +1 -1
- package/project/.sdk/tm/php/utility/TransformRequest.php +15 -3
- package/project/.sdk/tm/py/pkg/core/context.py +2 -1
- package/project/.sdk/tm/py/pkg/feature/paging_feature.py +29 -4
- package/project/.sdk/tm/py/pkg/feature/test_feature.py +29 -12
- package/project/.sdk/tm/py/pkg/utility/fetcher.py +63 -65
- package/project/.sdk/tm/py/pkg/utility/prepare_query.py +2 -1
- package/project/.sdk/tm/py/pkg/utility/transform_request.py +11 -3
- package/project/.sdk/tm/py/test/test_feature.py +27 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +11 -1
- package/project/.sdk/tm/rb/core/context.rb +1 -1
- package/project/.sdk/tm/rb/feature/paging_feature.rb +27 -5
- package/project/.sdk/tm/rb/feature/test_feature.rb +33 -7
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -1
- package/project/.sdk/tm/rb/test/feature_test.rb +38 -0
- package/project/.sdk/tm/rb/utility/fetcher.rb +49 -3
- package/project/.sdk/tm/rb/utility/prepare_query.rb +1 -1
- package/project/.sdk/tm/rb/utility/transform_request.rb +10 -3
- package/project/.sdk/tm/rust/core/context.rs +2 -6
- package/project/.sdk/tm/rust/feature/paging.rs +44 -3
- package/project/.sdk/tm/rust/feature/secrets.rs +1 -1
- package/project/.sdk/tm/rust/feature/test.rs +66 -33
- package/project/.sdk/tm/rust/utility/fetcher.rs +41 -14
- package/project/.sdk/tm/rust/utility/prepare_query.rs +1 -1
- package/project/.sdk/tm/rust/utility/transform_request.rs +31 -5
- package/project/.sdk/tm/scala/core/Context.scala +2 -1
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +41 -4
- package/project/.sdk/tm/scala/feature/TestFeature.scala +11 -12
- package/project/.sdk/tm/scala/utility/Fetcher.scala +34 -13
- package/project/.sdk/tm/scala/utility/Prepare.scala +1 -1
- package/project/.sdk/tm/scala/utility/Transform.scala +19 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +31 -4
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +12 -9
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +22 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +14 -3
- package/project/.sdk/tm/ts/src/Context.ts +7 -2
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +19 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +3 -0
- package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +1 -1
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +1 -0
- package/project/.sdk/tm/zig/core/context.zig +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +19 -5
- package/project/.sdk/tm/zig/feature/paging.zig +34 -4
- package/project/.sdk/tm/zig/feature/test.zig +54 -26
- package/project/sdkgen-package.json +1 -1
- package/src/action/doctor.ts +12 -9
- package/src/action/edition.ts +8 -0
- package/src/action/feature.ts +7 -1
- package/src/action/remove.ts +492 -0
- package/src/action/target.ts +8 -1
|
@@ -148,6 +148,7 @@ class TestFeature extends BaseFeature {
|
|
|
148
148
|
const found = select(entmap, args)
|
|
149
149
|
const ent = getelem(found, 0)
|
|
150
150
|
if (null == ent) {
|
|
151
|
+
// update miss: 404, never another record
|
|
151
152
|
return respond(404, undefined, { statusText: S_NOT_FOUND })
|
|
152
153
|
}
|
|
153
154
|
else {
|
|
@@ -161,13 +162,12 @@ class TestFeature extends BaseFeature {
|
|
|
161
162
|
const args = self.buildArgs(ctx, op, ctx.reqmatch)
|
|
162
163
|
const found = select(entmap, args)
|
|
163
164
|
const ent = getelem(found, 0)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
else {
|
|
165
|
+
// Remove only the first matched entity. If nothing matches,
|
|
166
|
+
// succeed as a no-op rather than erroring.
|
|
167
|
+
if (null != ent) {
|
|
168
168
|
delprop(entmap, getprop(ent, 'id'))
|
|
169
|
-
return respond(200)
|
|
170
169
|
}
|
|
170
|
+
return respond(200)
|
|
171
171
|
}
|
|
172
172
|
else if ('create' === op.name) {
|
|
173
173
|
const args = self.buildArgs(ctx, op, ctx.reqdata)
|
|
@@ -183,6 +183,8 @@ class TestFeature extends BaseFeature {
|
|
|
183
183
|
const out = clone(ent)
|
|
184
184
|
return respond(200, out)
|
|
185
185
|
}
|
|
186
|
+
|
|
187
|
+
return respond(404, undefined, { statusText: 'Unknown operation' })
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
// Optional network behaviour simulation over the mock transport. Enable
|
|
@@ -254,6 +254,10 @@ describe('FeatureCorpus', () => {
|
|
|
254
254
|
|
|
255
255
|
const probe = makeClient({})
|
|
256
256
|
if (!probe._rootctx.config.hasFeature(name)) {
|
|
257
|
+
// The one line every runner prints for a section that names a
|
|
258
|
+
// feature this SDK does not generate, so an inert section is
|
|
259
|
+
// visible in the output rather than inferred from a green tick.
|
|
260
|
+
t.diagnostic(`feature.${name}: inert (this SDK does not generate the feature)`)
|
|
257
261
|
return t.skip(`this SDK was generated without the ${name} feature`)
|
|
258
262
|
}
|
|
259
263
|
|
|
@@ -76,7 +76,7 @@ class Context(ctxmap: MutableMap<String, Any?>?, basectx: Context?) {
|
|
|
76
76
|
} else if (cv is Control) {
|
|
77
77
|
this.ctrl = cv
|
|
78
78
|
}
|
|
79
|
-
} else if (basectx != null) {
|
|
79
|
+
} else if (basectx != null && Helpers.getCtxProp(ctxmap, "opname") == null) {
|
|
80
80
|
this.ctrl = basectx.ctrl
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -8,8 +8,9 @@ import KOTLINPACKAGE.utility.struct.Struct
|
|
|
8
8
|
|
|
9
9
|
// Pagination support for list operations. On the way out (PreRequest) it
|
|
10
10
|
// stamps page/limit (or a cursor) into the request query; on the way back
|
|
11
|
-
// (PreResult) it reads the server's pagination signals and
|
|
12
|
-
// `ctx.result.paging
|
|
11
|
+
// (PreResult) it reads the server's pagination signals (camelCase and
|
|
12
|
+
// snake_case body forms) and records them on `ctx.result.paging` and the
|
|
13
|
+
// caller's `ctrl.paging`.
|
|
13
14
|
@Suppress("UNCHECKED_CAST")
|
|
14
15
|
class PagingFeature : BaseFeature("paging", "0.0.1", true) {
|
|
15
16
|
|
|
@@ -53,7 +54,9 @@ class PagingFeature : BaseFeature("paging", "0.0.1", true) {
|
|
|
53
54
|
if (cursor != null) {
|
|
54
55
|
spec.query[cursorParam] = cursor
|
|
55
56
|
} else if (spec.query[pageParam] == null) {
|
|
56
|
-
|
|
57
|
+
// A record written back by preResult holds the page just fetched as
|
|
58
|
+
// "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
59
|
+
val page = paging["nextPage"] ?: paging["page"]
|
|
57
60
|
if (page != null) {
|
|
58
61
|
spec.query[pageParam] = page
|
|
59
62
|
} else {
|
|
@@ -166,18 +169,33 @@ class PagingFeature : BaseFeature("paging", "0.0.1", true) {
|
|
|
166
169
|
}
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
// Body-level
|
|
172
|
+
// Body-level signals; snake_case forms are read first so camelCase wins.
|
|
170
173
|
if (body is MutableMap<*, *>) {
|
|
171
174
|
val bm = body as MutableMap<String, Any?>
|
|
172
175
|
if (bm["next"] != null && paging["next"] == null) {
|
|
173
176
|
paging["next"] = bm["next"]
|
|
174
177
|
}
|
|
178
|
+
if (bm["next_cursor"] != null) {
|
|
179
|
+
paging["cursor"] = bm["next_cursor"]
|
|
180
|
+
}
|
|
175
181
|
if (bm["cursor"] != null) {
|
|
176
182
|
paging["cursor"] = bm["cursor"]
|
|
177
183
|
}
|
|
178
184
|
if (bm["nextCursor"] != null) {
|
|
179
185
|
paging["cursor"] = bm["nextCursor"]
|
|
180
186
|
}
|
|
187
|
+
if (paging["nextPage"] == null) {
|
|
188
|
+
val np = bm["nextPage"] ?: bm["next_page"]
|
|
189
|
+
if (np is Number) {
|
|
190
|
+
paging["nextPage"] = np.toInt()
|
|
191
|
+
} else if (np is String) {
|
|
192
|
+
paging["nextPage"] = np
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (bm["has_more"] is Boolean) {
|
|
196
|
+
paging["hasMore"] = bm["has_more"]
|
|
197
|
+
explicitMore = true
|
|
198
|
+
}
|
|
181
199
|
if (bm["hasMore"] is Boolean) {
|
|
182
200
|
paging["hasMore"] = bm["hasMore"]
|
|
183
201
|
explicitMore = true
|
|
@@ -197,6 +215,20 @@ class PagingFeature : BaseFeature("paging", "0.0.1", true) {
|
|
|
197
215
|
|
|
198
216
|
result.paging = paging
|
|
199
217
|
this.last = paging
|
|
218
|
+
|
|
219
|
+
// The Context shares the caller's paging map by reference, so refill it
|
|
220
|
+
// in place; an immutable map falls back to replacing the reference.
|
|
221
|
+
val shared = ctx.ctrl.paging
|
|
222
|
+
if (shared == null) {
|
|
223
|
+
ctx.ctrl.paging = paging
|
|
224
|
+
} else {
|
|
225
|
+
try {
|
|
226
|
+
shared.clear()
|
|
227
|
+
shared.putAll(paging)
|
|
228
|
+
} catch (e: UnsupportedOperationException) {
|
|
229
|
+
ctx.ctrl.paging = paging
|
|
230
|
+
}
|
|
231
|
+
}
|
|
200
232
|
}
|
|
201
233
|
|
|
202
234
|
private fun isList(ctx: Context): Boolean {
|
|
@@ -160,20 +160,13 @@ class TestFeature : BaseFeature("test", "0.0.1", true) {
|
|
|
160
160
|
}
|
|
161
161
|
val args = buildArgs(ctx, op, updateMatch)
|
|
162
162
|
val found = Struct.select(entmap, args)
|
|
163
|
-
|
|
164
|
-
if (ent == null) {
|
|
165
|
-
for (e in entmap.values) {
|
|
166
|
-
if (e is MutableMap<*, *>) {
|
|
167
|
-
ent = e
|
|
168
|
-
break
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
163
|
+
val ent = Struct.getelem(found, 0, null)
|
|
172
164
|
if (ent == null) {
|
|
165
|
+
// update miss: 404, never another record
|
|
173
166
|
return respond(ctx, 404, null, extra("statusText", "Not found"))
|
|
174
167
|
}
|
|
175
168
|
if (ent is MutableMap<*, *>) {
|
|
176
|
-
(ent
|
|
169
|
+
Struct.merge(Struct.jt(ent, reqdata))
|
|
177
170
|
}
|
|
178
171
|
Struct.delprop(ent, "\$KEY")
|
|
179
172
|
val out = Struct.clone(ent)
|
|
@@ -320,10 +313,14 @@ class TestFeature : BaseFeature("test", "0.0.1", true) {
|
|
|
320
313
|
}
|
|
321
314
|
}
|
|
322
315
|
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
val
|
|
326
|
-
|
|
316
|
+
// Path AND query: a path-only read misses a query-addressed record
|
|
317
|
+
// (e.g. GET /result?trace_id=), which has no path param at all.
|
|
318
|
+
val reqdArgs = Struct.jt()
|
|
319
|
+
for (kind in listOf("params", "query")) {
|
|
320
|
+
val argsPath = Struct.getpath(point, listOf("args", kind))
|
|
321
|
+
reqdArgs.addAll(Struct.select(argsPath, Struct.jm("reqd", true)))
|
|
322
|
+
}
|
|
323
|
+
val reqd = Struct.transform(reqdArgs, Struct.jt("`\$EACH`", "", "`\$KEY.name`"))
|
|
327
324
|
|
|
328
325
|
val qand = Struct.jt()
|
|
329
326
|
val q = Struct.jm("`\$AND`", qand)
|
|
@@ -6,12 +6,44 @@ import java.net.URI
|
|
|
6
6
|
import java.net.http.HttpClient
|
|
7
7
|
import java.net.http.HttpRequest
|
|
8
8
|
import java.net.http.HttpResponse
|
|
9
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
9
10
|
import java.util.function.BiFunction
|
|
10
11
|
import java.util.function.Supplier
|
|
11
12
|
|
|
12
13
|
import KOTLINPACKAGE.core.Context
|
|
13
14
|
import KOTLINPACKAGE.utility.struct.Struct
|
|
14
15
|
|
|
16
|
+
// Clients keyed by redirect policy and proxy. Each HttpClient owns a
|
|
17
|
+
// connection pool and a selector thread, so one per request reused
|
|
18
|
+
// nothing; one per key pools across calls.
|
|
19
|
+
private val clients = ConcurrentHashMap<String, HttpClient>()
|
|
20
|
+
|
|
21
|
+
private fun clientFor(fetchdef: MutableMap<String, Any?>): HttpClient {
|
|
22
|
+
// "manual" (set by the station feature's middleware under a hosts
|
|
23
|
+
// policy) returns a 3xx like any other response: an automatic follow
|
|
24
|
+
// would carry injected credentials to a host no policy approved.
|
|
25
|
+
val redirectPolicy =
|
|
26
|
+
if ("manual" == fetchdef["redirect"]) HttpClient.Redirect.NEVER
|
|
27
|
+
else HttpClient.Redirect.NORMAL
|
|
28
|
+
|
|
29
|
+
val praw = fetchdef["proxy"]
|
|
30
|
+
val proxy = if (praw is String) praw else ""
|
|
31
|
+
|
|
32
|
+
return clients.computeIfAbsent("$redirectPolicy|$proxy") {
|
|
33
|
+
val clientb = HttpClient.newBuilder().followRedirects(redirectPolicy)
|
|
34
|
+
if ("" != proxy) {
|
|
35
|
+
try {
|
|
36
|
+
val proxyUri = URI.create(proxy)
|
|
37
|
+
val port = if (proxyUri.port < 0) 80 else proxyUri.port
|
|
38
|
+
clientb.proxy(ProxySelector.of(InetSocketAddress(proxyUri.host, port)))
|
|
39
|
+
} catch (e: RuntimeException) {
|
|
40
|
+
// Unparseable proxy target: direct connection, as the go transport.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
clientb.build()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
15
47
|
@Suppress("UNCHECKED_CAST")
|
|
16
48
|
fun defaultHttpFetch(fullurl: String, fetchdef: MutableMap<String, Any?>): MutableMap<String, Any?> {
|
|
17
49
|
var method = if (fetchdef["method"] is String) fetchdef["method"] as String else ""
|
|
@@ -50,32 +82,9 @@ fun defaultHttpFetch(fullurl: String, fetchdef: MutableMap<String, Any?>): Mutab
|
|
|
50
82
|
reqb.setHeader("User-Agent", "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)")
|
|
51
83
|
}
|
|
52
84
|
|
|
53
|
-
// Honour a redirect annotation on the fetch definition (set by the
|
|
54
|
-
// station feature's middleware under a hosts policy): "manual" returns
|
|
55
|
-
// a 3xx like any other response instead of following it — the ts
|
|
56
|
-
// donor's fetch honours the same key natively, and an automatic follow
|
|
57
|
-
// would carry injected credentials to a host no policy approved.
|
|
58
|
-
val redirectPolicy =
|
|
59
|
-
if ("manual" == fetchdef["redirect"]) HttpClient.Redirect.NEVER
|
|
60
|
-
else HttpClient.Redirect.NORMAL
|
|
61
|
-
|
|
62
|
-
// Honour a proxy annotation on the fetch definition (set by the proxy
|
|
63
|
-
// feature): route the request through a proxied HttpClient.
|
|
64
|
-
val clientb = HttpClient.newBuilder().followRedirects(redirectPolicy)
|
|
65
|
-
val proxy = fetchdef["proxy"]
|
|
66
|
-
if (proxy is String && "" != proxy) {
|
|
67
|
-
try {
|
|
68
|
-
val proxyUri = URI.create(proxy)
|
|
69
|
-
val port = if (proxyUri.port < 0) 80 else proxyUri.port
|
|
70
|
-
clientb.proxy(ProxySelector.of(InetSocketAddress(proxyUri.host, port)))
|
|
71
|
-
} catch (e: RuntimeException) {
|
|
72
|
-
// Unparseable proxy target: fall through to a direct connection.
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
85
|
val resp: HttpResponse<String>
|
|
77
86
|
try {
|
|
78
|
-
resp =
|
|
87
|
+
resp = clientFor(fetchdef).send(reqb.build(), HttpResponse.BodyHandlers.ofString())
|
|
79
88
|
} catch (e: Exception) {
|
|
80
89
|
throw RuntimeException("fetch: " + e.message, e)
|
|
81
90
|
}
|
|
@@ -194,7 +194,7 @@ fun prepareQuery(ctx: Context): MutableMap<String, Any?> {
|
|
|
194
194
|
for (item in Struct.items(reqmatch)) {
|
|
195
195
|
val key = if (item[0] is String) item[0] as String else ""
|
|
196
196
|
val v = item[1]
|
|
197
|
-
if (v != null && !containsStr(params, key)) {
|
|
197
|
+
if (v != null && "\$action" != key && !containsStr(params, key)) {
|
|
198
198
|
out[key] = v
|
|
199
199
|
}
|
|
200
200
|
}
|
|
@@ -58,21 +58,36 @@ fun resultHeaders(ctx: Context): KOTLINPACKAGE.core.Result {
|
|
|
58
58
|
return result!!
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// `$action` selects the point (see makePoint); it is never an API field, so
|
|
62
|
+
// the body is a copy without it. The caller's map is left untouched.
|
|
63
|
+
private fun stripAction(reqdata: Any?): Any? {
|
|
64
|
+
if (reqdata !is Map<*, *> || !reqdata.containsKey("\$action")) {
|
|
65
|
+
return reqdata
|
|
66
|
+
}
|
|
67
|
+
val body = linkedMapOf<String, Any?>()
|
|
68
|
+
for ((k, v) in reqdata) {
|
|
69
|
+
if ("\$action" != k) {
|
|
70
|
+
body[k.toString()] = v
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return body
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
fun transformRequest(ctx: Context): Any? {
|
|
62
77
|
if (ctx.spec != null) {
|
|
63
78
|
ctx.spec!!.step = "reqform"
|
|
64
79
|
}
|
|
65
80
|
|
|
66
81
|
val transform = Helpers.toMapAny(Struct.getprop(ctx.point, "transform"))
|
|
67
|
-
?: return ctx.reqdata
|
|
82
|
+
?: return stripAction(ctx.reqdata)
|
|
68
83
|
|
|
69
84
|
val reqform = Struct.getprop(transform, "req", null)
|
|
70
|
-
?: return ctx.reqdata
|
|
85
|
+
?: return stripAction(ctx.reqdata)
|
|
71
86
|
|
|
72
87
|
val data = linkedMapOf<String, Any?>()
|
|
73
88
|
data["reqdata"] = ctx.reqdata
|
|
74
89
|
|
|
75
|
-
return Struct.transform(data, reqform)
|
|
90
|
+
return stripAction(Struct.transform(data, reqform))
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
fun transformResponse(ctx: Context): Any? {
|
|
@@ -49,7 +49,14 @@ function Context.new(ctxmap, basectx)
|
|
|
49
49
|
if type(ctrl_raw.explain) == "table" then
|
|
50
50
|
self.ctrl.explain = ctrl_raw.explain
|
|
51
51
|
end
|
|
52
|
-
|
|
52
|
+
if ctrl_raw.actor ~= nil then
|
|
53
|
+
self.ctrl.actor = ctrl_raw.actor
|
|
54
|
+
end
|
|
55
|
+
if type(ctrl_raw.paging) == "table" then
|
|
56
|
+
self.ctrl.paging = ctrl_raw.paging
|
|
57
|
+
end
|
|
58
|
+
elseif basectx ~= nil and basectx.ctrl ~= nil
|
|
59
|
+
and helpers.get_ctx_prop(ctxmap, "opname") == nil then
|
|
53
60
|
self.ctrl = basectx.ctrl
|
|
54
61
|
end
|
|
55
62
|
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
-- (PreResult) it reads the server's pagination signals — a
|
|
6
6
|
-- `Link: <...>; rel="next"` header, `X-Page`/`X-Next-Page`/
|
|
7
7
|
-- `X-Total-Count` headers, or `next`/`cursor`/`nextCursor`/`hasMore`
|
|
8
|
-
-- fields in the body — and records them on
|
|
9
|
-
--
|
|
8
|
+
-- fields in the body (snake_case forms too) — and records them on
|
|
9
|
+
-- `ctx.result.paging` and the caller's `ctrl.paging`. A per-call
|
|
10
|
+
-- `ctrl.paging` (cursor or page) wins over the option defaults,
|
|
10
11
|
-- so auto-iteration can advance the cursor/page and re-issue the list
|
|
11
12
|
-- call until `hasMore` is false. Parameter names (`pageParam`,
|
|
12
13
|
-- `limitParam`, `cursorParam`), `startPage` (default 1) and `limit` are
|
|
@@ -95,8 +96,14 @@ function PagingFeature:PreRequest(ctx)
|
|
|
95
96
|
if paging["cursor"] ~= nil then
|
|
96
97
|
spec.query[cursor_param] = paging["cursor"]
|
|
97
98
|
elseif spec.query[page_param] == nil then
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
-- A record written back by PreResult holds the page just fetched as
|
|
100
|
+
-- "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
101
|
+
local page = paging["nextPage"]
|
|
102
|
+
if page == nil then
|
|
103
|
+
page = paging["page"]
|
|
104
|
+
end
|
|
105
|
+
if page ~= nil then
|
|
106
|
+
spec.query[page_param] = page
|
|
100
107
|
else
|
|
101
108
|
spec.query[page_param] = self.options["startPage"] or 1
|
|
102
109
|
end
|
|
@@ -218,17 +225,33 @@ function PagingFeature:PreResult(ctx)
|
|
|
218
225
|
end
|
|
219
226
|
end
|
|
220
227
|
|
|
221
|
-
-- Body-level
|
|
228
|
+
-- Body-level signals; snake_case forms are read first so camelCase wins.
|
|
222
229
|
if type(body) == "table" then
|
|
223
230
|
if body["next"] ~= nil and paging.next == nil then
|
|
224
231
|
paging.next = body["next"]
|
|
225
232
|
end
|
|
233
|
+
if body["next_cursor"] ~= nil then
|
|
234
|
+
paging.cursor = body["next_cursor"]
|
|
235
|
+
end
|
|
226
236
|
if body["cursor"] ~= nil then
|
|
227
237
|
paging.cursor = body["cursor"]
|
|
228
238
|
end
|
|
229
239
|
if body["nextCursor"] ~= nil then
|
|
230
240
|
paging.cursor = body["nextCursor"]
|
|
231
241
|
end
|
|
242
|
+
if paging.nextPage == nil then
|
|
243
|
+
local np = body["nextPage"]
|
|
244
|
+
if np == nil then
|
|
245
|
+
np = body["next_page"]
|
|
246
|
+
end
|
|
247
|
+
if type(np) == "number" or type(np) == "string" then
|
|
248
|
+
paging.nextPage = np
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
if type(body["has_more"]) == "boolean" then
|
|
252
|
+
paging.hasMore = body["has_more"]
|
|
253
|
+
explicit_more = true
|
|
254
|
+
end
|
|
232
255
|
if type(body["hasMore"]) == "boolean" then
|
|
233
256
|
paging.hasMore = body["hasMore"]
|
|
234
257
|
explicit_more = true
|
|
@@ -247,6 +270,22 @@ function PagingFeature:PreResult(ctx)
|
|
|
247
270
|
|
|
248
271
|
result.paging = paging
|
|
249
272
|
|
|
273
|
+
if ctx.ctrl ~= nil then
|
|
274
|
+
-- The Context shares the caller's paging table by reference, so refill
|
|
275
|
+
-- it in place; a fresh table on ctrl never reaches the caller.
|
|
276
|
+
if type(ctx.ctrl.paging) == "table" then
|
|
277
|
+
local shared = ctx.ctrl.paging
|
|
278
|
+
for k in pairs(shared) do
|
|
279
|
+
shared[k] = nil
|
|
280
|
+
end
|
|
281
|
+
for k, v in pairs(paging) do
|
|
282
|
+
shared[k] = v
|
|
283
|
+
end
|
|
284
|
+
else
|
|
285
|
+
ctx.ctrl.paging = paging
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
250
289
|
local client = self.client
|
|
251
290
|
client._paging = { last = paging }
|
|
252
291
|
end
|
|
@@ -167,21 +167,12 @@ function TestFeature:init(ctx, options)
|
|
|
167
167
|
local args = test_self:build_args(fctx, op, update_match)
|
|
168
168
|
local found = vs.select(entmap, args)
|
|
169
169
|
local ent = vs.getelem(found, 0)
|
|
170
|
-
if ent == nil and type(entmap) == "table" then
|
|
171
|
-
for _, e in pairs(entmap) do
|
|
172
|
-
if type(e) == "table" then ent = e; break end
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
170
|
if ent == nil then
|
|
171
|
+
-- update miss: 404, never another record
|
|
176
172
|
return respond(404, nil, { statusText = "Not found" })
|
|
177
173
|
end
|
|
178
|
-
if type(ent) == "table" then
|
|
179
|
-
|
|
180
|
-
if reqdata ~= nil then
|
|
181
|
-
for k, v in pairs(reqdata) do
|
|
182
|
-
ent[k] = v
|
|
183
|
-
end
|
|
184
|
-
end
|
|
174
|
+
if type(ent) == "table" and type(fctx.reqdata) == "table" then
|
|
175
|
+
vs.merge({ ent, fctx.reqdata })
|
|
185
176
|
end
|
|
186
177
|
vs.delprop(ent, "$KEY")
|
|
187
178
|
local out = vs.clone(ent)
|
|
@@ -312,12 +303,39 @@ function TestFeature:make_netsim(net, inner)
|
|
|
312
303
|
end
|
|
313
304
|
|
|
314
305
|
|
|
306
|
+
local function point_terminal(p)
|
|
307
|
+
local parts = vs.getprop(p, "parts")
|
|
308
|
+
if type(parts) ~= "table" or #parts == 0 then return false end
|
|
309
|
+
local last = parts[#parts]
|
|
310
|
+
return type(last) == "string" and last:sub(1, 1) == "{"
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
local function point_depth(p)
|
|
314
|
+
local parts = vs.getprop(p, "parts")
|
|
315
|
+
return type(parts) == "table" and #parts or 0
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
-- The entity's own endpoint: a terminal `{param}` marks a record route, and
|
|
319
|
+
-- among equals the shallower path wins (the same rule as make_point).
|
|
320
|
+
local function pick_point(points)
|
|
321
|
+
if type(points) ~= "table" or #points == 0 then return nil end
|
|
322
|
+
local point = points[1]
|
|
323
|
+
for i = 2, #points do
|
|
324
|
+
local cand = points[i]
|
|
325
|
+
if point_terminal(cand) ~= point_terminal(point) then
|
|
326
|
+
if point_terminal(cand) then point = cand end
|
|
327
|
+
elseif point_depth(cand) < point_depth(point) then
|
|
328
|
+
point = cand
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
return point
|
|
332
|
+
end
|
|
333
|
+
|
|
315
334
|
function TestFeature:build_args(ctx, op, args)
|
|
316
335
|
local opname = op.name
|
|
317
336
|
|
|
318
|
-
-- Get last point from config.
|
|
319
337
|
local points = vs.getpath(ctx.config, "entity." .. ctx.entity:get_name() .. ".op." .. opname .. ".points")
|
|
320
|
-
local point =
|
|
338
|
+
local point = pick_point(points)
|
|
321
339
|
|
|
322
340
|
-- Path AND query: a path-only read misses a query-addressed record
|
|
323
341
|
-- (e.g. GET /result?trace_id=), which has no path param at all.
|
|
@@ -29,7 +29,8 @@ local function prepare_query_util(ctx)
|
|
|
29
29
|
for _, item in ipairs(reqmatch_items) do
|
|
30
30
|
local key = item[1]
|
|
31
31
|
local val = item[2]
|
|
32
|
-
if val ~= nil and type(key) == "string" and
|
|
32
|
+
if val ~= nil and type(key) == "string" and key ~= "$action"
|
|
33
|
+
and not contains_param(params, key) then
|
|
33
34
|
out[key] = val
|
|
34
35
|
end
|
|
35
36
|
end
|
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
local vs = require("utility.struct.struct")
|
|
4
4
|
local helpers = require("core.helpers")
|
|
5
5
|
|
|
6
|
+
-- `$action` selects the point (see make_point_util); it is never an API
|
|
7
|
+
-- field, so the body is a copy without it. The caller's table is left
|
|
8
|
+
-- untouched.
|
|
9
|
+
local function strip_action(reqdata)
|
|
10
|
+
if not vs.ismap(reqdata) or reqdata["$action"] == nil then
|
|
11
|
+
return reqdata
|
|
12
|
+
end
|
|
13
|
+
local body = {}
|
|
14
|
+
for k, v in pairs(reqdata) do
|
|
15
|
+
if k ~= "$action" then
|
|
16
|
+
body[k] = v
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
return body
|
|
20
|
+
end
|
|
21
|
+
|
|
6
22
|
local function transform_request_util(ctx)
|
|
7
23
|
local spec = ctx.spec
|
|
8
24
|
local point = ctx.point
|
|
@@ -13,19 +29,19 @@ local function transform_request_util(ctx)
|
|
|
13
29
|
|
|
14
30
|
local transform = helpers.to_map(vs.getprop(point, "transform"))
|
|
15
31
|
if transform == nil then
|
|
16
|
-
return ctx.reqdata
|
|
32
|
+
return strip_action(ctx.reqdata)
|
|
17
33
|
end
|
|
18
34
|
|
|
19
35
|
local reqform = vs.getprop(transform, "req")
|
|
20
36
|
if reqform == nil then
|
|
21
|
-
return ctx.reqdata
|
|
37
|
+
return strip_action(ctx.reqdata)
|
|
22
38
|
end
|
|
23
39
|
|
|
24
40
|
local reqdata = vs.transform({
|
|
25
41
|
reqdata = ctx.reqdata,
|
|
26
42
|
}, reqform)
|
|
27
43
|
|
|
28
|
-
return reqdata
|
|
44
|
+
return strip_action(reqdata)
|
|
29
45
|
end
|
|
30
46
|
|
|
31
47
|
return transform_request_util
|