@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
@@ -0,0 +1,415 @@
1
+
2
+ import {
3
+ Content,
4
+ File,
5
+ Folder,
6
+ cmp,
7
+ isAuthSuppressed,
8
+ isHttpBasicAuth,
9
+ resolveAuthIn,
10
+ resolveAuthName,
11
+ resolveAuthPrefix,
12
+ } from '@voxgig/sdkgen'
13
+
14
+
15
+ import { scalaPackage } from './utility_scala'
16
+
17
+
18
+ // WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
19
+ // rather than templated. The scala port of cmp/ts/PrepareAuth_ts.ts; see
20
+ // that file for the full account.
21
+ //
22
+ // scala had no prepare_auth template to delete. The logic was an `object
23
+ // PrepareAuth` EMBEDDED in `tm/scala/utility/Prepare.scala` alongside the
24
+ // six other prepare steps, hardcoding `val HEADER_AUTH = "authorization"`.
25
+ // apidef has always resolved the scheme's `in` and `name` into
26
+ // `main.kit.info.security` — joplin's says `in: "query", name: "token"` —
27
+ // and generation dropped both, so the SDK sent a header the API does not
28
+ // read and never sent the query parameter it does.
29
+ //
30
+ // EXTRACTED INTO ITS OWN FILE rather than generating the whole of
31
+ // Prepare.scala. A Scala `object` is not tied to a file name: the compiler
32
+ // resolves `PrepareAuth.prepareAuth` by PACKAGE, and every caller —
33
+ // utility/Register.scala's `u.prepareAuth = (ctx) => PrepareAuth
34
+ // .prepareAuth(ctx)`, which is the only binding — already names it
35
+ // unqualified from inside `package SCALAPACKAGE.utility`. So moving the
36
+ // object into `utility/PrepareAuth.scala` in the same package leaves
37
+ // Register, core/Utility.scala's `var prepareAuth` field, utility/MakeSpec
38
+ // .scala's `utility.prepareAuth(ctx)` and core/SdkClient.scala's call
39
+ // site all resolving exactly as before, with no edit to any of them. The
40
+ // other six objects in Prepare.scala (PreparePath, PrepareMethod,
41
+ // PrepareParams, PrepareQuery, PrepareHeaders, PrepareBody) are placement-
42
+ // independent and stay templated, so the generated surface is exactly the
43
+ // part that varies with the API.
44
+ //
45
+ // A template cannot fix this, because the three placements need three
46
+ // different bodies and a template has to pick one. A component emits the
47
+ // branch this API actually uses and nothing else — no dead query code in a
48
+ // bearer-token SDK, and no runtime `if` on a value that is fixed at
49
+ // generation time.
50
+ const PrepareAuth = cmp(async function PrepareAuth(props: any) {
51
+ const { target } = props
52
+ const { model } = props.ctx$
53
+
54
+ const where = resolveAuthIn(model)
55
+
56
+ // LOWERCASED FOR A HEADER, VERBATIM OTHERWISE. apidef writes
57
+ // `name: "Authorization"`, the deleted `object PrepareAuth` hardcoded
58
+ // `"authorization"`, and the whole scala runtime reads the lower-case
59
+ // key: the shared primary corpus asserts
60
+ // `ctx:spec:headers:authorization`, SecretsFeature.reauth rewrites
61
+ // `headers.put("authorization", ...)` in place on a token refresh, and
62
+ // spec.headers is a plain case-sensitive LinkedHashMap. Emitting
63
+ // `Authorization` verbatim would put the credential under a key nothing
64
+ // reads. A query parameter and a cookie ARE case-sensitive, so they keep
65
+ // the spec's spelling exactly.
66
+ const resolved = resolveAuthName(model)
67
+ const name = 'header' === where ? resolved.toLowerCase() : resolved
68
+
69
+ const basic = isHttpBasicAuth(model)
70
+
71
+ // Read so the resolution is visible at generation time even though the
72
+ // emitted code takes the prefix from options at runtime (the secrets
73
+ // feature rewrites it there).
74
+ const prefix = resolveAuthPrefix(model)
75
+
76
+ // NOT `isAuthActive`, AND THE DIFFERENCE IS LOAD-BEARING. See
77
+ // isAuthSuppressed in sdkgen's utility.ts: `isAuthActive` is ALSO false
78
+ // whenever `main.kit.info.auth` is false, which says only that the SPEC
79
+ // declared no security scheme — such an SDK still carries a credential,
80
+ // because Make.scala's optspec always declares `apikey` and fills
81
+ // `options.auth` from its `{ "prefix": "" }` default, so the runtime
82
+ // `options.get("auth") == null` guard never fires and every such SDK has
83
+ // always sent `options.apikey`. Emitting a no-op for those would
84
+ // silently remove working authentication and take the secrets feature
85
+ // with it (SecretsFeature resolves a secret into options.apikey and
86
+ // prepareAuth then places nothing).
87
+ //
88
+ // `main.kit.config.auth.active: false` is the project saying "this SDK
89
+ // sends no credential, ever" — the only signal that can honestly be
90
+ // honoured before runtime, so it is the only one used.
91
+ const active = !isAuthSuppressed(model)
92
+
93
+ const scalapackage = scalaPackage(model)
94
+
95
+ // FOLDER NESTING: exactly one segment, `utility`.
96
+ //
97
+ // Worked out from the deleted code's own path and from Main_scala's call
98
+ // site. Main_scala's blanket `Copy({ from: 'tm/scala' })` runs at the
99
+ // TARGET ROOT (its excludes and the scala tree are root-relative, and
100
+ // project.scala/Makefile sit beside core/, utility/, entity/), so
101
+ // `tm/scala/utility/Prepare.scala` lands at `<root>/utility/Prepare
102
+ // .scala`. Reproducing that path from Main's TOP LEVEL therefore needs
103
+ // `utility` and nothing above it — the same one-segment shape
104
+ // EntityBase_scala uses for `entity`.
105
+ //
106
+ // So this component must be invoked from Main_scala's top level and NOT
107
+ // from inside its `Folder({ name: 'core' })`, where Config and
108
+ // EntityTypes are written: nested there it would emit
109
+ // `core/utility/PrepareAuth.scala`, whose `package <pkg>.utility`
110
+ // declaration contradicts its directory, while Register.scala kept
111
+ // binding the stale object. There is no `src` folder in a scala SDK at
112
+ // all, so the ts port's `src/src/utility/` failure has no scala twin —
113
+ // the scala twin would be `core/`.
114
+ Folder({ name: 'utility' }, () => {
115
+ File({ name: 'PrepareAuth.' + target.ext }, () => {
116
+ Content(render({ scalapackage, active, where, name, prefix, basic }))
117
+ })
118
+ })
119
+ })
120
+
121
+
122
+ type AuthSpec = {
123
+ scalapackage: string
124
+ active: boolean
125
+ where: string
126
+ name: string
127
+ prefix: string
128
+ basic: boolean
129
+ }
130
+
131
+
132
+ function render(spec: AuthSpec): string {
133
+ // The package line alone. Each placement emits its OWN import block,
134
+ // because they need different ones and the scala templates put the
135
+ // java.* imports first - Prepare.scala, Make.scala and Spec.scala all
136
+ // read `import java.util...` then the SDK's own packages.
137
+ const head = `package ${spec.scalapackage}.utility
138
+
139
+ `
140
+
141
+ // NO AUTH AT ALL - the project switched it off explicitly (see `active`
142
+ // above for why only an EXPLICIT switch counts). The SDK gets a
143
+ // prepareAuth that is honest about it rather than one that removes a
144
+ // header nobody set; Struct is not imported, because nothing here reads
145
+ // an option.
146
+ if (!spec.active) {
147
+ return head + `import ${spec.scalapackage}.core.{Context, Spec}
148
+
149
+ // This SDK is configured with authentication off
150
+ // (main.kit.config.auth.active: false), so there is no credential to
151
+ // place. The object stays in the pipeline because Register binds
152
+ // prepareAuth unconditionally and MakeSpec calls it on every request.
153
+ object PrepareAuth {
154
+
155
+ def prepareAuth(ctx: Context): Spec = {
156
+ val spec = ctx.spec
157
+ if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
158
+
159
+ spec
160
+ }
161
+ }
162
+ `
163
+ }
164
+
165
+ if ('query' === spec.where) return renderQuery(spec, head)
166
+ if ('cookie' === spec.where) return renderCookie(spec, head)
167
+
168
+ return renderHeader(spec, head)
169
+ }
170
+
171
+
172
+ // HEADER. Behaviourally the deleted `object PrepareAuth` when the scheme
173
+ // resolves to the defaults (header / Authorization): the same option names,
174
+ // the same `skip` idiom for a missing credential, the same auth_no_spec
175
+ // error, the same empty-prefix rule. Only the constant's VALUE moves with
176
+ // the model, plus the HTTP Basic block, which is emitted only for a basic
177
+ // scheme.
178
+ function renderHeader(spec: AuthSpec, head: string): string {
179
+ return head + `${spec.basic ? `import java.nio.charset.StandardCharsets
180
+ import java.util.Base64
181
+
182
+ ` : ''}import ${spec.scalapackage}.core.{Context, Spec}
183
+ import ${spec.scalapackage}.utility.struct.Struct
184
+
185
+ // Places the credential as the \`${spec.name}\` request header.
186
+ // GENERATED from the API's security scheme (main.kit.info.security:
187
+ // in: header, name: ${spec.name}${spec.basic ? ', http basic' : ''}).
188
+ object PrepareAuth {
189
+ val CRED_NAME = "${scalastr(spec.name)}"
190
+ val OPTION_APIKEY = "apikey"
191
+ ${spec.basic ? ` val OPTION_SECRET = "secret"\n` : ''} val NOT_FOUND = "__NOTFOUND__"
192
+
193
+ def prepareAuth(ctx: Context): Spec = {
194
+ val spec = ctx.spec
195
+ if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
196
+
197
+ val headers = spec.headers
198
+ val options = ctx.client.optionsMap()
199
+
200
+ // Public APIs that need no auth omit the options.auth block entirely.
201
+ if (options.get("auth") == null) {
202
+ headers.remove(CRED_NAME)
203
+ return spec
204
+ }
205
+
206
+ val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
207
+ ${basicBlock(spec)}
208
+ var skip = false
209
+ if (apikey == null) skip = true
210
+ else apikey match {
211
+ case s: String if NOT_FOUND == s || "" == s => skip = true
212
+ case _ =>
213
+ }
214
+
215
+ if (skip) {
216
+ headers.remove(CRED_NAME)
217
+ } else {
218
+ var authPrefix = ""
219
+ Struct.getpath(options, java.util.List.of("auth", "prefix")) match { case s: String => authPrefix = s; case _ => }
220
+ val apikeyVal = apikey match { case s: String => s; case _ => "" }
221
+ // A raw credential (empty prefix, e.g. an apiKey scheme) must go in
222
+ // as-is; only a non-empty prefix (Bearer/Basic/OAuth) is space-joined.
223
+ if ("" == authPrefix) headers.put(CRED_NAME, apikeyVal)
224
+ else headers.put(CRED_NAME, authPrefix + " " + apikeyVal)
225
+ }
226
+
227
+ spec
228
+ }
229
+ }
230
+ `
231
+ }
232
+
233
+
234
+ // HTTP Basic is header-only by definition: the scheme is `Authorization:
235
+ // Basic base64(user:pass)`. It cannot be expressed as a query parameter or
236
+ // a cookie, so renderQuery and renderCookie never call this - and it is
237
+ // emitted only when the model says the scheme IS basic, so an ordinary
238
+ // bearer SDK carries no dead code and regenerates unchanged.
239
+ function basicBlock(spec: AuthSpec): string {
240
+ if (!spec.basic) return ''
241
+
242
+ return `
243
+ // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
244
+ // token in the header (the branch below) can never authenticate against
245
+ // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
246
+ if (java.lang.Boolean.TRUE == Struct.getpath(options, java.util.List.of("auth", "basic"))) {
247
+ val secret = Struct.getprop(options, OPTION_SECRET, NOT_FOUND)
248
+ val noApikey = apikey == null || (apikey match { case s: String => NOT_FOUND == s || "" == s; case _ => false })
249
+ val noSecret = secret == null || (secret match { case s: String => NOT_FOUND == s || "" == s; case _ => false })
250
+
251
+ if (noApikey || noSecret) {
252
+ headers.remove(CRED_NAME)
253
+ } else {
254
+ var basicPrefix = ""
255
+ Struct.getpath(options, java.util.List.of("auth", "prefix")) match { case s: String => basicPrefix = s; case _ => }
256
+ val b64 = Base64.getEncoder.encodeToString(
257
+ ((apikey match { case s: String => s; case _ => "" }) + ":" +
258
+ (secret match { case s: String => s; case _ => "" })).getBytes(StandardCharsets.UTF_8))
259
+ if ("" == basicPrefix) headers.put(CRED_NAME, b64)
260
+ else headers.put(CRED_NAME, basicPrefix + " " + b64)
261
+ }
262
+
263
+ return spec
264
+ }
265
+ `
266
+ }
267
+
268
+
269
+ // QUERY. The credential is a query parameter, so it goes in spec.query and
270
+ // the headers are never touched. This is the placement joplin, pipedrive,
271
+ // trello and lm-umbrella declare and no generated SDK ever emitted.
272
+ function renderQuery(spec: AuthSpec, head: string): string {
273
+ return head + `import ${spec.scalapackage}.core.{Context, Spec}
274
+ import ${spec.scalapackage}.utility.struct.Struct
275
+
276
+ // Places the credential as the \`${spec.name}\` query parameter.
277
+ // GENERATED from the API's security scheme (main.kit.info.security:
278
+ // in: query, name: ${spec.name}).
279
+ object PrepareAuth {
280
+ val CRED_NAME = "${scalastr(spec.name)}"
281
+ val OPTION_APIKEY = "apikey"
282
+ val NOT_FOUND = "__NOTFOUND__"
283
+
284
+ def prepareAuth(ctx: Context): Spec = {
285
+ val spec = ctx.spec
286
+ if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
287
+
288
+ val query = spec.query
289
+ val options = ctx.client.optionsMap()
290
+
291
+ // Public APIs that need no auth omit the options.auth block entirely.
292
+ if (options.get("auth") == null) {
293
+ query.remove(CRED_NAME)
294
+ return spec
295
+ }
296
+
297
+ val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
298
+
299
+ var skip = false
300
+ if (apikey == null) skip = true
301
+ else apikey match {
302
+ case s: String if NOT_FOUND == s || "" == s => skip = true
303
+ case _ =>
304
+ }
305
+
306
+ if (skip) {
307
+ query.remove(CRED_NAME)
308
+ } else {
309
+ val apikeyVal = apikey match { case s: String => s; case _ => "" }
310
+ // NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
311
+ // thing any API reads: the prefix is a header convention, so the
312
+ // options auth.prefix is dropped here deliberately rather than
313
+ // silently concatenated.
314
+ query.put(CRED_NAME, apikeyVal)
315
+ }
316
+
317
+ spec
318
+ }
319
+ }
320
+ `
321
+ }
322
+
323
+
324
+ // COOKIE. A cookie IS a header, so the credential rides the header bag -
325
+ // but the `cookie` header is SHARED with whatever cookies the caller set,
326
+ // so the pair is spliced in and out rather than the header assigned over.
327
+ // Splicing also makes this idempotent: a retried request cannot end up
328
+ // carrying the credential twice.
329
+ function renderCookie(spec: AuthSpec, head: string): string {
330
+ return head + `import java.util.{Map => JMap}
331
+
332
+ import ${spec.scalapackage}.core.{Context, Spec}
333
+ import ${spec.scalapackage}.utility.struct.Struct
334
+
335
+ // Places the credential as the \`${spec.name}\` cookie.
336
+ // GENERATED from the API's security scheme (main.kit.info.security:
337
+ // in: cookie, name: ${spec.name}).
338
+ object PrepareAuth {
339
+ val COOKIE_HEADER = "cookie"
340
+ val CRED_NAME = "${scalastr(spec.name)}"
341
+ val OPTION_APIKEY = "apikey"
342
+ val NOT_FOUND = "__NOTFOUND__"
343
+
344
+ // The cookie header minus our own pair, every other cookie untouched.
345
+ private def without(headers: JMap[String, Object]): String = {
346
+ val existing = headers.get(COOKIE_HEADER) match { case s: String => s; case _ => "" }
347
+ if ("" == existing) return ""
348
+
349
+ val kept = existing.split(";").map(_.trim).filter { piece =>
350
+ "" != piece && CRED_NAME != piece && !piece.startsWith(CRED_NAME + "=")
351
+ }
352
+ kept.mkString("; ")
353
+ }
354
+
355
+ // Set (a value) or remove (null) our pair, leaving the rest in place.
356
+ private def place(headers: JMap[String, Object], value: String): Unit = {
357
+ val rest = without(headers)
358
+
359
+ if (value == null) {
360
+ if ("" == rest) headers.remove(COOKIE_HEADER)
361
+ else headers.put(COOKIE_HEADER, rest)
362
+ return
363
+ }
364
+
365
+ val pair = CRED_NAME + "=" + value
366
+ if ("" == rest) headers.put(COOKIE_HEADER, pair)
367
+ else headers.put(COOKIE_HEADER, rest + "; " + pair)
368
+ }
369
+
370
+ def prepareAuth(ctx: Context): Spec = {
371
+ val spec = ctx.spec
372
+ if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
373
+
374
+ val headers = spec.headers
375
+ val options = ctx.client.optionsMap()
376
+
377
+ // Public APIs that need no auth omit the options.auth block entirely.
378
+ if (options.get("auth") == null) {
379
+ place(headers, null)
380
+ return spec
381
+ }
382
+
383
+ val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
384
+
385
+ var skip = false
386
+ if (apikey == null) skip = true
387
+ else apikey match {
388
+ case s: String if NOT_FOUND == s || "" == s => skip = true
389
+ case _ =>
390
+ }
391
+
392
+ if (skip) {
393
+ place(headers, null)
394
+ } else {
395
+ val apikeyVal = apikey match { case s: String => s; case _ => "" }
396
+ // NO PREFIX IN A COOKIE either - a cookie carries a bare
397
+ // \`name=value\` pair, not a header's scheme-prefixed credential.
398
+ place(headers, apikeyVal)
399
+ }
400
+
401
+ spec
402
+ }
403
+ }
404
+ `
405
+ }
406
+
407
+
408
+ function scalastr(s: string): string {
409
+ return String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
410
+ }
411
+
412
+
413
+ export {
414
+ PrepareAuth
415
+ }
@@ -5,6 +5,8 @@ import {
5
5
  cmp,
6
6
  configDefinition,
7
7
  each,
8
+ resolveAuthIn,
9
+ resolveAuthName,
8
10
  targetFeatures,
9
11
  } from '@voxgig/sdkgen'
10
12
 
@@ -37,7 +39,41 @@ const Config = cmp(async function Config(props: any) {
37
39
  // into main.slug / main.version / main.target (the three station
38
40
  // descriptor identity fields, station design §4); swift has only the
39
41
  // JSON-literal rep, so that one call covers every rep this target emits.
40
- const { json } = configDefinition(model, target.name)
42
+ //
43
+ // `def` rather than the helper's own `json`, because `in` and `name` are
44
+ // added to the auth block below and the JSON has to be re-serialised over
45
+ // that. When nothing is added, `JSON.stringify(def)` IS the helper's
46
+ // `json` - same object, same key order - so a header SDK's config is
47
+ // byte-identical to what it was.
48
+ const { def } = configDefinition(model, target.name)
49
+
50
+ // `in` and `name` TRAVEL WITH THE PREFIX NOW. apidef resolved both from
51
+ // the spec's securityScheme all along (main.kit.info.security - joplin's
52
+ // says `in: "query", name: "token"`) and generation dropped them, so an
53
+ // apiKey-in-query API got an Authorization header it does not read.
54
+ //
55
+ // Emitted ONLY when they differ from the header/Authorization defaults, so
56
+ // every header-based SDK regenerates byte-identical. The generated
57
+ // prepareAuth bakes the placement in at generation time (see
58
+ // PrepareAuth_swift); these carry it in the config so a running SDK, and
59
+ // anything reading its descriptor, can say what its scheme actually is.
60
+ //
61
+ // THE OPTSPEC HAS TO NAME THEM TOO. `main.kit.optspec.auth` does, but
62
+ // swift does not read the model for its option spec - buildOptSpec() in
63
+ // tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift is written out
64
+ // by hand - so both were added there in the same change. Without that this
65
+ // block is inert at best: makeOptions merges the config's options and
66
+ // validates the result against that spec, and a key the spec does not name
67
+ // is dropped or rejected.
68
+ const authIn = resolveAuthIn(model)
69
+ const authName = resolveAuthName(model)
70
+ const authOpt: any = (def as any)?.options?.auth
71
+ if (null != authOpt) {
72
+ if ('header' !== authIn) authOpt.in = authIn
73
+ if ('Authorization' !== authName) authOpt.name = authName
74
+ }
75
+
76
+ const json = JSON.stringify(def)
41
77
 
42
78
  // Model-data defaults may carry the ProjectName placeholder (e.g. the
43
79
  // clienttrack clientName); resolve it to the API name so the embedded JSON
@@ -23,6 +23,7 @@ import {
23
23
 
24
24
  import { Package } from './Package_swift'
25
25
  import { Config } from './Config_swift'
26
+ import { PrepareAuth } from './PrepareAuth_swift'
26
27
  import { Gitignore } from './Gitignore_swift'
27
28
  import { MainEntity } from './MainEntity_swift'
28
29
  import { SdkError } from './SdkError_swift'
@@ -167,6 +168,15 @@ const Main = cmp(async function Main(props: any) {
167
168
  })
168
169
  })
169
170
 
171
+ // utility/PrepareAuth.swift — GENERATED, NOT COPIED. Where the credential
172
+ // goes (header, query or cookie, and under what name) is a fact about the
173
+ // API, and tm/ can only hold one answer; the function was extracted from
174
+ // the copied utility/Prepare.swift, which no longer declares it. Called at
175
+ // THIS level, not inside the `core` folder above: the component opens the
176
+ // whole `Sources/<Name>Sdk/utility` path itself, exactly as EntityTypes
177
+ // does for `entity`. See PrepareAuth_swift.
178
+ PrepareAuth({ target })
179
+
170
180
  // entity/<Name>Types.swift — documentary typed models (one struct per entity
171
181
  // + per op). Compiles with the SwiftPM target; nothing consumes it yet.
172
182
  EntityTypes({ target })