@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.
Files changed (106) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/sdkgen.d.ts +2 -2
  3. package/dist/sdkgen.js +6 -3
  4. package/dist/sdkgen.js.map +1 -1
  5. package/dist/tsconfig.tsbuildinfo +1 -1
  6. package/dist/utility.d.ts +4 -1
  7. package/dist/utility.js +60 -0
  8. package/dist/utility.js.map +1 -1
  9. package/model/sdkgen.aon +10 -0
  10. package/package.json +1 -1
  11. package/project/.sdk/src/cmp/c/Config_c.ts +15 -0
  12. package/project/.sdk/src/cmp/c/Main_c.ts +15 -0
  13. package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +371 -0
  14. package/project/.sdk/src/cmp/clojure/Config_clojure.ts +47 -1
  15. package/project/.sdk/src/cmp/clojure/Main_clojure.ts +11 -1
  16. package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +374 -0
  17. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +40 -0
  18. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +17 -0
  19. package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +441 -0
  20. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +42 -1
  21. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +18 -0
  22. package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +392 -0
  23. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +36 -1
  24. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +15 -0
  25. package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +418 -0
  26. package/project/.sdk/src/cmp/go/Config_go.ts +12 -1
  27. package/project/.sdk/src/cmp/go/Main_go.ts +14 -0
  28. package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +322 -0
  29. package/project/.sdk/src/cmp/java/Config_java.ts +20 -1
  30. package/project/.sdk/src/cmp/java/Main_java.ts +14 -0
  31. package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +298 -0
  32. package/project/.sdk/src/cmp/js/Config_js.ts +12 -1
  33. package/project/.sdk/src/cmp/js/Main_js.ts +6 -0
  34. package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +244 -0
  35. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +23 -1
  36. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +20 -0
  37. package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +440 -0
  38. package/project/.sdk/src/cmp/lua/Config_lua.ts +15 -1
  39. package/project/.sdk/src/cmp/lua/Main_lua.ts +11 -0
  40. package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +364 -0
  41. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +31 -1
  42. package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +19 -0
  43. package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +454 -0
  44. package/project/.sdk/src/cmp/perl/Config_perl.ts +30 -0
  45. package/project/.sdk/src/cmp/perl/Main_perl.ts +14 -0
  46. package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +387 -0
  47. package/project/.sdk/src/cmp/php/Config_php.ts +12 -1
  48. package/project/.sdk/src/cmp/php/Main_php.ts +7 -0
  49. package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +325 -0
  50. package/project/.sdk/src/cmp/py/Config_py.ts +12 -1
  51. package/project/.sdk/src/cmp/py/Main_py.ts +12 -0
  52. package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +389 -0
  53. package/project/.sdk/src/cmp/rb/Config_rb.ts +18 -1
  54. package/project/.sdk/src/cmp/rb/Main_rb.ts +10 -0
  55. package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +305 -0
  56. package/project/.sdk/src/cmp/rust/Config_rust.ts +41 -1
  57. package/project/.sdk/src/cmp/rust/Main_rust.ts +16 -0
  58. package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +410 -0
  59. package/project/.sdk/src/cmp/scala/Config_scala.ts +21 -1
  60. package/project/.sdk/src/cmp/scala/Main_scala.ts +14 -0
  61. package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +415 -0
  62. package/project/.sdk/src/cmp/swift/Config_swift.ts +37 -1
  63. package/project/.sdk/src/cmp/swift/Main_swift.ts +10 -0
  64. package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +439 -0
  65. package/project/.sdk/src/cmp/ts/Config_ts.ts +12 -1
  66. package/project/.sdk/src/cmp/ts/Main_ts.ts +5 -0
  67. package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +237 -0
  68. package/project/.sdk/src/cmp/zig/Config_zig.ts +33 -1
  69. package/project/.sdk/src/cmp/zig/Main_zig.ts +16 -0
  70. package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +545 -0
  71. package/project/.sdk/tm/clojure/src/sdk/core.clj +31 -20
  72. package/project/.sdk/tm/cpp/utility/pipeline.hpp +36 -40
  73. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +11 -0
  74. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -32
  75. package/project/.sdk/tm/java/utility/MakeOptions.java +13 -1
  76. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +14 -1
  77. package/project/.sdk/tm/kotlin/utility/Prepare.kt +13 -46
  78. package/project/.sdk/tm/ocaml/Makefile +11 -1
  79. package/project/.sdk/tm/ocaml/sdk_helpers.ml +15 -0
  80. package/project/.sdk/tm/ocaml/sdk_runtime.ml +31 -25
  81. package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +125 -12
  82. package/project/.sdk/tm/ocaml/test/t_pipeline.ml +64 -6
  83. package/project/.sdk/tm/perl/utility/make_options.pm +10 -1
  84. package/project/.sdk/tm/php/utility/MakeOptions.php +8 -1
  85. package/project/.sdk/tm/rust/utility/make_options.rs +13 -1
  86. package/project/.sdk/tm/scala/utility/Make.scala +14 -1
  87. package/project/.sdk/tm/scala/utility/Prepare.scala +16 -40
  88. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +30 -3
  89. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +21 -39
  90. package/project/.sdk/tm/zig/core/utility.zig +37 -45
  91. package/project/.sdk/tm/zig/test/pipeline_test.zig +34 -0
  92. package/project/sdkgen-package.json +1 -1
  93. package/src/sdkgen.ts +4 -1
  94. package/src/utility.ts +75 -0
  95. package/project/.sdk/tm/c/utility/prepare_auth.c +0 -65
  96. package/project/.sdk/tm/csharp/utility/PrepareAuth.cs +0 -54
  97. package/project/.sdk/tm/go/utility/prepare_auth.go +0 -59
  98. package/project/.sdk/tm/java/utility/PrepareAuth.java +0 -66
  99. package/project/.sdk/tm/js/src/utility/PrepareAuthUtility.js +0 -49
  100. package/project/.sdk/tm/lua/utility/prepare_auth.lua +0 -52
  101. package/project/.sdk/tm/perl/utility/prepare_auth.pm +0 -56
  102. package/project/.sdk/tm/php/utility/PrepareAuth.php +0 -45
  103. package/project/.sdk/tm/py/pkg/utility/prepare_auth.py +0 -46
  104. package/project/.sdk/tm/rb/utility/prepare_auth.rb +0 -35
  105. package/project/.sdk/tm/rust/utility/prepare_auth.rs +0 -65
  106. package/project/.sdk/tm/ts/src/utility/PrepareAuthUtility.ts +0 -78
@@ -15,6 +15,19 @@
15
15
 
16
16
  #include "../core/types.hpp"
17
17
 
18
+ // prepareAuth is GENERATED, not templated: WHERE the credential goes -
19
+ // header, query or cookie, and under what name - is a fact about THIS API
20
+ // (apidef resolves it into main.kit.info.security), and this file can only
21
+ // hold one answer. It used to hold `authorization`, so an apiKey-in-query
22
+ // API got a header it does not read. The component is
23
+ // src/cmp/cpp/PrepareAuth_cpp.ts; the emitted header defines
24
+ // `sdk::util::prepareAuth` exactly as this file used to, and register_all
25
+ // below still binds it.
26
+ //
27
+ // Included HERE, outside the namespace: the generated header opens its own
28
+ // `namespace sdk { namespace util {`.
29
+ #include "prepare_auth.hpp"
30
+
18
31
  namespace sdk {
19
32
  namespace util {
20
33
 
@@ -1018,45 +1031,11 @@ inline std::string preparePath(CtxPtr ctx) {
1018
1031
  }
1019
1032
 
1020
1033
  // ---- prepareAuth ------------------------------------------------------
1021
-
1022
- inline SpecPtr prepareAuth(CtxPtr ctx) {
1023
- SpecPtr spec = ctx->spec;
1024
- if (!spec) throw ctx->makeError("auth_no_spec", "Expected context spec property to be defined.");
1025
-
1026
- static const std::string HEADER_AUTH = "authorization";
1027
- static const std::string NOT_FOUND = "__NOTFOUND__";
1028
-
1029
- Value headers = spec->headers;
1030
- Value options = ctx->client->optionsMap();
1031
-
1032
- if (is_nullish(getp(options, "auth"))) {
1033
- map_remove(headers, HEADER_AUTH);
1034
- return spec;
1035
- }
1036
-
1037
- Value apikey = getp(options, "apikey", Value(NOT_FOUND));
1038
-
1039
- bool skip = false;
1040
- if (is_nullish(apikey)) {
1041
- skip = true;
1042
- } else if (apikey.is_string() && (apikey.as_string() == NOT_FOUND || apikey.as_string().empty())) {
1043
- skip = true;
1044
- }
1045
-
1046
- if (skip) {
1047
- map_remove(headers, HEADER_AUTH);
1048
- } else {
1049
- std::string authPrefix = as_str(Struct::getpath(options, {"auth", "prefix"}));
1050
- std::string apikeyVal = apikey.is_string() ? apikey.as_string() : "";
1051
- if (authPrefix.empty()) {
1052
- map_put(headers, HEADER_AUTH, Value(apikeyVal));
1053
- } else {
1054
- map_put(headers, HEADER_AUTH, Value(authPrefix + " " + apikeyVal));
1055
- }
1056
- }
1057
-
1058
- return spec;
1059
- }
1034
+ //
1035
+ // GENERATED into utility/prepare_auth.hpp, included at the top of this file
1036
+ // (see the note there). `util::prepareAuth` keeps its name, its signature
1037
+ // and its binding in register_all below; only the three-way choice of WHERE
1038
+ // the credential goes moved out, because a template cannot make it.
1060
1039
 
1061
1040
  // ---- transformRequest -------------------------------------------------
1062
1041
 
@@ -1079,10 +1058,27 @@ inline Value transformRequest(CtxPtr ctx) {
1079
1058
  inline const char* OPTSPEC_JSON() {
1080
1059
  return "{"
1081
1060
  "\"apikey\": \"\","
1061
+ // The SECOND credential, for the HTTP Basic scheme
1062
+ // (base64(apikey:secret)). main.kit.optspec names it and this copy did
1063
+ // not, and validate DROPS a top-level key this spec does not name - so a
1064
+ // caller that supplied `secret` never had it reach prepareAuth.
1065
+ "\"secret\": \"\","
1082
1066
  "\"base\": \"http://localhost:8000\","
1083
1067
  "\"prefix\": \"\","
1084
1068
  "\"suffix\": \"\","
1085
- "\"auth\": { \"prefix\": \"\" },"
1069
+ // WHERE the credential goes and under what name, plus the HTTP Basic
1070
+ // switch. `Struct::validate` SILENTLY DROPS a key this spec does not
1071
+ // declare - it does not reject it - so without these three the generated
1072
+ // config's own auth block was trimmed back to `prefix` on the way in and
1073
+ // the SDK could not see its own scheme. MEASURED, not assumed: a client
1074
+ // built from a Basic-scheme model reported `auth={prefix:Basic}` before
1075
+ // this line and `auth={basic:true,in:,name:,prefix:Basic}` after, and an
1076
+ // apiKey-in-query model now reports `in:query,name:token` where it used
1077
+ // to report neither. '' means "take what the spec said", which is what
1078
+ // prepare_auth was generated from. Mirrors main.kit.optspec.auth in
1079
+ // @voxgig/sdkgen/model/sdkgen.aon.
1080
+ "\"auth\": { \"prefix\": \"\", \"basic\": false,"
1081
+ " \"in\": \"\", \"name\": \"\" },"
1086
1082
  "\"headers\": { \"`$CHILD`\": \"`$STRING`\" },"
1087
1083
  "\"allow\": {"
1088
1084
  " \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\","
@@ -174,10 +174,21 @@ public static partial class SdkUtility
174
174
  // `basic` and `secret`: HTTP Basic Auth needs a second credential
175
175
  // and a flag to say the pair is Basic rather than a single bearer
176
176
  // token.
177
+ //
178
+ // `in` and `name`: WHERE the credential goes and UNDER WHAT NAME.
179
+ // apidef resolves both from the spec's securityScheme into
180
+ // main.kit.info.security, and the generated config carries them
181
+ // whenever they differ from header/Authorization. This map is
182
+ // CLOSED - validate rejects any key it does not declare - so
183
+ // without these two an apiKey-in-query SDK fails to construct on
184
+ // its own config with "Unexpected keys at field auth: in, name".
185
+ // '' means "take what the spec said".
177
186
  ["auth"] = new Dictionary<string, object?>
178
187
  {
179
188
  ["prefix"] = "",
180
189
  ["basic"] = false,
190
+ ["in"] = "",
191
+ ["name"] = "",
181
192
  },
182
193
  ["headers"] = new Dictionary<string, object?>
183
194
  {
@@ -10,6 +10,12 @@ defmodule ProjectName.Utility do
10
10
  alias Voxgig.Struct, as: S
11
11
  alias ProjectName.Helpers, as: H
12
12
  alias ProjectName.{Context, Spec, Result, Response, Operation}
13
+ # prepare_auth IS GENERATED, not templated: where the credential goes
14
+ # (header | query | cookie, and under what name) is a fact about the API,
15
+ # which apidef resolves into main.kit.info.security and a template cannot
16
+ # express. It lives in ProjectName.PrepareAuth, emitted by
17
+ # src/cmp/elixir/PrepareAuth_elixir.ts into lib/<app>/prepare_auth.ex.
18
+ alias ProjectName.PrepareAuth
13
19
 
14
20
  @default_user_agent "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)"
15
21
 
@@ -41,7 +47,7 @@ defmodule ProjectName.Utility do
41
47
  {"make_spec", &make_spec_impl/1},
42
48
  {"make_url", &make_url_impl/1},
43
49
  {"param", &param_impl/2},
44
- {"prepare_auth", &prepare_auth_impl/1},
50
+ {"prepare_auth", &PrepareAuth.prepare_auth_impl/1},
45
51
  {"prepare_body", &prepare_body_impl/1},
46
52
  {"prepare_headers", &prepare_headers_impl/1},
47
53
  {"prepare_method", &prepare_method_impl/1},
@@ -307,7 +313,18 @@ defmodule ProjectName.Utility do
307
313
  "suffix" => "",
308
314
  # `basic` and `secret`: HTTP Basic Auth needs a second credential and
309
315
  # a flag to say the pair is Basic rather than a single bearer token.
310
- "auth" => %{"prefix" => "", "basic" => false},
316
+ #
317
+ # `in` and `name` say WHERE the credential goes and under what name
318
+ # (header | query | cookie). The generated config carries them
319
+ # whenever the spec's scheme is not the header/Authorization default,
320
+ # and this port's validate REJECTS a key the spec does not declare -
321
+ # so without them an apiKey-in-query SDK cannot be CONSTRUCTED at all:
322
+ #
323
+ # ** (Voxgig.Struct.Error) Unexpected keys at field auth: in, name
324
+ #
325
+ # "" means "take what the spec said", which is what the generated
326
+ # prepare_auth was built from.
327
+ "auth" => %{"prefix" => "", "basic" => false, "in" => "", "name" => ""},
311
328
  "headers" => %{"`$CHILD`" => "`$STRING`"},
312
329
  "allow" => %{
313
330
  "method" => "GET,PUT,POST,PATCH,DELETE,OPTIONS",
@@ -1391,36 +1408,6 @@ defmodule ProjectName.Utility do
1391
1408
  end
1392
1409
  end
1393
1410
 
1394
- def prepare_auth_impl(ctx) do
1395
- spec = S.getprop(ctx, "spec")
1396
-
1397
- if spec == nil do
1398
- {nil, Context.make_error(ctx, "auth_no_spec", "Expected context spec property to be defined.")}
1399
- else
1400
- headers = S.getprop(spec, "headers")
1401
- options = opts_map(S.getprop(ctx, "client"))
1402
-
1403
- if S.getprop(options, "auth") == nil do
1404
- S.delprop(headers, "authorization")
1405
- {spec, nil}
1406
- else
1407
- apikey = S.getprop(options, "apikey", "__NOTFOUND__")
1408
-
1409
- if (is_binary(apikey) and apikey == "__NOTFOUND__") or apikey == nil or apikey == "" do
1410
- S.delprop(headers, "authorization")
1411
- else
1412
- ap = S.getpath(options, "auth.prefix")
1413
- auth_prefix = if is_binary(ap), do: ap, else: ""
1414
- apikey_val = if is_binary(apikey), do: apikey, else: ""
1415
- hv = if auth_prefix != "", do: auth_prefix <> " " <> apikey_val, else: apikey_val
1416
- S.setprop(headers, "authorization", hv)
1417
- end
1418
-
1419
- {spec, nil}
1420
- end
1421
- end
1422
- end
1423
-
1424
1411
  # ---- result_* ------------------------------------------------------------
1425
1412
 
1426
1413
  def result_basic_impl(ctx) do
@@ -96,10 +96,22 @@ final class MakeOptions {
96
96
  Map<String, Object> optspec = (Map<String, Object>) Json.parse(
97
97
  "{"
98
98
  + "\"apikey\": \"\","
99
+ // The SECOND credential, for the HTTP Basic scheme
100
+ // (base64(apikey:secret)). Named by main.kit.optspec but missing
101
+ // here, and validate drops what it does not name — so a caller
102
+ // that supplied `secret` never had it reach prepareAuth.
103
+ + "\"secret\": \"\","
99
104
  + "\"base\": \"http://localhost:8000\","
100
105
  + "\"prefix\": \"\","
101
106
  + "\"suffix\": \"\","
102
- + "\"auth\": { \"prefix\": \"\" },"
107
+ // WHERE the credential goes and under what name, plus the HTTP
108
+ // Basic switch. `Struct.validate` DROPS a key this spec does not
109
+ // name, so without these three the generated Config's auth block
110
+ // was trimmed back to `prefix` on the way in and the SDK could not
111
+ // see its own scheme. Mirrors main.kit.optspec.auth in
112
+ // @voxgig/sdkgen/model/sdkgen.aon, which ts builds its OPTSPEC from.
113
+ + "\"auth\": { \"prefix\": \"\", \"basic\": false,"
114
+ + " \"in\": \"\", \"name\": \"\" },"
103
115
  + "\"headers\": { \"`$CHILD`\": \"`$STRING`\" },"
104
116
  + "\"allow\": {"
105
117
  + " \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\","
@@ -82,10 +82,23 @@ fun makeOptions(ctx: Context): MutableMap<String, Any?> {
82
82
  val optspec = Json.parse(
83
83
  "{" +
84
84
  "\"apikey\": \"\"," +
85
+ // The SECOND credential, for the HTTP Basic scheme
86
+ // (base64(apikey:secret)). Named by main.kit.optspec but missing here,
87
+ // and validate drops what it does not name - so a caller that supplied
88
+ // `secret` never had it reach prepareAuth.
89
+ "\"secret\": \"\"," +
85
90
  "\"base\": \"http://localhost:8000\"," +
86
91
  "\"prefix\": \"\"," +
87
92
  "\"suffix\": \"\"," +
88
- "\"auth\": { \"prefix\": \"\" }," +
93
+ // WHERE the credential goes and UNDER WHAT NAME, plus the HTTP Basic
94
+ // switch. This map is CLOSED - Struct.validate drops, or rejects, a key
95
+ // it does not declare - so without these three the generated Config's
96
+ // auth block is trimmed back to `prefix` on the way in and an
97
+ // apiKey-in-query SDK cannot see its own scheme. '' means "take what
98
+ // the spec said". Mirrors main.kit.optspec.auth in
99
+ // @voxgig/sdkgen/model/sdkgen.aon, which ts builds its OPTSPEC from.
100
+ "\"auth\": { \"prefix\": \"\", \"basic\": false," +
101
+ " \"in\": \"\", \"name\": \"\" }," +
89
102
  "\"headers\": { \"`\$CHILD`\": \"`\$STRING`\" }," +
90
103
  "\"allow\": {" +
91
104
  " \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\"," +
@@ -2,12 +2,21 @@ package KOTLINPACKAGE.utility
2
2
 
3
3
  import KOTLINPACKAGE.core.Context
4
4
  import KOTLINPACKAGE.core.Helpers
5
- import KOTLINPACKAGE.core.Spec
6
5
  import KOTLINPACKAGE.utility.struct.Struct
7
6
 
8
- private const val HEADER_AUTH = "authorization"
9
- private const val OPTION_APIKEY = "apikey"
10
- private const val NOT_FOUND = "__NOTFOUND__"
7
+ // prepareAuth IS NOT HERE. It moved to utility/PrepareAuth.kt, which is
8
+ // GENERATED from the model rather than copied from tm/: where the credential
9
+ // goes (header, query parameter or cookie) and under what name are facts
10
+ // about the API, resolved by apidef into main.kit.info.security, and a
11
+ // template can only carry one answer. Hardcoding `authorization` here is
12
+ // exactly why an apiKey-in-query API got a header it does not read.
13
+ //
14
+ // It is still a top-level function in THIS package, so nothing changed for
15
+ // its callers: Register.kt still binds `u.prepareAuth = ::prepareAuth`.
16
+ // See src/cmp/kotlin/PrepareAuth_kotlin.ts.
17
+ //
18
+ // HEADER_AUTH, OPTION_APIKEY and NOT_FOUND went with it - they were
19
+ // file-private and nothing else in this file used them.
11
20
 
12
21
  private val METHOD_MAP: Map<String, String> = mapOf(
13
22
  "create" to "POST",
@@ -79,48 +88,6 @@ fun param(ctx: Context, paramdef: Any?): Any? {
79
88
  return v
80
89
  }
81
90
 
82
- fun prepareAuth(ctx: Context): Spec {
83
- val spec = ctx.spec
84
- ?: throw ctx.makeError("auth_no_spec", "Expected context spec property to be defined.")
85
-
86
- val headers = spec.headers
87
- val options = ctx.client!!.optionsMap()
88
-
89
- // Public APIs that need no auth omit the options.auth block entirely.
90
- if (options["auth"] == null) {
91
- headers.remove(HEADER_AUTH)
92
- return spec
93
- }
94
-
95
- val apikey = Struct.getprop(options, OPTION_APIKEY, NOT_FOUND)
96
-
97
- var skip = false
98
- if (apikey == null) {
99
- skip = true
100
- } else if (apikey is String && (NOT_FOUND == apikey || "" == apikey)) {
101
- skip = true
102
- }
103
-
104
- if (skip) {
105
- headers.remove(HEADER_AUTH)
106
- } else {
107
- var authPrefix = ""
108
- val ap = Struct.getpath(options, listOf("auth", "prefix"))
109
- if (ap is String) {
110
- authPrefix = ap
111
- }
112
- val apikeyVal = if (apikey is String) apikey else ""
113
- // Empty prefix (raw apiKey credential) must not add a leading space.
114
- if ("" == authPrefix) {
115
- headers[HEADER_AUTH] = apikeyVal
116
- } else {
117
- headers[HEADER_AUTH] = "$authPrefix $apikeyVal"
118
- }
119
- }
120
-
121
- return spec
122
- }
123
-
124
91
  fun prepareBody(ctx: Context): Any? {
125
92
  if ("data" == ctx.op.input) {
126
93
  return ctx.utility!!.transformRequest(ctx)
@@ -59,8 +59,18 @@ CORPUS = ../.sdk/test/test.json
59
59
 
60
60
  # A gated feature's modules come AFTER sdk_features.ml (a feature module
61
61
  # opens it) and BEFORE sdk_config.ml (the generated factory names them).
62
+ #
63
+ # sdk_prepare_auth.ml is GENERATED (src/cmp/ocaml/PrepareAuth_ocaml.ts), not
64
+ # copied from the template tree: WHERE the credential goes - a header, a query
65
+ # parameter or a cookie, and under what name - is a fact about the API, and a
66
+ # template can hold only one answer. Its position is STATED here because
67
+ # ocamlc compiles a module before anything that uses it and has no link-time
68
+ # reordering: after sdk_helpers.ml, whose client/value accessors it uses, and
69
+ # before sdk_runtime.ml, which binds `prepare_auth_util` to it and registers
70
+ # that in the utility record.
62
71
  RUNTIME = utility/vregex.ml utility/voxgig_struct.ml sdk_json.ml \
63
- sdk_types.ml sdk_helpers.ml sdk_runtime.ml sdk_features.ml $(FEATURE_SRC)
72
+ sdk_types.ml sdk_helpers.ml sdk_prepare_auth.ml sdk_runtime.ml \
73
+ sdk_features.ml $(FEATURE_SRC)
64
74
 
65
75
  # The vendored corpus engine plus the adapter that drives it. Order matters:
66
76
  # the adapter references the vendored module.
@@ -78,6 +78,21 @@ let cu (ctx : ctx) : utility =
78
78
  let cc (ctx : ctx) : sdk_client =
79
79
  match ctx.c_client with Some c -> c | None -> failwith "context client not set"
80
80
 
81
+ (* ----- client options -----
82
+ * A CLONE of the client's options, so a utility that writes into the map it
83
+ * is handed cannot mutate the client's own (the secrets feature relies on
84
+ * that: it rewrites the credential in the fetchdef, never in the shared
85
+ * options).
86
+ *
87
+ * Lives HERE, beside the other client accessors, rather than in Sdk_runtime
88
+ * where it began: the generated sdk_prepare_auth.ml reads it and is compiled
89
+ * BEFORE Sdk_runtime (ocamlc compiles a module before anything that uses it
90
+ * and has no link-time reordering), so a definition in Sdk_runtime is out of
91
+ * reach. Sdk_runtime opens this module, so its own call sites are unchanged.
92
+ *)
93
+ let client_options_map (client : sdk_client) : value =
94
+ match clone client.cl_options with Map _ as m -> m | _ -> empty_map ()
95
+
81
96
  (* ----- per-op feature scratch ----- *)
82
97
 
83
98
  let scratch_get (ctx : ctx) (key : string) : value option =
@@ -239,9 +239,6 @@ let make_context_impl (cs : ctxspec) (basectx : ctx option) : ctx =
239
239
  (* utilities *)
240
240
  (* ------------------------------------------------------------------ *)
241
241
 
242
- let client_options_map (client : sdk_client) : value =
243
- match clone client.cl_options with Map _ as m -> m | _ -> empty_map ()
244
-
245
242
  let clean_util (_ctx : ctx) (v : value) : value = v
246
243
 
247
244
  let make_error_util (ctx : ctx) (err_opt : sdk_error option) : value =
@@ -537,26 +534,24 @@ let graphql_errors_util (ctx : ctx) : bool =
537
534
  true
538
535
  end
539
536
 
540
- let prepare_auth_util (ctx : ctx) : (spec option * sdk_error option) =
541
- match ctx.c_spec with
542
- | None -> (None, Some (ctx_make_error ctx "auth_no_spec" "Expected context spec property to be defined."))
543
- | Some spec ->
544
- let headers = spec.sp_headers in
545
- let options = client_options_map (cc ctx) in
546
- (match getp options "auth" with
547
- | Noval | Null -> ignore (delprop headers (Str "authorization")); (Some spec, None)
548
- | _ ->
549
- let apikey = getprop ~alt:(Str "__NOTFOUND__") options (Str "apikey") in
550
- let is_notfound = (match apikey with Str "__NOTFOUND__" -> true | _ -> false) in
551
- if is_notfound || is_noval apikey || apikey = Str "" then
552
- ignore (delprop headers (Str "authorization"))
553
- else begin
554
- let auth_prefix = match getpath_s options "auth.prefix" with Str s -> s | _ -> "" in
555
- let apikey_val = match apikey with Str s -> s | _ -> "" in
556
- let authval = if auth_prefix <> "" then auth_prefix ^ " " ^ apikey_val else apikey_val in
557
- setp headers "authorization" (Str authval)
558
- end;
559
- (Some spec, None))
537
+ (* prepare_auth lives in its own GENERATED module, Sdk_prepare_auth
538
+ * (src/cmp/ocaml/PrepareAuth_ocaml.ts), and this is the binding every caller
539
+ * still reaches it by.
540
+ *
541
+ * WHY IT LEFT THIS FILE. WHERE the credential goes - a header, a query
542
+ * parameter or a cookie, and under what name - is a fact about the API, which
543
+ * apidef resolves into main.kit.info.security. A template can hold only one
544
+ * answer, so this one hardcoded an `authorization` header and an
545
+ * apiKey-in-query API (joplin's `?token=`) got a header it does not read and
546
+ * never got the parameter it does. The three placements need three different
547
+ * bodies, so the body is generated and the name stays here.
548
+ *
549
+ * The alias is not cosmetic: `new_utility`/`register` below bind
550
+ * `u_prepare_auth` to this name, make_spec_util calls it through the utility
551
+ * record, the secrets feature re-runs it the same way, and
552
+ * test/primary_utility_test.ml reaches `prepare_auth_util` by `open
553
+ * Sdk_runtime` to drive the shared corpus section. All of that is unchanged. *)
554
+ let prepare_auth_util = Sdk_prepare_auth.prepare_auth_util
560
555
 
561
556
  (* ----- transforms / result helpers ----- *)
562
557
 
@@ -942,8 +937,19 @@ let opt_spec_value () : value =
942
937
  ("prefix", Str "");
943
938
  ("suffix", Str "");
944
939
  (* `basic` and `secret`: HTTP Basic Auth needs a second credential and a
945
- flag to say the pair is Basic rather than a single bearer token. *)
946
- ("auth", jo [("prefix", Str ""); ("basic", Bool false)]);
940
+ flag to say the pair is Basic rather than a single bearer token.
941
+
942
+ `in` and `name`: WHERE the credential goes and UNDER WHAT NAME -
943
+ header (the default, `authorization`), query or cookie. This shape is
944
+ CLOSED, so a config carrying a key it does not list fails validation
945
+ and the client cannot be constructed at all: without these two an
946
+ apiKey-in-query SDK could not build its options, because the generated
947
+ sdk_config.ml now carries them whenever the spec's scheme is not the
948
+ header/Authorization default. They mirror main.kit.optspec.auth in
949
+ @voxgig/sdkgen/model/sdkgen.aon, and '' means "whatever the spec
950
+ resolved", which is what the generated prepare_auth was built from. *)
951
+ ("auth", jo [("prefix", Str ""); ("basic", Bool false);
952
+ ("in", Str ""); ("name", Str "")]);
947
953
  ("headers", jo [("`$CHILD`", Str "`$STRING`")]);
948
954
  ("allow", jo [("method", Str "GET,PUT,POST,PATCH,DELETE,OPTIONS");
949
955
  ("op", Str "create,update,load,list,remove,command,direct,graphql")]);
@@ -12,8 +12,11 @@
12
12
  * - the client is LIVE (Sdk_client.make), so `options.system.fetch` really
13
13
  * is the transport and the feature's wrapper sits above it - never the
14
14
  * test-mode mock, which replaces the fetcher and hides the seam;
15
- * - every assertion reads the authorization header the RECORDER received,
16
- * never the options map, which this feature never writes;
15
+ * - every assertion reads what the RECORDER received, never the options
16
+ * map, which this feature never writes. The FEATURE writes the
17
+ * authorization header (see the header-only note on placed_cred_of
18
+ * below), so its cases read that; the three cases where the feature is
19
+ * inactive or missed read whatever the generated prepare_auth placed;
17
20
  * - a refusal must carry the PROVIDER'S OWN message (sekreto's wording),
18
21
  * not any error;
19
22
  * - beside every refusal is a CONTROL leg: the same construction with a
@@ -87,7 +90,94 @@ let api_calls (r : recorder) : value list =
87
90
  let token_calls (r : recorder) : value list =
88
91
  List.filter (fun c -> is_token (match getp c "url" with Str u -> u | _ -> "")) !(r.calls)
89
92
 
90
- (* The authorization header the i-th API call carried, or Noval. *)
93
+ (* WHERE prepare_auth PUT THE CREDENTIAL, asked of the generated module
94
+ * rather than assumed to be a header.
95
+ *
96
+ * prepare_auth is GENERATED (Sdk_prepare_auth) from the API's own security
97
+ * scheme, so it is not always a header: an apiKey-in-query API - joplin's
98
+ * `?token=` - carries the credential in the URL, and a cookie scheme in the
99
+ * shared `cookie` header. Reading `authorization` outright made
100
+ * `inactive.apikey_option_behaves_as_before` a HEADER assertion, so it
101
+ * failed on a query-placement SDK that was carrying its credential
102
+ * perfectly well.
103
+ *
104
+ * KNOWN GAP, DELIBERATELY NOT PAPERED OVER HERE: the FEATURE'S own rewrite
105
+ * (secrets_feature.ml) still writes `authorization` whatever the scheme
106
+ * says, so a chain-resolved secret does NOT follow the placement. Every
107
+ * target's secrets feature does the same. The cases below that exercise the
108
+ * feature therefore keep reading the header - which is what the code
109
+ * actually does - and only the three cases where the feature is inactive or
110
+ * missed read the placed bag. Making the feature placement-aware is a
111
+ * separate change, across every target at once. *)
112
+
113
+ (* Percent-decoding, because make_url escapes a query value on the way in.
114
+ * `+` is NOT treated as a space: that is form encoding, and a literal `+`
115
+ * in a token must survive. *)
116
+ let pct_decode (s : string) : string =
117
+ let hex c =
118
+ if c >= '0' && c <= '9' then Char.code c - 48
119
+ else if c >= 'a' && c <= 'f' then Char.code c - 87
120
+ else if c >= 'A' && c <= 'F' then Char.code c - 55
121
+ else (-1) in
122
+ let n = String.length s in
123
+ let buf = Buffer.create n in
124
+ let i = ref 0 in
125
+ while !i < n do
126
+ if s.[!i] = '%' && !i + 2 < n && hex s.[!i + 1] >= 0 && hex s.[!i + 2] >= 0 then begin
127
+ Buffer.add_char buf (Char.chr ((hex s.[!i + 1] * 16) + hex s.[!i + 2]));
128
+ i := !i + 3
129
+ end
130
+ else (Buffer.add_char buf s.[!i]; incr i)
131
+ done;
132
+ Buffer.contents buf
133
+
134
+ (* The first `name=` value in a `sep`-separated list, trimmed and optionally
135
+ * percent-decoded. Noval when the list has no such entry. *)
136
+ let pair_value ?(decode = false) (raw : string) (sep : char) (name : string) : value =
137
+ let want = name ^ "=" in
138
+ let wl = String.length want in
139
+ let trim s =
140
+ let n = String.length s in
141
+ let b = ref 0 and e = ref n in
142
+ while !b < !e && s.[!b] = ' ' do incr b done;
143
+ while !e > !b && s.[!e - 1] = ' ' do decr e done;
144
+ String.sub s !b (!e - !b) in
145
+ let rec go = function
146
+ | [] -> Noval
147
+ | part :: rest ->
148
+ let part = trim part in
149
+ if String.length part >= wl && String.sub part 0 wl = want then
150
+ let v = String.sub part wl (String.length part - wl) in
151
+ Str (if decode then pct_decode v else v)
152
+ else go rest in
153
+ go (String.split_on_char sep raw)
154
+
155
+ (* The credential one recorded fetchdef carried, read from the bag this
156
+ * SDK's generated prepare_auth places it in. *)
157
+ let placed_cred_of (fd : value) : value =
158
+ let headers = getp fd "headers" in
159
+ if Sdk_prepare_auth.cred_where = "query" then
160
+ let url = match getp fd "url" with Str u -> u | _ -> "" in
161
+ (match String.index_opt url '?' with
162
+ | None -> Noval
163
+ | Some at ->
164
+ pair_value ~decode:true
165
+ (String.sub url (at + 1) (String.length url - at - 1)) '&'
166
+ Sdk_prepare_auth.cred_name)
167
+ else if Sdk_prepare_auth.cred_where = "cookie" then
168
+ (match headers with
169
+ | Map _ ->
170
+ (match Sdk_features.header_ci headers "cookie" with
171
+ | Str raw -> pair_value raw ';' Sdk_prepare_auth.cred_name
172
+ | _ -> Noval)
173
+ | _ -> Noval)
174
+ else
175
+ (match headers with
176
+ | Map _ -> Sdk_features.header_ci headers Sdk_prepare_auth.cred_name
177
+ | _ -> Noval)
178
+
179
+ (* The AUTHORIZATION HEADER the i-th API call carried, or Noval - what the
180
+ * feature's own rewrite writes (see the note above). *)
91
181
  let auth_of (r : recorder) (i : int) : value =
92
182
  match List.nth_opt (api_calls r) i with
93
183
  | Some c -> (match getp (getp c "fetchdef") "headers" with
@@ -95,9 +185,18 @@ let auth_of (r : recorder) (i : int) : value =
95
185
  | _ -> Noval)
96
186
  | None -> Noval
97
187
 
98
- (* The Authorization header carries the SPEC's credential prefix, which a
99
- * TEMPLATE cannot know - so assert on the CREDENTIAL and let the prefix be
100
- * whatever this SDK's API declares. *)
188
+ (* The same call read through the SCHEME'S placement, for the cases where no
189
+ * chain resolved anything and prepare_auth's own placement is what reached
190
+ * the wire. *)
191
+ let placed_of (r : recorder) (i : int) : value =
192
+ match List.nth_opt (api_calls r) i with
193
+ | Some c -> placed_cred_of (getp c "fetchdef")
194
+ | None -> Noval
195
+
196
+ (* A header credential carries the SPEC's prefix, which a TEMPLATE cannot
197
+ * know - so assert on the CREDENTIAL and let the prefix be whatever this
198
+ * SDK's API declares. A query parameter and a cookie carry no prefix at all,
199
+ * which the bare equality already covers. *)
101
200
  let credential_is (header : value) (token : string) : bool =
102
201
  match header with
103
202
  | Str s -> s = token || Sdk_features.ends_with s (" " ^ token)
@@ -105,6 +204,14 @@ let credential_is (header : value) (token : string) : bool =
105
204
 
106
205
  let absent (header : value) : bool = is_nullish header
107
206
 
207
+ (* SUPPRESSION IS ABOUT EVERY BAG, not just the header. `auth: null` must
208
+ * leave nothing anywhere, so a suppression case asserts the authorization
209
+ * header AND the bag this API's scheme places its credential in - otherwise
210
+ * a query-placement SDK could leak `?token=...` and still read green,
211
+ * because the header it never uses was empty. *)
212
+ let nothing_placed (r : recorder) (i : int) : bool =
213
+ absent (auth_of r i) && absent (placed_of r i)
214
+
108
215
  (* ---------------------------------------------------------------------- *)
109
216
  (* clients and paths *)
110
217
  (* ---------------------------------------------------------------------- *)
@@ -217,13 +324,17 @@ let () =
217
324
  let client = inactive ~sdkopts:[("apikey", Str "OPTKEY01")] fetch in
218
325
  check "ok" (res_ok (direct client));
219
326
  check_int "one call" (List.length (api_calls r)) 1;
220
- check "carries OPTKEY01" (credential_is (auth_of r 0) "OPTKEY01"));
327
+ (* THE FEATURE IS INACTIVE HERE, so what reaches the wire is exactly
328
+ what the generated prepare_auth placed - in the bag this API's
329
+ scheme names, which for an apiKey-in-query SDK is the query string
330
+ and not a header. *)
331
+ check "carries OPTKEY01" (credential_is (placed_of r 0) "OPTKEY01"));
221
332
 
222
333
  test "inactive.no_apikey_no_header" (fun () ->
223
334
  let (r, fetch) = recorder () in
224
335
  let client = inactive fetch in
225
336
  check "ok" (res_ok (direct client));
226
- check "no header" (absent (auth_of r 0)));
337
+ check "no header" (absent (placed_of r 0)));
227
338
 
228
339
  (* ---------------------------------------------------------------------- *)
229
340
  (* the chain *)
@@ -276,7 +387,9 @@ let () =
276
387
  let client = live fetch [("providers", ja [fn])] in
277
388
  check "ok" (res_ok (direct client));
278
389
  check_int "one call" (List.length (api_calls r)) 1;
279
- check "no header" (absent (auth_of r 0));
390
+ (* NOTHING resolved, so the feature wrote nothing and the placed bag -
391
+ whichever this API's scheme names - is what must be empty. *)
392
+ check "no header" (absent (placed_of r 0));
280
393
  check_int "the chain was asked" !asked 1);
281
394
 
282
395
  test "chain.miss_falls_through_to_the_next_provider" (fun () ->
@@ -451,7 +564,7 @@ let () =
451
564
  [("providers", ja [jo [("kind", Str "env"); ("prefix", Str envprefix)]])] in
452
565
  check "ok" (res_ok (direct client));
453
566
  check_int "one call" (List.length (api_calls r)) 1;
454
- check "no header, though the chain resolved" (absent (auth_of r 0));
567
+ check "nothing placed, though the chain resolved" (nothing_placed r 0);
455
568
  (* The suppression survives option validation as a present null. *)
456
569
  check "options.auth survives as a present null"
457
570
  (getprop_raw client.cl_options "auth" = Null));
@@ -461,7 +574,7 @@ let () =
461
574
  let client = live ~sdkopts:[("auth", Null); ("apikey", Str "OPTKEY01")] fetch
462
575
  [("providers", ja [working "CUSTOM01"])] in
463
576
  check "ok" (res_ok (direct client));
464
- check "no header" (absent (auth_of r 0)));
577
+ check "nothing placed" (nothing_placed r 0));
465
578
 
466
579
  test "auth.null_suppresses_on_the_entity_path" (fun () ->
467
580
  let (r, fetch) = recorder () in
@@ -470,7 +583,7 @@ let () =
470
583
  | Ok () -> ()
471
584
  | Error msg -> failwith ("entity op failed: " ^ msg));
472
585
  check_int "reached the transport once" (List.length (api_calls r)) 1;
473
- check "no header" (absent (auth_of r 0)));
586
+ check "nothing placed" (nothing_placed r 0));
474
587
 
475
588
  test "entity.carries_the_chain_resolved_credential" (fun () ->
476
589
  let (r, fetch) = recorder () in