@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
|
@@ -164,24 +164,12 @@ func (f *TestFeature) Init(ctx *core.Context, options map[string]any) {
|
|
|
164
164
|
args := self.buildArgs(ctx, op, updateMatch)
|
|
165
165
|
found := vs.Select(entmap, args)
|
|
166
166
|
ent := vs.GetElem(found, 0)
|
|
167
|
-
if ent == nil && entmap != nil {
|
|
168
|
-
for _, e := range entmap {
|
|
169
|
-
if _, ok := e.(map[string]any); ok {
|
|
170
|
-
ent = e
|
|
171
|
-
break
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
167
|
if ent == nil {
|
|
168
|
+
// update miss: 404, never another record
|
|
176
169
|
return respond(404, nil, map[string]any{"statusText": "Not found"}), nil
|
|
177
170
|
}
|
|
178
|
-
if
|
|
179
|
-
|
|
180
|
-
if reqdata != nil {
|
|
181
|
-
for k, v := range reqdata {
|
|
182
|
-
entm[k] = v
|
|
183
|
-
}
|
|
184
|
-
}
|
|
171
|
+
if _, ok := ent.(map[string]any); ok && ctx.Reqdata != nil {
|
|
172
|
+
vs.Merge([]any{ent, ctx.Reqdata})
|
|
185
173
|
}
|
|
186
174
|
vs.DelProp(ent, "$KEY")
|
|
187
175
|
out := vs.Clone(ent)
|
|
@@ -428,6 +428,7 @@ func TestFeatureCorpus(t *testing.T) {
|
|
|
428
428
|
"feature": []any{map[string]any{"name": name, "active": true}},
|
|
429
429
|
})
|
|
430
430
|
if fcRecord(probe, name) == nil {
|
|
431
|
+
t.Logf("feature.%s: inert (this SDK does not generate the feature)", name)
|
|
431
432
|
t.Skipf("this SDK was generated without the %s feature", name)
|
|
432
433
|
}
|
|
433
434
|
|
|
@@ -5,6 +5,7 @@ import (
|
|
|
5
5
|
"os"
|
|
6
6
|
"reflect"
|
|
7
7
|
"regexp"
|
|
8
|
+
"strconv"
|
|
8
9
|
"strings"
|
|
9
10
|
"testing"
|
|
10
11
|
"time"
|
|
@@ -1042,6 +1043,59 @@ func TestFeaturePaging(t *testing.T) {
|
|
|
1042
1043
|
}
|
|
1043
1044
|
})
|
|
1044
1045
|
|
|
1046
|
+
t.Run("snake-case-signals-and-ctrl-write-back", func(t *testing.T) {
|
|
1047
|
+
rec := &fhRecorder{reply: func(n int, _ map[string]any) (any, error) {
|
|
1048
|
+
if n == 1 {
|
|
1049
|
+
return fhResponse(200, map[string]any{"has_more": true, "next_cursor": "c2"}, nil), nil
|
|
1050
|
+
}
|
|
1051
|
+
return fhResponse(200, map[string]any{"has_more": false}, nil), nil
|
|
1052
|
+
}}
|
|
1053
|
+
h := fhMake(rec.fetch, fhF(feat.NewPagingFeature(), nil))
|
|
1054
|
+
pg := map[string]any{}
|
|
1055
|
+
ctrl := map[string]any{"paging": pg}
|
|
1056
|
+
res := h.op(fhOpSpec{op: "list", path: "/w", ctrl: ctrl})
|
|
1057
|
+
if res.result.Paging["hasMore"] != true {
|
|
1058
|
+
t.Errorf("expected has_more read, got %v", res.result.Paging["hasMore"])
|
|
1059
|
+
}
|
|
1060
|
+
if res.result.Paging["cursor"] != "c2" {
|
|
1061
|
+
t.Errorf("expected next_cursor read, got %v", res.result.Paging["cursor"])
|
|
1062
|
+
}
|
|
1063
|
+
if pg["cursor"] != "c2" || pg["hasMore"] != true {
|
|
1064
|
+
t.Errorf("expected record written back into ctrl, got %v", pg)
|
|
1065
|
+
}
|
|
1066
|
+
h.op(fhOpSpec{op: "list", path: "/w", ctrl: ctrl})
|
|
1067
|
+
if !strings.Contains(rec.url(1), "cursor=c2") {
|
|
1068
|
+
t.Errorf("expected written-back cursor sent, got %s", rec.url(1))
|
|
1069
|
+
}
|
|
1070
|
+
if pg["hasMore"] != false || pg["cursor"] != nil {
|
|
1071
|
+
t.Errorf("expected ctrl to carry the last record, got %v", pg)
|
|
1072
|
+
}
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
t.Run("continues-from-written-back-next-page", func(t *testing.T) {
|
|
1076
|
+
rec := &fhRecorder{reply: func(n int, _ map[string]any) (any, error) {
|
|
1077
|
+
body := map[string]any{}
|
|
1078
|
+
if n == 1 {
|
|
1079
|
+
body["next_page"] = 2
|
|
1080
|
+
}
|
|
1081
|
+
return fhResponse(200, body, map[string]any{"x-page": strconv.Itoa(n)}), nil
|
|
1082
|
+
}}
|
|
1083
|
+
h := fhMake(rec.fetch, fhF(feat.NewPagingFeature(), nil))
|
|
1084
|
+
pg := map[string]any{}
|
|
1085
|
+
ctrl := map[string]any{"paging": pg}
|
|
1086
|
+
h.op(fhOpSpec{op: "list", path: "/w", ctrl: ctrl})
|
|
1087
|
+
if pg["page"] != 1 || pg["nextPage"] != 2 || pg["hasMore"] != true {
|
|
1088
|
+
t.Errorf("expected next_page read and written back, got %v", pg)
|
|
1089
|
+
}
|
|
1090
|
+
h.op(fhOpSpec{op: "list", path: "/w", ctrl: ctrl})
|
|
1091
|
+
if !strings.Contains(rec.url(1), "page=2") {
|
|
1092
|
+
t.Errorf("expected written-back next page sent, got %s", rec.url(1))
|
|
1093
|
+
}
|
|
1094
|
+
if pg["hasMore"] != false {
|
|
1095
|
+
t.Errorf("expected no more pages, got %v", pg)
|
|
1096
|
+
}
|
|
1097
|
+
})
|
|
1098
|
+
|
|
1045
1099
|
t.Run("non-list-not-paged", func(t *testing.T) {
|
|
1046
1100
|
rec := &fhRecorder{}
|
|
1047
1101
|
h := fhMake(rec.fetch, fhF(feat.NewPagingFeature(), nil))
|
|
@@ -2,6 +2,7 @@ package sdktest
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
import (
|
|
5
|
+
"regexp"
|
|
5
6
|
"testing"
|
|
6
7
|
|
|
7
8
|
sdk "GOMODULE"
|
|
@@ -464,13 +465,105 @@ func TestPipelineFeatureOrder(t *testing.T) {
|
|
|
464
465
|
})
|
|
465
466
|
}
|
|
466
467
|
|
|
468
|
+
// plAuthCookiePair matches a cookie credential as PrepareAuth writes it:
|
|
469
|
+
// `<scheme>=K` for the probe key, with no scheme prefix and nothing else in
|
|
470
|
+
// the bag.
|
|
471
|
+
var plAuthCookiePair = regexp.MustCompile(`^[^=;]+=K$`)
|
|
472
|
+
|
|
473
|
+
// plAuthCred is where this SDK's PrepareAuth actually puts the credential.
|
|
474
|
+
// The name is the API's own scheme name, not always "authorization", so the
|
|
475
|
+
// cases below read it from a probe run instead of asserting a name. Pair is
|
|
476
|
+
// the `<scheme>=` lead-in of a COOKIE credential, which rides the header bag
|
|
477
|
+
// under the key "cookie" instead of taking a header of its own.
|
|
478
|
+
type plAuthCred struct {
|
|
479
|
+
where string
|
|
480
|
+
name string
|
|
481
|
+
value any
|
|
482
|
+
pair string
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// plAuthBag picks the container by placement. A cookie credential rides the
|
|
486
|
+
// header bag, because a cookie IS a header.
|
|
487
|
+
func plAuthBag(spec *sdk.Spec, where string) map[string]any {
|
|
488
|
+
if "query" == where {
|
|
489
|
+
return spec.Query
|
|
490
|
+
}
|
|
491
|
+
return spec.Headers
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// plAuth is the options auth block every case passes. `basic: false` is
|
|
495
|
+
// explicit: an HTTP Basic API's generated config carries `auth.basic: true`,
|
|
496
|
+
// and a client that merges it in takes a branch needing a secret as well.
|
|
497
|
+
// With none supplied that branch writes nothing, which the probe would then
|
|
498
|
+
// read as a public API.
|
|
499
|
+
func plAuth(prefix string) map[string]any {
|
|
500
|
+
return map[string]any{"prefix": prefix, "basic": false}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// plAuthCredential runs PrepareAuth once with both containers present and
|
|
504
|
+
// reports which one it wrote to, and under what name. Nil means this SDK
|
|
505
|
+
// places no credential at all - a public API - which is a legitimate shape.
|
|
506
|
+
func plAuthProbe(t *testing.T, sdkopts map[string]any) *plAuthCred {
|
|
507
|
+
t.Helper()
|
|
508
|
+
client, utility := plClient(t, sdkopts)
|
|
509
|
+
ctx := plCtx(client, utility, nil)
|
|
510
|
+
ctx.Spec = sdk.NewSpec(map[string]any{"step": "s"})
|
|
511
|
+
if _, err := utility.PrepareAuth(ctx); err != nil {
|
|
512
|
+
return nil
|
|
513
|
+
}
|
|
514
|
+
for _, where := range []string{"headers", "query"} {
|
|
515
|
+
for name, value := range plAuthBag(ctx.Spec, where) {
|
|
516
|
+
pair := ""
|
|
517
|
+
text, is := value.(string)
|
|
518
|
+
if "headers" == where && "cookie" == name && is &&
|
|
519
|
+
plAuthCookiePair.MatchString(text) {
|
|
520
|
+
pair = text[:len(text)-1]
|
|
521
|
+
}
|
|
522
|
+
return &plAuthCred{where: where, name: name, value: value, pair: pair}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return nil
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
func plAuthCredential(t *testing.T) *plAuthCred {
|
|
529
|
+
t.Helper()
|
|
530
|
+
return plAuthProbe(t, map[string]any{"apikey": "K", "auth": plAuth("Bearer")})
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// plAuthAnyCredential is every credential this SDK could possibly place: both
|
|
534
|
+
// credentials and Basic switched on, so whichever branch the API has,
|
|
535
|
+
// something lands unless the API is public.
|
|
536
|
+
func plAuthAnyCredential(t *testing.T) *plAuthCred {
|
|
537
|
+
t.Helper()
|
|
538
|
+
return plAuthProbe(t, map[string]any{
|
|
539
|
+
"apikey": "K",
|
|
540
|
+
"secret": "S",
|
|
541
|
+
"auth": map[string]any{"prefix": "Bearer", "basic": true},
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
|
|
467
545
|
func TestPipelinePrepareAuth(t *testing.T) {
|
|
468
546
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
547
|
+
cred := plAuthCredential(t)
|
|
548
|
+
|
|
549
|
+
// placed runs PrepareAuth over a fresh spec, seeding the credential slot
|
|
550
|
+
// first when seed is non-nil, and reports what is left there.
|
|
551
|
+
placed := func(t *testing.T, sdkopts map[string]any, seed any) (any, bool) {
|
|
552
|
+
t.Helper()
|
|
553
|
+
client, utility := plClient(t, sdkopts)
|
|
554
|
+
ctx := plCtx(client, utility, nil)
|
|
555
|
+
ctx.Spec = sdk.NewSpec(map[string]any{"step": "s"})
|
|
556
|
+
if cred != nil && seed != nil {
|
|
557
|
+
plAuthBag(ctx.Spec, cred.where)[cred.name] = seed
|
|
558
|
+
}
|
|
559
|
+
if _, err := utility.PrepareAuth(ctx); err != nil {
|
|
560
|
+
t.Fatalf("unexpected error: %v", err)
|
|
472
561
|
}
|
|
473
|
-
|
|
562
|
+
if cred == nil {
|
|
563
|
+
return nil, false
|
|
564
|
+
}
|
|
565
|
+
value, has := plAuthBag(ctx.Spec, cred.where)[cred.name]
|
|
566
|
+
return value, has
|
|
474
567
|
}
|
|
475
568
|
|
|
476
569
|
t.Run("guards-missing-spec", func(t *testing.T) {
|
|
@@ -482,86 +575,99 @@ func TestPipelinePrepareAuth(t *testing.T) {
|
|
|
482
575
|
}
|
|
483
576
|
})
|
|
484
577
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
578
|
+
// Without this the cases below cannot fail for an SDK whose credential the
|
|
579
|
+
// probe misses: every one of them takes the public-API path instead.
|
|
580
|
+
t.Run("probe-finds-the-credential-this-sdk-places", func(t *testing.T) {
|
|
581
|
+
if (cred == nil) != (plAuthAnyCredential(t) == nil) {
|
|
582
|
+
t.Error("the probe missed a credential this SDK places, so every " +
|
|
583
|
+
"case below takes the public-API path")
|
|
584
|
+
}
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
t.Run("apikey-placed-where-this-api-puts-it", func(t *testing.T) {
|
|
588
|
+
if cred == nil {
|
|
589
|
+
// A public API places nothing, and that is the whole assertion.
|
|
590
|
+
if _, has := placed(t, map[string]any{
|
|
591
|
+
"apikey": "K",
|
|
592
|
+
"auth": plAuth("Bearer"),
|
|
593
|
+
}, nil); has {
|
|
594
|
+
t.Errorf("expected no credential placed at all")
|
|
595
|
+
}
|
|
596
|
+
return
|
|
597
|
+
}
|
|
598
|
+
if cred.where != "headers" && cred.where != "query" {
|
|
599
|
+
t.Fatalf("unexpected credential container %q", cred.where)
|
|
600
|
+
}
|
|
601
|
+
if "" != cred.pair {
|
|
602
|
+
// A cookie credential is a `<scheme>=<key>` pair, and the scheme
|
|
603
|
+
// name leaves no room for the option's prefix.
|
|
604
|
+
text, _ := cred.value.(string)
|
|
605
|
+
if !plAuthCookiePair.MatchString(text) {
|
|
606
|
+
t.Errorf("expected a cookie pair, got %v", cred.value)
|
|
607
|
+
}
|
|
608
|
+
return
|
|
609
|
+
}
|
|
610
|
+
// A header credential is prefix-joined; a query credential is the raw
|
|
611
|
+
// key, because a query parameter has nowhere to put a scheme name.
|
|
612
|
+
want := "Bearer K"
|
|
613
|
+
if "query" == cred.where {
|
|
614
|
+
want = "K"
|
|
494
615
|
}
|
|
495
|
-
if
|
|
496
|
-
t.Errorf("expected
|
|
616
|
+
if cred.value != want {
|
|
617
|
+
t.Errorf("expected %q in %s[%q], got %v",
|
|
618
|
+
want, cred.where, cred.name, cred.value)
|
|
497
619
|
}
|
|
498
620
|
})
|
|
499
621
|
|
|
500
622
|
t.Run("raw-apikey-empty-prefix-as-is", func(t *testing.T) {
|
|
501
|
-
|
|
623
|
+
value, has := placed(t, map[string]any{
|
|
502
624
|
"apikey": "K",
|
|
503
|
-
"auth":
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
625
|
+
"auth": plAuth(""),
|
|
626
|
+
}, nil)
|
|
627
|
+
if cred == nil {
|
|
628
|
+
if has {
|
|
629
|
+
t.Errorf("expected no credential placed at all")
|
|
630
|
+
}
|
|
631
|
+
return
|
|
509
632
|
}
|
|
510
|
-
if
|
|
511
|
-
t.Errorf("expected
|
|
633
|
+
if value != cred.pair+"K" {
|
|
634
|
+
t.Errorf("expected %q, got %v", cred.pair+"K", value)
|
|
512
635
|
}
|
|
513
636
|
})
|
|
514
637
|
|
|
515
|
-
t.Run("empty-apikey-drops-
|
|
516
|
-
|
|
638
|
+
t.Run("empty-apikey-drops-credential", func(t *testing.T) {
|
|
639
|
+
if _, has := placed(t, map[string]any{
|
|
517
640
|
"apikey": "",
|
|
518
|
-
"auth":
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
ctx.Spec = authSpec(map[string]any{"authorization": "stale"})
|
|
522
|
-
if _, err := utility.PrepareAuth(ctx); err != nil {
|
|
523
|
-
t.Fatalf("unexpected error: %v", err)
|
|
524
|
-
}
|
|
525
|
-
if _, has := ctx.Spec.Headers["authorization"]; has {
|
|
526
|
-
t.Errorf("expected authorization dropped, got %v", ctx.Spec.Headers["authorization"])
|
|
641
|
+
"auth": plAuth("Bearer"),
|
|
642
|
+
}, "stale"); has {
|
|
643
|
+
t.Errorf("expected the credential dropped")
|
|
527
644
|
}
|
|
528
645
|
})
|
|
529
646
|
|
|
530
|
-
t.Run("missing-apikey-drops-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
})
|
|
647
|
+
t.Run("missing-apikey-drops-credential", func(t *testing.T) {
|
|
648
|
+
sdkopts := map[string]any{"auth": plAuth("Bearer")}
|
|
649
|
+
client, _ := plClient(t, sdkopts)
|
|
534
650
|
options := client.OptionsMap()
|
|
535
651
|
if apikey, _ := options["apikey"].(string); apikey != "" {
|
|
536
652
|
t.Skip("SDK options carry a configured apikey; case not reproducible here")
|
|
537
653
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if _, err := utility.PrepareAuth(ctx); err != nil {
|
|
541
|
-
t.Fatalf("unexpected error: %v", err)
|
|
542
|
-
}
|
|
543
|
-
if _, has := ctx.Spec.Headers["authorization"]; has {
|
|
544
|
-
t.Errorf("expected authorization dropped, got %v", ctx.Spec.Headers["authorization"])
|
|
654
|
+
if _, has := placed(t, sdkopts, "stale"); has {
|
|
655
|
+
t.Errorf("expected the credential dropped")
|
|
545
656
|
}
|
|
546
657
|
})
|
|
547
658
|
|
|
548
|
-
t.Run("public-api-no-auth-block-drops-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if
|
|
659
|
+
t.Run("public-api-no-auth-block-drops-credential", func(t *testing.T) {
|
|
660
|
+
sdkopts := map[string]any{"apikey": "K"}
|
|
661
|
+
client, _ := plClient(t, sdkopts)
|
|
662
|
+
if client.OptionsMap()["auth"] != nil {
|
|
552
663
|
// Option validation supplies an auth shape for this SDK, so a
|
|
553
664
|
// truly auth-less client cannot be constructed here (the ts test
|
|
554
665
|
// fakes the client object; Go's prepareAuth reads the concrete
|
|
555
666
|
// client options).
|
|
556
667
|
t.Skip("options always carry an auth block in this SDK")
|
|
557
668
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if _, err := utility.PrepareAuth(ctx); err != nil {
|
|
561
|
-
t.Fatalf("unexpected error: %v", err)
|
|
562
|
-
}
|
|
563
|
-
if _, has := ctx.Spec.Headers["authorization"]; has {
|
|
564
|
-
t.Errorf("expected authorization dropped, got %v", ctx.Spec.Headers["authorization"])
|
|
669
|
+
if _, has := placed(t, sdkopts, "stale"); has {
|
|
670
|
+
t.Errorf("expected the credential dropped")
|
|
565
671
|
}
|
|
566
672
|
})
|
|
567
673
|
}
|
|
@@ -618,6 +618,23 @@ func TestPrimaryUtility(t *testing.T) {
|
|
|
618
618
|
authClient := sdk.TestSDK(nil, setupOpts)
|
|
619
619
|
authUtility := authClient.GetUtility()
|
|
620
620
|
|
|
621
|
+
// The corpus writes the credential as `headers.authorization`: a
|
|
622
|
+
// PLACEHOLDER each runner points at the container and name this API
|
|
623
|
+
// actually uses. plAuthCredential (pipeline_test.go, same package)
|
|
624
|
+
// discovers both by running PrepareAuth once.
|
|
625
|
+
cred := plAuthCredential(t)
|
|
626
|
+
if cred == nil {
|
|
627
|
+
t.Fatal("prepareAuth placed no credential in headers or query")
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// An absent section is runsection's report to make, not a nil-map write.
|
|
631
|
+
if section, has := primary["prepareAuth"].(map[string]any); has &&
|
|
632
|
+
("headers" != cred.where || "authorization" != cred.name) {
|
|
633
|
+
original := section["basic"]
|
|
634
|
+
section["basic"] = retargetAuth(original, cred)
|
|
635
|
+
defer func() { section["basic"] = original }()
|
|
636
|
+
}
|
|
637
|
+
|
|
621
638
|
runsection(t, "prepareAuth", func(args ...any) (any, error) {
|
|
622
639
|
ctx := omniCtx(args[0], authClient, authUtility)
|
|
623
640
|
|
|
@@ -823,3 +840,39 @@ func makeTestFullCtx(client *sdk.ProjectNameSDK, utility *sdk.Utility) *sdk.Cont
|
|
|
823
840
|
|
|
824
841
|
// useVS prevents unused import error
|
|
825
842
|
var _ = vs.Clone
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
// retargetAuth rewrites the corpus's `headers` bag to the container this
|
|
846
|
+
// SDK's PrepareAuth writes to, and the `authorization` key inside it to the
|
|
847
|
+
// API's own credential name. Applied only to the prepareAuth section, so
|
|
848
|
+
// real header assertions elsewhere are untouched.
|
|
849
|
+
func retargetAuth(node any, cred *plAuthCred) any {
|
|
850
|
+
switch value := node.(type) {
|
|
851
|
+
case map[string]any:
|
|
852
|
+
out := map[string]any{}
|
|
853
|
+
for key, child := range value {
|
|
854
|
+
if "headers" == key {
|
|
855
|
+
bag := map[string]any{}
|
|
856
|
+
inner, _ := child.(map[string]any)
|
|
857
|
+
for bagkey, bagvalue := range inner {
|
|
858
|
+
name := bagkey
|
|
859
|
+
if "authorization" == bagkey {
|
|
860
|
+
name = cred.name
|
|
861
|
+
}
|
|
862
|
+
bag[name] = retargetAuth(bagvalue, cred)
|
|
863
|
+
}
|
|
864
|
+
out[cred.where] = bag
|
|
865
|
+
} else {
|
|
866
|
+
out[key] = retargetAuth(child, cred)
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return out
|
|
870
|
+
case []any:
|
|
871
|
+
out := make([]any, len(value))
|
|
872
|
+
for index, child := range value {
|
|
873
|
+
out[index] = retargetAuth(child, cred)
|
|
874
|
+
}
|
|
875
|
+
return out
|
|
876
|
+
}
|
|
877
|
+
return node
|
|
878
|
+
}
|
|
@@ -6,12 +6,52 @@ import (
|
|
|
6
6
|
"net/http"
|
|
7
7
|
"net/url"
|
|
8
8
|
"strings"
|
|
9
|
+
"sync"
|
|
9
10
|
|
|
10
11
|
vs "github.com/voxgig/struct"
|
|
11
12
|
|
|
12
13
|
"GOMODULE/core"
|
|
13
14
|
)
|
|
14
15
|
|
|
16
|
+
// Proxied clients keyed by proxy URL. A client per request would open a
|
|
17
|
+
// fresh connection every call; a cached one pools like http.DefaultClient.
|
|
18
|
+
var proxyClients sync.Map
|
|
19
|
+
|
|
20
|
+
func clientFor(fetchdef map[string]any) *http.Client {
|
|
21
|
+
client := http.DefaultClient
|
|
22
|
+
|
|
23
|
+
if proxy, ok := fetchdef["proxy"].(string); ok && proxy != "" {
|
|
24
|
+
if cached, ok := proxyClients.Load(proxy); ok {
|
|
25
|
+
client = cached.(*http.Client)
|
|
26
|
+
} else if proxyURL, perr := url.Parse(proxy); perr == nil {
|
|
27
|
+
// http.DefaultTransport is a package variable the host program can
|
|
28
|
+
// replace, and an unchecked assertion on it panics the SDK inside
|
|
29
|
+
// whatever called it. Clone the real one where it is there, and
|
|
30
|
+
// build a plain transport where it is not.
|
|
31
|
+
var transport *http.Transport
|
|
32
|
+
if def, ok := http.DefaultTransport.(*http.Transport); ok {
|
|
33
|
+
transport = def.Clone()
|
|
34
|
+
} else {
|
|
35
|
+
transport = &http.Transport{}
|
|
36
|
+
}
|
|
37
|
+
transport.Proxy = http.ProxyURL(proxyURL)
|
|
38
|
+
cached, _ := proxyClients.LoadOrStore(proxy, &http.Client{Transport: transport})
|
|
39
|
+
client = cached.(*http.Client)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A shallow copy shares the pooled Transport; only CheckRedirect differs.
|
|
44
|
+
if redirect, ok := fetchdef["redirect"].(string); ok && redirect == "manual" {
|
|
45
|
+
manual := *client
|
|
46
|
+
manual.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
|
47
|
+
return http.ErrUseLastResponse
|
|
48
|
+
}
|
|
49
|
+
client = &manual
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return client
|
|
53
|
+
}
|
|
54
|
+
|
|
15
55
|
func defaultHTTPFetch(fullurl string, fetchdef map[string]any) (map[string]any, error) {
|
|
16
56
|
method, _ := fetchdef["method"].(string)
|
|
17
57
|
if method == "" {
|
|
@@ -46,26 +86,7 @@ func defaultHTTPFetch(fullurl string, fetchdef map[string]any) (map[string]any,
|
|
|
46
86
|
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)")
|
|
47
87
|
}
|
|
48
88
|
|
|
49
|
-
|
|
50
|
-
// feature): route the request through an http.Transport with Proxy set.
|
|
51
|
-
client := http.DefaultClient
|
|
52
|
-
if proxy, ok := fetchdef["proxy"].(string); ok && proxy != "" {
|
|
53
|
-
if proxyURL, perr := url.Parse(proxy); perr == nil {
|
|
54
|
-
client = &http.Client{
|
|
55
|
-
Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if redirect, ok := fetchdef["redirect"].(string); ok && redirect == "manual" {
|
|
61
|
-
manual := *client
|
|
62
|
-
manual.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
|
63
|
-
return http.ErrUseLastResponse
|
|
64
|
-
}
|
|
65
|
-
client = &manual
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
resp, err := client.Do(req)
|
|
89
|
+
resp, err := clientFor(fetchdef).Do(req)
|
|
69
90
|
if err != nil {
|
|
70
91
|
return nil, err
|
|
71
92
|
}
|
|
@@ -29,7 +29,7 @@ func prepareQueryUtil(ctx *core.Context) map[string]any {
|
|
|
29
29
|
for _, item := range vs.Items(reqmatch) {
|
|
30
30
|
key, _ := item[0].(string)
|
|
31
31
|
val := item[1]
|
|
32
|
-
if val != nil && !containsStr(params, key) {
|
|
32
|
+
if val != nil && key != "$action" && !containsStr(params, key) {
|
|
33
33
|
out[key] = val
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -16,12 +16,12 @@ func transformRequestUtil(ctx *core.Context) any {
|
|
|
16
16
|
|
|
17
17
|
transform := core.ToMapAny(vs.GetProp(point, "transform"))
|
|
18
18
|
if transform == nil {
|
|
19
|
-
return ctx.Reqdata
|
|
19
|
+
return stripAction(ctx.Reqdata)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
reqform := vs.GetProp(transform, "req")
|
|
23
23
|
if reqform == nil {
|
|
24
|
-
return ctx.Reqdata
|
|
24
|
+
return stripAction(ctx.Reqdata)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
reqdata, terr := vs.Transform(map[string]any{
|
|
@@ -36,5 +36,24 @@ func transformRequestUtil(ctx *core.Context) any {
|
|
|
36
36
|
return terr
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return reqdata
|
|
39
|
+
return stripAction(reqdata)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// `$action` selects the point (see makePointUtil); it is never an API field,
|
|
43
|
+
// so the body is a copy without it. The caller's map is left untouched.
|
|
44
|
+
func stripAction(reqdata any) any {
|
|
45
|
+
src, ok := reqdata.(map[string]any)
|
|
46
|
+
if !ok {
|
|
47
|
+
return reqdata
|
|
48
|
+
}
|
|
49
|
+
if _, has := src["$action"]; !has {
|
|
50
|
+
return reqdata
|
|
51
|
+
}
|
|
52
|
+
body := make(map[string]any, len(src))
|
|
53
|
+
for k, v := range src {
|
|
54
|
+
if k != "$action" {
|
|
55
|
+
body[k] = v
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return body
|
|
40
59
|
}
|
|
@@ -82,7 +82,8 @@ public class Context {
|
|
|
82
82
|
this.ctrl = (Control) cv;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
else if (basectx != null && basectx.ctrl != null
|
|
85
|
+
else if (basectx != null && basectx.ctrl != null
|
|
86
|
+
&& Helpers.getCtxProp(ctxmap, "opname") == null) {
|
|
86
87
|
this.ctrl = basectx.ctrl;
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -18,8 +18,8 @@ import JAVAPACKAGE.utility.struct.Struct;
|
|
|
18
18
|
// stamps page/limit (or a cursor) into the request query; on the way back
|
|
19
19
|
// (PreResult) it reads the server's pagination signals — a `Link:
|
|
20
20
|
// rel="next"` header, `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
|
|
21
|
-
// `next`/`cursor`/`nextCursor`/`hasMore` fields in the body
|
|
22
|
-
// them on `ctx.result.paging`. A per-call cursor/page from ctrl takes
|
|
21
|
+
// `next`/`cursor`/`nextCursor`/`hasMore` fields in the body (snake_case
|
|
22
|
+
// forms too) — and records them on `ctx.result.paging` and `ctrl.paging`. A per-call cursor/page from ctrl takes
|
|
23
23
|
// priority (used by auto-iteration). Parameter names (`pageParam`,
|
|
24
24
|
// `limitParam`, `cursorParam`), the page size (`limit`) and the start page
|
|
25
25
|
// (`startPage`, default 1) are configurable.
|
|
@@ -82,7 +82,12 @@ public class PagingFeature extends BaseFeature {
|
|
|
82
82
|
spec.query.put(cursorParam, cursor);
|
|
83
83
|
}
|
|
84
84
|
else if (spec.query.get(pageParam) == null) {
|
|
85
|
-
|
|
85
|
+
// A record written back by preResult holds the page just fetched as
|
|
86
|
+
// "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
87
|
+
Object page = paging.get("nextPage");
|
|
88
|
+
if (page == null) {
|
|
89
|
+
page = paging.get("page");
|
|
90
|
+
}
|
|
86
91
|
if (page != null) {
|
|
87
92
|
spec.query.put(pageParam, page);
|
|
88
93
|
}
|
|
@@ -206,18 +211,37 @@ public class PagingFeature extends BaseFeature {
|
|
|
206
211
|
}
|
|
207
212
|
}
|
|
208
213
|
|
|
209
|
-
// Body-level
|
|
214
|
+
// Body-level signals; snake_case forms are read first so camelCase wins.
|
|
210
215
|
if (body instanceof Map) {
|
|
211
216
|
Map<String, Object> bm = (Map<String, Object>) body;
|
|
212
217
|
if (bm.get("next") != null && paging.get("next") == null) {
|
|
213
218
|
paging.put("next", bm.get("next"));
|
|
214
219
|
}
|
|
220
|
+
if (bm.get("next_cursor") != null) {
|
|
221
|
+
paging.put("cursor", bm.get("next_cursor"));
|
|
222
|
+
}
|
|
215
223
|
if (bm.get("cursor") != null) {
|
|
216
224
|
paging.put("cursor", bm.get("cursor"));
|
|
217
225
|
}
|
|
218
226
|
if (bm.get("nextCursor") != null) {
|
|
219
227
|
paging.put("cursor", bm.get("nextCursor"));
|
|
220
228
|
}
|
|
229
|
+
if (paging.get("nextPage") == null) {
|
|
230
|
+
Object np = bm.get("nextPage");
|
|
231
|
+
if (np == null) {
|
|
232
|
+
np = bm.get("next_page");
|
|
233
|
+
}
|
|
234
|
+
if (np instanceof Number) {
|
|
235
|
+
paging.put("nextPage", ((Number) np).intValue());
|
|
236
|
+
}
|
|
237
|
+
else if (np instanceof String) {
|
|
238
|
+
paging.put("nextPage", np);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (bm.get("has_more") instanceof Boolean) {
|
|
242
|
+
paging.put("hasMore", bm.get("has_more"));
|
|
243
|
+
explicitMore = true;
|
|
244
|
+
}
|
|
221
245
|
if (bm.get("hasMore") instanceof Boolean) {
|
|
222
246
|
paging.put("hasMore", bm.get("hasMore"));
|
|
223
247
|
explicitMore = true;
|
|
@@ -237,6 +261,24 @@ public class PagingFeature extends BaseFeature {
|
|
|
237
261
|
|
|
238
262
|
result.paging = paging;
|
|
239
263
|
this.last = paging;
|
|
264
|
+
|
|
265
|
+
if (ctx.ctrl != null) {
|
|
266
|
+
// The Context shares the caller's paging map by reference, so refill
|
|
267
|
+
// it in place; an immutable map falls back to replacing the reference.
|
|
268
|
+
Map<String, Object> shared = ctx.ctrl.paging;
|
|
269
|
+
if (shared == null) {
|
|
270
|
+
ctx.ctrl.paging = paging;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
try {
|
|
274
|
+
shared.clear();
|
|
275
|
+
shared.putAll(paging);
|
|
276
|
+
}
|
|
277
|
+
catch (UnsupportedOperationException e) {
|
|
278
|
+
ctx.ctrl.paging = paging;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
240
282
|
}
|
|
241
283
|
|
|
242
284
|
private boolean isList(Context ctx) {
|