@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,410 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
Content,
|
|
4
|
+
File,
|
|
5
|
+
Folder,
|
|
6
|
+
cmp,
|
|
7
|
+
isAuthSuppressed,
|
|
8
|
+
isHttpBasicAuth,
|
|
9
|
+
resolveAuthIn,
|
|
10
|
+
resolveAuthName,
|
|
11
|
+
resolveAuthPrefix,
|
|
12
|
+
} from '@voxgig/sdkgen'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
// WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
|
|
16
|
+
// rather than templated. This is the rust peer of PrepareAuth_ts; read that
|
|
17
|
+
// one first, it carries the full account of the defect.
|
|
18
|
+
//
|
|
19
|
+
// This was a static file at `tm/rust/utility/prepare_auth.rs` that hardcoded
|
|
20
|
+
//
|
|
21
|
+
// const HEADER_AUTH: &str = "authorization";
|
|
22
|
+
//
|
|
23
|
+
// apidef has always resolved the scheme's `in` and `name` into
|
|
24
|
+
// `main.kit.info.security` — joplin's says `in: "query", name: "token"` —
|
|
25
|
+
// and generation dropped both, so the SDK sent a header the API does not
|
|
26
|
+
// read and never sent the query parameter it does.
|
|
27
|
+
//
|
|
28
|
+
// A template cannot fix this, because the three placements need three
|
|
29
|
+
// different bodies and a template has to pick one. A component emits the
|
|
30
|
+
// branch this API actually uses and nothing else — no dead query code in a
|
|
31
|
+
// bearer-token SDK, and no runtime `if` on a value that is fixed at
|
|
32
|
+
// generation time. Rust makes that worth doing carefully: an import the
|
|
33
|
+
// emitted body never reaches is a warning on every build of the crate, so
|
|
34
|
+
// the `use` lines are chosen per placement too.
|
|
35
|
+
const PrepareAuth = cmp(async function PrepareAuth(props: any) {
|
|
36
|
+
const { target } = props
|
|
37
|
+
const { model } = props.ctx$
|
|
38
|
+
|
|
39
|
+
// The generated source cannot use the `ProjectName` placeholder: that is
|
|
40
|
+
// rewritten by Main's `Copy({from:'tm/rust'})`, and this file never
|
|
41
|
+
// travels through that Copy. The real error type goes in here, spelled
|
|
42
|
+
// the way Main_rust spells it in lib.rs's re-exports.
|
|
43
|
+
const errtype = model.const.Name + 'Error'
|
|
44
|
+
|
|
45
|
+
// `!isAuthSuppressed`, NOT `isAuthActive`. The latter is also false when
|
|
46
|
+
// the SPEC merely declares no security scheme (`main.kit.info.auth:
|
|
47
|
+
// false`), and those SDKs still carry a credential: optspec always
|
|
48
|
+
// declares `apikey` and makeOptions fills `options.auth` from its
|
|
49
|
+
// defaults, so the runtime guard never fired and they have always sent
|
|
50
|
+
// it. Only an explicit `main.kit.config.auth.active: false` means "no
|
|
51
|
+
// credential, ever", which is what isAuthSuppressed reads.
|
|
52
|
+
const active = !isAuthSuppressed(model)
|
|
53
|
+
const where = resolveAuthIn(model)
|
|
54
|
+
const name = resolveAuthName(model)
|
|
55
|
+
// Resolved, and deliberately NOT baked into the source: the prefix is a
|
|
56
|
+
// runtime option (`options.auth.prefix`), and the template this replaces
|
|
57
|
+
// read it from the options map so a caller could override it per client.
|
|
58
|
+
// Only the PLACEMENT is fixed at generation time.
|
|
59
|
+
const prefix = resolveAuthPrefix(model)
|
|
60
|
+
const basic = isHttpBasicAuth(model)
|
|
61
|
+
|
|
62
|
+
// FOLDER NESTING. Main_rust opens NO folder around this call: the rust
|
|
63
|
+
// crate root IS the target root (lib.rs, core/, feature/, utility/ all sit
|
|
64
|
+
// there — see the `[lib] path` in tm/rust/Cargo.toml), the template this
|
|
65
|
+
// replaces lived at `tm/rust/utility/prepare_auth.rs`, and
|
|
66
|
+
// `Copy({from:'tm/rust'})` lands it at `<root>/utility/`. So the `utility`
|
|
67
|
+
// folder is opened HERE, exactly as EntityBase_rust opens `entity`.
|
|
68
|
+
//
|
|
69
|
+
// The call site matters as much. Config_rust runs inside
|
|
70
|
+
// `Folder({name:'core'})`, and putting PrepareAuth beside it would write
|
|
71
|
+
// `core/utility/prepare_auth.rs` — a path no module declares, so rustc
|
|
72
|
+
// never compiles it, while `utility/mod.rs`'s `pub mod prepare_auth;`
|
|
73
|
+
// keeps binding whatever `utility/` actually holds. Main_rust calls this
|
|
74
|
+
// at ROOT level.
|
|
75
|
+
//
|
|
76
|
+
// The file NAME is not free either: `utility/mod.rs` declares the module
|
|
77
|
+
// `prepare_auth`, and `make_spec` calls
|
|
78
|
+
// `crate::utility::prepare_auth::prepare_auth_util`. The generated file
|
|
79
|
+
// has to answer to that name.
|
|
80
|
+
Folder({ name: 'utility' }, () => {
|
|
81
|
+
File({ name: 'prepare_auth.' + target.ext }, () => {
|
|
82
|
+
Content(render({ errtype, active, where, name, prefix, basic }))
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
type AuthSpec = {
|
|
89
|
+
errtype: string
|
|
90
|
+
active: boolean
|
|
91
|
+
where: string
|
|
92
|
+
name: string
|
|
93
|
+
prefix: string
|
|
94
|
+
basic: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
function render(spec: AuthSpec): string {
|
|
99
|
+
|
|
100
|
+
// NO AUTH AT ALL. A public API's SDK gets a prepare_auth that is honest
|
|
101
|
+
// about it rather than one that deletes a header nobody set. `vs`, `Value`
|
|
102
|
+
// and the helpers are left out of the imports deliberately — rustc warns
|
|
103
|
+
// on an unused import, on every build, forever.
|
|
104
|
+
if (!spec.active) {
|
|
105
|
+
return `// prepare_auth utility.
|
|
106
|
+
//
|
|
107
|
+
// GENERATED, not templated - see PrepareAuth_rust.
|
|
108
|
+
//
|
|
109
|
+
// This API declares no authentication, so there is no credential to place.
|
|
110
|
+
// The function stays in the pipeline because make_spec calls it
|
|
111
|
+
// unconditionally.
|
|
112
|
+
|
|
113
|
+
use std::cell::RefCell;
|
|
114
|
+
use std::rc::Rc;
|
|
115
|
+
|
|
116
|
+
use crate::core::context::Context;
|
|
117
|
+
use crate::core::error::${spec.errtype};
|
|
118
|
+
use crate::core::spec::Spec;
|
|
119
|
+
|
|
120
|
+
pub fn prepare_auth_util(ctx: &Rc<Context>) -> Result<Rc<RefCell<Spec>>, ${spec.errtype}> {
|
|
121
|
+
let spec = ctx.spec.borrow().clone().ok_or_else(|| {
|
|
122
|
+
ctx.make_error("auth_no_spec", "Expected context spec property to be defined.")
|
|
123
|
+
})?;
|
|
124
|
+
|
|
125
|
+
Ok(spec)
|
|
126
|
+
}
|
|
127
|
+
`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// HTTP Basic is header-only by definition: the scheme is
|
|
131
|
+
// `Authorization: Basic base64(user:pass)`. It cannot be expressed as a
|
|
132
|
+
// query parameter or a cookie, so the branch — and the base64 encoder it
|
|
133
|
+
// needs, which the rust core does not otherwise ship — is emitted only
|
|
134
|
+
// where it can mean something.
|
|
135
|
+
const withBasic = spec.basic && 'header' === spec.where
|
|
136
|
+
|
|
137
|
+
// `getpath` reads `options.auth.prefix`, and only a header placement has a
|
|
138
|
+
// prefix to read: a query parameter drops it, and a cookie pair has no
|
|
139
|
+
// room for one.
|
|
140
|
+
const withGetpath = 'header' === spec.where
|
|
141
|
+
|
|
142
|
+
const bag = bagName(spec.where)
|
|
143
|
+
|
|
144
|
+
const head = `// prepare_auth utility.
|
|
145
|
+
//
|
|
146
|
+
// GENERATED, not templated: where the credential goes - header, query or
|
|
147
|
+
// cookie, and under what name - is a fact about THIS API, and tm/ can only
|
|
148
|
+
// hold one answer. See PrepareAuth_rust.
|
|
149
|
+
|
|
150
|
+
use std::cell::RefCell;
|
|
151
|
+
use std::rc::Rc;
|
|
152
|
+
|
|
153
|
+
use crate::core::context::Context;
|
|
154
|
+
use crate::core::error::${spec.errtype};
|
|
155
|
+
use crate::core::helpers::{getp, ${withGetpath ? 'getpath, ' : ''}setp};
|
|
156
|
+
use crate::core::spec::Spec;
|
|
157
|
+
use crate::utility::voxgigstruct as vs;
|
|
158
|
+
use crate::utility::voxgigstruct::Value;
|
|
159
|
+
|
|
160
|
+
${credComment(spec.where)}
|
|
161
|
+
const CRED_NAME: &str = "${ruststr(credLiteral(spec.where, spec.name))}";
|
|
162
|
+
${'cookie' === spec.where ? `const COOKIE_HEADER: &str = "cookie";
|
|
163
|
+
` : ''}const OPTION_APIKEY: &str = "apikey";
|
|
164
|
+
${withBasic ? `const OPTION_SECRET: &str = "secret";
|
|
165
|
+
` : ''}const NOT_FOUND: &str = "__NOTFOUND__";
|
|
166
|
+
|
|
167
|
+
pub fn prepare_auth_util(ctx: &Rc<Context>) -> Result<Rc<RefCell<Spec>>, ${spec.errtype}> {
|
|
168
|
+
let spec = ctx.spec.borrow().clone().ok_or_else(|| {
|
|
169
|
+
ctx.make_error("auth_no_spec", "Expected context spec property to be defined.")
|
|
170
|
+
})?;
|
|
171
|
+
|
|
172
|
+
let ${bag} = spec.borrow().${bag}.clone();
|
|
173
|
+
let options = match ctx.client.borrow().clone() {
|
|
174
|
+
Some(client) => client.options_map(),
|
|
175
|
+
None => ctx.options.borrow().clone(),
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
179
|
+
let auth = getp(&options, "auth");
|
|
180
|
+
if auth.is_noval() || auth.is_null() {
|
|
181
|
+
${clear(spec.where)};
|
|
182
|
+
return Ok(spec);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let apikey = vs::get_prop(&options, &Value::str(OPTION_APIKEY), Value::str(NOT_FOUND));
|
|
186
|
+
|
|
187
|
+
let skip = match &apikey {
|
|
188
|
+
Value::Noval | Value::Null => true,
|
|
189
|
+
Value::Str(s) => s == NOT_FOUND || s.is_empty(),
|
|
190
|
+
_ => false,
|
|
191
|
+
};
|
|
192
|
+
`
|
|
193
|
+
|
|
194
|
+
const basicBlock = !withBasic ? '' : `
|
|
195
|
+
// True HTTP Basic Auth needs TWO credentials, base64-joined - a single
|
|
196
|
+
// token in the header (the branch below) can never authenticate against
|
|
197
|
+
// an API that actually checks \`Authorization: Basic base64(user:pass)\`.
|
|
198
|
+
if let Value::Bool(true) = getpath(&["auth", "basic"], &options) {
|
|
199
|
+
let secret = vs::get_prop(&options, &Value::str(OPTION_SECRET), Value::str(NOT_FOUND));
|
|
200
|
+
|
|
201
|
+
let no_secret = match &secret {
|
|
202
|
+
Value::Noval | Value::Null => true,
|
|
203
|
+
Value::Str(s) => s == NOT_FOUND || s.is_empty(),
|
|
204
|
+
_ => false,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if skip || no_secret {
|
|
208
|
+
vs::del_prop(headers, &Value::str(CRED_NAME));
|
|
209
|
+
} else {
|
|
210
|
+
let apikey_val = match &apikey {
|
|
211
|
+
Value::Str(s) => s.clone(),
|
|
212
|
+
_ => String::new(),
|
|
213
|
+
};
|
|
214
|
+
let secret_val = match &secret {
|
|
215
|
+
Value::Str(s) => s.clone(),
|
|
216
|
+
_ => String::new(),
|
|
217
|
+
};
|
|
218
|
+
let b64 = base64_encode(format!("{}:{}", apikey_val, secret_val).as_bytes());
|
|
219
|
+
|
|
220
|
+
let auth_prefix = match getpath(&["auth", "prefix"], &options) {
|
|
221
|
+
Value::Str(s) => s,
|
|
222
|
+
_ => String::new(),
|
|
223
|
+
};
|
|
224
|
+
// Empty prefix (raw apiKey credential) must not add a leading space.
|
|
225
|
+
if auth_prefix.is_empty() {
|
|
226
|
+
setp(&headers, CRED_NAME, Value::str(b64));
|
|
227
|
+
} else {
|
|
228
|
+
setp(
|
|
229
|
+
&headers,
|
|
230
|
+
CRED_NAME,
|
|
231
|
+
Value::str(format!("{} {}", auth_prefix, b64)),
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return Ok(spec);
|
|
237
|
+
}
|
|
238
|
+
`
|
|
239
|
+
|
|
240
|
+
const tail = `
|
|
241
|
+
if skip {
|
|
242
|
+
${clear(spec.where)};
|
|
243
|
+
} else {
|
|
244
|
+
${place(spec.where)}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
Ok(spec)
|
|
248
|
+
}
|
|
249
|
+
`
|
|
250
|
+
|
|
251
|
+
// The encoder goes AFTER prepare_auth_util, so the function a reader opens
|
|
252
|
+
// this file for is the first one they meet.
|
|
253
|
+
const encoder = !withBasic ? '' : `
|
|
254
|
+
/// Standard base64, for the \`Authorization: Basic base64(user:pass)\` value.
|
|
255
|
+
///
|
|
256
|
+
/// In-tree because the crate takes no dependency for it: the secrets
|
|
257
|
+
/// feature's base64 DECODER lives behind a feature module this utility
|
|
258
|
+
/// cannot reach, and adding a crate for twenty lines would put a dependency
|
|
259
|
+
/// into every SDK whose API happens to use HTTP Basic.
|
|
260
|
+
fn base64_encode(input: &[u8]) -> String {
|
|
261
|
+
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
262
|
+
|
|
263
|
+
let mut out = String::with_capacity((input.len() + 2) / 3 * 4);
|
|
264
|
+
|
|
265
|
+
for chunk in input.chunks(3) {
|
|
266
|
+
// \`chunks\` hands back a 1- or 2-byte tail for input that is not a
|
|
267
|
+
// multiple of three; the missing bytes read as zero and are padded out
|
|
268
|
+
// below.
|
|
269
|
+
let b0 = chunk[0] as usize;
|
|
270
|
+
let b1 = *chunk.get(1).unwrap_or(&0) as usize;
|
|
271
|
+
let b2 = *chunk.get(2).unwrap_or(&0) as usize;
|
|
272
|
+
|
|
273
|
+
out.push(ALPHABET[b0 >> 2] as char);
|
|
274
|
+
out.push(ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)] as char);
|
|
275
|
+
// The tail is padded, never truncated: a decoder that counts groups
|
|
276
|
+
// of four needs the '=' to know how many bytes came back.
|
|
277
|
+
out.push(if 1 < chunk.len() {
|
|
278
|
+
ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)] as char
|
|
279
|
+
} else {
|
|
280
|
+
'='
|
|
281
|
+
});
|
|
282
|
+
out.push(if 2 < chunk.len() {
|
|
283
|
+
ALPHABET[b2 & 0x3f] as char
|
|
284
|
+
} else {
|
|
285
|
+
'='
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
out
|
|
290
|
+
}
|
|
291
|
+
`
|
|
292
|
+
|
|
293
|
+
return head + basicBlock + tail + encoder
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
// The credential's key, as it goes into the bag.
|
|
298
|
+
//
|
|
299
|
+
// A HEADER name is lowercased. HTTP header names are case-insensitive
|
|
300
|
+
// (RFC 9110 5.1) and the fetcher writes them out as given, so nothing
|
|
301
|
+
// changes on the wire — but this SDK's own header map is keyed in lowercase
|
|
302
|
+
// throughout (`content-type`, and the `authorization` that tm/rust/tests/
|
|
303
|
+
// pipeline_test.rs asserts on), and the shipped `prepare_auth.rs` said
|
|
304
|
+
// `"authorization"`. Emitting the resolver's title-cased default here would
|
|
305
|
+
// have left every header SDK's own test suite failing on a purely cosmetic
|
|
306
|
+
// difference.
|
|
307
|
+
//
|
|
308
|
+
// A QUERY parameter and a COOKIE name are case-SENSITIVE, so those go in
|
|
309
|
+
// verbatim: `?token=` is not `?Token=`.
|
|
310
|
+
function credLiteral(where: string, name: string): string {
|
|
311
|
+
return 'header' === where ? String(name).toLowerCase() : String(name)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
function credComment(where: string): string {
|
|
316
|
+
if ('query' === where) {
|
|
317
|
+
return '/// The query parameter this API reads the credential from.'
|
|
318
|
+
}
|
|
319
|
+
if ('cookie' === where) {
|
|
320
|
+
return '/// The cookie name this API reads the credential from.'
|
|
321
|
+
}
|
|
322
|
+
return '/// The header this API reads the credential from.'
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
// The bag the credential lands in, per placement — and, in rust, the `Spec`
|
|
327
|
+
// field it is read from, which is spelled the same. Cookies ride the header
|
|
328
|
+
// bag because a cookie IS a header.
|
|
329
|
+
function bagName(where: string): string {
|
|
330
|
+
return 'query' === where ? 'query' : 'headers'
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
function clear(where: string): string {
|
|
335
|
+
// COOKIE clears the header named for the credential, NOT the cookie
|
|
336
|
+
// header: the cookie header may carry pairs this SDK never set, and
|
|
337
|
+
// deleting it to remove one pair would drop them all. Removing a header
|
|
338
|
+
// that placement never writes is a no-op, which is the honest outcome
|
|
339
|
+
// when there is no credential to place.
|
|
340
|
+
return `vs::del_prop(${bagName(where)}, &Value::str(CRED_NAME))`
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
function place(where: string): string {
|
|
345
|
+
if ('query' === where) {
|
|
346
|
+
// NO PREFIX IN A QUERY STRING. `?token=Bearer%20abc` is not a thing any
|
|
347
|
+
// API reads; the prefix is a header convention and is dropped here
|
|
348
|
+
// deliberately rather than silently concatenated.
|
|
349
|
+
return ` let apikey_val = match &apikey {
|
|
350
|
+
Value::Str(s) => s.clone(),
|
|
351
|
+
_ => String::new(),
|
|
352
|
+
};
|
|
353
|
+
// No prefix: a query parameter carries the raw credential.
|
|
354
|
+
setp(&query, CRED_NAME, Value::str(apikey_val));`
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if ('cookie' === where) {
|
|
358
|
+
// Append, never replace: the cookie header may already carry pairs this
|
|
359
|
+
// SDK did not set, and clobbering it would drop them.
|
|
360
|
+
return ` let apikey_val = match &apikey {
|
|
361
|
+
Value::Str(s) => s.clone(),
|
|
362
|
+
_ => String::new(),
|
|
363
|
+
};
|
|
364
|
+
let pair = format!("{}={}", CRED_NAME, apikey_val);
|
|
365
|
+
// APPEND: the cookie header may already carry pairs this SDK did not
|
|
366
|
+
// set, and replacing it outright would drop them.
|
|
367
|
+
let existing = match getp(&headers, COOKIE_HEADER) {
|
|
368
|
+
Value::Str(s) => s,
|
|
369
|
+
_ => String::new(),
|
|
370
|
+
};
|
|
371
|
+
if existing.is_empty() {
|
|
372
|
+
setp(&headers, COOKIE_HEADER, Value::str(pair));
|
|
373
|
+
} else {
|
|
374
|
+
setp(
|
|
375
|
+
&headers,
|
|
376
|
+
COOKIE_HEADER,
|
|
377
|
+
Value::str(format!("{}; {}", existing, pair)),
|
|
378
|
+
);
|
|
379
|
+
}`
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return ` let auth_prefix = match getpath(&["auth", "prefix"], &options) {
|
|
383
|
+
Value::Str(s) => s,
|
|
384
|
+
_ => String::new(),
|
|
385
|
+
};
|
|
386
|
+
let apikey_val = match &apikey {
|
|
387
|
+
Value::Str(s) => s.clone(),
|
|
388
|
+
_ => String::new(),
|
|
389
|
+
};
|
|
390
|
+
// Empty prefix (raw apiKey credential) must not add a leading space.
|
|
391
|
+
if auth_prefix.is_empty() {
|
|
392
|
+
setp(&headers, CRED_NAME, Value::str(apikey_val));
|
|
393
|
+
} else {
|
|
394
|
+
setp(
|
|
395
|
+
&headers,
|
|
396
|
+
CRED_NAME,
|
|
397
|
+
Value::str(format!("{} {}", auth_prefix, apikey_val)),
|
|
398
|
+
);
|
|
399
|
+
}`
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
function ruststr(s: string): string {
|
|
404
|
+
return String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
export {
|
|
409
|
+
PrepareAuth
|
|
410
|
+
}
|
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
cmp,
|
|
6
6
|
each,
|
|
7
7
|
isAuthActive,
|
|
8
|
+
isHttpBasicAuth,
|
|
9
|
+
resolveAuthIn,
|
|
10
|
+
resolveAuthName,
|
|
8
11
|
resolveAuthPrefix,
|
|
9
12
|
targetFeatures,
|
|
10
13
|
configDefinition,
|
|
@@ -84,6 +87,13 @@ const Config = cmp(async function Config(props: any) {
|
|
|
84
87
|
|
|
85
88
|
const authActive = isAuthActive(model)
|
|
86
89
|
const authPrefix = resolveAuthPrefix(model)
|
|
90
|
+
const authBasic = isHttpBasicAuth(model)
|
|
91
|
+
// WHERE the credential goes and under what name, as apidef resolved them
|
|
92
|
+
// from the spec's security scheme. Emitted below only when they DIFFER
|
|
93
|
+
// from the defaults, so every header-based SDK's Config.scala stays
|
|
94
|
+
// byte-identical.
|
|
95
|
+
const authIn = resolveAuthIn(model)
|
|
96
|
+
const authName = resolveAuthName(model)
|
|
87
97
|
|
|
88
98
|
let baseUrl = ''
|
|
89
99
|
try { baseUrl = getModelPath(model, `main.${KIT}.info.servers.0.url`) } catch (_e) { }
|
|
@@ -102,7 +112,17 @@ const Config = cmp(async function Config(props: any) {
|
|
|
102
112
|
base: baseUrl,
|
|
103
113
|
}
|
|
104
114
|
if (authActive) {
|
|
105
|
-
|
|
115
|
+
const auth: Record<string, any> = { prefix: authPrefix }
|
|
116
|
+
// ONLY WHEN THEY DIFFER FROM THE DEFAULT. `in: 'header'` and
|
|
117
|
+
// `name: 'Authorization'` are what resolveAuthIn/resolveAuthName answer
|
|
118
|
+
// for a spec that says nothing, and what the generated
|
|
119
|
+
// utility/PrepareAuth.scala assumes, so emitting them would be pure
|
|
120
|
+
// payload AND would move every existing header SDK's Config.scala. An
|
|
121
|
+
// apiKey-in-query API (joplin's `?token=`) is the case that needs them.
|
|
122
|
+
if (authBasic) auth.basic = true
|
|
123
|
+
if ('header' !== authIn) auth.in = authIn
|
|
124
|
+
if ('Authorization' !== authName) auth.name = authName
|
|
125
|
+
options.auth = auth
|
|
106
126
|
}
|
|
107
127
|
options.headers = headers
|
|
108
128
|
options.entity = optionsEntity
|
|
@@ -27,6 +27,7 @@ import { Gitignore } from './Gitignore_scala'
|
|
|
27
27
|
import { MainEntity } from './MainEntity_scala'
|
|
28
28
|
import { EntityBase } from './EntityBase_scala'
|
|
29
29
|
import { EntityTypes } from './EntityTypes_scala'
|
|
30
|
+
import { PrepareAuth } from './PrepareAuth_scala'
|
|
30
31
|
import { scalaPackage } from './utility_scala'
|
|
31
32
|
|
|
32
33
|
|
|
@@ -160,6 +161,19 @@ const Main = cmp(async function Main(props: any) {
|
|
|
160
161
|
// Shared entity runtime (entity/EntityBase.scala).
|
|
161
162
|
EntityBase({ target })
|
|
162
163
|
|
|
164
|
+
// utility/PrepareAuth.scala. WHERE the credential goes (header, query or
|
|
165
|
+
// cookie) and under what name is a fact about THIS API, and
|
|
166
|
+
// tm/scala/utility/Prepare.scala can only hold one answer - which is why
|
|
167
|
+
// its `object PrepareAuth` was removed and this generates the object
|
|
168
|
+
// instead. See PrepareAuth_scala.
|
|
169
|
+
//
|
|
170
|
+
// CALLED AT THE TOP LEVEL, NOT INSIDE `Folder({ name: 'core' })` below:
|
|
171
|
+
// the component opens `utility` itself, reproducing the path the blanket
|
|
172
|
+
// Copy above gives every other tm/scala/utility file. Nested in core/ it
|
|
173
|
+
// would write core/utility/PrepareAuth.scala, contradicting its own
|
|
174
|
+
// package declaration while Register.scala bound nothing.
|
|
175
|
+
PrepareAuth({ target })
|
|
176
|
+
|
|
163
177
|
// Generate the client class and config in core/.
|
|
164
178
|
Folder({ name: 'core' }, () => {
|
|
165
179
|
|