@voxgig/sdkgen 3.4.7 → 3.5.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 (194) hide show
  1. package/bin/voxgig-sdkgen +78 -10
  2. package/dist/action/action.d.ts +5 -2
  3. package/dist/action/action.js +113 -6
  4. package/dist/action/action.js.map +1 -1
  5. package/dist/action/check.d.ts +15 -0
  6. package/dist/action/check.js +347 -0
  7. package/dist/action/check.js.map +1 -0
  8. package/dist/action/dispatch.d.ts +8 -0
  9. package/dist/action/dispatch.js +74 -0
  10. package/dist/action/dispatch.js.map +1 -0
  11. package/dist/action/docs.d.ts +4 -0
  12. package/dist/action/docs.js +236 -0
  13. package/dist/action/docs.js.map +1 -0
  14. package/dist/action/doctor.d.ts +5 -2
  15. package/dist/action/doctor.js +526 -74
  16. package/dist/action/doctor.js.map +1 -1
  17. package/dist/action/feature.js +96 -17
  18. package/dist/action/feature.js.map +1 -1
  19. package/dist/action/kind.d.ts +30 -0
  20. package/dist/action/kind.js +258 -0
  21. package/dist/action/kind.js.map +1 -0
  22. package/dist/action/package.d.ts +25 -0
  23. package/dist/action/package.js +852 -0
  24. package/dist/action/package.js.map +1 -0
  25. package/dist/action/resolve.d.ts +18 -0
  26. package/dist/action/resolve.js +280 -0
  27. package/dist/action/resolve.js.map +1 -0
  28. package/dist/action/target.d.ts +7 -1
  29. package/dist/action/target.js +240 -97
  30. package/dist/action/target.js.map +1 -1
  31. package/dist/cmp/Docs.d.ts +4 -0
  32. package/dist/cmp/Docs.js +98 -0
  33. package/dist/cmp/Docs.js.map +1 -0
  34. package/dist/cmp/ExternalDocs.d.ts +2 -0
  35. package/dist/cmp/ExternalDocs.js +43 -0
  36. package/dist/cmp/ExternalDocs.js.map +1 -0
  37. package/dist/helpers/definition.d.ts +5 -0
  38. package/dist/helpers/definition.js +72 -0
  39. package/dist/helpers/definition.js.map +1 -0
  40. package/dist/helpers/featureSource.d.ts +8 -2
  41. package/dist/helpers/featureSource.js +61 -12
  42. package/dist/helpers/featureSource.js.map +1 -1
  43. package/dist/helpers/junk.d.ts +11 -0
  44. package/dist/helpers/junk.js +149 -0
  45. package/dist/helpers/junk.js.map +1 -0
  46. package/dist/helpers/manifest.d.ts +50 -0
  47. package/dist/helpers/manifest.js +328 -0
  48. package/dist/helpers/manifest.js.map +1 -0
  49. package/dist/helpers/modelcheck.d.ts +22 -0
  50. package/dist/helpers/modelcheck.js +203 -0
  51. package/dist/helpers/modelcheck.js.map +1 -0
  52. package/dist/helpers/semver.d.ts +4 -0
  53. package/dist/helpers/semver.js +174 -0
  54. package/dist/helpers/semver.js.map +1 -0
  55. package/dist/helpers/shipped.d.ts +4 -0
  56. package/dist/helpers/shipped.js +36 -0
  57. package/dist/helpers/shipped.js.map +1 -0
  58. package/dist/helpers/stdrep.d.ts +9 -1
  59. package/dist/helpers/stdrep.js +22 -0
  60. package/dist/helpers/stdrep.js.map +1 -1
  61. package/dist/sdkgen.d.ts +15 -3
  62. package/dist/sdkgen.js +233 -67
  63. package/dist/sdkgen.js.map +1 -1
  64. package/dist/tsconfig.tsbuildinfo +1 -1
  65. package/dist/types.d.ts +10 -2
  66. package/dist/utility.js +17 -0
  67. package/dist/utility.js.map +1 -1
  68. package/model/sdkgen.aontu +69 -0
  69. package/package.json +3 -2
  70. package/project/.sdk/model/feature/audit.aontu +1 -0
  71. package/project/.sdk/model/feature/cache.aontu +1 -0
  72. package/project/.sdk/model/feature/clienttrack.aontu +1 -0
  73. package/project/.sdk/model/feature/debug.aontu +1 -0
  74. package/project/.sdk/model/feature/idempotency.aontu +1 -0
  75. package/project/.sdk/model/feature/log.aontu +1 -0
  76. package/project/.sdk/model/feature/metrics.aontu +1 -0
  77. package/project/.sdk/model/feature/netsim.aontu +1 -0
  78. package/project/.sdk/model/feature/paging.aontu +1 -0
  79. package/project/.sdk/model/feature/proxy.aontu +1 -0
  80. package/project/.sdk/model/feature/ratelimit.aontu +1 -0
  81. package/project/.sdk/model/feature/rbac.aontu +1 -0
  82. package/project/.sdk/model/feature/retry.aontu +1 -0
  83. package/project/.sdk/model/feature/streaming.aontu +1 -0
  84. package/project/.sdk/model/feature/telemetry.aontu +1 -0
  85. package/project/.sdk/model/feature/test.aontu +1 -0
  86. package/project/.sdk/model/feature/timeout.aontu +1 -0
  87. package/project/.sdk/model/target/c.aontu +1 -0
  88. package/project/.sdk/model/target/clojure.aontu +1 -0
  89. package/project/.sdk/model/target/cpp.aontu +1 -0
  90. package/project/.sdk/model/target/dart.aontu +1 -0
  91. package/project/.sdk/model/target/elixir.aontu +1 -0
  92. package/project/.sdk/model/target/go-cli.aontu +1 -0
  93. package/project/.sdk/model/target/go-mcp.aontu +1 -0
  94. package/project/.sdk/model/target/go.aontu +1 -0
  95. package/project/.sdk/model/target/haskell.aontu +1 -0
  96. package/project/.sdk/model/target/java.aontu +1 -0
  97. package/project/.sdk/model/target/js.aontu +1 -0
  98. package/project/.sdk/model/target/kotlin.aontu +1 -0
  99. package/project/.sdk/model/target/lean.aontu +1 -0
  100. package/project/.sdk/model/target/lua.aontu +1 -0
  101. package/project/.sdk/model/target/ocaml.aontu +1 -0
  102. package/project/.sdk/model/target/perl.aontu +1 -0
  103. package/project/.sdk/model/target/php.aontu +1 -0
  104. package/project/.sdk/model/target/py-data.aontu +1 -0
  105. package/project/.sdk/model/target/py.aontu +1 -0
  106. package/project/.sdk/model/target/rb.aontu +1 -0
  107. package/project/.sdk/model/target/rust.aontu +1 -0
  108. package/project/.sdk/model/target/scala.aontu +1 -0
  109. package/project/.sdk/model/target/seneca-provider.aontu +1 -0
  110. package/project/.sdk/model/target/zig.aontu +1 -0
  111. package/project/.sdk/src/cmp/c/Config_c.ts +44 -0
  112. package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +3 -1
  113. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +37 -42
  114. package/project/.sdk/src/cmp/clojure/utility_clojure.ts +42 -0
  115. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +18 -0
  116. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +125 -52
  117. package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +3 -1
  118. package/project/.sdk/src/cmp/csharp/utility_csharp.ts +94 -6
  119. package/project/.sdk/src/cmp/dart/Config_dart.ts +43 -5
  120. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +74 -0
  121. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -9
  122. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +16 -5
  123. package/project/.sdk/src/cmp/dart/utility_dart.ts +18 -0
  124. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +128 -13
  125. package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +3 -1
  126. package/project/.sdk/src/cmp/elixir/utility_elixir.ts +6 -0
  127. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +35 -52
  128. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +1 -0
  129. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +1 -1
  130. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +15 -5
  131. package/project/.sdk/src/cmp/java/Config_java.ts +21 -0
  132. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +15 -0
  133. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +42 -39
  134. package/project/.sdk/src/cmp/perl/Config_perl.ts +15 -0
  135. package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +3 -1
  136. package/project/.sdk/src/cmp/rust/Config_rust.ts +44 -0
  137. package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +4 -2
  138. package/project/.sdk/src/cmp/scala/Config_scala.ts +15 -0
  139. package/project/.sdk/src/cmp/swift/Config_swift.ts +17 -0
  140. package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +3 -1
  141. package/project/.sdk/src/cmp/zig/Config_zig.ts +44 -0
  142. package/project/.sdk/src/cmp/zig/TestEntity_zig.ts +9 -2
  143. package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +3 -1
  144. package/project/.sdk/tm/c/core/sdk.h +1 -0
  145. package/project/.sdk/tm/c/utility/make_options.c +5 -1
  146. package/project/.sdk/tm/clojure/src/sdk/core.clj +51 -3
  147. package/project/.sdk/tm/cpp/core/types.hpp +4 -1
  148. package/project/.sdk/tm/cpp/utility/pipeline.hpp +5 -1
  149. package/project/.sdk/tm/csharp/test/PipelineTest.cs +15 -0
  150. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +67 -1
  151. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +43 -0
  152. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +58 -2
  153. package/project/.sdk/tm/haskell/{test/TestJson.hs → src/SdkJson.hs} +8 -4
  154. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +55 -2
  155. package/project/.sdk/tm/haskell/test/Runner.hs +1 -1
  156. package/project/.sdk/tm/haskell/test/StructCorpus.hs +1 -1
  157. package/project/.sdk/tm/java/core/SdkClient.java +3 -1
  158. package/project/.sdk/tm/java/utility/MakeOptions.java +5 -1
  159. package/project/.sdk/tm/kotlin/core/SdkClient.kt +3 -1
  160. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +5 -1
  161. package/project/.sdk/tm/ocaml/Makefile +3 -2
  162. package/project/.sdk/tm/ocaml/sdk_json.ml +106 -0
  163. package/project/.sdk/tm/ocaml/sdk_runtime.ml +56 -0
  164. package/project/.sdk/tm/ocaml/test/struct_corpus.ml +4 -86
  165. package/project/.sdk/tm/perl/utility/make_options.pm +5 -1
  166. package/project/.sdk/tm/rust/utility/make_options.rs +79 -1
  167. package/project/.sdk/tm/scala/core/SdkClient.scala +3 -1
  168. package/project/.sdk/tm/scala/utility/Make.scala +5 -1
  169. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +5 -1
  170. package/project/.sdk/tm/zig/core/utility.zig +85 -1
  171. package/project/sdkgen-package.json +57 -0
  172. package/src/action/action.ts +136 -6
  173. package/src/action/check.ts +495 -0
  174. package/src/action/dispatch.ts +105 -0
  175. package/src/action/docs.ts +310 -0
  176. package/src/action/doctor.ts +692 -81
  177. package/src/action/feature.ts +109 -17
  178. package/src/action/kind.ts +375 -0
  179. package/src/action/package.ts +1173 -0
  180. package/src/action/resolve.ts +362 -0
  181. package/src/action/target.ts +290 -108
  182. package/src/cmp/Docs.ts +125 -0
  183. package/src/cmp/ExternalDocs.ts +54 -0
  184. package/src/helpers/definition.ts +82 -0
  185. package/src/helpers/featureSource.ts +83 -13
  186. package/src/helpers/junk.ts +168 -0
  187. package/src/helpers/manifest.ts +474 -0
  188. package/src/helpers/modelcheck.ts +262 -0
  189. package/src/helpers/semver.ts +211 -0
  190. package/src/helpers/shipped.ts +41 -0
  191. package/src/helpers/stdrep.ts +70 -0
  192. package/src/sdkgen.ts +294 -66
  193. package/src/types.ts +33 -3
  194. package/src/utility.ts +17 -0
@@ -1,3 +1,5 @@
1
+ import '../ProjectNameError.dart';
2
+
1
3
  import 'voxgig_struct.dart' as vs;
2
4
 
3
5
  import 'FetcherUtility.dart';
@@ -55,6 +57,11 @@ dynamic makeOptions(dynamic ctx) {
55
57
  'clean': {
56
58
  'keys': 'key,token,id',
57
59
  },
60
+ // Server-variable values for a templated base URL (OpenAPI server
61
+ // variables): {name} placeholders in "base" are substituted from this
62
+ // map at construction. Spec defaults arrive via the generated config;
63
+ // user values override them. Mirrors go's make_options optspec.
64
+ 'server': {'`\$CHILD`': ''},
58
65
  };
59
66
 
60
67
  // Dart specific: preserve the (function-valued) system.fetch across
@@ -95,6 +102,42 @@ dynamic makeOptions(dynamic ctx) {
95
102
 
96
103
  opts = vs.validate(opts, optspec);
97
104
 
105
+ // Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
106
+ // Every placeholder must resolve to a non-empty value: from options.server
107
+ // (user), else the Config default. A placeholder that resolves to '' is a
108
+ // construction ERROR in live mode — the URL cannot work — but in test mode
109
+ // substitutes the deterministic value `test-<name>` so offline tests need no
110
+ // configuration. The SDK constructor has no error return, so a missing
111
+ // required variable THROWS: construction-time misconfiguration.
112
+ final baseVal = vs.getprop(opts, 'base');
113
+ if (baseVal is String && baseVal.contains('{')) {
114
+ final testmode = true == vs.getpath(opts, 'test.active') ||
115
+ true == vs.getpath(opts, 'feature.test.active');
116
+ final server = vs.getprop(opts, 'server') ?? {};
117
+ final sdkname = (vs.getpath(config, 'main.name') ?? 'SDK').toString();
118
+
119
+ opts['base'] = baseVal.replaceAllMapped(
120
+ RegExp(r'\{([A-Za-z0-9_]+)\}'),
121
+ (m) {
122
+ final name = m.group(1) as String;
123
+ final raw = vs.getprop(server, name);
124
+ final val = raw is String ? raw : '';
125
+ if ('' == val) {
126
+ if (testmode) {
127
+ return 'test-' + name;
128
+ }
129
+ throw ProjectNameError(
130
+ 'server_var_required',
131
+ "$sdkname: the server variable '$name' is required: the API base "
132
+ "URL is '$baseVal' — pass { 'server': { '$name': '...' } } in the "
133
+ 'SDK options',
134
+ );
135
+ }
136
+ return val;
137
+ },
138
+ );
139
+ }
140
+
98
141
  final sys = vs.getprop(opts, 'system');
99
142
  if (sys is Map) {
100
143
  sys['fetch'] = sysFetch;
@@ -263,15 +263,71 @@ defmodule ProjectName.Utility do
263
263
  "utility" => %{},
264
264
  "system" => %{},
265
265
  "test" => %{"active" => false, "entity" => %{"`$OPEN`" => true}},
266
- "clean" => %{"keys" => "key,token,id"}
266
+ "clean" => %{"keys" => "key,token,id"},
267
+ # Server-variable values for a templated base URL (OpenAPI server
268
+ # variables): {name} placeholders in "base" are substituted from this
269
+ # map at construction. Spec defaults arrive via the generated config;
270
+ # user values override them. Mirrors go's make_options optspec - elixir
271
+ # was the only target validating options that did not accept it, so a
272
+ # spec with a templated server URL failed validation outright.
273
+ "server" => %{"`$CHILD`" => ""}
267
274
  })
268
275
 
269
276
  sys_fetch = S.getpath(opts0, "system.fetch")
270
277
 
271
- merged = S.merge(S.jt([S.jm([]), cfgopts, opts0]))
278
+ # CLONE the config side: `config` is a process-wide singleton
279
+ # (ProjectName.Config.shared_config) and merge uses its nested nodes as
280
+ # merge TARGETS, so without this one client's options (headers, server,
281
+ # ...) are written into the shared config and inherited by every client
282
+ # constructed afterwards.
283
+ merged = S.merge(S.jt([S.jm([]), S.clone(cfgopts), opts0]))
272
284
  validated = S.validate(merged, optspec)
273
285
  opts = if S.ismap(validated), do: validated, else: S.jm([])
274
286
 
287
+ # Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
288
+ # Every placeholder must resolve to a non-empty value: from options.server
289
+ # (user), else the Config default. A placeholder that resolves to "" is a
290
+ # construction ERROR in live mode - the URL cannot work - but in test mode
291
+ # substitutes the deterministic value "test-<name>" so offline tests need
292
+ # no configuration. The SDK constructor has no error return, so a missing
293
+ # required variable RAISES: construction-time misconfiguration.
294
+ base = S.getprop(opts, "base")
295
+
296
+ if is_binary(base) and String.contains?(base, "{") do
297
+ testmode =
298
+ true == S.getpath(opts, "test.active") or
299
+ true == S.getpath(opts, "feature.test.active")
300
+
301
+ server = H.or_(S.getprop(opts, "server"), S.jm([]))
302
+ mn = S.getpath(config, "main.name")
303
+ sdkname = if is_binary(mn) and mn != "", do: mn, else: "SDK"
304
+
305
+ resolved =
306
+ Regex.replace(~r/\{([A-Za-z0-9_]+)\}/, base, fn _match, name ->
307
+ val = S.getprop(server, name)
308
+ val = if is_binary(val), do: val, else: ""
309
+
310
+ cond do
311
+ val != "" ->
312
+ val
313
+
314
+ testmode ->
315
+ "test-" <> name
316
+
317
+ true ->
318
+ raise ProjectName.Error,
319
+ code: "server_var_required",
320
+ sdk: "ProjectName",
321
+ msg:
322
+ "#{sdkname}: the server variable '#{name}' is required: the API " <>
323
+ "base URL is '#{base}' - pass %{\"server\" => %{\"#{name}\" => " <>
324
+ "\"...\"}} in the SDK options"
325
+ end
326
+ end)
327
+
328
+ S.setprop(opts, "base", resolved)
329
+ end
330
+
275
331
  if sys_fetch != nil do
276
332
  sysnode = S.getprop(opts, "system")
277
333
 
@@ -1,11 +1,15 @@
1
1
  -- Self-contained JSON reader building struct `Value` nodes directly (no
2
- -- third-party dependency beyond the `array` boot library). Shared by the
3
- -- struct corpus runner and the entity-fixture tests. Uses O(1) array indexing
4
- -- so large corpora parse in linear time.
2
+ -- third-party dependency beyond the `array` boot library). Uses O(1) array
3
+ -- indexing so large corpora parse in linear time.
4
+ --
5
+ -- Lives in src/ rather than test/ because SdkConfig needs it: above a size
6
+ -- threshold the API model is embedded as a JSON string constant and parsed
7
+ -- once at load (sdkgen rung L1) instead of being built as a CV literal. It is
8
+ -- still what the struct corpus runner and the entity-fixture tests use.
5
9
 
6
10
  {-# LANGUAGE LambdaCase #-}
7
11
 
8
- module TestJson (jsonRead) where
12
+ module SdkJson (jsonRead) where
9
13
 
10
14
  import Data.Array (Array, listArray, (!))
11
15
  import Data.Char (chr)
@@ -13,7 +13,7 @@ module SdkRuntime where
13
13
  import Control.Exception (throwIO)
14
14
  import Control.Monad (forM_, when)
15
15
  import Data.Bits ((.&.))
16
- import Data.Char (toUpper)
16
+ import Data.Char (isAlphaNum, toUpper)
17
17
  import Data.IORef
18
18
  import qualified Data.Map.Strict as Map
19
19
  import System.IO.Unsafe (unsafePerformIO)
@@ -1016,6 +1016,11 @@ optSpecValue :: IO Value
1016
1016
  optSpecValue = do
1017
1017
  auth <- jo [("prefix", VStr "")]
1018
1018
  hdrs <- jo [("`$CHILD`", VStr "`$STRING`")]
1019
+ -- OpenAPI server-variable defaults, carried by the generated config whenever
1020
+ -- the spec's server URL is templated. Accepted here so validation does not
1021
+ -- reject the SDK's own config; the {name} substitution into base is a
1022
+ -- separate concern.
1023
+ srv <- jo [("`$CHILD`", VStr "")]
1019
1024
  allow <- jo [("method", VStr "GET,PUT,POST,PATCH,DELETE,OPTIONS"), ("op", VStr "create,update,load,list,remove,command,direct,graphql")]
1020
1025
  entChild <- do a <- emptyMap; jo [("`$OPEN`", VBool True), ("active", VBool False), ("alias", a)]
1021
1026
  ent <- jo [("`$CHILD`", entChild)]
@@ -1027,7 +1032,7 @@ optSpecValue = do
1027
1032
  test <- jo [("active", VBool False), ("entity", testEnt)]
1028
1033
  clean <- jo [("keys", VStr "key,token,id")]
1029
1034
  jo [ ("apikey", VStr ""), ("base", VStr "http://localhost:8000"), ("prefix", VStr ""), ("suffix", VStr "")
1030
- , ("auth", auth), ("headers", hdrs), ("allow", allow), ("entity", ent), ("feature", feat)
1035
+ , ("auth", auth), ("headers", hdrs), ("server", srv), ("allow", allow), ("entity", ent), ("feature", feat)
1031
1036
  , ("utility", utilm), ("system", sysm), ("test", test), ("clean", clean) ]
1032
1037
 
1033
1038
  makeOptionsUtil :: Context -> IO Value
@@ -1079,6 +1084,54 @@ makeOptionsUtil ctx = do
1079
1084
  merged <- merge mlist
1080
1085
  validated <- validate INone merged optspec
1081
1086
  opts <- case validated of VMap _ -> pure validated; _ -> emptyMap
1087
+ -- Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
1088
+ -- Every placeholder must resolve to a non-empty value: from options.server
1089
+ -- (user), else the Config default. A placeholder that resolves to "" is a
1090
+ -- construction ERROR in live mode - the URL cannot work - but in test mode
1091
+ -- substitutes the deterministic value "test-<name>" so offline tests need no
1092
+ -- configuration. The SDK constructor has no error return, so a missing
1093
+ -- required variable THROWS: construction-time misconfiguration.
1094
+ baseV <- getp opts "base"
1095
+ case baseV of
1096
+ VStr base | '{' `elem` base -> do
1097
+ taV <- getpathS opts "test.active"
1098
+ faV <- getpathS opts "feature.test.active"
1099
+ -- Value has no Eq instance: match the constructor.
1100
+ let isTrue v = case v of VBool b -> b; _ -> False
1101
+ testmode = isTrue taV || isTrue faV
1102
+ server <- getp opts "server"
1103
+ mnV <- getpathS config "main.name"
1104
+ let sdkname = case mnV of VStr s | not (null s) -> s; _ -> "SDK"
1105
+
1106
+ -- Scanned by hand: a placeholder is `{` followed by [A-Za-z0-9_]+ and
1107
+ -- `}`; anything else is literal text, so a stray brace is left alone.
1108
+ let resolve [] = pure []
1109
+ resolve ('{' : rest) =
1110
+ let (name, tail') = span (\c -> isAlphaNum c || '_' == c) rest
1111
+ in case tail' of
1112
+ ('}' : more) | not (null name) -> do
1113
+ valV <- case server of VMap _ -> getp server name; _ -> pure VNoval
1114
+ let val = case valV of VStr s -> s; _ -> ""
1115
+ sub <- if not (null val)
1116
+ then pure val
1117
+ else if testmode
1118
+ then pure ("test-" ++ name)
1119
+ else do
1120
+ e <- jo [ ("code", VStr "server_var_required")
1121
+ , ("msg", VStr (sdkname ++ ": the server variable '" ++
1122
+ name ++ "' is required: the API base URL is '" ++
1123
+ base ++ "' - pass server = { \"" ++ name ++
1124
+ "\": \"...\" } in the SDK options"))
1125
+ , ("sdk", VStr "ProjectName") ]
1126
+ throwIO (SdkException e)
1127
+ more' <- resolve more
1128
+ pure (sub ++ more')
1129
+ _ -> do r <- resolve rest; pure ('{' : r)
1130
+ resolve (c : rest) = do r <- resolve rest; pure (c : r)
1131
+ resolved <- resolve base
1132
+ setp opts "base" (VStr resolved)
1133
+ _ -> pure ()
1134
+
1082
1135
  when (not (isNoval sysFetch)) $ do
1083
1136
  sys <- getp opts "system"
1084
1137
  case sys of
@@ -10,7 +10,7 @@ import Data.IORef
10
10
  import System.Exit (exitFailure)
11
11
 
12
12
  import Testutil
13
- import TestJson (jsonRead)
13
+ import SdkJson (jsonRead)
14
14
  import StructCorpus (runStructCorpus)
15
15
  import qualified TPipeline
16
16
  import qualified TFeature
@@ -14,7 +14,7 @@ import Data.List (intercalate, isPrefixOf)
14
14
 
15
15
  import VoxgigStruct
16
16
  import qualified Vregex
17
- import TestJson (jsonRead)
17
+ import SdkJson (jsonRead)
18
18
 
19
19
  nullmark, undefmark, existsmark :: String
20
20
  nullmark = "__NULL__"
@@ -27,7 +27,9 @@ public abstract class SdkClient {
27
27
  protected SdkClient(Map<String, Object> options) {
28
28
  this.utility = new Utility();
29
29
 
30
- Map<String, Object> config = Config.makeConfig();
30
+ // The process-wide config (sdkgen rung L2): read-only on the request path,
31
+ // so every client shares one rather than rebuilding it.
32
+ Map<String, Object> config = Config.sharedConfig();
31
33
 
32
34
  Map<String, Object> ctxmap = new LinkedHashMap<>();
33
35
  ctxmap.put("client", this);
@@ -93,7 +93,11 @@ final class MakeOptions {
93
93
 
94
94
  List<Object> mergeList = new ArrayList<>();
95
95
  mergeList.add(new LinkedHashMap<String, Object>());
96
- mergeList.add(cfgopts);
96
+ // CLONE the config side: `config` is a process-wide singleton
97
+ // (Config.sharedConfig) and merge uses its nested maps as merge TARGETS, so
98
+ // without this one client's options (headers, server, ...) are written into
99
+ // the shared config and inherited by every client constructed afterwards.
100
+ mergeList.add(Struct.clone(cfgopts));
97
101
  mergeList.add(opts);
98
102
  Object merged = Struct.merge(mergeList);
99
103
 
@@ -21,7 +21,9 @@ abstract class SdkClient(sdkopts: MutableMap<String, Any?>?) {
21
21
  protected val rootctx: Context
22
22
 
23
23
  init {
24
- val config = Config.makeConfig()
24
+ // The process-wide config (sdkgen rung L2): read-only on the request path,
25
+ // so every client shares one rather than rebuilding it.
26
+ val config = Config.sharedConfig()
25
27
 
26
28
  val ctxmap = linkedMapOf<String, Any?>()
27
29
  ctxmap["client"] = this
@@ -84,7 +84,11 @@ fun makeOptions(ctx: Context): MutableMap<String, Any?> {
84
84
 
85
85
  val mergeList = mutableListOf<Any?>()
86
86
  mergeList.add(linkedMapOf<String, Any?>())
87
- mergeList.add(cfgopts)
87
+ // CLONE the config side: `config` is a process-wide singleton
88
+ // (Config.sharedConfig) and merge uses its nested maps as merge TARGETS, so
89
+ // without this one client's options (headers, server, ...) are written into
90
+ // the shared config and inherited by every client constructed afterwards.
91
+ mergeList.add(Struct.clone(cfgopts))
88
92
  mergeList.add(opts)
89
93
  val merged = Struct.merge(mergeList)
90
94
 
@@ -14,7 +14,7 @@ INC = -I utility -I . -I test
14
14
  # The shared JSON corpus lives in the sibling .sdk workspace.
15
15
  CORPUS = ../.sdk/test/test.json
16
16
 
17
- RUNTIME = utility/vregex.ml utility/voxgig_struct.ml \
17
+ RUNTIME = utility/vregex.ml utility/voxgig_struct.ml sdk_json.ml \
18
18
  sdk_types.ml sdk_helpers.ml sdk_runtime.ml sdk_features.ml
19
19
 
20
20
  ENTITIES = $(sort $(wildcard sdk_entity_*.ml))
@@ -33,7 +33,8 @@ SDK_TEST = $(RUNTIME) sdk_config.ml $(ENTITIES) sdk_client.ml sdk_error.ml \
33
33
  test/t_pipeline.ml test/t_feature.ml test/custom_utility.ml \
34
34
  $(ENTITY_TESTS) $(DOC_TESTS) test/t_main.ml
35
35
 
36
- CORPUS_SRC = utility/vregex.ml utility/voxgig_struct.ml test/struct_corpus.ml
36
+ CORPUS_SRC = utility/vregex.ml utility/voxgig_struct.ml sdk_json.ml \
37
+ test/struct_corpus.ml
37
38
 
38
39
  .PHONY: test test-sdk test-corpus build lint clean
39
40
 
@@ -0,0 +1,106 @@
1
+ (* JSON text -> voxgig struct value.
2
+ *
3
+ * A dependency-free reader, so the SDK can parse JSON with no opam packages -
4
+ * the same position every other target is in (elixir's ProjectName.Json,
5
+ * clojure's sdk.core/json-parse, the json_parse in c/rust/zig).
6
+ *
7
+ * It lives in the RUNTIME rather than the test tree because the generated
8
+ * config uses it: above a size threshold the API model is emitted as a JSON
9
+ * string constant and parsed here (sdkgen rung L1) instead of as a literal the
10
+ * compiler has to walk. `test/struct_corpus.ml` uses this same function rather
11
+ * than its own copy, so the corpus and the SDK cannot disagree about what a
12
+ * given JSON text means.
13
+ *
14
+ * Numbers are read as Num (float), which is exactly what formatOcamlValue
15
+ * emits for the literal form - so the two representations cannot disagree
16
+ * about the type of a whole number.
17
+ *)
18
+
19
+ open Voxgig_struct
20
+
21
+ (* ---------------- JSON reader -> value ---------------- *)
22
+
23
+ let json_read (s : string) : value =
24
+ let n = String.length s in
25
+ let pos = ref 0 in
26
+ let peek () = if !pos < n then Some s.[!pos] else None in
27
+ let adv () = incr pos in
28
+ let skip_ws () =
29
+ while !pos < n && (match s.[!pos] with ' ' | '\t' | '\n' | '\r' -> true | _ -> false) do incr pos done
30
+ in
31
+ let rec pval () =
32
+ skip_ws ();
33
+ match peek () with
34
+ | Some '{' -> pobj ()
35
+ | Some '[' -> parr ()
36
+ | Some '"' -> Str (pstr ())
37
+ | Some 't' -> pos := !pos + 4; Bool true
38
+ | Some 'f' -> pos := !pos + 5; Bool false
39
+ | Some 'n' -> pos := !pos + 4; Null
40
+ | _ -> pnum ()
41
+ and pobj () =
42
+ adv (); skip_ws ();
43
+ if peek () = Some '}' then (adv (); empty_map ())
44
+ else begin
45
+ let m = empty_map () in
46
+ let rec loop () =
47
+ skip_ws ();
48
+ let k = pstr () in
49
+ skip_ws (); adv (); (* : *)
50
+ let v = pval () in
51
+ ignore (setprop m (Str k) v);
52
+ skip_ws ();
53
+ let c = (match peek () with Some c -> adv (); c | None -> '}') in
54
+ if c = ',' then loop () else m
55
+ in loop ()
56
+ end
57
+ and parr () =
58
+ adv (); skip_ws ();
59
+ if peek () = Some ']' then (adv (); empty_list ())
60
+ else begin
61
+ let acc = ref [] in
62
+ let rec loop () =
63
+ let v = pval () in
64
+ acc := v :: !acc;
65
+ skip_ws ();
66
+ let c = (match peek () with Some c -> adv (); c | None -> ']') in
67
+ if c = ',' then loop () else lst (List.rev !acc)
68
+ in loop ()
69
+ end
70
+ and pstr () =
71
+ adv ();
72
+ let b = Buffer.create 16 in
73
+ let rec loop () =
74
+ let c = s.[!pos] in adv ();
75
+ if c = '"' then Buffer.contents b
76
+ else if c = '\\' then begin
77
+ let e = s.[!pos] in adv ();
78
+ (match e with
79
+ | '"' -> Buffer.add_char b '"' | '\\' -> Buffer.add_char b '\\'
80
+ | '/' -> Buffer.add_char b '/' | 'n' -> Buffer.add_char b '\n'
81
+ | 't' -> Buffer.add_char b '\t' | 'r' -> Buffer.add_char b '\r'
82
+ | 'b' -> Buffer.add_char b '\b' | 'f' -> Buffer.add_char b '\012'
83
+ | 'u' ->
84
+ let hex = String.sub s !pos 4 in pos := !pos + 4;
85
+ let code = int_of_string ("0x" ^ hex) in
86
+ if code < 128 then Buffer.add_char b (Char.chr code)
87
+ else if code < 2048 then begin
88
+ Buffer.add_char b (Char.chr (0xC0 lor (code lsr 6)));
89
+ Buffer.add_char b (Char.chr (0x80 lor (code land 0x3F)))
90
+ end else begin
91
+ Buffer.add_char b (Char.chr (0xE0 lor (code lsr 12)));
92
+ Buffer.add_char b (Char.chr (0x80 lor ((code lsr 6) land 0x3F)));
93
+ Buffer.add_char b (Char.chr (0x80 lor (code land 0x3F)))
94
+ end
95
+ | c -> Buffer.add_char b c);
96
+ loop ()
97
+ end else (Buffer.add_char b c; loop ())
98
+ in loop ()
99
+ and pnum () =
100
+ let start = !pos in
101
+ while !pos < n && (match s.[!pos] with
102
+ | '0'..'9' | '-' | '+' | '.' | 'e' | 'E' -> true | _ -> false) do incr pos done;
103
+ let tok = String.sub s start (!pos - start) in
104
+ Num (float_of_string tok)
105
+ in
106
+ pval ()
@@ -905,6 +905,11 @@ let opt_spec_value () : value =
905
905
  ("system", empty_map ());
906
906
  ("test", jo [("active", Bool false); ("entity", jo [("`$OPEN`", Bool true)])]);
907
907
  ("clean", jo [("keys", Str "key,token,id")]);
908
+ (* Server-variable values for a templated base URL (OpenAPI server
909
+ * variables): {name} placeholders in "base" are substituted from this
910
+ * map at construction. Spec defaults arrive via the generated config;
911
+ * user values override them. Mirrors go's make_options optspec. *)
912
+ ("server", jo [("`$CHILD`", Str "")]);
908
913
  ]
909
914
 
910
915
  let make_options_util (ctx : ctx) : value =
@@ -946,6 +951,57 @@ let make_options_util (ctx : ctx) : value =
946
951
  let merged = merge (ja [empty_map (); cfgopts; opts]) in
947
952
  let validated = validate merged optspec in
948
953
  let opts = match validated with Map _ as m -> m | _ -> empty_map () in
954
+ (* Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
955
+ Every placeholder must resolve to a non-empty value: from options.server
956
+ (user), else the Config default. A placeholder that resolves to "" is a
957
+ construction ERROR in live mode - the URL cannot work - but in test mode
958
+ substitutes the deterministic value "test-<name>" so offline tests need no
959
+ configuration. The SDK constructor has no error return, so a missing
960
+ required variable RAISES: construction-time misconfiguration.
961
+
962
+ Scanned by hand: a placeholder is `{` followed by [A-Za-z0-9_]+ and `}`;
963
+ anything else is literal text, so a stray brace is left alone. *)
964
+ (match getp opts "base" with
965
+ | Str base when String.contains base '{' ->
966
+ let is_true v = match v with Bool b -> b | _ -> false in
967
+ let testmode =
968
+ is_true (getpath_s opts "test.active")
969
+ || is_true (getpath_s opts "feature.test.active") in
970
+ let server = match getp opts "server" with Map _ as m -> m | _ -> empty_map () in
971
+ let sdkname =
972
+ match getpath_s config "main.name" with Str s when s <> "" -> s | _ -> "SDK" in
973
+ let namechar c =
974
+ (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
975
+ || (c >= '0' && c <= '9') || c = '_' in
976
+ let buf = Buffer.create (String.length base) in
977
+ let n = String.length base in
978
+ let i = ref 0 in
979
+ while !i < n do
980
+ if base.[!i] <> '{' then begin
981
+ Buffer.add_char buf base.[!i];
982
+ incr i
983
+ end else begin
984
+ let j = ref (!i + 1) in
985
+ while !j < n && namechar base.[!j] do incr j done;
986
+ if !j >= n || base.[!j] <> '}' || !j = !i + 1 then begin
987
+ Buffer.add_char buf base.[!i];
988
+ incr i
989
+ end else begin
990
+ let name = String.sub base (!i + 1) (!j - !i - 1) in
991
+ let value = match getp server name with Str s -> s | _ -> "" in
992
+ if value <> "" then Buffer.add_string buf value
993
+ else if testmode then Buffer.add_string buf ("test-" ^ name)
994
+ else
995
+ raise (Sdk_error_exc (ctx_make_error ctx "server_var_required"
996
+ (sdkname ^ ": the server variable '" ^ name ^ "' is required: the API "
997
+ ^ "base URL is '" ^ base ^ "' - pass ~server:[(\"" ^ name
998
+ ^ "\", \"...\")] in the SDK options")));
999
+ i := !j + 1
1000
+ end
1001
+ end
1002
+ done;
1003
+ setp opts "base" (Str (Buffer.contents buf))
1004
+ | _ -> ());
949
1005
  (if not (is_noval sys_fetch) then
950
1006
  match getp opts "system" with
951
1007
  | Map _ as sys -> setp sys "fetch" sys_fetch
@@ -8,92 +8,10 @@ let nullmark = "__NULL__"
8
8
  let undefmark = "__UNDEF__"
9
9
  let existsmark = "__EXISTS__"
10
10
 
11
- (* ---------------- JSON reader -> value ---------------- *)
12
-
13
- let json_read (s : string) : value =
14
- let n = String.length s in
15
- let pos = ref 0 in
16
- let peek () = if !pos < n then Some s.[!pos] else None in
17
- let adv () = incr pos in
18
- let skip_ws () =
19
- while !pos < n && (match s.[!pos] with ' ' | '\t' | '\n' | '\r' -> true | _ -> false) do incr pos done
20
- in
21
- let rec pval () =
22
- skip_ws ();
23
- match peek () with
24
- | Some '{' -> pobj ()
25
- | Some '[' -> parr ()
26
- | Some '"' -> Str (pstr ())
27
- | Some 't' -> pos := !pos + 4; Bool true
28
- | Some 'f' -> pos := !pos + 5; Bool false
29
- | Some 'n' -> pos := !pos + 4; Null
30
- | _ -> pnum ()
31
- and pobj () =
32
- adv (); skip_ws ();
33
- if peek () = Some '}' then (adv (); empty_map ())
34
- else begin
35
- let m = empty_map () in
36
- let rec loop () =
37
- skip_ws ();
38
- let k = pstr () in
39
- skip_ws (); adv (); (* : *)
40
- let v = pval () in
41
- ignore (setprop m (Str k) v);
42
- skip_ws ();
43
- let c = (match peek () with Some c -> adv (); c | None -> '}') in
44
- if c = ',' then loop () else m
45
- in loop ()
46
- end
47
- and parr () =
48
- adv (); skip_ws ();
49
- if peek () = Some ']' then (adv (); empty_list ())
50
- else begin
51
- let acc = ref [] in
52
- let rec loop () =
53
- let v = pval () in
54
- acc := v :: !acc;
55
- skip_ws ();
56
- let c = (match peek () with Some c -> adv (); c | None -> ']') in
57
- if c = ',' then loop () else lst (List.rev !acc)
58
- in loop ()
59
- end
60
- and pstr () =
61
- adv ();
62
- let b = Buffer.create 16 in
63
- let rec loop () =
64
- let c = s.[!pos] in adv ();
65
- if c = '"' then Buffer.contents b
66
- else if c = '\\' then begin
67
- let e = s.[!pos] in adv ();
68
- (match e with
69
- | '"' -> Buffer.add_char b '"' | '\\' -> Buffer.add_char b '\\'
70
- | '/' -> Buffer.add_char b '/' | 'n' -> Buffer.add_char b '\n'
71
- | 't' -> Buffer.add_char b '\t' | 'r' -> Buffer.add_char b '\r'
72
- | 'b' -> Buffer.add_char b '\b' | 'f' -> Buffer.add_char b '\012'
73
- | 'u' ->
74
- let hex = String.sub s !pos 4 in pos := !pos + 4;
75
- let code = int_of_string ("0x" ^ hex) in
76
- if code < 128 then Buffer.add_char b (Char.chr code)
77
- else if code < 2048 then begin
78
- Buffer.add_char b (Char.chr (0xC0 lor (code lsr 6)));
79
- Buffer.add_char b (Char.chr (0x80 lor (code land 0x3F)))
80
- end else begin
81
- Buffer.add_char b (Char.chr (0xE0 lor (code lsr 12)));
82
- Buffer.add_char b (Char.chr (0x80 lor ((code lsr 6) land 0x3F)));
83
- Buffer.add_char b (Char.chr (0x80 lor (code land 0x3F)))
84
- end
85
- | c -> Buffer.add_char b c);
86
- loop ()
87
- end else (Buffer.add_char b c; loop ())
88
- in loop ()
89
- and pnum () =
90
- let start = !pos in
91
- while !pos < n && (match s.[!pos] with
92
- | '0'..'9' | '-' | '+' | '.' | 'e' | 'E' -> true | _ -> false) do incr pos done;
93
- let tok = String.sub s start (!pos - start) in
94
- Num (float_of_string tok)
95
- in
96
- pval ()
11
+ (* The JSON reader now lives in the runtime (sdk_json.ml) so the SDK's
12
+ * generated config can use it; the corpus runs against that same function
13
+ * rather than a second copy that could drift from it. *)
14
+ let json_read = Sdk_json.json_read
97
15
 
98
16
  (* ---------------- fixJSON / equality ---------------- *)
99
17
 
@@ -82,7 +82,11 @@ $REGISTRY{make_options} = sub {
82
82
 
83
83
  my $sys_fetch = ProjectNameHelpers::gpath($opts, 'system.fetch');
84
84
 
85
- my $merged = Voxgig::Struct::merge([{}, $cfgopts, $opts]);
85
+ # CLONE the config side: `config` is a process-wide singleton
86
+ # (ProjectNameConfig::shared_config) and merge uses its nested hashes as merge
87
+ # TARGETS, so without this one client's options (headers, server, ...) are
88
+ # written into the shared config and inherited by every client after it.
89
+ my $merged = Voxgig::Struct::merge([{}, Voxgig::Struct::clone($cfgopts), $opts]);
86
90
  my $validated = Voxgig::Struct::validate($merged, $optspec);
87
91
  $opts = Voxgig::Struct::ismap($validated) ? $validated : {};
88
92