@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
|
@@ -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
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
-
|
|
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", ¶m_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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
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
|
|
16
|
-
*
|
|
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
|
-
(*
|
|
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
|
|
99
|
-
*
|
|
100
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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
|