@voxgig/sdkgen 4.32.1 → 4.33.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/helpers/naming.js +1 -1
- package/dist/helpers/naming.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/model/sdkgen.aontu +7 -1
- package/package.json +1 -1
- package/project/.sdk/model/feature/debug.aontu +3 -1
- package/project/.sdk/src/cmp/c/PrepareAuth_c.ts +8 -28
- package/project/.sdk/src/cmp/c/TestClean_c.ts +774 -0
- package/project/.sdk/src/cmp/c/Test_c.ts +3 -0
- package/project/.sdk/src/cmp/c/fragment/Entity.fragment.c +16 -5
- package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +1 -1
- package/project/.sdk/src/cmp/clojure/PrepareAuth_clojure.ts +3 -0
- package/project/.sdk/src/cmp/clojure/TestClean_clojure.ts +473 -0
- package/project/.sdk/src/cmp/clojure/Test_clojure.ts +6 -0
- package/project/.sdk/src/cmp/clojure/fragment/Entity.fragment.clj +18 -8
- package/project/.sdk/src/cmp/cpp/PrepareAuth_cpp.ts +3 -0
- package/project/.sdk/src/cmp/cpp/TestClean_cpp.ts +782 -0
- package/project/.sdk/src/cmp/cpp/TestEntity_cpp.ts +1 -1
- package/project/.sdk/src/cmp/cpp/Test_cpp.ts +2 -0
- package/project/.sdk/src/cmp/csharp/PrepareAuth_csharp.ts +3 -0
- package/project/.sdk/src/cmp/csharp/TestClean_csharp.ts +887 -0
- package/project/.sdk/src/cmp/csharp/Test_csharp.ts +2 -0
- package/project/.sdk/src/cmp/csharp/fragment/EntityBase.fragment.cs +68 -4
- package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +8 -2
- package/project/.sdk/src/cmp/csharp/fragment/SdkError.fragment.cs +49 -0
- package/project/.sdk/src/cmp/elixir/PrepareAuth_elixir.ts +3 -0
- package/project/.sdk/src/cmp/elixir/Schema_elixir.ts +20 -10
- package/project/.sdk/src/cmp/elixir/TestClean_elixir.ts +666 -0
- package/project/.sdk/src/cmp/elixir/Test_elixir.ts +4 -0
- package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +1 -1
- package/project/.sdk/src/cmp/elixir/fragment/Pipeline.fragment.ex +14 -2
- package/project/.sdk/src/cmp/go/PrepareAuth_go.ts +3 -0
- package/project/.sdk/src/cmp/go/TestClean_go.ts +804 -0
- package/project/.sdk/src/cmp/go/Test_go.ts +3 -0
- package/project/.sdk/src/cmp/go/fragment/Entity.fragment.go +54 -4
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +27 -2
- package/project/.sdk/src/cmp/java/PrepareAuth_java.ts +3 -0
- package/project/.sdk/src/cmp/java/TestClean_java.ts +884 -0
- package/project/.sdk/src/cmp/java/Test_java.ts +2 -0
- package/project/.sdk/src/cmp/java/fragment/EntityBase.fragment.java +56 -11
- package/project/.sdk/src/cmp/java/fragment/SdkError.fragment.java +36 -3
- package/project/.sdk/src/cmp/js/PrepareAuth_js.ts +3 -0
- package/project/.sdk/src/cmp/js/TestClean_js.ts +458 -0
- package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
- package/project/.sdk/src/cmp/js/fragment/EntityBase.fragment.js +5 -4
- package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +7 -1
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +11 -2
- package/project/.sdk/src/cmp/js/fragment/SdkError.fragment.js +17 -1
- package/project/.sdk/src/cmp/kotlin/Package_kotlin.ts +5 -0
- package/project/.sdk/src/cmp/kotlin/PrepareAuth_kotlin.ts +3 -0
- package/project/.sdk/src/cmp/kotlin/TestClean_kotlin.ts +654 -0
- package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +3 -0
- package/project/.sdk/src/cmp/kotlin/fragment/EntityBase.fragment.kt +44 -11
- package/project/.sdk/src/cmp/kotlin/fragment/SdkError.fragment.kt +31 -0
- package/project/.sdk/src/cmp/lua/PrepareAuth_lua.ts +3 -0
- package/project/.sdk/src/cmp/lua/TestClean_lua.ts +769 -0
- package/project/.sdk/src/cmp/lua/Test_lua.ts +3 -0
- package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +77 -1
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +49 -2
- package/project/.sdk/src/cmp/ocaml/Entity_ocaml.ts +3 -2
- package/project/.sdk/src/cmp/ocaml/PrepareAuth_ocaml.ts +4 -25
- package/project/.sdk/src/cmp/ocaml/TestClean_ocaml.ts +538 -0
- package/project/.sdk/src/cmp/ocaml/Test_ocaml.ts +5 -0
- package/project/.sdk/src/cmp/ocaml/fragment/RunOp.fragment.ml +16 -4
- package/project/.sdk/src/cmp/perl/PrepareAuth_perl.ts +3 -0
- package/project/.sdk/src/cmp/perl/TestClean_perl.ts +619 -0
- package/project/.sdk/src/cmp/perl/Test_perl.ts +3 -0
- package/project/.sdk/src/cmp/perl/fragment/Entity.fragment.pm +67 -5
- package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +13 -1
- package/project/.sdk/src/cmp/php/PrepareAuth_php.ts +3 -0
- package/project/.sdk/src/cmp/php/TestClean_php.ts +809 -0
- package/project/.sdk/src/cmp/php/Test_php.ts +2 -0
- package/project/.sdk/src/cmp/php/fragment/Entity.fragment.php +96 -55
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +15 -2
- package/project/.sdk/src/cmp/py/PrepareAuth_py.ts +3 -0
- package/project/.sdk/src/cmp/py/TestClean_py.ts +549 -0
- package/project/.sdk/src/cmp/py/Test_py.ts +3 -0
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +83 -55
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +19 -3
- package/project/.sdk/src/cmp/rb/PrepareAuth_rb.ts +3 -0
- package/project/.sdk/src/cmp/rb/TestClean_rb.ts +535 -0
- package/project/.sdk/src/cmp/rb/Test_rb.ts +3 -0
- package/project/.sdk/src/cmp/rb/fragment/Entity.fragment.rb +108 -46
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +12 -1
- package/project/.sdk/src/cmp/rust/PrepareAuth_rust.ts +9 -45
- package/project/.sdk/src/cmp/rust/TestClean_rust.ts +881 -0
- package/project/.sdk/src/cmp/rust/Test_rust.ts +3 -0
- package/project/.sdk/src/cmp/rust/fragment/Entity.fragment.rs +14 -6
- package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +17 -1
- package/project/.sdk/src/cmp/rust/fragment/SdkError.fragment.rs +34 -2
- package/project/.sdk/src/cmp/scala/PrepareAuth_scala.ts +3 -0
- package/project/.sdk/src/cmp/scala/TestClean_scala.ts +608 -0
- package/project/.sdk/src/cmp/scala/Test_scala.ts +3 -0
- package/project/.sdk/src/cmp/scala/fragment/EntityBase.fragment.scala +58 -6
- package/project/.sdk/src/cmp/swift/PrepareAuth_swift.ts +3 -0
- package/project/.sdk/src/cmp/swift/TestClean_swift.ts +671 -0
- package/project/.sdk/src/cmp/swift/Test_swift.ts +3 -0
- package/project/.sdk/src/cmp/swift/fragment/EntityBase.fragment.swift +10 -0
- package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +16 -2
- package/project/.sdk/src/cmp/swift/fragment/SdkError.fragment.swift +47 -3
- package/project/.sdk/src/cmp/ts/PrepareAuth_ts.ts +3 -0
- package/project/.sdk/src/cmp/ts/TestClean_ts.ts +465 -0
- package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
- package/project/.sdk/src/cmp/ts/fragment/EntityBase.fragment.ts +5 -4
- package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.ts +7 -1
- package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.ts +7 -1
- package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.ts +7 -1
- package/project/.sdk/src/cmp/ts/fragment/EntityRemoveOp.fragment.ts +7 -1
- package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.ts +7 -1
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +8 -2
- package/project/.sdk/src/cmp/ts/fragment/SdkError.fragment.ts +20 -2
- package/project/.sdk/src/cmp/zig/PrepareAuth_zig.ts +3 -0
- package/project/.sdk/src/cmp/zig/TestClean_zig.ts +791 -0
- package/project/.sdk/src/cmp/zig/Test_zig.ts +5 -0
- package/project/.sdk/src/cmp/zig/fragment/Build.fragment.zig +19 -0
- package/project/.sdk/src/cmp/zig/fragment/Entity.fragment.zig +14 -5
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +1 -1
- package/project/.sdk/tm/c/core/context.c +25 -0
- package/project/.sdk/tm/c/core/error.c +16 -0
- package/project/.sdk/tm/c/core/sdk.h +22 -0
- package/project/.sdk/tm/c/feature/audit.c +2 -0
- package/project/.sdk/tm/c/feature/cost.c +1 -0
- package/project/.sdk/tm/c/feature/debug.c +19 -29
- package/project/.sdk/tm/c/feature/log.c +21 -2
- package/project/.sdk/tm/c/feature/proxy.c +55 -3
- package/project/.sdk/tm/c/feature/secrets.c +7 -0
- package/project/.sdk/tm/c/feature/telemetry.c +2 -0
- package/project/.sdk/tm/c/tests/feature_test.c +2 -2
- package/project/.sdk/tm/c/utility/clean.c +530 -8
- package/project/.sdk/tm/c/utility/done.c +28 -12
- package/project/.sdk/tm/c/utility/make_error.c +4 -3
- package/project/.sdk/tm/c/utility/make_options.c +71 -45
- package/project/.sdk/tm/clojure/feature/secrets/sdk/feature/secrets.clj +15 -0
- package/project/.sdk/tm/clojure/src/sdk/client.clj +20 -4
- package/project/.sdk/tm/clojure/src/sdk/core.clj +319 -35
- package/project/.sdk/tm/clojure/src/sdk/features.clj +68 -30
- package/project/.sdk/tm/clojure/test/sdk/test/feature.clj +4 -2
- package/project/.sdk/tm/clojure/test/sdk/test/pipeline.clj +47 -20
- package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +4 -0
- package/project/.sdk/tm/cpp/core/types.hpp +93 -3
- package/project/.sdk/tm/cpp/feature/audit.hpp +1 -0
- package/project/.sdk/tm/cpp/feature/cost.hpp +1 -0
- package/project/.sdk/tm/cpp/feature/debug.hpp +20 -12
- package/project/.sdk/tm/cpp/feature/log.hpp +31 -9
- package/project/.sdk/tm/cpp/feature/proxy.hpp +52 -6
- package/project/.sdk/tm/cpp/feature/secrets.hpp +13 -0
- package/project/.sdk/tm/cpp/feature/telemetry.hpp +4 -2
- package/project/.sdk/tm/cpp/test/feature_test.cpp +2 -2
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +426 -51
- package/project/.sdk/tm/csharp/core/Context.cs +32 -0
- package/project/.sdk/tm/csharp/core/Utility.cs +2 -0
- package/project/.sdk/tm/csharp/feature/AuditFeature.cs +1 -0
- package/project/.sdk/tm/csharp/feature/CostFeature.cs +7 -5
- package/project/.sdk/tm/csharp/feature/DebugFeature.cs +19 -16
- package/project/.sdk/tm/csharp/feature/FeatureOptions.cs +16 -0
- package/project/.sdk/tm/csharp/feature/LogFeature.cs +4 -1
- package/project/.sdk/tm/csharp/feature/ProxyFeature.cs +26 -5
- package/project/.sdk/tm/csharp/feature/SecretsFeature.cs +16 -0
- package/project/.sdk/tm/csharp/feature/TelemetryFeature.cs +4 -2
- package/project/.sdk/tm/csharp/test/FeatureTest.cs +2 -2
- package/project/.sdk/tm/csharp/utility/Clean.cs +440 -2
- package/project/.sdk/tm/csharp/utility/Done.cs +27 -11
- package/project/.sdk/tm/csharp/utility/MakeError.cs +19 -8
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +119 -29
- package/project/.sdk/tm/csharp/utility/MakeResponse.cs +21 -11
- package/project/.sdk/tm/csharp/utility/Register.cs +1 -0
- package/project/.sdk/tm/elixir/lib/projectname/context.ex +19 -0
- package/project/.sdk/tm/elixir/lib/projectname/entity_base.ex +22 -1
- package/project/.sdk/tm/elixir/lib/projectname/error.ex +24 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/audit.ex +2 -2
- package/project/.sdk/tm/elixir/lib/projectname/feature/cost.ex +2 -2
- package/project/.sdk/tm/elixir/lib/projectname/feature/debug.ex +13 -13
- package/project/.sdk/tm/elixir/lib/projectname/feature/log.ex +18 -5
- package/project/.sdk/tm/elixir/lib/projectname/feature/proxy.ex +19 -1
- package/project/.sdk/tm/elixir/lib/projectname/feature/secrets.ex +15 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/telemetry.ex +4 -2
- package/project/.sdk/tm/elixir/lib/projectname/feature.ex +22 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +464 -41
- package/project/.sdk/tm/elixir/test/feature_test.exs +3 -2
- package/project/.sdk/tm/go/core/context.go +42 -0
- package/project/.sdk/tm/go/core/error.go +30 -10
- package/project/.sdk/tm/go/core/utility_type.go +2 -0
- package/project/.sdk/tm/go/feature/audit_feature.go +1 -0
- package/project/.sdk/tm/go/feature/base_feature.go +12 -0
- package/project/.sdk/tm/go/feature/cost_feature.go +2 -2
- package/project/.sdk/tm/go/feature/debug_feature.go +14 -12
- package/project/.sdk/tm/go/feature/log_feature.go +13 -8
- package/project/.sdk/tm/go/feature/proxy_feature.go +26 -6
- package/project/.sdk/tm/go/feature/secrets_feature.go +19 -0
- package/project/.sdk/tm/go/feature/telemetry_feature.go +4 -2
- package/project/.sdk/tm/go/test/feature_test.go +2 -2
- package/project/.sdk/tm/go/utility/clean.go +585 -2
- package/project/.sdk/tm/go/utility/done.go +29 -9
- package/project/.sdk/tm/go/utility/make_error.go +51 -16
- package/project/.sdk/tm/go/utility/make_options.go +77 -36
- package/project/.sdk/tm/go/utility/register.go +12 -0
- package/project/.sdk/tm/java/core/Context.java +29 -0
- package/project/.sdk/tm/java/core/SdkClient.java +4 -2
- package/project/.sdk/tm/java/core/Utility.java +7 -0
- package/project/.sdk/tm/java/feature/AuditFeature.java +6 -0
- package/project/.sdk/tm/java/feature/CostFeature.java +5 -0
- package/project/.sdk/tm/java/feature/DebugFeature.java +25 -14
- package/project/.sdk/tm/java/feature/LogFeature.java +11 -4
- package/project/.sdk/tm/java/feature/ProxyFeature.java +41 -6
- package/project/.sdk/tm/java/feature/SecretsFeature.java +18 -0
- package/project/.sdk/tm/java/feature/TelemetryFeature.java +8 -2
- package/project/.sdk/tm/java/test/FeatureTest.java +2 -2
- package/project/.sdk/tm/java/utility/Clean.java +519 -1
- package/project/.sdk/tm/java/utility/Done.java +1 -1
- package/project/.sdk/tm/java/utility/MakeError.java +5 -1
- package/project/.sdk/tm/java/utility/MakeOptions.java +55 -31
- package/project/.sdk/tm/java/utility/Register.java +4 -0
- package/project/.sdk/tm/js/src/Context.js +5 -1
- package/project/.sdk/tm/js/src/feature/audit/AuditFeature.js +2 -2
- package/project/.sdk/tm/js/src/feature/cost/CostFeature.js +2 -2
- package/project/.sdk/tm/js/src/feature/debug/DebugFeature.js +13 -7
- package/project/.sdk/tm/js/src/feature/log/LogFeature.js +9 -5
- package/project/.sdk/tm/js/src/feature/proxy/ProxyFeature.js +19 -1
- package/project/.sdk/tm/js/src/feature/secrets/SecretsFeature.js +14 -0
- package/project/.sdk/tm/js/src/feature/telemetry/TelemetryFeature.js +4 -2
- package/project/.sdk/tm/js/src/utility/CleanUtility.js +294 -21
- package/project/.sdk/tm/js/src/utility/DoneUtility.js +17 -5
- package/project/.sdk/tm/js/src/utility/MakeErrorUtility.js +18 -2
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +39 -15
- package/project/.sdk/tm/js/src/utility/Utility.js +4 -2
- package/project/.sdk/tm/js/test/feature/harness.js +11 -1
- package/project/.sdk/tm/js/test/feature.test.js +9 -2
- package/project/.sdk/tm/kotlin/core/Context.kt +19 -0
- package/project/.sdk/tm/kotlin/core/SdkClient.kt +9 -2
- package/project/.sdk/tm/kotlin/core/Utility.kt +2 -0
- package/project/.sdk/tm/kotlin/feature/AuditFeature.kt +11 -9
- package/project/.sdk/tm/kotlin/feature/CostFeature.kt +8 -5
- package/project/.sdk/tm/kotlin/feature/DebugFeature.kt +15 -22
- package/project/.sdk/tm/kotlin/feature/LogFeature.kt +8 -4
- package/project/.sdk/tm/kotlin/feature/ProxyFeature.kt +26 -2
- package/project/.sdk/tm/kotlin/feature/SecretsFeature.kt +11 -0
- package/project/.sdk/tm/kotlin/feature/TelemetryFeature.kt +4 -2
- package/project/.sdk/tm/kotlin/test/FeatureTest.kt +2 -2
- package/project/.sdk/tm/kotlin/utility/CoreUtil.kt +447 -11
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +38 -31
- package/project/.sdk/tm/kotlin/utility/Register.kt +1 -0
- package/project/.sdk/tm/lua/core/context.lua +43 -0
- package/project/.sdk/tm/lua/core/error.lua +36 -1
- package/project/.sdk/tm/lua/core/utility_type.lua +4 -0
- package/project/.sdk/tm/lua/feature/audit_feature.lua +2 -2
- package/project/.sdk/tm/lua/feature/cost_feature.lua +2 -2
- package/project/.sdk/tm/lua/feature/debug_feature.lua +21 -17
- package/project/.sdk/tm/lua/feature/log_feature.lua +2 -1
- package/project/.sdk/tm/lua/feature/proxy_feature.lua +23 -1
- package/project/.sdk/tm/lua/feature/secrets_feature.lua +18 -0
- package/project/.sdk/tm/lua/feature/telemetry_feature.lua +4 -2
- package/project/.sdk/tm/lua/test/feature/secrets/secrets_feature_test.lua +3 -2
- package/project/.sdk/tm/lua/test/feature_test.lua +10 -3
- package/project/.sdk/tm/lua/utility/clean.lua +426 -2
- package/project/.sdk/tm/lua/utility/clean_explain.lua +34 -0
- package/project/.sdk/tm/lua/utility/done.lua +1 -7
- package/project/.sdk/tm/lua/utility/make_error.lua +3 -1
- package/project/.sdk/tm/lua/utility/make_options.lua +76 -30
- package/project/.sdk/tm/lua/utility/register.lua +4 -1
- package/project/.sdk/tm/ocaml/Makefile +5 -1
- package/project/.sdk/tm/ocaml/feature/secrets_feature.ml +12 -1
- package/project/.sdk/tm/ocaml/sdk_features.ml +78 -18
- package/project/.sdk/tm/ocaml/sdk_helpers.ml +27 -0
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +304 -26
- package/project/.sdk/tm/ocaml/sdk_types.ml +1 -0
- package/project/.sdk/tm/ocaml/test/feature/secrets/t_secrets.ml +3 -1
- package/project/.sdk/tm/ocaml/test/t_feature.ml +2 -2
- package/project/.sdk/tm/perl/core/context.pm +27 -0
- package/project/.sdk/tm/perl/core/error.pm +52 -5
- package/project/.sdk/tm/perl/feature/audit_feature.pm +2 -2
- package/project/.sdk/tm/perl/feature/cost_feature.pm +2 -2
- package/project/.sdk/tm/perl/feature/debug_feature.pm +17 -13
- package/project/.sdk/tm/perl/feature/log_feature.pm +3 -1
- package/project/.sdk/tm/perl/feature/proxy_feature.pm +21 -1
- package/project/.sdk/tm/perl/feature/secrets_feature.pm +20 -1
- package/project/.sdk/tm/perl/feature/telemetry_feature.pm +4 -2
- package/project/.sdk/tm/perl/t/feature.t +4 -2
- package/project/.sdk/tm/perl/utility/clean.pm +345 -1
- package/project/.sdk/tm/perl/utility/done.pm +24 -6
- package/project/.sdk/tm/perl/utility/make_error.pm +17 -4
- package/project/.sdk/tm/perl/utility/make_options.pm +33 -14
- package/project/.sdk/tm/php/core/Context.php +24 -1
- package/project/.sdk/tm/php/core/Error.php +34 -3
- package/project/.sdk/tm/php/core/UtilityType.php +4 -0
- package/project/.sdk/tm/php/feature/AuditFeature.php +2 -2
- package/project/.sdk/tm/php/feature/CostFeature.php +2 -2
- package/project/.sdk/tm/php/feature/DebugFeature.php +18 -9
- package/project/.sdk/tm/php/feature/LogFeature.php +5 -2
- package/project/.sdk/tm/php/feature/ProxyFeature.php +20 -1
- package/project/.sdk/tm/php/feature/SecretsFeature.php +18 -0
- package/project/.sdk/tm/php/feature/TelemetryFeature.php +4 -2
- package/project/.sdk/tm/php/test/FeatureTest.php +2 -2
- package/project/.sdk/tm/php/utility/Clean.php +438 -2
- package/project/.sdk/tm/php/utility/Done.php +14 -7
- package/project/.sdk/tm/php/utility/MakeError.php +9 -7
- package/project/.sdk/tm/php/utility/MakeOptions.php +62 -38
- package/project/.sdk/tm/php/utility/Register.php +2 -0
- package/project/.sdk/tm/py/pkg/core/context.py +17 -0
- package/project/.sdk/tm/py/pkg/core/error.py +42 -1
- package/project/.sdk/tm/py/pkg/core/utility_type.py +4 -0
- package/project/.sdk/tm/py/pkg/feature/audit_feature.py +2 -2
- package/project/.sdk/tm/py/pkg/feature/cost_feature.py +2 -2
- package/project/.sdk/tm/py/pkg/feature/debug_feature.py +17 -12
- package/project/.sdk/tm/py/pkg/feature/log_feature.py +2 -1
- package/project/.sdk/tm/py/pkg/feature/proxy_feature.py +18 -1
- package/project/.sdk/tm/py/pkg/feature/secrets_feature.py +12 -0
- package/project/.sdk/tm/py/pkg/feature/telemetry_feature.py +4 -2
- package/project/.sdk/tm/py/pkg/utility/clean.py +311 -1
- package/project/.sdk/tm/py/pkg/utility/done.py +18 -5
- package/project/.sdk/tm/py/pkg/utility/make_error.py +9 -6
- package/project/.sdk/tm/py/pkg/utility/make_options.py +64 -25
- package/project/.sdk/tm/py/pkg/utility/make_response.py +19 -13
- package/project/.sdk/tm/py/pkg/utility/register.py +4 -2
- package/project/.sdk/tm/py/test/feature_harness.py +4 -0
- package/project/.sdk/tm/py/test/test_feature.py +2 -2
- package/project/.sdk/tm/py/test/test_primary_utility.py +1 -0
- package/project/.sdk/tm/rb/core/context.rb +28 -0
- package/project/.sdk/tm/rb/core/error.rb +30 -0
- package/project/.sdk/tm/rb/core/utility_type.rb +1 -1
- package/project/.sdk/tm/rb/feature/audit_feature.rb +2 -2
- package/project/.sdk/tm/rb/feature/cost_feature.rb +2 -2
- package/project/.sdk/tm/rb/feature/debug_feature.rb +15 -12
- package/project/.sdk/tm/rb/feature/log_feature.rb +3 -1
- package/project/.sdk/tm/rb/feature/proxy_feature.rb +25 -1
- package/project/.sdk/tm/rb/feature/secrets_feature.rb +17 -0
- package/project/.sdk/tm/rb/feature/telemetry_feature.rb +4 -2
- package/project/.sdk/tm/rb/test/feature_test.rb +4 -2
- package/project/.sdk/tm/rb/utility/clean.rb +312 -1
- package/project/.sdk/tm/rb/utility/done.rb +15 -5
- package/project/.sdk/tm/rb/utility/make_error.rb +13 -4
- package/project/.sdk/tm/rb/utility/make_options.rb +31 -12
- package/project/.sdk/tm/rb/utility/register.rb +2 -0
- package/project/.sdk/tm/rust/core/context.rs +55 -0
- package/project/.sdk/tm/rust/core/utility_type.rs +17 -0
- package/project/.sdk/tm/rust/feature/audit.rs +2 -0
- package/project/.sdk/tm/rust/feature/cost.rs +3 -2
- package/project/.sdk/tm/rust/feature/debug.rs +16 -16
- package/project/.sdk/tm/rust/feature/log.rs +42 -7
- package/project/.sdk/tm/rust/feature/proxy.rs +48 -2
- package/project/.sdk/tm/rust/feature/secrets.rs +12 -0
- package/project/.sdk/tm/rust/feature/telemetry.rs +2 -0
- package/project/.sdk/tm/rust/tests/feature_test.rs +2 -2
- package/project/.sdk/tm/rust/utility/clean.rs +446 -5
- package/project/.sdk/tm/rust/utility/done.rs +22 -12
- package/project/.sdk/tm/rust/utility/make_error.rs +4 -1
- package/project/.sdk/tm/rust/utility/make_options.rs +73 -19
- package/project/.sdk/tm/scala/Makefile +3 -1
- package/project/.sdk/tm/scala/core/Context.scala +16 -0
- package/project/.sdk/tm/scala/core/SdkClient.scala +14 -4
- package/project/.sdk/tm/scala/core/SdkError.scala +28 -0
- package/project/.sdk/tm/scala/core/Types.scala +3 -0
- package/project/.sdk/tm/scala/core/Utility.scala +2 -0
- package/project/.sdk/tm/scala/feature/AuditFeature.scala +16 -10
- package/project/.sdk/tm/scala/feature/CostFeature.scala +10 -5
- package/project/.sdk/tm/scala/feature/DebugFeature.scala +24 -21
- package/project/.sdk/tm/scala/feature/LogFeature.scala +9 -4
- package/project/.sdk/tm/scala/feature/ProxyFeature.scala +23 -2
- package/project/.sdk/tm/scala/feature/SecretsFeature.scala +14 -0
- package/project/.sdk/tm/scala/feature/TelemetryFeature.scala +9 -2
- package/project/.sdk/tm/scala/sdktest/SdkTestMain.scala +2 -2
- package/project/.sdk/tm/scala/utility/Make.scala +63 -20
- package/project/.sdk/tm/scala/utility/Misc.scala +358 -5
- package/project/.sdk/tm/scala/utility/Register.scala +1 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +29 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Utility.swift +2 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/AuditFeature.swift +10 -9
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/CostFeature.swift +7 -5
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/DebugFeature.swift +19 -15
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/FeatureOptions.swift +16 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/LogFeature.swift +4 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/ProxyFeature.swift +20 -5
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/SecretsFeature.swift +13 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TelemetryFeature.swift +4 -2
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeError.swift +15 -5
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +58 -19
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Register.swift +1 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +391 -4
- package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/FeatureTest.swift +2 -2
- package/project/.sdk/tm/ts/src/Context.ts +5 -1
- package/project/.sdk/tm/ts/src/feature/audit/AuditFeature.ts +2 -2
- package/project/.sdk/tm/ts/src/feature/cost/CostFeature.ts +2 -2
- package/project/.sdk/tm/ts/src/feature/debug/DebugFeature.ts +13 -7
- package/project/.sdk/tm/ts/src/feature/log/LogFeature.ts +9 -5
- package/project/.sdk/tm/ts/src/feature/proxy/ProxyFeature.ts +19 -1
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +12 -0
- package/project/.sdk/tm/ts/src/feature/telemetry/TelemetryFeature.ts +4 -2
- package/project/.sdk/tm/ts/src/utility/CleanUtility.ts +287 -9
- package/project/.sdk/tm/ts/src/utility/DoneUtility.ts +18 -5
- package/project/.sdk/tm/ts/src/utility/MakeErrorUtility.ts +18 -8
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +38 -16
- package/project/.sdk/tm/ts/src/utility/Utility.ts +4 -2
- package/project/.sdk/tm/ts/test/feature/harness.ts +11 -1
- package/project/.sdk/tm/ts/test/feature.test.ts +9 -2
- package/project/.sdk/tm/zig/core/context.zig +34 -0
- package/project/.sdk/tm/zig/core/error.zig +22 -1
- package/project/.sdk/tm/zig/core/utility.zig +403 -39
- package/project/.sdk/tm/zig/feature/audit.zig +4 -2
- package/project/.sdk/tm/zig/feature/cost.zig +3 -2
- package/project/.sdk/tm/zig/feature/debug.zig +20 -25
- package/project/.sdk/tm/zig/feature/log.zig +3 -1
- package/project/.sdk/tm/zig/feature/proxy.zig +47 -6
- package/project/.sdk/tm/zig/feature/secrets.zig +7 -0
- package/project/.sdk/tm/zig/feature/telemetry.zig +4 -2
- package/project/.sdk/tm/zig/test/feature_test.zig +2 -2
- package/project/sdkgen-package.json +1 -1
- package/src/helpers/naming.ts +1 -1
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cmp,
|
|
3
|
+
File,
|
|
4
|
+
Content,
|
|
5
|
+
isAuthSuppressed,
|
|
6
|
+
isHttpBasicAuth,
|
|
7
|
+
resolveAuthIn,
|
|
8
|
+
resolveAuthName,
|
|
9
|
+
} from '@voxgig/sdkgen'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
import { perlStringLiteral } from './utility_perl'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
// The canary sweep: every credential slot holds a distinctive value, every
|
|
16
|
+
// diagnostic feature this SDK ships is switched on with a capturing sink, a
|
|
17
|
+
// real operation runs through every outcome, and every string that leaves
|
|
18
|
+
// the SDK is searched for the canaries and their encoded forms. It also
|
|
19
|
+
// proves its own sensitivity: with clean switched off the canary MUST show.
|
|
20
|
+
const TestClean = cmp(function TestClean(props: any) {
|
|
21
|
+
const { model } = props.ctx$
|
|
22
|
+
|
|
23
|
+
const auth = {
|
|
24
|
+
suppressed: isAuthSuppressed(model),
|
|
25
|
+
where: resolveAuthIn(model),
|
|
26
|
+
name: 'header' === resolveAuthIn(model)
|
|
27
|
+
? resolveAuthName(model).toLowerCase() : resolveAuthName(model),
|
|
28
|
+
basic: isHttpBasicAuth(model),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
File({ name: 'clean.t' }, () => Content(render(model.const.Name, auth)))
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
function render(Name: string, auth: {
|
|
36
|
+
suppressed: boolean, where: string, name: string, basic: boolean
|
|
37
|
+
}): string {
|
|
38
|
+
return `#!perl
|
|
39
|
+
# ${Name} SDK clean test
|
|
40
|
+
#
|
|
41
|
+
# The canary sweep: every credential slot holds a distinctive value, every
|
|
42
|
+
# diagnostic feature this SDK ships is switched on with a capturing sink, a
|
|
43
|
+
# real operation runs through every outcome, and every string that leaves
|
|
44
|
+
# the SDK is searched for the canaries and their encoded forms. It also
|
|
45
|
+
# proves its own sensitivity: with clean switched off the canary MUST show.
|
|
46
|
+
|
|
47
|
+
use strict;
|
|
48
|
+
use warnings;
|
|
49
|
+
use Test::More;
|
|
50
|
+
use FindBin;
|
|
51
|
+
use lib "$FindBin::Bin/../lib";
|
|
52
|
+
use Scalar::Util ();
|
|
53
|
+
use MIME::Base64 ();
|
|
54
|
+
use Data::Dumper ();
|
|
55
|
+
use JSON::PP ();
|
|
56
|
+
|
|
57
|
+
use ${Name}SDK;
|
|
58
|
+
|
|
59
|
+
# Generated: the credential's wire placement is fixed when the SDK is built.
|
|
60
|
+
my %AUTH = (
|
|
61
|
+
'suppressed' => ${auth.suppressed ? 1 : 0},
|
|
62
|
+
'where' => ${perlStringLiteral(auth.where)},
|
|
63
|
+
'name' => ${perlStringLiteral(auth.name)},
|
|
64
|
+
'basic' => ${auth.basic ? 1 : 0},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
my %CANARY = (
|
|
68
|
+
'apikey' => 'CANARY-APIKEY-k9x2m7q4p1',
|
|
69
|
+
'secret' => 'CANARY-SECRET-w3e8r5t2y6',
|
|
70
|
+
'header' => 'CANARY-HEADER-z1x4c7v0b3',
|
|
71
|
+
'value' => 'CANARY-VALUE-n5m8b2v9c4',
|
|
72
|
+
'config' => 'CANARY-CONFIG-h6j3k8l2m5',
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
my $MASK = '[redacted]';
|
|
76
|
+
|
|
77
|
+
# encodeURIComponent, the form a query credential travels in.
|
|
78
|
+
sub pct {
|
|
79
|
+
my ($value) = @_;
|
|
80
|
+
my $b = "$value";
|
|
81
|
+
$b =~ s/([^A-Za-z0-9\\-_.!~*'()])/sprintf('%%%02X', ord($1))/ge;
|
|
82
|
+
return $b;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Every form a canary can travel in.
|
|
86
|
+
my @FORMS;
|
|
87
|
+
for my $v (sort values %CANARY) {
|
|
88
|
+
push @FORMS, $v, MIME::Base64::encode_base64($v, ''), pct($v);
|
|
89
|
+
}
|
|
90
|
+
push @FORMS, MIME::Base64::encode_base64("$CANARY{apikey}:$CANARY{secret}", '');
|
|
91
|
+
|
|
92
|
+
# Blessed objects encode through TO_JSON; anything else that cannot be
|
|
93
|
+
# encoded is null rather than a die.
|
|
94
|
+
my $JSON = JSON::PP->new->canonical->convert_blessed->allow_blessed->allow_unknown;
|
|
95
|
+
|
|
96
|
+
# Header maps keep the caller's spelling; the assertion should not care.
|
|
97
|
+
sub header {
|
|
98
|
+
my ($map, $name) = @_;
|
|
99
|
+
return undef unless ref $map && (Scalar::Util::reftype($map) // '') eq 'HASH';
|
|
100
|
+
for my $k (keys %$map) {
|
|
101
|
+
return $map->{$k} if lc($k) eq lc($name);
|
|
102
|
+
}
|
|
103
|
+
return undef;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
sub leaks {
|
|
107
|
+
my ($text) = @_;
|
|
108
|
+
return grep { index($text, $_) >= 0 } @FORMS;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
sub surface {
|
|
112
|
+
my ($out, $name, $kind, $fn) = @_;
|
|
113
|
+
my $text = eval { $fn->() };
|
|
114
|
+
return if $@;
|
|
115
|
+
push @$out, { 'name' => "$name:$kind", 'text' => (defined $text ? "$text" : '') };
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Every printed form of a value that left the SDK. A blessed object is
|
|
120
|
+
# read through its own serialisers (TO_JSON, to_string), the way a logger
|
|
121
|
+
# or a JSON encoder reads it; a plain record and the error are also dumped
|
|
122
|
+
# whole, since Data::Dumper and Test::More's explain print every key.
|
|
123
|
+
sub forms {
|
|
124
|
+
my ($name, $val) = @_;
|
|
125
|
+
my $out = [];
|
|
126
|
+
my $blessed = Scalar::Util::blessed($val);
|
|
127
|
+
my $sdkerr = $blessed && $val->isa('${Name}Error');
|
|
128
|
+
|
|
129
|
+
surface($out, $name, 'string', sub { "$val" });
|
|
130
|
+
surface($out, $name, 'json', sub { $JSON->encode($val) });
|
|
131
|
+
# The struct's own serialiser has no cycle guard, so a live object (what
|
|
132
|
+
# the negative control gets with clean off) is read only through JSON::PP,
|
|
133
|
+
# which stops at its nesting limit.
|
|
134
|
+
surface($out, $name, 'struct', sub { Voxgig::Struct::stringify($val) }) unless $blessed;
|
|
135
|
+
surface($out, $name, 'to_string', sub { $val->to_string })
|
|
136
|
+
if $blessed && $val->can('to_string');
|
|
137
|
+
|
|
138
|
+
if (!$blessed || $sdkerr) {
|
|
139
|
+
surface($out, $name, 'dumper', sub {
|
|
140
|
+
local $Data::Dumper::Sortkeys = 1;
|
|
141
|
+
local $Data::Dumper::Indent = 1;
|
|
142
|
+
Data::Dumper::Dumper($val);
|
|
143
|
+
});
|
|
144
|
+
surface($out, $name, 'explain', sub { join('', Test::More::explain($val)) });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if ($sdkerr) {
|
|
148
|
+
surface($out, $name, 'msg', sub { $val->{msg} });
|
|
149
|
+
surface($out, $name, 'fields', sub {
|
|
150
|
+
join(' ', map {
|
|
151
|
+
"$_=" . (defined $val->{$_}
|
|
152
|
+
? (ref $val->{$_} ? Voxgig::Struct::stringify($val->{$_}) : $val->{$_})
|
|
153
|
+
: 'undef')
|
|
154
|
+
} sort keys %$val);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return @$out;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
sub response {
|
|
161
|
+
my ($status, $data, $headers) = @_;
|
|
162
|
+
my %h = ('content-type' => 'application/json', %{ $headers || {} });
|
|
163
|
+
return {
|
|
164
|
+
'status' => $status,
|
|
165
|
+
'statusText' => ($status < 400 ? 'OK' : 'ERR'),
|
|
166
|
+
'headers' => \\%h,
|
|
167
|
+
'json' => sub { $data },
|
|
168
|
+
'body' => Voxgig::Struct::stringify($data),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Captures the serialised context from inside the pipeline: what a hook
|
|
173
|
+
# author would hand to a logger.
|
|
174
|
+
{
|
|
175
|
+
package ${Name}CleanCaptureFeature;
|
|
176
|
+
our @ISA = ('${Name}BaseFeature');
|
|
177
|
+
|
|
178
|
+
sub new {
|
|
179
|
+
my ($class, $sinks) = @_;
|
|
180
|
+
my $self = ${Name}BaseFeature::new($class);
|
|
181
|
+
$self->{name} = 'capture';
|
|
182
|
+
$self->{version} = '0.0.1';
|
|
183
|
+
$self->{active} = 1;
|
|
184
|
+
$self->{sinks} = $sinks;
|
|
185
|
+
return $self;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
sub PreRequest { my ($s, $ctx) = @_; push @{ $s->{sinks} }, main::forms('ctx@PreRequest', $ctx); return }
|
|
189
|
+
sub PreResponse { my ($s, $ctx) = @_; push @{ $s->{sinks} }, main::forms('ctx@PreResponse', $ctx); return }
|
|
190
|
+
|
|
191
|
+
# The SDK's own error as a hook reads it, which an observability feature
|
|
192
|
+
# logs.
|
|
193
|
+
sub PreUnexpected {
|
|
194
|
+
my ($s, $ctx) = @_;
|
|
195
|
+
push @{ $s->{sinks} }, main::forms('ctx@PreUnexpected', $ctx);
|
|
196
|
+
my $err = $ctx->{ctrl}{err};
|
|
197
|
+
push @{ $s->{sinks} }, main::forms('ctrl.err@PreUnexpected', $err)
|
|
198
|
+
if Scalar::Util::blessed($err) && $err->isa('${Name}Error');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# A feature that dies from inside the pipeline, quoting the request it saw:
|
|
204
|
+
# an error make_error never handled.
|
|
205
|
+
{
|
|
206
|
+
package ${Name}CleanThrowFeature;
|
|
207
|
+
our @ISA = ('${Name}BaseFeature');
|
|
208
|
+
|
|
209
|
+
sub new {
|
|
210
|
+
my ($class, $response, $unexpected) = @_;
|
|
211
|
+
my $self = ${Name}BaseFeature::new($class);
|
|
212
|
+
$self->{name} = 'throwhook';
|
|
213
|
+
$self->{version} = '0.0.1';
|
|
214
|
+
$self->{active} = 1;
|
|
215
|
+
$self->{response} = defined $response ? $response : 1;
|
|
216
|
+
$self->{unexpected} = $unexpected ? 1 : 0;
|
|
217
|
+
return $self;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
sub PreResponse {
|
|
221
|
+
my ($s, $ctx) = @_;
|
|
222
|
+
die 'hook saw ' . $JSON->encode({ %{ $ctx->{spec} } }) if $s->{response};
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# Fires in make_error, and in the catch path before its cleaning.
|
|
227
|
+
sub PreUnexpected {
|
|
228
|
+
my ($s, $ctx) = @_;
|
|
229
|
+
die 'hook saw ' . $JSON->encode({ %{ $ctx->{spec} } }) if $s->{unexpected};
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# A stream that succeeds; the pipeline's terminal step ran before it.
|
|
235
|
+
{
|
|
236
|
+
package ${Name}CleanStreamOkFeature;
|
|
237
|
+
our @ISA = ('${Name}BaseFeature');
|
|
238
|
+
|
|
239
|
+
sub new {
|
|
240
|
+
my ($class) = @_;
|
|
241
|
+
my $self = ${Name}BaseFeature::new($class);
|
|
242
|
+
$self->{name} = 'streamok';
|
|
243
|
+
$self->{version} = '0.0.1';
|
|
244
|
+
$self->{active} = 1;
|
|
245
|
+
return $self;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
sub PreDone {
|
|
249
|
+
my ($s, $ctx) = @_;
|
|
250
|
+
my $data = $ctx->{result}{resdata};
|
|
251
|
+
my @items = ref $data eq 'ARRAY' ? @$data : (defined $data ? ($data) : ());
|
|
252
|
+
$ctx->{result}{stream} = sub { return shift @items };
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# A stream that fails while the caller pulls from it, quoting a credential.
|
|
258
|
+
{
|
|
259
|
+
package ${Name}CleanStreamThrowFeature;
|
|
260
|
+
our @ISA = ('${Name}BaseFeature');
|
|
261
|
+
|
|
262
|
+
sub new {
|
|
263
|
+
my ($class) = @_;
|
|
264
|
+
my $self = ${Name}BaseFeature::new($class);
|
|
265
|
+
$self->{name} = 'streamthrow';
|
|
266
|
+
$self->{version} = '0.0.1';
|
|
267
|
+
$self->{active} = 1;
|
|
268
|
+
return $self;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
sub PreDone {
|
|
272
|
+
my ($s, $ctx) = @_;
|
|
273
|
+
$ctx->{result}{stream} = sub { die "stream saw $CANARY{apikey}\\n" };
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
# Each scenario answers the transport's (response, err) pair.
|
|
279
|
+
my @SCENARIOS = (
|
|
280
|
+
['ok', sub {
|
|
281
|
+
return (response(200, { 'id' => 'i1', 'name' => 'n1' },
|
|
282
|
+
{ 'x-session-token' => 'RESP-TOKEN-a1b2c3d4e5' }), undef);
|
|
283
|
+
}],
|
|
284
|
+
['notfound', sub { return (response(404, { 'error' => 'no such record' }), undef) }],
|
|
285
|
+
['server', sub { return (response(500, { 'error' => 'boom' }), undef) }],
|
|
286
|
+
['transport', sub {
|
|
287
|
+
my ($url) = @_;
|
|
288
|
+
return (undef, "socket hang up (URL was: \\"$url\\")");
|
|
289
|
+
}],
|
|
290
|
+
# The SDK's own error, its code quoting a registered value.
|
|
291
|
+
['coded', sub { return (undef, ${Name}Error->new("denied_$CANARY{apikey}", 'coded failure')) }],
|
|
292
|
+
['notjson', sub {
|
|
293
|
+
return ({
|
|
294
|
+
'status' => 200, 'statusText' => 'OK', 'headers' => {},
|
|
295
|
+
'json' => sub { die "Unexpected token < in JSON\\n" },
|
|
296
|
+
'body' => '<html>',
|
|
297
|
+
}, undef);
|
|
298
|
+
}],
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
my @VARIANTS = (
|
|
302
|
+
['throw', sub { return {} }],
|
|
303
|
+
['explain', sub { return { 'explain' => {} } }],
|
|
304
|
+
['nothrow', sub { return { 'throw' => 0, 'explain' => {} } }],
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
# True when this SDK was generated with the named feature.
|
|
308
|
+
sub has_feature {
|
|
309
|
+
my ($name) = @_;
|
|
310
|
+
my $f = ${Name}Config::shared_config()->{feature};
|
|
311
|
+
return (Voxgig::Struct::ismap($f) && defined $f->{$name}) ? 1 : 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
sub make_sdk {
|
|
315
|
+
my ($scenario, $sinks, $cleanopts, $extra) = @_;
|
|
316
|
+
my $capture = sub {
|
|
317
|
+
my ($name) = @_;
|
|
318
|
+
return sub { push @$sinks, forms($name, $_[0]); return };
|
|
319
|
+
};
|
|
320
|
+
my %feature;
|
|
321
|
+
$feature{log} = { 'active' => 1,
|
|
322
|
+
'logger' => sub { push @$sinks, { 'name' => 'log', 'text' => "$_[0]" }; return } }
|
|
323
|
+
if has_feature('log');
|
|
324
|
+
$feature{debug} = { 'active' => 1, 'on_entry' => $capture->('debug') } if has_feature('debug');
|
|
325
|
+
$feature{audit} = { 'active' => 1, 'sink' => $capture->('audit') } if has_feature('audit');
|
|
326
|
+
$feature{telemetry} = { 'active' => 1, 'exporter' => $capture->('telemetry') }
|
|
327
|
+
if has_feature('telemetry');
|
|
328
|
+
$feature{cost} = { 'active' => 1, 'sink' => $capture->('cost') } if has_feature('cost');
|
|
329
|
+
$feature{metrics} = { 'active' => 1 } if has_feature('metrics');
|
|
330
|
+
$feature{clienttrack} = { 'active' => 1 } if has_feature('clienttrack');
|
|
331
|
+
|
|
332
|
+
my $respond = $scenario->[1];
|
|
333
|
+
return ${Name}SDK->new({
|
|
334
|
+
'apikey' => $CANARY{apikey},
|
|
335
|
+
'secret' => $CANARY{secret},
|
|
336
|
+
'headers' => { 'X-Custom-Token' => $CANARY{header} },
|
|
337
|
+
'clean' => { 'values' => $CANARY{value}, %{ $cleanopts || {} } },
|
|
338
|
+
'feature' => \\%feature,
|
|
339
|
+
'extend' => [ ${Name}CleanCaptureFeature->new($sinks), @{ $extra || [] } ],
|
|
340
|
+
'utility' => { 'fetcher' => sub {
|
|
341
|
+
my (undef, $url, $fetchdef) = @_;
|
|
342
|
+
return $respond->($url, $fetchdef);
|
|
343
|
+
} },
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# The first operation that completes against a plain 200: with no
|
|
348
|
+
# arguments, else with every path parameter its points declare filled in.
|
|
349
|
+
# An entity accessor is a capitalised client method whose result answers
|
|
350
|
+
# get_name, as the feature corpus runner finds them.
|
|
351
|
+
sub usable_op {
|
|
352
|
+
my $plain = ${Name}SDK->new({
|
|
353
|
+
'apikey' => $CANARY{apikey},
|
|
354
|
+
'utility' => { 'fetcher' => sub { return (response(200, { 'id' => 'i1' }), undef) } },
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
my %found;
|
|
358
|
+
my $pkg = ref $plain;
|
|
359
|
+
{
|
|
360
|
+
no strict 'refs';
|
|
361
|
+
for my $sym (sort keys %{"\${pkg}::"}) {
|
|
362
|
+
next unless $sym =~ /^[A-Z]/;
|
|
363
|
+
next unless defined &{"\${pkg}::\${sym}"};
|
|
364
|
+
my $ent = eval { $plain->$sym() };
|
|
365
|
+
next unless defined $ent && Scalar::Util::blessed($ent) && $ent->can('get_name');
|
|
366
|
+
my $entname = eval { $ent->get_name };
|
|
367
|
+
next unless defined $entname && length $entname;
|
|
368
|
+
$found{$entname} = $sym;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
my $entities = ${Name}Config::shared_config()->{entity} || {};
|
|
373
|
+
for my $entname (sort keys %found) {
|
|
374
|
+
my $accessor = $found{$entname};
|
|
375
|
+
for my $op (qw(list load create update remove)) {
|
|
376
|
+
next unless $plain->$accessor()->can($op);
|
|
377
|
+
my %filled;
|
|
378
|
+
my $opdef = eval { $entities->{$entname}{op}{$op} };
|
|
379
|
+
my $points = ref $opdef eq 'HASH' ? $opdef->{points} : undef;
|
|
380
|
+
for my $point (ref $points eq 'ARRAY' ? @$points : ()) {
|
|
381
|
+
my $params = ref $point eq 'HASH' && ref $point->{args} eq 'HASH'
|
|
382
|
+
? $point->{args}{params} : undef;
|
|
383
|
+
for my $p (ref $params eq 'ARRAY' ? @$params : ()) {
|
|
384
|
+
$filled{ $p->{name} } = 'p1' if ref $p eq 'HASH' && defined $p->{name} && !ref $p->{name};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
for my $match ({}, \\%filled) {
|
|
388
|
+
my $ok = eval { $plain->$accessor()->$op({ %$match }, {}); 1 };
|
|
389
|
+
return { 'accessor' => $accessor, 'op' => $op, 'match' => $match } if $ok;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return undef;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
sub drive {
|
|
397
|
+
my ($sdk, $target, $ctrl, $sinks) = @_;
|
|
398
|
+
# A caller may keep the record it passed rather than read ctrl.explain.
|
|
399
|
+
my $held = $ctrl->{explain};
|
|
400
|
+
my ($acc, $op) = ($target->{accessor}, $target->{op});
|
|
401
|
+
my $out;
|
|
402
|
+
my $ok = eval { $out = $sdk->$acc()->$op({ %{ $target->{match} } }, $ctrl); 1 };
|
|
403
|
+
my $err = $ok ? undef : $@;
|
|
404
|
+
push @$sinks, forms('error', $err) if defined $err;
|
|
405
|
+
push @$sinks, forms('result', $out) if defined $out;
|
|
406
|
+
push @$sinks, forms('explain', $ctrl->{explain}) if defined $ctrl->{explain};
|
|
407
|
+
push @$sinks, forms('explain:held', $held) if defined $held && (!defined $ctrl->{explain}
|
|
408
|
+
|| Scalar::Util::refaddr($held) != Scalar::Util::refaddr($ctrl->{explain}));
|
|
409
|
+
return $err;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
my $target = usable_op();
|
|
414
|
+
plan skip_all => 'no operation of this SDK completes against a plain 200; nothing to sweep'
|
|
415
|
+
unless defined $target;
|
|
416
|
+
|
|
417
|
+
{
|
|
418
|
+
my @sinks;
|
|
419
|
+
my %errors;
|
|
420
|
+
my %explains;
|
|
421
|
+
|
|
422
|
+
for my $scenario (@SCENARIOS) {
|
|
423
|
+
for my $variant (@VARIANTS) {
|
|
424
|
+
my ($vname, $make_ctrl) = @$variant;
|
|
425
|
+
my $sdk = make_sdk($scenario, \\@sinks);
|
|
426
|
+
my $ctrl = $make_ctrl->();
|
|
427
|
+
my $err = drive($sdk, $target, $ctrl, \\@sinks);
|
|
428
|
+
my $key = "$scenario->[0]/$vname";
|
|
429
|
+
$errors{$key} = $err if defined $err;
|
|
430
|
+
$explains{$key} = $ctrl->{explain} if defined $ctrl->{explain};
|
|
431
|
+
push @sinks, forms('sdk', $sdk);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# A credential mistyped as a map is rejected by validation, whose message
|
|
436
|
+
# quotes the value it rejected.
|
|
437
|
+
my $rejected;
|
|
438
|
+
eval {
|
|
439
|
+
${Name}SDK->new({
|
|
440
|
+
'apikey' => { 'value' => $CANARY{apikey} },
|
|
441
|
+
'clean' => { 'values' => $CANARY{value} },
|
|
442
|
+
});
|
|
443
|
+
1;
|
|
444
|
+
} or $rejected = $@;
|
|
445
|
+
ok(defined $rejected, 'a credential mistyped as a map is rejected');
|
|
446
|
+
push @sinks, forms('rejected', $rejected) if defined $rejected;
|
|
447
|
+
|
|
448
|
+
# An error a feature hook dies with, quoting the request, skips make_error.
|
|
449
|
+
# The variant dying only in PreUnexpected reaches make_error's own firing
|
|
450
|
+
# through a 404.
|
|
451
|
+
my $hooked;
|
|
452
|
+
for my $hook ([ $SCENARIOS[0], 1, 0 ], [ $SCENARIOS[0], 1, 1 ], [ $SCENARIOS[1], 0, 1 ]) {
|
|
453
|
+
my ($scenario, $response, $unexpected) = @$hook;
|
|
454
|
+
$hooked = make_sdk($scenario, \\@sinks, undef,
|
|
455
|
+
[ ${Name}CleanThrowFeature->new($response, $unexpected) ]);
|
|
456
|
+
ok(defined drive($hooked, $target, { 'explain' => {} }, \\@sinks),
|
|
457
|
+
'the throwing hook fails the operation');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
# Pulling from a stream runs inside the same catch path as the operation,
|
|
461
|
+
# and the explain record the caller passed is cleaned however it ends.
|
|
462
|
+
for my $case ([ 'stream', ${Name}CleanStreamThrowFeature->new ],
|
|
463
|
+
[ 'stream-ok', ${Name}CleanStreamOkFeature->new ], [ 'stream-plain' ]) {
|
|
464
|
+
my ($name, @extra) = @$case;
|
|
465
|
+
my $streamed = make_sdk($SCENARIOS[0], \\@sinks, undef, [ @extra ]);
|
|
466
|
+
my $explain = {};
|
|
467
|
+
my $streamerr;
|
|
468
|
+
eval {
|
|
469
|
+
my ($acc, $op) = ($target->{accessor}, $target->{op});
|
|
470
|
+
my $next = $streamed->$acc()->stream($op, { 'reqmatch' => { %{ $target->{match} } } },
|
|
471
|
+
{ 'ctrl' => { 'explain' => $explain } });
|
|
472
|
+
1 while defined $next->();
|
|
473
|
+
1;
|
|
474
|
+
} or $streamerr = $@;
|
|
475
|
+
is(defined $streamerr ? 1 : 0, 'stream' eq $name ? 1 : 0, "$name: only the failing stream dies");
|
|
476
|
+
push @sinks, forms($name, $streamerr) if defined $streamerr;
|
|
477
|
+
ok(scalar(keys %$explain) > 0, "$name: the explain record is filled");
|
|
478
|
+
push @sinks, forms("$name:explain", $explain);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
# A registered value used as a property name is masked; names that mask
|
|
482
|
+
# alike are all kept.
|
|
483
|
+
my $named = $hooked->get_utility()->{clean}->($hooked->get_root_ctx(),
|
|
484
|
+
{ $CANARY{header} => 1, $CANARY{value} => 2, 'plain' => 3 });
|
|
485
|
+
is_deeply($named, { $MASK => 1, "$MASK#1" => 2, 'plain' => 3 },
|
|
486
|
+
'a registered value used as a property name is masked, collisions kept');
|
|
487
|
+
push @sinks, forms('named', $named);
|
|
488
|
+
|
|
489
|
+
# The generated config's own clean block is read beside the caller's, and
|
|
490
|
+
# is not changed by it.
|
|
491
|
+
my $util = $hooked->get_utility();
|
|
492
|
+
my $cfgclean = { 'keys' => 'zzsens', 'values' => $CANARY{config} };
|
|
493
|
+
my $built = $util->{make_options}->($util->{make_context}->({
|
|
494
|
+
'utility' => $util,
|
|
495
|
+
'config' => { 'options' => { 'clean' => $cfgclean } },
|
|
496
|
+
'options' => { 'clean' => { 'values' => $CANARY{value} } },
|
|
497
|
+
}, undef));
|
|
498
|
+
my $cfgctx = $util->{make_context}->({ 'options' => $built }, undef);
|
|
499
|
+
my $seeded = $util->{clean}->($cfgctx, "config $CANARY{config} caller $CANARY{value}");
|
|
500
|
+
push @sinks, { 'name' => 'config-clean', 'text' => "$seeded" };
|
|
501
|
+
is($seeded, "config $MASK caller $MASK", "the config's clean values are registered");
|
|
502
|
+
is_deeply($util->{clean}->($cfgctx, { 'my_zzsens' => 'x', 'other' => 'y' }),
|
|
503
|
+
{ 'my_zzsens' => $MASK, 'other' => 'y' }, "the config's clean keys apply");
|
|
504
|
+
is_deeply($cfgclean, { 'keys' => 'zzsens', 'values' => $CANARY{config} },
|
|
505
|
+
"the config's clean block is unchanged");
|
|
506
|
+
|
|
507
|
+
# With no clean option at all, the schema defaults still apply.
|
|
508
|
+
my $bare = ${Name}SDK->new({
|
|
509
|
+
'apikey' => $CANARY{apikey},
|
|
510
|
+
'secret' => $CANARY{secret},
|
|
511
|
+
'headers' => { 'X-Custom-Token' => $CANARY{header} },
|
|
512
|
+
'utility' => { 'fetcher' => sub {
|
|
513
|
+
my (undef, $url, $fetchdef) = @_;
|
|
514
|
+
return $SCENARIOS[1][1]->($url, $fetchdef);
|
|
515
|
+
} },
|
|
516
|
+
});
|
|
517
|
+
ok(defined drive($bare, $target, { 'explain' => {} }, \\@sinks),
|
|
518
|
+
'the 404 fails without a clean option');
|
|
519
|
+
|
|
520
|
+
# A feature's name is not a field name: only the sensitive names inside
|
|
521
|
+
# its settings register. An entity block, of per-entity settings or seeded
|
|
522
|
+
# records keyed by entity name and id, is not read at all.
|
|
523
|
+
my $featured = ${Name}SDK->new({
|
|
524
|
+
'apikey' => $CANARY{apikey},
|
|
525
|
+
'feature' => {
|
|
526
|
+
'zzsecrets' => { 'active' => 0, 'kind' => 'PLAINSETTING-q8w2e4r6' },
|
|
527
|
+
'zzfeat' => { 'active' => 0, 'apitoken' => 'FEATTOKEN-z9y8x7w6' },
|
|
528
|
+
'test' => { 'active' => 0, 'entity' => {
|
|
529
|
+
'zztoken' => { 'ZZTOKEN01' => { 'note' => 'PLAINRECORD-t5r3e1w9' } } } },
|
|
530
|
+
},
|
|
531
|
+
'entity' => { 'zztoken' => { 'alias' => { 'zzkey' => 'PLAINALIAS-m2n4b6v8' } } },
|
|
532
|
+
});
|
|
533
|
+
my $fclean = $featured->get_utility()->{clean};
|
|
534
|
+
my $fplain = $fclean->($featured->get_root_ctx(), 'kind PLAINSETTING-q8w2e4r6');
|
|
535
|
+
my $ftoken = $fclean->($featured->get_root_ctx(), 'token FEATTOKEN-z9y8x7w6');
|
|
536
|
+
my $frecord = $fclean->($featured->get_root_ctx(), 'record PLAINRECORD-t5r3e1w9');
|
|
537
|
+
my $falias = $fclean->($featured->get_root_ctx(), 'alias PLAINALIAS-m2n4b6v8');
|
|
538
|
+
|
|
539
|
+
my @leaked = grep { @{ $_->{found} } }
|
|
540
|
+
map { { 'name' => $_->{name}, 'found' => [ leaks($_->{text}) ] } } @sinks;
|
|
541
|
+
|
|
542
|
+
note(sprintf('clean: swept %d surface(s), %d leak(s)', scalar(@sinks), scalar(@leaked)));
|
|
543
|
+
|
|
544
|
+
is(scalar(@leaked), 0, 'no credential leaves the SDK in any form')
|
|
545
|
+
or diag('credential leaked through: ' . join('; ',
|
|
546
|
+
map { "$_->{name} [" . join(', ', @{ $_->{found} }) . ']' } @leaked));
|
|
547
|
+
|
|
548
|
+
# The positive half: the slot the credential travelled in is masked,
|
|
549
|
+
# and an unregistered token in a response header is masked by name.
|
|
550
|
+
my $notfound = $errors{'notfound/throw'};
|
|
551
|
+
ok(Scalar::Util::blessed($notfound) && $notfound->isa('${Name}Error'),
|
|
552
|
+
'the 404 scenario dies with the SDK error');
|
|
553
|
+
is($notfound->{status}, 404, 'the error carries the status');
|
|
554
|
+
unless ($AUTH{suppressed}) {
|
|
555
|
+
my $spec = ref $notfound->{spec} ? $notfound->{spec} : {};
|
|
556
|
+
if ('query' eq $AUTH{where}) {
|
|
557
|
+
is(header($spec->{query}, $AUTH{name}), $MASK, "$AUTH{name} is masked");
|
|
558
|
+
}
|
|
559
|
+
elsif ('cookie' eq $AUTH{where}) {
|
|
560
|
+
my $cookie = header($spec->{headers}, 'cookie');
|
|
561
|
+
$cookie = '' unless defined $cookie;
|
|
562
|
+
ok(index($cookie, $MASK) >= 0, "cookie is masked: $cookie");
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
my $got = header($spec->{headers}, $AUTH{name});
|
|
566
|
+
$got = '' unless defined $got;
|
|
567
|
+
ok(length($got) >= length($MASK) && substr($got, -length($MASK)) eq $MASK,
|
|
568
|
+
"$AUTH{name} is masked: $got");
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
is(header($notfound->{spec}{headers}, 'x-custom-token'), $MASK, 'x-custom-token is masked');
|
|
572
|
+
|
|
573
|
+
my $coded = $errors{'coded/throw'};
|
|
574
|
+
ok(Scalar::Util::blessed($coded) && $coded->isa('${Name}Error')
|
|
575
|
+
&& $coded->{code} eq "denied_$MASK", 'the coded error keeps its code, masked');
|
|
576
|
+
|
|
577
|
+
is($fplain, 'kind PLAINSETTING-q8w2e4r6', "a feature's name does not register its settings");
|
|
578
|
+
is($ftoken, "token $MASK", 'a sensitive setting inside a feature registers');
|
|
579
|
+
is($frecord, 'record PLAINRECORD-t5r3e1w9', 'the records a test entity block seeds do not register');
|
|
580
|
+
is($falias, 'alias PLAINALIAS-m2n4b6v8', 'a per-entity setting does not register');
|
|
581
|
+
|
|
582
|
+
my $explained = $explains{'ok/explain'} || {};
|
|
583
|
+
ok(defined $explained->{result}, 'the explain record carries the result');
|
|
584
|
+
is(header($explained->{result}{headers}, 'x-session-token'), $MASK,
|
|
585
|
+
'x-session-token in the response is masked');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
# The sweep can see a leak: clean switched off shows the credential.
|
|
589
|
+
{
|
|
590
|
+
my @sinks;
|
|
591
|
+
my $sdk = make_sdk($SCENARIOS[1], \\@sinks, { 'active' => 0 });
|
|
592
|
+
my $err = drive($sdk, $target, {}, \\@sinks);
|
|
593
|
+
ok(defined $err, 'clean off: the 404 still dies');
|
|
594
|
+
|
|
595
|
+
# Explaining a failure must not cost it its error.
|
|
596
|
+
my $explained = drive(make_sdk($SCENARIOS[1], [], { 'active' => 0 }), $target, { 'explain' => {} }, []);
|
|
597
|
+
is(ref $explained ? $explained->{msg} : $explained, $err->{msg}, 'with clean off, explain keeps the error');
|
|
598
|
+
|
|
599
|
+
my @leaked = grep { leaks($_->{text}) } @sinks;
|
|
600
|
+
ok(scalar(@leaked) > 0, 'with clean off, the canary shows: the sweep is not blind');
|
|
601
|
+
|
|
602
|
+
unless ($AUTH{suppressed}) {
|
|
603
|
+
# With clean off the spec is the live object; the struct's own
|
|
604
|
+
# serialiser prints every field.
|
|
605
|
+
my $text = Voxgig::Struct::stringify($err->{spec});
|
|
606
|
+
my $pair = MIME::Base64::encode_base64("$CANARY{apikey}:$CANARY{secret}", '');
|
|
607
|
+
ok(index($text, $CANARY{apikey}) >= 0 || index($text, $pair) >= 0,
|
|
608
|
+
'the raw spec carries the credential when clean is off');
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
done_testing();
|
|
613
|
+
`
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
export {
|
|
618
|
+
TestClean
|
|
619
|
+
}
|
|
@@ -9,6 +9,7 @@ import { cmp, each, Folder, File, Content, entityCollection,
|
|
|
9
9
|
|
|
10
10
|
import { TestEntity } from './TestEntity_perl'
|
|
11
11
|
import { TestDirect } from './TestDirect_perl'
|
|
12
|
+
import { TestClean } from './TestClean_perl'
|
|
12
13
|
import { ReadmeExamplesTest } from './ReadmeExamplesTest_perl'
|
|
13
14
|
|
|
14
15
|
|
|
@@ -49,6 +50,8 @@ done_testing();
|
|
|
49
50
|
TestDirect({ target, entity })
|
|
50
51
|
})
|
|
51
52
|
|
|
53
|
+
TestClean({ target })
|
|
54
|
+
|
|
52
55
|
// README example snippet gate (syntax + offline test-mode run).
|
|
53
56
|
ReadmeExamplesTest({ target })
|
|
54
57
|
})
|