@voxgig/sdkgen 4.23.0 → 4.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/bin/voxgig-sdkgen +18 -2
  2. package/dist/action/doctor.d.ts +1 -1
  3. package/dist/action/doctor.js +12 -10
  4. package/dist/action/doctor.js.map +1 -1
  5. package/dist/action/edition.js +4 -0
  6. package/dist/action/edition.js.map +1 -1
  7. package/dist/action/feature.js +5 -1
  8. package/dist/action/feature.js.map +1 -1
  9. package/dist/action/remove.d.ts +16 -0
  10. package/dist/action/remove.js +345 -0
  11. package/dist/action/remove.js.map +1 -0
  12. package/dist/action/target.js +6 -1
  13. package/dist/action/target.js.map +1 -1
  14. package/dist/cmp/Deploy.js +9 -6
  15. package/dist/cmp/Deploy.js.map +1 -1
  16. package/dist/helpers/semver.js +3 -1
  17. package/dist/helpers/semver.js.map +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/package.json +2 -2
  20. package/project/.sdk/tm/c/core/context.c +3 -1
  21. package/project/.sdk/tm/c/feature/paging.c +37 -3
  22. package/project/.sdk/tm/c/feature/test.c +51 -27
  23. package/project/.sdk/tm/c/utility/prepare_query.c +2 -1
  24. package/project/.sdk/tm/c/utility/transform_request.c +25 -3
  25. package/project/.sdk/tm/clojure/src/sdk/core.clj +21 -7
  26. package/project/.sdk/tm/clojure/src/sdk/features.clj +68 -18
  27. package/project/.sdk/tm/cpp/core/types.hpp +1 -1
  28. package/project/.sdk/tm/cpp/feature/paging.hpp +36 -3
  29. package/project/.sdk/tm/cpp/feature/test.hpp +41 -13
  30. package/project/.sdk/tm/cpp/utility/pipeline.hpp +16 -4
  31. package/project/.sdk/tm/csharp/core/Context.cs +1 -1
  32. package/project/.sdk/tm/csharp/feature/PagingFeature.cs +58 -4
  33. package/project/.sdk/tm/csharp/feature/TestFeature.cs +13 -21
  34. package/project/.sdk/tm/csharp/utility/Fetcher.cs +19 -12
  35. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +1 -1
  36. package/project/.sdk/tm/csharp/utility/TransformRequest.cs +23 -3
  37. package/project/.sdk/tm/elixir/lib/projectname/context.ex +1 -1
  38. package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +52 -10
  39. package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +47 -17
  40. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +30 -9
  41. package/project/.sdk/tm/go/core/context.go +13 -13
  42. package/project/.sdk/tm/go/feature/paging_feature.go +40 -2
  43. package/project/.sdk/tm/go/feature/test_feature.go +3 -15
  44. package/project/.sdk/tm/go/test/feature_corpus_test.go +1 -0
  45. package/project/.sdk/tm/go/test/feature_test.go +54 -0
  46. package/project/.sdk/tm/go/test/pipeline_test.go +163 -57
  47. package/project/.sdk/tm/go/test/primary_utility_test.go +53 -0
  48. package/project/.sdk/tm/go/utility/fetcher.go +41 -20
  49. package/project/.sdk/tm/go/utility/prepare_query.go +1 -1
  50. package/project/.sdk/tm/go/utility/transform_request.go +22 -3
  51. package/project/.sdk/tm/java/core/Context.java +2 -1
  52. package/project/.sdk/tm/java/feature/PagingFeature.java +46 -4
  53. package/project/.sdk/tm/java/feature/TestFeature.java +10 -13
  54. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +3 -0
  55. package/project/.sdk/tm/java/test/FeatureTest.java +42 -0
  56. package/project/.sdk/tm/java/test/PipelineTest.java +141 -48
  57. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +67 -6
  58. package/project/.sdk/tm/java/utility/Fetcher.java +36 -28
  59. package/project/.sdk/tm/java/utility/PrepareQuery.java +1 -1
  60. package/project/.sdk/tm/java/utility/TransformRequest.java +18 -3
  61. package/project/.sdk/tm/js/src/Context.js +7 -2
  62. package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +24 -6
  63. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +7 -5
  64. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +1 -1
  65. package/project/.sdk/tm/js/test/feature/Corpus.test.js +4 -0
  66. package/project/.sdk/tm/js/test/pipeline.test.js +88 -22
  67. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +47 -1
  68. package/project/.sdk/tm/kotlin/core/Context.kt +1 -1
  69. package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +36 -4
  70. package/project/.sdk/tm/kotlin/feature/TestFeature.kt +11 -14
  71. package/project/.sdk/tm/kotlin/utility/Fetcher.kt +33 -24
  72. package/project/.sdk/tm/kotlin/utility/Prepare.kt +1 -1
  73. package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -3
  74. package/project/.sdk/tm/lua/core/context.lua +8 -1
  75. package/project/.sdk/tm/lua/core/control.lua +3 -0
  76. package/project/.sdk/tm/lua/feature/paging_feature.lua +44 -5
  77. package/project/.sdk/tm/lua/feature/test_feature.lua +32 -14
  78. package/project/.sdk/tm/lua/test/omni.lua +8 -1
  79. package/project/.sdk/tm/lua/test/pipeline_test.lua +107 -28
  80. package/project/.sdk/tm/lua/test/primary_utility_test.lua +83 -3
  81. package/project/.sdk/tm/lua/utility/prepare_query.lua +2 -1
  82. package/project/.sdk/tm/lua/utility/transform_request.lua +19 -3
  83. package/project/.sdk/tm/ocaml/sdk_features.ml +72 -21
  84. package/project/.sdk/tm/ocaml/sdk_runtime.ml +23 -8
  85. package/project/.sdk/tm/perl/core/context.pm +2 -1
  86. package/project/.sdk/tm/perl/feature/paging_feature.pm +31 -5
  87. package/project/.sdk/tm/perl/feature/test_feature.pm +46 -16
  88. package/project/.sdk/tm/perl/feature/timeout_feature.pm +17 -13
  89. package/project/.sdk/tm/perl/t/feature_corpus.t +14 -1
  90. package/project/.sdk/tm/perl/t/omni.pm +7 -1
  91. package/project/.sdk/tm/perl/t/pipeline.t +124 -27
  92. package/project/.sdk/tm/perl/t/primary_utility.t +73 -0
  93. package/project/.sdk/tm/perl/utility/fetcher.pm +15 -1
  94. package/project/.sdk/tm/perl/utility/prepare_query.pm +1 -0
  95. package/project/.sdk/tm/perl/utility/transform_request.pm +13 -3
  96. package/project/.sdk/tm/php/core/Context.php +5 -1
  97. package/project/.sdk/tm/php/core/Control.php +5 -0
  98. package/project/.sdk/tm/php/feature/PagingFeature.php +26 -5
  99. package/project/.sdk/tm/php/feature/TestFeature.php +27 -42
  100. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +17 -1
  101. package/project/.sdk/tm/php/test/FeatureTest.php +44 -0
  102. package/project/.sdk/tm/php/test/PipelineTest.php +143 -26
  103. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +80 -2
  104. package/project/.sdk/tm/php/utility/Fetcher.php +17 -3
  105. package/project/.sdk/tm/php/utility/PrepareQuery.php +1 -1
  106. package/project/.sdk/tm/php/utility/TransformRequest.php +15 -3
  107. package/project/.sdk/tm/py/pkg/core/context.py +2 -1
  108. package/project/.sdk/tm/py/pkg/feature/paging_feature.py +29 -4
  109. package/project/.sdk/tm/py/pkg/feature/test_feature.py +29 -12
  110. package/project/.sdk/tm/py/pkg/utility/fetcher.py +63 -65
  111. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +2 -1
  112. package/project/.sdk/tm/py/pkg/utility/transform_request.py +11 -3
  113. package/project/.sdk/tm/py/test/test_feature.py +27 -0
  114. package/project/.sdk/tm/py/test/test_feature_corpus.py +11 -1
  115. package/project/.sdk/tm/py/test/test_pipeline.py +108 -31
  116. package/project/.sdk/tm/py/test/test_primary_utility.py +76 -1
  117. package/project/.sdk/tm/rb/core/context.rb +1 -1
  118. package/project/.sdk/tm/rb/feature/paging_feature.rb +27 -5
  119. package/project/.sdk/tm/rb/feature/test_feature.rb +33 -7
  120. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -1
  121. package/project/.sdk/tm/rb/test/feature_test.rb +38 -0
  122. package/project/.sdk/tm/rb/test/pipeline_test.rb +100 -24
  123. package/project/.sdk/tm/rb/test/primary_utility_test.rb +59 -1
  124. package/project/.sdk/tm/rb/utility/fetcher.rb +49 -3
  125. package/project/.sdk/tm/rb/utility/prepare_query.rb +1 -1
  126. package/project/.sdk/tm/rb/utility/transform_request.rb +10 -3
  127. package/project/.sdk/tm/rust/core/context.rs +2 -6
  128. package/project/.sdk/tm/rust/feature/paging.rs +44 -3
  129. package/project/.sdk/tm/rust/feature/secrets.rs +1 -1
  130. package/project/.sdk/tm/rust/feature/test.rs +66 -33
  131. package/project/.sdk/tm/rust/utility/fetcher.rs +41 -14
  132. package/project/.sdk/tm/rust/utility/prepare_query.rs +1 -1
  133. package/project/.sdk/tm/rust/utility/transform_request.rs +31 -5
  134. package/project/.sdk/tm/scala/core/Context.scala +2 -1
  135. package/project/.sdk/tm/scala/feature/PagingFeature.scala +41 -4
  136. package/project/.sdk/tm/scala/feature/TestFeature.scala +11 -12
  137. package/project/.sdk/tm/scala/utility/Fetcher.scala +34 -13
  138. package/project/.sdk/tm/scala/utility/Prepare.scala +1 -1
  139. package/project/.sdk/tm/scala/utility/Transform.scala +19 -3
  140. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +1 -1
  141. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +31 -4
  142. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +12 -9
  143. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +22 -3
  144. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +1 -1
  145. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +14 -3
  146. package/project/.sdk/tm/ts/src/Context.ts +7 -2
  147. package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +19 -3
  148. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +3 -0
  149. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +1 -1
  150. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +1 -0
  151. package/project/.sdk/tm/ts/test/pipeline.test.ts +52 -11
  152. package/project/.sdk/tm/zig/core/context.zig +1 -1
  153. package/project/.sdk/tm/zig/core/utility.zig +19 -5
  154. package/project/.sdk/tm/zig/feature/paging.zig +34 -4
  155. package/project/.sdk/tm/zig/feature/test.zig +54 -26
  156. package/project/sdkgen-package.json +1 -1
  157. package/src/action/doctor.ts +12 -9
  158. package/src/action/edition.ts +8 -0
  159. package/src/action/feature.ts +7 -1
  160. package/src/action/remove.ts +492 -0
  161. package/src/action/target.ts +8 -1
  162. package/src/cmp/Deploy.ts +11 -6
  163. package/src/helpers/semver.ts +3 -1
@@ -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 `ctx.result.paging`. A
9
- -- per-call `ctrl.paging` (cursor or page) wins over the option defaults,
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
- if paging["page"] ~= nil then
99
- spec.query[page_param] = paging["page"]
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 cursors.
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
- local reqdata = fctx.reqdata
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 = vs.getelem(points, -1)
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.
@@ -403,7 +403,14 @@ end
403
403
  --- omni's value model), which keeps smoke tests free of fixture files.
404
404
  function M.makeRunner(testfile, client)
405
405
  local specref = testfile
406
- if "string" == type(testfile) and "/" ~= testfile:sub(1, 1) then
406
+ -- A drive letter and a UNC prefix are absolute too, and neither starts
407
+ -- with "/": treating one as relative joins it onto _test_dir and reads
408
+ -- nothing. Lua has no path library, so the shapes are spelled out.
409
+ local function isabs(p)
410
+ return nil ~= p:match("^/") or nil ~= p:match("^%a:[/\\]")
411
+ or nil ~= p:match("^\\\\")
412
+ end
413
+ if "string" == type(testfile) and not isabs(testfile) then
407
414
  specref = _test_dir .. testfile
408
415
  end
409
416
 
@@ -536,58 +536,137 @@ describe("pipeline", function()
536
536
 
537
537
  describe("prepareAuth", function()
538
538
 
539
+ -- A cookie credential as prepare_auth writes it: `<scheme>=K` for the
540
+ -- probe key, with no scheme prefix and nothing else in the bag.
541
+ local function is_cookie_pair(value)
542
+ return "string" == type(value) and nil ~= value:match("^[^=;]+=K$")
543
+ end
544
+
539
545
  -- Fake client so the exact options.auth / apikey shape is controlled.
540
- local function auth_ctx(options, headers)
546
+ local function auth_ctx(options, spec)
541
547
  local ctx = base_ctx({})
542
548
  ctx.client = {
543
549
  options_map = function(_self)
544
550
  return options
545
551
  end,
546
552
  }
547
- if headers == nil then
548
- ctx.spec = nil
549
- else
550
- ctx.spec = Spec.new({ headers = headers })
551
- end
553
+ ctx.spec = spec
552
554
  return ctx
553
555
  end
554
556
 
557
+ local function auth_bags()
558
+ return Spec.new({ headers = {}, query = {} })
559
+ end
560
+
561
+ -- A cookie credential rides the header bag, because a cookie IS a header.
562
+ local function auth_bag(spec, where)
563
+ if "query" == where then return spec.query end
564
+ return spec.headers
565
+ end
566
+
567
+ -- `basic = false` is explicit: an HTTP Basic API's generated config
568
+ -- carries `auth.basic: true`, and a client that merges it in takes a
569
+ -- branch needing a secret as well. With none supplied that branch writes
570
+ -- nothing, which the probe would read as a public API.
571
+ local function auth_block(prefix)
572
+ return { prefix = prefix, basic = false }
573
+ end
574
+
575
+ -- Run prepare_auth with both containers present and see which one the
576
+ -- generated utility writes to, and under what name. nil means this SDK
577
+ -- places no credential at all - a public API - which is a legitimate
578
+ -- shape, and the tests below assert exactly that instead. `pair` is the
579
+ -- `<scheme>=` lead-in of a COOKIE credential, which rides the header bag
580
+ -- under the key "cookie" instead of taking a header of its own.
581
+ local function auth_probe(options)
582
+ local ctx = auth_ctx(options, auth_bags())
583
+ ctx.utility.prepare_auth(ctx)
584
+ for _, where in ipairs({ "headers", "query" }) do
585
+ for name, value in pairs(auth_bag(ctx.spec, where)) do
586
+ local pair = ""
587
+ if "headers" == where and "cookie" == name and is_cookie_pair(value) then
588
+ pair = value:sub(1, -2)
589
+ end
590
+ return { where = where, name = name, value = value, pair = pair }
591
+ end
592
+ end
593
+ return nil
594
+ end
595
+
596
+ local function auth_credential()
597
+ return auth_probe({ apikey = "K", auth = auth_block("Bearer") })
598
+ end
599
+
600
+ -- Every credential this SDK could possibly place: both credentials and
601
+ -- Basic switched on, so whichever branch the API has, something lands
602
+ -- unless the API is public.
603
+ local function auth_any_credential()
604
+ return auth_probe({ apikey = "K", secret = "S",
605
+ auth = { prefix = "Bearer", basic = true } })
606
+ end
607
+
608
+ local function auth_placed(options, seed)
609
+ local cred = auth_credential()
610
+ local spec = auth_bags()
611
+ if nil ~= cred and nil ~= seed then
612
+ auth_bag(spec, cred.where)[cred.name] = seed
613
+ end
614
+ local ctx = auth_ctx(options, spec)
615
+ ctx.utility.prepare_auth(ctx)
616
+ if nil == cred then return nil end
617
+ return auth_bag(ctx.spec, cred.where)[cred.name]
618
+ end
619
+
555
620
  it("guards a missing spec", function()
556
- local ctx = auth_ctx({ auth = { prefix = "" }, apikey = "K" }, nil)
621
+ local ctx = auth_ctx({ auth = auth_block(""), apikey = "K" }, nil)
557
622
  local _, err = ctx.utility.prepare_auth(ctx)
558
623
  assert.are.equal("auth_no_spec", err.code)
559
624
  end)
560
625
 
561
- it("an apikey with a prefix is space-joined", function()
562
- local ctx = auth_ctx({ apikey = "K", auth = { prefix = "Bearer" } }, {})
563
- ctx.utility.prepare_auth(ctx)
564
- assert.are.equal("Bearer K", ctx.spec.headers["authorization"])
626
+ -- Without this the cases below cannot fail for an SDK whose credential the
627
+ -- probe misses: every one of them takes the public-API path instead.
628
+ it("the probe finds the credential this SDK places", function()
629
+ assert.are.equal(nil == auth_credential(), nil == auth_any_credential())
630
+ end)
631
+
632
+ it("the apikey is placed where this API puts it", function()
633
+ local cred = auth_credential()
634
+ if nil == cred then
635
+ -- A public API places nothing, and that is the whole assertion.
636
+ assert.is_nil(auth_placed({ apikey = "K", auth = auth_block("Bearer") }))
637
+ return
638
+ end
639
+ assert.is_true("headers" == cred.where or "query" == cred.where)
640
+ if "" ~= cred.pair then
641
+ -- A cookie credential is a `<scheme>=<key>` pair, and the scheme name
642
+ -- leaves no room for the option's prefix.
643
+ assert.is_true(is_cookie_pair(cred.value), tostring(cred.value))
644
+ return
645
+ end
646
+ -- A header credential is prefix-joined; a query credential is the raw
647
+ -- key, because a query parameter has nowhere to put a scheme name.
648
+ local want = "Bearer K"
649
+ if "query" == cred.where then want = "K" end
650
+ assert.are.equal(want, cred.value)
565
651
  end)
566
652
 
567
653
  it("a raw apikey (empty prefix) goes in as-is", function()
568
- local ctx = auth_ctx({ apikey = "K", auth = { prefix = "" } }, {})
569
- ctx.utility.prepare_auth(ctx)
570
- assert.are.equal("K", ctx.spec.headers["authorization"])
654
+ local cred = auth_credential()
655
+ local want = nil
656
+ if nil ~= cred then want = cred.pair .. "K" end
657
+ assert.are.equal(want, auth_placed({ apikey = "K", auth = auth_block("") }))
571
658
  end)
572
659
 
573
- it("an empty apikey drops the header", function()
574
- local ctx = auth_ctx({ apikey = "", auth = { prefix = "Bearer" } },
575
- { authorization = "stale" })
576
- ctx.utility.prepare_auth(ctx)
577
- assert.is_nil(ctx.spec.headers["authorization"])
660
+ it("an empty apikey drops the credential", function()
661
+ assert.is_nil(auth_placed({ apikey = "", auth = auth_block("Bearer") }, "stale"))
578
662
  end)
579
663
 
580
- it("a public API (no auth block) drops the header", function()
581
- local ctx = auth_ctx({ apikey = "K" }, { authorization = "stale" })
582
- ctx.utility.prepare_auth(ctx)
583
- assert.is_nil(ctx.spec.headers["authorization"])
664
+ it("a public API (no auth block) drops the credential", function()
665
+ assert.is_nil(auth_placed({ apikey = "K" }, "stale"))
584
666
  end)
585
667
 
586
- it("a missing apikey option drops the header", function()
587
- local ctx = auth_ctx({ auth = { prefix = "Bearer" } },
588
- { authorization = "stale" })
589
- ctx.utility.prepare_auth(ctx)
590
- assert.is_nil(ctx.spec.headers["authorization"])
668
+ it("a missing apikey option drops the credential", function()
669
+ assert.is_nil(auth_placed({ auth = auth_block("Bearer") }, "stale"))
591
670
  end)
592
671
  end)
593
672
 
@@ -70,6 +70,64 @@ local function get_spec(base, ...)
70
70
  end
71
71
 
72
72
 
73
+ -- Where this SDK's prepare_auth actually puts the credential: the name is
74
+ -- the API's OWN scheme name, not always `authorization`, so it is discovered
75
+ -- by running prepare_auth once.
76
+ local function auth_credential(auth_client, auth_utility)
77
+ local ctx = auth_utility.make_context({
78
+ opname = "load",
79
+ client = auth_client,
80
+ utility = auth_utility,
81
+ }, auth_client:get_root_ctx())
82
+ ctx.spec = Spec.new({ headers = {}, query = {} })
83
+ local _, err = auth_utility.prepare_auth(ctx)
84
+ if err ~= nil then
85
+ return nil
86
+ end
87
+ for _, where in ipairs({ "headers", "query" }) do
88
+ -- A cookie credential rides the header bag, because a cookie IS a header.
89
+ local bag = ctx.spec.headers
90
+ if "query" == where then bag = ctx.spec.query end
91
+ for name, _value in pairs(bag) do
92
+ return { where = where, name = name }
93
+ end
94
+ end
95
+ return nil
96
+ end
97
+
98
+
99
+ -- Rename the corpus's `headers` bag to the real container, and the
100
+ -- `authorization` key inside it to the real credential name. Applied only to
101
+ -- the prepareAuth section, so real header assertions elsewhere are untouched.
102
+ local function retarget_auth(node, cred)
103
+ if type(node) ~= "table" then
104
+ return node
105
+ end
106
+
107
+ -- The metatable carries the corpus's `__jsontype` marker, which the struct
108
+ -- library's islist/ismap read to tell a one-element list from a map. A
109
+ -- plain-table copy leaves the rebuilt `set` unrecognisable.
110
+ local out = setmetatable({}, getmetatable(node))
111
+ for key, value in pairs(node) do
112
+ if "headers" == key then
113
+ local bag = {}
114
+ if type(value) == "table" then
115
+ setmetatable(bag, getmetatable(value))
116
+ for bagkey, bagvalue in pairs(value) do
117
+ local name = bagkey
118
+ if "authorization" == bagkey then name = cred.name end
119
+ bag[name] = retarget_auth(bagvalue, cred)
120
+ end
121
+ end
122
+ out[cred.where] = bag
123
+ else
124
+ out[key] = retarget_auth(value, cred)
125
+ end
126
+ end
127
+ return out
128
+ end
129
+
130
+
73
131
  -- Sections deliberately left empty in the shared corpus
74
132
  -- (.sdk/test/primary/<name>.aon carries a PENDING header). Everything else
75
133
  -- MUST contribute cases.
@@ -749,7 +807,23 @@ describe("PrimaryUtility", function()
749
807
  local auth_client = sdk.test(nil, setup_opts)
750
808
  local auth_utility = auth_client:get_utility()
751
809
 
752
- runsection("prepareAuth", function(cin)
810
+ -- The corpus writes the credential as `headers.authorization`: a
811
+ -- PLACEHOLDER each runner points at the container and name this API
812
+ -- actually uses.
813
+ local cred = auth_credential(auth_client, auth_utility)
814
+ assert(cred ~= nil, "prepare_auth placed no credential in headers or query")
815
+
816
+ -- An absent section is runsection's report to make.
817
+ local section = get_spec(spec, "prepareAuth")
818
+ local original = nil
819
+ if section ~= nil
820
+ and not ("headers" == cred.where and "authorization" == cred.name)
821
+ then
822
+ original = section.basic
823
+ section.basic = retarget_auth(original, cred)
824
+ end
825
+
826
+ local subject = function(cin)
753
827
  if type(cin) ~= "table" then
754
828
  cin = {}
755
829
  end
@@ -758,15 +832,21 @@ describe("PrimaryUtility", function()
758
832
 
759
833
  unwrap(utility.prepare_auth(ctx))
760
834
 
761
- -- Write live-context spec headers back for match checking
835
+ -- Write the live-context spec bags back for match checking. QUERY as
836
+ -- well as headers: this API may place its credential there.
762
837
  if ctx.spec ~= nil then
763
838
  cin["spec"] = {
764
839
  headers = ctx.spec.headers,
840
+ query = ctx.spec.query,
765
841
  }
766
842
  end
767
843
 
768
844
  return nil
769
- end)
845
+ end
846
+
847
+ local ok, failure = pcall(runsection, "prepareAuth", subject)
848
+ if original ~= nil then section.basic = original end
849
+ if not ok then error(failure) end
770
850
  end)
771
851
 
772
852
 
@@ -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 not contains_param(params, key) then
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
@@ -1121,9 +1121,15 @@ let paging_feature () : feature =
1121
1121
  | Noval | Null ->
1122
1122
  (match getp q page_param with
1123
1123
  | Noval ->
1124
- let page = (match getp paging "page" with
1125
- | Noval | Null -> (match getp !options "startPage" with Num n -> Num n | _ -> Num 1.)
1126
- | p -> p) in
1124
+ (* A record written back by PreResult holds the page just
1125
+ * fetched as `page` and the one to fetch as `nextPage`,
1126
+ * so nextPage wins. *)
1127
+ let page = (match getp paging "nextPage" with
1128
+ | Noval | Null ->
1129
+ (match getp paging "page" with
1130
+ | Noval | Null -> (match getp !options "startPage" with Num n -> Num n | _ -> Num 1.)
1131
+ | p -> p)
1132
+ | np -> np) in
1127
1133
  setp q page_param page
1128
1134
  | _ -> ())
1129
1135
  | c -> setp q cursor_param c);
@@ -1176,11 +1182,20 @@ let paging_feature () : feature =
1176
1182
  (match body with
1177
1183
  | Map _ ->
1178
1184
  (match getp body "next" with Noval | Null -> () | n -> if is_nullish (getp paging "next") then setp paging "next" n);
1185
+ (* Both spellings, camelCase last so it wins when a body
1186
+ * carries the two. *)
1187
+ (match getp body "next_cursor" with Noval | Null -> () | c -> setp paging "cursor" c);
1179
1188
  (match getp body "cursor" with Noval | Null -> () | c -> setp paging "cursor" c);
1180
1189
  (match getp body "nextCursor" with Noval | Null -> () | c -> setp paging "cursor" c);
1181
- (match getp body "hasMore" with
1182
- | Bool b -> setp paging "hasMore" (Bool b); explicit_more := true
1183
- | _ -> ())
1190
+ if is_nullish (getp paging "nextPage") then begin
1191
+ let np = (match getp body "nextPage" with
1192
+ | Noval | Null -> getp body "next_page"
1193
+ | v -> v) in
1194
+ (match np with Num _ | Str _ -> setp paging "nextPage" np | _ -> ())
1195
+ end;
1196
+ List.iter (fun k -> match getp body k with
1197
+ | Bool b -> setp paging "hasMore" (Bool b); explicit_more := true
1198
+ | _ -> ()) ["has_more"; "hasMore"]
1184
1199
  | _ -> ());
1185
1200
  (* Cursor presence only INFERS another page. When the server
1186
1201
  * stated the answer outright — relay's `hasNextPage: false`, or
@@ -1196,6 +1211,16 @@ let paging_feature () : feature =
1196
1211
  setp paging "hasMore" (Bool hm)
1197
1212
  end;
1198
1213
  result.rt_paging <- paging;
1214
+ (* The record reached the caller through ctrl in every other typed
1215
+ * port and through nothing here, so a caller who asked for paging
1216
+ * got the pages and never the signals to continue. *)
1217
+ (match ctx.c_ctrl.ctrl_paging with
1218
+ | Map _ as held ->
1219
+ (* The caller holds this map: refill it in place. Every key of
1220
+ * the record is written, so no key of an earlier page lives on. *)
1221
+ List.iter (fun k -> setp held k (getp paging k))
1222
+ ["page"; "totalCount"; "nextPage"; "next"; "cursor"; "hasMore"]
1223
+ | _ -> ctx.c_ctrl.ctrl_paging <- paging);
1199
1224
  track_set (cc ctx) "paging" (jo [("last", paging)]))
1200
1225
  | _ -> ());
1201
1226
  f
@@ -1408,21 +1433,43 @@ let test_feature () : feature =
1408
1433
  let out = jo [("status", vint_of status); ("statusText", Str "OK"); ("json", json_thunk (envelope ctx data)); ("body", Str "not-used")] in
1409
1434
  (match extra with Some (Map _ as e) -> List.iter (fun k -> setp out k (getp e k)) (keysof e) | _ -> ());
1410
1435
  (out, None) in
1436
+ let point_terminal p =
1437
+ match getp p "parts" with
1438
+ | List r -> (match List.rev !r with Str s :: _ -> String.length s > 0 && s.[0] = '{' | _ -> false)
1439
+ | _ -> false in
1440
+ let point_depth p = match getp p "parts" with List r -> List.length !r | _ -> 0 in
1441
+ (* The entity's own endpoint: a terminal `{param}` marks a record route, and
1442
+ among equals the shallower path wins (the same rule as make_point). *)
1443
+ let pick_point points =
1444
+ match points with
1445
+ | List r ->
1446
+ (match !r with
1447
+ | [] -> Noval
1448
+ | first :: rest ->
1449
+ List.fold_left (fun point cand ->
1450
+ if point_terminal cand <> point_terminal point then (if point_terminal cand then cand else point)
1451
+ else if point_depth cand < point_depth point then cand
1452
+ else point) first rest)
1453
+ | _ -> Noval in
1454
+ let reqd_names point kind =
1455
+ let reqd_args = select (getpath_s point ("args." ^ kind)) (jo [("reqd", Bool true)]) in
1456
+ transform reqd_args (ja [Str "`$EACH`"; Str ""; Str "`$KEY.name`"]) in
1411
1457
  let build_args ctx (op : operation) args =
1412
1458
  let opname = op.op_name in
1413
1459
  let entname = match ctx.c_entity with Some e -> e.e_name | None -> "_" in
1414
1460
  let points = getpath_s ctx.c_config ("entity." ^ entname ^ ".op." ^ opname ^ ".points") in
1415
- let point = getelem points (Num (-1.0)) in
1416
- let params_path = getpath_s point "args.params" in
1417
- let reqd_params = select params_path (jo [("reqd", Bool true)]) in
1418
- let reqd = transform reqd_params (ja [Str "`$EACH`"; Str ""; Str "`$KEY.name`"]) in
1461
+ let point = pick_point points in
1462
+ (* Path AND query: a path-only read misses a query-addressed record
1463
+ (e.g. GET /result?trace_id=), which has no path param at all. *)
1464
+ let reqd_params = reqd_names point "params" in
1465
+ let reqd_query = reqd_names point "query" in
1419
1466
  let qand = ref [] in
1420
1467
  (match args with
1421
1468
  | Map _ ->
1422
1469
  List.iter (fun key ->
1423
1470
  let is_id = key = "id" in
1424
- let selected = select reqd (Str key) in
1425
- let is_reqd = not (isempty selected) in
1471
+ let is_reqd = not (isempty (select reqd_params (Str key)))
1472
+ || not (isempty (select reqd_query (Str key))) in
1426
1473
  if is_id || is_reqd then begin
1427
1474
  let v = (cu ctx).u_param ctx (Str key) in
1428
1475
  let ka = (match op.op_alias with Map _ -> (match getp op.op_alias key with Str s -> Some s | _ -> None) | _ -> None) in
@@ -1463,18 +1510,22 @@ let test_feature () : feature =
1463
1510
  end
1464
1511
  | "update" ->
1465
1512
  let update_match = empty_map () in
1466
- (match fctx.c_reqdata with Map _ -> (match getp fctx.c_reqdata "id" with Noval -> () | v -> setp update_match "id" v) | _ -> ());
1513
+ (match fctx.c_reqdata with
1514
+ | Map _ ->
1515
+ (match getp fctx.c_reqdata "id" with Noval -> () | v -> setp update_match "id" v);
1516
+ (match getp op.op_alias "id" with
1517
+ | Str alias_id -> (match getp fctx.c_reqdata alias_id with Noval -> () | v -> setp update_match alias_id v)
1518
+ | _ -> ())
1519
+ | _ -> ());
1467
1520
  let update_match = if size update_match > 0 then update_match else resolve_match fctx (empty_map ()) in
1468
1521
  let args = build_args fctx op update_match in
1469
- let ent = ref (getelem (select entmap args) (Num 0.0)) in
1470
- (if is_nullish !ent then match entmap with
1471
- | Map m -> (try (match List.find (fun (_, v) -> match v with Map _ -> true | _ -> false) m.entries with (_, v) -> ent := v) with Not_found -> ())
1472
- | _ -> ());
1473
- if is_nullish !ent then respond fctx 404 Noval (Some (jo [("statusText", Str "Not found")]))
1522
+ let ent = getelem (select entmap args) (Num 0.0) in
1523
+ (* update miss: 404, never another record *)
1524
+ if is_nullish ent then respond fctx 404 Noval (Some (jo [("statusText", Str "Not found")]))
1474
1525
  else begin
1475
- (match !ent with Map _ -> (match fctx.c_reqdata with Map _ -> List.iter (fun k -> setp !ent k (getp fctx.c_reqdata k)) (keysof fctx.c_reqdata) | _ -> ()) | _ -> ());
1476
- ignore (delprop !ent (Str "$KEY"));
1477
- respond fctx 200 (clone !ent) None
1526
+ (match ent, fctx.c_reqdata with Map _, Map _ -> ignore (merge (ja [ent; fctx.c_reqdata])) | _ -> ());
1527
+ ignore (delprop ent (Str "$KEY"));
1528
+ respond fctx 200 (clone ent) None
1478
1529
  end
1479
1530
  | "remove" ->
1480
1531
  let args = build_args fctx op (resolve_match fctx fctx.c_reqmatch) in