@voxgig/sdkgen-langpack 0.0.1
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/.sdk/model/target/dart.aon +63 -0
- package/.sdk/model/target/haskell.aon +72 -0
- package/.sdk/model/target/lean.aon +91 -0
- package/.sdk/src/cmp/dart/Config_dart.ts +248 -0
- package/.sdk/src/cmp/dart/EntityBase_dart.ts +34 -0
- package/.sdk/src/cmp/dart/EntityOperation_dart.ts +43 -0
- package/.sdk/src/cmp/dart/EntityTypes_dart.ts +207 -0
- package/.sdk/src/cmp/dart/Entity_dart.ts +87 -0
- package/.sdk/src/cmp/dart/Gitignore_dart.ts +34 -0
- package/.sdk/src/cmp/dart/MainEntity_dart.ts +33 -0
- package/.sdk/src/cmp/dart/Main_dart.ts +178 -0
- package/.sdk/src/cmp/dart/Package_dart.ts +82 -0
- package/.sdk/src/cmp/dart/ReadmeEntity_dart.ts +177 -0
- package/.sdk/src/cmp/dart/ReadmeExamplesTest_dart.ts +238 -0
- package/.sdk/src/cmp/dart/ReadmeExplanation_dart.ts +53 -0
- package/.sdk/src/cmp/dart/ReadmeHowto_dart.ts +152 -0
- package/.sdk/src/cmp/dart/ReadmeInstall_dart.ts +59 -0
- package/.sdk/src/cmp/dart/ReadmeIntro_dart.ts +65 -0
- package/.sdk/src/cmp/dart/ReadmeModel_dart.ts +151 -0
- package/.sdk/src/cmp/dart/ReadmeOptions_dart.ts +63 -0
- package/.sdk/src/cmp/dart/ReadmeQuick_dart.ts +225 -0
- package/.sdk/src/cmp/dart/ReadmeRef_dart.ts +397 -0
- package/.sdk/src/cmp/dart/ReadmeTopHowto_dart.ts +24 -0
- package/.sdk/src/cmp/dart/ReadmeTopQuick_dart.ts +106 -0
- package/.sdk/src/cmp/dart/ReadmeTopTest_dart.ts +78 -0
- package/.sdk/src/cmp/dart/SdkError_dart.ts +42 -0
- package/.sdk/src/cmp/dart/TestDirect_dart.ts +516 -0
- package/.sdk/src/cmp/dart/TestEntity_dart.ts +628 -0
- package/.sdk/src/cmp/dart/Test_dart.ts +138 -0
- package/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +98 -0
- package/.sdk/src/cmp/dart/fragment/Config.fragment.dart +70 -0
- package/.sdk/src/cmp/dart/fragment/Direct.test.fragment.dart +25 -0
- package/.sdk/src/cmp/dart/fragment/Entity.fragment.dart +29 -0
- package/.sdk/src/cmp/dart/fragment/Entity.test.fragment.dart +28 -0
- package/.sdk/src/cmp/dart/fragment/EntityBase.fragment.dart +302 -0
- package/.sdk/src/cmp/dart/fragment/EntityCreateOp.fragment.dart +98 -0
- package/.sdk/src/cmp/dart/fragment/EntityListOp.fragment.dart +90 -0
- package/.sdk/src/cmp/dart/fragment/EntityLoadOp.fragment.dart +102 -0
- package/.sdk/src/cmp/dart/fragment/EntityRemoveOp.fragment.dart +108 -0
- package/.sdk/src/cmp/dart/fragment/EntityUpdateOp.fragment.dart +102 -0
- package/.sdk/src/cmp/dart/fragment/Main.fragment.dart +339 -0
- package/.sdk/src/cmp/dart/fragment/SdkError.fragment.dart +26 -0
- package/.sdk/src/cmp/dart/tsconfig.json +15 -0
- package/.sdk/src/cmp/dart/utility_dart.ts +155 -0
- package/.sdk/src/cmp/haskell/Config_haskell.ts +98 -0
- package/.sdk/src/cmp/haskell/Entity_haskell.ts +13 -0
- package/.sdk/src/cmp/haskell/Gitignore_haskell.ts +27 -0
- package/.sdk/src/cmp/haskell/Main_haskell.ts +123 -0
- package/.sdk/src/cmp/haskell/Package_haskell.ts +60 -0
- package/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +186 -0
- package/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +135 -0
- package/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +48 -0
- package/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +168 -0
- package/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +51 -0
- package/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +67 -0
- package/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +162 -0
- package/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +74 -0
- package/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +241 -0
- package/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +408 -0
- package/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +31 -0
- package/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +104 -0
- package/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +80 -0
- package/.sdk/src/cmp/haskell/Test_haskell.ts +297 -0
- package/.sdk/src/cmp/haskell/tsconfig.json +15 -0
- package/.sdk/src/cmp/haskell/utility_haskell.ts +166 -0
- package/.sdk/src/cmp/lean/Config_lean.ts +93 -0
- package/.sdk/src/cmp/lean/Entity_lean.ts +9 -0
- package/.sdk/src/cmp/lean/Gitignore_lean.ts +18 -0
- package/.sdk/src/cmp/lean/Main_lean.ts +120 -0
- package/.sdk/src/cmp/lean/Package_lean.ts +93 -0
- package/.sdk/src/cmp/lean/ReadmeEntity_lean.ts +29 -0
- package/.sdk/src/cmp/lean/ReadmeExplanation_lean.ts +19 -0
- package/.sdk/src/cmp/lean/ReadmeHowto_lean.ts +25 -0
- package/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +39 -0
- package/.sdk/src/cmp/lean/ReadmeIntro_lean.ts +25 -0
- package/.sdk/src/cmp/lean/ReadmeModel_lean.ts +24 -0
- package/.sdk/src/cmp/lean/ReadmeOptions_lean.ts +22 -0
- package/.sdk/src/cmp/lean/ReadmeQuick_lean.ts +50 -0
- package/.sdk/src/cmp/lean/ReadmeRef_lean.ts +40 -0
- package/.sdk/src/cmp/lean/ReadmeTopHowto_lean.ts +16 -0
- package/.sdk/src/cmp/lean/ReadmeTopQuick_lean.ts +23 -0
- package/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +19 -0
- package/.sdk/src/cmp/lean/Test_lean.ts +261 -0
- package/.sdk/src/cmp/lean/utility_lean.ts +129 -0
- package/.sdk/tm/dart/LICENSE +22 -0
- package/.sdk/tm/dart/Makefile +50 -0
- package/.sdk/tm/dart/lib/Context.dart +150 -0
- package/.sdk/tm/dart/lib/Control.dart +15 -0
- package/.sdk/tm/dart/lib/Operation.dart +31 -0
- package/.sdk/tm/dart/lib/Point.dart +51 -0
- package/.sdk/tm/dart/lib/Response.dart +32 -0
- package/.sdk/tm/dart/lib/Result.dart +39 -0
- package/.sdk/tm/dart/lib/Spec.dart +49 -0
- package/.sdk/tm/dart/lib/feature/audit/AuditFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/base/BaseFeature.dart +76 -0
- package/.sdk/tm/dart/lib/feature/cache/CacheFeature.dart +152 -0
- package/.sdk/tm/dart/lib/feature/clienttrack/ClienttrackFeature.dart +104 -0
- package/.sdk/tm/dart/lib/feature/cost/CostFeature.dart +446 -0
- package/.sdk/tm/dart/lib/feature/debug/DebugFeature.dart +153 -0
- package/.sdk/tm/dart/lib/feature/idempotency/IdempotencyFeature.dart +92 -0
- package/.sdk/tm/dart/lib/feature/log/LogFeature.dart +85 -0
- package/.sdk/tm/dart/lib/feature/metrics/MetricsFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/netsim/NetsimFeature.dart +186 -0
- package/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +240 -0
- package/.sdk/tm/dart/lib/feature/proxy/ProxyFeature.dart +118 -0
- package/.sdk/tm/dart/lib/feature/ratelimit/RatelimitFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/rbac/RbacFeature.dart +121 -0
- package/.sdk/tm/dart/lib/feature/retry/RetryFeature.dart +155 -0
- package/.sdk/tm/dart/lib/feature/secrets/SecretsFeature.dart +649 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/capability.dart +105 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/catalog.dart +46 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/config.dart +297 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/depend.dart +198 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/env.dart +144 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/export.dart +58 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/graph.dart +160 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/host.dart +1067 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/order.dart +172 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/plugin.dart +23 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/point.dart +117 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/ref.dart +105 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/resolve.dart +58 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/types.dart +201 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/version.dart +116 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/aws.dart +285 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/azuresecrets.dart +178 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/boru.dart +145 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/crypto.dart +194 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/doppler.dart +101 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/gcpsecrets.dart +143 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/hashicorp.dart +190 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/httpjson.dart +363 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/infisical.dart +149 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/onepassword.dart +146 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/secretspec.dart +116 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/sigv4.dart +237 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/addr.dart +138 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/json.dart +276 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/provider.dart +31 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/providers.dart +243 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/sekreto.dart +730 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/spec.dart +200 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/support.dart +217 -0
- package/.sdk/tm/dart/lib/feature/streaming/StreamingFeature.dart +97 -0
- package/.sdk/tm/dart/lib/feature/telemetry/TelemetryFeature.dart +136 -0
- package/.sdk/tm/dart/lib/feature/test/TestFeature.dart +328 -0
- package/.sdk/tm/dart/lib/feature/timeout/TimeoutFeature.dart +104 -0
- package/.sdk/tm/dart/lib/utility/CleanUtility.dart +12 -0
- package/.sdk/tm/dart/lib/utility/DoneUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/ErrUtility.dart +46 -0
- package/.sdk/tm/dart/lib/utility/FeatureAddUtility.dart +35 -0
- package/.sdk/tm/dart/lib/utility/FeatureHookUtility.dart +20 -0
- package/.sdk/tm/dart/lib/utility/FeatureInitUtility.dart +9 -0
- package/.sdk/tm/dart/lib/utility/FetcherUtility.dart +104 -0
- package/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
- package/.sdk/tm/dart/lib/utility/MakeContextUtility.dart +6 -0
- package/.sdk/tm/dart/lib/utility/MakeErrorUtility.dart +71 -0
- package/.sdk/tm/dart/lib/utility/MakeFetchDefUtility.dart +42 -0
- package/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +233 -0
- package/.sdk/tm/dart/lib/utility/MakePointUtility.dart +130 -0
- package/.sdk/tm/dart/lib/utility/MakeRequestUtility.dart +66 -0
- package/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +59 -0
- package/.sdk/tm/dart/lib/utility/MakeResultUtility.dart +49 -0
- package/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +77 -0
- package/.sdk/tm/dart/lib/utility/MakeUrlUtility.dart +51 -0
- package/.sdk/tm/dart/lib/utility/ParamUtility.dart +56 -0
- package/.sdk/tm/dart/lib/utility/PrepareAuthUtility.dart +46 -0
- package/.sdk/tm/dart/lib/utility/PrepareBodyUtility.dart +19 -0
- package/.sdk/tm/dart/lib/utility/PrepareHeadersUtility.dart +11 -0
- package/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +30 -0
- package/.sdk/tm/dart/lib/utility/PrepareParamsUtility.dart +23 -0
- package/.sdk/tm/dart/lib/utility/PreparePathUtility.dart +9 -0
- package/.sdk/tm/dart/lib/utility/PrepareQueryUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/ResultBasicUtility.dart +29 -0
- package/.sdk/tm/dart/lib/utility/ResultBodyUtility.dart +15 -0
- package/.sdk/tm/dart/lib/utility/ResultHeadersUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/StructUtility.dart +127 -0
- package/.sdk/tm/dart/lib/utility/TransformRequestUtility.dart +28 -0
- package/.sdk/tm/dart/lib/utility/TransformResponseUtility.dart +44 -0
- package/.sdk/tm/dart/lib/utility/Utility.dart +239 -0
- package/.sdk/tm/dart/lib/utility/voxgig_struct.dart +2462 -0
- package/.sdk/tm/dart/src/feature/README.md +3 -0
- package/.sdk/tm/dart/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/cost/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/secrets/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/dart/test/custom_test.dart +65 -0
- package/.sdk/tm/dart/test/exists_test.dart +12 -0
- package/.sdk/tm/dart/test/feature/harness.dart +347 -0
- package/.sdk/tm/dart/test/feature/secrets/secrets_test.dart +1107 -0
- package/.sdk/tm/dart/test/feature_test.dart +1193 -0
- package/.sdk/tm/dart/test/harness.dart +153 -0
- package/.sdk/tm/dart/test/netsim_test.dart +48 -0
- package/.sdk/tm/dart/test/omni.dart +520 -0
- package/.sdk/tm/dart/test/omni_smoke_test.dart +143 -0
- package/.sdk/tm/dart/test/pipeline_test.dart +608 -0
- package/.sdk/tm/dart/test/primary_test.dart +476 -0
- package/.sdk/tm/dart/test/sdk-test-control.json +19 -0
- package/.sdk/tm/dart/test/struct_test.dart +468 -0
- package/.sdk/tm/dart/test/utility.dart +198 -0
- package/.sdk/tm/dart/test/vendor/omni/omni.dart +7 -0
- package/.sdk/tm/dart/test/vendor/omni/runner.dart +653 -0
- package/.sdk/tm/dart/test/vendor/omni/util.dart +232 -0
- package/.sdk/tm/haskell/LICENSE +21 -0
- package/.sdk/tm/haskell/Makefile +22 -0
- package/.sdk/tm/haskell/VERSION +1 -0
- package/.sdk/tm/haskell/src/SdkFeatures.hs +1440 -0
- package/.sdk/tm/haskell/src/SdkHelpers.hs +337 -0
- package/.sdk/tm/haskell/src/SdkJson.hs +111 -0
- package/.sdk/tm/haskell/src/SdkRuntime.hs +1279 -0
- package/.sdk/tm/haskell/src/SdkTypes.hs +195 -0
- package/.sdk/tm/haskell/src/VoxgigStruct.hs +2299 -0
- package/.sdk/tm/haskell/src/Vregex.hs +237 -0
- package/.sdk/tm/haskell/src/feature/README.md +5 -0
- package/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/haskell/test/Harness.hs +281 -0
- package/.sdk/tm/haskell/test/OmniResolver.hs +252 -0
- package/.sdk/tm/haskell/test/Runner.hs +49 -0
- package/.sdk/tm/haskell/test/StructCorpus.hs +449 -0
- package/.sdk/tm/haskell/test/TCustomUtility.hs +38 -0
- package/.sdk/tm/haskell/test/TFeature.hs +794 -0
- package/.sdk/tm/haskell/test/TNetsim.hs +50 -0
- package/.sdk/tm/haskell/test/TPipeline.hs +334 -0
- package/.sdk/tm/haskell/test/TPrimaryCorpus.hs +263 -0
- package/.sdk/tm/haskell/test/TPrimaryUtility.hs +386 -0
- package/.sdk/tm/haskell/test/Testutil.hs +59 -0
- package/.sdk/tm/haskell/test/vendor/omni/Omni.hs +1147 -0
- package/.sdk/tm/lean/LICENSE +21 -0
- package/.sdk/tm/lean/Makefile +32 -0
- package/.sdk/tm/lean/VERSION +1 -0
- package/.sdk/tm/lean/src/SdkFeature.lean +171 -0
- package/.sdk/tm/lean/src/SdkFeatures.lean +787 -0
- package/.sdk/tm/lean/src/SdkJson.lean +134 -0
- package/.sdk/tm/lean/src/SdkRuntime.lean +374 -0
- package/.sdk/tm/lean/src/SdkUtility.lean +809 -0
- package/.sdk/tm/lean/src/VoxgigStruct.lean +2880 -0
- package/.sdk/tm/lean/src/Vregex.lean +412 -0
- package/.sdk/tm/lean/src/feature/README.md +5 -0
- package/.sdk/tm/lean/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/cost/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/lean/test/OmniResolver.lean +358 -0
- package/.sdk/tm/lean/test/OmniSmoke.lean +174 -0
- package/.sdk/tm/lean/test/StructCorpus.lean +368 -0
- package/.sdk/tm/lean/test/TFeature.lean +281 -0
- package/.sdk/tm/lean/test/TPrimaryUtility.lean +227 -0
- package/.sdk/tm/lean/test/vendor/omni/Omni.lean +997 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/package.json +78 -0
- package/sdkgen-package.json +22 -0
|
@@ -0,0 +1,1147 @@
|
|
|
1
|
+
-- VENDORED: @voxgig/omni sdk-20260908-1556-0 (haskell/src/Omni.hs)
|
|
2
|
+
-- Source: https://github.com/voxgig/omni @ 274708cc2d12b21707d975543953f845f8444be0 [tag: sdk-20260908-1556-0]
|
|
3
|
+
-- License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
|
|
4
|
+
-- | Omni: the shared multi-language test runner (Haskell port).
|
|
5
|
+
--
|
|
6
|
+
-- A test spec is plain JSON. The same spec file drives the same tests in
|
|
7
|
+
-- every language that ships an omni port.
|
|
8
|
+
--
|
|
9
|
+
-- Port of the canonical TypeScript implementation
|
|
10
|
+
-- (typescript/src/Runner.ts). Behaviour must match, case for case.
|
|
11
|
+
--
|
|
12
|
+
-- Self-contained by design: the runner carries its own JSON value model,
|
|
13
|
+
-- parser and regex engine, so that it can test *any* library - including
|
|
14
|
+
-- one that provides those same operations - and adds no dependencies
|
|
15
|
+
-- beyond base.
|
|
16
|
+
|
|
17
|
+
module Omni
|
|
18
|
+
( Json (..),
|
|
19
|
+
Flags (..),
|
|
20
|
+
Provider (..),
|
|
21
|
+
RunPack (..),
|
|
22
|
+
Subject,
|
|
23
|
+
SubjectArgs,
|
|
24
|
+
OmniError (..),
|
|
25
|
+
nullmark,
|
|
26
|
+
undefmark,
|
|
27
|
+
existsmark,
|
|
28
|
+
specversion,
|
|
29
|
+
capabilities,
|
|
30
|
+
defaultFlags,
|
|
31
|
+
nonullFlags,
|
|
32
|
+
emptyProvider,
|
|
33
|
+
ismap,
|
|
34
|
+
islist,
|
|
35
|
+
isnode,
|
|
36
|
+
isabsent,
|
|
37
|
+
isnull,
|
|
38
|
+
isnone,
|
|
39
|
+
isnum,
|
|
40
|
+
isstr,
|
|
41
|
+
asnum,
|
|
42
|
+
asstr,
|
|
43
|
+
aslist,
|
|
44
|
+
asmap,
|
|
45
|
+
jget,
|
|
46
|
+
jhas,
|
|
47
|
+
jset,
|
|
48
|
+
parse,
|
|
49
|
+
loadspec,
|
|
50
|
+
resolvespec,
|
|
51
|
+
clone,
|
|
52
|
+
deepequal,
|
|
53
|
+
getpath,
|
|
54
|
+
walk,
|
|
55
|
+
jsonstr,
|
|
56
|
+
stringify,
|
|
57
|
+
numstr,
|
|
58
|
+
pathify,
|
|
59
|
+
fixjson,
|
|
60
|
+
errify,
|
|
61
|
+
providerErrify,
|
|
62
|
+
errmessage,
|
|
63
|
+
matchval,
|
|
64
|
+
nullmodifier,
|
|
65
|
+
regexFind,
|
|
66
|
+
makeRunner,
|
|
67
|
+
makeRunnerSpec,
|
|
68
|
+
)
|
|
69
|
+
where
|
|
70
|
+
|
|
71
|
+
import Control.Exception (Exception, SomeException, displayException, fromException, throwIO, try)
|
|
72
|
+
import Data.Char (isAlphaNum, isDigit, isSpace, toLower)
|
|
73
|
+
import Data.List (isInfixOf, isPrefixOf, sortBy)
|
|
74
|
+
import Data.Maybe (fromMaybe, isJust)
|
|
75
|
+
import Numeric (showHex)
|
|
76
|
+
import Text.Printf (printf)
|
|
77
|
+
|
|
78
|
+
-- ---- the JSON value model -------------------------------------------
|
|
79
|
+
|
|
80
|
+
-- | A JSON value. 'Absent' is the marker for "no value at all", as
|
|
81
|
+
-- distinct from 'Null'.
|
|
82
|
+
data Json
|
|
83
|
+
= Absent
|
|
84
|
+
| Null
|
|
85
|
+
| Bool Bool
|
|
86
|
+
| Num Double
|
|
87
|
+
| Str String
|
|
88
|
+
| JList [Json]
|
|
89
|
+
| JMap [(String, Json)]
|
|
90
|
+
deriving (Show)
|
|
91
|
+
|
|
92
|
+
nullmark :: String
|
|
93
|
+
nullmark = "__NULL__" -- Value is JSON null.
|
|
94
|
+
|
|
95
|
+
undefmark :: String
|
|
96
|
+
undefmark = "__UNDEF__" -- Value is not present.
|
|
97
|
+
|
|
98
|
+
existsmark :: String
|
|
99
|
+
existsmark = "__EXISTS__" -- Value exists (not undefined).
|
|
100
|
+
|
|
101
|
+
ismap :: Json -> Bool
|
|
102
|
+
ismap (JMap _) = True
|
|
103
|
+
ismap _ = False
|
|
104
|
+
|
|
105
|
+
islist :: Json -> Bool
|
|
106
|
+
islist (JList _) = True
|
|
107
|
+
islist _ = False
|
|
108
|
+
|
|
109
|
+
isnode :: Json -> Bool
|
|
110
|
+
isnode value = ismap value || islist value
|
|
111
|
+
|
|
112
|
+
isabsent :: Json -> Bool
|
|
113
|
+
isabsent Absent = True
|
|
114
|
+
isabsent _ = False
|
|
115
|
+
|
|
116
|
+
isnull :: Json -> Bool
|
|
117
|
+
isnull Null = True
|
|
118
|
+
isnull _ = False
|
|
119
|
+
|
|
120
|
+
isnone :: Json -> Bool
|
|
121
|
+
isnone value = isabsent value || isnull value
|
|
122
|
+
|
|
123
|
+
isnum :: Json -> Bool
|
|
124
|
+
isnum (Num _) = True
|
|
125
|
+
isnum _ = False
|
|
126
|
+
|
|
127
|
+
isstr :: Json -> Bool
|
|
128
|
+
isstr (Str _) = True
|
|
129
|
+
isstr _ = False
|
|
130
|
+
|
|
131
|
+
asnum :: Json -> Maybe Double
|
|
132
|
+
asnum (Num value) = Just value
|
|
133
|
+
asnum _ = Nothing
|
|
134
|
+
|
|
135
|
+
asstr :: Json -> Maybe String
|
|
136
|
+
asstr (Str value) = Just value
|
|
137
|
+
asstr _ = Nothing
|
|
138
|
+
|
|
139
|
+
aslist :: Json -> Maybe [Json]
|
|
140
|
+
aslist (JList value) = Just value
|
|
141
|
+
aslist _ = Nothing
|
|
142
|
+
|
|
143
|
+
asmap :: Json -> Maybe [(String, Json)]
|
|
144
|
+
asmap (JMap value) = Just value
|
|
145
|
+
asmap _ = Nothing
|
|
146
|
+
|
|
147
|
+
-- | Read a map entry. Returns 'Absent' when missing.
|
|
148
|
+
jget :: Json -> String -> Json
|
|
149
|
+
jget (JMap entries) key = fromMaybe Absent (lookup key entries)
|
|
150
|
+
jget _ _ = Absent
|
|
151
|
+
|
|
152
|
+
-- | Is a map key present at all (even with a null value)?
|
|
153
|
+
jhas :: Json -> String -> Bool
|
|
154
|
+
jhas (JMap entries) key = isJust (lookup key entries)
|
|
155
|
+
jhas _ _ = False
|
|
156
|
+
|
|
157
|
+
-- | A copy of a map with one entry set (no-op for non-maps).
|
|
158
|
+
jset :: Json -> String -> Json -> Json
|
|
159
|
+
jset (JMap entries) key entry =
|
|
160
|
+
if isJust (lookup key entries)
|
|
161
|
+
then JMap (map (\(k, e) -> if k == key then (k, entry) else (k, e)) entries)
|
|
162
|
+
else JMap (entries ++ [(key, entry)])
|
|
163
|
+
jset other _ _ = other
|
|
164
|
+
|
|
165
|
+
-- ---- errors ----------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
-- | A test failure (or a malformed spec). Distinct from an exception
|
|
168
|
+
-- thrown by the subject under test, which is a candidate for an @err@
|
|
169
|
+
-- expectation.
|
|
170
|
+
newtype OmniError = OmniError String
|
|
171
|
+
|
|
172
|
+
instance Show OmniError where
|
|
173
|
+
show (OmniError message) = message
|
|
174
|
+
|
|
175
|
+
instance Exception OmniError
|
|
176
|
+
|
|
177
|
+
-- ---- number and string rendering ------------------------------------
|
|
178
|
+
|
|
179
|
+
-- | Render a number the same way in every port: 5.0 prints as 5.
|
|
180
|
+
numstr :: Double -> String
|
|
181
|
+
numstr value
|
|
182
|
+
| isNaN value || isInfinite value = "null"
|
|
183
|
+
| value == fromIntegral rounded && abs value < 9007199254740992.0 = show rounded
|
|
184
|
+
| otherwise = trimzeros (printf "%.17g" value)
|
|
185
|
+
where
|
|
186
|
+
rounded = truncate value :: Integer
|
|
187
|
+
|
|
188
|
+
trimzeros text =
|
|
189
|
+
if '.' `elem` text && 'e' `notElem` text && 'E' `notElem` text
|
|
190
|
+
then reverse (dropdot (dropWhile (== '0') (reverse text)))
|
|
191
|
+
else text
|
|
192
|
+
|
|
193
|
+
dropdot ('.' : rest) = rest
|
|
194
|
+
dropdot rest = rest
|
|
195
|
+
|
|
196
|
+
-- | Render a string as a JSON string literal.
|
|
197
|
+
quote :: String -> String
|
|
198
|
+
quote value = "\"" ++ concatMap escape value ++ "\""
|
|
199
|
+
where
|
|
200
|
+
escape ch = case ch of
|
|
201
|
+
'"' -> "\\\""
|
|
202
|
+
'\\' -> "\\\\"
|
|
203
|
+
'\n' -> "\\n"
|
|
204
|
+
'\r' -> "\\r"
|
|
205
|
+
'\t' -> "\\t"
|
|
206
|
+
_ ->
|
|
207
|
+
if fromEnum ch < 0x20
|
|
208
|
+
then "\\u" ++ pad (showHex (fromEnum ch) "")
|
|
209
|
+
else [ch]
|
|
210
|
+
|
|
211
|
+
pad text = replicate (4 - length text) '0' ++ text
|
|
212
|
+
|
|
213
|
+
-- | Compact JSON text with map keys sorted, identical in every port.
|
|
214
|
+
jsonstr :: Json -> String
|
|
215
|
+
jsonstr Absent = "undefined"
|
|
216
|
+
jsonstr Null = "null"
|
|
217
|
+
jsonstr (Bool flag) = if flag then "true" else "false"
|
|
218
|
+
jsonstr (Num value) = numstr value
|
|
219
|
+
jsonstr (Str text) = quote text
|
|
220
|
+
jsonstr (JList entries) = "[" ++ joinwith "," (map jsonstr entries) ++ "]"
|
|
221
|
+
jsonstr (JMap entries) =
|
|
222
|
+
"{" ++ joinwith "," (map render sorted) ++ "}"
|
|
223
|
+
where
|
|
224
|
+
sorted = sortBy (\(a, _) (b, _) -> compare a b) entries
|
|
225
|
+
render (key, entry) = quote key ++ ":" ++ jsonstr entry
|
|
226
|
+
|
|
227
|
+
-- | Strings verbatim, everything else as compact JSON.
|
|
228
|
+
stringify :: Json -> String
|
|
229
|
+
stringify (Str text) = text
|
|
230
|
+
stringify other = jsonstr other
|
|
231
|
+
|
|
232
|
+
joinwith :: String -> [String] -> String
|
|
233
|
+
joinwith _ [] = ""
|
|
234
|
+
joinwith _ [single] = single
|
|
235
|
+
joinwith sep (head' : rest) = head' ++ sep ++ joinwith sep rest
|
|
236
|
+
|
|
237
|
+
-- | Render a path as a dotted string.
|
|
238
|
+
pathify :: [String] -> String
|
|
239
|
+
pathify = joinwith "."
|
|
240
|
+
|
|
241
|
+
-- ---- json parser -----------------------------------------------------
|
|
242
|
+
|
|
243
|
+
-- | Parse JSON text into the omni value model.
|
|
244
|
+
parse :: String -> Either String Json
|
|
245
|
+
parse text = case parseValue (skipws text) of
|
|
246
|
+
Left message -> Left message
|
|
247
|
+
Right (value, rest) ->
|
|
248
|
+
if null (skipws rest)
|
|
249
|
+
then Right value
|
|
250
|
+
else Left "omni: trailing JSON content"
|
|
251
|
+
|
|
252
|
+
skipws :: String -> String
|
|
253
|
+
skipws = dropWhile isSpace
|
|
254
|
+
|
|
255
|
+
parseValue :: String -> Either String (Json, String)
|
|
256
|
+
parseValue [] = Left "omni: unexpected end of JSON"
|
|
257
|
+
parseValue text@(ch : rest)
|
|
258
|
+
| '{' == ch = parseMap (skipws rest) []
|
|
259
|
+
| '[' == ch = parseList (skipws rest) []
|
|
260
|
+
| '"' == ch = fmap (\(value, more) -> (Str value, more)) (parseString rest "")
|
|
261
|
+
| "true" `isPrefixOf` text = Right (Bool True, drop 4 text)
|
|
262
|
+
| "false" `isPrefixOf` text = Right (Bool False, drop 5 text)
|
|
263
|
+
| "null" `isPrefixOf` text = Right (Null, drop 4 text)
|
|
264
|
+
| otherwise = parseNumber text
|
|
265
|
+
|
|
266
|
+
parseMap :: String -> [(String, Json)] -> Either String (Json, String)
|
|
267
|
+
parseMap ('}' : rest) entries = Right (JMap (reverse entries), rest)
|
|
268
|
+
parseMap text entries = parseMapPair text entries
|
|
269
|
+
|
|
270
|
+
-- A comma must be followed by another pair: no trailing commas.
|
|
271
|
+
parseMapPair :: String -> [(String, Json)] -> Either String (Json, String)
|
|
272
|
+
parseMapPair text entries = do
|
|
273
|
+
(key, afterkey) <- case skipws text of
|
|
274
|
+
'"' : more -> parseString more ""
|
|
275
|
+
_ -> Left "omni: expected string key"
|
|
276
|
+
|
|
277
|
+
aftercolon <- case skipws afterkey of
|
|
278
|
+
':' : more -> Right more
|
|
279
|
+
_ -> Left "omni: expected ':'"
|
|
280
|
+
|
|
281
|
+
(value, aftervalue) <- parseValue (skipws aftercolon)
|
|
282
|
+
|
|
283
|
+
-- A duplicate key overrides the earlier value (last wins), as in the
|
|
284
|
+
-- canonical implementation.
|
|
285
|
+
let entries' =
|
|
286
|
+
if isJust (lookup key entries)
|
|
287
|
+
then map (\(k, e) -> if k == key then (k, value) else (k, e)) entries
|
|
288
|
+
else (key, value) : entries
|
|
289
|
+
|
|
290
|
+
case skipws aftervalue of
|
|
291
|
+
',' : more -> parseMapPair (skipws more) entries'
|
|
292
|
+
'}' : more -> Right (JMap (reverse entries'), more)
|
|
293
|
+
_ -> Left "omni: expected ',' or '}'"
|
|
294
|
+
|
|
295
|
+
parseList :: String -> [Json] -> Either String (Json, String)
|
|
296
|
+
parseList (']' : rest) entries = Right (JList (reverse entries), rest)
|
|
297
|
+
parseList text entries = parseListItem text entries
|
|
298
|
+
|
|
299
|
+
-- A comma must be followed by another element: no trailing commas.
|
|
300
|
+
parseListItem :: String -> [Json] -> Either String (Json, String)
|
|
301
|
+
parseListItem text entries = do
|
|
302
|
+
(value, aftervalue) <- parseValue (skipws text)
|
|
303
|
+
|
|
304
|
+
case skipws aftervalue of
|
|
305
|
+
',' : more -> parseListItem (skipws more) (value : entries)
|
|
306
|
+
']' : more -> Right (JList (reverse (value : entries)), more)
|
|
307
|
+
_ -> Left "omni: expected ',' or ']'"
|
|
308
|
+
|
|
309
|
+
parseString :: String -> String -> Either String (String, String)
|
|
310
|
+
parseString [] _ = Left "omni: unterminated JSON string"
|
|
311
|
+
parseString ('"' : rest) out = Right (reverse out, rest)
|
|
312
|
+
parseString ('\\' : escape : rest) out = case escape of
|
|
313
|
+
'"' -> parseString rest ('"' : out)
|
|
314
|
+
'\\' -> parseString rest ('\\' : out)
|
|
315
|
+
'/' -> parseString rest ('/' : out)
|
|
316
|
+
'b' -> parseString rest ('\b' : out)
|
|
317
|
+
'f' -> parseString rest ('\f' : out)
|
|
318
|
+
'n' -> parseString rest ('\n' : out)
|
|
319
|
+
'r' -> parseString rest ('\r' : out)
|
|
320
|
+
't' -> parseString rest ('\t' : out)
|
|
321
|
+
'u' ->
|
|
322
|
+
-- Exactly four hex digits: anything else is a parse error, not
|
|
323
|
+
-- garbage arithmetic on arbitrary characters.
|
|
324
|
+
let (hex, more) = splitAt 4 rest
|
|
325
|
+
in if 4 == length hex && all ishex hex
|
|
326
|
+
then parseString more (toEnum (readhex hex) : out)
|
|
327
|
+
else Left "omni: bad JSON unicode escape"
|
|
328
|
+
_ -> Left "omni: bad JSON escape"
|
|
329
|
+
where
|
|
330
|
+
ishex ch = isDigit ch || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
|
331
|
+
readhex = foldl (\acc ch -> acc * 16 + digit ch) 0
|
|
332
|
+
digit ch
|
|
333
|
+
| isDigit ch = fromEnum ch - fromEnum '0'
|
|
334
|
+
| ch >= 'a' && ch <= 'f' = fromEnum ch - fromEnum 'a' + 10
|
|
335
|
+
| otherwise = fromEnum ch - fromEnum 'A' + 10
|
|
336
|
+
parseString (ch : rest) out = parseString rest (ch : out)
|
|
337
|
+
|
|
338
|
+
parseNumber :: String -> Either String (Json, String)
|
|
339
|
+
parseNumber text =
|
|
340
|
+
let (sign, afterSign) = span (\ch -> '-' == ch || '+' == ch) text
|
|
341
|
+
(digits, rest) = span (\ch -> isDigit ch || '.' == ch || 'e' == ch || 'E' == ch || '-' == ch || '+' == ch) afterSign
|
|
342
|
+
span' = sign ++ digits
|
|
343
|
+
in if null digits
|
|
344
|
+
then Left "omni: bad JSON number"
|
|
345
|
+
else case reads (fixup span') :: [(Double, String)] of
|
|
346
|
+
[(value, "")] -> Right (Num value, rest)
|
|
347
|
+
_ -> Left ("omni: bad JSON number [" ++ span' ++ "]")
|
|
348
|
+
where
|
|
349
|
+
-- Haskell's `reads` wants a digit before the point and no leading '+'.
|
|
350
|
+
fixup ('+' : more) = fixup more
|
|
351
|
+
fixup ('-' : '.' : more) = "-0." ++ more
|
|
352
|
+
fixup ('.' : more) = "0." ++ more
|
|
353
|
+
fixup other = other
|
|
354
|
+
|
|
355
|
+
-- | Load a spec: a path to a JSON file.
|
|
356
|
+
loadspec :: String -> IO Json
|
|
357
|
+
loadspec path = do
|
|
358
|
+
text <- readFile path
|
|
359
|
+
case parse text of
|
|
360
|
+
Right value -> pure value
|
|
361
|
+
Left message -> throwIO (OmniError ("omni: cannot parse spec: " ++ path ++ ": " ++ message))
|
|
362
|
+
|
|
363
|
+
-- ---- util ------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
-- | Deep copy a JSON value (the value model is immutable).
|
|
366
|
+
clone :: Json -> Json
|
|
367
|
+
clone = id
|
|
368
|
+
|
|
369
|
+
-- | Read a value by path. Returns 'Absent' when any step is missing.
|
|
370
|
+
getpath :: Json -> [String] -> Json
|
|
371
|
+
getpath value [] = value
|
|
372
|
+
getpath value (part : rest) =
|
|
373
|
+
let next = case value of
|
|
374
|
+
JList entries -> case readIndex part of
|
|
375
|
+
Just index | index >= 0 && index < length entries -> entries !! index
|
|
376
|
+
_ -> Absent
|
|
377
|
+
JMap _ -> jget value part
|
|
378
|
+
_ -> Absent
|
|
379
|
+
in if isabsent next then Absent else getpath next rest
|
|
380
|
+
where
|
|
381
|
+
readIndex text = case reads text :: [(Int, String)] of
|
|
382
|
+
[(index, "")] -> Just index
|
|
383
|
+
_ -> Nothing
|
|
384
|
+
|
|
385
|
+
-- | Depth-first walk: children first, then the containing node.
|
|
386
|
+
walk :: Json -> (Json -> [String] -> Json) -> [String] -> Json
|
|
387
|
+
walk value apply path = apply next path
|
|
388
|
+
where
|
|
389
|
+
next = case value of
|
|
390
|
+
JList entries ->
|
|
391
|
+
JList (zipWith (\index entry -> walk entry apply (path ++ [show index])) [0 :: Int ..] entries)
|
|
392
|
+
JMap entries -> JMap (map (\(key, entry) -> (key, walk entry apply (path ++ [key]))) entries)
|
|
393
|
+
other -> other
|
|
394
|
+
|
|
395
|
+
-- | Deep structural equality: numbers by value, bools never numbers.
|
|
396
|
+
deepequal :: Json -> Json -> Bool
|
|
397
|
+
deepequal Absent Absent = True
|
|
398
|
+
deepequal Null Null = True
|
|
399
|
+
deepequal (Bool x) (Bool y) = x == y
|
|
400
|
+
deepequal (Num x) (Num y) = x == y || (isNaN x && isNaN y)
|
|
401
|
+
deepequal (Str x) (Str y) = x == y
|
|
402
|
+
deepequal (JList x) (JList y) = length x == length y && and (zipWith deepequal x y)
|
|
403
|
+
deepequal (JMap x) (JMap y) =
|
|
404
|
+
length x == length y
|
|
405
|
+
&& all (\(key, entry) -> maybe False (deepequal entry) (lookup key y)) x
|
|
406
|
+
deepequal _ _ = False
|
|
407
|
+
|
|
408
|
+
-- ---- regex engine ----------------------------------------------------
|
|
409
|
+
|
|
410
|
+
-- Omni specs match error messages with @/pattern/@ expectations, so every
|
|
411
|
+
-- port needs a regex. Haskell has none in base, so omni carries this
|
|
412
|
+
-- engine, a direct port of the Rust one (rust/src/regex.rs).
|
|
413
|
+
|
|
414
|
+
data ClassItem
|
|
415
|
+
= IChar Char
|
|
416
|
+
| IRange Char Char
|
|
417
|
+
| IDigit Bool
|
|
418
|
+
| IWord Bool
|
|
419
|
+
| ISpace Bool
|
|
420
|
+
|
|
421
|
+
data Node
|
|
422
|
+
= RChar Char
|
|
423
|
+
| RAny
|
|
424
|
+
| RClass [ClassItem] Bool
|
|
425
|
+
| RStart
|
|
426
|
+
| REnd
|
|
427
|
+
| RSeq [Node]
|
|
428
|
+
| RAlt [Node]
|
|
429
|
+
| RRepeat Node Int (Maybe Int) Bool
|
|
430
|
+
|
|
431
|
+
regexParse :: String -> Maybe Node
|
|
432
|
+
regexParse pattern = case parseAlt pattern of
|
|
433
|
+
Just (node, "") -> Just node
|
|
434
|
+
_ -> Nothing
|
|
435
|
+
|
|
436
|
+
parseAlt :: String -> Maybe (Node, String)
|
|
437
|
+
parseAlt text = do
|
|
438
|
+
(first, rest) <- parseSeq text
|
|
439
|
+
branches rest [first]
|
|
440
|
+
where
|
|
441
|
+
branches ('|' : more) acc = do
|
|
442
|
+
(branch, rest) <- parseSeq more
|
|
443
|
+
branches rest (branch : acc)
|
|
444
|
+
branches rest acc =
|
|
445
|
+
Just (if 1 == length acc then head acc else RAlt (reverse acc), rest)
|
|
446
|
+
|
|
447
|
+
parseSeq :: String -> Maybe (Node, String)
|
|
448
|
+
parseSeq = collect []
|
|
449
|
+
where
|
|
450
|
+
collect acc text
|
|
451
|
+
| null text || '|' == head text || ')' == head text = Just (RSeq (reverse acc), text)
|
|
452
|
+
| otherwise = do
|
|
453
|
+
(atom, rest) <- parseAtom text
|
|
454
|
+
(node, more) <- parseRepeat atom rest
|
|
455
|
+
collect (node : acc) more
|
|
456
|
+
|
|
457
|
+
parseRepeat :: Node -> String -> Maybe (Node, String)
|
|
458
|
+
parseRepeat atom text = case text of
|
|
459
|
+
'*' : rest -> greedy atom 0 Nothing rest
|
|
460
|
+
'+' : rest -> greedy atom 1 Nothing rest
|
|
461
|
+
'?' : rest -> greedy atom 0 (Just 1) rest
|
|
462
|
+
'{' : _ -> case parseCount text of
|
|
463
|
+
Just (low, high, rest) -> greedy atom low high rest
|
|
464
|
+
Nothing -> Just (atom, text)
|
|
465
|
+
_ -> Just (atom, text)
|
|
466
|
+
where
|
|
467
|
+
greedy inner low high ('?' : rest) = Just (RRepeat inner low high False, rest)
|
|
468
|
+
greedy inner low high rest = Just (RRepeat inner low high True, rest)
|
|
469
|
+
|
|
470
|
+
parseCount :: String -> Maybe (Int, Maybe Int, String)
|
|
471
|
+
parseCount ('{' : rest) =
|
|
472
|
+
let (lowtext, afterlow) = span isDigit rest
|
|
473
|
+
in if null lowtext
|
|
474
|
+
then Nothing
|
|
475
|
+
else case afterlow of
|
|
476
|
+
'}' : more -> Just (read lowtext, Just (read lowtext), more)
|
|
477
|
+
',' : more ->
|
|
478
|
+
let (hightext, afterhigh) = span isDigit more
|
|
479
|
+
in case afterhigh of
|
|
480
|
+
'}' : final ->
|
|
481
|
+
Just (read lowtext, if null hightext then Nothing else Just (read hightext), final)
|
|
482
|
+
_ -> Nothing
|
|
483
|
+
_ -> Nothing
|
|
484
|
+
parseCount _ = Nothing
|
|
485
|
+
|
|
486
|
+
parseAtom :: String -> Maybe (Node, String)
|
|
487
|
+
parseAtom [] = Nothing
|
|
488
|
+
parseAtom ('(' : rest) = do
|
|
489
|
+
let inner = if "?:" `isPrefixOf` rest then drop 2 rest else rest
|
|
490
|
+
(node, more) <- parseAlt inner
|
|
491
|
+
case more of
|
|
492
|
+
')' : final -> Just (node, final)
|
|
493
|
+
_ -> Nothing
|
|
494
|
+
parseAtom ('[' : rest) = parseClass rest
|
|
495
|
+
parseAtom ('.' : rest) = Just (RAny, rest)
|
|
496
|
+
parseAtom ('^' : rest) = Just (RStart, rest)
|
|
497
|
+
parseAtom ('$' : rest) = Just (REnd, rest)
|
|
498
|
+
parseAtom ('\\' : escape : rest) = Just (escapeNode escape, rest)
|
|
499
|
+
parseAtom (ch : rest) = Just (RChar ch, rest)
|
|
500
|
+
|
|
501
|
+
escapeNode :: Char -> Node
|
|
502
|
+
escapeNode escape = case escape of
|
|
503
|
+
'd' -> RClass [IDigit False] False
|
|
504
|
+
'D' -> RClass [IDigit False] True
|
|
505
|
+
'w' -> RClass [IWord False] False
|
|
506
|
+
'W' -> RClass [IWord False] True
|
|
507
|
+
's' -> RClass [ISpace False] False
|
|
508
|
+
'S' -> RClass [ISpace False] True
|
|
509
|
+
'n' -> RChar '\n'
|
|
510
|
+
'r' -> RChar '\r'
|
|
511
|
+
't' -> RChar '\t'
|
|
512
|
+
other -> RChar other
|
|
513
|
+
|
|
514
|
+
parseClass :: String -> Maybe (Node, String)
|
|
515
|
+
parseClass text =
|
|
516
|
+
let (negated, rest) = case text of
|
|
517
|
+
'^' : more -> (True, more)
|
|
518
|
+
_ -> (False, text)
|
|
519
|
+
in collect [] True negated rest
|
|
520
|
+
where
|
|
521
|
+
collect _ _ _ [] = Nothing
|
|
522
|
+
collect acc first negated (']' : rest)
|
|
523
|
+
| not first = Just (RClass (reverse acc) negated, rest)
|
|
524
|
+
| otherwise = collect (IChar ']' : acc) False negated rest
|
|
525
|
+
collect acc _ negated ('\\' : escape : rest) =
|
|
526
|
+
collect (classEscape escape : acc) False negated rest
|
|
527
|
+
collect acc _ negated (from : '-' : to : rest)
|
|
528
|
+
| ']' /= to = collect (IRange from to : acc) False negated rest
|
|
529
|
+
collect acc _ negated (ch : rest) = collect (IChar ch : acc) False negated rest
|
|
530
|
+
|
|
531
|
+
classEscape escape = case escape of
|
|
532
|
+
'd' -> IDigit False
|
|
533
|
+
'w' -> IWord False
|
|
534
|
+
's' -> ISpace False
|
|
535
|
+
'n' -> IChar '\n'
|
|
536
|
+
'r' -> IChar '\r'
|
|
537
|
+
't' -> IChar '\t'
|
|
538
|
+
other -> IChar other
|
|
539
|
+
|
|
540
|
+
isWordChar :: Char -> Bool
|
|
541
|
+
isWordChar ch = isAlphaNum ch || '_' == ch
|
|
542
|
+
|
|
543
|
+
classMatch :: [ClassItem] -> Bool -> Char -> Bool
|
|
544
|
+
classMatch items negated ch = hit /= negated
|
|
545
|
+
where
|
|
546
|
+
hit = any check items
|
|
547
|
+
check item = case item of
|
|
548
|
+
IChar want -> ch == want
|
|
549
|
+
IRange from to -> from <= ch && ch <= to
|
|
550
|
+
IDigit invert -> isDigit ch /= invert
|
|
551
|
+
IWord invert -> isWordChar ch /= invert
|
|
552
|
+
ISpace invert -> isSpace ch /= invert
|
|
553
|
+
|
|
554
|
+
matchNode :: Node -> String -> Int -> Int -> (Int -> Bool) -> Bool
|
|
555
|
+
matchNode node text len pos cont = case node of
|
|
556
|
+
RChar want -> pos < len && (text !! pos) == want && cont (pos + 1)
|
|
557
|
+
RAny -> pos < len && cont (pos + 1)
|
|
558
|
+
RClass items negated -> pos < len && classMatch items negated (text !! pos) && cont (pos + 1)
|
|
559
|
+
RStart -> 0 == pos && cont pos
|
|
560
|
+
REnd -> len == pos && cont pos
|
|
561
|
+
RSeq nodes -> matchSeq nodes text len pos cont
|
|
562
|
+
RAlt branches -> any (\branch -> matchNode branch text len pos cont) branches
|
|
563
|
+
RRepeat inner low high greedy -> matchRepeat inner low high greedy 0 text len pos cont
|
|
564
|
+
|
|
565
|
+
matchSeq :: [Node] -> String -> Int -> Int -> (Int -> Bool) -> Bool
|
|
566
|
+
matchSeq [] _ _ pos cont = cont pos
|
|
567
|
+
matchSeq (node : rest) text len pos cont =
|
|
568
|
+
matchNode node text len pos (\next -> matchSeq rest text len next cont)
|
|
569
|
+
|
|
570
|
+
matchRepeat :: Node -> Int -> Maybe Int -> Bool -> Int -> String -> Int -> Int -> (Int -> Bool) -> Bool
|
|
571
|
+
matchRepeat inner low high greedy count text len pos cont =
|
|
572
|
+
if greedy then takemore || stopnow else stopnow || takemore
|
|
573
|
+
where
|
|
574
|
+
canmore = maybe True (count <) high
|
|
575
|
+
|
|
576
|
+
takemore =
|
|
577
|
+
canmore
|
|
578
|
+
&& matchNode
|
|
579
|
+
inner
|
|
580
|
+
text
|
|
581
|
+
len
|
|
582
|
+
pos
|
|
583
|
+
-- A zero-width repeat would loop forever.
|
|
584
|
+
(\next -> next > pos && matchRepeat inner low high greedy (count + 1) text len next cont)
|
|
585
|
+
|
|
586
|
+
stopnow = count >= low && cont pos
|
|
587
|
+
|
|
588
|
+
-- | Is the pattern found anywhere in the text?
|
|
589
|
+
regexFind :: String -> String -> Bool
|
|
590
|
+
regexFind pattern text = case regexParse pattern of
|
|
591
|
+
Nothing -> False
|
|
592
|
+
Just root -> any (\start -> matchNode root text len start (const True)) [0 .. len]
|
|
593
|
+
where
|
|
594
|
+
len = length text
|
|
595
|
+
|
|
596
|
+
-- ---- runner ----------------------------------------------------------
|
|
597
|
+
|
|
598
|
+
-- | The function under test. Arguments arrive as JSON values; failure is
|
|
599
|
+
-- reported by throwing.
|
|
600
|
+
type Subject = [Json] -> IO Json
|
|
601
|
+
|
|
602
|
+
-- | A subject that may REPLACE its arguments, which @match.args@ can then
|
|
603
|
+
-- assert on. @minor/setpath@ is the case in point: eight of its nine entries
|
|
604
|
+
-- assert that the store was rewritten in place, and @merge/integrity@ all six.
|
|
605
|
+
--
|
|
606
|
+
-- Haskell's 'Json' is immutable, so a consumer holding a converted copy has no
|
|
607
|
+
-- way to write through the argument list even when its own nodes are mutable
|
|
608
|
+
-- (struct/haskell keeps them in 'IORef' cells). Returning the arguments
|
|
609
|
+
-- alongside the result is the only channel there is.
|
|
610
|
+
--
|
|
611
|
+
-- Separate from 'Subject' rather than replacing it: a signature change would
|
|
612
|
+
-- break every consumer for a capability most subjects do not need. omni-rust,
|
|
613
|
+
-- omni-cpp, omni-ocaml and omni-elixir carry the same pair, for the same
|
|
614
|
+
-- reason.
|
|
615
|
+
type SubjectArgs = [Json] -> IO ([Json], Json)
|
|
616
|
+
|
|
617
|
+
-- | Run-time options for a set of test entries.
|
|
618
|
+
data Flags = Flags {flagNull :: Bool, flagName :: Maybe String}
|
|
619
|
+
|
|
620
|
+
defaultFlags :: Flags
|
|
621
|
+
defaultFlags = Flags {flagNull = True, flagName = Nothing}
|
|
622
|
+
|
|
623
|
+
nonullFlags :: Flags
|
|
624
|
+
nonullFlags = Flags {flagNull = False, flagName = Nothing}
|
|
625
|
+
|
|
626
|
+
-- | The host of the system under test. Every hook is optional.
|
|
627
|
+
data Provider = Provider
|
|
628
|
+
{ providerSubject :: Maybe (String -> Maybe Subject),
|
|
629
|
+
providerClient :: Maybe (Json -> Provider),
|
|
630
|
+
providerContextify :: Maybe (Json -> Json),
|
|
631
|
+
providerInject :: Maybe (Json -> Json -> Json),
|
|
632
|
+
-- | Build the @match.err@ base from the failure, REPLACING 'errify'.
|
|
633
|
+
--
|
|
634
|
+
-- Haskell reaches this point with a message and nothing else, so a
|
|
635
|
+
-- library whose errors carry a code has no other way to put one in
|
|
636
|
+
-- the base. The hook receives that message and returns the base,
|
|
637
|
+
-- letting a spec assert @match: {err: {code: "x"}}@ instead of
|
|
638
|
+
-- pattern-matching prose.
|
|
639
|
+
providerErrify :: Maybe (String -> Json)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
emptyProvider :: Provider
|
|
643
|
+
emptyProvider =
|
|
644
|
+
Provider
|
|
645
|
+
{ providerSubject = Nothing,
|
|
646
|
+
providerClient = Nothing,
|
|
647
|
+
providerContextify = Nothing,
|
|
648
|
+
providerInject = Nothing,
|
|
649
|
+
providerErrify = Nothing
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
-- | What a runner returns for one named spec section.
|
|
653
|
+
data RunPack = RunPack
|
|
654
|
+
{ packSpec :: Json,
|
|
655
|
+
packSet :: String -> Json,
|
|
656
|
+
runset :: Json -> Maybe Subject -> IO (),
|
|
657
|
+
runsetFlags :: Json -> Flags -> Maybe Subject -> IO (),
|
|
658
|
+
-- | Everything 'runsetFlags' does, for a subject that may replace its
|
|
659
|
+
-- arguments. See 'SubjectArgs'.
|
|
660
|
+
runsetFlagsArgs :: Json -> Flags -> SubjectArgs -> IO (),
|
|
661
|
+
packSubject :: Maybe Subject,
|
|
662
|
+
packClient :: Provider
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
-- | The newest spec format version this runner understands. A spec with
|
|
666
|
+
-- no OMNI block is version 0: the original, lenient format, frozen
|
|
667
|
+
-- forever. Version 1 turns on strict entry validation (see checkentry).
|
|
668
|
+
specversion :: Int
|
|
669
|
+
specversion = 1
|
|
670
|
+
|
|
671
|
+
-- | Capability strings this runner supports beyond the version baseline.
|
|
672
|
+
-- A spec's OMNI.requires list is checked against this: an unknown
|
|
673
|
+
-- capability refuses the spec loudly at load time, instead of a lagging
|
|
674
|
+
-- port silently mis-running it. (Empty today; future format features
|
|
675
|
+
-- mint a string here.)
|
|
676
|
+
capabilities :: [String]
|
|
677
|
+
capabilities = []
|
|
678
|
+
|
|
679
|
+
-- | The complete set of fields an entry may carry. Under version 1
|
|
680
|
+
-- anything else is an error: an unrecognised key is almost always a
|
|
681
|
+
-- typo'd assertion, and a typo'd assertion is a test that silently
|
|
682
|
+
-- stopped testing.
|
|
683
|
+
entryfields :: [String]
|
|
684
|
+
entryfields = ["in", "args", "ctx", "out", "err", "match", "client", "id", "doc"]
|
|
685
|
+
|
|
686
|
+
-- | Read the spec's format version from its optional top-level OMNI
|
|
687
|
+
-- block, and refuse a spec this runner cannot faithfully run: a version
|
|
688
|
+
-- newer than specversion, or a required capability not in capabilities.
|
|
689
|
+
resolveversion :: Json -> IO Int
|
|
690
|
+
resolveversion alltests
|
|
691
|
+
-- A present-but-null (or otherwise non-map) OMNI block is malformed;
|
|
692
|
+
-- only a genuinely absent key is legacy version 0 - presence, not
|
|
693
|
+
-- definedness, is the test.
|
|
694
|
+
| not (jhas alltests "OMNI") = pure 0
|
|
695
|
+
| otherwise = do
|
|
696
|
+
let meta = jget alltests "OMNI"
|
|
697
|
+
|
|
698
|
+
version <- case (asmap meta, asnum (jget meta "version")) of
|
|
699
|
+
(Just _, Just num)
|
|
700
|
+
| num == fromIntegral (truncate num :: Integer) -> pure (round num :: Int)
|
|
701
|
+
_ -> throwIO (OmniError "omni: malformed OMNI version block")
|
|
702
|
+
|
|
703
|
+
if version < 0 || specversion < version
|
|
704
|
+
then throwIO (OmniError ("omni: unsupported spec version: " ++ show version))
|
|
705
|
+
else pure ()
|
|
706
|
+
|
|
707
|
+
-- `requires` present but null (or otherwise not a list) is
|
|
708
|
+
-- malformed; a genuinely absent key skips the check.
|
|
709
|
+
if jhas meta "requires"
|
|
710
|
+
then case aslist (jget meta "requires") of
|
|
711
|
+
Nothing -> throwIO (OmniError "omni: malformed OMNI requires list")
|
|
712
|
+
Just caps -> mapM_ checkcap caps
|
|
713
|
+
else pure ()
|
|
714
|
+
|
|
715
|
+
pure version
|
|
716
|
+
where
|
|
717
|
+
checkcap cap = case asstr cap of
|
|
718
|
+
Just text | text `elem` capabilities -> pure ()
|
|
719
|
+
_ -> throwIO (OmniError ("omni: spec requires unsupported capability: " ++ stringify cap))
|
|
720
|
+
|
|
721
|
+
-- | Strict entry validation, applied when the spec declares version 1 or
|
|
722
|
+
-- later. The lenient format converts each of these mistakes into a
|
|
723
|
+
-- silent pass or a dead field; here they fail with the entry named.
|
|
724
|
+
checkentry :: String -> Int -> Json -> IO ()
|
|
725
|
+
checkentry label index entry
|
|
726
|
+
| not (ismap entry) = throwIO (failure label index entry "entry is not a map" Nothing Nothing)
|
|
727
|
+
| otherwise = do
|
|
728
|
+
mapM_ checkfield (map fst (fromMaybe [] (asmap entry)))
|
|
729
|
+
|
|
730
|
+
let argsources = length (filter (jhas entry) ["in", "args", "ctx"])
|
|
731
|
+
if 1 < argsources
|
|
732
|
+
then throwIO (failure label index entry "entry has more than one of in, args, ctx" Nothing Nothing)
|
|
733
|
+
else pure ()
|
|
734
|
+
|
|
735
|
+
if not (isnone (jget entry "err")) && jhas entry "out"
|
|
736
|
+
then throwIO (failure label index entry "entry has both err and out" Nothing Nothing)
|
|
737
|
+
else pure ()
|
|
738
|
+
|
|
739
|
+
-- Presence, not definedness: an authored `id: null` must fail here,
|
|
740
|
+
-- before null-normalisation can rewrite it into a sentinel string.
|
|
741
|
+
let entryid = jget entry "id"
|
|
742
|
+
if not (isabsent entryid) && not (isJust (asstr entryid))
|
|
743
|
+
then throwIO (failure label index entry "entry id is not a string" Nothing Nothing)
|
|
744
|
+
else pure ()
|
|
745
|
+
where
|
|
746
|
+
checkfield key =
|
|
747
|
+
if key `elem` entryfields
|
|
748
|
+
then pure ()
|
|
749
|
+
else throwIO (failure label index entry ("unknown entry field: " ++ key) Nothing Nothing)
|
|
750
|
+
|
|
751
|
+
-- | Validate a version-1 group up front, against the AUTHORED entries -
|
|
752
|
+
-- null-normalisation would otherwise rewrite an authored null (e.g.
|
|
753
|
+
-- id: null) into a sentinel string and hide it from validation. A
|
|
754
|
+
-- malformed spec is a spec error, not a test result, so it fails before
|
|
755
|
+
-- any subject runs.
|
|
756
|
+
checkset :: String -> Json -> [Json] -> IO ()
|
|
757
|
+
checkset label testspec normalset = do
|
|
758
|
+
let origset = case (asmap testspec, aslist (jget testspec "set")) of
|
|
759
|
+
(Just _, Just list) -> list
|
|
760
|
+
_ -> normalset
|
|
761
|
+
|
|
762
|
+
isemptymarked = case jget testspec "empty" of
|
|
763
|
+
Bool True -> True
|
|
764
|
+
_ -> False
|
|
765
|
+
|
|
766
|
+
if null origset && not isemptymarked
|
|
767
|
+
then throwIO (OmniError ("omni: empty test set: " ++ label))
|
|
768
|
+
else pure ()
|
|
769
|
+
|
|
770
|
+
mapM_ (\(index, entry) -> checkentry label index entry) (zip [0 :: Int ..] origset)
|
|
771
|
+
|
|
772
|
+
-- | Find @primary.\<name\>@, then @\<name\>@, then the whole spec.
|
|
773
|
+
resolvespec :: String -> Json -> Json
|
|
774
|
+
resolvespec name alltests
|
|
775
|
+
| null name = alltests
|
|
776
|
+
| not (isabsent primary) = primary
|
|
777
|
+
| not (isabsent section) = section
|
|
778
|
+
| otherwise = alltests
|
|
779
|
+
where
|
|
780
|
+
primary = jget (jget alltests "primary") name
|
|
781
|
+
section = jget alltests name
|
|
782
|
+
|
|
783
|
+
-- | Nulls (and absent values) become NULLMARK. Always a fresh copy.
|
|
784
|
+
fixjson :: Json -> Bool -> Json
|
|
785
|
+
fixjson value donull
|
|
786
|
+
-- Canonical returns the value UNCHANGED when donull is false
|
|
787
|
+
-- (typescript/src/Runner.ts): absent stays absent and null stays null.
|
|
788
|
+
-- Answering Null for both collapsed two states the corpus distinguishes,
|
|
789
|
+
-- so a subject that correctly returned nothing was compared against null
|
|
790
|
+
-- and marked wrong. Same defect the other ports carried (#17, #23, #25,
|
|
791
|
+
-- #26, #27); this one was missed because no consumer had exercised it.
|
|
792
|
+
| isnone value = if donull then Str nullmark else value
|
|
793
|
+
| otherwise = case value of
|
|
794
|
+
JList entries -> JList (map (`fixjson` donull) entries)
|
|
795
|
+
JMap entries -> JMap (map (\(key, entry) -> (key, fixjson entry donull)) entries)
|
|
796
|
+
other -> other
|
|
797
|
+
|
|
798
|
+
-- | The JSON form of an error: always at least {name,message}.
|
|
799
|
+
errify :: String -> Json
|
|
800
|
+
errify message = JMap [("name", Str "Error"), ("message", Str message)]
|
|
801
|
+
|
|
802
|
+
-- | The message an @err@ expectation matches.
|
|
803
|
+
errmessage :: SomeException -> String
|
|
804
|
+
errmessage = displayException
|
|
805
|
+
|
|
806
|
+
-- | Match one leaf: \/regex\/ or case-insensitive substring for strings.
|
|
807
|
+
matchval :: Json -> Json -> Bool
|
|
808
|
+
matchval check base
|
|
809
|
+
| deepequal check base = True
|
|
810
|
+
| isnone want = isnone base || asstr base == Just nullmark
|
|
811
|
+
| otherwise = case asstr want of
|
|
812
|
+
-- An empty want is not a wildcard: the empty string is a substring of
|
|
813
|
+
-- everything, so `match:{out:""}` (or `err:""`) would accept any value.
|
|
814
|
+
Just "" -> asstr base == Just ""
|
|
815
|
+
Just text ->
|
|
816
|
+
let basestr = stringify base
|
|
817
|
+
in if length text > 2 && "/" `isPrefixOf` text && "/" == [last text]
|
|
818
|
+
then regexFind (init (drop 1 text)) basestr
|
|
819
|
+
else map toLower text `isInfixOf` map toLower basestr
|
|
820
|
+
Nothing -> deepequal want base
|
|
821
|
+
where
|
|
822
|
+
want = case asstr check of
|
|
823
|
+
Just text | text == undefmark || text == nullmark -> Null
|
|
824
|
+
_ -> check
|
|
825
|
+
|
|
826
|
+
-- | Convert NULLMARK sentinels back into real nulls.
|
|
827
|
+
nullmodifier :: Json -> Json
|
|
828
|
+
nullmodifier value = case asstr value of
|
|
829
|
+
Just text | text == nullmark -> Null
|
|
830
|
+
Just text -> Str (replace nullmark "null" text)
|
|
831
|
+
Nothing -> value
|
|
832
|
+
where
|
|
833
|
+
replace _ _ [] = []
|
|
834
|
+
replace from to text@(ch : rest)
|
|
835
|
+
| from `isPrefixOf` text = to ++ replace from to (drop (length from) text)
|
|
836
|
+
| otherwise = ch : replace from to rest
|
|
837
|
+
|
|
838
|
+
-- The spec-defined part of an entry (drop runner bookkeeping).
|
|
839
|
+
entrysummary :: Json -> Json
|
|
840
|
+
entrysummary (JMap entries) =
|
|
841
|
+
JMap (filter (\(key, _) -> key /= "res" && key /= "thrown" && key /= "ctx") entries)
|
|
842
|
+
entrysummary other = other
|
|
843
|
+
|
|
844
|
+
-- The label of one entry, for failure messages.
|
|
845
|
+
entryref :: String -> Int -> Json -> String
|
|
846
|
+
entryref label index entry = label ++ "[" ++ show index ++ "]" ++ idpart
|
|
847
|
+
where
|
|
848
|
+
entryid = jget entry "id"
|
|
849
|
+
idpart = if isabsent entryid then "" else " (" ++ stringify entryid ++ ")"
|
|
850
|
+
|
|
851
|
+
failure :: String -> Int -> Json -> String -> Maybe String -> Maybe String -> OmniError
|
|
852
|
+
failure label index entry reason expected actual =
|
|
853
|
+
OmniError
|
|
854
|
+
( "omni: "
|
|
855
|
+
++ entryref label index entry
|
|
856
|
+
++ ": "
|
|
857
|
+
++ reason
|
|
858
|
+
++ maybe "" ("\n expected: " ++) expected
|
|
859
|
+
++ maybe "" ("\n actual: " ++) actual
|
|
860
|
+
++ "\n entry: "
|
|
861
|
+
++ stringify (entrysummary entry)
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
-- Check that every leaf of `check` is present, and matches, in `base`.
|
|
865
|
+
matchcheck :: String -> Int -> Json -> Json -> Json -> [String] -> IO ()
|
|
866
|
+
matchcheck label index entry check base path = case check of
|
|
867
|
+
JList entries ->
|
|
868
|
+
mapM_
|
|
869
|
+
(\(at, subcheck) -> matchcheck label index entry subcheck base (path ++ [show at]))
|
|
870
|
+
(zip [0 :: Int ..] entries)
|
|
871
|
+
JMap entries ->
|
|
872
|
+
mapM_
|
|
873
|
+
(\(key, subcheck) -> matchcheck label index entry subcheck base (path ++ [key]))
|
|
874
|
+
entries
|
|
875
|
+
leaf -> checkleaf leaf
|
|
876
|
+
where
|
|
877
|
+
place = if null path then "<root>" else pathify path
|
|
878
|
+
baseval = getpath base path
|
|
879
|
+
|
|
880
|
+
bad reason expected actual =
|
|
881
|
+
throwIO (failure label index entry (reason ++ place) (Just expected) (Just actual))
|
|
882
|
+
|
|
883
|
+
-- The sentinels are tested BEFORE the identity check below.
|
|
884
|
+
-- Otherwise a subject returning the literal string "__UNDEF__"
|
|
885
|
+
-- satisfies an assertion that the key is absent - two mutually
|
|
886
|
+
-- exclusive states passing one check. A sentinel that accepts its own
|
|
887
|
+
-- literal is not a sentinel. (NULLMARK still accepts NULLMARK: under
|
|
888
|
+
-- the default null flag a real null has already been normalised to
|
|
889
|
+
-- it, so the two are genuinely indistinguishable here - that one
|
|
890
|
+
-- needs a raw-value escape, not an ordering change.)
|
|
891
|
+
checkleaf leaf
|
|
892
|
+
-- Explicitly absent: satisfied only by a genuinely missing key,
|
|
893
|
+
-- never by a present null (the distinction the sentinels keep).
|
|
894
|
+
| asstr leaf == Just undefmark =
|
|
895
|
+
if isabsent baseval
|
|
896
|
+
then pure ()
|
|
897
|
+
else bad "expected absent at " "absent" (stringify baseval)
|
|
898
|
+
-- Explicitly null: satisfied only by a present null.
|
|
899
|
+
| asstr leaf == Just nullmark =
|
|
900
|
+
if isnull baseval || asstr baseval == Just nullmark
|
|
901
|
+
then pure ()
|
|
902
|
+
else bad "expected null at " "null" (stringify baseval)
|
|
903
|
+
-- Explicitly present: any present value, including null.
|
|
904
|
+
| asstr leaf == Just existsmark =
|
|
905
|
+
if not (isabsent baseval)
|
|
906
|
+
then pure ()
|
|
907
|
+
else bad "expected present at " "present" "absent"
|
|
908
|
+
-- Identical values match. This sits below the sentinel branches on
|
|
909
|
+
-- purpose - see the note above.
|
|
910
|
+
| deepequal leaf baseval = pure ()
|
|
911
|
+
-- A concrete expectation never matches a missing key - a match
|
|
912
|
+
-- leaf against an absent value must fail, not substring-match
|
|
913
|
+
-- "undefined".
|
|
914
|
+
| isabsent baseval = bad "match failed at " (stringify leaf) "absent"
|
|
915
|
+
| matchval leaf baseval = pure ()
|
|
916
|
+
| otherwise = bad "match failed at " (stringify leaf) (stringify baseval)
|
|
917
|
+
|
|
918
|
+
checkresult :: String -> Int -> Json -> [Json] -> Json -> IO ()
|
|
919
|
+
checkresult label index entry args res = do
|
|
920
|
+
let entryerr = jget entry "err"
|
|
921
|
+
|
|
922
|
+
if not (isnone entryerr)
|
|
923
|
+
then
|
|
924
|
+
throwIO
|
|
925
|
+
( failure
|
|
926
|
+
label
|
|
927
|
+
index
|
|
928
|
+
entry
|
|
929
|
+
"expected error did not occur"
|
|
930
|
+
(Just (stringify entryerr))
|
|
931
|
+
(Just (stringify res))
|
|
932
|
+
)
|
|
933
|
+
else pure ()
|
|
934
|
+
|
|
935
|
+
let check = jget entry "match"
|
|
936
|
+
matched = not (isnone check)
|
|
937
|
+
|
|
938
|
+
if matched
|
|
939
|
+
then
|
|
940
|
+
matchcheck
|
|
941
|
+
label
|
|
942
|
+
index
|
|
943
|
+
entry
|
|
944
|
+
check
|
|
945
|
+
( JMap
|
|
946
|
+
[ ("in", jget entry "in"),
|
|
947
|
+
("args", JList args),
|
|
948
|
+
("out", jget entry "res"),
|
|
949
|
+
("ctx", jget entry "ctx")
|
|
950
|
+
]
|
|
951
|
+
)
|
|
952
|
+
[]
|
|
953
|
+
else pure ()
|
|
954
|
+
|
|
955
|
+
let out = jget entry "out"
|
|
956
|
+
|
|
957
|
+
if deepequal res out
|
|
958
|
+
then pure ()
|
|
959
|
+
else
|
|
960
|
+
-- NOTE: a match with no explicit out is a complete check on its own.
|
|
961
|
+
if matched && (isnone out || asstr out == Just nullmark)
|
|
962
|
+
then pure ()
|
|
963
|
+
else
|
|
964
|
+
throwIO
|
|
965
|
+
(failure label index entry "result mismatch" (Just (stringify out)) (Just (stringify res)))
|
|
966
|
+
|
|
967
|
+
-- | The error base a @match.err@ sees: the provider's own, when it has one.
|
|
968
|
+
errbase :: String -> Provider -> Json
|
|
969
|
+
errbase message provider = case providerErrify provider of
|
|
970
|
+
Just hook -> hook message
|
|
971
|
+
Nothing -> errify message
|
|
972
|
+
|
|
973
|
+
handleerror :: String -> Int -> Json -> String -> Provider -> IO ()
|
|
974
|
+
handleerror label index entry message provider = do
|
|
975
|
+
let entryerr = jget entry "err"
|
|
976
|
+
|
|
977
|
+
if isnone entryerr
|
|
978
|
+
then throwIO (failure label index entry "unexpected error" Nothing (Just message))
|
|
979
|
+
else do
|
|
980
|
+
let istrue = case entryerr of
|
|
981
|
+
Bool True -> True
|
|
982
|
+
_ -> False
|
|
983
|
+
|
|
984
|
+
if istrue || matchval entryerr (Str message)
|
|
985
|
+
then do
|
|
986
|
+
let check = jget entry "match"
|
|
987
|
+
if not (isnone check)
|
|
988
|
+
then
|
|
989
|
+
matchcheck
|
|
990
|
+
label
|
|
991
|
+
index
|
|
992
|
+
entry
|
|
993
|
+
check
|
|
994
|
+
( JMap
|
|
995
|
+
[ ("in", jget entry "in"),
|
|
996
|
+
("out", jget entry "res"),
|
|
997
|
+
("ctx", jget entry "ctx"),
|
|
998
|
+
("err", errbase message provider)
|
|
999
|
+
]
|
|
1000
|
+
)
|
|
1001
|
+
[]
|
|
1002
|
+
else pure ()
|
|
1003
|
+
else
|
|
1004
|
+
throwIO
|
|
1005
|
+
( failure
|
|
1006
|
+
label
|
|
1007
|
+
index
|
|
1008
|
+
entry
|
|
1009
|
+
"error mismatch"
|
|
1010
|
+
(Just (stringify entryerr))
|
|
1011
|
+
(Just message)
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
-- | Make a runner for a spec value and a provider.
|
|
1015
|
+
makeRunnerSpec :: Json -> Provider -> String -> IO RunPack
|
|
1016
|
+
makeRunnerSpec alltests provider name = do
|
|
1017
|
+
-- Resolved once, immediately after the spec is available - fail fast on
|
|
1018
|
+
-- an unsupported version rather than mid-run.
|
|
1019
|
+
specver <- resolveversion alltests
|
|
1020
|
+
|
|
1021
|
+
let spec = resolvespec name alltests
|
|
1022
|
+
|
|
1023
|
+
clients = case (asmap (jget (jget spec "DEF") "client"), providerClient provider) of
|
|
1024
|
+
(Just entries, Just clientmaker) ->
|
|
1025
|
+
map
|
|
1026
|
+
( \(clientname, cdef) ->
|
|
1027
|
+
let copts = jget (jget cdef "test") "options"
|
|
1028
|
+
in (clientname, clientmaker (if isabsent copts then JMap [] else copts))
|
|
1029
|
+
)
|
|
1030
|
+
entries
|
|
1031
|
+
_ -> []
|
|
1032
|
+
|
|
1033
|
+
defsubject = case providerSubject provider of
|
|
1034
|
+
Just resolve | not (null name) -> resolve name
|
|
1035
|
+
_ -> Nothing
|
|
1036
|
+
|
|
1037
|
+
runsetflags testspec flags testsubject =
|
|
1038
|
+
drive testspec flags testsubject Nothing
|
|
1039
|
+
|
|
1040
|
+
runsetflagsargs testspec flags argsubject =
|
|
1041
|
+
drive testspec flags Nothing (Just argsubject)
|
|
1042
|
+
|
|
1043
|
+
drive testspec flags testsubject argsubject = do
|
|
1044
|
+
let label = fromMaybe (if null name then "set" else name) (flagName flags)
|
|
1045
|
+
|
|
1046
|
+
subject <- case (argsubject, testsubject, defsubject) of
|
|
1047
|
+
(Just _, _, _) -> pure Nothing
|
|
1048
|
+
(Nothing, Just found, _) -> pure (Just found)
|
|
1049
|
+
(Nothing, Nothing, Just found) -> pure (Just found)
|
|
1050
|
+
_ -> throwIO (OmniError ("omni: no test subject for: " ++ label))
|
|
1051
|
+
|
|
1052
|
+
let testspecmap = fixjson testspec (flagNull flags)
|
|
1053
|
+
|
|
1054
|
+
testset <- case aslist (jget testspecmap "set") of
|
|
1055
|
+
Just entries -> pure entries
|
|
1056
|
+
Nothing -> throwIO (OmniError ("omni: test spec has no set: " ++ label))
|
|
1057
|
+
|
|
1058
|
+
if 1 <= specver
|
|
1059
|
+
then checkset label testspec testset
|
|
1060
|
+
else pure ()
|
|
1061
|
+
|
|
1062
|
+
mapM_ (runentry label subject argsubject clients flags) (zip [0 ..] testset)
|
|
1063
|
+
|
|
1064
|
+
runentry label subject argsubject clients' flags (index, rawentry) = do
|
|
1065
|
+
if not (ismap rawentry)
|
|
1066
|
+
then throwIO (OmniError ("omni: " ++ label ++ "[" ++ show index ++ "]: entry is not a map"))
|
|
1067
|
+
else pure ()
|
|
1068
|
+
|
|
1069
|
+
-- An entry with no `out` expects a null (or absent) result.
|
|
1070
|
+
let entry0 =
|
|
1071
|
+
if flagNull flags && isnone (jget rawentry "out")
|
|
1072
|
+
then jset rawentry "out" (Str nullmark)
|
|
1073
|
+
else rawentry
|
|
1074
|
+
|
|
1075
|
+
entrysubject <- case (subject, asstr (jget entry0 "client")) of
|
|
1076
|
+
(Nothing, _) -> pure Nothing
|
|
1077
|
+
(Just found, Just clientname) -> case lookup clientname clients' of
|
|
1078
|
+
Nothing -> throwIO (OmniError ("omni: unknown client: " ++ clientname))
|
|
1079
|
+
Just client -> case providerSubject client of
|
|
1080
|
+
Just resolve -> pure (Just (fromMaybe found (resolve name)))
|
|
1081
|
+
Nothing -> pure (Just found)
|
|
1082
|
+
(Just found, Nothing) -> pure (Just found)
|
|
1083
|
+
|
|
1084
|
+
-- Build the argument list: `ctx`, `args`, or `in`.
|
|
1085
|
+
let hasctx = jhas entry0 "ctx"
|
|
1086
|
+
hasargs = jhas entry0 "args"
|
|
1087
|
+
|
|
1088
|
+
args0
|
|
1089
|
+
| hasctx = [jget entry0 "ctx"]
|
|
1090
|
+
| hasargs = fromMaybe [jget entry0 "args"] (aslist (jget entry0 "args"))
|
|
1091
|
+
| otherwise = [clone (jget entry0 "in")]
|
|
1092
|
+
|
|
1093
|
+
(args, entry) =
|
|
1094
|
+
if (hasctx || hasargs) && not (null args0) && ismap (head args0)
|
|
1095
|
+
then
|
|
1096
|
+
let contextified = case providerContextify provider of
|
|
1097
|
+
Just contextify -> contextify (head args0)
|
|
1098
|
+
Nothing -> head args0
|
|
1099
|
+
-- The resolved client is a live Provider (closures
|
|
1100
|
+
-- over the system under test), and this port's Json
|
|
1101
|
+
-- carries no host-object variant to hold one - so
|
|
1102
|
+
-- canonical's `first.client = testpack.client`
|
|
1103
|
+
-- becomes presence, not identity: enough for a
|
|
1104
|
+
-- subject to prove the runner attached a client.
|
|
1105
|
+
first = jset contextified "client" (Bool True)
|
|
1106
|
+
in (first : tail args0, jset entry0 "ctx" first)
|
|
1107
|
+
else (args0, entry0)
|
|
1108
|
+
|
|
1109
|
+
-- An args-subject returns its arguments alongside the result, so
|
|
1110
|
+
-- `match.args` sees what the subject actually did with them.
|
|
1111
|
+
let invoke = case (argsubject, entrysubject) of
|
|
1112
|
+
(Just call, _) -> call args
|
|
1113
|
+
(Nothing, Just call) -> (,) args <$> call args
|
|
1114
|
+
(Nothing, Nothing) ->
|
|
1115
|
+
throwIO (OmniError ("omni: no test subject for: " ++ label))
|
|
1116
|
+
|
|
1117
|
+
outcome <- try invoke
|
|
1118
|
+
|
|
1119
|
+
case outcome of
|
|
1120
|
+
Right (callargs, rawres) -> do
|
|
1121
|
+
let res = fixjson rawres (flagNull flags)
|
|
1122
|
+
checkresult label index (jset entry "res" res) callargs res
|
|
1123
|
+
Left err -> case fromOmni err of
|
|
1124
|
+
Just omnierr -> throwIO omnierr
|
|
1125
|
+
Nothing -> handleerror label index entry (errmessage err) provider
|
|
1126
|
+
|
|
1127
|
+
pure
|
|
1128
|
+
RunPack
|
|
1129
|
+
{ packSpec = spec,
|
|
1130
|
+
packSet = jget spec,
|
|
1131
|
+
runset = \testspec testsubject -> runsetflags testspec defaultFlags testsubject,
|
|
1132
|
+
runsetFlags = runsetflags,
|
|
1133
|
+
runsetFlagsArgs = runsetflagsargs,
|
|
1134
|
+
packSubject = defsubject,
|
|
1135
|
+
packClient = provider
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
-- An OmniError raised inside a subject is a runner failure, not a
|
|
1139
|
+
-- candidate for an `err` expectation.
|
|
1140
|
+
fromOmni :: SomeException -> Maybe OmniError
|
|
1141
|
+
fromOmni = fromException
|
|
1142
|
+
|
|
1143
|
+
-- | Make a runner for a spec file path and a provider.
|
|
1144
|
+
makeRunner :: String -> Provider -> String -> IO RunPack
|
|
1145
|
+
makeRunner path provider name = do
|
|
1146
|
+
alltests <- loadspec path
|
|
1147
|
+
makeRunnerSpec alltests provider name
|