@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.
- 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/cmp/Deploy.js +9 -6
- package/dist/cmp/Deploy.js.map +1 -1
- package/dist/helpers/semver.js +3 -1
- package/dist/helpers/semver.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- 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/test/pipeline_test.go +163 -57
- package/project/.sdk/tm/go/test/primary_utility_test.go +53 -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/test/PipelineTest.java +141 -48
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +67 -6
- 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/js/test/pipeline.test.js +88 -22
- package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +47 -1
- 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/test/omni.lua +8 -1
- package/project/.sdk/tm/lua/test/pipeline_test.lua +107 -28
- package/project/.sdk/tm/lua/test/primary_utility_test.lua +83 -3
- 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/t/omni.pm +7 -1
- package/project/.sdk/tm/perl/t/pipeline.t +124 -27
- package/project/.sdk/tm/perl/t/primary_utility.t +73 -0
- 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/test/PipelineTest.php +143 -26
- package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +80 -2
- 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/py/test/test_pipeline.py +108 -31
- package/project/.sdk/tm/py/test/test_primary_utility.py +76 -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/test/pipeline_test.rb +100 -24
- package/project/.sdk/tm/rb/test/primary_utility_test.rb +59 -1
- 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/ts/test/pipeline.test.ts +52 -11
- 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
- package/src/cmp/Deploy.ts +11 -6
- package/src/helpers/semver.ts +3 -1
|
@@ -533,7 +533,24 @@ class PrimaryUtilityTest < Minitest::Test
|
|
|
533
533
|
unwrap(@utility.prepare_auth.call(ctx))
|
|
534
534
|
end
|
|
535
535
|
|
|
536
|
-
|
|
536
|
+
# The corpus writes the credential as `headers.authorization`: a
|
|
537
|
+
# PLACEHOLDER each runner points at the container and name this API
|
|
538
|
+
# actually uses.
|
|
539
|
+
cred = auth_credential(auth_client)
|
|
540
|
+
refute_nil cred, "prepare_auth placed no credential in headers or query"
|
|
541
|
+
|
|
542
|
+
# An absent section is runsection's report to make.
|
|
543
|
+
section = @spec.is_a?(Hash) ? @spec["prepareAuth"] : nil
|
|
544
|
+
swap = section.is_a?(Hash) &&
|
|
545
|
+
!("headers" == cred["where"] && "authorization" == cred["name"])
|
|
546
|
+
original = swap ? section["basic"] : nil
|
|
547
|
+
section["basic"] = retarget_auth(original, cred) if swap
|
|
548
|
+
|
|
549
|
+
begin
|
|
550
|
+
runsection("prepareAuth", subject)
|
|
551
|
+
ensure
|
|
552
|
+
section["basic"] = original if swap
|
|
553
|
+
end
|
|
537
554
|
end
|
|
538
555
|
|
|
539
556
|
|
|
@@ -650,6 +667,47 @@ class PrimaryUtilityTest < Minitest::Test
|
|
|
650
667
|
utility.make_context.call(ctxmap, client.get_root_ctx)
|
|
651
668
|
end
|
|
652
669
|
|
|
670
|
+
# Where this SDK's prepare_auth actually puts the credential: the name is
|
|
671
|
+
# the API's OWN scheme name, not always `authorization`, so it is
|
|
672
|
+
# discovered by running prepare_auth once.
|
|
673
|
+
def auth_credential(client)
|
|
674
|
+
utility = client.get_utility
|
|
675
|
+
ctx = make_test_ctx(client, utility, nil)
|
|
676
|
+
ctx.spec = ProjectNameSpec.new({ "headers" => {}, "query" => {}, "step" => "s" })
|
|
677
|
+
_, err = utility.prepare_auth.call(ctx)
|
|
678
|
+
return nil unless err.nil?
|
|
679
|
+
["headers", "query"].each do |where|
|
|
680
|
+
# A cookie credential rides the header bag, because a cookie IS a header.
|
|
681
|
+
bag = "query" == where ? ctx.spec.query : ctx.spec.headers
|
|
682
|
+
bag.each { |name, _value| return { "where" => where, "name" => name } }
|
|
683
|
+
end
|
|
684
|
+
nil
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
# Rename the corpus's `headers` bag to the real container, and the
|
|
688
|
+
# `authorization` key inside it to the real credential name. Applied only
|
|
689
|
+
# to the prepareAuth section, so real header assertions elsewhere are
|
|
690
|
+
# untouched.
|
|
691
|
+
def retarget_auth(node, cred)
|
|
692
|
+
return node.map { |child| retarget_auth(child, cred) } if node.is_a?(Array)
|
|
693
|
+
return node unless node.is_a?(Hash)
|
|
694
|
+
|
|
695
|
+
out = {}
|
|
696
|
+
node.each do |key, value|
|
|
697
|
+
if "headers" == key
|
|
698
|
+
bag = {}
|
|
699
|
+
(value.is_a?(Hash) ? value : {}).each do |bagkey, bagvalue|
|
|
700
|
+
name = "authorization" == bagkey ? cred["name"] : bagkey
|
|
701
|
+
bag[name] = retarget_auth(bagvalue, cred)
|
|
702
|
+
end
|
|
703
|
+
out[cred["where"]] = bag
|
|
704
|
+
else
|
|
705
|
+
out[key] = retarget_auth(value, cred)
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
out
|
|
709
|
+
end
|
|
710
|
+
|
|
653
711
|
# === Helper: make_test_full_ctx ===
|
|
654
712
|
def make_test_full_ctx(client, utility)
|
|
655
713
|
ctx = make_test_ctx(client, utility, nil)
|
|
@@ -5,6 +5,42 @@ require 'uri'
|
|
|
5
5
|
require 'json'
|
|
6
6
|
|
|
7
7
|
module ProjectNameUtilities
|
|
8
|
+
# Started Net::HTTP connections keyed by scheme, host, port and proxy,
|
|
9
|
+
# checked out for one request and returned. A Net::HTTP serves one
|
|
10
|
+
# request at a time, so the pool holds idle ones rather than sharing one.
|
|
11
|
+
HTTP_POOL = Hash.new { |h, k| h[k] = [] }
|
|
12
|
+
HTTP_POOL_LOCK = Mutex.new
|
|
13
|
+
HTTP_POOL_IDLE_MAX = 4
|
|
14
|
+
|
|
15
|
+
def self.http_checkout(key, uri, proxy)
|
|
16
|
+
http = HTTP_POOL_LOCK.synchronize { HTTP_POOL[key].pop }
|
|
17
|
+
return http if http && http.started?
|
|
18
|
+
|
|
19
|
+
if proxy
|
|
20
|
+
purl = URI.parse(proxy)
|
|
21
|
+
http = Net::HTTP.new(uri.host, uri.port, purl.host, purl.port, purl.user, purl.password)
|
|
22
|
+
else
|
|
23
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
24
|
+
end
|
|
25
|
+
http.use_ssl = (uri.scheme == "https")
|
|
26
|
+
http.start
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.http_checkin(key, http)
|
|
30
|
+
kept = HTTP_POOL_LOCK.synchronize do
|
|
31
|
+
HTTP_POOL[key].size < HTTP_POOL_IDLE_MAX && HTTP_POOL[key].push(http)
|
|
32
|
+
end
|
|
33
|
+
http.finish unless kept
|
|
34
|
+
rescue IOError
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.http_discard(http)
|
|
39
|
+
http.finish if http.started?
|
|
40
|
+
rescue IOError
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
8
44
|
DefaultHttpFetch = ->(fullurl, fetchdef) {
|
|
9
45
|
method_str = fetchdef["method"] || "GET"
|
|
10
46
|
body_str = fetchdef["body"]
|
|
@@ -12,8 +48,9 @@ module ProjectNameUtilities
|
|
|
12
48
|
|
|
13
49
|
begin
|
|
14
50
|
uri = URI.parse(fullurl)
|
|
15
|
-
|
|
16
|
-
|
|
51
|
+
proxy = fetchdef["proxy"]
|
|
52
|
+
proxy = nil unless proxy.is_a?(String) && !proxy.empty?
|
|
53
|
+
key = [uri.scheme, uri.host, uri.port, proxy]
|
|
17
54
|
|
|
18
55
|
klass = case method_str.upcase
|
|
19
56
|
when "POST" then Net::HTTP::Post
|
|
@@ -35,7 +72,16 @@ module ProjectNameUtilities
|
|
|
35
72
|
request['User-Agent'] = 'Mozilla/5.0 (compatible; ProjectNameSDK/1.0)' unless has_ua
|
|
36
73
|
request.body = body_str if body_str.is_a?(String)
|
|
37
74
|
|
|
38
|
-
|
|
75
|
+
http = ProjectNameUtilities.http_checkout(key, uri, proxy)
|
|
76
|
+
begin
|
|
77
|
+
# Net::HTTP retries idempotent requests; an EOF can follow a committed POST.
|
|
78
|
+
resp = http.request(request)
|
|
79
|
+
rescue StandardError
|
|
80
|
+
ProjectNameUtilities.http_discard(http)
|
|
81
|
+
raise
|
|
82
|
+
end
|
|
83
|
+
ProjectNameUtilities.http_checkin(key, http)
|
|
84
|
+
|
|
39
85
|
resp_headers = {}
|
|
40
86
|
resp.each_header { |k, v| resp_headers[k.downcase] = v }
|
|
41
87
|
|
|
@@ -14,7 +14,7 @@ module ProjectNameUtilities
|
|
|
14
14
|
if items
|
|
15
15
|
items.each do |item|
|
|
16
16
|
key, val = item[0], item[1]
|
|
17
|
-
out[key] = val if val && key.is_a?(String) && !params.include?(key)
|
|
17
|
+
out[key] = val if val && key.is_a?(String) && key != "$action" && !params.include?(key)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
out
|
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
require_relative 'struct/voxgig_struct'
|
|
3
3
|
require_relative '../core/helpers'
|
|
4
4
|
module ProjectNameUtilities
|
|
5
|
+
# `$action` selects the point (see MakePoint); it is never an API field, so
|
|
6
|
+
# the body is a copy without it. The caller's hash is left untouched.
|
|
7
|
+
def self.strip_action(reqdata)
|
|
8
|
+
return reqdata unless reqdata.is_a?(Hash) && reqdata.key?("$action")
|
|
9
|
+
reqdata.reject { |k, _| k == "$action" }
|
|
10
|
+
end
|
|
11
|
+
|
|
5
12
|
TransformRequest = ->(ctx) {
|
|
6
13
|
spec = ctx.spec
|
|
7
14
|
point = ctx.point
|
|
8
15
|
spec.step = "reqform" if spec
|
|
9
16
|
transform = ProjectNameHelpers.to_map(VoxgigStruct.getprop(point, "transform"))
|
|
10
|
-
return ctx.reqdata unless transform
|
|
17
|
+
return ProjectNameUtilities.strip_action(ctx.reqdata) unless transform
|
|
11
18
|
reqform = VoxgigStruct.getprop(transform, "req")
|
|
12
|
-
return ctx.reqdata unless reqform
|
|
13
|
-
VoxgigStruct.transform({ "reqdata" => ctx.reqdata }, reqform)
|
|
19
|
+
return ProjectNameUtilities.strip_action(ctx.reqdata) unless reqform
|
|
20
|
+
ProjectNameUtilities.strip_action(VoxgigStruct.transform({ "reqdata" => ctx.reqdata }, reqform))
|
|
14
21
|
}
|
|
15
22
|
end
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
use std::cell::RefCell;
|
|
3
2
|
use std::collections::HashMap;
|
|
4
3
|
use std::rc::Rc;
|
|
@@ -101,7 +100,7 @@ impl Context {
|
|
|
101
100
|
Rc::new(RefCell::new(c))
|
|
102
101
|
} else if let Some(co) = ctxspec.ctrl_obj {
|
|
103
102
|
co
|
|
104
|
-
} else if let Some(b) = basectx {
|
|
103
|
+
} else if let Some(b) = basectx.filter(|_| ctxspec.opname.is_none()) {
|
|
105
104
|
b.ctrl.borrow().clone()
|
|
106
105
|
} else {
|
|
107
106
|
Rc::new(RefCell::new(Control::new()))
|
|
@@ -249,10 +248,7 @@ impl Context {
|
|
|
249
248
|
return Rc::new(Operation::new(&Value::empty_map()));
|
|
250
249
|
}
|
|
251
250
|
|
|
252
|
-
let opcfg = getpath(
|
|
253
|
-
&["entity", &entname, "op", opname],
|
|
254
|
-
&self.config.borrow(),
|
|
255
|
-
);
|
|
251
|
+
let opcfg = getpath(&["entity", &entname, "op", opname], &self.config.borrow());
|
|
256
252
|
|
|
257
253
|
let input = if opname == "update" || opname == "create" {
|
|
258
254
|
"data"
|
|
@@ -7,6 +7,7 @@ use crate::core::spec::Spec;
|
|
|
7
7
|
use crate::core::helpers::{get_bool, getp, getpath, setp};
|
|
8
8
|
use crate::core::types::Feature;
|
|
9
9
|
use crate::feature::support::*;
|
|
10
|
+
use crate::utility::voxgigstruct::ordered_map::OrderedMap;
|
|
10
11
|
use crate::utility::voxgigstruct::Value;
|
|
11
12
|
|
|
12
13
|
pub struct PagingFeature {
|
|
@@ -193,7 +194,12 @@ impl Feature for PagingFeature {
|
|
|
193
194
|
if !cursor.is_noval() && !cursor.is_null() {
|
|
194
195
|
setp(&query, &cursor_param, cursor);
|
|
195
196
|
} else if getp(&query, &page_param).is_noval() {
|
|
196
|
-
|
|
197
|
+
// A record written back by pre_result holds the page just fetched
|
|
198
|
+
// as "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
199
|
+
let mut page = getp(&paging, "nextPage");
|
|
200
|
+
if page.is_noval() || page.is_null() {
|
|
201
|
+
page = getp(&paging, "page");
|
|
202
|
+
}
|
|
197
203
|
if !page.is_noval() && !page.is_null() {
|
|
198
204
|
setp(&query, &page_param, page);
|
|
199
205
|
} else {
|
|
@@ -281,12 +287,16 @@ impl Feature for PagingFeature {
|
|
|
281
287
|
}
|
|
282
288
|
}
|
|
283
289
|
|
|
284
|
-
// Body-level
|
|
290
|
+
// Body-level signals; snake_case forms are read first so camelCase wins.
|
|
285
291
|
if let Value::Map(_) = body {
|
|
286
292
|
let next = getp(&body, "next");
|
|
287
293
|
if !next.is_noval() && !next.is_null() && getp(&paging, "next").is_noval() {
|
|
288
294
|
setp(&paging, "next", next);
|
|
289
295
|
}
|
|
296
|
+
let snake_cursor = getp(&body, "next_cursor");
|
|
297
|
+
if !snake_cursor.is_noval() && !snake_cursor.is_null() {
|
|
298
|
+
setp(&paging, "cursor", snake_cursor);
|
|
299
|
+
}
|
|
290
300
|
let cursor = getp(&body, "cursor");
|
|
291
301
|
if !cursor.is_noval() && !cursor.is_null() {
|
|
292
302
|
setp(&paging, "cursor", cursor);
|
|
@@ -295,6 +305,19 @@ impl Feature for PagingFeature {
|
|
|
295
305
|
if !next_cursor.is_noval() && !next_cursor.is_null() {
|
|
296
306
|
setp(&paging, "cursor", next_cursor);
|
|
297
307
|
}
|
|
308
|
+
if getp(&paging, "nextPage").is_noval() {
|
|
309
|
+
let mut np = getp(&body, "nextPage");
|
|
310
|
+
if np.is_noval() || np.is_null() {
|
|
311
|
+
np = getp(&body, "next_page");
|
|
312
|
+
}
|
|
313
|
+
if matches!(np, Value::Num(_) | Value::Str(_)) {
|
|
314
|
+
setp(&paging, "nextPage", np);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if let Some(has_more) = get_bool(&body, "has_more") {
|
|
318
|
+
setp(&paging, "hasMore", Value::Bool(has_more));
|
|
319
|
+
explicit_more = true;
|
|
320
|
+
}
|
|
298
321
|
if let Some(has_more) = get_bool(&body, "hasMore") {
|
|
299
322
|
setp(&paging, "hasMore", Value::Bool(has_more));
|
|
300
323
|
explicit_more = true;
|
|
@@ -316,6 +339,24 @@ impl Feature for PagingFeature {
|
|
|
316
339
|
}
|
|
317
340
|
|
|
318
341
|
result.borrow_mut().paging = paging.clone();
|
|
319
|
-
self.last = paging;
|
|
342
|
+
self.last = paging.clone();
|
|
343
|
+
|
|
344
|
+
// Value::Map is Rc-shared, so the caller's map is refilled in place;
|
|
345
|
+
// a fresh map on ctrl never reaches the caller.
|
|
346
|
+
let ctrl = ctx.ctrl.borrow().clone();
|
|
347
|
+
let shared = ctrl.borrow().paging.clone();
|
|
348
|
+
if let Value::Map(m) = &shared {
|
|
349
|
+
let entries: Vec<(String, Value)> = match &paging {
|
|
350
|
+
Value::Map(pm) => pm.borrow().iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
|
|
351
|
+
_ => Vec::new(),
|
|
352
|
+
};
|
|
353
|
+
let mut mm = m.borrow_mut();
|
|
354
|
+
*mm = OrderedMap::new();
|
|
355
|
+
for (k, v) in entries {
|
|
356
|
+
mm.insert(k, v);
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
ctrl.borrow_mut().paging = paging;
|
|
360
|
+
}
|
|
320
361
|
}
|
|
321
362
|
}
|
|
@@ -754,7 +754,7 @@ fn raw_exchange_fetch(fullurl: &str, fetchdef: &Value) -> Result<Value, ProjectN
|
|
|
754
754
|
.filter(|m| !m.is_empty())
|
|
755
755
|
.unwrap_or_else(|| "POST".to_string());
|
|
756
756
|
|
|
757
|
-
let agent =
|
|
757
|
+
let agent = crate::utility::fetcher::default_agent();
|
|
758
758
|
let mut req = agent.request(&method, fullurl);
|
|
759
759
|
|
|
760
760
|
if let Value::Map(m) = getp(fetchdef, "headers") {
|
|
@@ -80,6 +80,61 @@ fn resolve_match(ctx: &Rc<Context>, explicit: &Value) -> Value {
|
|
|
80
80
|
Value::empty_map()
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
fn point_terminal(p: &Value) -> bool {
|
|
84
|
+
match getp(p, "parts") {
|
|
85
|
+
Value::List(l) => match l.borrow().last() {
|
|
86
|
+
Some(Value::Str(s)) => s.starts_with('{'),
|
|
87
|
+
_ => false,
|
|
88
|
+
},
|
|
89
|
+
_ => false,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn point_depth(p: &Value) -> usize {
|
|
94
|
+
match getp(p, "parts") {
|
|
95
|
+
Value::List(l) => l.borrow().len(),
|
|
96
|
+
_ => 0,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The entity's own endpoint: a terminal `{param}` marks a record route, and
|
|
101
|
+
// among equals the shallower path wins (the same rule as make_point).
|
|
102
|
+
fn pick_point(points: &Value) -> Value {
|
|
103
|
+
let list: Vec<Value> = match points {
|
|
104
|
+
Value::List(l) => l.borrow().clone(),
|
|
105
|
+
_ => return Value::Noval,
|
|
106
|
+
};
|
|
107
|
+
let mut point = match list.first() {
|
|
108
|
+
Some(p) => p.clone(),
|
|
109
|
+
None => return Value::Noval,
|
|
110
|
+
};
|
|
111
|
+
for cand in list.iter().skip(1) {
|
|
112
|
+
if point_terminal(cand) != point_terminal(&point) {
|
|
113
|
+
if point_terminal(cand) {
|
|
114
|
+
point = cand.clone();
|
|
115
|
+
}
|
|
116
|
+
} else if point_depth(cand) < point_depth(&point) {
|
|
117
|
+
point = cand.clone();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
point
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn reqd_names(point: &Value, kind: &str) -> Value {
|
|
124
|
+
let args_path = crate::core::helpers::getpath(&["args", kind], point);
|
|
125
|
+
let reqd_args = vs::select(&args_path, &jo(vec![("reqd", Value::Bool(true))]));
|
|
126
|
+
vs::transform(
|
|
127
|
+
&reqd_args,
|
|
128
|
+
&ja(vec![
|
|
129
|
+
Value::str("`$EACH`"),
|
|
130
|
+
Value::str(""),
|
|
131
|
+
Value::str("`$KEY.name`"),
|
|
132
|
+
]),
|
|
133
|
+
None,
|
|
134
|
+
)
|
|
135
|
+
.unwrap_or_else(|_| Value::empty_list())
|
|
136
|
+
}
|
|
137
|
+
|
|
83
138
|
fn build_args(ctx: &Rc<Context>, args: &Value) -> Value {
|
|
84
139
|
let op = ctx.op.borrow().clone();
|
|
85
140
|
let opname = op.name.clone();
|
|
@@ -88,26 +143,16 @@ fn build_args(ctx: &Rc<Context>, args: &Value) -> Value {
|
|
|
88
143
|
None => op.entity.clone(),
|
|
89
144
|
};
|
|
90
145
|
|
|
91
|
-
// Get last point from config.
|
|
92
146
|
let points = crate::core::helpers::getpath(
|
|
93
147
|
&["entity", &entname, "op", &opname, "points"],
|
|
94
148
|
&ctx.config.borrow(),
|
|
95
149
|
);
|
|
96
|
-
let point =
|
|
150
|
+
let point = pick_point(&points);
|
|
97
151
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
let reqd_params =
|
|
101
|
-
let
|
|
102
|
-
&reqd_params,
|
|
103
|
-
&ja(vec![
|
|
104
|
-
Value::str("`$EACH`"),
|
|
105
|
-
Value::str(""),
|
|
106
|
-
Value::str("`$KEY.name`"),
|
|
107
|
-
]),
|
|
108
|
-
None,
|
|
109
|
-
)
|
|
110
|
-
.unwrap_or_else(|_| Value::empty_list());
|
|
152
|
+
// Path AND query: a path-only read misses a query-addressed record
|
|
153
|
+
// (e.g. GET /result?trace_id=), which has no path param at all.
|
|
154
|
+
let reqd_params = reqd_names(&point, "params");
|
|
155
|
+
let reqd_query = reqd_names(&point, "query");
|
|
111
156
|
|
|
112
157
|
let qand = Value::empty_list();
|
|
113
158
|
let q = jo(vec![("`$AND`", qand.clone())]);
|
|
@@ -116,8 +161,8 @@ fn build_args(ctx: &Rc<Context>, args: &Value) -> Value {
|
|
|
116
161
|
let keys = vs::keysof_vec(args);
|
|
117
162
|
for key in keys {
|
|
118
163
|
let is_id = key == "id";
|
|
119
|
-
let
|
|
120
|
-
|
|
164
|
+
let is_reqd = !vs::is_empty(&vs::select(&reqd_params, &Value::str(key.clone())))
|
|
165
|
+
|| !vs::is_empty(&vs::select(&reqd_query, &Value::str(key.clone())));
|
|
121
166
|
|
|
122
167
|
if is_id || is_reqd {
|
|
123
168
|
let v = ctx.util().param(ctx, &Value::str(key.clone()));
|
|
@@ -215,28 +260,16 @@ fn test_fetch(
|
|
|
215
260
|
};
|
|
216
261
|
let args = build_args(ctx, &update_match);
|
|
217
262
|
let found = vs::select(&entmap, &args);
|
|
218
|
-
let
|
|
219
|
-
if (ent.is_noval() || ent.is_null()) && vs::size(&entmap) > 0 {
|
|
220
|
-
// Fall back to any entity in the fixture.
|
|
221
|
-
for (_k, e) in vs::items_vec(&entmap) {
|
|
222
|
-
if let Value::Map(_) = e {
|
|
223
|
-
ent = e;
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
263
|
+
let ent = vs::get_elem(&found, &Value::Num(0.0), Value::Noval);
|
|
228
264
|
if ent.is_noval() || ent.is_null() {
|
|
265
|
+
// update miss: 404, never another record
|
|
229
266
|
return Ok(respond(ctx, 404,
|
|
230
267
|
Value::Noval,
|
|
231
268
|
vec![("statusText", Value::str("Not found"))],
|
|
232
269
|
));
|
|
233
270
|
}
|
|
234
|
-
if let Value::Map(_) = &ent {
|
|
235
|
-
|
|
236
|
-
for (k, v) in rm.borrow().iter() {
|
|
237
|
-
setp(&ent, k, v.clone());
|
|
238
|
-
}
|
|
239
|
-
}
|
|
271
|
+
if let (Value::Map(_), Value::Map(_)) = (&ent, &reqdata) {
|
|
272
|
+
vs::merge(&Value::list(vec![ent.clone(), reqdata.clone()]), None);
|
|
240
273
|
}
|
|
241
274
|
vs::del_prop(ent.clone(), &Value::str("$KEY"));
|
|
242
275
|
Ok(respond(ctx, 200, vs::clone(&ent), vec![]))
|
|
@@ -1,29 +1,56 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
1
2
|
use std::rc::Rc;
|
|
3
|
+
use std::sync::{Mutex, OnceLock};
|
|
2
4
|
|
|
3
5
|
use crate::core::context::Context;
|
|
4
6
|
use crate::core::error::ProjectNameError;
|
|
5
7
|
use crate::core::helpers::{call_vfn, get_str, getp, getpath, ja, jo, json_thunk, setp};
|
|
6
8
|
use crate::utility::voxgigstruct::Value;
|
|
7
9
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
10
|
+
// One agent per process: ureq pools keep-alive connections per agent, so
|
|
11
|
+
// an agent per request reused nothing. Cloning shares the pool.
|
|
12
|
+
pub(crate) fn default_agent() -> ureq::Agent {
|
|
13
|
+
static AGENT: OnceLock<ureq::Agent> = OnceLock::new();
|
|
14
|
+
AGENT.get_or_init(|| ureq::AgentBuilder::new().build()).clone()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Agents keyed by the `proxy` annotation (set by the proxy feature) and
|
|
18
|
+
// manual redirects, for requests the default agent cannot serve.
|
|
19
|
+
fn agent_for(fetchdef: &Value) -> ureq::Agent {
|
|
20
|
+
let proxy = get_str(fetchdef, "proxy").filter(|p| !p.is_empty());
|
|
21
|
+
let manual = get_str(fetchdef, "redirect").as_deref() == Some("manual");
|
|
22
|
+
if proxy.is_none() && !manual {
|
|
23
|
+
return default_agent();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static AGENTS: OnceLock<Mutex<HashMap<(Option<String>, bool), ureq::Agent>>> =
|
|
27
|
+
OnceLock::new();
|
|
28
|
+
let mut agents = AGENTS
|
|
29
|
+
.get_or_init(|| Mutex::new(HashMap::new()))
|
|
30
|
+
.lock()
|
|
31
|
+
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
32
|
+
agents
|
|
33
|
+
.entry((proxy.clone(), manual))
|
|
34
|
+
.or_insert_with(|| {
|
|
35
|
+
let mut builder = ureq::AgentBuilder::new();
|
|
36
|
+
if let Some(p) = proxy.as_deref().and_then(|p| ureq::Proxy::new(p).ok()) {
|
|
37
|
+
builder = builder.proxy(p);
|
|
38
|
+
}
|
|
39
|
+
if manual {
|
|
40
|
+
builder = builder.redirects(0);
|
|
41
|
+
}
|
|
42
|
+
builder.build()
|
|
43
|
+
})
|
|
44
|
+
.clone()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Default live transport: ureq (blocking, minimal).
|
|
11
48
|
fn default_http_fetch(fullurl: &str, fetchdef: &Value) -> Result<Value, ProjectNameError> {
|
|
12
49
|
let method = get_str(fetchdef, "method")
|
|
13
50
|
.filter(|m| !m.is_empty())
|
|
14
51
|
.unwrap_or_else(|| "GET".to_string());
|
|
15
52
|
|
|
16
|
-
let
|
|
17
|
-
if let Some(proxy) = get_str(fetchdef, "proxy").filter(|p| !p.is_empty()) {
|
|
18
|
-
if let Ok(p) = ureq::Proxy::new(&proxy) {
|
|
19
|
-
builder = builder.proxy(p);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if get_str(fetchdef, "redirect").as_deref() == Some("manual") {
|
|
23
|
-
builder = builder.redirects(0);
|
|
24
|
-
}
|
|
25
|
-
let agent = builder.build();
|
|
26
|
-
|
|
53
|
+
let agent = agent_for(fetchdef);
|
|
27
54
|
let mut req = agent.request(&method, fullurl);
|
|
28
55
|
|
|
29
56
|
let mut has_ua = false;
|
|
@@ -37,7 +37,7 @@ pub fn prepare_query_util(ctx: &Rc<Context>) -> Value {
|
|
|
37
37
|
.map(|(k, v)| (k.clone(), v.clone()))
|
|
38
38
|
.collect();
|
|
39
39
|
for (key, val) in entries {
|
|
40
|
-
if !val.is_noval() && !val.is_null() && !contains(&key) {
|
|
40
|
+
if !val.is_noval() && !val.is_null() && "$action" != key && !contains(&key) {
|
|
41
41
|
setp(&out, &key, val);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
use std::rc::Rc;
|
|
2
2
|
|
|
3
3
|
use crate::core::context::Context;
|
|
4
|
-
use crate::core::helpers::{getp, jo, to_map};
|
|
4
|
+
use crate::core::helpers::{getp, jo, setp, to_map};
|
|
5
5
|
use crate::utility::voxgigstruct as vs;
|
|
6
6
|
use crate::utility::voxgigstruct::Value;
|
|
7
7
|
|
|
8
|
+
// `$action` selects the point (see make_point_util); it is never an API
|
|
9
|
+
// field, so the body is a copy without it. The caller's map is left untouched.
|
|
10
|
+
fn strip_action(reqdata: Value) -> Value {
|
|
11
|
+
let has = match &reqdata {
|
|
12
|
+
Value::Map(src) => src.borrow().contains_key("$action"),
|
|
13
|
+
_ => false,
|
|
14
|
+
};
|
|
15
|
+
if !has {
|
|
16
|
+
return reqdata;
|
|
17
|
+
}
|
|
18
|
+
let body = Value::empty_map();
|
|
19
|
+
if let Value::Map(src) = &reqdata {
|
|
20
|
+
let entries: Vec<(String, Value)> = src
|
|
21
|
+
.borrow()
|
|
22
|
+
.iter()
|
|
23
|
+
.map(|(k, v)| (k.clone(), v.clone()))
|
|
24
|
+
.collect();
|
|
25
|
+
for (k, v) in entries {
|
|
26
|
+
if "$action" != k {
|
|
27
|
+
setp(&body, &k, v);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
body
|
|
32
|
+
}
|
|
33
|
+
|
|
8
34
|
pub fn transform_request_util(ctx: &Rc<Context>) -> Value {
|
|
9
35
|
let spec = ctx.spec.borrow().clone();
|
|
10
36
|
let point = ctx.point.borrow().clone();
|
|
@@ -15,17 +41,17 @@ pub fn transform_request_util(ctx: &Rc<Context>) -> Value {
|
|
|
15
41
|
|
|
16
42
|
let transform = to_map(&getp(&point, "transform"));
|
|
17
43
|
if transform.is_noval() {
|
|
18
|
-
return ctx.reqdata.borrow().clone();
|
|
44
|
+
return strip_action(ctx.reqdata.borrow().clone());
|
|
19
45
|
}
|
|
20
46
|
|
|
21
47
|
let reqform = getp(&transform, "req");
|
|
22
48
|
if reqform.is_noval() || reqform.is_null() {
|
|
23
|
-
return ctx.reqdata.borrow().clone();
|
|
49
|
+
return strip_action(ctx.reqdata.borrow().clone());
|
|
24
50
|
}
|
|
25
51
|
|
|
26
52
|
let store = jo(vec![("reqdata", ctx.reqdata.borrow().clone())]);
|
|
27
|
-
match vs::transform(&store, &reqform, None) {
|
|
53
|
+
strip_action(match vs::transform(&store, &reqform, None) {
|
|
28
54
|
Ok(reqdata) => reqdata,
|
|
29
55
|
Err(_) => ctx.reqdata.borrow().clone(),
|
|
30
|
-
}
|
|
56
|
+
})
|
|
31
57
|
}
|
|
@@ -51,7 +51,8 @@ class Context(ctxmap: JMap[String, Object], basectx: Context) {
|
|
|
51
51
|
case c: Control => ctrl = c
|
|
52
52
|
case _ =>
|
|
53
53
|
}
|
|
54
|
-
} else if (basectx != null && basectx.ctrl != null
|
|
54
|
+
} else if (basectx != null && basectx.ctrl != null
|
|
55
|
+
&& Helpers.getCtxProp(ctxmap, "opname") == null) {
|
|
55
56
|
ctrl = basectx.ctrl
|
|
56
57
|
}
|
|
57
58
|
|