@voxgig/sdkgen 4.30.2 → 4.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/ReadmeTop.js +6 -5
  3. package/dist/cmp/ReadmeTop.js.map +1 -1
  4. package/dist/helpers/definitionPlan.d.ts +27 -0
  5. package/dist/helpers/definitionPlan.js +219 -0
  6. package/dist/helpers/definitionPlan.js.map +1 -0
  7. package/dist/helpers/naming.d.ts +2 -1
  8. package/dist/helpers/naming.js +13 -4
  9. package/dist/helpers/naming.js.map +1 -1
  10. package/dist/sdkgen.d.ts +3 -2
  11. package/dist/sdkgen.js +6 -3
  12. package/dist/sdkgen.js.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +2 -2
  15. package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +5 -3
  16. package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +7 -5
  17. package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +5 -3
  18. package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +6 -4
  19. package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +8 -6
  20. package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +7 -9
  21. package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +5 -3
  22. package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +6 -4
  23. package/project/.sdk/src/cmp/js/ReadmeInstall_js.ts +22 -5
  24. package/project/.sdk/src/cmp/js/TestDefinition_js.ts +40 -0
  25. package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
  26. package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +5 -3
  27. package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +7 -3
  28. package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +7 -5
  29. package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +6 -3
  30. package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +5 -3
  31. package/project/.sdk/src/cmp/php/ReadmeInstall_php.ts +11 -5
  32. package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +5 -3
  33. package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +5 -3
  34. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +14 -4
  35. package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +5 -3
  36. package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +5 -3
  37. package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +5 -3
  38. package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +6 -4
  39. package/project/.sdk/src/cmp/ts/ReadmeInstall_ts.ts +10 -4
  40. package/project/.sdk/src/cmp/ts/TestDefinition_ts.ts +40 -0
  41. package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
  42. package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +5 -3
  43. package/project/.sdk/tm/c/utility/prepare_query.c +34 -2
  44. package/project/.sdk/tm/clojure/src/sdk/core.clj +20 -2
  45. package/project/.sdk/tm/cpp/utility/pipeline.hpp +31 -2
  46. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +32 -4
  47. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +35 -1
  48. package/project/.sdk/tm/go/utility/prepare_query.go +31 -1
  49. package/project/.sdk/tm/java/utility/PrepareQuery.java +29 -5
  50. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +16 -2
  51. package/project/.sdk/tm/js/test/definition-runner.js +161 -0
  52. package/project/.sdk/tm/js/test/pipeline.test.js +43 -3
  53. package/project/.sdk/tm/js/test/sdk-test-control.json +4 -1
  54. package/project/.sdk/tm/kotlin/utility/Prepare.kt +29 -5
  55. package/project/.sdk/tm/lua/utility/prepare_query.lua +31 -2
  56. package/project/.sdk/tm/ocaml/sdk_runtime.ml +22 -1
  57. package/project/.sdk/tm/perl/utility/prepare_query.pm +25 -2
  58. package/project/.sdk/tm/php/utility/PrepareQuery.php +28 -1
  59. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +23 -2
  60. package/project/.sdk/tm/rb/utility/prepare_query.rb +24 -2
  61. package/project/.sdk/tm/rust/utility/prepare_query.rs +34 -2
  62. package/project/.sdk/tm/scala/utility/Prepare.scala +31 -4
  63. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +23 -5
  64. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +16 -2
  65. package/project/.sdk/tm/ts/test/definition-runner.ts +179 -0
  66. package/project/.sdk/tm/ts/test/pipeline.test.ts +43 -3
  67. package/project/.sdk/tm/ts/test/sdk-test-control.json +4 -1
  68. package/project/.sdk/tm/ts/test/utility.ts +3 -2
  69. package/project/.sdk/tm/zig/core/utility.zig +29 -1
  70. package/project/sdkgen-package.json +1 -1
  71. package/src/cmp/ReadmeTop.ts +7 -6
  72. package/src/helpers/definitionPlan.ts +265 -0
  73. package/src/helpers/naming.ts +17 -4
  74. package/src/sdkgen.ts +4 -1
@@ -106,19 +106,21 @@ ${p} (if (= auth-prefix "") apikey-val (str auth-prefix " " apikey-val)
106
106
 
107
107
 
108
108
  function basicBlock(): string {
109
- return ` ;; True HTTP Basic Auth needs TWO credentials, base64-joined - a
110
- ;; single token in the header (the branch below) can never
109
+ return ` ;; True HTTP Basic Auth joins the two credentials, base64-encoded -
110
+ ;; a single token in the header (the branch below) can never
111
111
  ;; authenticate against an API that actually checks
112
- ;; \`Authorization: Basic base64(user:pass)\`.
112
+ ;; \`Authorization: Basic base64(user:pass)\`. The password may
113
+ ;; be empty (RFC 7617): Lob, for one, documents the key as the
114
+ ;; user with a blank password (\`curl -u key:\`).
113
115
  (if (= true (vs/getpath options "auth.basic"))
114
116
  (let [secret (vs/getprop options OPTION-SECRET NOT-FOUND)
115
117
  missing? (fn [v] (or (nil? v)
116
118
  (and (string? v) (or (= v NOT-FOUND) (= v "")))))]
117
- (if (or (missing? apikey) (missing? secret))
119
+ (if (missing? apikey)
118
120
  (vs/delprop headers HEADER-AUTH)
119
121
  (let [auth-prefix (or (vs/getpath options "auth.prefix") "")
120
122
  b64 (.encodeToString (java.util.Base64/getEncoder)
121
- (.getBytes ^String (str apikey ":" secret) "UTF-8"))]
123
+ (.getBytes ^String (str apikey ":" (if (missing? secret) "" secret)) "UTF-8"))]
122
124
  (.put ^java.util.Map headers HEADER-AUTH
123
125
  (if (= auth-prefix "") b64 (str auth-prefix " " b64))))))
124
126
  ${headerPlace(16)})`
@@ -165,9 +165,11 @@ ${clear(spec.where, 4)}
165
165
  `
166
166
 
167
167
  const basicBlock = !withBasic ? '' : `
168
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
168
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
169
169
  // token in the header (the branch below) can never authenticate against
170
170
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
171
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
172
+ // the user with a blank password (\`curl -u key:\`).
171
173
  if (is_true(Struct::getpath(options, {"auth", "basic"}))) {
172
174
  Value secret = getp(options, "secret", Value(NOT_FOUND));
173
175
 
@@ -178,13 +180,13 @@ ${clear(spec.where, 4)}
178
180
  noSecret = true;
179
181
  }
180
182
 
181
- if (skip || noSecret) {
183
+ if (skip) {
182
184
  ${clear(spec.where, 6)}
183
185
  } else {
184
186
  std::string authPrefix = as_str(Struct::getpath(options, {"auth", "prefix"}));
185
187
  std::string b64 = authBase64(
186
188
  (apikey.is_string() ? apikey.as_string() : "") + ":" +
187
- (secret.is_string() ? secret.as_string() : ""));
189
+ (!noSecret && secret.is_string() ? secret.as_string() : ""));
188
190
  if (authPrefix.empty()) {
189
191
  map_put(headers, CRED_NAME, Value(b64));
190
192
  } else {
@@ -92,10 +92,12 @@ function renderHeader(Name: string, cred: string, basic: boolean): string {
92
92
  private const string OptionSecret = "secret";` : ''
93
93
 
94
94
  const basicBlock = basic ? `
95
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a
95
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a
96
96
  // single token in the header (the branch below) can never
97
97
  // authenticate against an API that actually checks
98
- // \`Authorization: Basic base64(user:pass)\`.
98
+ // \`Authorization: Basic base64(user:pass)\`. The password may be
99
+ // empty (RFC 7617): Lob, for one, documents the key as the user with
100
+ // a blank password (\`curl -u key:\`).
99
101
  if (StructUtils.GetPath(options, StructUtils.Jt("auth", "basic")) is bool isBasic &&
100
102
  isBasic)
101
103
  {
@@ -106,7 +108,7 @@ function renderHeader(Name: string, cred: string, basic: boolean): string {
106
108
  var noSecret = secret == null ||
107
109
  (secret is string skStr && (skStr == NotFound || skStr == ""));
108
110
 
109
- if (noApikey || noSecret)
111
+ if (noApikey)
110
112
  {
111
113
  headers.Remove(HeaderAuth);
112
114
  }
@@ -118,7 +120,7 @@ function renderHeader(Name: string, cred: string, basic: boolean): string {
118
120
  basicPrefix = bp;
119
121
  }
120
122
  var b64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(
121
- (apikey as string ?? "") + ":" + (secret as string ?? "")));
123
+ (apikey as string ?? "") + ":" + (noSecret ? "" : secret as string ?? "")));
122
124
  headers[HeaderAuth] = basicPrefix == ""
123
125
  ? b64
124
126
  : basicPrefix + " " + b64;
@@ -139,21 +139,23 @@ ${bag}
139
139
  `
140
140
 
141
141
  const body = basicHere
142
- ? ` # True HTTP Basic Auth needs TWO credentials, base64-joined - a single
143
- # token in the header (the branch below) can never authenticate
144
- # against an API that actually checks
145
- # \`Authorization: Basic base64(user:pass)\`.
142
+ ? ` # True HTTP Basic Auth joins the two credentials, base64-encoded - a
143
+ # single token in the header (the branch below) can never
144
+ # authenticate against an API that actually checks
145
+ # \`Authorization: Basic base64(user:pass)\`. The password may be
146
+ # empty (RFC 7617): Lob, for one, documents the key as the user with
147
+ # a blank password (\`curl -u key:\`).
146
148
  if S.getpath(options, "auth.basic") == true do
147
149
  secret = S.getprop(options, @option_secret, @not_found)
148
150
  no_apikey = not is_binary(apikey) or apikey == @not_found or apikey == ""
149
151
  no_secret = not is_binary(secret) or secret == @not_found or secret == ""
150
152
 
151
- if no_apikey or no_secret do
153
+ if no_apikey do
152
154
  S.delprop(headers, @cred_name)
153
155
  else
154
156
  ap = S.getpath(options, "auth.prefix")
155
157
  auth_prefix = if is_binary(ap), do: ap, else: ""
156
- b64 = Base.encode64(apikey <> ":" <> secret)
158
+ b64 = Base.encode64(apikey <> ":" <> if(no_secret, do: "", else: secret))
157
159
  hv = if auth_prefix != "", do: auth_prefix <> " " <> b64, else: b64
158
160
  S.setprop(headers, @cred_name, hv)
159
161
  end
@@ -128,25 +128,23 @@ ${cookieHelper(spec.where)}func prepareAuthUtil(ctx *core.Context) (*core.Spec,
128
128
  `
129
129
 
130
130
  const basicBlock = !withBasic ? '' : `
131
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
131
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
132
132
  // token in the header (the branch below) can never authenticate against
133
133
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
134
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
135
+ // the user with a blank password (\`curl -u key:\`).
134
136
  if basicAuth, _ := vs.GetPath(options, []any{"auth", "basic"}).(bool); basicAuth {
135
137
  secret := vs.GetProp(options, optionSecret, notFound)
136
138
 
137
- noSecret := false
138
- if secret == nil {
139
- noSecret = true
140
- } else if secretStr, ok := secret.(string); ok &&
141
- (secretStr == notFound || secretStr == "") {
142
- noSecret = true
139
+ secretVal, _ := secret.(string)
140
+ if secretVal == notFound {
141
+ secretVal = ""
143
142
  }
144
143
 
145
- if skip || noSecret {
144
+ if skip {
146
145
  ${clear(spec.where)}
147
146
  } else {
148
147
  apikeyVal, _ := apikey.(string)
149
- secretVal, _ := secret.(string)
150
148
  b64 := base64.StdEncoding.EncodeToString([]byte(apikeyVal + ":" + secretVal))
151
149
 
152
150
  basicPrefix := ""
@@ -100,9 +100,11 @@ final class PrepareAuth {
100
100
  ].join('\n')
101
101
 
102
102
  const basicBlock = basicBranch ? `
103
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
103
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
104
104
  // token in the header (the branch below) can never authenticate against
105
105
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
106
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
107
+ // the user with a blank password (\`curl -u key:\`).
106
108
  if (Boolean.TRUE.equals(Struct.getpath(options, List.of("auth", "basic")))) {
107
109
  Object secret = Struct.getprop(options, OPTION_SECRET, NOT_FOUND);
108
110
  boolean noApikey = !(apikey instanceof String)
@@ -110,7 +112,7 @@ final class PrepareAuth {
110
112
  boolean noSecret = !(secret instanceof String)
111
113
  || NOT_FOUND.equals(secret) || "".equals(secret);
112
114
 
113
- if (noApikey || noSecret) {
115
+ if (noApikey) {
114
116
  headers.remove(CRED_NAME);
115
117
  }
116
118
  else {
@@ -120,7 +122,7 @@ final class PrepareAuth {
120
122
  basicPrefix = (String) bp;
121
123
  }
122
124
  String b64 = Base64.getEncoder().encodeToString(
123
- ((String) apikey + ":" + (String) secret).getBytes(StandardCharsets.UTF_8));
125
+ ((String) apikey + ":" + (noSecret ? "" : (String) secret)).getBytes(StandardCharsets.UTF_8));
124
126
  if ("".equals(basicPrefix)) {
125
127
  headers.put(CRED_NAME, b64);
126
128
  }
@@ -104,19 +104,21 @@ ${cookieHelper(spec.where)}
104
104
  // query parameter or a cookie, so the branch is emitted only where it
105
105
  // can mean something.
106
106
  const basicBlock = (spec.basic && 'header' === spec.where) ? `
107
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
107
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
108
108
  // token in the header (the branch below) can never authenticate against
109
109
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
110
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
111
+ // the user with a blank password (\`curl -u key:\`).
110
112
  if (true === options.auth.basic) {
111
113
  const secret = getprop(options, OPTION_secret, NOTFOUND)
112
114
  const noApikey = NOTFOUND === apikey || null == apikey || '' === apikey
113
- const noSecret = NOTFOUND === secret || null == secret || '' === secret
115
+ const pass = NOTFOUND === secret || null == secret ? '' : secret
114
116
 
115
- if (noApikey || noSecret) {
117
+ if (noApikey) {
116
118
  delprop(headers, CRED_name)
117
119
  }
118
120
  else {
119
- const b64 = Buffer.from(apikey + ':' + secret).toString('base64')
121
+ const b64 = Buffer.from(apikey + ':' + pass).toString('base64')
120
122
  setprop(headers, CRED_name,
121
123
  options.auth.prefix ? options.auth.prefix + ' ' + b64 : b64)
122
124
  }
@@ -1,16 +1,33 @@
1
1
 
2
- import { cmp, Content } from '@voxgig/sdkgen'
2
+ import { cmp, Content, isPublished, repoInfo } from '@voxgig/sdkgen'
3
3
 
4
4
 
5
5
  const ReadmeInstall = cmp(function ReadmeInstall(props: any) {
6
- const { target } = props
6
+ const { target, ctx$ } = props
7
+ const { model } = ctx$
7
8
 
8
- Content('```js')
9
- Content(`
9
+ if (isPublished(model, target.name)) {
10
+ Content('```js')
11
+ Content(`
10
12
  npm install ${target.module.name}
11
13
  `)
12
- Content('```')
14
+ Content('```')
15
+ return
16
+ }
13
17
 
18
+ // Publish pending, as in the ts target: a tag may not exist, so a clone is
19
+ // always offered. The js target needs no build.
20
+ const { releasesUrl, repoUrl, repo } = repoInfo(model)
21
+ Content(`This package is not yet published to npm. Install it from the GitHub
22
+ release tag (\`${target.name}/vX.Y.Z\`, see [Releases](${releasesUrl})), or from a
23
+ clone:
24
+
25
+ \`\`\`bash
26
+ git clone ${repoUrl}
27
+ npm install ./${repo}/${target.name}
28
+ \`\`\`
29
+
30
+ `)
14
31
  })
15
32
 
16
33
 
@@ -0,0 +1,40 @@
1
+ import { cmp, File, Content, definitionPlan } from '@voxgig/sdkgen'
2
+
3
+
4
+ // Every operation checked against the API definition, offline, in the SDK's
5
+ // own suite: see test/definition-runner. Mirrors the ts component.
6
+ const TestDefinition = cmp(function TestDefinition(props: any) {
7
+ const plan = definitionPlan(props.ctx$)
8
+ if (0 === plan.length) return
9
+
10
+ File({ name: 'definition.test.js' }, () => Content(`const { describe, test } = require('node:test')
11
+ const { SDK } = require('..')
12
+ const { runDefinitionPoint } = require('./definition-runner')
13
+ const { isControlSkipped } = require('./utility')
14
+
15
+
16
+ // Generated from the API definition, not from the model this SDK was built
17
+ // from: the route, the declared query parameters, the credential the security
18
+ // scheme names, and the definition's own response example.
19
+ const PLAN = ${JSON.stringify(plan, null, 2)}
20
+
21
+
22
+ describe('definition', () => {
23
+ for (const point of PLAN) {
24
+ test(point.entity + '.' + point.op + ' ' + point.method + ' ' + point.path, async (t) => {
25
+ const control = isControlSkipped('entityOp', point.entity + '.' + point.op, 'definition')
26
+ if (control.skip) {
27
+ t.skip(control.reason || 'skipped via sdk-test-control.json')
28
+ return
29
+ }
30
+ await runDefinitionPoint(SDK, point)
31
+ })
32
+ }
33
+ })
34
+ `))
35
+ })
36
+
37
+
38
+ export {
39
+ TestDefinition
40
+ }
@@ -8,6 +8,7 @@ import { cmp, each, Folder, entityCollection,
8
8
 
9
9
 
10
10
  import { TestLive } from './TestLive_js'
11
+ import { TestDefinition } from './TestDefinition_js'
11
12
  import { TestDirect } from './TestDirect_js'
12
13
  import { TestEntity } from './TestEntity_js'
13
14
 
@@ -21,6 +22,7 @@ const Test = cmp(function Test(props: any) {
21
22
  // Write-once: a project's edited control file survives regeneration.
22
23
  TestControl({ target, dir: 'test' })
23
24
  TestLive({ target })
25
+ TestDefinition({ target })
24
26
 
25
27
  Folder({ name: 'entity' }, () => {
26
28
  const entity = each(entityCollection(model))
@@ -156,9 +156,11 @@ function basicBlock(spec: AuthSpec): string {
156
156
  if (!spec.basic) return ''
157
157
 
158
158
  return `
159
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
159
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
160
160
  // token in the header (the branch below) can never authenticate against
161
161
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
162
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
163
+ // the user with a blank password (\`curl -u key:\`).
162
164
  val basicOpt = Struct.getpath(options, listOf("auth", "basic"))
163
165
  if (basicOpt is Boolean && basicOpt) {
164
166
  val secret = Struct.getprop(options, OPTION_SECRET, NOT_FOUND)
@@ -167,7 +169,7 @@ function basicBlock(spec: AuthSpec): string {
167
169
  val noSecret = secret == null ||
168
170
  (secret is String && (NOT_FOUND == secret || "" == secret))
169
171
 
170
- if (noApikey || noSecret) {
172
+ if (noApikey) {
171
173
  headers.remove(HEADER_AUTH)
172
174
  } else {
173
175
  var basicPrefix = ""
@@ -176,7 +178,7 @@ function basicBlock(spec: AuthSpec): string {
176
178
  basicPrefix = bp
177
179
  }
178
180
  val b64 = Base64.getEncoder().encodeToString(
179
- (apikey.toString() + ":" + secret.toString()).toByteArray(Charsets.UTF_8))
181
+ (apikey.toString() + ":" + (if (noSecret) "" else secret.toString())).toByteArray(Charsets.UTF_8))
180
182
  if ("" == basicPrefix) {
181
183
  headers[HEADER_AUTH] = b64
182
184
  } else {
@@ -189,15 +189,19 @@ function place(where: string): string {
189
189
 
190
190
 
191
191
  const BASIC = `
192
- -- True HTTP Basic Auth needs TWO credentials, base64-joined - a single
192
+ -- True HTTP Basic Auth joins the two credentials, base64-encoded - a single
193
193
  -- token in the header (the branch below) can never authenticate against
194
194
  -- an API that actually checks "Authorization: Basic base64(user:pass)".
195
+ -- The password may be empty (RFC 7617): Lob, for one, documents the key as
196
+ -- the user with a blank password ("curl -u key:").
195
197
  if vs.getpath(options, "auth.basic") == true then
196
198
  local secret = vs.getprop(options, OPTION_SECRET, NOT_FOUND)
199
+ if secret == nil or secret == NOT_FOUND then
200
+ secret = ""
201
+ end
197
202
 
198
- if apikey == nil or secret == nil
203
+ if apikey == nil
199
204
  or (type(apikey) == "string" and (apikey == NOT_FOUND or apikey == ""))
200
- or (type(secret) == "string" and (secret == NOT_FOUND or secret == ""))
201
205
  then
202
206
  headers[HEADER_AUTH] = nil
203
207
  else
@@ -255,20 +255,22 @@ let prepare_auth_util (ctx : ctx) : (spec option * sdk_error option) =
255
255
 
256
256
 
257
257
  const BASIC = ` if (match getpath_s options "auth.basic" with Bool b -> b | _ -> false) then begin
258
- (* True HTTP Basic Auth needs TWO credentials, base64-joined - a
259
- * single token in the header (the branch below) can never
258
+ (* True HTTP Basic Auth joins the two credentials, base64-encoded -
259
+ * a single token in the header (the branch below) can never
260
260
  * authenticate against an API that actually checks
261
- * \`Authorization: Basic base64(user:pass)\`. *)
261
+ * \`Authorization: Basic base64(user:pass)\`. The password may be
262
+ * empty (RFC 7617): Lob, for one, documents the key as the user
263
+ * with a blank password (\`curl -u key:\`). *)
262
264
  let secret = getprop ~alt:(Str not_found) options (Str option_secret) in
263
265
  let no_secret =
264
266
  (match secret with Str s -> s = not_found | _ -> false)
265
267
  || is_noval secret || secret = Str "" in
266
- if no_apikey || no_secret then
268
+ if no_apikey then
267
269
  ignore (delprop headers (Str cred_name))
268
270
  else begin
269
271
  let auth_prefix = match getpath_s options "auth.prefix" with Str s -> s | _ -> "" in
270
272
  let apikey_val = match apikey with Str s -> s | _ -> "" in
271
- let secret_val = match secret with Str s -> s | _ -> "" in
273
+ let secret_val = if no_secret then "" else match secret with Str s -> s | _ -> "" in
272
274
  let joined = base64_encode (apikey_val ^ ":" ^ secret_val) in
273
275
  setp headers cred_name
274
276
  (Str (if auth_prefix <> "" then auth_prefix ^ " " ^ joined else joined))
@@ -266,9 +266,11 @@ function basicBlock(spec: AuthSpec, wantBasic: boolean): string {
266
266
  }
267
267
 
268
268
  return `
269
- # True HTTP Basic Auth needs TWO credentials, base64-joined - a single
269
+ # True HTTP Basic Auth joins the two credentials, base64-encoded - a single
270
270
  # token in the header (the branch below) can never authenticate against
271
271
  # an API that actually checks \`Authorization: Basic base64(user:pass)\`.
272
+ # The password may be empty (RFC 7617): Lob, for one, documents the key as
273
+ # the user with a blank password (\`curl -u key:\`).
272
274
  if (${spec.Name}Helpers::is_true(${spec.Name}Helpers::gpath($options, 'auth.basic'))) {
273
275
  my $secret = Voxgig::Struct::getprop($options, $OPTION_SECRET, $NOT_FOUND);
274
276
 
@@ -279,7 +281,7 @@ function basicBlock(spec: AuthSpec, wantBasic: boolean): string {
279
281
  || Voxgig::Struct::is_jnull($secret)
280
282
  || (!ref $secret && ($secret eq $NOT_FOUND || $secret eq ''));
281
283
 
282
- if ($no_apikey || $no_secret) {
284
+ if ($no_apikey) {
283
285
  delete $headers->{$CRED_NAME};
284
286
  }
285
287
  else {
@@ -287,7 +289,8 @@ function basicBlock(spec: AuthSpec, wantBasic: boolean): string {
287
289
  $auth_prefix = '' unless defined $auth_prefix && !ref $auth_prefix;
288
290
  # '' as the eol: encode_base64 wraps at 76 columns by default, and a
289
291
  # newline inside a header value is not a header value.
290
- my $b64 = MIME::Base64::encode_base64("$apikey:$secret", '');
292
+ my $pass = $no_secret ? '' : $secret;
293
+ my $b64 = MIME::Base64::encode_base64("$apikey:$pass", '');
291
294
  $headers->{$CRED_NAME} =
292
295
  ('' eq $auth_prefix) ? $b64 : "$auth_prefix $b64";
293
296
  }
@@ -201,16 +201,18 @@ function basicBlock(spec: AuthSpec): string {
201
201
  }
202
202
 
203
203
  return `
204
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a
204
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a
205
205
  // single token in the header (the branch below) can never
206
206
  // authenticate against an API that actually checks
207
- // \`Authorization: Basic base64(user:pass)\`.
207
+ // \`Authorization: Basic base64(user:pass)\`. The password may be
208
+ // empty (RFC 7617): Lob, for one, documents the key as the user with
209
+ // a blank password (\`curl -u key:\`).
208
210
  if (true === (\\Voxgig\\Struct\\Struct::getpath($options, 'auth.basic') ?? false)) {
209
211
  $secret = \\Voxgig\\Struct\\Struct::getprop($options, self::OPTION_SECRET, self::NOT_FOUND);
210
212
  $apikey_val = is_string($apikey) && $apikey !== self::NOT_FOUND ? $apikey : '';
211
213
  $secret_val = is_string($secret) && $secret !== self::NOT_FOUND ? $secret : '';
212
214
 
213
- if ($apikey_val === '' || $secret_val === '') {
215
+ if ($apikey_val === '') {
214
216
  unset($headers[self::HEADER_AUTH]);
215
217
  } else {
216
218
  $auth_prefix = \\Voxgig\\Struct\\Struct::getpath($options, 'auth.prefix') ?? '';
@@ -1,5 +1,5 @@
1
1
 
2
- import { cmp, Content, installCommand, isPublished, repoInfo } from '@voxgig/sdkgen'
2
+ import { cmp, Content, installCommand, isPublished, packageName, repoInfo } from '@voxgig/sdkgen'
3
3
 
4
4
 
5
5
  const ReadmeInstall = cmp(function ReadmeInstall(props: any) {
@@ -15,12 +15,18 @@ ${installCommand(model, target.name)}
15
15
  return
16
16
  }
17
17
 
18
- // Publish pending: not yet on Packagist. Install from the git release tag.
19
- const { releasesUrl } = repoInfo(model)
18
+ // Publish pending: not yet on Packagist. Install from the git release tag,
19
+ // or from a clone as a Composer path repository, since a tag may not exist.
20
+ const { releasesUrl, repoUrl, repo } = repoInfo(model)
20
21
  Content(`This package is not yet published to Packagist. Install it from the
21
- GitHub release tag (\`${target.name}/vX.Y.Z\`):
22
+ GitHub release tag (\`${target.name}/vX.Y.Z\`, see [Releases](${releasesUrl})), or
23
+ from a clone as a Composer path repository:
22
24
 
23
- - Releases: [${releasesUrl}](${releasesUrl})
25
+ \`\`\`bash
26
+ git clone ${repoUrl}
27
+ composer config repositories.${repo} path ./${repo}/${target.name}
28
+ composer require ${packageName(model, target.name)}:@dev
29
+ \`\`\`
24
30
 
25
31
  `)
26
32
  })
@@ -140,9 +140,11 @@ function basicBlock(spec: AuthSpec): string {
140
140
  if (!spec.basic) return ''
141
141
 
142
142
  return `
143
- # True HTTP Basic Auth needs TWO credentials, base64-joined - a single
143
+ # True HTTP Basic Auth joins the two credentials, base64-encoded - a single
144
144
  # token in the header (the branch below) can never authenticate against
145
145
  # an API that actually checks \`Authorization: Basic base64(user:pass)\`.
146
+ # The password may be empty (RFC 7617): Lob, for one, documents the key as
147
+ # the user with a blank password (\`curl -u key:\`).
146
148
  if vs.getpath(options, "auth.basic") is True:
147
149
  secret = vs.getprop(options, OPTION_SECRET, NOT_FOUND)
148
150
  no_apikey = (
@@ -156,7 +158,7 @@ function basicBlock(spec: AuthSpec): string {
156
158
  or secret == ""
157
159
  )
158
160
 
159
- if no_apikey or no_secret:
161
+ if no_apikey:
160
162
  headers.pop(HEADER_AUTH, None)
161
163
  else:
162
164
  auth_prefix = ""
@@ -164,7 +166,7 @@ function basicBlock(spec: AuthSpec): string {
164
166
  if isinstance(ap, str):
165
167
  auth_prefix = ap
166
168
  b64 = base64.b64encode(
167
- (str(apikey) + ":" + str(secret)).encode("utf-8")
169
+ (str(apikey) + ":" + ("" if no_secret else str(secret))).encode("utf-8")
168
170
  ).decode("ascii")
169
171
  headers[HEADER_AUTH] = (
170
172
  auth_prefix + " " + b64 if auth_prefix else b64
@@ -124,21 +124,23 @@ end
124
124
  `
125
125
 
126
126
  const basicBlock = !basicHere ? '' : `
127
- # True HTTP Basic Auth needs TWO credentials, base64-joined - a single
127
+ # True HTTP Basic Auth joins the two credentials, base64-encoded - a single
128
128
  # token in the header (the branch below) can never authenticate against
129
129
  # an API that actually checks \`Authorization: Basic base64(user:pass)\`.
130
+ # The password may be empty (RFC 7617): Lob, for one, documents the key as
131
+ # the user with a blank password (\`curl -u key:\`).
130
132
  if VoxgigStruct.getpath(options, "auth.basic") == true
131
133
  secret = VoxgigStruct.getprop(options, OPTION_SECRET, NOT_FOUND)
132
134
  no_apikey = apikey.nil? || !apikey.is_a?(String) || apikey == NOT_FOUND || apikey == ""
133
135
  no_secret = secret.nil? || !secret.is_a?(String) || secret == NOT_FOUND || secret == ""
134
136
 
135
- if no_apikey || no_secret
137
+ if no_apikey
136
138
  headers.delete(HEADER_AUTH)
137
139
  else
138
140
  auth_prefix = VoxgigStruct.getpath(options, "auth.prefix") || ""
139
141
  # \`pack("m0")\` rather than \`Base64.strict_encode64\`: base64 left
140
142
  # Ruby's default gems in 3.4, and pack is core.
141
- b64 = ["#{apikey}:#{secret}"].pack("m0")
143
+ b64 = ["#{apikey}:#{no_secret ? "" : secret}"].pack("m0")
142
144
  headers[HEADER_AUTH] =
143
145
  auth_prefix.empty? ? b64 : "#{auth_prefix} #{b64}"
144
146
  end
@@ -27,12 +27,22 @@ bundle install
27
27
  return
28
28
  }
29
29
 
30
- // Publish pending: not yet on RubyGems. Install from the git release tag.
31
- const { releasesUrl } = repoInfo(model)
30
+ // Publish pending: not yet on RubyGems. Install from the git release tag,
31
+ // or from a clone through Bundler, since a tag may not exist.
32
+ const { releasesUrl, repoUrl, repo } = repoInfo(model)
32
33
  Content(`This package is not yet published to RubyGems. Install it from the
33
- GitHub release tag (\`${target.name}/vX.Y.Z\`):
34
+ GitHub release tag (\`${target.name}/vX.Y.Z\`, see [Releases](${releasesUrl})), or
35
+ from a clone:
34
36
 
35
- - Releases: [${releasesUrl}](${releasesUrl})
37
+ \`\`\`bash
38
+ git clone ${repoUrl}
39
+ \`\`\`
40
+
41
+ Then add it to your \`Gemfile\` by path, and run \`bundle install\`:
42
+
43
+ \`\`\`ruby
44
+ gem "${packageName(model, target.name)}", path: "./${repo}/${target.name}"
45
+ \`\`\`
36
46
 
37
47
  `)
38
48
  })
@@ -136,9 +136,11 @@ pub fn prepare_auth_util(ctx: &Rc<Context>) -> Result<Rc<RefCell<Spec>>, ${spec.
136
136
  `
137
137
 
138
138
  const basicBlock = !withBasic ? '' : `
139
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
139
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
140
140
  // token in the header (the branch below) can never authenticate against
141
141
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
142
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
143
+ // the user with a blank password (\`curl -u key:\`).
142
144
  if let Value::Bool(true) = getpath(&["auth", "basic"], &options) {
143
145
  let secret = vs::get_prop(&options, &Value::str(OPTION_SECRET), Value::str(NOT_FOUND));
144
146
 
@@ -148,7 +150,7 @@ pub fn prepare_auth_util(ctx: &Rc<Context>) -> Result<Rc<RefCell<Spec>>, ${spec.
148
150
  _ => false,
149
151
  };
150
152
 
151
- if skip || no_secret {
153
+ if skip {
152
154
  vs::del_prop(headers, &Value::str(CRED_NAME));
153
155
  } else {
154
156
  let apikey_val = match &apikey {
@@ -156,7 +158,7 @@ pub fn prepare_auth_util(ctx: &Rc<Context>) -> Result<Rc<RefCell<Spec>>, ${spec.
156
158
  _ => String::new(),
157
159
  };
158
160
  let secret_val = match &secret {
159
- Value::Str(s) => s.clone(),
161
+ Value::Str(s) if !no_secret => s.clone(),
160
162
  _ => String::new(),
161
163
  };
162
164
  let b64 = base64_encode(format!("{}:{}", apikey_val, secret_val).as_bytes());
@@ -146,22 +146,24 @@ function basicBlock(spec: AuthSpec): string {
146
146
  if (!spec.basic) return ''
147
147
 
148
148
  return `
149
- // True HTTP Basic Auth needs TWO credentials, base64-joined - a single
149
+ // True HTTP Basic Auth joins the two credentials, base64-encoded - a single
150
150
  // token in the header (the branch below) can never authenticate against
151
151
  // an API that actually checks \`Authorization: Basic base64(user:pass)\`.
152
+ // The password may be empty (RFC 7617): Lob, for one, documents the key as
153
+ // the user with a blank password (\`curl -u key:\`).
152
154
  if (java.lang.Boolean.TRUE == Struct.getpath(options, java.util.List.of("auth", "basic"))) {
153
155
  val secret = Struct.getprop(options, OPTION_SECRET, NOT_FOUND)
154
156
  val noApikey = apikey == null || (apikey match { case s: String => NOT_FOUND == s || "" == s; case _ => false })
155
157
  val noSecret = secret == null || (secret match { case s: String => NOT_FOUND == s || "" == s; case _ => false })
156
158
 
157
- if (noApikey || noSecret) {
159
+ if (noApikey) {
158
160
  headers.remove(CRED_NAME)
159
161
  } else {
160
162
  var basicPrefix = ""
161
163
  Struct.getpath(options, java.util.List.of("auth", "prefix")) match { case s: String => basicPrefix = s; case _ => }
162
164
  val b64 = Base64.getEncoder.encodeToString(
163
165
  ((apikey match { case s: String => s; case _ => "" }) + ":" +
164
- (secret match { case s: String => s; case _ => "" })).getBytes(StandardCharsets.UTF_8))
166
+ (if (noSecret) "" else secret match { case s: String => s; case _ => "" })).getBytes(StandardCharsets.UTF_8))
165
167
  if ("" == basicPrefix) headers.put(CRED_NAME, b64)
166
168
  else headers.put(CRED_NAME, basicPrefix + " " + b64)
167
169
  }