@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,418 @@
1
+
2
+ import {
3
+ Content,
4
+ File,
5
+ Folder,
6
+ cmp,
7
+ isHttpBasicAuth,
8
+ resolveAuthIn,
9
+ resolveAuthName,
10
+ resolveAuthPrefix,
11
+ } from '@voxgig/sdkgen'
12
+
13
+
14
+ import {
15
+ KIT,
16
+ getModelPath,
17
+ } from '@voxgig/apidef'
18
+
19
+
20
+ import {
21
+ elixirString,
22
+ } from './utility_elixir'
23
+
24
+
25
+ // WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
26
+ // rather than templated. The elixir port of cmp/ts/PrepareAuth_ts.ts; see
27
+ // that file for the full account, and cmp/rb/PrepareAuth_rb.ts for the
28
+ // cookie-splice and basic-auth shapes this one follows.
29
+ //
30
+ // apidef has always resolved the scheme's `in` and `name` into
31
+ // `main.kit.info.security` — joplin's says `in: "query", name: "token"` —
32
+ // and generation dropped both. The result was an SDK that sent a header the
33
+ // API does not read and never sent the query parameter it does, so it could
34
+ // not authenticate at all. Four repos in the cedar fleet shipped that way:
35
+ // joplin (`token`), pipedrive (`api_token`), trello (`key`),
36
+ // lm-umbrella (`apiKey`).
37
+ //
38
+ // A template cannot fix this, because the three placements need three
39
+ // different bodies and a template has to pick one. A component emits the
40
+ // branch this API actually uses and nothing else — no dead query code in a
41
+ // bearer-token SDK, and no runtime `if` on a value that is fixed at
42
+ // generation time.
43
+ //
44
+ // ELIXIR IS THE ODD ONE OUT: it had NO prepare_auth template. The logic was
45
+ // embedded in `tm/elixir/lib/projectname/utility.ex` as `prepare_auth_impl/1`,
46
+ // one clause among thirty in the 1670-line runtime module. So this is an
47
+ // EXTRACTION as well as a port, and the extraction is into the function's
48
+ // OWN compilation unit — the PREFERRED shape — because Elixir has no reason
49
+ // to need the fallback:
50
+ //
51
+ // * mix compiles every `.ex` under `lib/`, with no manifest, include list
52
+ // or import to keep in step. A new file in lib/<app>/ is compiled by
53
+ // virtue of existing.
54
+ // * Elixir resolves modules by their `defmodule`, never by path, so a
55
+ // module in its own file is indistinguishable at every call site from
56
+ // the same code inlined elsewhere.
57
+ // * THE BINDING IS A VALUE, NOT A LEXICAL REFERENCE. utility.ex builds the
58
+ // utility node by setprop-ing a function into each slot
59
+ // (`{"prepare_auth", &prepare_auth_impl/1}`), and every caller reaches it
60
+ // through `u(ctx, "prepare_auth").(ctx)`. A remote capture
61
+ // `&PrepareAuth.prepare_auth_impl/1` is the same kind of value, so the
62
+ // registrar, the feature-override contract (a feature setprops over the
63
+ // slot on the reference-stable node) and the `Utility.prepare_auth/1`
64
+ // dispatch wrapper are all untouched. `Utility.prepare_auth(ctx)` —
65
+ // which is what Main.fragment.ex, make_spec_impl, pipeline_test.exs and
66
+ // primary_utility_test.exs all call — still resolves, still dispatches
67
+ // through the node, and still honours an override.
68
+ const PrepareAuth = cmp(async function PrepareAuth(props: any) {
69
+ const { target } = props
70
+ const { model } = props.ctx$
71
+
72
+ // FOLDER NESTING, worked out from the template's own path rather than
73
+ // guessed. utility.ex lives at `tm/elixir/lib/projectname/utility.ex`, and
74
+ // Main_elixir copies that directory with
75
+ //
76
+ // Folder({name:'lib'}, () => Copy({from:'tm/elixir/lib/projectname',
77
+ // to: model.const.name}))
78
+ //
79
+ // so the runtime lands at `lib/<name>/`. This component is CALLED from
80
+ // inside Main's second `Folder({ name: 'lib' })` block — the one that
81
+ // writes the generated (as opposed to copied) lib files — so `lib` is
82
+ // ALREADY OPEN at the call site and only `<name>` is opened here. The
83
+ // result is `lib/<name>/prepare_auth.ex`, beside the utility.ex that
84
+ // registers it.
85
+ //
86
+ // Opening `lib` again here would write `lib/lib/<name>/prepare_auth.ex`.
87
+ // That is the ts port's `src/src/utility/` trap, and it is WORSE in elixir
88
+ // than anywhere else: mix compiles everything under lib/ regardless of
89
+ // depth, so the module would still compile, still be found by name, and
90
+ // the mistake would be invisible to every test — only the published tree
91
+ // would look wrong. (Main_elixir carries the same warning about
92
+ // lib/projectname/ for the same reason.)
93
+ //
94
+ // Main's two `Folder({ name: 'lib' })` blocks are SIBLINGS, not nested:
95
+ // jostraca folders are scoped to their callback, so both resolve to
96
+ // `lib/` and this component sees exactly one of them open.
97
+ Folder({ name: model.const.name }, () => {
98
+ File({ name: 'prepare_auth.' + target.ext }, () => {
99
+ Content(render({
100
+ Name: model.const.Name,
101
+ active: authSwitchedOn(model),
102
+ where: resolveAuthIn(model),
103
+ name: resolveAuthName(model),
104
+ basic: isHttpBasicAuth(model),
105
+ // Read so the resolution is visible at generation time even though
106
+ // the emitted code takes the prefix from options at runtime (the
107
+ // secrets feature rewrites it there).
108
+ prefix: resolveAuthPrefix(model),
109
+ }))
110
+ })
111
+ })
112
+ })
113
+
114
+
115
+ type AuthSpec = {
116
+ Name: string
117
+ active: boolean
118
+ where: string
119
+ name: string
120
+ basic: boolean
121
+ prefix: string
122
+ }
123
+
124
+
125
+ function render(spec: AuthSpec): string {
126
+ const head = `# ${spec.Name} SDK utility: prepare_auth
127
+ #
128
+ # WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so this module is
129
+ # GENERATED from the model rather than copied from tm/. Do not edit by hand.
130
+ #
131
+ # Extracted from ${spec.Name}.Utility, which still owns the registration
132
+ # (\`{"prepare_auth", &PrepareAuth.prepare_auth_impl/1}\`) and the
133
+ # \`prepare_auth/1\` dispatch wrapper every caller goes through. A feature that
134
+ # overrides the utility slot overrides this, exactly as before.
135
+
136
+ defmodule ${spec.Name}.PrepareAuth do
137
+ alias Voxgig.Struct, as: S
138
+ alias ${spec.Name}.Context
139
+
140
+ `
141
+
142
+ // NO AUTH AT ALL — the project switched it off. See authSwitchedOn for why
143
+ // ONLY an explicit switch counts.
144
+ if (!spec.active) {
145
+ return head + ` # This SDK is configured with authentication off, so there is no
146
+ # credential to place. The function stays in the pipeline because
147
+ # make_spec calls it unconditionally and Utility registers it
148
+ # unconditionally.
149
+ def prepare_auth_impl(ctx) do
150
+ spec = S.getprop(ctx, "spec")
151
+
152
+ if spec == nil do
153
+ {nil, Context.make_error(ctx, "auth_no_spec", "Expected context spec property to be defined.")}
154
+ else
155
+ {spec, nil}
156
+ end
157
+ end
158
+ end
159
+ `
160
+ }
161
+
162
+ const query = 'query' === spec.where
163
+ const cookie = 'cookie' === spec.where
164
+
165
+ // HTTP Basic is header-only by definition: the scheme is
166
+ // `Authorization: Basic base64(user:pass)`. It cannot be expressed as a
167
+ // query parameter or a cookie, so the branch is emitted only where it can
168
+ // mean something — and only when the model says the scheme IS basic, so an
169
+ // ordinary bearer SDK carries no dead code and regenerates byte-identical
170
+ // to what utility.ex produced.
171
+ const basicHere = spec.basic && 'header' === spec.where
172
+
173
+ // HEADER NAMES ARE LOWER-CASED; QUERY AND COOKIE NAMES ARE NOT.
174
+ //
175
+ // `resolveAuthName` answers 'Authorization' by default, but the whole
176
+ // elixir runtime spells header names in lower case: the extracted code
177
+ // said `S.delprop(headers, "authorization")`, feature/secrets.ex rewrites
178
+ // `headers["authorization"]`, feature/debug.ex masks the same key, the
179
+ // shared corpus asserts `ctx:spec:headers:authorization`, and
180
+ // pipeline_test.exs reads it. A struct map key is case-sensitive, so
181
+ // emitting "Authorization" verbatim would put the credential under a key
182
+ // nothing reads and leave the secrets feature writing a SECOND header.
183
+ // HTTP header names are case-insensitive on the wire, so lower-casing
184
+ // costs nothing. A query parameter and a cookie ARE case-sensitive
185
+ // (`?Token=` is not `?token=`), so those keep the spec's spelling.
186
+ const credName = query || cookie ? spec.name : spec.name.toLowerCase()
187
+
188
+ const attrs = ` @cred_name ${elixirString(credName)}
189
+ ` +
190
+ (cookie ? ` @cookie_header "cookie"
191
+ ` : '') +
192
+ ` @option_apikey "apikey"
193
+ ` +
194
+ (basicHere ? ` @option_secret "secret"
195
+ ` : '') +
196
+ ` @not_found "__NOTFOUND__"
197
+
198
+ `
199
+
200
+ // The bag the credential lands in. A cookie rides the HEADER bag, because
201
+ // a cookie IS a header.
202
+ const bag = query
203
+ ? ` query = S.getprop(spec, "query")`
204
+ : ` headers = S.getprop(spec, "headers")`
205
+
206
+ // Clearing: a query parameter and a header are a plain delprop, but a
207
+ // cookie header is SHARED with whatever cookies the caller set, so only
208
+ // our own pair comes out.
209
+ const clear = query
210
+ ? 'S.delprop(query, @cred_name)'
211
+ : cookie
212
+ ? 'apply_cookie(headers, nil)'
213
+ : 'S.delprop(headers, @cred_name)'
214
+
215
+ const preamble = ` def prepare_auth_impl(ctx) do
216
+ spec = S.getprop(ctx, "spec")
217
+
218
+ if spec == nil do
219
+ {nil, Context.make_error(ctx, "auth_no_spec", "Expected context spec property to be defined.")}
220
+ else
221
+ ${bag}
222
+ options = opts_map(S.getprop(ctx, "client"))
223
+
224
+ # Public APIs that need no auth omit the options.auth block entirely.
225
+ if S.getprop(options, "auth") == nil do
226
+ ${clear}
227
+ {spec, nil}
228
+ else
229
+ apikey = S.getprop(options, @option_apikey, @not_found)
230
+
231
+ `
232
+
233
+ const body = basicHere
234
+ ? ` # True HTTP Basic Auth needs TWO credentials, base64-joined - a single
235
+ # token in the header (the branch below) can never authenticate
236
+ # against an API that actually checks
237
+ # \`Authorization: Basic base64(user:pass)\`.
238
+ if S.getpath(options, "auth.basic") == true do
239
+ secret = S.getprop(options, @option_secret, @not_found)
240
+ no_apikey = not is_binary(apikey) or apikey == @not_found or apikey == ""
241
+ no_secret = not is_binary(secret) or secret == @not_found or secret == ""
242
+
243
+ if no_apikey or no_secret do
244
+ S.delprop(headers, @cred_name)
245
+ else
246
+ ap = S.getpath(options, "auth.prefix")
247
+ auth_prefix = if is_binary(ap), do: ap, else: ""
248
+ b64 = Base.encode64(apikey <> ":" <> secret)
249
+ hv = if auth_prefix != "", do: auth_prefix <> " " <> b64, else: b64
250
+ S.setprop(headers, @cred_name, hv)
251
+ end
252
+ else
253
+ ${indent(place(spec, clear), 1)}
254
+ end
255
+ `
256
+ : place(spec, clear)
257
+
258
+ return head + attrs + preamble + body + `
259
+ {spec, nil}
260
+ end
261
+ end
262
+ end
263
+
264
+ ` + helpers(spec).replace(/\n+$/, '\n') + `end
265
+ `
266
+ }
267
+
268
+
269
+ // The one thing that actually differs between placements: where the
270
+ // credential is written, and whether the prefix travels with it.
271
+ function place(spec: AuthSpec, clear: string): string {
272
+ const guard = ` if (is_binary(apikey) and apikey == @not_found) or apikey == nil or apikey == "" do
273
+ ${clear}
274
+ else
275
+ `
276
+
277
+ if ('query' === spec.where) {
278
+ return guard + ` apikey_val = if is_binary(apikey), do: apikey, else: ""
279
+ # NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
280
+ # thing any API reads: the prefix is a header convention, so
281
+ # options.auth.prefix is dropped here deliberately rather than
282
+ # silently concatenated.
283
+ S.setprop(query, @cred_name, apikey_val)
284
+ end
285
+ `
286
+ }
287
+
288
+ if ('cookie' === spec.where) {
289
+ return guard + ` apikey_val = if is_binary(apikey), do: apikey, else: ""
290
+ # NO PREFIX IN A COOKIE either - a cookie carries a bare
291
+ # \`name=value\` pair, not a header's scheme-prefixed credential.
292
+ apply_cookie(headers, apikey_val)
293
+ end
294
+ `
295
+ }
296
+
297
+ // HEADER: the extracted body, unchanged.
298
+ return guard + ` ap = S.getpath(options, "auth.prefix")
299
+ auth_prefix = if is_binary(ap), do: ap, else: ""
300
+ apikey_val = if is_binary(apikey), do: apikey, else: ""
301
+ # Empty prefix (raw apiKey credential) must not add a leading space.
302
+ hv = if auth_prefix != "", do: auth_prefix <> " " <> apikey_val, else: apikey_val
303
+ S.setprop(headers, @cred_name, hv)
304
+ end
305
+ `
306
+ }
307
+
308
+
309
+ // The private helpers the emitted body needs. `opts_map/1` is the one
310
+ // utility.ex keeps for itself (it is `defp` there, so it could not be
311
+ // called across the module boundary) - eight lines, reproduced rather than
312
+ // made public, because widening a private helper to export one caller is a
313
+ // bigger change to utility.ex than this extraction is.
314
+ function helpers(spec: AuthSpec): string {
315
+ const optsMap = ` # The client's options as a MAP, cloned. The clone is load-bearing: the
316
+ # secrets feature rewrites options.apikey on the live client, and
317
+ # prepare_auth must read a snapshot rather than the node the feature is
318
+ # mutating. Same body as ${spec.Name}.Utility's own private opts_map/1.
319
+ defp opts_map(client) do
320
+ o = S.clone(S.getprop(client, "options"))
321
+ if S.ismap(o), do: o, else: S.jm([])
322
+ end
323
+
324
+ `
325
+
326
+ if ('cookie' !== spec.where) {
327
+ return optsMap
328
+ }
329
+
330
+ // A cookie IS a header, and the `cookie` header is SHARED with whatever
331
+ // cookies the caller set - so the pair is spliced in and out rather than
332
+ // the header assigned over. Splicing also makes this idempotent: a
333
+ // retried request cannot end up carrying the credential twice.
334
+ return optsMap + ` # The cookie header minus our own pair, every other cookie untouched.
335
+ defp cookies_without_cred(headers) do
336
+ existing = S.getprop(headers, @cookie_header)
337
+
338
+ if is_binary(existing) and existing != "" do
339
+ existing
340
+ |> String.split(";")
341
+ |> Enum.map(&String.trim/1)
342
+ |> Enum.reject(fn piece ->
343
+ piece == "" or piece == @cred_name or
344
+ String.starts_with?(piece, @cred_name <> "=")
345
+ end)
346
+ |> Enum.join("; ")
347
+ else
348
+ ""
349
+ end
350
+ end
351
+
352
+ # Remove our pair, leaving the rest of the cookie header in place.
353
+ defp apply_cookie(headers, nil) do
354
+ rest = cookies_without_cred(headers)
355
+
356
+ if rest == "" do
357
+ S.delprop(headers, @cookie_header)
358
+ else
359
+ S.setprop(headers, @cookie_header, rest)
360
+ end
361
+ end
362
+
363
+ # Set our pair, leaving the rest of the cookie header in place.
364
+ defp apply_cookie(headers, value) do
365
+ rest = cookies_without_cred(headers)
366
+ pair = @cred_name <> "=" <> value
367
+ S.setprop(headers, @cookie_header, if(rest == "", do: pair, else: rest <> "; " <> pair))
368
+ end
369
+
370
+ `
371
+ }
372
+
373
+
374
+ // Indent a rendered block by `n` two-space levels, leaving blank lines blank
375
+ // (a trailing-whitespace-only line is what `mix format --check-formatted`
376
+ // reports first).
377
+ function indent(block: string, n: number): string {
378
+ const pad = ' '.repeat(n)
379
+ return block.split('\n')
380
+ .map((line) => '' === line ? line : pad + line)
381
+ .join('\n')
382
+ .replace(/\n$/, '')
383
+ }
384
+
385
+
386
+ // NOT `isAuthActive`, AND THE DIFFERENCE IS LOAD-BEARING (the py port found
387
+ // this first; same reasoning, same fix).
388
+ //
389
+ // `isAuthActive` is false whenever the SPEC declares no security scheme
390
+ // (`main.kit.info.auth: false`). That is a statement about the DEFINITION,
391
+ // not a ban on ever sending a credential: apidef writes it for every spec
392
+ // with no securitySchemes block, GitHub's official OpenAPI included, and
393
+ // those SDKs are still expected to honour an `apikey` the caller passes.
394
+ // `optspec` always declares `apikey` and make_options fills `options.auth`
395
+ // from its defaults, so the runtime `S.getprop(options, "auth") == nil`
396
+ // guard never fired and every such SDK has always sent `options.apikey`.
397
+ //
398
+ // Gating the body on `isAuthActive` therefore does not trim dead code, it
399
+ // deletes working authentication - and takes the secrets feature with it,
400
+ // since that resolves a secret into options.apikey and prepare_auth then
401
+ // places nothing. elixir is one of the three targets in generatedcompile's
402
+ // AUTHNULL_STANDALONE list, whose lane (`elixir: the secrets feature runs
403
+ // with the feature active`) runs the shipped secrets suite against a LIVE
404
+ // client on a real mix and names its two auth-nil cases.
405
+ //
406
+ // So the no-op is emitted only when the PROJECT says so: `config.auth.active:
407
+ // false`, an explicit per-SDK switch nobody sets by accident. A spec that is
408
+ // merely silent keeps the credential path it has always had.
409
+ function authSwitchedOn(model: any): boolean {
410
+ const auth = getModelPath(model, `main.${KIT}.config.auth`,
411
+ { only_active: false, required: false })
412
+ return !(null != auth && false === auth.active)
413
+ }
414
+
415
+
416
+ export {
417
+ PrepareAuth
418
+ }
@@ -14,6 +14,8 @@ import {
14
14
  goModule,
15
15
  isAuthActive,
16
16
  isConfigData,
17
+ resolveAuthIn,
18
+ resolveAuthName,
17
19
  resolveAuthPrefix,
18
20
  serverVariables,
19
21
  targetFeatures,
@@ -64,9 +66,18 @@ const Config = cmp(async function Config(props: any) {
64
66
  svars.map((v: any) => `\t\t\t\t${JSON.stringify(v.name)}: ${JSON.stringify(v.dflt)},\n`).join('') +
65
67
  '\t\t\t},\n'
66
68
 
69
+ // `in` and `name` travel with the prefix now. They were resolved by
70
+ // apidef all along and dropped here, so an apiKey-in-query API got an
71
+ // Authorization header it does not read. Emitted only when they differ
72
+ // from the defaults, so a header/Authorization SDK is byte-identical to
73
+ // what it generated before.
74
+ const authIn = resolveAuthIn(model)
75
+ const authName = resolveAuthName(model)
67
76
  const authBlock = authActive
68
77
  ? ` "auth": map[string]any{
69
- "prefix": "${authPrefix}",
78
+ "prefix": "${authPrefix}",${'header' === authIn ? '' : `
79
+ "in": "${authIn}",`}${'Authorization' === authName ? '' : `
80
+ "name": "${authName}",`}
70
81
  },\n`
71
82
  : ''
72
83
 
@@ -27,6 +27,7 @@ import { Config } from './Config_go'
27
27
  import { Gitignore } from './Gitignore_go'
28
28
  import { MainEntity } from './MainEntity_go'
29
29
  import { EntityTypes } from './EntityTypes_go'
30
+ import { PrepareAuth } from './PrepareAuth_go'
30
31
 
31
32
 
32
33
  const Main = cmp(async function Main(props: any) {
@@ -78,6 +79,19 @@ const Main = cmp(async function Main(props: any) {
78
79
  // generated *_entity.go files so the typed accessors resolve without imports.
79
80
  EntityTypes({ target })
80
81
 
82
+ // utility/prepare_auth.go is GENERATED, not templated: where the
83
+ // credential goes (header / query / cookie, and under what name) is a
84
+ // fact about the API, and tm/ can only hold one answer. See
85
+ // PrepareAuth_go.
86
+ //
87
+ // AT ROOT LEVEL, and outside the `core` Folder below. Go's layout is flat
88
+ // at the target root, and the component opens `utility` itself - the same
89
+ // path `Copy({from:'tm/go'})` used for the template it replaces. Calling
90
+ // it beside Config, inside `Folder({name:'core'})`, would emit
91
+ // core/utility/prepare_auth.go instead: a package nothing imports, while
92
+ // registerAll keeps binding whatever utility/ actually holds.
93
+ PrepareAuth({ target })
94
+
81
95
  // Generate main SDK file in core/ folder
82
96
  Folder({ name: 'core' }, () => {
83
97