@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
@@ -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 entm, ok := ent.(map[string]any); ok {
179
- reqdata := ctx.Reqdata
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
- authSpec := func(headers map[string]any) *sdk.Spec {
470
- if headers == nil {
471
- headers = map[string]any{}
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
- return sdk.NewSpec(map[string]any{"headers": headers, "step": "s"})
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
- t.Run("apikey-with-prefix-space-joined", func(t *testing.T) {
486
- client, utility := plClient(t, map[string]any{
487
- "apikey": "K",
488
- "auth": map[string]any{"prefix": "Bearer"},
489
- })
490
- ctx := plCtx(client, utility, nil)
491
- ctx.Spec = authSpec(nil)
492
- if _, err := utility.PrepareAuth(ctx); err != nil {
493
- t.Fatalf("unexpected error: %v", err)
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 ctx.Spec.Headers["authorization"] != "Bearer K" {
496
- t.Errorf("expected 'Bearer K', got %v", ctx.Spec.Headers["authorization"])
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
- client, utility := plClient(t, map[string]any{
623
+ value, has := placed(t, map[string]any{
502
624
  "apikey": "K",
503
- "auth": map[string]any{"prefix": ""},
504
- })
505
- ctx := plCtx(client, utility, nil)
506
- ctx.Spec = authSpec(nil)
507
- if _, err := utility.PrepareAuth(ctx); err != nil {
508
- t.Fatalf("unexpected error: %v", err)
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 ctx.Spec.Headers["authorization"] != "K" {
511
- t.Errorf("expected raw 'K', got %v", ctx.Spec.Headers["authorization"])
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-header", func(t *testing.T) {
516
- client, utility := plClient(t, map[string]any{
638
+ t.Run("empty-apikey-drops-credential", func(t *testing.T) {
639
+ if _, has := placed(t, map[string]any{
517
640
  "apikey": "",
518
- "auth": map[string]any{"prefix": "Bearer"},
519
- })
520
- ctx := plCtx(client, utility, nil)
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-header", func(t *testing.T) {
531
- client, utility := plClient(t, map[string]any{
532
- "auth": map[string]any{"prefix": "Bearer"},
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
- ctx := plCtx(client, utility, nil)
539
- ctx.Spec = authSpec(map[string]any{"authorization": "stale"})
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-header", func(t *testing.T) {
549
- client, utility := plClient(t, map[string]any{"apikey": "K"})
550
- options := client.OptionsMap()
551
- if options["auth"] != nil {
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
- ctx := plCtx(client, utility, nil)
559
- ctx.Spec = authSpec(map[string]any{"authorization": "stale"})
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
- // Honour a proxy annotation on the fetch definition (set by the proxy
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 — and records
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
- Object page = paging.get("page");
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 cursors.
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) {