@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,454 @@
1
+
2
+ import {
3
+ Content,
4
+ File,
5
+ cmp,
6
+ isHttpBasicAuth,
7
+ resolveAuthIn,
8
+ resolveAuthName,
9
+ resolveAuthPrefix,
10
+ } from '@voxgig/sdkgen'
11
+
12
+
13
+ import {
14
+ KIT,
15
+ getModelPath,
16
+ } from '@voxgig/apidef'
17
+
18
+
19
+ import {
20
+ ocamlString,
21
+ } from './utility_ocaml'
22
+
23
+
24
+ // WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
25
+ // rather than templated. The ocaml peer of PrepareAuth_ts / PrepareAuth_py;
26
+ // read PrepareAuth_ts first, it carries the full account of the defect.
27
+ //
28
+ // apidef has always resolved the scheme's `in` and `name` into
29
+ // `main.kit.info.security` - joplin's says `in: "query", name: "token"` -
30
+ // and generation dropped both, so the SDK sent a header the API does not
31
+ // read and never sent the query parameter it does. Four repos in the cedar
32
+ // fleet ship SDKs that cannot authenticate for this reason: joplin
33
+ // (`token`), pipedrive (`api_token`), trello (`key`), lm-umbrella
34
+ // (`apiKey`).
35
+ //
36
+ // OCAML IS THE ODD ONE OUT: it had no prepare_auth TEMPLATE to replace.
37
+ // The body was one `let` inside tm/ocaml/sdk_runtime.ml, the 1200-line
38
+ // module that holds every `*_util` and the registrar that binds them. So
39
+ // the extraction is a real one, and the shape was chosen rather than
40
+ // inherited - see EXTRACTION SHAPE below.
41
+ const PrepareAuth = cmp(async function PrepareAuth(props: any) {
42
+ const { target } = props
43
+ const { model } = props.ctx$
44
+
45
+ const where = resolveAuthIn(model)
46
+
47
+ // A HEADER NAME IS LOWER-CASED HERE; A QUERY OR COOKIE NAME IS NOT.
48
+ // apidef writes `name: "Authorization"`, the extracted body hardcoded
49
+ // `"authorization"`, and the whole ocaml runtime keys headers in lower
50
+ // case: make_spec writes "content-type", header_ci lower-cases before it
51
+ // looks up, the secrets feature rewrites `headers.authorization`, and the
52
+ // SHARED corpus asserts `ctx:spec:headers:authorization` - which
53
+ // test/primary_utility_test.ml drives through this very function. A
54
+ // struct map key is case-SENSITIVE, so emitting "Authorization" verbatim
55
+ // would put the credential under a key nothing in the SDK reads.
56
+ //
57
+ // A query parameter and a cookie name are case-sensitive ON THE WIRE:
58
+ // lm-umbrella's `apiKey` is not `apikey`, and lower-casing it would break
59
+ // exactly the APIs this change exists to fix. So they keep the spec's
60
+ // spelling, byte for byte.
61
+ const resolved = resolveAuthName(model)
62
+ const name = 'header' === where ? resolved.toLowerCase() : resolved
63
+
64
+ // Read so the resolution is visible at generation time, even though the
65
+ // emitted code takes the prefix from options at runtime (the secrets
66
+ // feature rewrites it there, and `auth.prefix` is a documented option).
67
+ const prefix = resolveAuthPrefix(model)
68
+
69
+ const basic = isHttpBasicAuth(model)
70
+ const active = authSwitchedOn(model)
71
+
72
+ // FOLDER NESTING. Main_ocaml calls this with NO Folder open: its own
73
+ // Config, SdkError and `sdk_client.ml` all write straight to the target
74
+ // root, and the only Folder it opens (`feature/secrets` for feature.mk)
75
+ // is scoped to that one block and closed before this call. The ocaml tree
76
+ // is FLAT at the root - sdk_types.ml, sdk_helpers.ml, sdk_runtime.ml,
77
+ // sdk_features.ml, sdk_config.ml all sit there, and the Makefile names
78
+ // each by that exact path in `RUNTIME`.
79
+ //
80
+ // So this component opens NO folder. Opening one (`utility`, say, copying
81
+ // the c and lua ports) would write `<root>/utility/sdk_prepare_auth.ml`,
82
+ // which is NOT what `RUNTIME` lists and NOT where `-I .` looks: ocamlc
83
+ // would report `Unbound module Sdk_prepare_auth` while compiling
84
+ // sdk_runtime.ml, and `utility/` already holds the vendored struct port,
85
+ // which the Copy owns.
86
+ File({ name: 'sdk_prepare_auth.' + target.ext }, () => {
87
+ Content(render({
88
+ project: model.const.Name, active, where, name, prefix, basic,
89
+ }))
90
+ })
91
+ })
92
+
93
+
94
+ // NOT `isAuthActive`, AND THE DIFFERENCE IS LOAD-BEARING (the py port found
95
+ // this first; six of the eleven ports found it independently).
96
+ //
97
+ // `isAuthActive` is false whenever the SPEC declares no security scheme
98
+ // (`main.kit.info.auth: false`). That is a statement about the DEFINITION,
99
+ // not a ban on ever sending a credential: apidef writes it for every spec
100
+ // with no securitySchemes block - GitHub's official OpenAPI included - and
101
+ // those SDKs are still expected to honour an `apikey` the caller passes.
102
+ // `optspec` always declares `apikey`, and make_options fills `options.auth`
103
+ // from the optspec defaults, so the runtime `auth = Noval | Null` guard
104
+ // never fired and every such SDK has ALWAYS sent the credential.
105
+ //
106
+ // Gating the body on `isAuthActive` therefore does not trim dead code, it
107
+ // deletes working authentication - and takes the secrets feature with it,
108
+ // since that resolves a secret into `options.apikey` and prepare_auth then
109
+ // places nothing. generatedcompile's own fixture is one of these
110
+ // (`main: kit: info: { ... auth: false }` in generateharness), and the
111
+ // shipped tm/ocaml/test/feature/secrets/t_secrets.ml drives a LIVE client
112
+ // on it.
113
+ //
114
+ // `main.kit.config.auth.active: false` is the project saying "this SDK
115
+ // sends no credential, ever" - an explicit per-SDK switch nobody sets by
116
+ // accident, and the only signal that can honestly be honoured before
117
+ // runtime. So it is the only one used here.
118
+ function authSwitchedOn(model: any): boolean {
119
+ const auth = getModelPath(model, `main.${KIT}.config.auth`,
120
+ { only_active: false, required: false })
121
+ return !(null != auth && false === auth.active)
122
+ }
123
+
124
+
125
+ type AuthSpec = {
126
+ project: string
127
+ active: boolean
128
+ where: string
129
+ name: string
130
+ prefix: string
131
+ basic: boolean
132
+ }
133
+
134
+
135
+ // EXTRACTION SHAPE: ITS OWN COMPILATION UNIT, and the module keeps calling
136
+ // it.
137
+ //
138
+ // The preferred shape, and it fits: `sdk_prepare_auth.ml` is module
139
+ // `Sdk_prepare_auth`, compiled between sdk_helpers.ml and sdk_runtime.ml -
140
+ // the Makefile's `RUNTIME` is an explicit ORDERED list (ocamlc compiles a
141
+ // module before anything that uses it and has no link-time reordering), so
142
+ // the position is stated there rather than discovered.
143
+ //
144
+ // THE BINDING IS PRESERVED EXACTLY. sdk_runtime.ml keeps
145
+ //
146
+ // let prepare_auth_util = Sdk_prepare_auth.prepare_auth_util
147
+ //
148
+ // so every existing call site resolves at the same name it always did:
149
+ // - `u_prepare_auth = prepare_auth_util` in new_utility, and
150
+ // `u.u_prepare_auth <- prepare_auth_util` in register - the closure
151
+ // record that IS the ocaml registrar (sdk_types.ml declares the field);
152
+ // - `u.u_prepare_auth ctx` in make_spec_util (sdk_runtime.ml) and in the
153
+ // secrets feature's re-run of the pipeline (sdk_features.ml);
154
+ // - `prepare_auth_util c` in test/primary_utility_test.ml, which reaches
155
+ // it through `open Sdk_runtime` and drives the SHARED corpus section
156
+ // through it - the parity suite requires ocaml to execute `prepareAuth`
157
+ // (parity.test.ts FULL tier), so that name had to keep resolving;
158
+ // - `cl.cl_utility.u_prepare_auth ctx` in the four t_pipeline.ml cases.
159
+ // Nothing in the tree was re-pointed at the new module, and nothing needed
160
+ // to be.
161
+ //
162
+ // THE ONE THING THAT HAD TO MOVE. The body reads the client's options
163
+ // through `client_options_map`, which was defined in sdk_runtime.ml - i.e.
164
+ // AFTER this module in compile order, so it could not be called from here.
165
+ // It is a one-line accessor over `sdk_client.cl_options` with no dependency
166
+ // on anything else in that module, so it moved DOWN into sdk_helpers.ml,
167
+ // where `cc`, `cu`, `getp` and every other shared accessor already live.
168
+ // sdk_runtime.ml opens Sdk_helpers, so its own three call sites are
169
+ // unchanged. The alternative - re-deriving the options map here - would
170
+ // fork a definition that must not drift.
171
+ function render(spec: AuthSpec): string {
172
+ const head = `(* ${spec.project} SDK utility: prepare_auth.
173
+ *
174
+ * GENERATED by @voxgig/sdkgen (src/cmp/ocaml/PrepareAuth_ocaml.ts), not
175
+ * copied from tm/ocaml - WHERE the credential goes and UNDER WHAT NAME is a
176
+ * fact about this API, and a template can hold only one answer. This SDK's
177
+ * scheme places it ${placement(spec)}.
178
+ *
179
+ * Sdk_runtime binds \`prepare_auth_util\` to this one and registers it in the
180
+ * utility record, so every caller reaches it exactly as before. *)
181
+
182
+ open Voxgig_struct
183
+ open Sdk_types
184
+ open Sdk_helpers
185
+
186
+ (* THE PLACEMENT THIS SDK WAS GENERATED FOR, DECLARED rather than left
187
+ * implicit in the code below.
188
+ *
189
+ * cred_active false only for main.kit.config.auth.active: false
190
+ * cred_where header | query | cookie
191
+ * cred_name the key used - ALREADY LOWER-CASED for a header, because
192
+ * every header key the runtime writes is; verbatim for a
193
+ * query parameter or a cookie, which are case-sensitive
194
+ *
195
+ * They exist so the SHIPPED suite can hold the runtime to its own contract
196
+ * instead of guessing at it: test/t_pipeline.ml's four \`prepare_auth.*\`
197
+ * cases hardcoded \`spec.headers["authorization"]\`, so they failed on any
198
+ * SDK whose scheme is not a header - precisely the SDK this change exists to
199
+ * make work. Reading them is not circular: the assertions still drive the
200
+ * real prepare_auth and check the bag, key and value it actually wrote. *)
201
+ let cred_active = ${spec.active}
202
+ let cred_where = "${ocamlString(spec.where)}"
203
+ let cred_name = "${ocamlString(spec.name)}"
204
+ `
205
+
206
+ // AUTH SWITCHED OFF BY THE PROJECT (see authSwitchedOn for why only an
207
+ // EXPLICIT switch counts). The SDK gets a prepare_auth that is honest
208
+ // about it rather than one that deletes a header nobody set. Nothing is
209
+ // read from options, so no constant is emitted either.
210
+ if (!spec.active) {
211
+ return head + `
212
+ (* This SDK is configured with authentication off
213
+ * (main.kit.config.auth.active: false), so there is no credential to place.
214
+ * The function stays in the pipeline because make_spec calls it
215
+ * unconditionally. *)
216
+ let prepare_auth_util (ctx : ctx) : (spec option * sdk_error option) =
217
+ match ctx.c_spec with
218
+ | None -> (None, Some (ctx_make_error ctx "auth_no_spec" "Expected context spec property to be defined."))
219
+ | Some spec -> (Some spec, None)
220
+ `
221
+ }
222
+
223
+ if ('query' === spec.where) return renderQuery(spec, head)
224
+ if ('cookie' === spec.where) return renderCookie(spec, head)
225
+
226
+ return renderHeader(spec, head)
227
+ }
228
+
229
+
230
+ function placement(spec: AuthSpec): string {
231
+ if ('query' === spec.where) return `in the query string, as \`${spec.name}\``
232
+ if ('cookie' === spec.where) return `in the \`cookie\` header, as \`${spec.name}\``
233
+ return `in the \`${spec.name}\` header`
234
+ }
235
+
236
+
237
+ // HEADER. Behaviourally what sdk_runtime.ml's `prepare_auth_util` did, line
238
+ // for line and idiom for idiom: the same `auth_no_spec` error, the same
239
+ // `__NOTFOUND__` sentinel read through `getprop ~alt`, the same
240
+ // missing-credential handling (delete the header), the same empty-prefix
241
+ // rule. Only the credential NAME moves with the model - and it resolves to
242
+ // "authorization" for every header SDK, so those regenerate unchanged.
243
+ //
244
+ // The HTTP Basic block is the one addition, and it is emitted ONLY when the
245
+ // model says the scheme IS basic (`isHttpBasicAuth`). An ordinary
246
+ // bearer/apiKey SDK carries no dead code and no behaviour change. ocaml's
247
+ // extracted body never had this branch: a basic scheme resolved
248
+ // `auth.prefix` to "Basic" and sent `Basic <apikey>` - a single token where
249
+ // the scheme demands `base64(user:pass)`, which cannot authenticate. The
250
+ // other eleven ports added the same branch for the same reason.
251
+ function renderHeader(spec: AuthSpec, head: string): string {
252
+ return head + `
253
+ let option_apikey = "apikey"
254
+ ${spec.basic ? `let option_secret = "secret"
255
+ ` : ''}let not_found = "__NOTFOUND__"
256
+ ` + (spec.basic ? BASE64 : '') + `
257
+ let prepare_auth_util (ctx : ctx) : (spec option * sdk_error option) =
258
+ match ctx.c_spec with
259
+ | None -> (None, Some (ctx_make_error ctx "auth_no_spec" "Expected context spec property to be defined."))
260
+ | Some spec ->
261
+ let headers = spec.sp_headers in
262
+ let options = client_options_map (cc ctx) in
263
+ (match getp options "auth" with
264
+ (* \`auth: null\` is the documented suppression, and a public API that
265
+ * needs no auth omits the block entirely. Both land here. *)
266
+ | Noval | Null -> ignore (delprop headers (Str cred_name)); (Some spec, None)
267
+ | _ ->
268
+ let apikey = getprop ~alt:(Str not_found) options (Str option_apikey) in
269
+ let is_notfound = (match apikey with Str s -> s = not_found | _ -> false) in
270
+ let no_apikey = is_notfound || is_noval apikey || apikey = Str "" in
271
+ ${spec.basic ? BASIC : ''} if no_apikey then
272
+ ignore (delprop headers (Str cred_name))
273
+ else begin
274
+ let auth_prefix = match getpath_s options "auth.prefix" with Str s -> s | _ -> "" in
275
+ let apikey_val = match apikey with Str s -> s | _ -> "" in
276
+ (* Empty prefix (a raw apiKey credential) must not add a leading space. *)
277
+ let authval = if auth_prefix <> "" then auth_prefix ^ " " ^ apikey_val else apikey_val in
278
+ setp headers cred_name (Str authval)
279
+ end;
280
+ (Some spec, None))
281
+ `
282
+ }
283
+
284
+
285
+ // QUERY. The credential is a query parameter, so it goes in `spec.sp_query`
286
+ // and the headers are never touched. prepare_auth runs AFTER prepare_query
287
+ // and BEFORE make_url in make_spec_util, and make_url walks `sp_query` into
288
+ // the URL - so this placement reaches the wire without any other change.
289
+ function renderQuery(spec: AuthSpec, head: string): string {
290
+ return head + `
291
+ let option_apikey = "apikey"
292
+ let not_found = "__NOTFOUND__"
293
+
294
+ let prepare_auth_util (ctx : ctx) : (spec option * sdk_error option) =
295
+ match ctx.c_spec with
296
+ | None -> (None, Some (ctx_make_error ctx "auth_no_spec" "Expected context spec property to be defined."))
297
+ | Some spec ->
298
+ let query = spec.sp_query in
299
+ let options = client_options_map (cc ctx) in
300
+ (match getp options "auth" with
301
+ (* \`auth: null\` is the documented suppression, and a public API that
302
+ * needs no auth omits the block entirely. Both land here. *)
303
+ | Noval | Null -> ignore (delprop query (Str cred_name)); (Some spec, None)
304
+ | _ ->
305
+ let apikey = getprop ~alt:(Str not_found) options (Str option_apikey) in
306
+ let is_notfound = (match apikey with Str s -> s = not_found | _ -> false) in
307
+ if is_notfound || is_noval apikey || apikey = Str "" then
308
+ ignore (delprop query (Str cred_name))
309
+ else begin
310
+ (* NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
311
+ * thing any API reads: the prefix is a header-value convention, so
312
+ * it is dropped here deliberately rather than concatenated. *)
313
+ let apikey_val = match apikey with Str s -> s | _ -> "" in
314
+ setp query cred_name (Str apikey_val)
315
+ end;
316
+ (Some spec, None))
317
+ `
318
+ }
319
+
320
+
321
+ // COOKIE. A cookie IS a header, so the credential rides the header bag -
322
+ // but the \`cookie\` header is SHARED with whatever cookies the caller set
323
+ // through options.headers, so our pair is SPLICED in and out rather than
324
+ // the header assigned over. Splicing also makes this idempotent: the
325
+ // secrets feature re-runs the pipeline on a retried request, and an
326
+ // assignment would leave the credential in the header twice.
327
+ function renderCookie(spec: AuthSpec, head: string): string {
328
+ return head + `
329
+ let cookie_header = "cookie"
330
+ let option_apikey = "apikey"
331
+ let not_found = "__NOTFOUND__"
332
+
333
+ (* Strip ASCII spaces and tabs from both ends of one cookie pair. *)
334
+ let cookie_trim (s : string) : string =
335
+ let n = String.length s in
336
+ let b = ref 0 and e = ref n in
337
+ while !b < !e && (s.[!b] = ' ' || s.[!b] = '\\t') do incr b done;
338
+ while !e > !b && (s.[!e - 1] = ' ' || s.[!e - 1] = '\\t') do decr e done;
339
+ String.sub s !b (!e - !b)
340
+
341
+ (* True for OUR pair only: the bare name, or the name followed by '='. A
342
+ * cookie called "${ocamlString(spec.name)}_backup" must survive. *)
343
+ let cookie_is_cred (pair : string) : bool =
344
+ let n = String.length cred_name in
345
+ pair = cred_name
346
+ || (String.length pair > n && String.sub pair 0 (n + 1) = cred_name ^ "=")
347
+
348
+ (* Rewrite the cookie header with our pair set (Some v) or removed (None),
349
+ * every other cookie kept in order. *)
350
+ let cookie_set (headers : value) (v : string option) : unit =
351
+ let existing = match getp headers cookie_header with Str s -> s | _ -> "" in
352
+ let kept =
353
+ List.filter (fun p -> p <> "" && not (cookie_is_cred p))
354
+ (List.map cookie_trim (String.split_on_char ';' existing)) in
355
+ let kept = match v with None -> kept | Some x -> kept @ [cred_name ^ "=" ^ x] in
356
+ if [] = kept then ignore (delprop headers (Str cookie_header))
357
+ else setp headers cookie_header (Str (String.concat "; " kept))
358
+
359
+ let prepare_auth_util (ctx : ctx) : (spec option * sdk_error option) =
360
+ match ctx.c_spec with
361
+ | None -> (None, Some (ctx_make_error ctx "auth_no_spec" "Expected context spec property to be defined."))
362
+ | Some spec ->
363
+ let headers = spec.sp_headers in
364
+ let options = client_options_map (cc ctx) in
365
+ (match getp options "auth" with
366
+ (* \`auth: null\` is the documented suppression, and a public API that
367
+ * needs no auth omits the block entirely. Both land here. *)
368
+ | Noval | Null -> cookie_set headers None; (Some spec, None)
369
+ | _ ->
370
+ let apikey = getprop ~alt:(Str not_found) options (Str option_apikey) in
371
+ let is_notfound = (match apikey with Str s -> s = not_found | _ -> false) in
372
+ if is_notfound || is_noval apikey || apikey = Str "" then
373
+ cookie_set headers None
374
+ else begin
375
+ (* NO PREFIX IN A COOKIE either - a cookie carries a bare
376
+ * \`name=value\` pair, not a header's scheme-prefixed credential. *)
377
+ let apikey_val = match apikey with Str s -> s | _ -> "" in
378
+ cookie_set headers (Some apikey_val)
379
+ end;
380
+ (Some spec, None))
381
+ `
382
+ }
383
+
384
+
385
+ // True HTTP Basic Auth: TWO credentials, base64-joined. Emitted only for a
386
+ // HEADER placement, because the scheme IS a header -
387
+ // \`Authorization: Basic base64(user:pass)\` cannot be expressed as a query
388
+ // parameter or a cookie pair, so renderQuery and renderCookie never carry
389
+ // it.
390
+ //
391
+ // It is the FIRST of three `unit` branches - basic, no-credential,
392
+ // single-token - and the shared \`(Some spec, None)\` after them is the one
393
+ // result. Written that way rather than as an early return because OCaml
394
+ // sequences with \`;\`: a branch returning the tuple could not sit beside
395
+ // two returning unit.
396
+ const BASIC = ` if (match getpath_s options "auth.basic" with Bool b -> b | _ -> false) then begin
397
+ (* True HTTP Basic Auth needs TWO credentials, base64-joined - a
398
+ * single token in the header (the branch below) can never
399
+ * authenticate against an API that actually checks
400
+ * \`Authorization: Basic base64(user:pass)\`. *)
401
+ let secret = getprop ~alt:(Str not_found) options (Str option_secret) in
402
+ let no_secret =
403
+ (match secret with Str s -> s = not_found | _ -> false)
404
+ || is_noval secret || secret = Str "" in
405
+ if no_apikey || no_secret then
406
+ ignore (delprop headers (Str cred_name))
407
+ else begin
408
+ let auth_prefix = match getpath_s options "auth.prefix" with Str s -> s | _ -> "" in
409
+ let apikey_val = match apikey with Str s -> s | _ -> "" in
410
+ let secret_val = match secret with Str s -> s | _ -> "" in
411
+ let joined = base64_encode (apikey_val ^ ":" ^ secret_val) in
412
+ setp headers cred_name
413
+ (Str (if auth_prefix <> "" then auth_prefix ^ " " ^ joined else joined))
414
+ end
415
+ end
416
+ else
417
+ `
418
+
419
+
420
+ // OCaml's stdlib has no base64 (4.14 ships none, and the SDK is
421
+ // dependency-free: stock ocamlc, no opam, no dune). The vendored sekreto
422
+ // port has one, but it lives inside the OPTIONAL secrets feature - an SDK
423
+ // that never asked for secrets does not compile it - so an HTTP Basic SDK
424
+ // carries its own. Emitted only in that branch, so nothing else pays for
425
+ // it. The lua port carries the same encoder for the same reason.
426
+ const BASE64 = `
427
+ let b64_alphabet =
428
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
429
+
430
+ let base64_encode (text : string) : string =
431
+ let n = String.length text in
432
+ let buf = Buffer.create (((n + 2) / 3) * 4) in
433
+ let i = ref 0 in
434
+ while !i < n do
435
+ let rest = n - !i in
436
+ let a = Char.code text.[!i] in
437
+ let b = if 1 < rest then Char.code text.[!i + 1] else 0 in
438
+ let c = if 2 < rest then Char.code text.[!i + 2] else 0 in
439
+ let word = (a lsl 16) lor (b lsl 8) lor c in
440
+ Buffer.add_char buf b64_alphabet.[(word lsr 18) land 0x3f];
441
+ Buffer.add_char buf b64_alphabet.[(word lsr 12) land 0x3f];
442
+ (* One trailing source byte yields two characters and "==", two yield
443
+ * three and "=". *)
444
+ Buffer.add_char buf (if 1 < rest then b64_alphabet.[(word lsr 6) land 0x3f] else '=');
445
+ Buffer.add_char buf (if 2 < rest then b64_alphabet.[word land 0x3f] else '=');
446
+ i := !i + 3
447
+ done;
448
+ Buffer.contents buf
449
+ `
450
+
451
+
452
+ export {
453
+ PrepareAuth
454
+ }
@@ -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
 
@@ -92,6 +94,34 @@ const Config = cmp(async function Config(props: any) {
92
94
  // descriptor inputs), matching the ts/js/rb targets.
93
95
  const { def: configDef } = configDefinition(model, target.name)
94
96
 
97
+ // `in` and `name` TRAVEL WITH THE PREFIX NOW. apidef resolved both from
98
+ // the spec's securityScheme all along (joplin's says `in: "query",
99
+ // name: "token"`) and generation dropped them, so an apiKey-in-query
100
+ // SDK carried an Authorization header the API does not read. The
101
+ // placement is a runtime option like the prefix, so it belongs in the
102
+ // config the SDK loads - see PrepareAuth_perl, which bakes the same two
103
+ // values into prepare_auth.pm.
104
+ //
105
+ // Overlaid HERE rather than inside configDefinition because that helper
106
+ // is shared by every target, and the other targets' emitters do not
107
+ // carry the fields yet - widening it would move their output too.
108
+ //
109
+ // Emitted ONLY when they differ from the defaults, so a
110
+ // header/Authorization SDK's config.pm is byte-identical to what it
111
+ // generated before.
112
+ if (null != configDef.options && null != configDef.options.auth) {
113
+ const authIn = resolveAuthIn(model)
114
+ const authName = resolveAuthName(model)
115
+
116
+ if ('header' !== authIn) {
117
+ configDef.options.auth.in = authIn
118
+ }
119
+
120
+ if ('Authorization' !== authName) {
121
+ configDef.options.auth.name = authName
122
+ }
123
+ }
124
+
95
125
  const configJson = JSON.stringify(configDef, null, 2)
96
126
 
97
127
  // Gated by the applicability tags, so this target never imports a
@@ -23,6 +23,7 @@ import {
23
23
 
24
24
  import { Package } from './Package_perl'
25
25
  import { Config } from './Config_perl'
26
+ import { PrepareAuth } from './PrepareAuth_perl'
26
27
  import { Gitignore } from './Gitignore_perl'
27
28
  import { MainEntity } from './MainEntity_perl'
28
29
 
@@ -131,6 +132,19 @@ const Main = cmp(async function Main(props: any) {
131
132
  Config({ target })
132
133
  })
133
134
 
135
+ // GENERATED, NOT COPIED. Where the credential goes is a fact about the
136
+ // API, and tm/ can only hold one answer. See PrepareAuth_perl.
137
+ //
138
+ // NO FOLDER OPEN HERE, deliberately. perl has no `src/` tree (the ts
139
+ // port's Main wraps Config and PrepareAuth in `Folder({name:'src'})`,
140
+ // and this one must not): Main writes `lib/`, `config.pm` and
141
+ // `features.pm` into the SDK root, and the blanket `Copy({from:
142
+ // 'tm/perl'})` above lands the utility tree at `<sdk>/utility/`. So
143
+ // this call sits OUTSIDE the `.` folder above and PrepareAuth opens the
144
+ // single `utility` folder itself, writing exactly the path the deleted
145
+ // template occupied.
146
+ PrepareAuth({ target })
147
+
134
148
  // Generate feature factory module
135
149
  File({ name: 'features.pm' }, () => {
136
150
  Content(`# ${model.const.Name} SDK feature factory