@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,415 @@
|
|
|
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
|
+
import { scalaPackage } from './utility_scala'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
// WHERE THE CREDENTIAL GOES IS A FACT ABOUT THE API, so it is generated
|
|
19
|
+
// rather than templated. The scala port of cmp/ts/PrepareAuth_ts.ts; see
|
|
20
|
+
// that file for the full account.
|
|
21
|
+
//
|
|
22
|
+
// scala had no prepare_auth template to delete. The logic was an `object
|
|
23
|
+
// PrepareAuth` EMBEDDED in `tm/scala/utility/Prepare.scala` alongside the
|
|
24
|
+
// six other prepare steps, hardcoding `val HEADER_AUTH = "authorization"`.
|
|
25
|
+
// apidef has always resolved the scheme's `in` and `name` into
|
|
26
|
+
// `main.kit.info.security` — joplin's says `in: "query", name: "token"` —
|
|
27
|
+
// and generation dropped both, so the SDK sent a header the API does not
|
|
28
|
+
// read and never sent the query parameter it does.
|
|
29
|
+
//
|
|
30
|
+
// EXTRACTED INTO ITS OWN FILE rather than generating the whole of
|
|
31
|
+
// Prepare.scala. A Scala `object` is not tied to a file name: the compiler
|
|
32
|
+
// resolves `PrepareAuth.prepareAuth` by PACKAGE, and every caller —
|
|
33
|
+
// utility/Register.scala's `u.prepareAuth = (ctx) => PrepareAuth
|
|
34
|
+
// .prepareAuth(ctx)`, which is the only binding — already names it
|
|
35
|
+
// unqualified from inside `package SCALAPACKAGE.utility`. So moving the
|
|
36
|
+
// object into `utility/PrepareAuth.scala` in the same package leaves
|
|
37
|
+
// Register, core/Utility.scala's `var prepareAuth` field, utility/MakeSpec
|
|
38
|
+
// .scala's `utility.prepareAuth(ctx)` and core/SdkClient.scala's call
|
|
39
|
+
// site all resolving exactly as before, with no edit to any of them. The
|
|
40
|
+
// other six objects in Prepare.scala (PreparePath, PrepareMethod,
|
|
41
|
+
// PrepareParams, PrepareQuery, PrepareHeaders, PrepareBody) are placement-
|
|
42
|
+
// independent and stay templated, so the generated surface is exactly the
|
|
43
|
+
// part that varies with the API.
|
|
44
|
+
//
|
|
45
|
+
// A template cannot fix this, because the three placements need three
|
|
46
|
+
// different bodies and a template has to pick one. A component emits the
|
|
47
|
+
// branch this API actually uses and nothing else — no dead query code in a
|
|
48
|
+
// bearer-token SDK, and no runtime `if` on a value that is fixed at
|
|
49
|
+
// generation time.
|
|
50
|
+
const PrepareAuth = cmp(async function PrepareAuth(props: any) {
|
|
51
|
+
const { target } = props
|
|
52
|
+
const { model } = props.ctx$
|
|
53
|
+
|
|
54
|
+
const where = resolveAuthIn(model)
|
|
55
|
+
|
|
56
|
+
// LOWERCASED FOR A HEADER, VERBATIM OTHERWISE. apidef writes
|
|
57
|
+
// `name: "Authorization"`, the deleted `object PrepareAuth` hardcoded
|
|
58
|
+
// `"authorization"`, and the whole scala runtime reads the lower-case
|
|
59
|
+
// key: the shared primary corpus asserts
|
|
60
|
+
// `ctx:spec:headers:authorization`, SecretsFeature.reauth rewrites
|
|
61
|
+
// `headers.put("authorization", ...)` in place on a token refresh, and
|
|
62
|
+
// spec.headers is a plain case-sensitive LinkedHashMap. Emitting
|
|
63
|
+
// `Authorization` verbatim would put the credential under a key nothing
|
|
64
|
+
// reads. A query parameter and a cookie ARE case-sensitive, so they keep
|
|
65
|
+
// the spec's spelling exactly.
|
|
66
|
+
const resolved = resolveAuthName(model)
|
|
67
|
+
const name = 'header' === where ? resolved.toLowerCase() : resolved
|
|
68
|
+
|
|
69
|
+
const basic = isHttpBasicAuth(model)
|
|
70
|
+
|
|
71
|
+
// Read so the resolution is visible at generation time even though the
|
|
72
|
+
// emitted code takes the prefix from options at runtime (the secrets
|
|
73
|
+
// feature rewrites it there).
|
|
74
|
+
const prefix = resolveAuthPrefix(model)
|
|
75
|
+
|
|
76
|
+
// NOT `isAuthActive`, AND THE DIFFERENCE IS LOAD-BEARING. See
|
|
77
|
+
// isAuthSuppressed in sdkgen's utility.ts: `isAuthActive` is ALSO false
|
|
78
|
+
// whenever `main.kit.info.auth` is false, which says only that the SPEC
|
|
79
|
+
// declared no security scheme — such an SDK still carries a credential,
|
|
80
|
+
// because Make.scala's optspec always declares `apikey` and fills
|
|
81
|
+
// `options.auth` from its `{ "prefix": "" }` default, so the runtime
|
|
82
|
+
// `options.get("auth") == null` guard never fires and every such SDK has
|
|
83
|
+
// always sent `options.apikey`. Emitting a no-op for those would
|
|
84
|
+
// silently remove working authentication and take the secrets feature
|
|
85
|
+
// with it (SecretsFeature resolves a secret into options.apikey and
|
|
86
|
+
// prepareAuth then places nothing).
|
|
87
|
+
//
|
|
88
|
+
// `main.kit.config.auth.active: false` is the project saying "this SDK
|
|
89
|
+
// sends no credential, ever" — the only signal that can honestly be
|
|
90
|
+
// honoured before runtime, so it is the only one used.
|
|
91
|
+
const active = !isAuthSuppressed(model)
|
|
92
|
+
|
|
93
|
+
const scalapackage = scalaPackage(model)
|
|
94
|
+
|
|
95
|
+
// FOLDER NESTING: exactly one segment, `utility`.
|
|
96
|
+
//
|
|
97
|
+
// Worked out from the deleted code's own path and from Main_scala's call
|
|
98
|
+
// site. Main_scala's blanket `Copy({ from: 'tm/scala' })` runs at the
|
|
99
|
+
// TARGET ROOT (its excludes and the scala tree are root-relative, and
|
|
100
|
+
// project.scala/Makefile sit beside core/, utility/, entity/), so
|
|
101
|
+
// `tm/scala/utility/Prepare.scala` lands at `<root>/utility/Prepare
|
|
102
|
+
// .scala`. Reproducing that path from Main's TOP LEVEL therefore needs
|
|
103
|
+
// `utility` and nothing above it — the same one-segment shape
|
|
104
|
+
// EntityBase_scala uses for `entity`.
|
|
105
|
+
//
|
|
106
|
+
// So this component must be invoked from Main_scala's top level and NOT
|
|
107
|
+
// from inside its `Folder({ name: 'core' })`, where Config and
|
|
108
|
+
// EntityTypes are written: nested there it would emit
|
|
109
|
+
// `core/utility/PrepareAuth.scala`, whose `package <pkg>.utility`
|
|
110
|
+
// declaration contradicts its directory, while Register.scala kept
|
|
111
|
+
// binding the stale object. There is no `src` folder in a scala SDK at
|
|
112
|
+
// all, so the ts port's `src/src/utility/` failure has no scala twin —
|
|
113
|
+
// the scala twin would be `core/`.
|
|
114
|
+
Folder({ name: 'utility' }, () => {
|
|
115
|
+
File({ name: 'PrepareAuth.' + target.ext }, () => {
|
|
116
|
+
Content(render({ scalapackage, active, where, name, prefix, basic }))
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
type AuthSpec = {
|
|
123
|
+
scalapackage: string
|
|
124
|
+
active: boolean
|
|
125
|
+
where: string
|
|
126
|
+
name: string
|
|
127
|
+
prefix: string
|
|
128
|
+
basic: boolean
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
function render(spec: AuthSpec): string {
|
|
133
|
+
// The package line alone. Each placement emits its OWN import block,
|
|
134
|
+
// because they need different ones and the scala templates put the
|
|
135
|
+
// java.* imports first - Prepare.scala, Make.scala and Spec.scala all
|
|
136
|
+
// read `import java.util...` then the SDK's own packages.
|
|
137
|
+
const head = `package ${spec.scalapackage}.utility
|
|
138
|
+
|
|
139
|
+
`
|
|
140
|
+
|
|
141
|
+
// NO AUTH AT ALL - the project switched it off explicitly (see `active`
|
|
142
|
+
// above for why only an EXPLICIT switch counts). The SDK gets a
|
|
143
|
+
// prepareAuth that is honest about it rather than one that removes a
|
|
144
|
+
// header nobody set; Struct is not imported, because nothing here reads
|
|
145
|
+
// an option.
|
|
146
|
+
if (!spec.active) {
|
|
147
|
+
return head + `import ${spec.scalapackage}.core.{Context, Spec}
|
|
148
|
+
|
|
149
|
+
// This SDK is configured with authentication off
|
|
150
|
+
// (main.kit.config.auth.active: false), so there is no credential to
|
|
151
|
+
// place. The object stays in the pipeline because Register binds
|
|
152
|
+
// prepareAuth unconditionally and MakeSpec calls it on every request.
|
|
153
|
+
object PrepareAuth {
|
|
154
|
+
|
|
155
|
+
def prepareAuth(ctx: Context): Spec = {
|
|
156
|
+
val spec = ctx.spec
|
|
157
|
+
if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
158
|
+
|
|
159
|
+
spec
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if ('query' === spec.where) return renderQuery(spec, head)
|
|
166
|
+
if ('cookie' === spec.where) return renderCookie(spec, head)
|
|
167
|
+
|
|
168
|
+
return renderHeader(spec, head)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
// HEADER. Behaviourally the deleted `object PrepareAuth` when the scheme
|
|
173
|
+
// resolves to the defaults (header / Authorization): the same option names,
|
|
174
|
+
// the same `skip` idiom for a missing credential, the same auth_no_spec
|
|
175
|
+
// error, the same empty-prefix rule. Only the constant's VALUE moves with
|
|
176
|
+
// the model, plus the HTTP Basic block, which is emitted only for a basic
|
|
177
|
+
// scheme.
|
|
178
|
+
function renderHeader(spec: AuthSpec, head: string): string {
|
|
179
|
+
return head + `${spec.basic ? `import java.nio.charset.StandardCharsets
|
|
180
|
+
import java.util.Base64
|
|
181
|
+
|
|
182
|
+
` : ''}import ${spec.scalapackage}.core.{Context, Spec}
|
|
183
|
+
import ${spec.scalapackage}.utility.struct.Struct
|
|
184
|
+
|
|
185
|
+
// Places the credential as the \`${spec.name}\` request header.
|
|
186
|
+
// GENERATED from the API's security scheme (main.kit.info.security:
|
|
187
|
+
// in: header, name: ${spec.name}${spec.basic ? ', http basic' : ''}).
|
|
188
|
+
object PrepareAuth {
|
|
189
|
+
val CRED_NAME = "${scalastr(spec.name)}"
|
|
190
|
+
val OPTION_APIKEY = "apikey"
|
|
191
|
+
${spec.basic ? ` val OPTION_SECRET = "secret"\n` : ''} val NOT_FOUND = "__NOTFOUND__"
|
|
192
|
+
|
|
193
|
+
def prepareAuth(ctx: Context): Spec = {
|
|
194
|
+
val spec = ctx.spec
|
|
195
|
+
if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
196
|
+
|
|
197
|
+
val headers = spec.headers
|
|
198
|
+
val options = ctx.client.optionsMap()
|
|
199
|
+
|
|
200
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
201
|
+
if (options.get("auth") == null) {
|
|
202
|
+
headers.remove(CRED_NAME)
|
|
203
|
+
return spec
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
207
|
+
${basicBlock(spec)}
|
|
208
|
+
var skip = false
|
|
209
|
+
if (apikey == null) skip = true
|
|
210
|
+
else apikey match {
|
|
211
|
+
case s: String if NOT_FOUND == s || "" == s => skip = true
|
|
212
|
+
case _ =>
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (skip) {
|
|
216
|
+
headers.remove(CRED_NAME)
|
|
217
|
+
} else {
|
|
218
|
+
var authPrefix = ""
|
|
219
|
+
Struct.getpath(options, java.util.List.of("auth", "prefix")) match { case s: String => authPrefix = s; case _ => }
|
|
220
|
+
val apikeyVal = apikey match { case s: String => s; case _ => "" }
|
|
221
|
+
// A raw credential (empty prefix, e.g. an apiKey scheme) must go in
|
|
222
|
+
// as-is; only a non-empty prefix (Bearer/Basic/OAuth) is space-joined.
|
|
223
|
+
if ("" == authPrefix) headers.put(CRED_NAME, apikeyVal)
|
|
224
|
+
else headers.put(CRED_NAME, authPrefix + " " + apikeyVal)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
spec
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
`
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
// HTTP Basic is header-only by definition: the scheme is `Authorization:
|
|
235
|
+
// Basic base64(user:pass)`. It cannot be expressed as a query parameter or
|
|
236
|
+
// a cookie, so renderQuery and renderCookie never call this - and it is
|
|
237
|
+
// emitted only when the model says the scheme IS basic, so an ordinary
|
|
238
|
+
// bearer SDK carries no dead code and regenerates unchanged.
|
|
239
|
+
function basicBlock(spec: AuthSpec): string {
|
|
240
|
+
if (!spec.basic) return ''
|
|
241
|
+
|
|
242
|
+
return `
|
|
243
|
+
// True HTTP Basic Auth needs TWO credentials, base64-joined - a single
|
|
244
|
+
// token in the header (the branch below) can never authenticate against
|
|
245
|
+
// an API that actually checks \`Authorization: Basic base64(user:pass)\`.
|
|
246
|
+
if (java.lang.Boolean.TRUE == Struct.getpath(options, java.util.List.of("auth", "basic"))) {
|
|
247
|
+
val secret = Struct.getprop(options, OPTION_SECRET, NOT_FOUND)
|
|
248
|
+
val noApikey = apikey == null || (apikey match { case s: String => NOT_FOUND == s || "" == s; case _ => false })
|
|
249
|
+
val noSecret = secret == null || (secret match { case s: String => NOT_FOUND == s || "" == s; case _ => false })
|
|
250
|
+
|
|
251
|
+
if (noApikey || noSecret) {
|
|
252
|
+
headers.remove(CRED_NAME)
|
|
253
|
+
} else {
|
|
254
|
+
var basicPrefix = ""
|
|
255
|
+
Struct.getpath(options, java.util.List.of("auth", "prefix")) match { case s: String => basicPrefix = s; case _ => }
|
|
256
|
+
val b64 = Base64.getEncoder.encodeToString(
|
|
257
|
+
((apikey match { case s: String => s; case _ => "" }) + ":" +
|
|
258
|
+
(secret match { case s: String => s; case _ => "" })).getBytes(StandardCharsets.UTF_8))
|
|
259
|
+
if ("" == basicPrefix) headers.put(CRED_NAME, b64)
|
|
260
|
+
else headers.put(CRED_NAME, basicPrefix + " " + b64)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return spec
|
|
264
|
+
}
|
|
265
|
+
`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
// QUERY. The credential is a query parameter, so it goes in spec.query and
|
|
270
|
+
// the headers are never touched. This is the placement joplin, pipedrive,
|
|
271
|
+
// trello and lm-umbrella declare and no generated SDK ever emitted.
|
|
272
|
+
function renderQuery(spec: AuthSpec, head: string): string {
|
|
273
|
+
return head + `import ${spec.scalapackage}.core.{Context, Spec}
|
|
274
|
+
import ${spec.scalapackage}.utility.struct.Struct
|
|
275
|
+
|
|
276
|
+
// Places the credential as the \`${spec.name}\` query parameter.
|
|
277
|
+
// GENERATED from the API's security scheme (main.kit.info.security:
|
|
278
|
+
// in: query, name: ${spec.name}).
|
|
279
|
+
object PrepareAuth {
|
|
280
|
+
val CRED_NAME = "${scalastr(spec.name)}"
|
|
281
|
+
val OPTION_APIKEY = "apikey"
|
|
282
|
+
val NOT_FOUND = "__NOTFOUND__"
|
|
283
|
+
|
|
284
|
+
def prepareAuth(ctx: Context): Spec = {
|
|
285
|
+
val spec = ctx.spec
|
|
286
|
+
if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
287
|
+
|
|
288
|
+
val query = spec.query
|
|
289
|
+
val options = ctx.client.optionsMap()
|
|
290
|
+
|
|
291
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
292
|
+
if (options.get("auth") == null) {
|
|
293
|
+
query.remove(CRED_NAME)
|
|
294
|
+
return spec
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
298
|
+
|
|
299
|
+
var skip = false
|
|
300
|
+
if (apikey == null) skip = true
|
|
301
|
+
else apikey match {
|
|
302
|
+
case s: String if NOT_FOUND == s || "" == s => skip = true
|
|
303
|
+
case _ =>
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (skip) {
|
|
307
|
+
query.remove(CRED_NAME)
|
|
308
|
+
} else {
|
|
309
|
+
val apikeyVal = apikey match { case s: String => s; case _ => "" }
|
|
310
|
+
// NO PREFIX IN A QUERY STRING. \`?${spec.name}=Bearer%20abc\` is not a
|
|
311
|
+
// thing any API reads: the prefix is a header convention, so the
|
|
312
|
+
// options auth.prefix is dropped here deliberately rather than
|
|
313
|
+
// silently concatenated.
|
|
314
|
+
query.put(CRED_NAME, apikeyVal)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
spec
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
// COOKIE. A cookie IS a header, so the credential rides the header bag -
|
|
325
|
+
// but the `cookie` header is SHARED with whatever cookies the caller set,
|
|
326
|
+
// so the pair is spliced in and out rather than the header assigned over.
|
|
327
|
+
// Splicing also makes this idempotent: a retried request cannot end up
|
|
328
|
+
// carrying the credential twice.
|
|
329
|
+
function renderCookie(spec: AuthSpec, head: string): string {
|
|
330
|
+
return head + `import java.util.{Map => JMap}
|
|
331
|
+
|
|
332
|
+
import ${spec.scalapackage}.core.{Context, Spec}
|
|
333
|
+
import ${spec.scalapackage}.utility.struct.Struct
|
|
334
|
+
|
|
335
|
+
// Places the credential as the \`${spec.name}\` cookie.
|
|
336
|
+
// GENERATED from the API's security scheme (main.kit.info.security:
|
|
337
|
+
// in: cookie, name: ${spec.name}).
|
|
338
|
+
object PrepareAuth {
|
|
339
|
+
val COOKIE_HEADER = "cookie"
|
|
340
|
+
val CRED_NAME = "${scalastr(spec.name)}"
|
|
341
|
+
val OPTION_APIKEY = "apikey"
|
|
342
|
+
val NOT_FOUND = "__NOTFOUND__"
|
|
343
|
+
|
|
344
|
+
// The cookie header minus our own pair, every other cookie untouched.
|
|
345
|
+
private def without(headers: JMap[String, Object]): String = {
|
|
346
|
+
val existing = headers.get(COOKIE_HEADER) match { case s: String => s; case _ => "" }
|
|
347
|
+
if ("" == existing) return ""
|
|
348
|
+
|
|
349
|
+
val kept = existing.split(";").map(_.trim).filter { piece =>
|
|
350
|
+
"" != piece && CRED_NAME != piece && !piece.startsWith(CRED_NAME + "=")
|
|
351
|
+
}
|
|
352
|
+
kept.mkString("; ")
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Set (a value) or remove (null) our pair, leaving the rest in place.
|
|
356
|
+
private def place(headers: JMap[String, Object], value: String): Unit = {
|
|
357
|
+
val rest = without(headers)
|
|
358
|
+
|
|
359
|
+
if (value == null) {
|
|
360
|
+
if ("" == rest) headers.remove(COOKIE_HEADER)
|
|
361
|
+
else headers.put(COOKIE_HEADER, rest)
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
val pair = CRED_NAME + "=" + value
|
|
366
|
+
if ("" == rest) headers.put(COOKIE_HEADER, pair)
|
|
367
|
+
else headers.put(COOKIE_HEADER, rest + "; " + pair)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
def prepareAuth(ctx: Context): Spec = {
|
|
371
|
+
val spec = ctx.spec
|
|
372
|
+
if (spec == null) throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
|
|
373
|
+
|
|
374
|
+
val headers = spec.headers
|
|
375
|
+
val options = ctx.client.optionsMap()
|
|
376
|
+
|
|
377
|
+
// Public APIs that need no auth omit the options.auth block entirely.
|
|
378
|
+
if (options.get("auth") == null) {
|
|
379
|
+
place(headers, null)
|
|
380
|
+
return spec
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
|
|
384
|
+
|
|
385
|
+
var skip = false
|
|
386
|
+
if (apikey == null) skip = true
|
|
387
|
+
else apikey match {
|
|
388
|
+
case s: String if NOT_FOUND == s || "" == s => skip = true
|
|
389
|
+
case _ =>
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (skip) {
|
|
393
|
+
place(headers, null)
|
|
394
|
+
} else {
|
|
395
|
+
val apikeyVal = apikey match { case s: String => s; case _ => "" }
|
|
396
|
+
// NO PREFIX IN A COOKIE either - a cookie carries a bare
|
|
397
|
+
// \`name=value\` pair, not a header's scheme-prefixed credential.
|
|
398
|
+
place(headers, apikeyVal)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
spec
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
`
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
function scalastr(s: string): string {
|
|
409
|
+
return String(s).replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
export {
|
|
414
|
+
PrepareAuth
|
|
415
|
+
}
|
|
@@ -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
|
|
|
@@ -37,7 +39,41 @@ const Config = cmp(async function Config(props: any) {
|
|
|
37
39
|
// into main.slug / main.version / main.target (the three station
|
|
38
40
|
// descriptor identity fields, station design §4); swift has only the
|
|
39
41
|
// JSON-literal rep, so that one call covers every rep this target emits.
|
|
40
|
-
|
|
42
|
+
//
|
|
43
|
+
// `def` rather than the helper's own `json`, because `in` and `name` are
|
|
44
|
+
// added to the auth block below and the JSON has to be re-serialised over
|
|
45
|
+
// that. When nothing is added, `JSON.stringify(def)` IS the helper's
|
|
46
|
+
// `json` - same object, same key order - so a header SDK's config is
|
|
47
|
+
// byte-identical to what it was.
|
|
48
|
+
const { def } = configDefinition(model, target.name)
|
|
49
|
+
|
|
50
|
+
// `in` and `name` TRAVEL WITH THE PREFIX NOW. apidef resolved both from
|
|
51
|
+
// the spec's securityScheme all along (main.kit.info.security - joplin's
|
|
52
|
+
// says `in: "query", name: "token"`) and generation dropped them, so an
|
|
53
|
+
// apiKey-in-query API got an Authorization header it does not read.
|
|
54
|
+
//
|
|
55
|
+
// Emitted ONLY when they differ from the header/Authorization defaults, so
|
|
56
|
+
// every header-based SDK regenerates byte-identical. The generated
|
|
57
|
+
// prepareAuth bakes the placement in at generation time (see
|
|
58
|
+
// PrepareAuth_swift); these carry it in the config so a running SDK, and
|
|
59
|
+
// anything reading its descriptor, can say what its scheme actually is.
|
|
60
|
+
//
|
|
61
|
+
// THE OPTSPEC HAS TO NAME THEM TOO. `main.kit.optspec.auth` does, but
|
|
62
|
+
// swift does not read the model for its option spec - buildOptSpec() in
|
|
63
|
+
// tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift is written out
|
|
64
|
+
// by hand - so both were added there in the same change. Without that this
|
|
65
|
+
// block is inert at best: makeOptions merges the config's options and
|
|
66
|
+
// validates the result against that spec, and a key the spec does not name
|
|
67
|
+
// is dropped or rejected.
|
|
68
|
+
const authIn = resolveAuthIn(model)
|
|
69
|
+
const authName = resolveAuthName(model)
|
|
70
|
+
const authOpt: any = (def as any)?.options?.auth
|
|
71
|
+
if (null != authOpt) {
|
|
72
|
+
if ('header' !== authIn) authOpt.in = authIn
|
|
73
|
+
if ('Authorization' !== authName) authOpt.name = authName
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const json = JSON.stringify(def)
|
|
41
77
|
|
|
42
78
|
// Model-data defaults may carry the ProjectName placeholder (e.g. the
|
|
43
79
|
// clienttrack clientName); resolve it to the API name so the embedded JSON
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
|
|
24
24
|
import { Package } from './Package_swift'
|
|
25
25
|
import { Config } from './Config_swift'
|
|
26
|
+
import { PrepareAuth } from './PrepareAuth_swift'
|
|
26
27
|
import { Gitignore } from './Gitignore_swift'
|
|
27
28
|
import { MainEntity } from './MainEntity_swift'
|
|
28
29
|
import { SdkError } from './SdkError_swift'
|
|
@@ -167,6 +168,15 @@ const Main = cmp(async function Main(props: any) {
|
|
|
167
168
|
})
|
|
168
169
|
})
|
|
169
170
|
|
|
171
|
+
// utility/PrepareAuth.swift — GENERATED, NOT COPIED. Where the credential
|
|
172
|
+
// goes (header, query or cookie, and under what name) is a fact about the
|
|
173
|
+
// API, and tm/ can only hold one answer; the function was extracted from
|
|
174
|
+
// the copied utility/Prepare.swift, which no longer declares it. Called at
|
|
175
|
+
// THIS level, not inside the `core` folder above: the component opens the
|
|
176
|
+
// whole `Sources/<Name>Sdk/utility` path itself, exactly as EntityTypes
|
|
177
|
+
// does for `entity`. See PrepareAuth_swift.
|
|
178
|
+
PrepareAuth({ target })
|
|
179
|
+
|
|
170
180
|
// entity/<Name>Types.swift — documentary typed models (one struct per entity
|
|
171
181
|
// + per op). Compiles with the SwiftPM target; nothing consumes it yet.
|
|
172
182
|
EntityTypes({ target })
|