@voxgig/sdkgen 4.5.3 → 4.7.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 (248) 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 +22 -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/cmp/TestControl.d.ts +4 -0
  21. package/dist/cmp/TestControl.js +51 -0
  22. package/dist/cmp/TestControl.js.map +1 -0
  23. package/dist/helpers/applicability.d.ts +6 -0
  24. package/dist/helpers/applicability.js +103 -0
  25. package/dist/helpers/applicability.js.map +1 -0
  26. package/dist/helpers/collectDeps.js +4 -2
  27. package/dist/helpers/collectDeps.js.map +1 -1
  28. package/dist/helpers/featureSource.d.ts +3 -1
  29. package/dist/helpers/featureSource.js +105 -0
  30. package/dist/helpers/featureSource.js.map +1 -1
  31. package/dist/helpers/serverVars.d.ts +2 -1
  32. package/dist/helpers/serverVars.js +17 -0
  33. package/dist/helpers/serverVars.js.map +1 -1
  34. package/dist/sdkgen.d.ts +5 -3
  35. package/dist/sdkgen.js +16 -4
  36. package/dist/sdkgen.js.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/utility.d.ts +2 -1
  39. package/dist/utility.js +59 -1
  40. package/dist/utility.js.map +1 -1
  41. package/model/sdkgen.aon +97 -0
  42. package/package.json +1 -1
  43. package/project/.sdk/model/feature/feature-index.aon +1 -0
  44. package/project/.sdk/model/feature/secrets.aon +200 -0
  45. package/project/.sdk/model/target/ts.aon +8 -1
  46. package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
  47. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
  48. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
  49. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
  50. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
  51. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
  52. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
  53. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
  54. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
  55. package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
  56. package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
  57. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
  58. package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
  59. package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
  60. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
  61. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
  62. package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
  63. package/project/.sdk/src/cmp/go/Main_go.ts +8 -3
  64. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
  65. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
  66. package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
  67. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
  68. package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
  69. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
  70. package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
  71. package/project/.sdk/src/cmp/java/Main_java.ts +7 -2
  72. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
  73. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
  74. package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
  75. package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
  76. package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
  77. package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
  78. package/project/.sdk/src/cmp/js/Main_js.ts +7 -2
  79. package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
  80. package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
  81. package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
  82. package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
  83. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
  84. package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
  85. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
  86. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
  87. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
  88. package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
  89. package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
  90. package/project/.sdk/src/cmp/lua/Main_lua.ts +7 -2
  91. package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
  92. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
  93. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
  94. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
  95. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
  96. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  97. package/project/.sdk/src/cmp/perl/Main_perl.ts +7 -2
  98. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
  99. package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
  100. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
  101. package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
  102. package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
  103. package/project/.sdk/src/cmp/php/Main_php.ts +7 -2
  104. package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
  105. package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
  106. package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
  107. package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
  108. package/project/.sdk/src/cmp/py/Main_py.ts +7 -2
  109. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
  110. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
  111. package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
  112. package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
  113. package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
  114. package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
  115. package/project/.sdk/src/cmp/rb/Main_rb.ts +7 -2
  116. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
  117. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
  118. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
  119. package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
  120. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
  121. package/project/.sdk/src/cmp/rust/Main_rust.ts +7 -2
  122. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
  123. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
  124. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
  125. package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
  126. package/project/.sdk/src/cmp/scala/Main_scala.ts +7 -2
  127. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
  128. package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
  129. package/project/.sdk/src/cmp/ts/Config_ts.ts +55 -9
  130. package/project/.sdk/src/cmp/ts/Main_ts.ts +67 -3
  131. package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
  132. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
  133. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
  134. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
  135. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +75 -8
  136. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +36 -5
  137. package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
  138. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
  139. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
  140. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
  141. package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
  142. package/project/.sdk/tm/c/utility/make_options.c +22 -0
  143. package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
  144. package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
  145. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
  146. package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
  147. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
  148. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
  149. package/project/.sdk/tm/dart/test/utility.dart +39 -0
  150. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
  151. package/project/.sdk/tm/go/core/context.go +1 -1
  152. package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
  153. package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
  154. package/project/.sdk/tm/go/feature/test_feature.go +12 -5
  155. package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
  156. package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
  157. package/project/.sdk/tm/go/test/runner_test.go +46 -0
  158. package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
  159. package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
  160. package/project/.sdk/tm/go/utility/fetcher.go +2 -2
  161. package/project/.sdk/tm/go/utility/make_options.go +24 -5
  162. package/project/.sdk/tm/go/utility/make_point.go +1 -1
  163. package/project/.sdk/tm/go/utility/make_spec.go +17 -1
  164. package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
  165. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
  166. package/project/.sdk/tm/go/utility/transform_request.go +29 -1
  167. package/project/.sdk/tm/go/utility/transform_response.go +20 -1
  168. package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
  169. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
  170. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
  171. package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
  172. package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
  173. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
  174. package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
  175. package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
  176. package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
  177. package/project/.sdk/tm/js/test/utility.js +91 -1
  178. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
  179. package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
  180. package/project/.sdk/tm/lua/test/runner.lua +40 -0
  181. package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
  182. package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
  183. package/project/.sdk/tm/perl/t/runner.pm +30 -0
  184. package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
  185. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
  186. package/project/.sdk/tm/php/test/Runner.php +36 -0
  187. package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
  188. package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
  189. package/project/.sdk/tm/py/test/runner.py +32 -0
  190. package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
  191. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
  192. package/project/.sdk/tm/rb/test/runner.rb +25 -0
  193. package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
  194. package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
  195. package/project/.sdk/tm/rust/utility/make_options.rs +26 -1
  196. package/project/.sdk/tm/scala/utility/Make.scala +17 -0
  197. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
  198. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
  199. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +387 -0
  200. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +175 -0
  201. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +51 -0
  202. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
  203. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
  204. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
  205. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
  206. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
  207. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
  208. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
  209. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
  210. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
  211. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
  212. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
  213. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
  214. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
  215. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
  216. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
  217. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
  218. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
  219. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
  220. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
  221. package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
  222. package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
  223. package/project/.sdk/tm/ts/test/omni.ts +86 -0
  224. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
  225. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
  226. package/project/.sdk/tm/ts/test/utility.ts +122 -0
  227. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +694 -0
  228. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +244 -0
  229. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
  230. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
  231. package/project/.sdk/tm/zig/core/utility.zig +24 -1
  232. package/project/sdkgen-package.json +2 -1
  233. package/src/action/check.ts +18 -0
  234. package/src/cmp/AgentGuide.ts +1 -1
  235. package/src/cmp/AgentGuideContent.ts +9 -2
  236. package/src/cmp/Feature.ts +24 -0
  237. package/src/cmp/FeatureDocs.ts +7 -1
  238. package/src/cmp/ReadmeExplanation.ts +4 -1
  239. package/src/cmp/ReadmeFeatures.ts +1 -1
  240. package/src/cmp/ReadmeRefFeatures.ts +1 -1
  241. package/src/cmp/TestControl.ts +64 -0
  242. package/src/helpers/applicability.ts +123 -0
  243. package/src/helpers/collectDeps.ts +5 -1
  244. package/src/helpers/featureSource.ts +130 -0
  245. package/src/helpers/serverVars.ts +19 -0
  246. package/src/sdkgen.ts +16 -1
  247. package/src/utility.ts +71 -1
  248. package/project/.sdk/tm/ts/test/runner.ts +0 -425
@@ -143,6 +143,45 @@ bool skipIfMissingIds(T t, Map setup, List<String> requiredKeys) {
143
143
 
144
144
  // Per-test live pacing delay (ms). Read from sdk-test-control.json
145
145
  // `test.live.delayMs`; defaults to 500ms if absent or invalid.
146
+ // Extra SDK options every LIVE client is constructed with, read from
147
+ // sdk-test-control.json `test.client.options`.
148
+ //
149
+ // The generated live client knows two things: the base URL (from the spec)
150
+ // and the credential (from the environment). Everything else about how a
151
+ // particular API wants to be talked to - which features to switch on, and
152
+ // with what settings - is a property of THAT API, known to the project and
153
+ // to nothing in the toolchain.
154
+ //
155
+ // Merged UNDER the generated fields, so the suite's own base/apikey/server
156
+ // values win: this ADDS to the live client, it does not redirect it.
157
+ //
158
+ // Reserved fields are stripped HERE rather than at each merge site: the
159
+ // generated map only names a field when the model calls for one, so a
160
+ // 'base' in this block would face no competing value and would silently
161
+ // redirect the whole suite - credential included - to another host.
162
+ const List<String> liveReserved = [
163
+ 'base', 'prefix', 'suffix', 'server', 'apikey', 'secret'
164
+ ];
165
+
166
+ Map<String, dynamic> liveClientOptions() {
167
+ final ctrl = loadTestControl();
168
+ final t = ctrl['test'];
169
+ if (t is! Map) return <String, dynamic>{};
170
+ final c = t['client'];
171
+ if (c is! Map) return <String, dynamic>{};
172
+ final opts = c['options'];
173
+ if (opts is! Map) return <String, dynamic>{};
174
+
175
+ final out = <String, dynamic>{};
176
+ opts.forEach((k, v) {
177
+ if (!liveReserved.contains(k)) {
178
+ out['$k'] = v;
179
+ }
180
+ });
181
+ return out;
182
+ }
183
+
184
+
146
185
  int liveDelayMs() {
147
186
  final ctrl = loadTestControl();
148
187
  final t = ctrl['test'];
@@ -243,8 +243,24 @@ defmodule ProjectName.Utility do
243
243
  end
244
244
  end
245
245
 
246
+ # `auth` nil is the documented way to disable auth outright, and
247
+ # prepare_auth honours it before it ever reads the apikey. It cannot
248
+ # survive validate: a stored null reads as "no value", so the optspec's
249
+ # `auth` default fires and the suppression silently becomes "use default
250
+ # auth" - transmitting the credential the caller withheld. Withhold the
251
+ # key for validate, then put the nil back. Same fix as ts/js/go
252
+ # makeOptions.
253
+ #
254
+ # keysof rather than S.haskey: haskey is `getprop(...) != nil`, which
255
+ # collapses a stored null and so cannot tell an ABSENT auth from a
256
+ # suppressed one. keysof lists the key either way.
257
+ auth_suppressed =
258
+ S.ismap(options) and "auth" in S.keysof(options) and
259
+ S.getprop(options, "auth") == nil
260
+
246
261
  opts0 = S.clone(options)
247
262
  opts0 = if S.ismap(opts0), do: opts0, else: S.jm([])
263
+ if auth_suppressed, do: S.delprop(opts0, "auth")
248
264
 
249
265
  # Feature add-order. options.feature may be given as an ordered LIST of
250
266
  # {name, active, ...opts} entries (list position = add order) or a
@@ -329,6 +345,9 @@ defmodule ProjectName.Utility do
329
345
  validated = S.validate(merged, optspec)
330
346
  opts = if S.ismap(validated), do: validated, else: S.jm([])
331
347
 
348
+ # Restore the suppression the optspec default would otherwise erase.
349
+ if auth_suppressed, do: S.setprop(opts, "auth", nil)
350
+
332
351
  # Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
333
352
  # Every placeholder must resolve to a non-empty value: from options.server
334
353
  # (user), else the Config default. A placeholder that resolves to "" is a
@@ -245,7 +245,7 @@ func (ctx *Context) resolveOp(opname string) *Operation {
245
245
  return NewOperation(map[string]any{})
246
246
  }
247
247
 
248
- opcfg := vs.GetPath([]any{"entity", entname, "op", opname}, ctx.Config)
248
+ opcfg := vs.GetPath(ctx.Config, []any{"entity", entname, "op", opname})
249
249
 
250
250
  input := "match"
251
251
  if opname == "update" || opname == "create" {
@@ -367,7 +367,7 @@ func (f *CostFeature) body(ctx *core.Context) (float64, bool) {
367
367
  return 0, false
368
368
  }
369
369
 
370
- switch n := vs.GetPath(path, ctx.Result.Body).(type) {
370
+ switch n := vs.GetPath(ctx.Result.Body, path).(type) {
371
371
  case int:
372
372
  return float64(n) * f.perUnit(), true
373
373
  case int64:
@@ -109,7 +109,7 @@ func (f *PagingFeature) graphqlPreRequest(ctx *core.Context, paging map[string]a
109
109
  // Only bind variables the operation actually declares, or the server
110
110
  // rejects the document.
111
111
  declared := map[string]bool{}
112
- if varlist, ok := vs.GetPath([]any{"graphql", "vars"}, ctx.Point).([]any); ok {
112
+ if varlist, ok := vs.GetPath(ctx.Point, []any{"graphql", "vars"}).([]any); ok {
113
113
  for _, v := range varlist {
114
114
  if name, ok := vs.GetProp(v, "name").(string); ok {
115
115
  declared[name] = true
@@ -160,7 +160,7 @@ func (f *PagingFeature) PreResult(ctx *core.Context) {
160
160
 
161
161
  // Relay connections carry the cursor in pageInfo, at the path the model
162
162
  // recorded for this op.
163
- page := vs.GetPath([]any{"graphql", "page"}, ctx.Point)
163
+ page := vs.GetPath(ctx.Point, []any{"graphql", "page"})
164
164
  if pm, ok := page.(map[string]any); ok {
165
165
  if _, isMap := body.(map[string]any); isMap {
166
166
  // `connpath` locates the connection object inside the response
@@ -168,18 +168,18 @@ func (f *PagingFeature) PreResult(ctx *core.Context) {
168
168
  // to it.
169
169
  conn := body
170
170
  if cp, ok := pm["connpath"].(string); ok && cp != "" {
171
- if sub := vs.GetPath(cp, body); sub != nil {
171
+ if sub := vs.GetPath(body, cp); sub != nil {
172
172
  conn = sub
173
173
  }
174
174
  }
175
175
 
176
176
  if cp, ok := pm["cursor"].(string); ok && cp != "" {
177
- if cursor := vs.GetPath(cp, conn); cursor != nil {
177
+ if cursor := vs.GetPath(conn, cp); cursor != nil {
178
178
  paging["cursor"] = cursor
179
179
  }
180
180
  }
181
181
  if mp, ok := pm["more"].(string); ok && mp != "" {
182
- if more, ok := vs.GetPath(mp, conn).(bool); ok {
182
+ if more, ok := vs.GetPath(conn, mp).(bool); ok {
183
183
  paging["hasMore"] = more
184
184
  explicitMore = true
185
185
  }
@@ -323,7 +323,7 @@ func (f *TestFeature) buildArgs(ctx *core.Context, op *core.Operation, args map[
323
323
  // back to, so the seed-data query is built from the endpoint the request
324
324
  // will actually be sent to: a terminal `{id}` marks a record route, and
325
325
  // failing that the shallower path wins.
326
- points := vs.GetPath([]any{"entity", ctx.Entity.GetName(), "op", opname, "points"}, ctx.Config)
326
+ points := vs.GetPath(ctx.Config, []any{"entity", ctx.Entity.GetName(), "op", opname, "points"})
327
327
  point := vs.GetElem(points, 0)
328
328
  if plist, ok := points.([]any); ok {
329
329
  partsLen := func(p any) int {
@@ -355,12 +355,19 @@ func (f *TestFeature) buildArgs(ctx *core.Context, op *core.Operation, args map[
355
355
 
356
356
  // Path AND query: a path-only read misses a query-addressed record
357
357
  // (e.g. GET /result?trace_id=), which has no path param at all.
358
- paramsPath := vs.GetPath([]any{"args", "params"}, point)
358
+ paramsPath := vs.GetPath(point, []any{"args", "params"})
359
359
  reqdParams := vs.Select(paramsPath, map[string]any{"reqd": true})
360
- reqdFromParams := vs.Transform(reqdParams, []any{"`$EACH`", "", "`$KEY.name`"})
361
- queryPath := vs.GetPath([]any{"args", "query"}, point)
360
+ // The error return Transform gained in struct go 0.1.3 is discarded
361
+ // DELIBERATELY here, unlike in transform_request/response: these two
362
+ // transform a spec the SDK itself built from the point definition, not
363
+ // user data, so an error means a generator bug rather than something a
364
+ // caller can act on — and buildArgs has no error seam to route it
365
+ // through. An empty result is the conservative outcome: no required
366
+ // fields, which is what an absent spec already produces.
367
+ reqdFromParams, _ := vs.Transform(reqdParams, []any{"`$EACH`", "", "`$KEY.name`"})
368
+ queryPath := vs.GetPath(point, []any{"args", "query"})
362
369
  reqdQuery := vs.Select(queryPath, map[string]any{"reqd": true})
363
- reqdFromQuery := vs.Transform(reqdQuery, []any{"`$EACH`", "", "`$KEY.name`"})
370
+ reqdFromQuery, _ := vs.Transform(reqdQuery, []any{"`$EACH`", "", "`$KEY.name`"})
364
371
 
365
372
  qand := []any{}
366
373
  q := map[string]any{"`$AND`": &qand}
@@ -109,7 +109,12 @@ func TestCustomUtility(t *testing.T) {
109
109
  // that was broken: a plain function literal in a map[string]any has the
110
110
  // unnamed signature, so an override that only accepted the named type
111
111
  // shelved it in Custom. Converting here would have hidden that.
112
+ // "test" is the OPTION, not the `test` FEATURE: it says "this client
113
+ // is not live", so a REQUIRED OpenAPI server variable resolves to a
114
+ // deterministic test-<name> instead of panicking at construction. It
115
+ // installs no transport, so the override under test still stands.
112
116
  client := sdk.NewProjectNameSDK(map[string]any{
117
+ "test": map[string]any{"active": true},
113
118
  "utility": map[string]any{"fetcher": scripted},
114
119
  })
115
120
 
@@ -138,7 +143,12 @@ func TestCustomUtility(t *testing.T) {
138
143
  // the subtest above, and broken by any fix that swaps one for the other.
139
144
  t.Run("a converted fetcher is accepted too", func(t *testing.T) {
140
145
  reached := 0
146
+ // "test" is the OPTION, not the `test` FEATURE: it says "this client
147
+ // is not live", so a REQUIRED OpenAPI server variable resolves to a
148
+ // deterministic test-<name> instead of panicking at construction. It
149
+ // installs no transport, so the override under test still stands.
141
150
  client := sdk.NewProjectNameSDK(map[string]any{
151
+ "test": map[string]any{"active": true},
142
152
  "utility": map[string]any{
143
153
  "fetcher": sdk.FetcherFunc(func(
144
154
  ctx *sdk.Context, fullurl string, fetchdef map[string]any,
@@ -171,7 +181,12 @@ func TestCustomUtility(t *testing.T) {
171
181
  // An unknown key must still be attached rather than dropped, so the two
172
182
  // halves cannot be satisfied by a switch that also swallows extras.
173
183
  t.Run("an unknown key is still attached", func(t *testing.T) {
184
+ // "test" is the OPTION, not the `test` FEATURE: it says "this client
185
+ // is not live", so a REQUIRED OpenAPI server variable resolves to a
186
+ // deterministic test-<name> instead of panicking at construction. It
187
+ // installs no transport, so the override under test still stands.
174
188
  client := sdk.NewProjectNameSDK(map[string]any{
189
+ "test": map[string]any{"active": true},
175
190
  "utility": map[string]any{
176
191
  "notAUtilityMember": func() string { return "EXTRA" },
177
192
  },
@@ -114,7 +114,14 @@ func fcFetcher(res []any) sdk.FetcherFunc {
114
114
  // REPLACES the transport, so a client in test mode would shadow the script.
115
115
  func fcClient(kase map[string]any) *sdk.ProjectNameSDK {
116
116
  res, _ := kase["res"].([]any)
117
+ // "test" here is the OPTION, not the `test` FEATURE. It says "this
118
+ // client is not live", which is what makes a REQUIRED OpenAPI server
119
+ // variable resolve to a deterministic test-<name> rather than panic at
120
+ // construction (see makeOptions). It installs no transport, so the
121
+ // scripted fetcher still stands — the FEATURE is transport: 'base' and
122
+ // would shadow it, which is why this cannot just turn the feature on.
117
123
  opts := map[string]any{
124
+ "test": map[string]any{"active": true},
118
125
  "utility": map[string]any{"fetcher": fcFetcher(res)},
119
126
  }
120
127
  if f, ok := kase["feature"]; ok {
@@ -156,6 +156,52 @@ func isControlSkipped(kind, name, mode string) (bool, string) {
156
156
  }
157
157
 
158
158
  // liveDelayMs returns the configured per-test live delay in ms; default 500.
159
+ // liveClientOptions returns the extra SDK options every LIVE client is
160
+ // constructed with, from sdk-test-control.json `test.client.options`.
161
+ //
162
+ // The generated live client knows two things: the base URL (from the spec)
163
+ // and the credential (from the environment). Everything else about how a
164
+ // particular API wants to be talked to - which features to switch on, and
165
+ // with what settings - is a property of THAT API, known to the project and
166
+ // to nothing in the toolchain.
167
+ //
168
+ // Merged UNDER the generated fields, so the suite's own base/apikey/server
169
+ // values win: this ADDS to the live client, it does not redirect it.
170
+ //
171
+ // That contract is enforced HERE rather than left to each merge site: the
172
+ // generated map only names a field when the model calls for one, so a
173
+ // "base" in this block would face no competing value and would silently
174
+ // redirect the whole suite - credential included - to another host.
175
+ var liveReserved = map[string]bool{
176
+ "base": true, "prefix": true, "suffix": true,
177
+ "server": true, "apikey": true, "secret": true,
178
+ }
179
+
180
+ func liveClientOptions() map[string]any {
181
+ ctrl := loadTestControl()
182
+ test, _ := ctrl["test"].(map[string]any)
183
+ if test == nil {
184
+ return map[string]any{}
185
+ }
186
+ client, _ := test["client"].(map[string]any)
187
+ if client == nil {
188
+ return map[string]any{}
189
+ }
190
+ opts, _ := client["options"].(map[string]any)
191
+ if opts == nil {
192
+ return map[string]any{}
193
+ }
194
+
195
+ out := map[string]any{}
196
+ for k, v := range opts {
197
+ if !liveReserved[k] {
198
+ out[k] = v
199
+ }
200
+ }
201
+ return out
202
+ }
203
+
204
+
159
205
  func liveDelayMs() int {
160
206
  ctrl := loadTestControl()
161
207
  test, _ := ctrl["test"].(map[string]any)
@@ -33,7 +33,7 @@ type StructUtility struct {
33
33
  IsNode func(val any) bool
34
34
  Clone func(val any) any
35
35
  CloneFlags func(val any, flags map[string]bool) any
36
- GetPath func(path any, store any, injdefs ...*voxgigstruct.Injection) any
36
+ GetPath func(store any, path any, injdefs ...*voxgigstruct.Injection) any
37
37
  Inject func(val any, store any, injdefs ...*voxgigstruct.Injection) any
38
38
  Items func(val any) [][2]any
39
39
  Stringify func(val any, maxlen ...int) string
@@ -65,7 +65,7 @@ type StructUtility struct {
65
65
  Size func(val any) int
66
66
  Slice func(val any, args ...any) any
67
67
  StrKey func(key any) string
68
- Transform func(data any, spec any, injdefs ...*voxgigstruct.Injection) any
68
+ Transform func(data any, spec any, injdefs ...*voxgigstruct.Injection) (any, error)
69
69
  Typify func(value any) int
70
70
  Typename func(t int) string
71
71
  Validate func(data any, spec any, injdefs ...*voxgigstruct.Injection) (any, error)
@@ -73,8 +73,8 @@ type StructUtility struct {
73
73
  SKIP any
74
74
  DELETE any
75
75
 
76
- Jo func(kv ...any) map[string]any
77
- Ja func(v ...any) []any
76
+ Jm func(kv ...any) map[string]any
77
+ Jt func(v ...any) []any
78
78
 
79
79
  CheckPlacement func(modes int, ijname string, parentTypes int, inj *voxgigstruct.Injection) bool
80
80
  InjectorArgs func(argTypes []int, args []any) []any
@@ -681,7 +681,7 @@ func MatchNode(
681
681
  scalar := !structUtil.IsNode(val)
682
682
 
683
683
  if scalar {
684
- baseval := structUtil.GetPath(path, base)
684
+ baseval := structUtil.GetPath(base, path)
685
685
  if !MatchScalar(val, baseval, structUtil) {
686
686
  pass = false
687
687
  err = fmt.Errorf(
@@ -1058,8 +1058,8 @@ func NewStructSDK(opts map[string]any) *StructSDK {
1058
1058
  SKIP: voxgigstruct.SKIP,
1059
1059
  DELETE: voxgigstruct.DELETE,
1060
1060
 
1061
- Jo: voxgigstruct.Jo,
1062
- Ja: voxgigstruct.Ja,
1061
+ Jm: voxgigstruct.Jm,
1062
+ Jt: voxgigstruct.Jt,
1063
1063
 
1064
1064
  CheckPlacement: voxgigstruct.CheckPlacement,
1065
1065
  InjectorArgs: voxgigstruct.InjectorArgs,
@@ -159,20 +159,44 @@ func TestStructUtility(t *testing.T) {
159
159
  })
160
160
 
161
161
  t.Run("minor-escurl", func(t *testing.T) {
162
- structRunSet(t, minorSpec["escurl"], func(in string) string {
163
- return strings.ReplaceAll(voxgigstruct.EscUrl(fmt.Sprint(in)), "+", "%20")
164
- })
162
+ // Passed BARE, like every other minor subject. This used to wrap
163
+ // EscUrl in ReplaceAll(..., "+", "%20") because the vendored copy
164
+ // implemented it with net/url.QueryEscape, which form-encodes: a
165
+ // space became '+' rather than '%20'. The workaround made the
166
+ // shared corpus pass while make_url.go still emitted '+' into real
167
+ // paths and query strings. Upstream's EscUrl matches the reference
168
+ // encodeURIComponent exactly, so there is nothing left to paper over.
169
+ structRunSet(t, minorSpec["escurl"], voxgigstruct.EscUrl)
165
170
  })
166
171
 
167
172
  t.Run("minor-stringify", func(t *testing.T) {
168
173
  structRunSet(t, minorSpec["stringify"], func(v any) any {
169
174
  m := v.(map[string]any)
170
- val := m["val"]
175
+ val, hasVal := m["val"]
171
176
 
172
177
  if "__NULL__" == val {
173
178
  val = "null"
174
179
  }
175
180
 
181
+ // Go has no `undefined`. Upstream Stringify renders nil as
182
+ // "null" ON PURPOSE - in Go nil IS JSON null, and
183
+ // JSON.stringify(null) is "null" - so the corpus's two
184
+ // separate cases,
185
+ //
186
+ // {"val": null} -> "null" and {} -> ""
187
+ //
188
+ // both arrive here as a nil val and cannot be told apart by
189
+ // VALUE. They can be told apart by PRESENCE: the second case
190
+ // is the JavaScript stringify(undefined), and an absent key
191
+ // is the only thing Go has that means undefined.
192
+ //
193
+ // The runner is the right place for this: it is the seam
194
+ // between a language-neutral corpus and one language's type
195
+ // system, and it already bridges the same gap for pathify.
196
+ if !hasVal {
197
+ return ""
198
+ }
199
+
176
200
  max, hasMax := m["max"]
177
201
  if !hasMax || nil == max {
178
202
  return voxgigstruct.Stringify(val)
@@ -585,9 +609,21 @@ func TestStructUtility(t *testing.T) {
585
609
  } else {
586
610
  ks = *k
587
611
  }
612
+
613
+ // The ROOT node has no parent. The reference passes
614
+ // `undefined` there and logs `p=`; Go has only nil, which
615
+ // Stringify renders as "null" (its documented choice - see
616
+ // minor-stringify above). Rendering the absent parent as ""
617
+ // keeps the log identical to the reference's, which is what
618
+ // the corpus records.
619
+ ps := ""
620
+ if nil != p {
621
+ ps = voxgigstruct.Stringify(p)
622
+ }
623
+
588
624
  entry := "k=" + voxgigstruct.Stringify(ks) +
589
625
  ", v=" + voxgigstruct.Stringify(v) +
590
- ", p=" + voxgigstruct.Stringify(p) +
626
+ ", p=" + ps +
591
627
  ", t=" + voxgigstruct.Pathify(t)
592
628
  return entry
593
629
  }
@@ -840,7 +876,7 @@ func TestStructUtility(t *testing.T) {
840
876
  path := m["path"]
841
877
  store := m["store"]
842
878
 
843
- return voxgigstruct.GetPath(path, store)
879
+ return voxgigstruct.GetPath(store, path)
844
880
  })
845
881
  })
846
882
 
@@ -862,7 +898,7 @@ func TestStructUtility(t *testing.T) {
862
898
  Dpath: dpath,
863
899
  }
864
900
 
865
- return voxgigstruct.GetPath(path, store, inj)
901
+ return voxgigstruct.GetPath(store, path, inj)
866
902
  })
867
903
  })
868
904
 
@@ -884,10 +920,10 @@ func TestStructUtility(t *testing.T) {
884
920
  inj.Meta = metaMap
885
921
  }
886
922
  }
887
- return voxgigstruct.GetPath(path, store, inj)
923
+ return voxgigstruct.GetPath(store, path, inj)
888
924
  }
889
925
 
890
- return voxgigstruct.GetPath(path, store)
926
+ return voxgigstruct.GetPath(store, path)
891
927
  })
892
928
  })
893
929
 
@@ -947,14 +983,14 @@ func TestStructUtility(t *testing.T) {
947
983
  data := inVal["data"]
948
984
  spec := inVal["spec"]
949
985
  outVal := subtest["out"]
950
- result := voxgigstruct.Transform(data, spec)
986
+ result, _ := voxgigstruct.Transform(data, spec)
951
987
  if !reflect.DeepEqual(result, outVal) {
952
988
  t.Errorf("Expected: %v, Got: %v", outVal, result)
953
989
  }
954
990
  })
955
991
 
956
992
  t.Run("transform-paths", func(t *testing.T) {
957
- structRunSet(t, transformSpec["paths"], func(v any) any {
993
+ structRunSet(t, transformSpec["paths"], func(v any) (any, error) {
958
994
  m := v.(map[string]any)
959
995
  data := m["data"]
960
996
  spec := m["spec"]
@@ -963,7 +999,7 @@ func TestStructUtility(t *testing.T) {
963
999
  })
964
1000
 
965
1001
  t.Run("transform-cmds", func(t *testing.T) {
966
- structRunSet(t, transformSpec["cmds"], func(v any) any {
1002
+ structRunSet(t, transformSpec["cmds"], func(v any) (any, error) {
967
1003
  m := v.(map[string]any)
968
1004
  data := m["data"]
969
1005
  spec := m["spec"]
@@ -972,7 +1008,7 @@ func TestStructUtility(t *testing.T) {
972
1008
  })
973
1009
 
974
1010
  t.Run("transform-each", func(t *testing.T) {
975
- structRunSet(t, transformSpec["each"], func(v any) any {
1011
+ structRunSet(t, transformSpec["each"], func(v any) (any, error) {
976
1012
  m := v.(map[string]any)
977
1013
  data := m["data"]
978
1014
  spec := m["spec"]
@@ -981,7 +1017,7 @@ func TestStructUtility(t *testing.T) {
981
1017
  })
982
1018
 
983
1019
  t.Run("transform-pack", func(t *testing.T) {
984
- structRunSet(t, transformSpec["pack"], func(v any) any {
1020
+ structRunSet(t, transformSpec["pack"], func(v any) (any, error) {
985
1021
  m := v.(map[string]any)
986
1022
  data := m["data"]
987
1023
  spec := m["spec"]
@@ -990,7 +1026,7 @@ func TestStructUtility(t *testing.T) {
990
1026
  })
991
1027
 
992
1028
  t.Run("transform-ref", func(t *testing.T) {
993
- structRunSet(t, transformSpec["ref"], func(v any) any {
1029
+ structRunSet(t, transformSpec["ref"], func(v any) (any, error) {
994
1030
  m := v.(map[string]any)
995
1031
  data := m["data"]
996
1032
  spec := m["spec"]
@@ -999,7 +1035,7 @@ func TestStructUtility(t *testing.T) {
999
1035
  })
1000
1036
 
1001
1037
  t.Run("transform-format", func(t *testing.T) {
1002
- structRunSetFlags(t, transformSpec["format"], map[string]bool{"null": false}, func(v any) any {
1038
+ structRunSetFlags(t, transformSpec["format"], map[string]bool{"null": false}, func(v any) (any, error) {
1003
1039
  m := v.(map[string]any)
1004
1040
  data := m["data"]
1005
1041
  spec := m["spec"]
@@ -1021,7 +1057,7 @@ func TestStructUtility(t *testing.T) {
1021
1057
  })
1022
1058
 
1023
1059
  t.Run("transform-edge-apply", func(t *testing.T) {
1024
- result := voxgigstruct.Transform(
1060
+ result, _ := voxgigstruct.Transform(
1025
1061
  map[string]any{},
1026
1062
  []any{"`$APPLY`", func(v any) any { return 1 + v.(int) }, 1},
1027
1063
  )
@@ -1105,25 +1141,25 @@ func TestStructUtility(t *testing.T) {
1105
1141
  t.Run("transform-funcval", func(t *testing.T) {
1106
1142
  f0 := func() int { return 22 }
1107
1143
 
1108
- result1 := voxgigstruct.Transform(map[string]any{}, map[string]any{"x": 1})
1144
+ result1, _ := voxgigstruct.Transform(map[string]any{}, map[string]any{"x": 1})
1109
1145
  expected1 := map[string]any{"x": 1}
1110
1146
  if !reflect.DeepEqual(expected1, result1) {
1111
1147
  t.Errorf("Expected simple value transform result")
1112
1148
  }
1113
1149
 
1114
- result2 := voxgigstruct.Transform(map[string]any{}, map[string]any{"x": f0})
1150
+ result2, _ := voxgigstruct.Transform(map[string]any{}, map[string]any{"x": f0})
1115
1151
  var fr0 = result2.(map[string]any)["x"].(func() int)
1116
1152
  if f0() != fr0() {
1117
1153
  t.Errorf("Expected x to be f0")
1118
1154
  }
1119
1155
 
1120
- result3 := voxgigstruct.Transform(map[string]any{"a": 1}, map[string]any{"x": "`a`"})
1156
+ result3, _ := voxgigstruct.Transform(map[string]any{"a": 1}, map[string]any{"x": "`a`"})
1121
1157
  expected3 := map[string]any{"x": 1}
1122
1158
  if !reflect.DeepEqual(expected3, result3) {
1123
1159
  t.Errorf("Expected value lookup transform to work")
1124
1160
  }
1125
1161
 
1126
- result4 := voxgigstruct.Transform(map[string]any{"f0": f0}, map[string]any{"x": "`f0`"})
1162
+ result4, _ := voxgigstruct.Transform(map[string]any{"f0": f0}, map[string]any{"x": "`f0`"})
1127
1163
  var fr4 = result4.(map[string]any)["x"].(func() int)
1128
1164
  if 22 != fr4() {
1129
1165
  t.Errorf("Expected function to be preserved")
@@ -1347,22 +1383,22 @@ func TestStructUtility(t *testing.T) {
1347
1383
 
1348
1384
  t.Run("json-builder", func(t *testing.T) {
1349
1385
  expected0 := "{\n \"a\": 1\n}"
1350
- result0 := voxgigstruct.Jsonify(voxgigstruct.Jo("a", 1))
1386
+ result0 := voxgigstruct.Jsonify(voxgigstruct.Jm("a", 1))
1351
1387
  if result0 != expected0 {
1352
1388
  t.Errorf("Expected: %v, Got: %v", expected0, result0)
1353
1389
  }
1354
1390
 
1355
1391
  expected1 := "[\n \"b\",\n 2\n]"
1356
- result1 := voxgigstruct.Jsonify(voxgigstruct.Ja("b", 2))
1392
+ result1 := voxgigstruct.Jsonify(voxgigstruct.Jt("b", 2))
1357
1393
  if result1 != expected1 {
1358
1394
  t.Errorf("Expected: %v, Got: %v", expected1, result1)
1359
1395
  }
1360
1396
 
1361
1397
  expected2 := "{\n \"c\": \"C\",\n \"d\": {\n \"x\": true\n },\n \"e\": [\n null,\n false\n ]\n}"
1362
- result2 := voxgigstruct.Jsonify(voxgigstruct.Jo(
1398
+ result2 := voxgigstruct.Jsonify(voxgigstruct.Jm(
1363
1399
  "c", "C",
1364
- "d", voxgigstruct.Jo("x", true),
1365
- "e", voxgigstruct.Ja(nil, false),
1400
+ "d", voxgigstruct.Jm("x", true),
1401
+ "e", voxgigstruct.Jt(nil, false),
1366
1402
  ))
1367
1403
  if result2 != expected2 {
1368
1404
  t.Errorf("Expected:\n%v\nGot:\n%v", expected2, result2)
@@ -1397,7 +1433,7 @@ func TestStructUtility(t *testing.T) {
1397
1433
  },
1398
1434
  }
1399
1435
 
1400
- return voxgigstruct.GetPath(path, store, inj)
1436
+ return voxgigstruct.GetPath(store, path, inj)
1401
1437
  })
1402
1438
  })
1403
1439
  }
@@ -118,13 +118,13 @@ func fetcherUtil(ctx *core.Context, fullurl string, fetchdef map[string]any) (an
118
118
  }
119
119
 
120
120
  options := ctx.Client.OptionsMap()
121
- if vs.GetPath([]any{"feature", "test", "active"}, options) == true {
121
+ if vs.GetPath(options, []any{"feature", "test", "active"}) == true {
122
122
  return nil, ctx.MakeError("fetch_test_block",
123
123
  "Request blocked as test feature is active"+
124
124
  " (URL was: \""+fullurl+"\")")
125
125
  }
126
126
 
127
- sysFetch := vs.GetPath([]any{"system", "fetch"}, options)
127
+ sysFetch := vs.GetPath(options, []any{"system", "fetch"})
128
128
 
129
129
  if sysFetch == nil {
130
130
  return defaultHTTPFetch(fullurl, fetchdef)
@@ -36,6 +36,20 @@ func makeOptionsUtil(ctx *core.Context) map[string]any {
36
36
  }
37
37
  }
38
38
 
39
+ // `auth: nil` is the documented way to disable auth outright, and
40
+ // prepareAuth honours it before it ever reads the apikey. But validate
41
+ // treats a stored nil as "no value", so the optspec's `auth` default
42
+ // fires and the suppression silently becomes "use default auth" —
43
+ // transmitting a credential the caller explicitly asked not to send.
44
+ //
45
+ // Suppliedness cannot be recovered after validate, hence here. The
46
+ // two-value form is required: options["auth"] reads nil both when the
47
+ // key is ABSENT and when it is present and nil, and only the second is
48
+ // a suppression. (ts gets this for free — `null === options.auth` is
49
+ // false for an omitted key, since that reads undefined.)
50
+ authval, authgiven := options["auth"]
51
+ authsuppressed := authgiven && authval == nil
52
+
39
53
  opts := vs.Clone(options).(map[string]any)
40
54
 
41
55
  // Feature add-order (feature #2). options.feature may be given as an ordered
@@ -129,7 +143,7 @@ func makeOptionsUtil(ctx *core.Context) map[string]any {
129
143
 
130
144
  // Preserve system.fetch before merge/validate.
131
145
  var sysFetch any
132
- if sf := vs.GetPath([]any{"system", "fetch"}, opts); sf != nil {
146
+ if sf := vs.GetPath(opts, []any{"system", "fetch"}); sf != nil {
133
147
  sysFetch = sf
134
148
  }
135
149
 
@@ -141,6 +155,11 @@ func makeOptionsUtil(ctx *core.Context) map[string]any {
141
155
  validated, _ := vs.Validate(merged, optspec)
142
156
  opts = validated.(map[string]any)
143
157
 
158
+ // Restore the suppression the optspec default would otherwise erase.
159
+ if authsuppressed {
160
+ opts["auth"] = nil
161
+ }
162
+
144
163
  // Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
145
164
  // Every placeholder must resolve to a non-empty value: from
146
165
  // options["server"] (user), else the config default. A placeholder that
@@ -151,15 +170,15 @@ func makeOptionsUtil(ctx *core.Context) map[string]any {
151
170
  // PANICS (construction-time misconfiguration, as regexp.MustCompile).
152
171
  if base, ok := opts["base"].(string); ok && strings.Contains(base, "{") {
153
172
  testmode := false
154
- if ta, ok := vs.GetPath([]any{"test", "active"}, opts).(bool); ok && ta {
173
+ if ta, ok := vs.GetPath(opts, []any{"test", "active"}).(bool); ok && ta {
155
174
  testmode = true
156
175
  }
157
- if fa, ok := vs.GetPath([]any{"feature", "test", "active"}, opts).(bool); ok && fa {
176
+ if fa, ok := vs.GetPath(opts, []any{"feature", "test", "active"}).(bool); ok && fa {
158
177
  testmode = true
159
178
  }
160
179
  server := core.ToMapAny(opts["server"])
161
180
  sdkname := "SDK"
162
- if mn, ok := vs.GetPath([]any{"main", "name"}, config).(string); ok && mn != "" {
181
+ if mn, ok := vs.GetPath(config, []any{"main", "name"}).(string); ok && mn != "" {
163
182
  sdkname = mn
164
183
  }
165
184
  resolved := serverVarRe.ReplaceAllStringFunc(base, func(ph string) string {
@@ -191,7 +210,7 @@ func makeOptionsUtil(ctx *core.Context) map[string]any {
191
210
 
192
211
  // Derived clean config.
193
212
  cleanKeys := "key,token,id"
194
- if ck := vs.GetPath([]any{"clean", "keys"}, opts); ck != nil {
213
+ if ck := vs.GetPath(opts, []any{"clean", "keys"}); ck != nil {
195
214
  if cks, ok := ck.(string); ok {
196
215
  cleanKeys = cks
197
216
  }