@voxgig/sdkgen 4.5.3 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/check.js +15 -0
  3. package/dist/action/check.js.map +1 -1
  4. package/dist/cmp/AgentGuide.js +1 -1
  5. package/dist/cmp/AgentGuide.js.map +1 -1
  6. package/dist/cmp/AgentGuideContent.d.ts +1 -1
  7. package/dist/cmp/AgentGuideContent.js +8 -2
  8. package/dist/cmp/AgentGuideContent.js.map +1 -1
  9. package/dist/cmp/Feature.js +17 -0
  10. package/dist/cmp/Feature.js.map +1 -1
  11. package/dist/cmp/FeatureDocs.d.ts +1 -1
  12. package/dist/cmp/FeatureDocs.js +6 -1
  13. package/dist/cmp/FeatureDocs.js.map +1 -1
  14. package/dist/cmp/ReadmeExplanation.js +3 -1
  15. package/dist/cmp/ReadmeExplanation.js.map +1 -1
  16. package/dist/cmp/ReadmeFeatures.js +1 -1
  17. package/dist/cmp/ReadmeFeatures.js.map +1 -1
  18. package/dist/cmp/ReadmeRefFeatures.js +1 -1
  19. package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
  20. package/dist/helpers/applicability.d.ts +6 -0
  21. package/dist/helpers/applicability.js +103 -0
  22. package/dist/helpers/applicability.js.map +1 -0
  23. package/dist/helpers/collectDeps.js +4 -2
  24. package/dist/helpers/collectDeps.js.map +1 -1
  25. package/dist/helpers/serverVars.d.ts +2 -1
  26. package/dist/helpers/serverVars.js +17 -0
  27. package/dist/helpers/serverVars.js.map +1 -1
  28. package/dist/sdkgen.d.ts +3 -2
  29. package/dist/sdkgen.js +9 -2
  30. package/dist/sdkgen.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/dist/utility.js +7 -1
  33. package/dist/utility.js.map +1 -1
  34. package/model/sdkgen.aon +39 -0
  35. package/package.json +1 -1
  36. package/project/.sdk/model/feature/feature-index.aon +1 -0
  37. package/project/.sdk/model/feature/secrets.aon +118 -0
  38. package/project/.sdk/model/target/ts.aon +8 -1
  39. package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
  40. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
  41. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
  42. package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
  43. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
  44. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
  45. package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
  46. package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
  47. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
  48. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
  49. package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
  50. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
  51. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
  52. package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
  53. package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
  54. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
  55. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
  56. package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
  57. package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
  58. package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
  59. package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
  60. package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
  61. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
  62. package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
  63. package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
  64. package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
  65. package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
  66. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
  67. package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
  68. package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
  69. package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
  70. package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
  71. package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
  72. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
  73. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
  74. package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
  75. package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
  76. package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
  77. package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
  78. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
  79. package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
  80. package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
  81. package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
  82. package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
  83. package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
  84. package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
  85. package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
  86. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
  87. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
  88. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
  89. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
  90. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
  91. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
  92. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
  93. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
  94. package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
  95. package/project/.sdk/tm/c/utility/make_options.c +22 -0
  96. package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
  97. package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
  98. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
  99. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
  100. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
  101. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
  102. package/project/.sdk/tm/go/core/context.go +1 -1
  103. package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
  104. package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
  105. package/project/.sdk/tm/go/feature/test_feature.go +12 -5
  106. package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
  107. package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
  108. package/project/.sdk/tm/go/test/runner_test.go +46 -0
  109. package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
  110. package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
  111. package/project/.sdk/tm/go/utility/fetcher.go +2 -2
  112. package/project/.sdk/tm/go/utility/make_options.go +24 -5
  113. package/project/.sdk/tm/go/utility/make_point.go +1 -1
  114. package/project/.sdk/tm/go/utility/make_spec.go +17 -1
  115. package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
  116. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
  117. package/project/.sdk/tm/go/utility/transform_request.go +29 -1
  118. package/project/.sdk/tm/go/utility/transform_response.go +20 -1
  119. package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
  120. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
  121. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
  122. package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
  123. package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
  124. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
  125. package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
  126. package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
  127. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
  128. package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
  129. package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
  130. package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
  131. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
  132. package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
  133. package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
  134. package/project/.sdk/tm/py/test/runner.py +32 -0
  135. package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
  136. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
  137. package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
  138. package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
  139. package/project/.sdk/tm/scala/utility/Make.scala +17 -0
  140. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
  141. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
  142. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
  143. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
  144. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
  145. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
  146. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
  147. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
  148. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
  149. package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
  150. package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
  151. package/project/.sdk/tm/ts/test/omni.ts +86 -0
  152. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
  153. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
  154. package/project/.sdk/tm/ts/test/utility.ts +122 -0
  155. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
  156. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
  157. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
  158. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
  159. package/project/.sdk/tm/zig/core/utility.zig +23 -0
  160. package/project/sdkgen-package.json +2 -1
  161. package/src/action/check.ts +18 -0
  162. package/src/cmp/AgentGuide.ts +1 -1
  163. package/src/cmp/AgentGuideContent.ts +9 -2
  164. package/src/cmp/Feature.ts +19 -0
  165. package/src/cmp/FeatureDocs.ts +7 -1
  166. package/src/cmp/ReadmeExplanation.ts +4 -1
  167. package/src/cmp/ReadmeFeatures.ts +1 -1
  168. package/src/cmp/ReadmeRefFeatures.ts +1 -1
  169. package/src/helpers/applicability.ts +123 -0
  170. package/src/helpers/collectDeps.ts +5 -1
  171. package/src/helpers/serverVars.ts +19 -0
  172. package/src/sdkgen.ts +9 -1
  173. package/src/utility.ts +10 -1
  174. package/project/.sdk/tm/ts/test/runner.ts +0 -425
@@ -84,7 +84,16 @@ function scriptedFetcher(res) {
84
84
 
85
85
 
86
86
  function makeClient(kase) {
87
+ // `test: { active: true }` is the OPTION, not the `test` FEATURE.
88
+ //
89
+ // It says "this client is not live", which is what makes a REQUIRED
90
+ // OpenAPI server variable resolve to a deterministic `test-<name>`
91
+ // rather than refuse to construct (see makeOptions). It installs no
92
+ // transport, so the scripted fetcher below still stands — the FEATURE
93
+ // is transport: 'base' and would shadow it, which is why this cannot
94
+ // just turn the feature on.
87
95
  return new SDK({
96
+ test: { active: true },
88
97
  feature: kase.feature,
89
98
  utility: { fetcher: scriptedFetcher(kase.res || [{ status: 200, body: {} }]) },
90
99
  })
@@ -28,8 +28,24 @@ fun makeOptions(ctx: Context): MutableMap<String, Any?> {
28
28
  }
29
29
  }
30
30
 
31
+ // `auth: null` is the documented way to disable auth outright, and
32
+ // PrepareAuth honours it before it ever reads the apikey. It cannot survive
33
+ // validate: depending on the struct port a stored null is either REPLACED
34
+ // by the optspec default - transmitting the credential the caller withheld
35
+ // - or REJECTED outright. Withhold the key for validate, then put the null
36
+ // back. Same fix as ts/js/go makeOptions.
37
+ //
38
+ // Suppliedness cannot be recovered after validate, hence here, and it must
39
+ // tell an ABSENT auth from a present null: containsKey rather than a null
40
+ // check on the value, which cannot distinguish them.
41
+ val authSuppressed = options.containsKey("auth") && null == options["auth"]
42
+
31
43
  var opts = Struct.clone(options) as MutableMap<String, Any?>
32
44
 
45
+ if (authSuppressed) {
46
+ opts.remove("auth")
47
+ }
48
+
33
49
  // Feature add-order. options.feature may be given as an ordered LIST of
34
50
  // { name, active, ...opts } entries (the list position IS the order in which
35
51
  // features are added), or as a { name: {opts} } map. Normalize a list to a
@@ -107,6 +123,11 @@ fun makeOptions(ctx: Context): MutableMap<String, Any?> {
107
123
  val validated = Struct.validate(merged, optspec, vopts)
108
124
  opts = validated as MutableMap<String, Any?>
109
125
 
126
+ // Restore the suppression the optspec default would otherwise erase.
127
+ if (authSuppressed) {
128
+ opts["auth"] = null
129
+ }
130
+
110
131
  // Restore system.fetch.
111
132
  if (sysFetch != null) {
112
133
  val sys = Helpers.toMapAny(opts["system"])
@@ -260,8 +260,19 @@ def prepareAuth (ctx : Value) : SIO (Value × Option Value) := do
260
260
  | .map _ => do
261
261
  let headers ← gpMap specV "headers"
262
262
  let options ← gp ctx "options"
263
+ -- `auth: null` is the documented way to suppress auth outright: NO
264
+ -- authorization header, whatever the apikey says. Without this the
265
+ -- withheld credential goes on the wire.
266
+ --
267
+ -- The ports that run options through validate against an optspec carrying
268
+ -- an `auth` default can use absence as the signal (validate guarantees the
269
+ -- key is present otherwise). lean's makeOptions has no optspec, so `auth`
270
+ -- is ABSENT in the ordinary case and absence must stay the ordinary case.
271
+ -- getpropRaw is the only reader that tells a STORED null from an absent
272
+ -- key - gp collapses both to .noval - so the stored null alone suppresses.
273
+ let authRaw ← getpropRaw options "auth"
263
274
  let apikey ← gpS options "apikey"
264
- if apikey == "" then do
275
+ if authRaw == .null || apikey == "" then do
265
276
  dp headers "authorization"
266
277
  pure (specV, none)
267
278
  else do
@@ -968,7 +968,16 @@ let make_options_util (ctx : ctx) : value =
968
968
  | Some u -> List.iter (fun k -> setp u.u_custom k (getp custom_utils k)) (keysof custom_utils)
969
969
  | None -> ())
970
970
  | _ -> ());
971
+ (* `auth: null` is the documented way to suppress auth outright. validate()
972
+ treats a STORED null as "no value" (getprop returns the alt), so the
973
+ optspec `auth` default would fire and turn the suppression into "use the
974
+ default auth" - transmitting a credential the caller withheld. getprop_raw
975
+ is the only reader that tells a stored Null from an absent key. Withhold
976
+ the key across merge/validate, then restore the null below. *)
977
+ let auth_suppressed =
978
+ (match getprop_raw options "auth" with Null -> true | _ -> false) in
971
979
  let opts = match clone options with Map _ as m -> m | _ -> empty_map () in
980
+ let () = if auth_suppressed then ignore (delprop opts (Str "auth")) in
972
981
  (* Feature add-order. options.feature may be given as an ordered LIST of
973
982
  {name; active; ...opts} entries (list position = add order) or a
974
983
  {name => {opts}} map. Normalize a list to a map (so merge/validate/init
@@ -999,6 +1008,8 @@ let make_options_util (ctx : ctx) : value =
999
1008
  let merged = merge (ja [empty_map (); cfgopts; opts]) in
1000
1009
  let validated = validate merged optspec in
1001
1010
  let opts = match validated with Map _ as m -> m | _ -> empty_map () in
1011
+ (* Restore the suppression the optspec default would otherwise erase. *)
1012
+ let () = if auth_suppressed then setp opts "auth" Null in
1002
1013
  (* Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
1003
1014
  Every placeholder must resolve to a non-empty value: from options.server
1004
1015
  (user), else the Config default. A placeholder that resolves to "" is a
@@ -69,7 +69,22 @@ $REGISTRY{make_options} = sub {
69
69
  # reads options.extend, but clone/validate dropped the instances.
70
70
  my $extend_raw = ProjectNameHelpers::gp($options, 'extend');
71
71
 
72
+ # `auth => undef` is the documented way to disable auth outright, and
73
+ # prepare_auth honours it before it ever reads the apikey. It cannot survive
74
+ # validate: depending on the struct port a stored null is either REPLACED by
75
+ # the optspec default - transmitting the credential the caller withheld - or
76
+ # REJECTED outright. Withhold the key for validate, then put the undef back.
77
+ # Same fix as ts/js/go make_options.
78
+ #
79
+ # Suppliedness cannot be recovered after validate, hence here, and it must
80
+ # tell an ABSENT auth from a present undef: exists rather than defined,
81
+ # which is false for both.
82
+ my $authsuppressed =
83
+ (ref($options) eq 'HASH' && exists $options->{auth} && !defined $options->{auth}) ? 1 : 0;
84
+
72
85
  my $opts = Voxgig::Struct::clone($options);
86
+
87
+ delete $opts->{auth} if $authsuppressed && ref($opts) eq 'HASH';
73
88
  $opts = {} unless Voxgig::Struct::ismap($opts);
74
89
  delete $opts->{extend};
75
90
 
@@ -143,6 +158,9 @@ $REGISTRY{make_options} = sub {
143
158
  my $validated = Voxgig::Struct::validate($merged, $optspec);
144
159
  $opts = Voxgig::Struct::ismap($validated) ? $validated : {};
145
160
 
161
+ # Restore the suppression the optspec default would otherwise erase.
162
+ $opts->{auth} = undef if $authsuppressed;
163
+
146
164
  if ($sys_fetch) {
147
165
  $opts->{system} = {} unless Voxgig::Struct::ismap($opts->{system});
148
166
  $opts->{system}{fetch} = $sys_fetch;
@@ -91,7 +91,16 @@ class FeatureCorpusTest extends TestCase
91
91
  */
92
92
  private static function buildClient(array $kase)
93
93
  {
94
- $opts = ['utility' => ['fetcher' => self::scriptedFetcher($kase['res'] ?? null)]];
94
+ // 'test' here is the OPTION, not the `test` FEATURE. It says "this
95
+ // client is not live", which is what makes a REQUIRED OpenAPI server
96
+ // variable resolve to a deterministic test-<name> rather than throw
97
+ // at construction (see makeOptions). It installs no transport, so the
98
+ // scripted fetcher still stands - the FEATURE is transport: 'base'
99
+ // and would shadow it.
100
+ $opts = [
101
+ 'test' => ['active' => true],
102
+ 'utility' => ['fetcher' => self::scriptedFetcher($kase['res'] ?? null)],
103
+ ];
95
104
  if (isset($kase['feature'])) {
96
105
  $opts['feature'] = $kase['feature'];
97
106
  }
@@ -94,12 +94,31 @@ class ProjectNameMakeOptions
94
94
  }
95
95
  }
96
96
 
97
+ // `auth: null` is the documented way to disable auth outright, and
98
+ // prepareAuth honours it before it ever reads the apikey. It cannot
99
+ // survive validate: depending on the struct port a stored null is
100
+ // either REPLACED by the optspec default - transmitting the
101
+ // credential the caller withheld - or REJECTED outright. Withhold the
102
+ // key for validate, then put the null back. Same fix as ts/js/go
103
+ // makeOptions.
104
+ //
105
+ // Suppliedness cannot be recovered after validate, hence here, and it
106
+ // must tell an ABSENT auth from a present null: array_key_exists
107
+ // rather than isset, which is false for both.
108
+ $authsuppressed = is_array($options)
109
+ && array_key_exists('auth', $options)
110
+ && null === $options['auth'];
111
+
97
112
  $opts = \Voxgig\Struct\Struct::clone($options);
98
113
  $opts = self::to_array_deep($opts);
99
114
  if (!is_array($opts)) {
100
115
  $opts = [];
101
116
  }
102
117
 
118
+ if ($authsuppressed) {
119
+ unset($opts['auth']);
120
+ }
121
+
103
122
  // Feature add-order. options['feature'] may be given as an ordered LIST
104
123
  // of ['name'=>..., 'active'=>..., ...] entries (the list position IS the
105
124
  // order in which features are added), or as a ['name'=>[opts]] map.
@@ -164,6 +183,11 @@ class ProjectNameMakeOptions
164
183
  $opts = [];
165
184
  }
166
185
 
186
+ // Restore the suppression the optspec default would otherwise erase.
187
+ if ($authsuppressed) {
188
+ $opts['auth'] = null;
189
+ }
190
+
167
191
  // Reattach the station binding handle held aside above (the feature
168
192
  // optspec is `$OPEN`, so the extra keys are legal; they were only
169
193
  // kept out of the clone/validate round-trip).
@@ -65,10 +65,26 @@ def make_options_util(ctx):
65
65
  if isinstance(options, dict) and options.get("extend") is not None:
66
66
  options = {k: v for k, v in options.items() if k != "extend"}
67
67
 
68
+ # `auth: None` is the documented way to disable auth outright, and
69
+ # prepare_auth honours it before it ever reads the apikey. It cannot
70
+ # survive validate: depending on the struct port a stored null is either
71
+ # REPLACED by the optspec default — transmitting the credential the
72
+ # caller withheld — or REJECTED outright. Withhold the key for validate,
73
+ # then put the null back. Same fix as ts/js/go make_options.
74
+ #
75
+ # Suppliedness cannot be recovered after validate, hence here, and it
76
+ # must tell an ABSENT auth from a present None: only the latter is a
77
+ # suppression.
78
+ authsuppressed = (
79
+ isinstance(options, dict) and "auth" in options and options["auth"] is None)
80
+
68
81
  opts = vs.clone(options)
69
82
  if not isinstance(opts, dict):
70
83
  opts = {}
71
84
 
85
+ if authsuppressed:
86
+ opts.pop("auth", None)
87
+
72
88
  # Feature add-order. options["feature"] may be given as an ordered LIST of
73
89
  # {name, active, ...opts} entries (the list position IS the order in which
74
90
  # features are added), or as a {name: {opts}} map. Normalize a list to a
@@ -162,6 +178,10 @@ def make_options_util(ctx):
162
178
  validated = {}
163
179
  opts = validated
164
180
 
181
+ # Restore the suppression the optspec default would otherwise erase.
182
+ if authsuppressed:
183
+ opts["auth"] = None
184
+
165
185
  # Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
166
186
  # Every placeholder must resolve to a non-empty value: from
167
187
  # options["server"] (user), else the config default. A placeholder that
@@ -101,6 +101,34 @@ class ProjectNameTestRunner:
101
101
  return True, item.get("reason")
102
102
  return False, None
103
103
 
104
+ @staticmethod
105
+ def live_client_options():
106
+ """Extra SDK options every LIVE client is constructed with, from
107
+ sdk-test-control.json `test.client.options`.
108
+
109
+ The generated live client knows two things: the base URL (from the
110
+ spec) and the credential (from the environment). Everything else
111
+ about how a particular API wants to be talked to - which features to
112
+ switch on, and with what settings - is a property of THAT API, known
113
+ to the project and to nothing in the toolchain.
114
+
115
+ Merged UNDER the generated fields, so the suite's own
116
+ base/apikey/server values win: this ADDS to the live client, it does
117
+ not redirect it.
118
+
119
+ Reserved fields are stripped HERE rather than at each merge site:
120
+ the generated dict only names a field when the model calls for one,
121
+ so a "base" in this block would face no competing value and would
122
+ silently redirect the whole suite - credential included - to another
123
+ host.
124
+ """
125
+ ctrl = ProjectNameTestRunner.load_test_control()
126
+ opts = ctrl.get("test", {}).get("client", {}).get("options")
127
+ if not isinstance(opts, dict):
128
+ return {}
129
+ reserved = ("base", "prefix", "suffix", "server", "apikey", "secret")
130
+ return {k: v for k, v in opts.items() if k not in reserved}
131
+
104
132
  @staticmethod
105
133
  def live_delay_ms():
106
134
  """Per-test live pacing delay (ms); default 500."""
@@ -161,5 +189,9 @@ def load_test_control():
161
189
  return ProjectNameTestRunner.load_test_control()
162
190
 
163
191
 
192
+ def live_client_options():
193
+ return ProjectNameTestRunner.live_client_options()
194
+
195
+
164
196
  def live_delay_ms():
165
197
  return ProjectNameTestRunner.live_delay_ms()
@@ -95,7 +95,16 @@ def _client(kase):
95
95
  transport: 'base' and REPLACES the transport, so a client in test mode
96
96
  would shadow the script.
97
97
  """
98
- opts = {"utility": {"fetcher": _scripted_fetcher(kase.get("res"))}}
98
+ # "test" here is the OPTION, not the `test` FEATURE. It says "this
99
+ # client is not live", which is what makes a REQUIRED OpenAPI server
100
+ # variable resolve to a deterministic test-<name> rather than raise at
101
+ # construction (see make_options). It installs no transport, so the
102
+ # scripted fetcher still stands - the FEATURE is transport: 'base' and
103
+ # would shadow it, which is why this cannot just turn the feature on.
104
+ opts = {
105
+ "test": {"active": True},
106
+ "utility": {"fetcher": _scripted_fetcher(kase.get("res"))},
107
+ }
99
108
  if kase.get("feature") is not None:
100
109
  opts["feature"] = kase["feature"]
101
110
  return ProjectNameSDK(opts)
@@ -71,7 +71,15 @@ class FeatureCorpusTest < Minitest::Test
71
71
  # transport: 'base' and REPLACES the transport, so a client in test mode
72
72
  # would shadow the script.
73
73
  def build_client(kase)
74
- opts = { "utility" => { "fetcher" => scripted_fetcher(kase["res"]) } }
74
+ # "test" here is the OPTION, not the `test` FEATURE. It says "this
75
+ # client is not live", which is what makes a REQUIRED OpenAPI server
76
+ # variable resolve to a deterministic test-<name> rather than raise at
77
+ # construction (see make_options). It installs no transport, so the
78
+ # scripted fetcher still stands.
79
+ opts = {
80
+ "test" => { "active" => true },
81
+ "utility" => { "fetcher" => scripted_fetcher(kase["res"]) },
82
+ }
75
83
  opts["feature"] = kase["feature"] unless kase["feature"].nil?
76
84
  ProjectNameSDK.new(opts)
77
85
  end
@@ -40,9 +40,23 @@ module ProjectNameUtilities
40
40
  end
41
41
  end
42
42
 
43
+ # `auth: nil` is the documented way to disable auth outright, and
44
+ # prepare_auth honours it before it ever reads the apikey. It cannot
45
+ # survive validate: depending on the struct port a stored null is either
46
+ # REPLACED by the optspec default - transmitting the credential the
47
+ # caller withheld - or REJECTED outright. Withhold the key for validate,
48
+ # then put the nil back. Same fix as ts/js/go make_options.
49
+ #
50
+ # Suppliedness cannot be recovered after validate, hence here, and it
51
+ # must tell an ABSENT auth from a present nil: only the latter is a
52
+ # suppression.
53
+ authsuppressed = options.is_a?(Hash) && options.key?('auth') && options['auth'].nil?
54
+
43
55
  opts = VoxgigStruct.clone(options)
44
56
  opts = {} unless opts.is_a?(Hash)
45
57
 
58
+ opts.delete('auth') if authsuppressed
59
+
46
60
  # Feature add-order. options["feature"] may be given as an ordered ARRAY of
47
61
  # { "name" => ..., "active" => ..., ... } entries (the array position IS the
48
62
  # order in which features are added), or as a { "name" => {opts} } map.
@@ -109,6 +123,9 @@ module ProjectNameUtilities
109
123
  validated = VoxgigStruct.validate(merged, optspec)
110
124
  opts = validated.is_a?(Hash) ? validated : {}
111
125
 
126
+ # Restore the suppression the optspec default would otherwise erase.
127
+ opts['auth'] = nil if authsuppressed
128
+
112
129
  # Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
113
130
  # Every placeholder must resolve to a non-empty value: from
114
131
  # options["server"] (user), else the config default. A placeholder that
@@ -23,8 +23,28 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
23
23
  }
24
24
  }
25
25
 
26
+ // `auth: null` is the documented way to disable auth outright, and
27
+ // prepare_auth honours it before it ever reads the apikey. It cannot
28
+ // survive validate: depending on the struct port a stored null is either
29
+ // REPLACED by the optspec default - transmitting the credential the
30
+ // caller withheld - or REJECTED outright. Withhold the key for validate,
31
+ // then put the null back. Same fix as ts/js/go make_options.
32
+ //
33
+ // Read the map DIRECTLY rather than through get_prop: get_prop applies
34
+ // the Group A rule and returns the alt for a stored null, so it cannot
35
+ // tell an absent auth from a suppressed one - and only the latter is a
36
+ // suppression.
37
+ let auth_suppressed = match &options {
38
+ Value::Map(m) => matches!(m.borrow().get("auth"), Some(Value::Null)),
39
+ _ => false,
40
+ };
41
+
26
42
  let mut opts = vs::clone(&options);
27
43
 
44
+ if auth_suppressed {
45
+ opts = vs::del_prop(opts, &Value::str("auth"));
46
+ }
47
+
28
48
  // Feature add-order. `options.feature` may be an ordered List of
29
49
  // { name, active, ...opts } entries (the List position IS the order in
30
50
  // which features are added), or a { name: {opts} } map. Normalize a List
@@ -120,6 +140,11 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
120
140
  }
121
141
  }
122
142
 
143
+ // Restore the suppression the optspec default would otherwise erase.
144
+ if auth_suppressed {
145
+ setp(&opts, "auth", Value::Null);
146
+ }
147
+
123
148
  // Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
124
149
  // Every placeholder must resolve to a non-empty value: from options.server
125
150
  // (user), else the Config default. A placeholder that resolves to "" is a
@@ -254,8 +254,22 @@ object MakeOptions {
254
254
  }
255
255
  }
256
256
 
257
+ // `auth: null` is the documented way to suppress auth outright, and
258
+ // prepareAuth honours it before it ever reads the apikey. It cannot survive
259
+ // validate: a stored null reads as "no value", so the optspec `auth`
260
+ // default fires and the suppression becomes "use the default auth" -
261
+ // transmitting the credential the caller withheld. Withhold the key for
262
+ // validate, then put the null back. Same fix as ts/js/go/java makeOptions.
263
+ //
264
+ // Suppliedness cannot be recovered after validate, hence here, and it must
265
+ // tell an ABSENT auth from a present null: containsKey rather than a get()
266
+ // null check, which cannot distinguish them.
267
+ val authSuppressed = options.containsKey("auth") && null == options.get("auth")
268
+
257
269
  var opts = Struct.clone(options).asInstanceOf[JMap[String, Object]]
258
270
 
271
+ if (authSuppressed) opts.remove("auth")
272
+
259
273
  // Feature add-order. options.feature may be given as an ordered LIST of
260
274
  // { name, active, ...opts } entries (the list position IS the order in
261
275
  // which features are added), or as a { name: {opts} } map. Normalize a
@@ -330,6 +344,9 @@ object MakeOptions {
330
344
  val validated = Struct.validate(merged, optspec, vopts)
331
345
  opts = validated.asInstanceOf[JMap[String, Object]]
332
346
 
347
+ // Restore the suppression the optspec default would otherwise erase.
348
+ if (authSuppressed) opts.put("auth", null)
349
+
333
350
  // Restore system.fetch.
334
351
  if (sysFetch != null) {
335
352
  val sys = Helpers.toMapAny(opts.get("system"))
@@ -85,8 +85,24 @@ func makeOptionsUtil(_ ctx: Context) -> VMap {
85
85
  }
86
86
  }
87
87
 
88
+ // `auth: .null` is the documented way to disable auth outright, and
89
+ // prepareAuth honours it before it ever reads the apikey. It cannot survive
90
+ // validate: depending on the struct port a stored null is either REPLACED
91
+ // by the optspec default - transmitting the credential the caller withheld
92
+ // - or REJECTED outright. Withhold the key for validate, then put the null
93
+ // back. Same fix as ts/js/go makeOptions.
94
+ //
95
+ // `isNull` rather than `isNil`: the latter is also true for Noval, so it
96
+ // cannot tell an ABSENT auth from a suppressed one, and only the latter is
97
+ // a suppression.
98
+ let authSuppressed = options.entries["auth"]?.isNull ?? false
99
+
88
100
  let opts = clone(.map(options)).asMap ?? VMap()
89
101
 
102
+ if authSuppressed {
103
+ opts.entries.removeValue(forKey: "auth")
104
+ }
105
+
90
106
  // Feature add-order. options.feature may be given as an ordered LIST of
91
107
  // { name, active, ...opts } entries (the list position IS the order in which
92
108
  // features are added), or as a { name: {opts} } map. Normalize a list to a
@@ -124,6 +140,11 @@ func makeOptionsUtil(_ ctx: Context) -> VMap {
124
140
  let validated = validate(merged, optspec)
125
141
  let result = validated.asMap ?? VMap()
126
142
 
143
+ // Restore the suppression the optspec default would otherwise erase.
144
+ if authSuppressed {
145
+ result.entries["auth"] = .null
146
+ }
147
+
127
148
  // Restore system.fetch.
128
149
  if !isNil(sysFetch) {
129
150
  if let sm = gp(result, "system").asMap {