@voxgig/sdkgen 4.19.0 → 4.20.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 (112) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/AgentGuideContent.js +1 -1
  3. package/dist/sdkgen.d.ts +2 -2
  4. package/dist/sdkgen.js +6 -3
  5. package/dist/sdkgen.js.map +1 -1
  6. package/dist/testkit.js +2 -2
  7. package/dist/testkit.js.map +1 -1
  8. package/dist/tsconfig.tsbuildinfo +1 -1
  9. package/dist/utility.d.ts +4 -1
  10. package/dist/utility.js +60 -0
  11. package/dist/utility.js.map +1 -1
  12. package/model/sdkgen.aon +10 -0
  13. package/package.json +1 -1
  14. package/project/.sdk/model/feature/feature-index.aon +20 -20
  15. package/project/.sdk/src/cmp/c/Config_c.ts +15 -0
  16. package/project/.sdk/src/cmp/c/Main_c.ts +15 -0
  17. package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +371 -0
  18. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +47 -1
  19. package/project/.sdk/src/cmp/clojure/Main_clojure.ts +11 -1
  20. package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +374 -0
  21. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +40 -0
  22. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +17 -0
  23. package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +441 -0
  24. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +42 -1
  25. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +18 -0
  26. package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +392 -0
  27. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +36 -1
  28. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +15 -0
  29. package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +418 -0
  30. package/project/.sdk/src/cmp/go/Config_go.ts +12 -1
  31. package/project/.sdk/src/cmp/go/Main_go.ts +14 -0
  32. package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +322 -0
  33. package/project/.sdk/src/cmp/java/Config_java.ts +20 -1
  34. package/project/.sdk/src/cmp/java/Main_java.ts +14 -0
  35. package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +298 -0
  36. package/project/.sdk/src/cmp/js/Config_js.ts +12 -1
  37. package/project/.sdk/src/cmp/js/Main_js.ts +6 -0
  38. package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +244 -0
  39. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +23 -1
  40. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +20 -0
  41. package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +440 -0
  42. package/project/.sdk/src/cmp/lua/Config_lua.ts +15 -1
  43. package/project/.sdk/src/cmp/lua/Main_lua.ts +11 -0
  44. package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +364 -0
  45. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +31 -1
  46. package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +19 -0
  47. package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +454 -0
  48. package/project/.sdk/src/cmp/perl/Config_perl.ts +30 -0
  49. package/project/.sdk/src/cmp/perl/Main_perl.ts +14 -0
  50. package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +387 -0
  51. package/project/.sdk/src/cmp/php/Config_php.ts +12 -1
  52. package/project/.sdk/src/cmp/php/Main_php.ts +7 -0
  53. package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +325 -0
  54. package/project/.sdk/src/cmp/py/Config_py.ts +12 -1
  55. package/project/.sdk/src/cmp/py/Main_py.ts +12 -0
  56. package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +389 -0
  57. package/project/.sdk/src/cmp/rb/Config_rb.ts +18 -1
  58. package/project/.sdk/src/cmp/rb/Main_rb.ts +10 -0
  59. package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +305 -0
  60. package/project/.sdk/src/cmp/rust/Config_rust.ts +41 -1
  61. package/project/.sdk/src/cmp/rust/Main_rust.ts +16 -0
  62. package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +410 -0
  63. package/project/.sdk/src/cmp/scala/Config_scala.ts +21 -1
  64. package/project/.sdk/src/cmp/scala/Main_scala.ts +14 -0
  65. package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +415 -0
  66. package/project/.sdk/src/cmp/swift/Config_swift.ts +37 -1
  67. package/project/.sdk/src/cmp/swift/Main_swift.ts +10 -0
  68. package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +439 -0
  69. package/project/.sdk/src/cmp/ts/Config_ts.ts +12 -1
  70. package/project/.sdk/src/cmp/ts/Main_ts.ts +5 -0
  71. package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +237 -0
  72. package/project/.sdk/src/cmp/zig/Config_zig.ts +33 -1
  73. package/project/.sdk/src/cmp/zig/Main_zig.ts +16 -0
  74. package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +545 -0
  75. package/project/.sdk/tm/clojure/src/sdk/core.clj +31 -20
  76. package/project/.sdk/tm/cpp/utility/pipeline.hpp +36 -40
  77. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +11 -0
  78. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -32
  79. package/project/.sdk/tm/java/utility/MakeOptions.java +13 -1
  80. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +14 -1
  81. package/project/.sdk/tm/kotlin/utility/Prepare.kt +13 -46
  82. package/project/.sdk/tm/ocaml/Makefile +11 -1
  83. package/project/.sdk/tm/ocaml/sdk_helpers.ml +15 -0
  84. package/project/.sdk/tm/ocaml/sdk_runtime.ml +31 -25
  85. package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +125 -12
  86. package/project/.sdk/tm/ocaml/test/t_pipeline.ml +64 -6
  87. package/project/.sdk/tm/perl/utility/make_options.pm +10 -1
  88. package/project/.sdk/tm/php/utility/MakeOptions.php +8 -1
  89. package/project/.sdk/tm/rust/utility/make_options.rs +13 -1
  90. package/project/.sdk/tm/scala/utility/Make.scala +14 -1
  91. package/project/.sdk/tm/scala/utility/Prepare.scala +16 -40
  92. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +30 -3
  93. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +21 -39
  94. package/project/.sdk/tm/zig/core/utility.zig +37 -45
  95. package/project/.sdk/tm/zig/test/pipeline_test.zig +34 -0
  96. package/project/sdkgen-package.json +1 -1
  97. package/src/cmp/AgentGuideContent.ts +1 -1
  98. package/src/sdkgen.ts +4 -1
  99. package/src/testkit.ts +2 -2
  100. package/src/utility.ts +75 -0
  101. package/project/.sdk/tm/c/utility/prepare_auth.c +0 -65
  102. package/project/.sdk/tm/csharp/utility/PrepareAuth.cs +0 -54
  103. package/project/.sdk/tm/go/utility/prepare_auth.go +0 -59
  104. package/project/.sdk/tm/java/utility/PrepareAuth.java +0 -66
  105. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +0 -49
  106. package/project/.sdk/tm/lua/utility/prepare_auth.lua +0 -52
  107. package/project/.sdk/tm/perl/utility/prepare_auth.pm +0 -56
  108. package/project/.sdk/tm/php/utility/PrepareAuth.php +0 -45
  109. package/project/.sdk/tm/py/pkg/utility/prepare_auth.py +0 -46
  110. package/project/.sdk/tm/rb/utility/prepare_auth.rb +0 -35
  111. package/project/.sdk/tm/rust/utility/prepare_auth.rs +0 -65
  112. package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +0 -78
@@ -308,13 +308,71 @@ let () =
308
308
  ignore (u.u_fetcher ctx "http://h/a" (jo [("method", Str "GET"); ("headers", empty_map ())]));
309
309
  check "order" (!order = ["second"; "first"; "server"]));
310
310
 
311
+ (* prepare_auth IS GENERATED (Sdk_prepare_auth), so these four cases ask
312
+ * the generated module where this SDK puts its credential instead of
313
+ * assuming a header. They used to read `spec.headers["authorization"]`
314
+ * outright, which made them pass only for a header scheme and FAIL on
315
+ * every apiKey-in-query SDK - joplin's `?token=` - i.e. on exactly the
316
+ * SDKs the model-driven placement exists to make work. The assertions
317
+ * still drive the real u_prepare_auth end to end and check the bag, the
318
+ * key and the value it actually wrote; only the expectation is read from
319
+ * the SDK's own declared contract rather than hardcoded. *)
320
+ let auth_bag (ctx : ctx) : value =
321
+ let sp = match ctx.c_spec with Some s -> s | None -> assert false in
322
+ if Sdk_prepare_auth.cred_where = "query" then sp.sp_query else sp.sp_headers in
323
+
324
+ (* A cookie rides the shared `cookie` header, so the KEY is that header and
325
+ * the VALUE is a `name=value` pair. A header and a query parameter are
326
+ * keyed by the credential name itself. *)
327
+ let auth_key =
328
+ if Sdk_prepare_auth.cred_where = "cookie" then "cookie"
329
+ else Sdk_prepare_auth.cred_name in
330
+
331
+ (* A header carries the auth.prefix, space-joined when it is non-empty. A
332
+ * query parameter and a cookie do NOT: `?token=Bearer%20abc` is not a
333
+ * thing any API reads. *)
334
+ let auth_placed (prefix : string) (cred : string) : string =
335
+ if Sdk_prepare_auth.cred_where = "query" then cred
336
+ else if Sdk_prepare_auth.cred_where = "cookie" then
337
+ Sdk_prepare_auth.cred_name ^ "=" ^ cred
338
+ else if prefix <> "" then prefix ^ " " ^ cred
339
+ else cred in
340
+
341
+ (* An SDK built with main.kit.config.auth.active: false places nothing at
342
+ * all, by design, so the two placement cases assert the absence instead. *)
343
+ let check_placed (label : string) (ctx : ctx) (prefix : string) (cred : string) =
344
+ if Sdk_prepare_auth.cred_active then
345
+ check_vstr label (getp (auth_bag ctx) auth_key) (auth_placed prefix cred)
346
+ else
347
+ check (label ^ ": auth off, nothing placed")
348
+ (is_noval (getp (auth_bag ctx) auth_key)) in
349
+
350
+ (* The stale credential a clearing case must remove, written the way
351
+ * prepare_auth would have written it - a bare value for a header or a
352
+ * query parameter, a `name=value` pair for a cookie, since cookie_set
353
+ * splices OUR pair out and leaves every other cookie alone. *)
354
+ let stale_spec () : spec =
355
+ let placed = Str (auth_placed "" "stale") in
356
+ if Sdk_prepare_auth.cred_where = "query" then
357
+ new_spec (jo [("headers", empty_map ()); ("query", jo [(auth_key, placed)])])
358
+ else
359
+ new_spec (jo [("headers", jo [(auth_key, placed)])]) in
360
+
361
+ (* With auth switched off nothing is removed either: the function is a
362
+ * no-op by design, so a stale value is expected to survive. *)
363
+ let check_cleared (label : string) (ctx : ctx) =
364
+ let got = getp (auth_bag ctx) auth_key in
365
+ if Sdk_prepare_auth.cred_active then check label (is_noval got)
366
+ else check (label ^ ": auth off, nothing touched")
367
+ (got = Str (auth_placed "" "stale")) in
368
+
311
369
  test "prepare_auth.apikey_prefix_space_joined" (fun () ->
312
370
  let cl = client () in
313
371
  let ctx = mk_ctx cl "load" in
314
372
  cl.cl_options <- jo [("apikey", Str "K"); ("auth", jo [("prefix", Str "Bearer")])];
315
373
  ctx.c_spec <- Some (new_spec (jo [("headers", empty_map ())]));
316
374
  ignore (cl.cl_utility.u_prepare_auth ctx);
317
- check_vstr "auth" (getp (match ctx.c_spec with Some s -> s.sp_headers | None -> Noval) "authorization") "Bearer K");
375
+ check_placed "auth" ctx "Bearer" "K");
318
376
 
319
377
  test "prepare_auth.raw_apikey" (fun () ->
320
378
  let cl = client () in
@@ -322,23 +380,23 @@ let () =
322
380
  cl.cl_options <- jo [("apikey", Str "K"); ("auth", jo [("prefix", Str "")])];
323
381
  ctx.c_spec <- Some (new_spec (jo [("headers", empty_map ())]));
324
382
  ignore (cl.cl_utility.u_prepare_auth ctx);
325
- check_vstr "auth" (getp (match ctx.c_spec with Some s -> s.sp_headers | None -> Noval) "authorization") "K");
383
+ check_placed "auth" ctx "" "K");
326
384
 
327
385
  test "prepare_auth.empty_apikey_drops_header" (fun () ->
328
386
  let cl = client () in
329
387
  let ctx = mk_ctx cl "load" in
330
388
  cl.cl_options <- jo [("apikey", Str ""); ("auth", jo [("prefix", Str "Bearer")])];
331
- ctx.c_spec <- Some (new_spec (jo [("headers", jo [("authorization", Str "stale")])]));
389
+ ctx.c_spec <- Some (stale_spec ());
332
390
  ignore (cl.cl_utility.u_prepare_auth ctx);
333
- check "dropped" (is_noval (getp (match ctx.c_spec with Some s -> s.sp_headers | None -> Noval) "authorization")));
391
+ check_cleared "dropped" ctx);
334
392
 
335
393
  test "prepare_auth.no_auth_block_drops_header" (fun () ->
336
394
  let cl = client () in
337
395
  let ctx = mk_ctx cl "load" in
338
396
  cl.cl_options <- jo [("apikey", Str "K")];
339
- ctx.c_spec <- Some (new_spec (jo [("headers", jo [("authorization", Str "stale")])]));
397
+ ctx.c_spec <- Some (stale_spec ());
340
398
  ignore (cl.cl_utility.u_prepare_auth ctx);
341
- check "dropped" (is_noval (getp (match ctx.c_spec with Some s -> s.sp_headers | None -> Noval) "authorization")));
399
+ check_cleared "dropped" ctx);
342
400
 
343
401
  test "result_headers.no_headers_empty_map" (fun () ->
344
402
  let cl = client () in
@@ -128,7 +128,16 @@ $REGISTRY{make_options} = sub {
128
128
  'suffix' => '',
129
129
  # `basic` and `secret`: HTTP Basic Auth needs a second credential and a
130
130
  # flag to say the pair is Basic rather than a single bearer token.
131
- 'auth' => { 'prefix' => '', 'basic' => $JF },
131
+ #
132
+ # `in` and `name`: WHERE the credential goes and UNDER WHAT NAME -
133
+ # header (the default, `authorization`), query or cookie. This shape
134
+ # is CLOSED, so a config carrying a key it does not list makes every
135
+ # request fail validation: without these two entries an
136
+ # apiKey-in-query SDK (joplin's `?token=`) could not build its
137
+ # options at all. They mirror main.kit.optspec.auth, which the
138
+ # model-driven targets read directly, and '' means "whatever the
139
+ # spec resolved", which is what prepare_auth already baked in.
140
+ 'auth' => { 'prefix' => '', 'basic' => $JF, 'in' => '', 'name' => '' },
132
141
  'headers' => { '`$CHILD`' => '`$STRING`' },
133
142
  'allow' => {
134
143
  'method' => 'GET,PUT,POST,PATCH,DELETE,OPTIONS',
@@ -153,7 +153,14 @@ class ProjectNameMakeOptions
153
153
  'base' => 'http://localhost:8000',
154
154
  'prefix' => '',
155
155
  'suffix' => '',
156
- 'auth' => ['prefix' => '', 'basic' => false],
156
+ // `in` and `name` say WHERE the credential goes and under what
157
+ // name (header | query | cookie). The generated config carries
158
+ // them whenever the spec's scheme is not the header/Authorization
159
+ // default, and validate REJECTS a key this spec does not declare,
160
+ // so an apiKey-in-query SDK cannot be constructed without them.
161
+ // '' means "take what the spec said", which is what prepare_auth
162
+ // was generated from.
163
+ 'auth' => ['prefix' => '', 'basic' => false, 'in' => '', 'name' => ''],
157
164
  'headers' => ['`$CHILD`' => '`$STRING`'],
158
165
  'allow' => [
159
166
  'method' => 'GET,PUT,POST,PATCH,DELETE,OPTIONS',
@@ -78,7 +78,19 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
78
78
  ("base", Value::str("http://localhost:8000")),
79
79
  ("prefix", Value::str("")),
80
80
  ("suffix", Value::str("")),
81
- ("auth", jo(vec![("prefix", Value::str("")), ("basic", Value::Bool(false))])),
81
+ // CLOSED - validate rejects any key it does not declare - so without
82
+ // `in` and `name` an apiKey-in-query SDK fails to construct on its
83
+ // own config with "Unexpected keys at field auth: in, name".
84
+ // '' means "take what the spec said".
85
+ (
86
+ "auth",
87
+ jo(vec![
88
+ ("prefix", Value::str("")),
89
+ ("basic", Value::Bool(false)),
90
+ ("in", Value::str("")),
91
+ ("name", Value::str("")),
92
+ ]),
93
+ ),
82
94
  ("headers", jo(vec![("`$CHILD`", Value::str("`$STRING`"))])),
83
95
  (
84
96
  "allow",
@@ -306,10 +306,23 @@ object MakeOptions {
306
306
  val optspec = Json.parse(
307
307
  "{"
308
308
  + "\"apikey\": \"\","
309
+ // The SECOND credential, for the HTTP Basic scheme
310
+ // (base64(apikey:secret)). Named by main.kit.optspec but missing
311
+ // here, and validate drops what it does not name - so a caller
312
+ // that supplied `secret` never had it reach prepareAuth.
313
+ + "\"secret\": \"\","
309
314
  + "\"base\": \"http://localhost:8000\","
310
315
  + "\"prefix\": \"\","
311
316
  + "\"suffix\": \"\","
312
- + "\"auth\": { \"prefix\": \"\" },"
317
+ // WHERE the credential goes and under what name, plus the HTTP
318
+ // Basic switch. `Struct.validate` DROPS a key this spec does not
319
+ // name, so without these three the generated Config's auth block
320
+ // was trimmed back to `prefix` on the way in and the SDK could not
321
+ // see its own scheme - which made Config_scala's `in`/`name`
322
+ // emission inert. Mirrors main.kit.optspec.auth in
323
+ // @voxgig/sdkgen/model/sdkgen.aon, which ts builds its OPTSPEC from.
324
+ + "\"auth\": { \"prefix\": \"\", \"basic\": false,"
325
+ + " \"in\": \"\", \"name\": \"\" },"
313
326
  + "\"headers\": { \"`$CHILD`\": \"`$STRING`\" },"
314
327
  + "\"allow\": {"
315
328
  + " \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\","
@@ -108,43 +108,19 @@ object PrepareBody {
108
108
  if ("data" == ctx.op.input) ctx.utility.transformRequest(ctx) else null
109
109
  }
110
110
 
111
- object PrepareAuth {
112
- val HEADER_AUTH = "authorization"
113
- val OPTION_APIKEY = "apikey"
114
- val NOT_FOUND = "__NOTFOUND__"
115
-
116
- def prepareAuth(ctx: Context): Spec = {
117
- val spec = ctx.spec
118
- if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
119
-
120
- val headers = spec.headers
121
- val options = ctx.client.optionsMap()
122
-
123
- // Public APIs that need no auth omit the options.auth block entirely.
124
- if (options.get("auth") == null) {
125
- headers.remove(HEADER_AUTH)
126
- return spec
127
- }
128
-
129
- val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
130
-
131
- var skip = false
132
- if (apikey == null) skip = true
133
- else apikey match {
134
- case s: String if NOT_FOUND == s || "" == s => skip = true
135
- case _ =>
136
- }
137
-
138
- if (skip) {
139
- headers.remove(HEADER_AUTH)
140
- } else {
141
- var authPrefix = ""
142
- Struct.getpath(options, java.util.List.of("auth", "prefix")) match { case s: String => authPrefix = s; case _ => }
143
- val apikeyVal = apikey match { case s: String => s; case _ => "" }
144
- if ("" == authPrefix) headers.put(HEADER_AUTH, apikeyVal)
145
- else headers.put(HEADER_AUTH, authPrefix + " " + apikeyVal)
146
- }
147
-
148
- spec
149
- }
150
- }
111
+ // NO `object PrepareAuth` HERE, and its absence is the point.
112
+ //
113
+ // WHERE the credential goes is a fact about the API - header, query or
114
+ // cookie, under the name the spec gives - and apidef resolves all of it
115
+ // into main.kit.info.security. This file can hold only ONE answer, so the
116
+ // object that used to sit here hardcoded `val HEADER_AUTH = "authorization"`
117
+ // and an apiKey-in-query API (joplin's `?token=`) got a header it ignores.
118
+ //
119
+ // So prepareAuth is GENERATED, into utility/PrepareAuth.scala beside this
120
+ // file, by src/cmp/scala/PrepareAuth_scala.ts - which emits the one branch
121
+ // this API actually uses. It stays `object PrepareAuth` in this same
122
+ // package, so utility/Register.scala's `u.prepareAuth = (ctx) =>
123
+ // PrepareAuth.prepareAuth(ctx)` binds it with no change: scala resolves the
124
+ // object by package, not by file name.
125
+ //
126
+ // The six objects above are placement-independent and stay templated.
@@ -4,8 +4,30 @@
4
4
  import Foundation
5
5
 
6
6
  private func buildOptSpec() -> Value {
7
- let authPrefix = VMap()
8
- authPrefix.entries["prefix"] = .string("")
7
+ // THE AUTH OPTION BLOCK, and every key the generated config can put in it.
8
+ //
9
+ // This spec is what `validate` checks the merged options against, and a key
10
+ // it does not name does not survive - so the generated Config's auth block
11
+ // was trimmed back to `prefix` on the way in and the SDK could not see its
12
+ // own scheme.
13
+ //
14
+ // prefix the credential prefix (Bearer/Basic/OAuth, or '' for a raw key)
15
+ // basic the scheme is real HTTP Basic: base64(apikey:secret), not a
16
+ // single bearer token
17
+ // in WHERE the credential goes: header (the default) | query | cookie
18
+ // name the header, query parameter or cookie name it goes in under
19
+ //
20
+ // `in` and `name` mirror main.kit.optspec.auth in
21
+ // @voxgig/sdkgen/model/sdkgen.aon, which the model-driven targets read
22
+ // directly; '' means "take what the spec said", which is what the generated
23
+ // prepareAuth was built from, so an SDK that sets neither behaves exactly as
24
+ // it did before. Without them an apiKey-in-query SDK (joplin's `?token=`)
25
+ // cannot even construct its own options.
26
+ let authSpec = VMap()
27
+ authSpec.entries["prefix"] = .string("")
28
+ authSpec.entries["basic"] = .bool(false)
29
+ authSpec.entries["in"] = .string("")
30
+ authSpec.entries["name"] = .string("")
9
31
 
10
32
  let headers = VMap()
11
33
  headers.entries["`$CHILD`"] = .string("`$STRING`")
@@ -38,10 +60,15 @@ private func buildOptSpec() -> Value {
38
60
 
39
61
  let spec = VMap()
40
62
  spec.entries["apikey"] = .string("")
63
+ // The SECOND credential, for the HTTP Basic scheme
64
+ // (base64(apikey:secret)). Named by main.kit.optspec but missing here, and
65
+ // validate does not pass what it does not name - so a caller that supplied
66
+ // `secret` never had it reach prepareAuth.
67
+ spec.entries["secret"] = .string("")
41
68
  spec.entries["base"] = .string("http://localhost:8000")
42
69
  spec.entries["prefix"] = .string("")
43
70
  spec.entries["suffix"] = .string("")
44
- spec.entries["auth"] = .map(authPrefix)
71
+ spec.entries["auth"] = .map(authSpec)
45
72
  spec.entries["headers"] = .map(headers)
46
73
  spec.entries["allow"] = .map(allow)
47
74
  spec.entries["entity"] = .map(entity)
@@ -1,5 +1,6 @@
1
1
  // ProjectName SDK utility: request preparation steps (method, path, params,
2
- // query, headers, body, auth) plus param resolution.
2
+ // query, headers, body) plus param resolution. The auth step is GENERATED
3
+ // into utility/PrepareAuth.swift - see the note below.
3
4
 
4
5
  import Foundation
5
6
 
@@ -89,44 +90,25 @@ func prepareBodyUtil(_ ctx: Context) -> Value {
89
90
  return .noval
90
91
  }
91
92
 
92
- private let headerAuth = "authorization"
93
- private let optionApikey = "apikey"
94
- private let notFound = "__NOTFOUND__"
95
-
96
- func prepareAuthUtil(_ ctx: Context) throws -> Spec {
97
- guard let spec = ctx.spec else {
98
- throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
99
- }
100
-
101
- let headers = spec.headers
102
- let options = ctx.client!.optionsMap()
103
-
104
- // Public APIs that need no auth omit the options.auth block entirely.
105
- let auth = getprop(.map(options), .string("auth"))
106
- if isNil(auth) {
107
- headers.entries.removeValue(forKey: headerAuth)
108
- return spec
109
- }
110
-
111
- let apikey = getprop(.map(options), .string(optionApikey), .string(notFound))
112
-
113
- var skip = isNil(apikey)
114
- if let apikeyStr = apikey.asString, apikeyStr == notFound || apikeyStr == "" {
115
- skip = true
116
- }
117
-
118
- if skip {
119
- headers.entries.removeValue(forKey: headerAuth)
120
- } else {
121
- var authPrefix = ""
122
- if let ap = gpath(options, "auth", "prefix").asString { authPrefix = ap }
123
- let apikeyVal = apikey.asString ?? ""
124
- // Empty prefix (raw apiKey credential) must not add a leading space.
125
- headers.entries[headerAuth] = .string(authPrefix == "" ? apikeyVal : authPrefix + " " + apikeyVal)
126
- }
127
-
128
- return spec
129
- }
93
+ // prepareAuth IS NOT HERE. It was, and it hardcoded
94
+ //
95
+ // private let headerAuth = "authorization"
96
+ //
97
+ // WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API - header, query or
98
+ // cookie, and under what name - and apidef resolves it into
99
+ // main.kit.info.security. A template can only hold one answer, so an
100
+ // apiKey-in-query API (joplin's `?token=`) got an Authorization header it
101
+ // does not read and never got the query parameter it does.
102
+ //
103
+ // So `prepareAuthUtil` is GENERATED, into utility/PrepareAuth.swift beside
104
+ // this file, by cmp/swift/PrepareAuth_swift.ts. Same module, same internal
105
+ // symbol, so utility/Register.swift still binds it with
106
+ // `u.prepareAuth = prepareAuthUtil` and nothing else moved. Declaring it
107
+ // here as well would be an "invalid redeclaration" that fails the whole
108
+ // SwiftPM target.
109
+ //
110
+ // The seven functions above and paramUtil below do not depend on the model,
111
+ // so they stay templated.
130
112
 
131
113
  func paramUtil(_ ctx: Context, _ paramdef: Value) -> Value {
132
114
  let point = ctx.point
@@ -372,7 +372,18 @@ pub fn make_options_util(ctx: *Context) Value {
372
372
  .{ "base", h.vstr("http://localhost:8000") },
373
373
  .{ "prefix", h.vstr("") },
374
374
  .{ "suffix", h.vstr("") },
375
- .{ "auth", h.jo(&.{ .{ "prefix", h.vstr("") }, .{ "basic", h.vbool(false) } }) },
375
+ // CLOSED - validate REJECTS any key this spec does not declare - so
376
+ // without `in` and `name` an apiKey-in-query SDK fails validation on
377
+ // its OWN generated config (Config_zig emits them whenever the
378
+ // scheme is not the header/Authorization default), and the whole
379
+ // merged options object is discarded. '' means "take what the spec
380
+ // said", which is what the generated prepare_auth was built from.
381
+ .{ "auth", h.jo(&.{
382
+ .{ "prefix", h.vstr("") },
383
+ .{ "basic", h.vbool(false) },
384
+ .{ "in", h.vstr("") },
385
+ .{ "name", h.vstr("") },
386
+ }) },
376
387
  .{ "headers", h.jo(&.{.{ "`$CHILD`", h.vstr("`$STRING`") }}) },
377
388
  .{ "allow", h.jo(&.{
378
389
  .{ "method", h.vstr("GET,PUT,POST,PATCH,DELETE,OPTIONS") },
@@ -1308,50 +1319,31 @@ pub fn graphql_errors_util(ctx: *Context) bool {
1308
1319
  return true;
1309
1320
  }
1310
1321
 
1311
- const HEADER_AUTH = "authorization";
1312
- const OPTION_APIKEY = "apikey";
1313
- const NOT_FOUND = "__NOTFOUND__";
1314
-
1315
- pub fn prepare_auth_util(ctx: *Context) E!*Spec {
1316
- const spec = ctx.spec orelse return ctx.fail("auth_no_spec", "Expected context spec property to be defined.");
1317
-
1318
- const headers = spec.headers;
1319
- const options: Value = if (ctx.client) |client| client.options_map() else ctx.options;
1320
-
1321
- const auth = h.getp(options, "auth");
1322
- if (h.is_noval(auth)) {
1323
- h.del_prop(headers, h.vstr(HEADER_AUTH));
1324
- return spec;
1325
- }
1326
-
1327
- const apikey = vs.getprop(h.A(), options, h.vstr(OPTION_APIKEY), h.vstr(NOT_FOUND)) catch h.vstr(NOT_FOUND);
1328
-
1329
- const skip = switch (apikey) {
1330
- .null => true,
1331
- .string => |s| std.mem.eql(u8, s, NOT_FOUND) or s.len == 0,
1332
- else => false,
1333
- };
1334
-
1335
- if (skip) {
1336
- h.del_prop(headers, h.vstr(HEADER_AUTH));
1337
- } else {
1338
- const auth_prefix: []const u8 = switch (h.getpath(&.{ "auth", "prefix" }, options)) {
1339
- .string => |s| s,
1340
- else => "",
1341
- };
1342
- const apikey_val: []const u8 = switch (apikey) {
1343
- .string => |s| s,
1344
- else => "",
1345
- };
1346
- if (auth_prefix.len == 0) {
1347
- h.setp(headers, HEADER_AUTH, h.vstr(apikey_val));
1348
- } else {
1349
- h.setp(headers, HEADER_AUTH, h.vstr(fmt("{s} {s}", .{ auth_prefix, apikey_val })));
1350
- }
1351
- }
1352
-
1353
- return spec;
1354
- }
1322
+ // prepare_auth IS GENERATED (src/cmp/zig/PrepareAuth_zig.ts), not templated.
1323
+ //
1324
+ // WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API. apidef resolves the
1325
+ // security scheme's `in` and `name` into main.kit.info.security - joplin's
1326
+ // says `in: "query", name: "token"` - and this file could hold only one
1327
+ // answer, which was `const HEADER_AUTH = "authorization"`. So an
1328
+ // apiKey-in-query API was sent a header it does not read and never sent the
1329
+ // query parameter it does. Header, query and cookie need three different
1330
+ // bodies; a component emits the one this API uses and nothing else.
1331
+ //
1332
+ // RE-EXPORTED, NOT REWIRED. Every caller keeps naming the same symbol:
1333
+ // `Utility.prepare_auth` above, `make_spec_util`'s unqualified
1334
+ // `try prepare_auth_util(ctx)`, and `sdk.utilmod.prepare_auth_util` - the
1335
+ // path root.zig publishes and test/primary_utility_test.zig drives the
1336
+ // shared corpus's `prepareAuth` section through. A file-scope const bound to
1337
+ // the generated function is the whole of the binding change.
1338
+ pub const prepare_auth_util = @import("prepare_auth.zig").prepare_auth_util;
1339
+
1340
+ // The two comptime FACTS the generated file decided: where this SDK puts its
1341
+ // credential ("header" | "query" | "cookie" | "none"), and whether the scheme
1342
+ // is genuine HTTP Basic. Re-exported on the same path as the function, so
1343
+ // test/pipeline_test.zig can reach both as `sdk.utilmod.<name>` and skip the
1344
+ // header-shape cases on an SDK that has no header credential to assert on.
1345
+ pub const prepare_auth_placement = @import("prepare_auth.zig").PLACEMENT;
1346
+ pub const prepare_auth_basic = @import("prepare_auth.zig").BASIC;
1355
1347
 
1356
1348
  pub fn result_basic_util(ctx: *Context) ?*SdkResult {
1357
1349
  const response = ctx.response;
@@ -367,6 +367,35 @@ test "pipeline feature_add: ordering before after replace" {
367
367
  // prepare_auth
368
368
  // =====================================================================
369
369
 
370
+ // WHERE THE CREDENTIAL GOES IS MODEL-DRIVEN: core/prepare_auth.zig is
371
+ // GENERATED from the API's security scheme, and has exactly one branch. The
372
+ // cases below assert the HEADER shape - a key in `spec.headers` named
373
+ // `authorization` - which is the right assertion for the default and the
374
+ // wrong one for the other three. An SDK whose scheme puts the credential in
375
+ // the query string (`?token=`) or a cookie, and one whose project set
376
+ // `main.kit.config.auth.active: false`, correctly place NOTHING in the header
377
+ // bag, so these would go red for a reason that is not a defect.
378
+ //
379
+ // The generated file exports what it chose as a COMPTIME constant, and
380
+ // core/utility.zig re-exports it. Not read from the options: a query SDK's
381
+ // `options.auth.in` does say "query", but an auth-OFF SDK looks exactly like
382
+ // a header one there - Config omits `options.auth` entirely when auth is
383
+ // inactive and the optspec then supplies the same empty defaults.
384
+ //
385
+ // A header SDK - every SDK whose scheme resolves to the default - runs all of
386
+ // these exactly as it did before prepare_auth became a component.
387
+ fn headerCred() bool {
388
+ return std.mem.eql(u8, sdk.utilmod.prepare_auth_placement, "header");
389
+ }
390
+
391
+ // The two cases that assert how a single token is SHAPED into the header
392
+ // value need more than a header placement: genuine HTTP Basic replaces that
393
+ // shaping with base64(apikey:secret), and refuses outright when the secret is
394
+ // missing - which is exactly what those two fixtures leave out.
395
+ fn singleTokenCred() bool {
396
+ return headerCred() and !sdk.utilmod.prepare_auth_basic;
397
+ }
398
+
370
399
  test "pipeline prepare_auth: guards missing spec" {
371
400
  const client = sdk.test_sdk(vnull(), h.jo(&.{.{ "apikey", h.vstr("K") }}));
372
401
  const utility = client.get_utility();
@@ -381,6 +410,7 @@ test "pipeline prepare_auth: apikey with prefix space joined" {
381
410
  .{ "apikey", h.vstr("K") },
382
411
  .{ "auth", h.jo(&.{.{ "prefix", h.vstr("Bearer") }}) },
383
412
  }));
413
+ if (!singleTokenCred()) return;
384
414
  const utility = client.get_utility();
385
415
  const ctx = plCtx(client, utility, null);
386
416
  ctx.spec = authSpec(vnull());
@@ -393,6 +423,7 @@ test "pipeline prepare_auth: raw apikey empty prefix as is" {
393
423
  .{ "apikey", h.vstr("K") },
394
424
  .{ "auth", h.jo(&.{.{ "prefix", h.vstr("") }}) },
395
425
  }));
426
+ if (!singleTokenCred()) return;
396
427
  const utility = client.get_utility();
397
428
  const ctx = plCtx(client, utility, null);
398
429
  ctx.spec = authSpec(vnull());
@@ -405,6 +436,7 @@ test "pipeline prepare_auth: empty apikey drops header" {
405
436
  .{ "apikey", h.vstr("") },
406
437
  .{ "auth", h.jo(&.{.{ "prefix", h.vstr("Bearer") }}) },
407
438
  }));
439
+ if (!headerCred()) return;
408
440
  const utility = client.get_utility();
409
441
  const ctx = plCtx(client, utility, null);
410
442
  ctx.spec = authSpec(h.jo(&.{.{ "authorization", h.vstr("stale") }}));
@@ -416,6 +448,7 @@ test "pipeline prepare_auth: missing apikey drops header" {
416
448
  const client = sdk.test_sdk(vnull(), h.jo(&.{
417
449
  .{ "auth", h.jo(&.{.{ "prefix", h.vstr("Bearer") }}) },
418
450
  }));
451
+ if (!headerCred()) return;
419
452
  const utility = client.get_utility();
420
453
  const options = client.options_map();
421
454
  // Skip if this SDK's options happen to carry a configured apikey.
@@ -430,6 +463,7 @@ test "pipeline prepare_auth: missing apikey drops header" {
430
463
 
431
464
  test "pipeline prepare_auth: public api no auth block drops header" {
432
465
  const client = sdk.test_sdk(vnull(), h.jo(&.{.{ "apikey", h.vstr("K") }}));
466
+ if (!headerCred()) return;
433
467
  const utility = client.get_utility();
434
468
  // Option validation supplies an auth shape for this SDK, so a truly
435
469
  // auth-less client cannot be constructed here — mirror the rust skip.
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "4.19.0",
6
+ "version": "4.20.0",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",
@@ -274,7 +274,7 @@ syntax) with unification semantics:
274
274
  | \`*default \\| type\` | A default value unified against a type (e.g. \`*true \\| boolean\`). |
275
275
  | \`name: key()\` | Bind a field to its map key (so \`feature: log: {}\` gets \`name: 'log'\`). |
276
276
  | \`$$path$$\` | Interpolate a model value into a string — e.g. the SDK \`name\`. |
277
- | \`@"file.aon"\` | Include another fragment (how the index files work). |
277
+ | \`@"./file.aon"\` | Include another fragment (how the index files work). The \`./\` is required on a local path. |
278
278
  | \`x: .y\` | Reference another path's value (e.g. \`deps: ts: .js\`). |
279
279
 
280
280
  For example, the schema for every feature entry:
package/src/sdkgen.ts CHANGED
@@ -17,7 +17,7 @@ import type {
17
17
  ActionResult,
18
18
  } from './types'
19
19
 
20
- import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix, isHttpBasicAuth,
20
+ import { SdkGenError, requirePath, isAuthActive, resolveAuthPrefix, resolveAuthIn, resolveAuthName, isAuthSuppressed, isHttpBasicAuth,
21
21
  CONFIG_DATA_THRESHOLD, CONFIG_REPR_VALUES, isConfigData, configRepr,
22
22
  configReprSetting, configDefinition, clean, rawStringLiteral } from './utility'
23
23
 
@@ -1220,6 +1220,9 @@ export {
1220
1220
  requirePath,
1221
1221
  isAuthActive,
1222
1222
  resolveAuthPrefix,
1223
+ resolveAuthIn,
1224
+ resolveAuthName,
1225
+ isAuthSuppressed,
1223
1226
  isHttpBasicAuth,
1224
1227
  CONFIG_DATA_THRESHOLD,
1225
1228
  CONFIG_REPR_VALUES,
package/src/testkit.ts CHANGED
@@ -220,8 +220,8 @@ function stageConsumer(opts: StageOptions = {}): Consumer {
220
220
  // kind arrives, rather than assuming every index is already wired.
221
221
  Fs.writeFileSync(Path.join(sdk, 'model', 'sdk.aon'),
222
222
  "name: '" + name + "'\n" +
223
- '@"target/target-index.aon"\n' +
224
- '@"feature/feature-index.aon"\n' +
223
+ '@"./target/target-index.aon"\n' +
224
+ '@"./feature/feature-index.aon"\n' +
225
225
  (opts.extra ? opts.extra + '\n' : ''))
226
226
 
227
227
  // `@voxgig/sdkgen` has to be resolvable FROM THE CONSUMER, because the