@voxgig/sdkgen 4.19.1 → 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.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/sdkgen.d.ts +2 -2
- package/dist/sdkgen.js +6 -3
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.d.ts +4 -1
- package/dist/utility.js +60 -0
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +10 -0
- package/package.json +1 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +15 -0
- package/project/.sdk/src/cmp/c/Main_c.ts +15 -0
- package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +371 -0
- package/project/.sdk/src/cmp/clojure/Config_clojure.ts +47 -1
- package/project/.sdk/src/cmp/clojure/Main_clojure.ts +11 -1
- package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +374 -0
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +40 -0
- package/project/.sdk/src/cmp/cpp/Main_cpp.ts +17 -0
- package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +441 -0
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +42 -1
- package/project/.sdk/src/cmp/csharp/Main_csharp.ts +18 -0
- package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +392 -0
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +36 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +15 -0
- package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +418 -0
- package/project/.sdk/src/cmp/go/Config_go.ts +12 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +14 -0
- package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +322 -0
- package/project/.sdk/src/cmp/java/Config_java.ts +20 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +14 -0
- package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +298 -0
- package/project/.sdk/src/cmp/js/Config_js.ts +12 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +6 -0
- package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +244 -0
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +23 -1
- package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +20 -0
- package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +440 -0
- package/project/.sdk/src/cmp/lua/Config_lua.ts +15 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +11 -0
- package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +364 -0
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +31 -1
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +19 -0
- package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +454 -0
- package/project/.sdk/src/cmp/perl/Config_perl.ts +30 -0
- package/project/.sdk/src/cmp/perl/Main_perl.ts +14 -0
- package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +387 -0
- package/project/.sdk/src/cmp/php/Config_php.ts +12 -1
- package/project/.sdk/src/cmp/php/Main_php.ts +7 -0
- package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +325 -0
- package/project/.sdk/src/cmp/py/Config_py.ts +12 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +12 -0
- package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +389 -0
- package/project/.sdk/src/cmp/rb/Config_rb.ts +18 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +10 -0
- package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +305 -0
- package/project/.sdk/src/cmp/rust/Config_rust.ts +41 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +16 -0
- package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +410 -0
- package/project/.sdk/src/cmp/scala/Config_scala.ts +21 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +14 -0
- package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +415 -0
- package/project/.sdk/src/cmp/swift/Config_swift.ts +37 -1
- package/project/.sdk/src/cmp/swift/Main_swift.ts +10 -0
- package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +439 -0
- package/project/.sdk/src/cmp/ts/Config_ts.ts +12 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +5 -0
- package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +237 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +33 -1
- package/project/.sdk/src/cmp/zig/Main_zig.ts +16 -0
- package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +545 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +31 -20
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +36 -40
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +11 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -32
- package/project/.sdk/tm/java/utility/MakeOptions.java +13 -1
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +14 -1
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +13 -46
- package/project/.sdk/tm/ocaml/Makefile +11 -1
- package/project/.sdk/tm/ocaml/sdk_helpers.ml +15 -0
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +31 -25
- package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +125 -12
- package/project/.sdk/tm/ocaml/test/t_pipeline.ml +64 -6
- package/project/.sdk/tm/perl/utility/make_options.pm +10 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +8 -1
- package/project/.sdk/tm/rust/utility/make_options.rs +13 -1
- package/project/.sdk/tm/scala/utility/Make.scala +14 -1
- package/project/.sdk/tm/scala/utility/Prepare.scala +16 -40
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +30 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +21 -39
- package/project/.sdk/tm/zig/core/utility.zig +37 -45
- package/project/.sdk/tm/zig/test/pipeline_test.zig +34 -0
- package/project/sdkgen-package.json +1 -1
- package/src/sdkgen.ts +4 -1
- package/src/utility.ts +75 -0
- package/project/.sdk/tm/c/utility/prepare_auth.c +0 -65
- package/project/.sdk/tm/csharp/utility/PrepareAuth.cs +0 -54
- package/project/.sdk/tm/go/utility/prepare_auth.go +0 -59
- package/project/.sdk/tm/java/utility/PrepareAuth.java +0 -66
- package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +0 -49
- package/project/.sdk/tm/lua/utility/prepare_auth.lua +0 -52
- package/project/.sdk/tm/perl/utility/prepare_auth.pm +0 -56
- package/project/.sdk/tm/php/utility/PrepareAuth.php +0 -45
- package/project/.sdk/tm/py/pkg/utility/prepare_auth.py +0 -46
- package/project/.sdk/tm/rb/utility/prepare_auth.rb +0 -35
- package/project/.sdk/tm/rust/utility/prepare_auth.rs +0 -65
- 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
|