@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
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// query; on the way back (PreResult) it reads the server's pagination
|
|
4
4
|
// signals — a `Link: rel="next"` header, `X-Page`/`X-Next-Page`/
|
|
5
5
|
// `X-Total-Count` headers, or `next`/`cursor`/`nextCursor`/`hasMore` fields
|
|
6
|
-
// in the body — and records them on `result.paging
|
|
6
|
+
// in the body (snake_case forms too) — and records them on `result.paging`
|
|
7
|
+
// and the caller's ctrl paging. A per-call cursor/page
|
|
7
8
|
// from ctrl takes priority (used by auto-iteration). Parameter names
|
|
8
9
|
// (`pageParam`, `limitParam`, `cursorParam`), the page size (`limit`) and
|
|
9
10
|
// the start page (`startPage`, default 1) are configurable.
|
|
@@ -225,7 +226,10 @@ static void paging_pre_request(PagingFeature* pf, Context* ctx) {
|
|
|
225
226
|
if (!v_is_noval(cursor) && !v_is_null(cursor)) {
|
|
226
227
|
setp(query, cursor_param, cursor);
|
|
227
228
|
} else if (v_is_noval(getp(query, page_param))) {
|
|
228
|
-
|
|
229
|
+
// A record written back by pre_result holds the page just fetched as
|
|
230
|
+
// "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
231
|
+
voxgig_value* page = getp(paging, "nextPage");
|
|
232
|
+
if (v_is_noval(page) || v_is_null(page)) page = getp(paging, "page");
|
|
229
233
|
if (!v_is_noval(page) && !v_is_null(page)) {
|
|
230
234
|
setp(query, page_param, page);
|
|
231
235
|
} else {
|
|
@@ -297,12 +301,16 @@ static void paging_pre_result(PagingFeature* pf, Context* ctx) {
|
|
|
297
301
|
}
|
|
298
302
|
}
|
|
299
303
|
|
|
300
|
-
// Body-level
|
|
304
|
+
// Body-level signals; snake_case forms are read first so camelCase wins.
|
|
301
305
|
if (voxgig_is_map(body)) {
|
|
302
306
|
voxgig_value* next = getp(body, "next");
|
|
303
307
|
if (!v_is_noval(next) && !v_is_null(next) && v_is_noval(getp(paging, "next"))) {
|
|
304
308
|
setp(paging, "next", next);
|
|
305
309
|
}
|
|
310
|
+
voxgig_value* snake_cursor = getp(body, "next_cursor");
|
|
311
|
+
if (!v_is_noval(snake_cursor) && !v_is_null(snake_cursor)) {
|
|
312
|
+
setp(paging, "cursor", snake_cursor);
|
|
313
|
+
}
|
|
306
314
|
voxgig_value* cursor = getp(body, "cursor");
|
|
307
315
|
if (!v_is_noval(cursor) && !v_is_null(cursor)) {
|
|
308
316
|
setp(paging, "cursor", cursor);
|
|
@@ -311,7 +319,18 @@ static void paging_pre_result(PagingFeature* pf, Context* ctx) {
|
|
|
311
319
|
if (!v_is_noval(next_cursor) && !v_is_null(next_cursor)) {
|
|
312
320
|
setp(paging, "cursor", next_cursor);
|
|
313
321
|
}
|
|
322
|
+
if (v_is_noval(getp(paging, "nextPage"))) {
|
|
323
|
+
voxgig_value* np = getp(body, "nextPage");
|
|
324
|
+
if (v_is_noval(np) || v_is_null(np)) np = getp(body, "next_page");
|
|
325
|
+
if (voxgig_is_number(np) || voxgig_is_string(np)) {
|
|
326
|
+
setp(paging, "nextPage", np);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
314
329
|
bool has_more;
|
|
330
|
+
if (get_bool(body, "has_more", &has_more)) {
|
|
331
|
+
setp(paging, "hasMore", v_bool(has_more));
|
|
332
|
+
explicit_more = true;
|
|
333
|
+
}
|
|
315
334
|
if (get_bool(body, "hasMore", &has_more)) {
|
|
316
335
|
setp(paging, "hasMore", v_bool(has_more));
|
|
317
336
|
explicit_more = true;
|
|
@@ -334,6 +353,21 @@ static void paging_pre_result(PagingFeature* pf, Context* ctx) {
|
|
|
334
353
|
|
|
335
354
|
ctx->result->paging = paging;
|
|
336
355
|
pf->last = paging;
|
|
356
|
+
|
|
357
|
+
if (ctx->ctrl) {
|
|
358
|
+
// The Context shares the caller's paging map by pointer, so refill it in
|
|
359
|
+
// place; a fresh map on ctrl never reaches the caller.
|
|
360
|
+
if (voxgig_is_map(ctx->ctrl->paging)) {
|
|
361
|
+
voxgig_map* dst = voxgig_as_map(ctx->ctrl->paging);
|
|
362
|
+
voxgig_map* src = voxgig_as_map(paging);
|
|
363
|
+
voxgig_map_clear(dst);
|
|
364
|
+
for (size_t i = 0; i < voxgig_map_len(src); i++) {
|
|
365
|
+
voxgig_map_set(dst, voxgig_map_key_at(src, i), v_share(voxgig_map_val_at(src, i)));
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
ctx->ctrl->paging = paging;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
337
371
|
}
|
|
338
372
|
|
|
339
373
|
static void paging_hook(Feature* f, const char* name, Context* ctx) {
|
|
@@ -92,23 +92,61 @@ static const char* test_entity_name(Context* ctx) {
|
|
|
92
92
|
return ctx->op->entity;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
static bool point_terminal(voxgig_value* p) {
|
|
96
|
+
voxgig_value* parts = getp(p, "parts");
|
|
97
|
+
if (!voxgig_is_list(parts)) return false;
|
|
98
|
+
voxgig_list* l = voxgig_as_list(parts);
|
|
99
|
+
if (0 == l->len) return false;
|
|
100
|
+
voxgig_value* last = l->items[l->len - 1];
|
|
101
|
+
return voxgig_is_string(last) && '{' == voxgig_as_string(last)[0];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static size_t point_depth(voxgig_value* p) {
|
|
105
|
+
voxgig_value* parts = getp(p, "parts");
|
|
106
|
+
return voxgig_is_list(parts) ? voxgig_as_list(parts)->len : 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// The entity's own endpoint: a terminal `{param}` marks a record route, and
|
|
110
|
+
// among equals the shallower path wins (the same rule as make_point).
|
|
111
|
+
static voxgig_value* pick_point(voxgig_value* points) {
|
|
112
|
+
if (!voxgig_is_list(points)) return NULL;
|
|
113
|
+
voxgig_list* l = voxgig_as_list(points);
|
|
114
|
+
if (0 == l->len) return NULL;
|
|
115
|
+
voxgig_value* point = l->items[0];
|
|
116
|
+
for (size_t i = 1; i < l->len; i++) {
|
|
117
|
+
voxgig_value* cand = l->items[i];
|
|
118
|
+
if (point_terminal(cand) != point_terminal(point)) {
|
|
119
|
+
if (point_terminal(cand)) point = cand;
|
|
120
|
+
} else if (point_depth(cand) < point_depth(point)) {
|
|
121
|
+
point = cand;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return point;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static voxgig_value* reqd_names(voxgig_value* point, const char* kind) {
|
|
128
|
+
voxgig_value* args_path = getpath2(point, "args", kind);
|
|
129
|
+
voxgig_value* reqd_args = voxgig_select(args_path, cmap(1, "reqd", v_bool(true)));
|
|
130
|
+
voxgig_value* reqd_spec =
|
|
131
|
+
clist(3, v_str("`$EACH`"), v_str(""), v_str("`$KEY.name`"));
|
|
132
|
+
voxgig_value* reqd = voxgig_transform(reqd_args, reqd_spec, NULL);
|
|
133
|
+
if (!voxgig_is_list(reqd)) reqd = v_list();
|
|
134
|
+
return reqd;
|
|
135
|
+
}
|
|
136
|
+
|
|
95
137
|
static voxgig_value* build_args(Context* ctx, voxgig_value* args) {
|
|
96
138
|
Operation* op = ctx->op;
|
|
97
139
|
const char* opname = op->name;
|
|
98
140
|
const char* entname = test_entity_name(ctx);
|
|
99
141
|
|
|
100
|
-
// Get last point from config.
|
|
101
142
|
const char* ppath[6] = {"entity", entname, "op", opname, "points", NULL};
|
|
102
143
|
voxgig_value* points = getpath_c(ctx->config, ppath);
|
|
103
|
-
voxgig_value* point =
|
|
144
|
+
voxgig_value* point = pick_point(points);
|
|
104
145
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
voxgig_value* reqd_params =
|
|
108
|
-
voxgig_value*
|
|
109
|
-
clist(3, v_str("`$EACH`"), v_str(""), v_str("`$KEY.name`"));
|
|
110
|
-
voxgig_value* reqd = voxgig_transform(reqd_params, reqd_spec, NULL);
|
|
111
|
-
if (!voxgig_is_list(reqd)) reqd = v_list();
|
|
146
|
+
// Path AND query: a path-only read misses a query-addressed record
|
|
147
|
+
// (e.g. GET /result?trace_id=), which has no path param at all.
|
|
148
|
+
voxgig_value* reqd_params = reqd_names(point, "params");
|
|
149
|
+
voxgig_value* reqd_query = reqd_names(point, "query");
|
|
112
150
|
|
|
113
151
|
voxgig_value* qand = v_list();
|
|
114
152
|
voxgig_value* q = cmap(1, "`$AND`", qand);
|
|
@@ -118,8 +156,8 @@ static voxgig_value* build_args(Context* ctx, voxgig_value* args) {
|
|
|
118
156
|
for (size_t ki = 0; ki < keys.len; ki++) {
|
|
119
157
|
const char* key = keys.data[ki];
|
|
120
158
|
bool is_id = strcmp(key, "id") == 0;
|
|
121
|
-
|
|
122
|
-
|
|
159
|
+
bool is_reqd = !voxgig_isempty(voxgig_select(reqd_params, v_str(key))) ||
|
|
160
|
+
!voxgig_isempty(voxgig_select(reqd_query, v_str(key)));
|
|
123
161
|
|
|
124
162
|
if (is_id || is_reqd) {
|
|
125
163
|
voxgig_value* v = param_util(ctx, v_str(key));
|
|
@@ -209,28 +247,14 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
|
|
|
209
247
|
voxgig_value* args = build_args(ctx, update_match);
|
|
210
248
|
voxgig_value* found = voxgig_select(entmap, args);
|
|
211
249
|
voxgig_value* ent = voxgig_getelem(found, v_int(0), NULL);
|
|
212
|
-
if ((v_is_noval(ent) || v_is_null(ent)) && voxgig_size(entmap) > 0) {
|
|
213
|
-
// Fall back to any entity in the fixture.
|
|
214
|
-
voxgig_value* items = voxgig_items_v(entmap);
|
|
215
|
-
voxgig_list* il = voxgig_as_list(items);
|
|
216
|
-
for (size_t i = 0; i < il->len; i++) {
|
|
217
|
-
voxgig_value* e = voxgig_getelem(il->items[i], v_int(1), NULL);
|
|
218
|
-
if (voxgig_is_map(e)) {
|
|
219
|
-
ent = e;
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
250
|
if (v_is_noval(ent) || v_is_null(ent)) {
|
|
251
|
+
// update miss: 404, never another record
|
|
225
252
|
voxgig_value* r = respond(ctx, 404, v_undef());
|
|
226
253
|
setp(r, "statusText", v_str("Not found"));
|
|
227
254
|
return r;
|
|
228
255
|
}
|
|
229
256
|
if (voxgig_is_map(ent) && voxgig_is_map(reqdata)) {
|
|
230
|
-
|
|
231
|
-
for (size_t i = 0; i < rm->len; i++) {
|
|
232
|
-
setp(ent, rm->entries[i].key, rm->entries[i].value);
|
|
233
|
-
}
|
|
257
|
+
ent = voxgig_merge(clist(2, v_share(ent), v_share(reqdata)), VOXGIG_MAXDEPTH);
|
|
234
258
|
}
|
|
235
259
|
voxgig_delprop(ent, v_str("$KEY"));
|
|
236
260
|
return respond(ctx, 200, voxgig_clone(ent));
|
|
@@ -28,7 +28,8 @@ voxgig_value* prepare_query_util(Context* ctx) {
|
|
|
28
28
|
for (size_t i = 0; i < rm->len; i++) {
|
|
29
29
|
const char* key = rm->entries[i].key;
|
|
30
30
|
voxgig_value* val = rm->entries[i].value;
|
|
31
|
-
if (!v_is_noval(val) && !v_is_null(val) &&
|
|
31
|
+
if (!v_is_noval(val) && !v_is_null(val) && strcmp(key, "$action") != 0 &&
|
|
32
|
+
!params_contains(params, key)) {
|
|
32
33
|
setp(out, key, v_share(val));
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
#include "sdk.h"
|
|
4
4
|
|
|
5
|
+
#include <string.h>
|
|
6
|
+
|
|
7
|
+
/* `$action` selects the point (see make_point_util); it is never an API
|
|
8
|
+
field, so the body is a copy without it. The caller's map is left
|
|
9
|
+
untouched. */
|
|
10
|
+
static voxgig_value* strip_action(voxgig_value* reqdata) {
|
|
11
|
+
if (!voxgig_is_map(reqdata)) return reqdata;
|
|
12
|
+
voxgig_map* rm = voxgig_as_map(reqdata);
|
|
13
|
+
bool has = false;
|
|
14
|
+
for (size_t i = 0; i < rm->len; i++) {
|
|
15
|
+
if (strcmp(rm->entries[i].key, "$action") == 0) { has = true; break; }
|
|
16
|
+
}
|
|
17
|
+
if (!has) return reqdata;
|
|
18
|
+
voxgig_value* body = voxgig_new_map();
|
|
19
|
+
for (size_t i = 0; i < rm->len; i++) {
|
|
20
|
+
if (strcmp(rm->entries[i].key, "$action") != 0) {
|
|
21
|
+
setp(body, rm->entries[i].key, v_share(rm->entries[i].value));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return body;
|
|
25
|
+
}
|
|
26
|
+
|
|
5
27
|
voxgig_value* transform_request_util(Context* ctx) {
|
|
6
28
|
Spec* spec = ctx->spec;
|
|
7
29
|
voxgig_value* point = ctx->point;
|
|
@@ -9,14 +31,14 @@ voxgig_value* transform_request_util(Context* ctx) {
|
|
|
9
31
|
if (spec) spec_set_step(spec, "reqform");
|
|
10
32
|
|
|
11
33
|
voxgig_value* transform = to_map(getp(point, "transform"));
|
|
12
|
-
if (v_is_noval(transform)) return ctx->reqdata;
|
|
34
|
+
if (v_is_noval(transform)) return strip_action(ctx->reqdata);
|
|
13
35
|
|
|
14
36
|
voxgig_value* reqform = getp(transform, "req");
|
|
15
|
-
if (v_is_noval(reqform) || v_is_null(reqform)) return ctx->reqdata;
|
|
37
|
+
if (v_is_noval(reqform) || v_is_null(reqform)) return strip_action(ctx->reqdata);
|
|
16
38
|
|
|
17
39
|
voxgig_value* store = cmap(1, "reqdata", v_share(ctx->reqdata));
|
|
18
40
|
/* Return the transform result verbatim, as the ts reference does. Falling
|
|
19
41
|
back to ctx->reqdata meant a reqform naming a path that does not resolve
|
|
20
42
|
sent the WHOLE request data instead of nothing. */
|
|
21
|
-
return voxgig_transform(store, reqform, NULL);
|
|
43
|
+
return strip_action(voxgig_transform(store, reqform, NULL));
|
|
22
44
|
}
|
|
@@ -305,7 +305,9 @@
|
|
|
305
305
|
cprop (fn [k] (mget ctxmap k))
|
|
306
306
|
client (or (cprop "client") (bget :client))
|
|
307
307
|
utility (or (cprop "utility") (bget :utility))
|
|
308
|
-
;; ctrl: explicit ctrl map -> fresh control;
|
|
308
|
+
;; ctrl: explicit ctrl map -> fresh control; an OPERATION (one named by
|
|
309
|
+
;; opname) also gets its own, so a paging cursor cannot leak into the
|
|
310
|
+
;; next call; anything else shares the base ctrl.
|
|
309
311
|
ctrl-raw (cprop "ctrl")
|
|
310
312
|
ctrl (cond
|
|
311
313
|
(vs/ismap ctrl-raw)
|
|
@@ -315,7 +317,7 @@
|
|
|
315
317
|
(when (contains? (into #{} (vs/keysof ctrl-raw)) "actor") (oset! c :actor (mget ctrl-raw "actor")))
|
|
316
318
|
(when (vs/ismap (mget ctrl-raw "paging")) (oset! c :paging (mget ctrl-raw "paging")))
|
|
317
319
|
c)
|
|
318
|
-
(bget :ctrl) (bget :ctrl)
|
|
320
|
+
(and (nil? (cprop "opname")) (bget :ctrl)) (bget :ctrl)
|
|
319
321
|
:else (make-control))
|
|
320
322
|
meta (let [m (cprop "meta")] (if (vs/ismap m) m (or (bget :meta) (vs/jm))))
|
|
321
323
|
config (let [c (cprop "config")] (if (vs/ismap c) c (bget :config)))
|
|
@@ -502,18 +504,30 @@
|
|
|
502
504
|
out (vs/jm)]
|
|
503
505
|
(doseq [item (or (vs/items reqmatch) [])]
|
|
504
506
|
(let [k (vs/getprop item 0) v (vs/getprop item 1)]
|
|
505
|
-
(when (and (some? v) (string? k) (not (contains? pset k)))
|
|
507
|
+
(when (and (some? v) (string? k) (not= "$action" k) (not (contains? pset k)))
|
|
506
508
|
(.put ^java.util.Map out k v))))
|
|
507
509
|
out))
|
|
508
510
|
|
|
511
|
+
;; `$action` selects the point (see u-make-point); it is never an API field,
|
|
512
|
+
;; so the body is a copy without it. The caller's map is left untouched.
|
|
513
|
+
(defn- strip-action [reqdata]
|
|
514
|
+
(if (and (vs/ismap reqdata) (.containsKey ^java.util.Map reqdata "$action"))
|
|
515
|
+
(let [body (vs/jm)]
|
|
516
|
+
(doseq [item (or (vs/items reqdata) [])]
|
|
517
|
+
(let [k (vs/getprop item 0) v (vs/getprop item 1)]
|
|
518
|
+
(when (not= "$action" k) (.put ^java.util.Map body k v))))
|
|
519
|
+
body)
|
|
520
|
+
reqdata))
|
|
521
|
+
|
|
509
522
|
(defn u-transform-request [ctx]
|
|
510
523
|
(let [spec (oget ctx :spec) point (oget ctx :point)]
|
|
511
524
|
(when spec (oset! spec :step "reqform"))
|
|
512
525
|
(let [transform (to-map (vs/getprop point "transform"))]
|
|
513
|
-
(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
526
|
+
(strip-action
|
|
527
|
+
(if (nil? transform) (oget ctx :reqdata)
|
|
528
|
+
(let [reqform (vs/getprop transform "req")]
|
|
529
|
+
(if (nil? reqform) (oget ctx :reqdata)
|
|
530
|
+
(vs/transform (vs/jm "reqdata" (oget ctx :reqdata)) reqform))))))))
|
|
517
531
|
|
|
518
532
|
(defn u-prepare-body [ctx]
|
|
519
533
|
(if (= "data" (op-input (oget ctx :op))) (ucall ctx :transform-request) nil))
|
|
@@ -177,14 +177,11 @@
|
|
|
177
177
|
(let [update-match (if (zero? (vs/size update-match)) (resolve-match (vs/jm)) update-match)
|
|
178
178
|
args (test-build-args fctx op update-match)
|
|
179
179
|
found (vs/select entmap args)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
(first (filter vs/ismap (map #(vs/getprop entmap %) (vs/keysof entmap))))
|
|
183
|
-
ent0)]
|
|
180
|
+
ent (vs/getelem found 0)]
|
|
181
|
+
;; update miss: 404, never another record
|
|
184
182
|
(if (nil? ent) (respond 404 nil (vs/jm "statusText" "Not found"))
|
|
185
|
-
(do (when (and (vs/ismap ent) reqdata)
|
|
186
|
-
(
|
|
187
|
-
(.put ^java.util.Map ent (vs/getprop item 0) (vs/getprop item 1))))
|
|
183
|
+
(do (when (and (vs/ismap ent) (vs/ismap reqdata))
|
|
184
|
+
(vs/merge (vs/jt ent reqdata)))
|
|
188
185
|
(vs/delprop ent "$KEY")
|
|
189
186
|
(respond 200 (vs/clone ent) nil)))))
|
|
190
187
|
(= opn "remove")
|
|
@@ -210,20 +207,47 @@
|
|
|
210
207
|
(if (nil? net) test-fetcher (make-netsim fa net test-fetcher)))))))
|
|
211
208
|
fa))
|
|
212
209
|
|
|
210
|
+
(defn- point-terminal? [p]
|
|
211
|
+
(let [parts (vs/getprop p "parts")
|
|
212
|
+
n (if (vs/islist parts) (vs/size parts) 0)
|
|
213
|
+
last-part (when (pos? n) (vs/getelem parts (dec n)))]
|
|
214
|
+
(and (string? last-part) (str/starts-with? last-part "{"))))
|
|
215
|
+
|
|
216
|
+
(defn- point-depth [p]
|
|
217
|
+
(let [parts (vs/getprop p "parts")]
|
|
218
|
+
(if (vs/islist parts) (vs/size parts) 0)))
|
|
219
|
+
|
|
220
|
+
;; The entity's own endpoint: a terminal `{param}` marks a record route, and
|
|
221
|
+
;; among equals the shallower path wins (the same rule as make-point).
|
|
222
|
+
(defn- pick-point [points]
|
|
223
|
+
(when (and (vs/islist points) (pos? (vs/size points)))
|
|
224
|
+
(reduce (fn [point cand]
|
|
225
|
+
(cond
|
|
226
|
+
(not= (point-terminal? cand) (point-terminal? point)) (if (point-terminal? cand) cand point)
|
|
227
|
+
(< (point-depth cand) (point-depth point)) cand
|
|
228
|
+
:else point))
|
|
229
|
+
(vs/getelem points 0)
|
|
230
|
+
(map #(vs/getelem points %) (range 1 (vs/size points))))))
|
|
231
|
+
|
|
232
|
+
(defn- reqd-names [point kind]
|
|
233
|
+
(vs/transform (vs/select (vs/getpath point (str "args." kind)) (vs/jm "reqd" true))
|
|
234
|
+
(vs/jt "`$EACH`" "" "`$KEY.name`")))
|
|
235
|
+
|
|
213
236
|
(defn- test-build-args [ctx op args]
|
|
214
237
|
(let [opname (core/op-name op)
|
|
215
238
|
points (vs/getpath (core/oget ctx :config)
|
|
216
239
|
(str "entity." (core/entity-get-name (core/oget ctx :entity)) ".op." opname ".points"))
|
|
217
|
-
point (
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
reqd (
|
|
240
|
+
point (pick-point points)
|
|
241
|
+
;; Path AND query: a path-only read misses a query-addressed record
|
|
242
|
+
;; (e.g. GET /result?trace_id=), which has no path param at all.
|
|
243
|
+
reqd-params (reqd-names point "params")
|
|
244
|
+
reqd-query (reqd-names point "query")
|
|
221
245
|
qand (vs/jt)]
|
|
222
246
|
(when args
|
|
223
247
|
(doseq [key (or (vs/keysof args) [])]
|
|
224
248
|
(let [is-id (= key "id")
|
|
225
|
-
|
|
226
|
-
|
|
249
|
+
is-reqd (or (not (vs/isempty (vs/select reqd-params key)))
|
|
250
|
+
(not (vs/isempty (vs/select reqd-query key))))]
|
|
227
251
|
(when (or is-id is-reqd)
|
|
228
252
|
(let [v ((core/uget ctx :param) ctx key)
|
|
229
253
|
ka (when (core/op-alias op) (vs/getprop (core/op-alias op) key))
|
|
@@ -564,7 +588,14 @@
|
|
|
564
588
|
(cond
|
|
565
589
|
(some? (mget paging "cursor")) (.put ^java.util.Map q cursor-param (mget paging "cursor"))
|
|
566
590
|
(nil? (mget q page-param))
|
|
567
|
-
|
|
591
|
+
;; A record written back by PreResult holds the page
|
|
592
|
+
;; just fetched as `page` and the one to fetch as
|
|
593
|
+
;; `nextPage`, so nextPage wins.
|
|
594
|
+
(.put ^java.util.Map q page-param
|
|
595
|
+
(cond
|
|
596
|
+
(some? (mget paging "nextPage")) (mget paging "nextPage")
|
|
597
|
+
(some? (mget paging "page")) (mget paging "page")
|
|
598
|
+
:else (opt fa "startPage" 1))))
|
|
568
599
|
(when (and (some? (opt fa "limit")) (nil? (mget q limit-param)))
|
|
569
600
|
(.put ^java.util.Map q limit-param (opt fa "limit"))))))))))
|
|
570
601
|
"PreResult"
|
|
@@ -609,12 +640,21 @@
|
|
|
609
640
|
(reset! explicit-more true))))))
|
|
610
641
|
(when (vs/ismap body)
|
|
611
642
|
(when (some? (mget body "next")) (when (nil? (mget paging "next")) (.put ^java.util.Map paging "next" (mget body "next"))))
|
|
643
|
+
;; Both spellings, camelCase last so it wins when a
|
|
644
|
+
;; body carries the two.
|
|
645
|
+
(when (some? (mget body "next_cursor")) (.put ^java.util.Map paging "cursor" (mget body "next_cursor")))
|
|
612
646
|
(when (some? (mget body "cursor")) (.put ^java.util.Map paging "cursor" (mget body "cursor")))
|
|
613
647
|
(when (some? (mget body "nextCursor")) (.put ^java.util.Map paging "cursor" (mget body "nextCursor")))
|
|
614
|
-
(
|
|
615
|
-
(
|
|
616
|
-
|
|
617
|
-
(
|
|
648
|
+
(when (nil? (mget paging "nextPage"))
|
|
649
|
+
(let [np (if (some? (mget body "nextPage"))
|
|
650
|
+
(mget body "nextPage") (mget body "next_page"))]
|
|
651
|
+
(when (or (number? np) (string? np))
|
|
652
|
+
(.put ^java.util.Map paging "nextPage" np))))
|
|
653
|
+
(doseq [k ["has_more" "hasMore"]]
|
|
654
|
+
(let [hm (mget body k)]
|
|
655
|
+
(when (or (= hm true) (= hm false))
|
|
656
|
+
(.put ^java.util.Map paging "hasMore" hm)
|
|
657
|
+
(reset! explicit-more true)))))
|
|
618
658
|
;; Cursor presence only INFERS another page. When the
|
|
619
659
|
;; server stated the answer outright — relay's
|
|
620
660
|
;; `hasNextPage: false`, or a body `hasMore` — that
|
|
@@ -627,6 +667,16 @@
|
|
|
627
667
|
(boolean (or (mget paging "hasMore") (some? (mget paging "next"))
|
|
628
668
|
(some? (mget paging "cursor")) (some? (mget paging "nextPage")))))))
|
|
629
669
|
(core/oset! result :paging paging)
|
|
670
|
+
(when-let [ctrl (core/oget ctx :ctrl)]
|
|
671
|
+
(let [held (core/oget ctrl :paging)]
|
|
672
|
+
(if (vs/ismap held)
|
|
673
|
+
;; The caller holds this map, so refill it in place
|
|
674
|
+
;; as the other typed ports do; a fresh map on ctrl
|
|
675
|
+
;; would never reach them. Every key of the record
|
|
676
|
+
;; is written, so no key of an earlier page lives on.
|
|
677
|
+
(doseq [k (vec (vs/keysof paging))]
|
|
678
|
+
(.put ^java.util.Map held k (mget paging k)))
|
|
679
|
+
(core/oset! ctrl :paging paging))))
|
|
630
680
|
(track-set! fa "_paging" (vs/jm "last" paging)))))))
|
|
631
681
|
)
|
|
632
682
|
fa))
|
|
@@ -690,7 +690,7 @@ inline Context::Context(const CtxSpec& cs, const CtxPtr& basectx) {
|
|
|
690
690
|
if (a.is_string()) ctrl->actor = a.as_string();
|
|
691
691
|
Value p = getp(cs.ctrlMap, "paging");
|
|
692
692
|
if (p.is_map()) ctrl->paging = p;
|
|
693
|
-
} else if (basectx && basectx->ctrl) {
|
|
693
|
+
} else if (basectx && basectx->ctrl && cs.opname.empty()) {
|
|
694
694
|
ctrl = basectx->ctrl;
|
|
695
695
|
}
|
|
696
696
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// page/limit (or a cursor) into the request query; on the way back
|
|
4
4
|
// (PreResult) it reads the server's pagination signals — a `Link` rel="next"
|
|
5
5
|
// header, `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
|
|
6
|
-
// `next`/`cursor`/`nextCursor`/`hasMore` body fields
|
|
6
|
+
// `next`/`cursor`/`nextCursor`/`hasMore` body fields (snake_case forms too)
|
|
7
|
+
// — onto ctx.result.paging and the caller's ctrl paging.
|
|
7
8
|
// A per-call cursor/page from ctrl takes priority (auto-iteration).
|
|
8
9
|
|
|
9
10
|
#ifndef SDK_FEATURE_PAGING_HPP
|
|
@@ -61,7 +62,10 @@ public:
|
|
|
61
62
|
if (!is_nullish(cursor)) {
|
|
62
63
|
map_put(spec->query, cursorParam, cursor);
|
|
63
64
|
} else if (is_nullish(getp(spec->query, pageParam))) {
|
|
64
|
-
|
|
65
|
+
// A record written back by preResult holds the page just fetched as
|
|
66
|
+
// "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
67
|
+
Value page = getp(paging, "nextPage");
|
|
68
|
+
if (is_nullish(page)) page = getp(paging, "page");
|
|
65
69
|
if (!is_nullish(page)) {
|
|
66
70
|
map_put(spec->query, pageParam, page);
|
|
67
71
|
} else {
|
|
@@ -171,12 +175,16 @@ public:
|
|
|
171
175
|
}
|
|
172
176
|
}
|
|
173
177
|
|
|
174
|
-
// Body-level
|
|
178
|
+
// Body-level signals; snake_case forms are read first so camelCase wins.
|
|
175
179
|
if (body.is_map()) {
|
|
176
180
|
Value bnext = getp(body, "next");
|
|
177
181
|
if (!is_nullish(bnext) && is_nullish(getp(paging, "next"))) {
|
|
178
182
|
map_put(paging, "next", bnext);
|
|
179
183
|
}
|
|
184
|
+
Value bsnakeCursor = getp(body, "next_cursor");
|
|
185
|
+
if (!is_nullish(bsnakeCursor)) {
|
|
186
|
+
map_put(paging, "cursor", bsnakeCursor);
|
|
187
|
+
}
|
|
180
188
|
Value bcursor = getp(body, "cursor");
|
|
181
189
|
if (!is_nullish(bcursor)) {
|
|
182
190
|
map_put(paging, "cursor", bcursor);
|
|
@@ -185,6 +193,18 @@ public:
|
|
|
185
193
|
if (!is_nullish(bnextCursor)) {
|
|
186
194
|
map_put(paging, "cursor", bnextCursor);
|
|
187
195
|
}
|
|
196
|
+
if (is_nullish(getp(paging, "nextPage"))) {
|
|
197
|
+
Value np = getp(body, "nextPage");
|
|
198
|
+
if (is_nullish(np)) np = getp(body, "next_page");
|
|
199
|
+
if (np.is_number() || np.is_string()) {
|
|
200
|
+
map_put(paging, "nextPage", np);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
Value bsnakeMore = getp(body, "has_more");
|
|
204
|
+
if (bsnakeMore.is_bool()) {
|
|
205
|
+
map_put(paging, "hasMore", bsnakeMore);
|
|
206
|
+
explicitMore = true;
|
|
207
|
+
}
|
|
188
208
|
Value bhasMore = getp(body, "hasMore");
|
|
189
209
|
if (bhasMore.is_bool()) {
|
|
190
210
|
map_put(paging, "hasMore", bhasMore);
|
|
@@ -205,6 +225,19 @@ public:
|
|
|
205
225
|
|
|
206
226
|
result->paging = paging;
|
|
207
227
|
last = paging;
|
|
228
|
+
|
|
229
|
+
if (ctx->ctrl) {
|
|
230
|
+
// Value maps are shared_ptr-backed, so the caller's map is refilled in
|
|
231
|
+
// place; a fresh map on ctrl never reaches the caller.
|
|
232
|
+
if (ctx->ctrl->paging.is_map()) {
|
|
233
|
+
ctx->ctrl->paging.as_map()->clear();
|
|
234
|
+
for (const auto& kv : *paging.as_map()) {
|
|
235
|
+
map_put(ctx->ctrl->paging, kv.first, kv.second);
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
ctx->ctrl->paging = paging;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
208
241
|
}
|
|
209
242
|
|
|
210
243
|
private:
|
|
@@ -170,14 +170,10 @@ private:
|
|
|
170
170
|
Value args = buildArgs(ctx, op, updateMatch);
|
|
171
171
|
std::vector<Value> found = Struct::select(entmap, args);
|
|
172
172
|
Value ent = found.empty() ? Value::undef() : found[0];
|
|
173
|
-
|
|
174
|
-
for (const auto& kv : *entmap.as_map()) {
|
|
175
|
-
if (kv.second.is_map()) { ent = kv.second; break; }
|
|
176
|
-
}
|
|
177
|
-
}
|
|
173
|
+
// update miss: 404, never another record
|
|
178
174
|
if (is_nullish(ent)) return respond(ctx, 404, Value(nullptr), extra1("statusText", Value("Not found")));
|
|
179
175
|
if (ent.is_map() && ctx->reqdata.is_map()) {
|
|
180
|
-
|
|
176
|
+
Struct::merge(vlist({ent, ctx->reqdata}));
|
|
181
177
|
}
|
|
182
178
|
Struct::delprop(ent, Value("$KEY"));
|
|
183
179
|
Value out = Struct::clone(ent);
|
|
@@ -272,19 +268,51 @@ private:
|
|
|
272
268
|
fopt::foptSleep(net)(ms);
|
|
273
269
|
}
|
|
274
270
|
|
|
271
|
+
static bool pointTerminal(const Value& p) {
|
|
272
|
+
Value parts = getp(p, "parts");
|
|
273
|
+
if (!parts.is_list() || parts.as_list()->empty()) return false;
|
|
274
|
+
const Value& last = parts.as_list()->back();
|
|
275
|
+
return last.is_string() && 0 == last.as_string().rfind("{", 0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
static size_t pointDepth(const Value& p) {
|
|
279
|
+
Value parts = getp(p, "parts");
|
|
280
|
+
return parts.is_list() ? parts.as_list()->size() : 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// The entity's own endpoint: a terminal `{param}` marks a record route, and
|
|
284
|
+
// among equals the shallower path wins (the same rule as makePoint).
|
|
285
|
+
static Value pickPoint(const Value& points) {
|
|
286
|
+
if (!points.is_list() || points.as_list()->empty()) return Value::undef();
|
|
287
|
+
Value point = (*points.as_list())[0];
|
|
288
|
+
for (const Value& cand : *points.as_list()) {
|
|
289
|
+
if (pointTerminal(cand) != pointTerminal(point)) {
|
|
290
|
+
if (pointTerminal(cand)) point = cand;
|
|
291
|
+
} else if (pointDepth(cand) < pointDepth(point)) {
|
|
292
|
+
point = cand;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return point;
|
|
296
|
+
}
|
|
297
|
+
|
|
275
298
|
Value buildArgs(CtxPtr ctx, OperationPtr op, const Value& args) {
|
|
276
299
|
std::string opname = op->name;
|
|
277
300
|
|
|
278
301
|
Value points = Struct::getpath(ctx->config,
|
|
279
302
|
{"entity", ctx->entity == nullptr ? std::string("") : ctx->entity->getName(),
|
|
280
303
|
"op", opname, "points"});
|
|
281
|
-
Value point =
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
Value
|
|
286
|
-
for (
|
|
287
|
-
|
|
304
|
+
Value point = pickPoint(points);
|
|
305
|
+
|
|
306
|
+
// Path AND query: a path-only read misses a query-addressed record
|
|
307
|
+
// (e.g. GET /result?trace_id=), which has no path param at all.
|
|
308
|
+
Value reqdArgs = vlist();
|
|
309
|
+
for (const char* kind : {"params", "query"}) {
|
|
310
|
+
Value argsPath = Struct::getpath(point, {"args", kind});
|
|
311
|
+
for (auto& p : Struct::select(argsPath, vmap({{"reqd", Value(true)}}))) {
|
|
312
|
+
reqdArgs.as_list()->push_back(p);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
Value reqd = Struct::transform(reqdArgs, vlist({Value("`$EACH`"), Value(""), Value("`$KEY.name`")}));
|
|
288
316
|
|
|
289
317
|
Value qand = vlist();
|
|
290
318
|
Value q = vmap({{"`$AND`", qand}});
|
|
@@ -1026,7 +1026,7 @@ inline Value prepareQuery(CtxPtr ctx) {
|
|
|
1026
1026
|
for (const auto& item : Struct::items(reqmatch)) {
|
|
1027
1027
|
std::string key = as_str(pair_key(item));
|
|
1028
1028
|
Value val = pair_val(item);
|
|
1029
|
-
if (!is_nullish(val) && !contains_str(params, key)) {
|
|
1029
|
+
if (!is_nullish(val) && "$action" != key && !contains_str(params, key)) {
|
|
1030
1030
|
map_put(out, key, val);
|
|
1031
1031
|
}
|
|
1032
1032
|
}
|
|
@@ -1050,18 +1050,30 @@ inline std::string preparePath(CtxPtr ctx) {
|
|
|
1050
1050
|
|
|
1051
1051
|
// ---- transformRequest -------------------------------------------------
|
|
1052
1052
|
|
|
1053
|
+
// `$action` selects the point (see makePoint); it is never an API field, so
|
|
1054
|
+
// the body is a copy without it. The caller's map is left untouched.
|
|
1055
|
+
inline Value stripAction(const Value& reqdata) {
|
|
1056
|
+
if (!map_contains(reqdata, "$action")) return reqdata;
|
|
1057
|
+
Value body = vmap();
|
|
1058
|
+
for (const auto& item : Struct::items(reqdata)) {
|
|
1059
|
+
std::string key = as_str(pair_key(item));
|
|
1060
|
+
if ("$action" != key) map_put(body, key, pair_val(item));
|
|
1061
|
+
}
|
|
1062
|
+
return body;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1053
1065
|
inline Value transformRequest(CtxPtr ctx) {
|
|
1054
1066
|
if (ctx->spec) ctx->spec->step = "reqform";
|
|
1055
1067
|
|
|
1056
1068
|
Value transform = Helpers::toMapAny(getp(ctx->point, "transform"));
|
|
1057
|
-
if (!transform.is_map()) return ctx->reqdata;
|
|
1069
|
+
if (!transform.is_map()) return stripAction(ctx->reqdata);
|
|
1058
1070
|
|
|
1059
1071
|
Value reqform = getp(transform, "req");
|
|
1060
|
-
if (is_nullish(reqform)) return ctx->reqdata;
|
|
1072
|
+
if (is_nullish(reqform)) return stripAction(ctx->reqdata);
|
|
1061
1073
|
|
|
1062
1074
|
Value data = vmap();
|
|
1063
1075
|
map_put(data, "reqdata", ctx->reqdata);
|
|
1064
|
-
return Struct::transform(data, reqform);
|
|
1076
|
+
return stripAction(Struct::transform(data, reqform));
|
|
1065
1077
|
}
|
|
1066
1078
|
|
|
1067
1079
|
// ---- makeOptions ------------------------------------------------------
|