@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,439 @@
1
+ import {
2
+ Content,
3
+ File,
4
+ Folder,
5
+ cmp,
6
+ isAuthSuppressed,
7
+ isHttpBasicAuth,
8
+ resolveAuthIn,
9
+ resolveAuthName,
10
+ resolveAuthPrefix,
11
+ } from '@voxgig/sdkgen'
12
+
13
+
14
+ import { swiftString, swiftTargetDir } from './utility_swift'
15
+
16
+
17
+ // WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
18
+ // rather than templated. The swift peer of cmp/ts/PrepareAuth_ts.ts; read
19
+ // that one first, it carries the full account of the defect.
20
+ //
21
+ // apidef has always resolved the scheme's `in` and `name` into
22
+ // `main.kit.info.security` — joplin's says `in: "query", name: "token"` —
23
+ // and generation dropped both, so the SDK sent a header the API does not
24
+ // read and never sent the query parameter it does. Four repos in the cedar
25
+ // fleet ship SDKs that cannot authenticate for this reason: joplin
26
+ // (`token`), pipedrive (`api_token`), trello (`key`), lm-umbrella
27
+ // (`apiKey`).
28
+ //
29
+ // A template cannot fix this, because the three placements need three
30
+ // different bodies and a template has to pick one. A component emits the
31
+ // branch this API actually uses and nothing else — no dead query code in a
32
+ // bearer-token SDK, and no runtime `if` on a value that is fixed at
33
+ // generation time.
34
+ //
35
+ // SWIFT WAS THE ODD ONE OUT: it had no prepare_auth template to delete.
36
+ // `prepareAuthUtil` was one function among eight in
37
+ // `tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift`, beside
38
+ // prepareMethod/Path/Params/Query/Headers/Body and paramUtil — none of
39
+ // which is model-dependent. So the EXTRACTION is the move, not a rewrite of
40
+ // the module: `prepareAuthUtil` and its three private constants move out of
41
+ // Prepare.swift into this generated `utility/PrepareAuth.swift`, and the
42
+ // other seven functions stay templated where they are.
43
+ //
44
+ // SWIFT LETS THAT WORK WITH NO REWIRING AT ALL. Every .swift file in the
45
+ // SwiftPM target is one MODULE, and a top-level `func` is `internal` by
46
+ // default — visible to the whole module, importable by nothing, declared
47
+ // once. So moving the function to another file in the same target changes
48
+ // no name and no import:
49
+ //
50
+ // core/Utility.swift `public var prepareAuth: ((Context) throws -> Spec)!`
51
+ // utility/Register.swift `u.prepareAuth = prepareAuthUtil` <- resolves here
52
+ // utility/Make.swift `let spec = try utility.prepareAuth(ctx)`
53
+ // cmp/swift/fragment/Main.fragment.swift `_ = try utility.prepareAuth(ctx)`
54
+ //
55
+ // all stand unchanged. The one thing swift does NOT tolerate is the
56
+ // function existing in BOTH files — same module, same name, "invalid
57
+ // redeclaration of 'prepareAuthUtil'", which fails the whole target — so
58
+ // the template copy is deleted in the same change. That is the check that
59
+ // makes a stale copy impossible here, and it is a compile error rather than
60
+ // the silent double-life the ts port hit (see the folder note below).
61
+ const PrepareAuth = cmp(async function PrepareAuth(props: any) {
62
+ const { target } = props
63
+ const { model } = props.ctx$
64
+
65
+ // AUTH SWITCHED OFF BY THE PROJECT — and ONLY by that.
66
+ //
67
+ // NOT `isAuthActive`, and the difference is load-bearing. That helper is
68
+ // false whenever `main.kit.info.auth` is false, which only says the SPEC
69
+ // declared no security scheme; such an SDK still carries a credential,
70
+ // because `optspec` always declares `apikey` and makeOptions fills
71
+ // `options.auth` from the optspec defaults — so the runtime
72
+ // `isNil(auth)` guard below never fires and these SDKs have ALWAYS sent
73
+ // `options.apikey`. Emitting a no-op for them silently breaks working
74
+ // authentication and takes the secrets feature with it (it resolves a
75
+ // secret into `options.apikey`, and prepareAuth then places nothing).
76
+ //
77
+ // `main.kit.config.auth.active: false` is the project saying "this SDK
78
+ // sends no credential, ever" — the only signal that can honestly be
79
+ // honoured before runtime. `isAuthSuppressed` in @voxgig/sdkgen IS that
80
+ // predicate (utility.ts), so it is used here rather than re-derived: the
81
+ // py port wrote its own `isAuthActive_py` before the shared helper
82
+ // existed, and two copies of one rule is how they drift.
83
+ const suppressed = isAuthSuppressed(model)
84
+
85
+ const where = resolveAuthIn(model)
86
+
87
+ // LOWERCASED FOR A HEADER, VERBATIM OTHERWISE. HTTP header names are
88
+ // case-insensitive on the wire (RFC 9110 5.1), but this SDK's header bag
89
+ // is a plain `VMap` keyed in lower case throughout: the code being
90
+ // extracted said `private let headerAuth = "authorization"`, the shared
91
+ // corpus asserts `ctx:spec:headers:authorization`, the shipped
92
+ // PipelineTest reads `headers.entries["authorization"]`, and
93
+ // SecretsFeature.swift writes `headers.entries["authorization"]` as the
94
+ // other credential writer. apidef writes `name: "Authorization"`, so
95
+ // emitting it verbatim would put the credential under a key nothing in
96
+ // this SDK reads. A query parameter and a cookie ARE case-sensitive
97
+ // (`?token=` is not `?Token=`), so those keep the spec's spelling.
98
+ const resolvedName = resolveAuthName(model)
99
+ const name = 'header' === where ? resolvedName.toLowerCase() : resolvedName
100
+
101
+ // Resolved, and deliberately NOT baked into the emitted source: the
102
+ // prefix is a RUNTIME option (`options.auth.prefix`) and the code being
103
+ // extracted read it from the options map, so a caller — and the secrets
104
+ // feature, which rewrites it there — can still override it per client.
105
+ // Only the PLACEMENT is fixed at generation time.
106
+ const prefix = resolveAuthPrefix(model)
107
+
108
+ const basic = isHttpBasicAuth(model)
109
+
110
+ // FOLDER NESTING, worked out from the template's own path and from
111
+ // Main_swift, not guessed.
112
+ //
113
+ // The file being replaced lives at
114
+ // `tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift`, and Main_swift
115
+ // copies that tree with `Folder({name:'Sources'})` +
116
+ // `Copy({from:'tm/swift/Sources/ProjectNameSDK', to: swiftTargetDir(model)})`
117
+ // — note the `to`, which is why the runtime lands under the API's own
118
+ // name (`Sources/<Name>Sdk/utility/`) rather than under the placeholder
119
+ // directory. So the full destination is
120
+ // `Sources/<Name>Sdk/utility/PrepareAuth.swift`.
121
+ //
122
+ // ALL THREE SEGMENTS ARE OPENED HERE, and Main calls this at its TOP
123
+ // level, beside `EntityTypes({ target })` — which is the swift precedent
124
+ // for exactly this (EntityTypes_swift opens
125
+ // `Sources`/`swiftTargetDir`/`entity` itself). Two other call sites were
126
+ // available and both are wrong:
127
+ //
128
+ // * inside Main's generated-sources block, beside `Config({target})`.
129
+ // That block sits inside `Folder({name:'core'})`, so this would write
130
+ // `Sources/<Name>Sdk/core/utility/PrepareAuth.swift`.
131
+ // * inside that block but one level out, opening only `utility`. That
132
+ // is correct TODAY and breaks the moment the surrounding folders
133
+ // move; self-contained is what EntityTypes_swift chose.
134
+ //
135
+ // Getting it wrong is not merely a misplaced file: SwiftPM compiles
136
+ // EVERY .swift under `Sources/<target>/`, so a stray copy at the wrong
137
+ // path is still in the module and still collides with the real one. Here
138
+ // that is a hard build failure — which is the good case, and the reason
139
+ // the ts port's `src/src/utility/` accident (a second `Folder('src')`,
140
+ // the stale file still imported, invisible to the suite) cannot repeat on
141
+ // swift.
142
+ Folder({ name: 'Sources' }, () => {
143
+ Folder({ name: swiftTargetDir(model) }, () => {
144
+ Folder({ name: 'utility' }, () => {
145
+ File({ name: 'PrepareAuth.' + target.ext }, () => {
146
+ Content(render({
147
+ Name: model.const.Name,
148
+ suppressed, where, name, prefix, basic,
149
+ }))
150
+ })
151
+ })
152
+ })
153
+ })
154
+ })
155
+
156
+
157
+ type AuthSpec = {
158
+ Name: string
159
+ suppressed: boolean
160
+ where: string
161
+ name: string
162
+ prefix: string
163
+ basic: boolean
164
+ }
165
+
166
+
167
+ function render(spec: AuthSpec): string {
168
+ // Foundation on every file is the tm/swift convention (Prepare.swift,
169
+ // Make.swift, MakeOptions.swift, Spec.swift all open with it), and the
170
+ // basic and cookie branches genuinely need it — `base64EncodedString()`
171
+ // and `trimmingCharacters(in:)` are Foundation. Swift does not warn on an
172
+ // unused import, so the header branch carrying it costs nothing and keeps
173
+ // the file shaped like its neighbours.
174
+ const head = `// ${spec.Name} SDK utility: prepareAuth - place the API credential.
175
+ //
176
+ // GENERATED, not templated: WHERE the credential goes - header, query or
177
+ // cookie, and under what name - is a fact about THIS API, and tm/ can only
178
+ // hold one answer. Extracted from utility/Prepare.swift, which keeps the
179
+ // seven prepare* functions that do not depend on the model. Bound by
180
+ // utility/Register.swift (\`u.prepareAuth = prepareAuthUtil\`) exactly as
181
+ // before: same module, same internal symbol, no import needed.
182
+ //
183
+ // See cmp/swift/PrepareAuth_swift.ts.
184
+
185
+ import Foundation
186
+ `
187
+
188
+ if (spec.suppressed) {
189
+ // The project set `main.kit.config.auth.active: false`. No credential,
190
+ // ever — so the SDK gets a prepareAuth that is honest about it rather
191
+ // than one that pops a header nobody set.
192
+ return head + `
193
+ // This SDK is configured with authentication OFF
194
+ // (main.kit.config.auth.active is false), so there is no credential to
195
+ // place. The function stays in the pipeline because Register binds it
196
+ // unconditionally and makeSpec calls it on every request.
197
+ func prepareAuthUtil(_ ctx: Context) throws -> Spec {
198
+ guard let spec = ctx.spec else {
199
+ throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
200
+ }
201
+
202
+ return spec
203
+ }
204
+ `
205
+ }
206
+
207
+ if ('query' === spec.where) return head + renderQuery(spec)
208
+ if ('cookie' === spec.where) return head + renderCookie(spec)
209
+
210
+ return head + renderHeader(spec)
211
+ }
212
+
213
+
214
+ // HEADER. Byte-for-byte the function that was in Prepare.swift when the
215
+ // scheme resolves to the defaults (header / Authorization) and is not
216
+ // Basic — same constant names, same `skip` shape, same removeValue /
217
+ // entries[...] idioms, same error. Only the constant's VALUE moves with the
218
+ // model, plus the Basic block, which is emitted only for a basic scheme.
219
+ function renderHeader(spec: AuthSpec): string {
220
+ const basicConst = spec.basic ? `
221
+ private let optionSecret = "secret"` : ''
222
+
223
+ return `
224
+ private let headerAuth = ${swiftString(spec.name)}
225
+ private let optionApikey = "apikey"${basicConst}
226
+ private let notFound = "__NOTFOUND__"
227
+
228
+ func prepareAuthUtil(_ ctx: Context) throws -> Spec {
229
+ guard let spec = ctx.spec else {
230
+ throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
231
+ }
232
+
233
+ let headers = spec.headers
234
+ let options = ctx.client!.optionsMap()
235
+
236
+ // Public APIs that need no auth omit the options.auth block entirely.
237
+ let auth = getprop(.map(options), .string("auth"))
238
+ if isNil(auth) {
239
+ headers.entries.removeValue(forKey: headerAuth)
240
+ return spec
241
+ }
242
+
243
+ let apikey = getprop(.map(options), .string(optionApikey), .string(notFound))
244
+
245
+ var skip = isNil(apikey)
246
+ if let apikeyStr = apikey.asString, apikeyStr == notFound || apikeyStr == "" {
247
+ skip = true
248
+ }
249
+ ${basicBlock(spec)}
250
+ if skip {
251
+ headers.entries.removeValue(forKey: headerAuth)
252
+ } else {
253
+ var authPrefix = ""
254
+ if let ap = gpath(options, "auth", "prefix").asString { authPrefix = ap }
255
+ let apikeyVal = apikey.asString ?? ""
256
+ // Empty prefix (raw apiKey credential) must not add a leading space.
257
+ headers.entries[headerAuth] = .string(authPrefix == "" ? apikeyVal : authPrefix + " " + apikeyVal)
258
+ }
259
+
260
+ return spec
261
+ }
262
+ `
263
+ }
264
+
265
+
266
+ // HTTP Basic is header-only BY DEFINITION: the scheme is
267
+ // `Authorization: Basic base64(user:pass)`. It cannot be expressed as a
268
+ // query parameter or a cookie, so the branch is emitted only where it can
269
+ // mean something — and only when the model says the scheme IS basic, so an
270
+ // ordinary bearer SDK carries no dead code and regenerates unchanged.
271
+ function basicBlock(spec: AuthSpec): string {
272
+ if (!spec.basic) return ''
273
+
274
+ return `
275
+ // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
276
+ // token in the header (the branch below) can never authenticate against
277
+ // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
278
+ if true == gpath(options, "auth", "basic").asBool {
279
+ let secret = getprop(.map(options), .string(optionSecret), .string(notFound))
280
+
281
+ var noSecret = isNil(secret)
282
+ if let secretStr = secret.asString, secretStr == notFound || secretStr == "" {
283
+ noSecret = true
284
+ }
285
+
286
+ if skip || noSecret {
287
+ headers.entries.removeValue(forKey: headerAuth)
288
+ } else {
289
+ var authPrefix = ""
290
+ if let ap = gpath(options, "auth", "prefix").asString { authPrefix = ap }
291
+ let joined = (apikey.asString ?? "") + ":" + (secret.asString ?? "")
292
+ let b64 = Data(joined.utf8).base64EncodedString()
293
+ // Empty prefix (raw credential) must not add a leading space.
294
+ headers.entries[headerAuth] = .string(authPrefix == "" ? b64 : authPrefix + " " + b64)
295
+ }
296
+
297
+ return spec
298
+ }
299
+ `
300
+ }
301
+
302
+
303
+ // QUERY. The credential is a query parameter, so it goes in `spec.query` and
304
+ // the headers are never touched. `spec.query` is the bag makeUrl builds the
305
+ // query string from (utility/Make.swift), and prepareAuth runs AFTER
306
+ // prepareQuery in makeSpec, so the credential joins the op's own arguments
307
+ // rather than being overwritten by them.
308
+ function renderQuery(spec: AuthSpec): string {
309
+ return `
310
+ private let queryAuth = ${swiftString(spec.name)}
311
+ private let optionApikey = "apikey"
312
+ private let notFound = "__NOTFOUND__"
313
+
314
+ func prepareAuthUtil(_ ctx: Context) throws -> Spec {
315
+ guard let spec = ctx.spec else {
316
+ throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
317
+ }
318
+
319
+ let query = spec.query
320
+ let options = ctx.client!.optionsMap()
321
+
322
+ // Public APIs that need no auth omit the options.auth block entirely.
323
+ let auth = getprop(.map(options), .string("auth"))
324
+ if isNil(auth) {
325
+ query.entries.removeValue(forKey: queryAuth)
326
+ return spec
327
+ }
328
+
329
+ let apikey = getprop(.map(options), .string(optionApikey), .string(notFound))
330
+
331
+ var skip = isNil(apikey)
332
+ if let apikeyStr = apikey.asString, apikeyStr == notFound || apikeyStr == "" {
333
+ skip = true
334
+ }
335
+
336
+ if skip {
337
+ query.entries.removeValue(forKey: queryAuth)
338
+ } else {
339
+ let apikeyVal = apikey.asString ?? ""
340
+ // NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
341
+ // thing any API reads: the prefix is a header convention, so it is
342
+ // dropped here deliberately rather than silently concatenated.
343
+ query.entries[queryAuth] = .string(apikeyVal)
344
+ }
345
+
346
+ return spec
347
+ }
348
+ `
349
+ }
350
+
351
+
352
+ // COOKIE. A cookie IS a header, so the credential rides the header bag -
353
+ // but the `cookie` header is SHARED with whatever cookies the caller set, so
354
+ // the pair is spliced in and out rather than the header assigned over.
355
+ // Splicing is also what makes it idempotent: a retried request cannot end up
356
+ // carrying the credential twice.
357
+ function renderCookie(spec: AuthSpec): string {
358
+ return `
359
+ private let cookieHeader = "cookie"
360
+ private let cookieAuth = ${swiftString(spec.name)}
361
+ private let optionApikey = "apikey"
362
+ private let notFound = "__NOTFOUND__"
363
+
364
+ // The cookie header minus our own pair, every other cookie untouched.
365
+ private func cookiesWithoutCred(_ headers: VMap) -> String {
366
+ guard let existing = headers.entries[cookieHeader]?.asString, existing != "" else {
367
+ return ""
368
+ }
369
+
370
+ var kept: [String] = []
371
+ for part in existing.split(separator: ";", omittingEmptySubsequences: false) {
372
+ let piece = part.trimmingCharacters(in: .whitespaces)
373
+ if piece == "" || piece == cookieAuth || piece.hasPrefix(cookieAuth + "=") {
374
+ continue
375
+ }
376
+ kept.append(piece)
377
+ }
378
+
379
+ return kept.joined(separator: "; ")
380
+ }
381
+
382
+ // Set (non-nil) or remove (nil) our pair, leaving every other cookie in
383
+ // place. Deleting the whole header to remove one pair would drop cookies
384
+ // this SDK never set.
385
+ private func applyCookie(_ headers: VMap, _ value: String?) {
386
+ let rest = cookiesWithoutCred(headers)
387
+
388
+ guard let value = value else {
389
+ if rest == "" {
390
+ headers.entries.removeValue(forKey: cookieHeader)
391
+ } else {
392
+ headers.entries[cookieHeader] = .string(rest)
393
+ }
394
+ return
395
+ }
396
+
397
+ let pair = cookieAuth + "=" + value
398
+ headers.entries[cookieHeader] = .string(rest == "" ? pair : rest + "; " + pair)
399
+ }
400
+
401
+ func prepareAuthUtil(_ ctx: Context) throws -> Spec {
402
+ guard let spec = ctx.spec else {
403
+ throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
404
+ }
405
+
406
+ let headers = spec.headers
407
+ let options = ctx.client!.optionsMap()
408
+
409
+ // Public APIs that need no auth omit the options.auth block entirely.
410
+ let auth = getprop(.map(options), .string("auth"))
411
+ if isNil(auth) {
412
+ applyCookie(headers, nil)
413
+ return spec
414
+ }
415
+
416
+ let apikey = getprop(.map(options), .string(optionApikey), .string(notFound))
417
+
418
+ var skip = isNil(apikey)
419
+ if let apikeyStr = apikey.asString, apikeyStr == notFound || apikeyStr == "" {
420
+ skip = true
421
+ }
422
+
423
+ if skip {
424
+ applyCookie(headers, nil)
425
+ } else {
426
+ // NO PREFIX IN A COOKIE either - a cookie carries a bare \`name=value\`
427
+ // pair, not a header's scheme-prefixed credential.
428
+ applyCookie(headers, apikey.asString ?? "")
429
+ }
430
+
431
+ return spec
432
+ }
433
+ `
434
+ }
435
+
436
+
437
+ export {
438
+ PrepareAuth
439
+ }
@@ -15,6 +15,8 @@ import {
15
15
  isAuthActive,
16
16
  isConfigData,
17
17
  isHttpBasicAuth,
18
+ resolveAuthIn,
19
+ resolveAuthName,
18
20
  resolveAuthPrefix,
19
21
  serverVariables,
20
22
  targetFeatures,
@@ -54,10 +56,19 @@ const Config = cmp(async function Config(props: any) {
54
56
  // config.auth.prefix override -> spec-derived info.security.prefix -> 'Bearer'
55
57
  const authPrefix = resolveAuthPrefix(model)
56
58
  const authBasic = isHttpBasicAuth(model)
59
+ // `in` and `name` travel with the prefix now. They were resolved by
60
+ // apidef all along and dropped here, so an apiKey-in-query API got an
61
+ // Authorization header it does not read. Emitted only when they differ
62
+ // from the defaults, so a header/Authorization SDK is byte-identical to
63
+ // what it generated before.
64
+ const authIn = resolveAuthIn(model)
65
+ const authName = resolveAuthName(model)
57
66
  const authBlock = authActive
58
67
  ? `auth: {
59
68
  prefix: '${authPrefix}',${authBasic ? `
60
- basic: true,` : ''}
69
+ basic: true,` : ''}${'header' === authIn ? '' : `
70
+ in: '${authIn}',`}${'Authorization' === authName ? '' : `
71
+ name: '${authName}',`}
61
72
  },
62
73
 
63
74
  `
@@ -24,6 +24,7 @@ import {
24
24
 
25
25
  import { Package } from './Package_ts'
26
26
  import { Config } from './Config_ts'
27
+ import { PrepareAuth } from './PrepareAuth_ts'
27
28
  import { Schema } from './Schema_ts'
28
29
  import { Gitignore } from './Gitignore_ts'
29
30
  import { MainEntity } from './MainEntity_ts'
@@ -193,6 +194,10 @@ if (fres instanceof Promise) { await fres }
193
194
  })
194
195
 
195
196
  Config({ target })
197
+
198
+ // GENERATED, NOT COPIED. Where the credential goes is a fact about the
199
+ // API, and tm/ can only hold one answer. See PrepareAuth_ts.
200
+ PrepareAuth({ target })
196
201
  Schema({ target })
197
202
  EntityBase({ target })
198
203
  EntityTypes({ target })