@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
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// stamps page/limit (or a cursor) into the request query; on the way back
|
|
3
3
|
// (PreResult) it reads the server's pagination signals - a `Link:
|
|
4
4
|
// rel="next"` header, `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
|
|
5
|
-
// `next`/`cursor`/`nextCursor`/`hasMore` fields in the body
|
|
6
|
-
// them on `ctx.Result.Paging`. A per-call cursor/page from ctrl takes
|
|
5
|
+
// `next`/`cursor`/`nextCursor`/`hasMore` fields in the body (snake_case
|
|
6
|
+
// forms too) - and records them on `ctx.Result.Paging` and `Ctrl.Paging`. A per-call cursor/page from ctrl takes
|
|
7
7
|
// priority (used by auto-iteration). Parameter names (`pageParam`,
|
|
8
8
|
// `limitParam`, `cursorParam`), the page size (`limit`) and the start page
|
|
9
9
|
// (`startPage`, default 1) are configurable.
|
|
@@ -79,8 +79,12 @@ public class PagingFeature : BaseFeature
|
|
|
79
79
|
}
|
|
80
80
|
else if (!spec.Query.TryGetValue(pageParam, out var existing) || existing == null)
|
|
81
81
|
{
|
|
82
|
+
// A record written back by PreResult holds the page just fetched
|
|
83
|
+
// as "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
82
84
|
object? page = null;
|
|
83
|
-
var hasPage = paging != null &&
|
|
85
|
+
var hasPage = paging != null &&
|
|
86
|
+
((paging.TryGetValue("nextPage", out page) && page != null) ||
|
|
87
|
+
(paging.TryGetValue("page", out page) && page != null));
|
|
84
88
|
spec.Query[pageParam] = hasPage ? page : FoptInt(_options, "startPage", 1);
|
|
85
89
|
}
|
|
86
90
|
|
|
@@ -224,7 +228,7 @@ public class PagingFeature : BaseFeature
|
|
|
224
228
|
}
|
|
225
229
|
}
|
|
226
230
|
|
|
227
|
-
// Body-level
|
|
231
|
+
// Body-level signals; snake_case forms are read first so camelCase wins.
|
|
228
232
|
if (body is Dictionary<string, object?> bm)
|
|
229
233
|
{
|
|
230
234
|
if (bm.TryGetValue("next", out var next) && next != null &&
|
|
@@ -232,6 +236,10 @@ public class PagingFeature : BaseFeature
|
|
|
232
236
|
{
|
|
233
237
|
paging["next"] = next;
|
|
234
238
|
}
|
|
239
|
+
if (bm.TryGetValue("next_cursor", out var snakeCursor) && snakeCursor != null)
|
|
240
|
+
{
|
|
241
|
+
paging["cursor"] = snakeCursor;
|
|
242
|
+
}
|
|
235
243
|
if (bm.TryGetValue("cursor", out var cursor) && cursor != null)
|
|
236
244
|
{
|
|
237
245
|
paging["cursor"] = cursor;
|
|
@@ -240,6 +248,34 @@ public class PagingFeature : BaseFeature
|
|
|
240
248
|
{
|
|
241
249
|
paging["cursor"] = nextCursor;
|
|
242
250
|
}
|
|
251
|
+
if (!paging.TryGetValue("nextPage", out var np0) || np0 == null)
|
|
252
|
+
{
|
|
253
|
+
bm.TryGetValue("nextPage", out var np);
|
|
254
|
+
if (np == null)
|
|
255
|
+
{
|
|
256
|
+
bm.TryGetValue("next_page", out np);
|
|
257
|
+
}
|
|
258
|
+
switch (np)
|
|
259
|
+
{
|
|
260
|
+
case int n:
|
|
261
|
+
paging["nextPage"] = n;
|
|
262
|
+
break;
|
|
263
|
+
case long n:
|
|
264
|
+
paging["nextPage"] = (int)n;
|
|
265
|
+
break;
|
|
266
|
+
case double n:
|
|
267
|
+
paging["nextPage"] = (int)n;
|
|
268
|
+
break;
|
|
269
|
+
case string s:
|
|
270
|
+
paging["nextPage"] = s;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (bm.TryGetValue("has_more", out var snakeMore) && snakeMore is bool smb)
|
|
275
|
+
{
|
|
276
|
+
paging["hasMore"] = smb;
|
|
277
|
+
explicitMore = true;
|
|
278
|
+
}
|
|
243
279
|
if (bm.TryGetValue("hasMore", out var hasMore) && hasMore is bool hmb)
|
|
244
280
|
{
|
|
245
281
|
paging["hasMore"] = hmb;
|
|
@@ -262,6 +298,24 @@ public class PagingFeature : BaseFeature
|
|
|
262
298
|
|
|
263
299
|
result.Paging = paging;
|
|
264
300
|
Last = paging;
|
|
301
|
+
|
|
302
|
+
if (ctx.Ctrl != null)
|
|
303
|
+
{
|
|
304
|
+
// The Context shares the caller's paging map by reference, so
|
|
305
|
+
// refill it in place; a fresh map on Ctrl never reaches the caller.
|
|
306
|
+
if (ctx.Ctrl.Paging != null)
|
|
307
|
+
{
|
|
308
|
+
ctx.Ctrl.Paging.Clear();
|
|
309
|
+
foreach (var kv in paging)
|
|
310
|
+
{
|
|
311
|
+
ctx.Ctrl.Paging[kv.Key] = kv.Value;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else
|
|
315
|
+
{
|
|
316
|
+
ctx.Ctrl.Paging = paging;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
265
319
|
}
|
|
266
320
|
|
|
267
321
|
private bool IsList(Context ctx)
|
|
@@ -188,30 +188,17 @@ public class TestFeature : BaseFeature
|
|
|
188
188
|
var args = BuildArgs(ctx2, op, updateMatch);
|
|
189
189
|
var found = StructUtils.Select(entmap, args);
|
|
190
190
|
var ent = StructUtils.GetElem(found, 0);
|
|
191
|
-
if (ent == null && entmap != null)
|
|
192
|
-
{
|
|
193
|
-
foreach (var e in entmap.Values)
|
|
194
|
-
{
|
|
195
|
-
if (e is Dictionary<string, object?>)
|
|
196
|
-
{
|
|
197
|
-
ent = e;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
191
|
if (ent == null)
|
|
203
192
|
{
|
|
193
|
+
// update miss: 404, never another record
|
|
204
194
|
return Respond(404, null, new Dictionary<string, object?>
|
|
205
195
|
{
|
|
206
196
|
["statusText"] = "Not found",
|
|
207
197
|
});
|
|
208
198
|
}
|
|
209
|
-
if (ent is Dictionary<string, object?>
|
|
199
|
+
if (ent is Dictionary<string, object?> && ctx2.Reqdata != null)
|
|
210
200
|
{
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
entm[kv.Key] = kv.Value;
|
|
214
|
-
}
|
|
201
|
+
StructUtils.Merge(new List<object?> { ent, ctx2.Reqdata });
|
|
215
202
|
}
|
|
216
203
|
StructUtils.DelProp(ent, "$KEY");
|
|
217
204
|
var outval = StructUtils.Clone(ent);
|
|
@@ -384,11 +371,16 @@ public class TestFeature : BaseFeature
|
|
|
384
371
|
}
|
|
385
372
|
}
|
|
386
373
|
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
var
|
|
390
|
-
|
|
391
|
-
|
|
374
|
+
// Path AND query: a path-only read misses a query-addressed record
|
|
375
|
+
// (e.g. GET /result?trace_id=), which has no path param at all.
|
|
376
|
+
var reqdArgs = new List<object?>();
|
|
377
|
+
foreach (var kind in new[] { "params", "query" })
|
|
378
|
+
{
|
|
379
|
+
var argsPath = StructUtils.GetPath(point, StructUtils.Jt("args", kind));
|
|
380
|
+
reqdArgs.AddRange(StructUtils.Select(argsPath,
|
|
381
|
+
new Dictionary<string, object?> { ["reqd"] = true }));
|
|
382
|
+
}
|
|
383
|
+
var reqd = StructUtils.Transform(reqdArgs,
|
|
392
384
|
StructUtils.Jt("`$EACH`", "", "`$KEY.name`"));
|
|
393
385
|
|
|
394
386
|
var qand = new List<object?>();
|
|
@@ -11,7 +11,19 @@ namespace ProjectNameSdk.Util;
|
|
|
11
11
|
|
|
12
12
|
public static partial class SdkUtility
|
|
13
13
|
{
|
|
14
|
-
|
|
14
|
+
// Cookies OFF on every handler. The clients are process-wide, so a
|
|
15
|
+
// CookieContainer would send one call's Set-Cookie on the next, under a
|
|
16
|
+
// different credential — and .NET ADDS container cookies to a request
|
|
17
|
+
// that already carries a Cookie header, which an `apiKey in: cookie`
|
|
18
|
+
// scheme does. Pooling is the handler's own and is unaffected.
|
|
19
|
+
private static HttpClientHandler CookielessHandler(bool allowRedirect) =>
|
|
20
|
+
new HttpClientHandler
|
|
21
|
+
{
|
|
22
|
+
AllowAutoRedirect = allowRedirect,
|
|
23
|
+
UseCookies = false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
private static readonly HttpClient DefaultHttpClient = new(CookielessHandler(true));
|
|
15
27
|
|
|
16
28
|
// Non-following twin of DefaultHttpClient. The station feature's
|
|
17
29
|
// middleware sets `redirect: manual` on the fetch definition under a
|
|
@@ -20,11 +32,8 @@ public static partial class SdkUtility
|
|
|
20
32
|
// to a Location no policy approved (the ts donor's fetch honours the
|
|
21
33
|
// same key natively; redirect policy is per-handler in .NET, hence a
|
|
22
34
|
// second client).
|
|
23
|
-
private static readonly HttpClient ManualRedirectHttpClient =
|
|
24
|
-
new
|
|
25
|
-
{
|
|
26
|
-
AllowAutoRedirect = false,
|
|
27
|
-
});
|
|
35
|
+
private static readonly HttpClient ManualRedirectHttpClient =
|
|
36
|
+
new(CookielessHandler(false));
|
|
28
37
|
|
|
29
38
|
// Proxy-routed clients, cached per proxy URL and redirect policy (see
|
|
30
39
|
// the proxy feature's fetchdef annotation).
|
|
@@ -43,12 +52,10 @@ public static partial class SdkUtility
|
|
|
43
52
|
var key = (manual ? "manual|" : "auto|") + proxy;
|
|
44
53
|
if (!ProxyClients.TryGetValue(key, out var client))
|
|
45
54
|
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
AllowAutoRedirect = !manual,
|
|
51
|
-
});
|
|
55
|
+
var handler = CookielessHandler(!manual);
|
|
56
|
+
handler.Proxy = new System.Net.WebProxy(proxy);
|
|
57
|
+
handler.UseProxy = true;
|
|
58
|
+
client = new HttpClient(handler);
|
|
52
59
|
ProxyClients[key] = client;
|
|
53
60
|
}
|
|
54
61
|
return client;
|
|
@@ -24,7 +24,7 @@ public static partial class SdkUtility
|
|
|
24
24
|
{
|
|
25
25
|
var key = item[0] as string ?? "";
|
|
26
26
|
var val = item[1];
|
|
27
|
-
if (val != null && !ContainsStr(paramnames, key))
|
|
27
|
+
if (val != null && "$action" != key && !ContainsStr(paramnames, key))
|
|
28
28
|
{
|
|
29
29
|
query[key] = val;
|
|
30
30
|
}
|
|
@@ -20,13 +20,13 @@ public static partial class SdkUtility
|
|
|
20
20
|
var transform = Helpers.ToMapAny(StructUtils.GetProp(point, "transform"));
|
|
21
21
|
if (transform == null)
|
|
22
22
|
{
|
|
23
|
-
return ctx.Reqdata;
|
|
23
|
+
return StripAction(ctx.Reqdata);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
var reqform = StructUtils.GetProp(transform, "req");
|
|
27
27
|
if (reqform == null)
|
|
28
28
|
{
|
|
29
|
-
return ctx.Reqdata;
|
|
29
|
+
return StripAction(ctx.Reqdata);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
var reqdata = StructUtils.Transform(new Dictionary<string, object?>
|
|
@@ -34,6 +34,26 @@ public static partial class SdkUtility
|
|
|
34
34
|
["reqdata"] = ctx.Reqdata,
|
|
35
35
|
}, reqform);
|
|
36
36
|
|
|
37
|
-
return reqdata;
|
|
37
|
+
return StripAction(reqdata);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// `$action` selects the point (see MakePointUtil); it is never an API
|
|
41
|
+
// field, so the body is a copy without it. The caller's map is left
|
|
42
|
+
// untouched.
|
|
43
|
+
private static object? StripAction(object? reqdata)
|
|
44
|
+
{
|
|
45
|
+
if (reqdata is not IDictionary<string, object?> src || !src.ContainsKey("$action"))
|
|
46
|
+
{
|
|
47
|
+
return reqdata;
|
|
48
|
+
}
|
|
49
|
+
var body = new Dictionary<string, object?>();
|
|
50
|
+
foreach (var kv in src)
|
|
51
|
+
{
|
|
52
|
+
if ("$action" != kv.Key)
|
|
53
|
+
{
|
|
54
|
+
body[kv.Key] = kv.Value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return body;
|
|
38
58
|
}
|
|
39
59
|
}
|
|
@@ -57,7 +57,7 @@ defmodule ProjectName.Context do
|
|
|
57
57
|
if S.ismap(pg), do: S.setprop(ctrl, "paging", pg)
|
|
58
58
|
S.setprop(ctx, "ctrl", ctrl)
|
|
59
59
|
|
|
60
|
-
basectx != nil and bp.("ctrl") != nil ->
|
|
60
|
+
basectx != nil and bp.("ctrl") != nil and gp.("opname") == nil ->
|
|
61
61
|
S.setprop(ctx, "ctrl", bp.("ctrl"))
|
|
62
62
|
|
|
63
63
|
true ->
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
# stamps page/limit (or a cursor) into the request query; on the way back
|
|
5
5
|
# (PreResult) it reads the server's pagination signals — a `Link: rel="next"`
|
|
6
6
|
# header, `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
|
|
7
|
-
# `next`/`cursor`/`nextCursor`/`hasMore` fields in the body
|
|
8
|
-
# on `ctx.result.paging
|
|
7
|
+
# `next`/`cursor`/`nextCursor`/`hasMore` fields in the body (snake_case forms
|
|
8
|
+
# too) — and records them on `ctx.result.paging` and the caller's ctrl paging. A per-call cursor/page supplied via ctrl paging
|
|
9
9
|
# takes priority (used by auto-iteration). Parameter names
|
|
10
10
|
# (`pageParam`/`limitParam`/`cursorParam`), `startPage` and page size
|
|
11
11
|
# (`limit`) are configurable.
|
|
@@ -67,7 +67,10 @@ defmodule ProjectName.Feature.Paging do
|
|
|
67
67
|
S.setprop(query, cursor_param, S.getprop(paging, "cursor"))
|
|
68
68
|
|
|
69
69
|
S.getprop(query, page_param) == nil ->
|
|
70
|
-
|
|
70
|
+
# A record written back by pre_result holds the page just fetched
|
|
71
|
+
# as "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
72
|
+
page = S.getprop(paging, "nextPage")
|
|
73
|
+
page = if page == nil, do: S.getprop(paging, "page"), else: page
|
|
71
74
|
page = if page == nil, do: por(S.getprop(opts, "startPage"), 1), else: page
|
|
72
75
|
S.setprop(query, page_param, page)
|
|
73
76
|
|
|
@@ -182,18 +185,16 @@ defmodule ProjectName.Feature.Paging do
|
|
|
182
185
|
bnext -> por(next_from_link, bnext)
|
|
183
186
|
end
|
|
184
187
|
|
|
188
|
+
# snake_case forms rank below the camelCase ones.
|
|
185
189
|
c =
|
|
186
190
|
cond do
|
|
187
191
|
S.getprop(body, "nextCursor") != nil -> S.getprop(body, "nextCursor")
|
|
188
192
|
S.getprop(body, "cursor") != nil -> S.getprop(body, "cursor")
|
|
193
|
+
S.getprop(body, "next_cursor") != nil -> S.getprop(body, "next_cursor")
|
|
189
194
|
true -> nil
|
|
190
195
|
end
|
|
191
196
|
|
|
192
|
-
hm =
|
|
193
|
-
case S.getprop(body, "hasMore") do
|
|
194
|
-
v when is_boolean(v) -> v
|
|
195
|
-
_ -> false
|
|
196
|
-
end
|
|
197
|
+
{hm, _} = body_more(body)
|
|
197
198
|
|
|
198
199
|
{n, c, hm}
|
|
199
200
|
else
|
|
@@ -239,10 +240,19 @@ defmodule ProjectName.Feature.Paging do
|
|
|
239
240
|
# No usable relay `more` path, but a top-level body hasMore is
|
|
240
241
|
# still an outright statement — keep it explicit, or cursor
|
|
241
242
|
# inference below would flip an explicit false to true.
|
|
242
|
-
{c, has_more,
|
|
243
|
+
{c, has_more, elem(body_more(body), 1)}
|
|
243
244
|
end
|
|
244
245
|
else
|
|
245
|
-
{cursor_val, has_more,
|
|
246
|
+
{cursor_val, has_more, elem(body_more(body), 1)}
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
next_page =
|
|
250
|
+
if next_page == nil and S.ismap(body) do
|
|
251
|
+
np = S.getprop(body, "nextPage")
|
|
252
|
+
np = if np == nil, do: S.getprop(body, "next_page"), else: np
|
|
253
|
+
if is_number(np) or is_binary(np), do: np, else: nil
|
|
254
|
+
else
|
|
255
|
+
next_page
|
|
246
256
|
end
|
|
247
257
|
|
|
248
258
|
# Cursor presence only INFERS another page. When the server stated the
|
|
@@ -268,6 +278,7 @@ defmodule ProjectName.Feature.Paging do
|
|
|
268
278
|
])
|
|
269
279
|
|
|
270
280
|
S.setprop(result, "paging", paging)
|
|
281
|
+
write_back(S.getprop(ctx, "ctrl"), paging)
|
|
271
282
|
S.setprop(S.getprop(f, "client"), "_paging", S.jm(["last", paging]))
|
|
272
283
|
end
|
|
273
284
|
end
|
|
@@ -275,6 +286,37 @@ defmodule ProjectName.Feature.Paging do
|
|
|
275
286
|
nil
|
|
276
287
|
end
|
|
277
288
|
|
|
289
|
+
# The Context shares the caller's paging map by reference, so refill it in
|
|
290
|
+
# place; a fresh map on ctrl never reaches the caller.
|
|
291
|
+
defp write_back(ctrl, paging) do
|
|
292
|
+
shared = if ctrl == nil, do: nil, else: S.getprop(ctrl, "paging")
|
|
293
|
+
|
|
294
|
+
cond do
|
|
295
|
+
ctrl == nil ->
|
|
296
|
+
nil
|
|
297
|
+
|
|
298
|
+
S.ismap(shared) ->
|
|
299
|
+
Enum.each(S.keysof(shared), fn k -> S.delprop(shared, k) end)
|
|
300
|
+
Enum.each(S.keysof(paging), fn k -> S.setprop(shared, k, S.getprop(paging, k)) end)
|
|
301
|
+
|
|
302
|
+
true ->
|
|
303
|
+
S.setprop(ctrl, "paging", paging)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
nil
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Body `hasMore`, or `has_more` when the camelCase form is absent: {value,
|
|
310
|
+
# stated outright}.
|
|
311
|
+
defp body_more(body) do
|
|
312
|
+
cond do
|
|
313
|
+
not S.ismap(body) -> {false, false}
|
|
314
|
+
is_boolean(S.getprop(body, "hasMore")) -> {S.getprop(body, "hasMore"), true}
|
|
315
|
+
is_boolean(S.getprop(body, "has_more")) -> {S.getprop(body, "has_more"), true}
|
|
316
|
+
true -> {false, false}
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
278
320
|
defp is_list_op(f, ctx) do
|
|
279
321
|
ops = opt_list(S.getprop(F.opts(f), "ops"), ["list"])
|
|
280
322
|
op = S.getprop(ctx, "op")
|
|
@@ -167,20 +167,11 @@ defmodule ProjectName.Feature.Test do
|
|
|
167
167
|
found = S.select(entmap, args)
|
|
168
168
|
ent = S.getelem(found, 0)
|
|
169
169
|
|
|
170
|
-
ent =
|
|
171
|
-
if ent == nil and S.ismap(entmap) do
|
|
172
|
-
ks = S.keysof(entmap)
|
|
173
|
-
Enum.find_value(ks, fn k -> v = S.getprop(entmap, k); if S.ismap(v), do: v end)
|
|
174
|
-
else
|
|
175
|
-
ent
|
|
176
|
-
end
|
|
177
|
-
|
|
178
170
|
if ent == nil do
|
|
171
|
+
# update miss: 404, never another record
|
|
179
172
|
respond(fctx, 404, nil, S.jm(["statusText", "Not found"]))
|
|
180
173
|
else
|
|
181
|
-
if S.ismap(ent) and reqdata
|
|
182
|
-
Enum.each(H.entries(reqdata), fn {k, v} -> S.setprop(ent, k, v) end)
|
|
183
|
-
end
|
|
174
|
+
if S.ismap(ent) and S.ismap(reqdata), do: S.merge(S.jt([ent, reqdata]))
|
|
184
175
|
|
|
185
176
|
S.delprop(ent, "$KEY")
|
|
186
177
|
respond(fctx, 200, S.clone(ent))
|
|
@@ -222,6 +213,45 @@ defmodule ProjectName.Feature.Test do
|
|
|
222
213
|
:crypto.strong_rand_bytes(8) |> Base.encode16(case: :lower)
|
|
223
214
|
end
|
|
224
215
|
|
|
216
|
+
defp point_terminal?(p) do
|
|
217
|
+
parts = S.getprop(p, "parts")
|
|
218
|
+
n = if S.islist(parts), do: S.size(parts), else: 0
|
|
219
|
+
last = if n > 0, do: S.getelem(parts, n - 1), else: nil
|
|
220
|
+
is_binary(last) and String.starts_with?(last, "{")
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
defp point_depth(p) do
|
|
224
|
+
parts = S.getprop(p, "parts")
|
|
225
|
+
if S.islist(parts), do: S.size(parts), else: 0
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# The entity's own endpoint: a terminal `{param}` marks a record route,
|
|
229
|
+
# and among equals the shallower path wins (the same rule as make_point).
|
|
230
|
+
defp pick_point(points) do
|
|
231
|
+
n = if S.islist(points), do: S.size(points), else: 0
|
|
232
|
+
|
|
233
|
+
if n == 0 do
|
|
234
|
+
nil
|
|
235
|
+
else
|
|
236
|
+
rest = if n > 1, do: Enum.to_list(1..(n - 1)), else: []
|
|
237
|
+
|
|
238
|
+
Enum.reduce(rest, S.getelem(points, 0), fn i, point ->
|
|
239
|
+
cand = S.getelem(points, i)
|
|
240
|
+
|
|
241
|
+
cond do
|
|
242
|
+
point_terminal?(cand) != point_terminal?(point) -> if point_terminal?(cand), do: cand, else: point
|
|
243
|
+
point_depth(cand) < point_depth(point) -> cand
|
|
244
|
+
true -> point
|
|
245
|
+
end
|
|
246
|
+
end)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
defp reqd_names(point, kind) do
|
|
251
|
+
reqd_args = S.select(S.getpath(point, "args." <> kind), S.jm(["reqd", true]))
|
|
252
|
+
S.transform(reqd_args, S.jt(["`$EACH`", "", "`$KEY.name`"]))
|
|
253
|
+
end
|
|
254
|
+
|
|
225
255
|
# Build a struct select query ($AND of $OR alternatives) from the required
|
|
226
256
|
# params + id, resolving each value through the pipeline param resolver.
|
|
227
257
|
def build_args(_f, ctx, op, args) do
|
|
@@ -229,10 +259,11 @@ defmodule ProjectName.Feature.Test do
|
|
|
229
259
|
config = S.getprop(ctx, "config")
|
|
230
260
|
entname = Context.entity_name(S.getprop(ctx, "entity"))
|
|
231
261
|
points = S.getpath(config, "entity." <> entname <> ".op." <> opname <> ".points")
|
|
232
|
-
point =
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
262
|
+
point = pick_point(points)
|
|
263
|
+
# Path AND query: a path-only read misses a query-addressed record
|
|
264
|
+
# (e.g. GET /result?trace_id=), which has no path param at all.
|
|
265
|
+
reqd_params = reqd_names(point, "params")
|
|
266
|
+
reqd_query = reqd_names(point, "query")
|
|
236
267
|
|
|
237
268
|
qand = S.jt([])
|
|
238
269
|
q = S.jm(["`$AND`", qand])
|
|
@@ -240,8 +271,7 @@ defmodule ProjectName.Feature.Test do
|
|
|
240
271
|
if args != nil do
|
|
241
272
|
Enum.each(S.keysof(args), fn key ->
|
|
242
273
|
is_id = key == "id"
|
|
243
|
-
|
|
244
|
-
is_reqd = not S.isempty(selected)
|
|
274
|
+
is_reqd = not S.isempty(S.select(reqd_params, key)) or not S.isempty(S.select(reqd_query, key))
|
|
245
275
|
|
|
246
276
|
if is_id or is_reqd do
|
|
247
277
|
v = Utility.param(ctx, key)
|
|
@@ -1209,7 +1209,8 @@ defmodule ProjectName.Utility do
|
|
|
1209
1209
|
out = S.jm([])
|
|
1210
1210
|
|
|
1211
1211
|
Enum.each(H.entries(reqmatch), fn {key, val} ->
|
|
1212
|
-
if val != nil and is_binary(key) and
|
|
1212
|
+
if val != nil and is_binary(key) and key != "$action" and
|
|
1213
|
+
not Enum.member?(param_strs, key) do
|
|
1213
1214
|
S.setprop(out, key, val)
|
|
1214
1215
|
end
|
|
1215
1216
|
end)
|
|
@@ -1446,6 +1447,23 @@ defmodule ProjectName.Utility do
|
|
|
1446
1447
|
|
|
1447
1448
|
# ---- transform_* ---------------------------------------------------------
|
|
1448
1449
|
|
|
1450
|
+
# `$action` selects the point (see make_point_impl); it is never an API
|
|
1451
|
+
# field, so the body is a copy without it. The caller's map is left
|
|
1452
|
+
# untouched.
|
|
1453
|
+
defp strip_action(reqdata) do
|
|
1454
|
+
if S.ismap(reqdata) and S.haskey(reqdata, "$action") do
|
|
1455
|
+
body = S.jm([])
|
|
1456
|
+
|
|
1457
|
+
Enum.each(H.entries(reqdata), fn {key, val} ->
|
|
1458
|
+
if key != "$action", do: S.setprop(body, key, val)
|
|
1459
|
+
end)
|
|
1460
|
+
|
|
1461
|
+
body
|
|
1462
|
+
else
|
|
1463
|
+
reqdata
|
|
1464
|
+
end
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1449
1467
|
def transform_request_impl(ctx) do
|
|
1450
1468
|
spec = S.getprop(ctx, "spec")
|
|
1451
1469
|
point = S.getprop(ctx, "point")
|
|
@@ -1453,17 +1471,20 @@ defmodule ProjectName.Utility do
|
|
|
1453
1471
|
|
|
1454
1472
|
transform = H.to_map(S.getprop(point, "transform"))
|
|
1455
1473
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
else
|
|
1459
|
-
reqform = S.getprop(transform, "req")
|
|
1460
|
-
|
|
1461
|
-
if reqform == nil do
|
|
1474
|
+
reqdata =
|
|
1475
|
+
if transform == nil do
|
|
1462
1476
|
S.getprop(ctx, "reqdata")
|
|
1463
1477
|
else
|
|
1464
|
-
|
|
1478
|
+
reqform = S.getprop(transform, "req")
|
|
1479
|
+
|
|
1480
|
+
if reqform == nil do
|
|
1481
|
+
S.getprop(ctx, "reqdata")
|
|
1482
|
+
else
|
|
1483
|
+
S.transform(S.jm(["reqdata", S.getprop(ctx, "reqdata")]), reqform)
|
|
1484
|
+
end
|
|
1465
1485
|
end
|
|
1466
|
-
|
|
1486
|
+
|
|
1487
|
+
strip_action(reqdata)
|
|
1467
1488
|
end
|
|
1468
1489
|
|
|
1469
1490
|
def transform_response_impl(ctx) do
|
|
@@ -9,18 +9,18 @@ import (
|
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
type Context struct {
|
|
12
|
-
Id
|
|
13
|
-
Out
|
|
14
|
-
Ctrl
|
|
15
|
-
Meta
|
|
16
|
-
Client
|
|
17
|
-
Utility
|
|
18
|
-
Op
|
|
19
|
-
Point
|
|
20
|
-
Config
|
|
21
|
-
Entopts
|
|
22
|
-
Options
|
|
23
|
-
Opmap
|
|
12
|
+
Id string
|
|
13
|
+
Out map[string]any
|
|
14
|
+
Ctrl *Control
|
|
15
|
+
Meta map[string]any
|
|
16
|
+
Client *ProjectNameSDK
|
|
17
|
+
Utility *Utility
|
|
18
|
+
Op *Operation
|
|
19
|
+
Point map[string]any
|
|
20
|
+
Config map[string]any
|
|
21
|
+
Entopts map[string]any
|
|
22
|
+
Options map[string]any
|
|
23
|
+
Opmap map[string]*Operation
|
|
24
24
|
Opmu *sync.Mutex
|
|
25
25
|
Response *Response
|
|
26
26
|
Result *Result
|
|
@@ -86,7 +86,7 @@ func NewContext(ctxmap map[string]any, basectx *Context) *Context {
|
|
|
86
86
|
} else if ctrl, ok := c.(*Control); ok {
|
|
87
87
|
ctx.Ctrl = ctrl
|
|
88
88
|
}
|
|
89
|
-
} else if basectx != nil && basectx.Ctrl != nil {
|
|
89
|
+
} else if basectx != nil && basectx.Ctrl != nil && getCtxProp(ctxmap, "opname") == nil {
|
|
90
90
|
ctx.Ctrl = basectx.Ctrl
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -68,7 +68,11 @@ func (f *PagingFeature) PreRequest(ctx *core.Context) {
|
|
|
68
68
|
if cursor, has := paging["cursor"]; has && cursor != nil {
|
|
69
69
|
spec.Query[cursorParam] = cursor
|
|
70
70
|
} else if spec.Query[pageParam] == nil {
|
|
71
|
-
|
|
71
|
+
// A record written back by PreResult holds the page just fetched as
|
|
72
|
+
// "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
73
|
+
if next, has := paging["nextPage"]; has && next != nil {
|
|
74
|
+
spec.Query[pageParam] = next
|
|
75
|
+
} else if page, has := paging["page"]; has && page != nil {
|
|
72
76
|
spec.Query[pageParam] = page
|
|
73
77
|
} else {
|
|
74
78
|
spec.Query[pageParam] = foptInt(f.options, "startPage", 1)
|
|
@@ -178,17 +182,36 @@ func (f *PagingFeature) PreResult(ctx *core.Context) {
|
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
// Body-level
|
|
185
|
+
// Body-level signals; snake_case forms are read first so camelCase wins.
|
|
182
186
|
if bm, ok := body.(map[string]any); ok {
|
|
183
187
|
if bm["next"] != nil && paging["next"] == nil {
|
|
184
188
|
paging["next"] = bm["next"]
|
|
185
189
|
}
|
|
190
|
+
if bm["next_cursor"] != nil {
|
|
191
|
+
paging["cursor"] = bm["next_cursor"]
|
|
192
|
+
}
|
|
186
193
|
if bm["cursor"] != nil {
|
|
187
194
|
paging["cursor"] = bm["cursor"]
|
|
188
195
|
}
|
|
189
196
|
if bm["nextCursor"] != nil {
|
|
190
197
|
paging["cursor"] = bm["nextCursor"]
|
|
191
198
|
}
|
|
199
|
+
if paging["nextPage"] == nil {
|
|
200
|
+
np := bm["nextPage"]
|
|
201
|
+
if np == nil {
|
|
202
|
+
np = bm["next_page"]
|
|
203
|
+
}
|
|
204
|
+
switch n := np.(type) {
|
|
205
|
+
case float64:
|
|
206
|
+
paging["nextPage"] = int(n)
|
|
207
|
+
case int, string:
|
|
208
|
+
paging["nextPage"] = n
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if hasMore, ok := bm["has_more"].(bool); ok {
|
|
212
|
+
paging["hasMore"] = hasMore
|
|
213
|
+
explicitMore = true
|
|
214
|
+
}
|
|
192
215
|
if hasMore, ok := bm["hasMore"].(bool); ok {
|
|
193
216
|
paging["hasMore"] = hasMore
|
|
194
217
|
explicitMore = true
|
|
@@ -207,6 +230,21 @@ func (f *PagingFeature) PreResult(ctx *core.Context) {
|
|
|
207
230
|
|
|
208
231
|
result.Paging = paging
|
|
209
232
|
f.Last = paging
|
|
233
|
+
|
|
234
|
+
if ctx.Ctrl != nil {
|
|
235
|
+
// The Context shares the caller's paging map by reference, so refill
|
|
236
|
+
// it in place; a fresh map on Ctrl would never reach the caller.
|
|
237
|
+
if ctx.Ctrl.Paging != nil {
|
|
238
|
+
for k := range ctx.Ctrl.Paging {
|
|
239
|
+
delete(ctx.Ctrl.Paging, k)
|
|
240
|
+
}
|
|
241
|
+
for k, v := range paging {
|
|
242
|
+
ctx.Ctrl.Paging[k] = v
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
ctx.Ctrl.Paging = paging
|
|
246
|
+
}
|
|
247
|
+
}
|
|
210
248
|
}
|
|
211
249
|
|
|
212
250
|
func (f *PagingFeature) isList(ctx *core.Context) bool {
|