@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
@@ -24,9 +24,37 @@ func transformRequestUtil(ctx *core.Context) any {
24
24
  return ctx.Reqdata
25
25
  }
26
26
 
27
- reqdata := vs.Transform(map[string]any{
27
+ // Transform gained an error return in struct go 0.1.3. The TS reference
28
+ // wraps this call in try/catch and returns makeError(ctx, err) — but ts's
29
+ // makeError THROWS unless ctrl.throw is false, so that `return` is only
30
+ // ever reached on the non-throwing path. go's makeError cannot throw; it
31
+ // returns the error instead. Handing that back as the `any` this seam
32
+ // returns would make it the request BODY, so makeSpec re-raises it (see
33
+ // the abort there). On the ctrl.throw-false path makeError yields
34
+ // result.Resdata and this returns a real value, as ts does.
35
+ reqdata, terr := vs.Transform(map[string]any{
28
36
  "reqdata": ctx.Reqdata,
29
37
  }, reqform)
30
38
 
39
+ if terr != nil {
40
+ // Only the NON-throwing path goes through makeError here. With
41
+ // ctrl.throw false, ts's makeError returns result.resdata and the
42
+ // pipeline continues with it as the body; mirror that.
43
+ //
44
+ // On the throwing path the error is handed back raw, for makeSpec to
45
+ // re-raise and the operation to process once. Calling makeError here
46
+ // as well would report the same failure TWICE — it fires the
47
+ // PreUnexpected hook, and the operation fires it again when it
48
+ // handles what makeSpec re-raised. The built-in observability
49
+ // features happen to dedupe on a per-context marker, but a
50
+ // project-supplied feature may legitimately count or log on every
51
+ // dispatch, and ts dispatches once.
52
+ if ctx.Ctrl != nil && ctx.Ctrl.Throw != nil && !*ctx.Ctrl.Throw {
53
+ out, _ := makeErrorUtil(ctx, terr)
54
+ return out
55
+ }
56
+ return terr
57
+ }
58
+
31
59
  return reqdata
32
60
  }
@@ -29,7 +29,12 @@ func transformResponseUtil(ctx *core.Context) any {
29
29
  return nil
30
30
  }
31
31
 
32
- resdata := vs.Transform(map[string]any{
32
+ // Transform gained an error return in struct go 0.1.3. Both callers
33
+ // (makeResponse, makeResult) DISCARD this function's return value, so an
34
+ // error handed back that way would vanish. The convention they do honour
35
+ // is result.Err — makeResponse sets result.Ok only when it is nil, and
36
+ // GraphqlErrors reports the same way — so report there.
37
+ resdata, terr := vs.Transform(map[string]any{
33
38
  "ok": result.Ok,
34
39
  "status": result.Status,
35
40
  "statusText": result.StatusText,
@@ -40,6 +45,20 @@ func transformResponseUtil(ctx *core.Context) any {
40
45
  "resmatch": result.Resmatch,
41
46
  }, resform)
42
47
 
48
+ if terr != nil {
49
+ // Ok must be cleared as well as Err. This runs from BOTH makeResponse
50
+ // and makeResult, and only the makeResult call actually reaches the
51
+ // transform: resultBasic never sets Ok, so the makeResponse call
52
+ // returns at the !result.Ok guard above, and makeResponse then sets
53
+ // Ok true. By the time the transform really runs, Ok is already true —
54
+ // and doneUtil returns result.Resdata whenever Ok is true, without
55
+ // ever consulting Err. Recording only the error would leave a failed
56
+ // response transform resolving successfully.
57
+ result.Err = ctx.MakeError("resform", "resform: "+terr.Error())
58
+ result.Ok = false
59
+ return nil
60
+ }
61
+
43
62
  result.Resdata = resdata
44
63
  return resdata
45
64
  }
@@ -86,7 +86,12 @@ public class CustomUtilityTest {
86
86
  // transport: 'base' - it REPLACES the transport by design - so a client in
87
87
  // test mode would shadow the scripted fetcher and this would assert
88
88
  // nothing.
89
+ // "test" is the OPTION, not the `test` FEATURE: it says "this client is
90
+ // not live", so a REQUIRED OpenAPI server variable resolves to a
91
+ // deterministic test-<name> instead of failing construction. It installs
92
+ // no transport, so the scripted fetcher still stands.
89
93
  ProjectNameSDK client = new ProjectNameSDK(fhMap(
94
+ "test", fhMap("active", true),
90
95
  "utility", fhMap("fetcher", scripted)));
91
96
 
92
97
  Utility u = client.getUtility();
@@ -106,7 +111,12 @@ public class CustomUtilityTest {
106
111
  // halves cannot be satisfied by a switch that also swallows extras.
107
112
  @Test
108
113
  public void anUnknownKeyIsStillAttached() {
114
+ // "test" is the OPTION, not the `test` FEATURE: it says "this client is
115
+ // not live", so a REQUIRED OpenAPI server variable resolves to a
116
+ // deterministic test-<name> instead of failing construction. It installs
117
+ // no transport, so the scripted fetcher still stands.
109
118
  ProjectNameSDK client = new ProjectNameSDK(fhMap(
119
+ "test", fhMap("active", true),
110
120
  "utility", fhMap("notAUtilityMember", (Supplier<String>) () -> "EXTRA")));
111
121
  assertTrue(client.getUtility().custom.containsKey("notAUtilityMember"),
112
122
  "an unknown utility key was dropped instead of kept in custom");
@@ -128,7 +128,14 @@ public class FeatureCorpusTest {
128
128
  Map<String, Object> utility = new LinkedHashMap<>();
129
129
  utility.put("fetcher", scriptedFetcher(kase.get("res")));
130
130
 
131
+ // "test" is the OPTION, not the `test` FEATURE: it says "this client is
132
+ // not live", so a REQUIRED OpenAPI server variable resolves to a
133
+ // deterministic test-<name> instead of failing construction. It installs
134
+ // no transport, so the scripted fetcher still stands.
131
135
  Map<String, Object> opts = new LinkedHashMap<>();
136
+ Map<String, Object> testopt = new LinkedHashMap<>();
137
+ testopt.put("active", true);
138
+ opts.put("test", testopt);
132
139
  opts.put("utility", utility);
133
140
  if (kase.get("feature") != null) {
134
141
  opts.put("feature", kase.get("feature"));
@@ -271,7 +271,10 @@ public class PrimaryUtilityTest {
271
271
  @Test
272
272
  public void fetcherLive() {
273
273
  final List<Map<String, Object>> calls = new ArrayList<>();
274
+ // Concrete base: a live construction must satisfy any server variables a
275
+ // templated base URL declares; a literal base sidesteps the requirement.
274
276
  ProjectNameSDK liveClient = new ProjectNameSDK(fhMap(
277
+ "base", "http://localhost:8080",
275
278
  "system", fhMap(
276
279
  "fetch", (BiFunction<String, Map<String, Object>, Map<String, Object>>)
277
280
  (url, fetchdef) -> {
@@ -296,7 +299,10 @@ public class PrimaryUtilityTest {
296
299
  public void fetcherBlockedTestMode() {
297
300
  // Create a live SDK then set mode to test (not using testSDK, which
298
301
  // installs the test feature).
302
+ // Concrete base: a live construction must satisfy any server variables a
303
+ // templated base URL declares; a literal base sidesteps the requirement.
299
304
  ProjectNameSDK blockedClient = new ProjectNameSDK(fhMap(
305
+ "base", "http://localhost:8080",
300
306
  "system", fhMap(
301
307
  "fetch", (BiFunction<String, Map<String, Object>, Map<String, Object>>)
302
308
  (url, fetchdef) -> new LinkedHashMap<>())));
@@ -172,6 +172,49 @@ public final class RunnerSupport {
172
172
  return null;
173
173
  }
174
174
 
175
+ // liveClientOptions returns the extra SDK options every LIVE client is
176
+ // constructed with, from sdk-test-control.json `test.client.options`.
177
+ //
178
+ // The generated live client knows two things: the base URL (from the
179
+ // spec) and the credential (from the environment). Everything else about
180
+ // how a particular API wants to be talked to - which features to switch
181
+ // on, and with what settings - is a property of THAT API, known to the
182
+ // project and to nothing in the toolchain.
183
+ //
184
+ // Merged UNDER the generated fields, so the suite's own base/apikey/server
185
+ // values win: this ADDS to the live client, it does not redirect it.
186
+ //
187
+ // That contract is enforced HERE rather than left to each merge site: the
188
+ // generated map only names a field when the model calls for one, so a
189
+ // "base" in this block would face no competing value and would silently
190
+ // redirect the whole suite - credential included - to another host.
191
+ private static final List<String> LIVE_RESERVED = List.of(
192
+ "base", "prefix", "suffix", "server", "apikey", "secret");
193
+
194
+ public static Map<String, Object> liveClientOptions() {
195
+ Map<String, Object> ctrl = loadTestControl();
196
+ Map<String, Object> test = Helpers.toMapAny(ctrl.get("test"));
197
+ if (test == null) {
198
+ return new LinkedHashMap<>();
199
+ }
200
+ Map<String, Object> client = Helpers.toMapAny(test.get("client"));
201
+ if (client == null) {
202
+ return new LinkedHashMap<>();
203
+ }
204
+ Map<String, Object> opts = Helpers.toMapAny(client.get("options"));
205
+ if (opts == null) {
206
+ return new LinkedHashMap<>();
207
+ }
208
+
209
+ Map<String, Object> out = new LinkedHashMap<>();
210
+ for (Map.Entry<String, Object> e : opts.entrySet()) {
211
+ if (!LIVE_RESERVED.contains(e.getKey())) {
212
+ out.put(e.getKey(), e.getValue());
213
+ }
214
+ }
215
+ return out;
216
+ }
217
+
175
218
  // liveDelayMs returns the configured per-test live delay in ms; default 500.
176
219
  public static int liveDelayMs() {
177
220
  Map<String, Object> ctrl = loadTestControl();
@@ -4,6 +4,8 @@ import java.util.ArrayList;
4
4
  import java.util.LinkedHashMap;
5
5
  import java.util.List;
6
6
  import java.util.Map;
7
+ import java.util.regex.Matcher;
8
+ import java.util.regex.Pattern;
7
9
 
8
10
  import JAVAPACKAGE.core.Context;
9
11
  import JAVAPACKAGE.core.Helpers;
@@ -12,6 +14,9 @@ import JAVAPACKAGE.utility.struct.Struct;
12
14
  @SuppressWarnings({"unchecked"})
13
15
  final class MakeOptions {
14
16
 
17
+ // {name} placeholders in a templated server URL (OpenAPI server variables).
18
+ private static final Pattern SERVER_VAR = Pattern.compile("\\{([A-Za-z0-9_]+)\\}");
19
+
15
20
  private MakeOptions() {}
16
21
 
17
22
  static Map<String, Object> makeOptions(Context ctx) {
@@ -36,8 +41,24 @@ final class MakeOptions {
36
41
  }
37
42
  }
38
43
 
44
+ // `auth: null` is the documented way to disable auth outright, and
45
+ // PrepareAuth honours it before it ever reads the apikey. It cannot
46
+ // survive validate: depending on the struct port a stored null is either
47
+ // REPLACED by the optspec default - transmitting the credential the
48
+ // caller withheld - or REJECTED outright. Withhold the key for validate,
49
+ // then put the null back. Same fix as ts/js/go makeOptions.
50
+ //
51
+ // Suppliedness cannot be recovered after validate, hence here, and it
52
+ // must tell an ABSENT auth from a present null: containsKey rather than
53
+ // a get() null check, which cannot distinguish them.
54
+ boolean authSuppressed = options.containsKey("auth") && null == options.get("auth");
55
+
39
56
  Map<String, Object> opts = (Map<String, Object>) Struct.clone(options);
40
57
 
58
+ if (authSuppressed) {
59
+ opts.remove("auth");
60
+ }
61
+
41
62
  // Feature add-order. options.feature may be given as an ordered LIST of
42
63
  // { name, active, ...opts } entries (the list position IS the order in
43
64
  // which features are added), or as a { name: {opts} } map. Normalize a
@@ -91,7 +112,12 @@ final class MakeOptions {
91
112
  + "\"utility\": {},"
92
113
  + "\"system\": {},"
93
114
  + "\"test\": { \"active\": false, \"entity\": { \"`$OPEN`\": true } },"
94
- + "\"clean\": { \"keys\": \"key,token,id\" }"
115
+ + "\"clean\": { \"keys\": \"key,token,id\" },"
116
+ // Server-variable values for a templated base URL (OpenAPI server
117
+ // variables): {name} placeholders in `base` are substituted from
118
+ // this map at construction. Spec defaults arrive via the generated
119
+ // Config; user values override them.
120
+ + "\"server\": { \"`$CHILD`\": \"\" }"
95
121
  + "}");
96
122
 
97
123
  // Preserve system.fetch before merge/validate.
@@ -115,6 +141,11 @@ final class MakeOptions {
115
141
  Object validated = Struct.validate(merged, optspec, vopts);
116
142
  opts = (Map<String, Object>) validated;
117
143
 
144
+ // Restore the suppression the optspec default would otherwise erase.
145
+ if (authSuppressed) {
146
+ opts.put("auth", null);
147
+ }
148
+
118
149
  // Restore system.fetch.
119
150
  if (sysFetch != null) {
120
151
  Map<String, Object> sys = Helpers.toMapAny(opts.get("system"));
@@ -128,6 +159,58 @@ final class MakeOptions {
128
159
  }
129
160
  }
130
161
 
162
+ // Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
163
+ //
164
+ // Every placeholder must resolve to a non-empty value: from
165
+ // options["server"] (user), else the Config default. A placeholder that
166
+ // resolves to "" is a construction ERROR in live mode - the URL cannot
167
+ // work - but in test mode substitutes the deterministic value
168
+ // `test-<name>` so offline tests need no configuration.
169
+ Object baseObj = opts.get("base");
170
+ if (baseObj instanceof String && ((String) baseObj).indexOf('{') >= 0) {
171
+ String base = (String) baseObj;
172
+
173
+ boolean testmode =
174
+ Boolean.TRUE.equals(Struct.getpath(opts, List.of("test", "active")))
175
+ || Boolean.TRUE.equals(
176
+ Struct.getpath(opts, List.of("feature", "test", "active")));
177
+
178
+ Map<String, Object> server = Helpers.toMapAny(opts.get("server"));
179
+
180
+ Matcher m = SERVER_VAR.matcher(base);
181
+ StringBuilder resolved = new StringBuilder();
182
+ int at = 0;
183
+ while (m.find()) {
184
+ String name = m.group(1);
185
+ Object raw = server == null ? null : server.get(name);
186
+ String val = raw instanceof String ? (String) raw : "";
187
+
188
+ if ("".equals(val)) {
189
+ if (testmode) {
190
+ val = "test-" + name;
191
+ }
192
+ else {
193
+ String sdkname = "SDK";
194
+ Object mainName = Struct.getpath(config, List.of("main", "name"));
195
+ if (mainName instanceof String) {
196
+ sdkname = (String) mainName;
197
+ }
198
+ throw new IllegalArgumentException(
199
+ sdkname + ": the server variable '" + name + "' is required: "
200
+ + "the API base URL is '" + base + "' - pass "
201
+ + "options.put(\"server\", Map.of(\"" + name + "\", \"...\")) "
202
+ + "in the SDK options");
203
+ }
204
+ }
205
+
206
+ resolved.append(base, at, m.start()).append(val);
207
+ at = m.end();
208
+ }
209
+ resolved.append(base.substring(at));
210
+
211
+ opts.put("base", resolved.toString());
212
+ }
213
+
131
214
  // Derived clean config.
132
215
  String cleanKeys = "key,token,id";
133
216
  Object ck = Struct.getpath(opts, List.of("clean", "keys"));
@@ -9,8 +9,27 @@ function makeOptions(ctx) {
9
9
  const validate = struct.validate
10
10
  const escre = struct.escre
11
11
 
12
+ // `auth: null` is the documented way to disable auth outright, and
13
+ // prepareAuth honours it before it ever reads the apikey. But it cannot
14
+ // survive validate: this target's struct rejects a stored null outright
15
+ // ("Expected field auth to be map, but found no value"), so passing it
16
+ // through turns the documented suppression into a CONSTRUCTION ERROR.
17
+ //
18
+ // Withhold the key for validate, then put the null back. Deleting rather
19
+ // than leaving it is what avoids the throw; restoring it afterwards is
20
+ // what stops the optspec's `auth` default from taking its place, which
21
+ // would transmit the credential the caller asked to withhold.
22
+ //
23
+ // Suppliedness cannot be recovered after validate, hence here. `null ===`
24
+ // rather than `null ==`, so an OMITTED auth is not read as a suppression.
25
+ const authsuppressed = null === (options || {}).auth
26
+
12
27
  let opts = { ...(options || {}) }
13
28
 
29
+ if (authsuppressed) {
30
+ delete opts.auth
31
+ }
32
+
14
33
  // Feature add-order. `options.feature` may be given as an ordered ARRAY of
15
34
  // { name, active, ...opts } entries (the array position IS the order in
16
35
  // which features are added), or as a { name: {opts} } map. Normalize an
@@ -108,6 +127,11 @@ function makeOptions(ctx) {
108
127
 
109
128
  opts = validate(opts, optspec)
110
129
 
130
+ // Restore the suppression the optspec default would otherwise erase.
131
+ if (authsuppressed) {
132
+ opts.auth = null
133
+ }
134
+
111
135
  // Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
112
136
  // Every placeholder must resolve to a non-empty value: from
113
137
  // options.server (user), else the Config default. A placeholder that