@voxgig/sdkgen 4.5.3 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/action/check.js +15 -0
  3. package/dist/action/check.js.map +1 -1
  4. package/dist/cmp/AgentGuide.js +1 -1
  5. package/dist/cmp/AgentGuide.js.map +1 -1
  6. package/dist/cmp/AgentGuideContent.d.ts +1 -1
  7. package/dist/cmp/AgentGuideContent.js +8 -2
  8. package/dist/cmp/AgentGuideContent.js.map +1 -1
  9. package/dist/cmp/Feature.js +22 -0
  10. package/dist/cmp/Feature.js.map +1 -1
  11. package/dist/cmp/FeatureDocs.d.ts +1 -1
  12. package/dist/cmp/FeatureDocs.js +6 -1
  13. package/dist/cmp/FeatureDocs.js.map +1 -1
  14. package/dist/cmp/ReadmeExplanation.js +3 -1
  15. package/dist/cmp/ReadmeExplanation.js.map +1 -1
  16. package/dist/cmp/ReadmeFeatures.js +1 -1
  17. package/dist/cmp/ReadmeFeatures.js.map +1 -1
  18. package/dist/cmp/ReadmeRefFeatures.js +1 -1
  19. package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
  20. package/dist/cmp/TestControl.d.ts +4 -0
  21. package/dist/cmp/TestControl.js +51 -0
  22. package/dist/cmp/TestControl.js.map +1 -0
  23. package/dist/helpers/applicability.d.ts +6 -0
  24. package/dist/helpers/applicability.js +103 -0
  25. package/dist/helpers/applicability.js.map +1 -0
  26. package/dist/helpers/collectDeps.js +4 -2
  27. package/dist/helpers/collectDeps.js.map +1 -1
  28. package/dist/helpers/featureSource.d.ts +3 -1
  29. package/dist/helpers/featureSource.js +105 -0
  30. package/dist/helpers/featureSource.js.map +1 -1
  31. package/dist/helpers/serverVars.d.ts +2 -1
  32. package/dist/helpers/serverVars.js +17 -0
  33. package/dist/helpers/serverVars.js.map +1 -1
  34. package/dist/sdkgen.d.ts +5 -3
  35. package/dist/sdkgen.js +16 -4
  36. package/dist/sdkgen.js.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/utility.d.ts +2 -1
  39. package/dist/utility.js +59 -1
  40. package/dist/utility.js.map +1 -1
  41. package/model/sdkgen.aon +97 -0
  42. package/package.json +1 -1
  43. package/project/.sdk/model/feature/feature-index.aon +1 -0
  44. package/project/.sdk/model/feature/secrets.aon +200 -0
  45. package/project/.sdk/model/target/ts.aon +8 -1
  46. package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
  47. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
  48. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
  49. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
  50. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
  51. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
  52. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
  53. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
  54. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
  55. package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
  56. package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
  57. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
  58. package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
  59. package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
  60. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
  61. package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
  62. package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
  63. package/project/.sdk/src/cmp/go/Main_go.ts +8 -3
  64. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
  65. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
  66. package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
  67. package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
  68. package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
  69. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
  70. package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
  71. package/project/.sdk/src/cmp/java/Main_java.ts +7 -2
  72. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
  73. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
  74. package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
  75. package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
  76. package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
  77. package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
  78. package/project/.sdk/src/cmp/js/Main_js.ts +7 -2
  79. package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
  80. package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
  81. package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
  82. package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
  83. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
  84. package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
  85. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
  86. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
  87. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
  88. package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
  89. package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
  90. package/project/.sdk/src/cmp/lua/Main_lua.ts +7 -2
  91. package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
  92. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
  93. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
  94. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
  95. package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
  96. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  97. package/project/.sdk/src/cmp/perl/Main_perl.ts +7 -2
  98. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
  99. package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
  100. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
  101. package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
  102. package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
  103. package/project/.sdk/src/cmp/php/Main_php.ts +7 -2
  104. package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
  105. package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
  106. package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
  107. package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
  108. package/project/.sdk/src/cmp/py/Main_py.ts +7 -2
  109. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
  110. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
  111. package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
  112. package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
  113. package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
  114. package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
  115. package/project/.sdk/src/cmp/rb/Main_rb.ts +7 -2
  116. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
  117. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
  118. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
  119. package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
  120. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
  121. package/project/.sdk/src/cmp/rust/Main_rust.ts +7 -2
  122. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
  123. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
  124. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
  125. package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
  126. package/project/.sdk/src/cmp/scala/Main_scala.ts +7 -2
  127. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
  128. package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
  129. package/project/.sdk/src/cmp/ts/Config_ts.ts +55 -9
  130. package/project/.sdk/src/cmp/ts/Main_ts.ts +67 -3
  131. package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
  132. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
  133. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
  134. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
  135. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +75 -8
  136. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +36 -5
  137. package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
  138. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
  139. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
  140. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
  141. package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
  142. package/project/.sdk/tm/c/utility/make_options.c +22 -0
  143. package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
  144. package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
  145. package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
  146. package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
  147. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
  148. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
  149. package/project/.sdk/tm/dart/test/utility.dart +39 -0
  150. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
  151. package/project/.sdk/tm/go/core/context.go +1 -1
  152. package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
  153. package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
  154. package/project/.sdk/tm/go/feature/test_feature.go +12 -5
  155. package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
  156. package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
  157. package/project/.sdk/tm/go/test/runner_test.go +46 -0
  158. package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
  159. package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
  160. package/project/.sdk/tm/go/utility/fetcher.go +2 -2
  161. package/project/.sdk/tm/go/utility/make_options.go +24 -5
  162. package/project/.sdk/tm/go/utility/make_point.go +1 -1
  163. package/project/.sdk/tm/go/utility/make_spec.go +17 -1
  164. package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
  165. package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
  166. package/project/.sdk/tm/go/utility/transform_request.go +29 -1
  167. package/project/.sdk/tm/go/utility/transform_response.go +20 -1
  168. package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
  169. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
  170. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
  171. package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
  172. package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
  173. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
  174. package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
  175. package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
  176. package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
  177. package/project/.sdk/tm/js/test/utility.js +91 -1
  178. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
  179. package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
  180. package/project/.sdk/tm/lua/test/runner.lua +40 -0
  181. package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
  182. package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
  183. package/project/.sdk/tm/perl/t/runner.pm +30 -0
  184. package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
  185. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
  186. package/project/.sdk/tm/php/test/Runner.php +36 -0
  187. package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
  188. package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
  189. package/project/.sdk/tm/py/test/runner.py +32 -0
  190. package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
  191. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
  192. package/project/.sdk/tm/rb/test/runner.rb +25 -0
  193. package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
  194. package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
  195. package/project/.sdk/tm/rust/utility/make_options.rs +26 -1
  196. package/project/.sdk/tm/scala/utility/Make.scala +17 -0
  197. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
  198. package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
  199. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +387 -0
  200. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +175 -0
  201. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +51 -0
  202. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
  203. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
  204. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
  205. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
  206. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
  207. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
  208. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
  209. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
  210. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
  211. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
  212. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
  213. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
  214. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
  215. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
  216. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
  217. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
  218. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
  219. package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
  220. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
  221. package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
  222. package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
  223. package/project/.sdk/tm/ts/test/omni.ts +86 -0
  224. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
  225. package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
  226. package/project/.sdk/tm/ts/test/utility.ts +122 -0
  227. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +694 -0
  228. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +244 -0
  229. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
  230. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
  231. package/project/.sdk/tm/zig/core/utility.zig +24 -1
  232. package/project/sdkgen-package.json +2 -1
  233. package/src/action/check.ts +18 -0
  234. package/src/cmp/AgentGuide.ts +1 -1
  235. package/src/cmp/AgentGuideContent.ts +9 -2
  236. package/src/cmp/Feature.ts +24 -0
  237. package/src/cmp/FeatureDocs.ts +7 -1
  238. package/src/cmp/ReadmeExplanation.ts +4 -1
  239. package/src/cmp/ReadmeFeatures.ts +1 -1
  240. package/src/cmp/ReadmeRefFeatures.ts +1 -1
  241. package/src/cmp/TestControl.ts +64 -0
  242. package/src/helpers/applicability.ts +123 -0
  243. package/src/helpers/collectDeps.ts +5 -1
  244. package/src/helpers/featureSource.ts +130 -0
  245. package/src/helpers/serverVars.ts +19 -0
  246. package/src/sdkgen.ts +16 -1
  247. package/src/utility.ts +71 -1
  248. package/project/.sdk/tm/ts/test/runner.ts +0 -425
@@ -0,0 +1,694 @@
1
+ // VENDORED: @voxgig/omni 0.1.2 (typescript/src/Runner.ts)
2
+ // Source: https://github.com/voxgig/omni @ 5956cc4e5ecdaeebd11eab8bb4b9462dfc76e018
3
+ // License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
4
+ // Omni: the shared multi-language test runner.
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, so behaviour is defined once and
8
+ // verified everywhere.
9
+ //
10
+ // This file is CANONICAL. Every other port is a translation of it.
11
+
12
+ import { readFileSync } from 'node:fs'
13
+
14
+ import {
15
+ EXISTSMARK,
16
+ Json,
17
+ NULLMARK,
18
+ UNDEFMARK,
19
+ clone,
20
+ deepequal,
21
+ getpath,
22
+ islist,
23
+ ismap,
24
+ isnode,
25
+ pathify,
26
+ stringify,
27
+ walk,
28
+ } from './Util'
29
+
30
+ // A test subject: the function under test.
31
+ export type Subject = (...args: Json[]) => any
32
+
33
+ // Run-time options for a set of test entries.
34
+ export type Flags = {
35
+ // Convert JSON nulls to the NULLMARK sentinel (default: true).
36
+ null?: boolean
37
+ // Label used in failure messages (default: the runner name).
38
+ name?: string
39
+ [flag: string]: any
40
+ }
41
+
42
+ // The host of the system under test. Every hook is optional: a spec that
43
+ // resolves its subjects explicitly needs no provider at all.
44
+ export type Provider = {
45
+ // Resolve a test subject by name.
46
+ subject?: (name: string) => Subject | undefined | null
47
+ // Build a sub-provider from a spec DEF.client entry's options.
48
+ client?: (options: Json) => Provider | Promise<Provider>
49
+ // Wrap a map argument as a call context before it is passed in.
50
+ contextify?: (val: Json) => Json
51
+ // Resolve references in client options against the runner store.
52
+ inject?: (options: Json, store: Json) => Json
53
+ }
54
+
55
+ export type RunSet = (testspec: Json, testsubject?: Subject) => Promise<void>
56
+
57
+ export type RunSetFlags = (
58
+ testspec: Json,
59
+ flags: Flags,
60
+ testsubject?: Subject,
61
+ ) => Promise<void>
62
+
63
+ export type RunPack = {
64
+ // The resolved spec (the named section, or the whole file).
65
+ spec: Json
66
+ // Run one set of test entries.
67
+ runset: RunSet
68
+ // Run one set of test entries with flags.
69
+ runsetflags: RunSetFlags
70
+ // The default subject, if the provider could resolve one.
71
+ subject?: Subject
72
+ // The root provider.
73
+ client?: Provider
74
+ }
75
+
76
+ export type Runner = (name?: string, store?: Json) => Promise<RunPack>
77
+
78
+ // The newest spec format version this runner understands. A spec with no
79
+ // OMNI block is version 0: the original, lenient format, frozen forever.
80
+ // Version 1 turns on strict entry validation (see checkentry).
81
+ export const SPECVERSION = 1
82
+
83
+ // Capability strings this runner supports beyond the version baseline. A
84
+ // spec's OMNI.requires list is checked against this: an unknown capability
85
+ // refuses the spec loudly at load time, instead of a lagging port silently
86
+ // mis-running it. (Empty today; future format features mint a string here.)
87
+ export const CAPABILITIES: string[] = []
88
+
89
+ // The complete set of fields an entry may carry. Under version 1 anything
90
+ // else is an error: an unrecognised key is almost always a typo'd
91
+ // assertion, and a typo'd assertion is a test that silently stopped
92
+ // testing.
93
+ const ENTRYFIELDS = ['in', 'args', 'ctx', 'out', 'err', 'match', 'client', 'id', 'doc']
94
+
95
+ // A test failure (or a malformed spec). Distinct from errors thrown by the
96
+ // subject under test, which are candidates for an `err` expectation.
97
+ export class OmniError extends Error {
98
+ entry?: Json
99
+ constructor(message: string, entry?: Json) {
100
+ super(message)
101
+ this.name = 'OmniError'
102
+ this.entry = entry
103
+ }
104
+ }
105
+
106
+ // Load a spec: either a path to a JSON file, or an already-parsed object.
107
+ function loadspec(specref: string | Json): Json {
108
+ if ('string' === typeof specref) {
109
+ return JSON.parse(readFileSync(specref, 'utf8'))
110
+ }
111
+ return specref
112
+ }
113
+
114
+ // Read the spec's format version from its optional top-level OMNI block,
115
+ // and refuse a spec this runner cannot faithfully run: a version newer
116
+ // than SPECVERSION, or a required capability not in CAPABILITIES.
117
+ function resolveversion(alltests: Json): number {
118
+ const meta = ismap(alltests) ? alltests.OMNI : undefined
119
+
120
+ if (undefined === meta) {
121
+ return 0
122
+ }
123
+
124
+ if (!ismap(meta) || 'number' !== typeof meta.version || 0 !== meta.version % 1) {
125
+ throw new OmniError('omni: malformed OMNI version block')
126
+ }
127
+
128
+ if (meta.version < 0 || SPECVERSION < meta.version) {
129
+ throw new OmniError('omni: unsupported spec version: ' + meta.version)
130
+ }
131
+
132
+ const requires = meta.requires
133
+ if (undefined !== requires) {
134
+ if (!islist(requires)) {
135
+ throw new OmniError('omni: malformed OMNI requires list')
136
+ }
137
+ for (const cap of requires) {
138
+ if ('string' !== typeof cap || !CAPABILITIES.includes(cap)) {
139
+ throw new OmniError('omni: spec requires unsupported capability: ' + stringify(cap))
140
+ }
141
+ }
142
+ }
143
+
144
+ return meta.version
145
+ }
146
+
147
+ // Strict entry validation, applied when the spec declares version 1 or
148
+ // later. The lenient format converts each of these mistakes into a silent
149
+ // pass or a dead field; here they fail with the entry named.
150
+ function checkentry(flags: Flags, index: number, entry: Json): void {
151
+ if (!ismap(entry)) {
152
+ throw fail(flags, index, entry, 'entry is not a map')
153
+ }
154
+
155
+ for (const key of Object.keys(entry)) {
156
+ if (!ENTRYFIELDS.includes(key)) {
157
+ throw fail(flags, index, entry, 'unknown entry field: ' + key)
158
+ }
159
+ }
160
+
161
+ let argsources = 0
162
+ for (const key of ['in', 'args', 'ctx']) {
163
+ if (undefined !== entry[key]) {
164
+ argsources++
165
+ }
166
+ }
167
+ if (1 < argsources) {
168
+ throw fail(flags, index, entry, 'entry has more than one of in, args, ctx')
169
+ }
170
+
171
+ if (null != entry.err && undefined !== entry.out) {
172
+ throw fail(flags, index, entry, 'entry has both err and out')
173
+ }
174
+
175
+ if (undefined !== entry.id && 'string' !== typeof entry.id) {
176
+ throw fail(flags, index, entry, 'entry id is not a string')
177
+ }
178
+ }
179
+
180
+ // Validate a version-1 group up front, against the AUTHORED entries -
181
+ // null-normalisation would otherwise rewrite an authored null (e.g.
182
+ // id: null) into a sentinel string and hide it from validation. A
183
+ // malformed spec is a spec error, not a test result, so it fails before
184
+ // any subject runs.
185
+ function checkset(flags: Flags, testspec: Json, normalset: Json[]): void {
186
+ const origset =
187
+ ismap(testspec) && islist(testspec.set) ? testspec.set : normalset
188
+
189
+ if (0 === origset.length && true !== (ismap(testspec) ? testspec.empty : undefined)) {
190
+ throw new OmniError('omni: empty test set: ' + flags.name)
191
+ }
192
+
193
+ for (let index = 0; index < origset.length; index++) {
194
+ checkentry(flags, index, origset[index])
195
+ }
196
+ }
197
+
198
+ // Find the named section of a spec: `primary.<name>`, then `<name>`, then
199
+ // the whole spec.
200
+ function resolvespec(name: string | undefined, alltests: Json): Json {
201
+ if (null == name) {
202
+ return alltests
203
+ }
204
+
205
+ const primary = alltests.primary
206
+ if (ismap(primary) && null != primary[name]) {
207
+ return primary[name]
208
+ }
209
+
210
+ if (ismap(alltests) && null != alltests[name]) {
211
+ return alltests[name]
212
+ }
213
+
214
+ return alltests
215
+ }
216
+
217
+ // Build the named clients declared by the spec's DEF.client block.
218
+ async function resolveclients(
219
+ provider: Provider,
220
+ spec: Json,
221
+ store: Json,
222
+ ): Promise<Record<string, Provider>> {
223
+ const clients: Record<string, Provider> = {}
224
+
225
+ const defclient = ismap(spec) && ismap(spec.DEF) ? spec.DEF.client : undefined
226
+ if (!ismap(defclient)) {
227
+ return clients
228
+ }
229
+
230
+ // A spec may define clients that a given test run never references.
231
+ if (null == provider.client) {
232
+ return clients
233
+ }
234
+
235
+ for (const clientname of Object.keys(defclient)) {
236
+ const cdef = defclient[clientname]
237
+ const copts = clone((ismap(cdef) && ismap(cdef.test) ? cdef.test.options : undefined) || {})
238
+
239
+ if (ismap(store) && null != provider.inject) {
240
+ provider.inject(copts, store)
241
+ }
242
+
243
+ clients[clientname] = await provider.client(copts)
244
+ }
245
+
246
+ return clients
247
+ }
248
+
249
+ function resolvesubject(name: string | undefined, provider: Provider): Subject | undefined {
250
+ if (null == name || null == provider.subject) {
251
+ return undefined
252
+ }
253
+ return provider.subject(name) || undefined
254
+ }
255
+
256
+ function resolveflags(flags?: Flags): Flags {
257
+ const out: Flags = null == flags ? {} : { ...flags }
258
+ out.null = null == out.null ? true : !!out.null
259
+ return out
260
+ }
261
+
262
+ // An entry with no `out` expects a null (or absent) result.
263
+ function resolveentry(entry: Json, flags: Flags): Json {
264
+ if (null == entry.out && flags.null) {
265
+ entry.out = NULLMARK
266
+ }
267
+ return entry
268
+ }
269
+
270
+ function resolvetestpack(
271
+ name: string | undefined,
272
+ entry: Json,
273
+ subject: Subject,
274
+ provider: Provider,
275
+ clients: Record<string, Provider>,
276
+ ) {
277
+ const testpack = { client: provider, subject }
278
+
279
+ if (null != entry.client) {
280
+ const client = clients[entry.client]
281
+ if (null == client) {
282
+ throw new OmniError('omni: unknown client: ' + entry.client, entry)
283
+ }
284
+ testpack.client = client
285
+ testpack.subject = resolvesubject(name, client) || subject
286
+ }
287
+
288
+ return testpack
289
+ }
290
+
291
+ // Build the argument list for one entry: `ctx` (single context argument),
292
+ // `args` (explicit list), or `in` (single value).
293
+ function resolveargs(entry: Json, testpack: { client: Provider }, provider: Provider): Json[] {
294
+ let args: Json[]
295
+
296
+ if (undefined !== entry.ctx) {
297
+ args = [entry.ctx]
298
+ } else if (undefined !== entry.args) {
299
+ args = entry.args
300
+ } else {
301
+ args = [clone(entry.in)]
302
+ }
303
+
304
+ if (undefined !== entry.ctx || undefined !== entry.args) {
305
+ let first = args[0]
306
+ if (ismap(first)) {
307
+ first = clone(first)
308
+ if (null != provider.contextify) {
309
+ first = provider.contextify(first)
310
+ }
311
+ args[0] = first
312
+ entry.ctx = first
313
+ if (ismap(first)) {
314
+ first.client = testpack.client
315
+ }
316
+ }
317
+ }
318
+
319
+ return args
320
+ }
321
+
322
+ // Normalise a value for comparison: JSON nulls (and absent values) become
323
+ // the NULLMARK sentinel, errors become {name,message} maps. Always returns
324
+ // a fresh copy, so a spec can be run repeatedly.
325
+ function fixjson(val: Json, flags?: Flags): Json {
326
+ const donull = null == flags || null == flags.null ? true : !!flags.null
327
+ return fixjsonval(val, donull)
328
+ }
329
+
330
+ function fixjsonval(val: Json, donull: boolean): Json {
331
+ if (null == val) {
332
+ return donull ? NULLMARK : val
333
+ }
334
+
335
+ if (val instanceof Error) {
336
+ return errify(val)
337
+ }
338
+
339
+ if (islist(val)) {
340
+ return val.map((entry: Json) => fixjsonval(entry, donull))
341
+ }
342
+
343
+ if (ismap(val)) {
344
+ const out: Record<string, Json> = {}
345
+ for (const key of Object.keys(val)) {
346
+ out[key] = fixjsonval(val[key], donull)
347
+ }
348
+ return out
349
+ }
350
+
351
+ return val
352
+ }
353
+
354
+ // The JSON form of an error: always at least {name,message}.
355
+ //
356
+ // A thrown value need not be an Error. Ports commonly rethrow an
357
+ // error-SHAPED map ({name, message, ...}) - voxgig/sdkgen's generated
358
+ // makeError rethrows the fixture's own error object verbatim - and
359
+ // collapsing that to String(err) yields '[object Object]', which fails both
360
+ // the `err` check and every `match.err.*` leaf. The struct repository's
361
+ // original runner read `.message` regardless of the thrown value's class.
362
+ function errify(err: any): Json {
363
+ if (err instanceof Error) {
364
+ return { ...err, name: err.name, message: err.message }
365
+ }
366
+
367
+ if (null != err && 'object' === typeof err) {
368
+ return { name: 'Error', ...err }
369
+ }
370
+
371
+ return { name: 'Error', message: String(err) }
372
+ }
373
+
374
+ function errmessage(err: any): string {
375
+ return err instanceof Error ? err.message
376
+ : null != err && 'string' === typeof err.message ? err.message
377
+ : String(err)
378
+ }
379
+
380
+ // The label of one entry, for failure messages.
381
+ function entryref(flags: Flags, index: number, entry: Json): string {
382
+ const label = flags.name || 'set'
383
+ const id = null != entry && null != entry.id ? ' (' + entry.id + ')' : ''
384
+ return label + '[' + index + ']' + id
385
+ }
386
+
387
+ function fail(
388
+ flags: Flags,
389
+ index: number,
390
+ entry: Json,
391
+ reason: string,
392
+ expected?: string,
393
+ actual?: string,
394
+ ): OmniError {
395
+ let msg = 'omni: ' + entryref(flags, index, entry) + ': ' + reason
396
+ if (undefined !== expected) {
397
+ msg += '\n expected: ' + expected
398
+ }
399
+ if (undefined !== actual) {
400
+ msg += '\n actual: ' + actual
401
+ }
402
+ msg += '\n entry: ' + stringify(entrysummary(entry))
403
+ return new OmniError(msg, entry)
404
+ }
405
+
406
+ // The spec-defined part of an entry (drop runner bookkeeping).
407
+ function entrysummary(entry: Json): Json {
408
+ if (!ismap(entry)) {
409
+ return entry
410
+ }
411
+ const out: Record<string, Json> = {}
412
+ for (const key of Object.keys(entry)) {
413
+ if ('res' !== key && 'thrown' !== key && 'ctx' !== key) {
414
+ out[key] = entry[key]
415
+ }
416
+ }
417
+ return out
418
+ }
419
+
420
+ function checkresult(flags: Flags, index: number, entry: Json, args: Json[], res: Json) {
421
+ let matched = false
422
+
423
+ if (null != entry.err) {
424
+ throw fail(
425
+ flags,
426
+ index,
427
+ entry,
428
+ 'expected error did not occur',
429
+ stringify(entry.err),
430
+ stringify(res),
431
+ )
432
+ }
433
+
434
+ if (null != entry.match) {
435
+ match(flags, index, entry, entry.match, {
436
+ in: entry.in,
437
+ args,
438
+ out: entry.res,
439
+ ctx: entry.ctx,
440
+ })
441
+ matched = true
442
+ }
443
+
444
+ const out = entry.out
445
+
446
+ if (out === res) {
447
+ return
448
+ }
449
+
450
+ // NOTE: a match with no explicit out is a complete check on its own.
451
+ if (matched && (NULLMARK === out || null == out)) {
452
+ return
453
+ }
454
+
455
+ if (!deepequal(res, out)) {
456
+ throw fail(flags, index, entry, 'result mismatch', stringify(out), stringify(res))
457
+ }
458
+ }
459
+
460
+ function handleerror(flags: Flags, index: number, entry: Json, err: any) {
461
+ entry.thrown = err
462
+
463
+ const entryerr = entry.err
464
+
465
+ if (null != entryerr) {
466
+ if (true === entryerr || matchval(entryerr, errmessage(err))) {
467
+ if (null != entry.match) {
468
+ match(flags, index, entry, entry.match, {
469
+ in: entry.in,
470
+ out: entry.res,
471
+ ctx: entry.ctx,
472
+ err: errify(err),
473
+ })
474
+ }
475
+ return
476
+ }
477
+
478
+ throw fail(flags, index, entry, 'error mismatch', stringify(entryerr), errmessage(err))
479
+ }
480
+
481
+ throw fail(flags, index, entry, 'unexpected error', undefined, errmessage(err))
482
+ }
483
+
484
+ // Check that every leaf of `check` is present, and matches, in `base`.
485
+ function match(flags: Flags, index: number, entry: Json, check: Json, base: Json) {
486
+ // Read the base DIRECTLY. The clone bought nothing - the walk below only
487
+ // reads, via getpath - and it blows the stack on a cyclic base. A port
488
+ // that drives entries with live objects rather than pure JSON produces
489
+ // those routinely: voxgig/sdkgen's corpus matches against a live client
490
+ // context whose root context reaches the client again.
491
+ const cbase = base
492
+
493
+ const at = (path: Json[]) => (0 === path.length ? '<root>' : pathify(path))
494
+
495
+ walk(clone(check), (_key, val, _parent, path) => {
496
+ // An empty container in the check is a structural placeholder: it has
497
+ // no leaves to check, so it matches whatever is at that path. This is
498
+ // deliberate - voxgig/struct's corpus relies on it (struct-compat), and
499
+ // an empty sub-pattern matching anything is the usual partial-match
500
+ // convention. The leaf checks below are where the strictness lives.
501
+ if (!isnode(val)) {
502
+ const baseval = getpath(cbase, path)
503
+
504
+ // The sentinels are tested BEFORE the identity check below. Otherwise
505
+ // a subject returning the literal string "__UNDEF__" satisfies an
506
+ // assertion that the key is absent - two mutually exclusive states
507
+ // passing one check. A sentinel that accepts its own literal is not a
508
+ // sentinel. (NULLMARK still accepts NULLMARK: under the default null
509
+ // flag a real null has already been normalised to it, so the two are
510
+ // genuinely indistinguishable here - that one needs a raw-value
511
+ // escape, not an ordering change.)
512
+
513
+ // Explicitly absent: satisfied only by a genuinely missing key, never
514
+ // by a present null (the distinction the sentinels exist to keep).
515
+ if (UNDEFMARK === val) {
516
+ if (undefined === baseval) {
517
+ return val
518
+ }
519
+ throw fail(flags, index, entry, 'expected absent at ' + at(path), 'absent', stringify(baseval))
520
+ }
521
+
522
+ // Explicitly null: satisfied only by a present null.
523
+ if (NULLMARK === val) {
524
+ if (null === baseval || NULLMARK === baseval) {
525
+ return val
526
+ }
527
+ throw fail(flags, index, entry, 'expected null at ' + at(path), 'null', stringify(baseval))
528
+ }
529
+
530
+ // Explicitly present: any present value, including null.
531
+ if (EXISTSMARK === val) {
532
+ if (undefined !== baseval) {
533
+ return val
534
+ }
535
+ throw fail(flags, index, entry, 'expected present at ' + at(path), 'present', 'absent')
536
+ }
537
+
538
+ // Identical values match. This sits below the sentinel branches on
539
+ // purpose - see the note above.
540
+ if (baseval === val) {
541
+ return val
542
+ }
543
+
544
+ // A concrete expectation never matches a missing key - a match leaf
545
+ // against an absent value must fail, not substring-match "undefined".
546
+ if (undefined === baseval) {
547
+ throw fail(flags, index, entry, 'match failed at ' + at(path), stringify(val), 'absent')
548
+ }
549
+
550
+ if (!matchval(val, baseval)) {
551
+ throw fail(flags, index, entry, 'match failed at ' + at(path), stringify(val), stringify(baseval))
552
+ }
553
+ }
554
+
555
+ return val
556
+ })
557
+ }
558
+
559
+
560
+ // Match one leaf. Strings are matched as /regex/ or as a case-insensitive
561
+ // substring, which is what makes error-message expectations portable.
562
+ function matchval(check: Json, base: Json): boolean {
563
+ if (check === base) {
564
+ return true
565
+ }
566
+
567
+ let want = check
568
+ if (UNDEFMARK === want || NULLMARK === want) {
569
+ want = null
570
+ }
571
+
572
+ if (null == want) {
573
+ return null == base || NULLMARK === base
574
+ }
575
+
576
+ if ('string' === typeof want) {
577
+ // An empty want is not a wildcard: the empty string is a substring of
578
+ // everything, so `match:{out:""}` (or `err:""`) would accept any value.
579
+ if ('' === want) {
580
+ return '' === base
581
+ }
582
+
583
+ const basestr = stringify(base)
584
+
585
+ const rem = want.match(/^\/(.+)\/$/)
586
+ if (rem) {
587
+ return new RegExp(rem[1]).test(basestr)
588
+ }
589
+
590
+ return basestr.toLowerCase().includes(want.toLowerCase())
591
+ }
592
+
593
+ if ('function' === typeof want) {
594
+ return true
595
+ }
596
+
597
+ return deepequal(want, base)
598
+ }
599
+
600
+ // Convert NULLMARK sentinels back into real nulls. Use as a walk callback
601
+ // when a spec value must reach the subject as an actual null.
602
+ function nullmodifier(val: Json, key: any, parent: Json) {
603
+ if (NULLMARK === val) {
604
+ parent[key] = null
605
+ } else if ('string' === typeof val) {
606
+ parent[key] = val.split(NULLMARK).join('null')
607
+ }
608
+ }
609
+
610
+ // Make a runner for a spec file (or spec object) and a provider.
611
+ async function makeRunner(specref: string | Json, provider?: Provider): Promise<Runner> {
612
+ const alltests = loadspec(specref)
613
+ const specversion = resolveversion(alltests)
614
+ const useprovider: Provider = provider || {}
615
+
616
+ return async function runner(name?: string, store?: Json): Promise<RunPack> {
617
+ const spec = resolvespec(name, alltests)
618
+ const clients = await resolveclients(useprovider, spec, store || {})
619
+ const defsubject = resolvesubject(name, useprovider)
620
+
621
+ const runsetflags: RunSetFlags = async (
622
+ testspec: Json,
623
+ flags: Flags,
624
+ testsubject?: Subject,
625
+ ) => {
626
+ const useflags = resolveflags(flags)
627
+ useflags.name = useflags.name || name || 'set'
628
+
629
+ const subject = testsubject || defsubject
630
+ if (null == subject) {
631
+ throw new OmniError('omni: no test subject for: ' + useflags.name)
632
+ }
633
+
634
+ const testspecmap = fixjson(testspec, useflags)
635
+
636
+ if (!ismap(testspecmap) || !islist(testspecmap.set)) {
637
+ throw new OmniError('omni: test spec has no set: ' + useflags.name)
638
+ }
639
+
640
+ const testset: Json[] = testspecmap.set
641
+
642
+ if (1 <= specversion) {
643
+ checkset(useflags, testspec, testset)
644
+ }
645
+
646
+ for (let index = 0; index < testset.length; index++) {
647
+ let entry = testset[index]
648
+
649
+ try {
650
+ entry = resolveentry(entry, useflags)
651
+
652
+ const testpack = resolvetestpack(name, entry, subject, useprovider, clients)
653
+ const args = resolveargs(entry, testpack, useprovider)
654
+
655
+ let res = await testpack.subject(...args)
656
+ res = fixjson(res, useflags)
657
+ entry.res = res
658
+
659
+ checkresult(useflags, index, entry, args, res)
660
+ } catch (err: any) {
661
+ if (err instanceof OmniError) {
662
+ throw err
663
+ }
664
+ handleerror(useflags, index, entry, err)
665
+ }
666
+ }
667
+ }
668
+
669
+ const runset: RunSet = async (testspec: Json, testsubject?: Subject) =>
670
+ runsetflags(testspec, {}, testsubject)
671
+
672
+ return {
673
+ spec,
674
+ runset,
675
+ runsetflags,
676
+ subject: defsubject,
677
+ client: useprovider,
678
+ }
679
+ }
680
+ }
681
+
682
+ export {
683
+ EXISTSMARK,
684
+ NULLMARK,
685
+ UNDEFMARK,
686
+ errify,
687
+ fixjson,
688
+ loadspec,
689
+ makeRunner,
690
+ match,
691
+ matchval,
692
+ nullmodifier,
693
+ resolvespec,
694
+ }