@voxgig/sdkgen 4.5.3 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/voxgig-sdkgen +1 -1
- package/dist/action/check.js +15 -0
- package/dist/action/check.js.map +1 -1
- package/dist/cmp/AgentGuide.js +1 -1
- package/dist/cmp/AgentGuide.js.map +1 -1
- package/dist/cmp/AgentGuideContent.d.ts +1 -1
- package/dist/cmp/AgentGuideContent.js +8 -2
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +17 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/cmp/FeatureDocs.d.ts +1 -1
- package/dist/cmp/FeatureDocs.js +6 -1
- package/dist/cmp/FeatureDocs.js.map +1 -1
- package/dist/cmp/ReadmeExplanation.js +3 -1
- package/dist/cmp/ReadmeExplanation.js.map +1 -1
- package/dist/cmp/ReadmeFeatures.js +1 -1
- package/dist/cmp/ReadmeFeatures.js.map +1 -1
- package/dist/cmp/ReadmeRefFeatures.js +1 -1
- package/dist/cmp/ReadmeRefFeatures.js.map +1 -1
- package/dist/helpers/applicability.d.ts +6 -0
- package/dist/helpers/applicability.js +103 -0
- package/dist/helpers/applicability.js.map +1 -0
- package/dist/helpers/collectDeps.js +4 -2
- package/dist/helpers/collectDeps.js.map +1 -1
- package/dist/helpers/serverVars.d.ts +2 -1
- package/dist/helpers/serverVars.js +17 -0
- package/dist/helpers/serverVars.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -2
- package/dist/sdkgen.js +9 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.js +7 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +39 -0
- package/package.json +1 -1
- package/project/.sdk/model/feature/feature-index.aon +1 -0
- package/project/.sdk/model/feature/secrets.aon +118 -0
- package/project/.sdk/model/target/ts.aon +8 -1
- package/project/.sdk/src/cmp/c/Config_c.ts +5 -1
- package/project/.sdk/src/cmp/cpp/Config_cpp.ts +5 -1
- package/project/.sdk/src/cmp/csharp/Config_csharp.ts +5 -1
- package/project/.sdk/src/cmp/dart/Config_dart.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Config_elixir.ts +5 -1
- package/project/.sdk/src/cmp/elixir/Main_elixir.ts +5 -1
- package/project/.sdk/src/cmp/go/Config_go.ts +5 -1
- package/project/.sdk/src/cmp/go/Main_go.ts +6 -2
- package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +16 -3
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +28 -4
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +43 -10
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +5 -5
- package/project/.sdk/src/cmp/java/Config_java.ts +22 -1
- package/project/.sdk/src/cmp/java/Main_java.ts +5 -1
- package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +17 -3
- package/project/.sdk/src/cmp/java/TestDirect_java.ts +23 -4
- package/project/.sdk/src/cmp/java/TestEntity_java.ts +29 -4
- package/project/.sdk/src/cmp/js/Config_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Main_js.ts +5 -1
- package/project/.sdk/src/cmp/js/Package_js.ts +4 -1
- package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +5 -1
- package/project/.sdk/src/cmp/lean/Config_lean.ts +5 -1
- package/project/.sdk/src/cmp/lua/Config_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Main_lua.ts +5 -1
- package/project/.sdk/src/cmp/lua/Package_lua.ts +4 -1
- package/project/.sdk/src/cmp/ocaml/Config_ocaml.ts +5 -1
- package/project/.sdk/src/cmp/perl/Main_perl.ts +5 -1
- package/project/.sdk/src/cmp/php/Config_php.ts +5 -1
- package/project/.sdk/src/cmp/php/Main_php.ts +5 -1
- package/project/.sdk/src/cmp/py/Config_py.ts +5 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +5 -1
- package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +16 -3
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +24 -5
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +23 -3
- package/project/.sdk/src/cmp/rb/Config_rb.ts +5 -1
- package/project/.sdk/src/cmp/rb/Main_rb.ts +5 -1
- package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -1
- package/project/.sdk/src/cmp/rust/Main_rust.ts +5 -1
- package/project/.sdk/src/cmp/scala/Config_scala.ts +5 -1
- package/project/.sdk/src/cmp/scala/Main_scala.ts +5 -1
- package/project/.sdk/src/cmp/swift/Config_swift.ts +5 -1
- package/project/.sdk/src/cmp/ts/Config_ts.ts +5 -1
- package/project/.sdk/src/cmp/ts/Main_ts.ts +57 -1
- package/project/.sdk/src/cmp/ts/Package_ts.ts +4 -1
- package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +15 -3
- package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +18 -5
- package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +19 -5
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +67 -7
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +27 -4
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +9 -2
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +7 -0
- package/project/.sdk/src/cmp/zig/Config_zig.ts +5 -1
- package/project/.sdk/tm/c/utility/make_options.c +22 -0
- package/project/.sdk/tm/clojure/src/sdk/core.clj +18 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +23 -0
- package/project/.sdk/tm/csharp/test/CustomUtilityTest.cs +15 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +23 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +22 -0
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +19 -0
- package/project/.sdk/tm/go/core/context.go +1 -1
- package/project/.sdk/tm/go/feature/cost_feature.go +1 -1
- package/project/.sdk/tm/go/feature/paging_feature.go +5 -5
- package/project/.sdk/tm/go/feature/test_feature.go +12 -5
- package/project/.sdk/tm/go/test/custom_utility_test.go +15 -0
- package/project/.sdk/tm/go/test/feature_corpus_test.go +7 -0
- package/project/.sdk/tm/go/test/runner_test.go +46 -0
- package/project/.sdk/tm/go/test/struct_runner_test.go +7 -7
- package/project/.sdk/tm/go/test/struct_utility_test.go +63 -27
- package/project/.sdk/tm/go/utility/fetcher.go +2 -2
- package/project/.sdk/tm/go/utility/make_options.go +24 -5
- package/project/.sdk/tm/go/utility/make_point.go +1 -1
- package/project/.sdk/tm/go/utility/make_spec.go +17 -1
- package/project/.sdk/tm/go/utility/prepare_auth.go +1 -1
- package/project/.sdk/tm/go/utility/struct/voxgigstruct.go +370 -307
- package/project/.sdk/tm/go/utility/transform_request.go +29 -1
- package/project/.sdk/tm/go/utility/transform_response.go +20 -1
- package/project/.sdk/tm/java/test/CustomUtilityTest.java +10 -0
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +7 -0
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +6 -0
- package/project/.sdk/tm/java/test/RunnerSupport.java +43 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +84 -1
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +24 -0
- package/project/.sdk/tm/js/src/utility/StructUtility.js +2076 -1837
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +9 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +21 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +12 -1
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +11 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +18 -0
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +10 -1
- package/project/.sdk/tm/php/utility/MakeOptions.php +24 -0
- package/project/.sdk/tm/py/pkg/utility/make_options.py +20 -0
- package/project/.sdk/tm/py/test/runner.py +32 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +10 -1
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +9 -1
- package/project/.sdk/tm/rb/utility/make_options.rb +17 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +25 -0
- package/project/.sdk/tm/scala/utility/Make.scala +17 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +21 -0
- package/project/.sdk/tm/ts/src/feature/secrets/SecretsFeature.ts +419 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +1221 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +381 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +151 -0
- package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +42 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +18 -0
- package/project/.sdk/tm/ts/src/utility/StructUtility.ts +1321 -710
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +9 -0
- package/project/.sdk/tm/ts/test/feature/secrets/Secrets.test.ts +764 -0
- package/project/.sdk/tm/ts/test/omni.test.ts +84 -0
- package/project/.sdk/tm/ts/test/omni.ts +86 -0
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +6 -2
- package/project/.sdk/tm/ts/test/utility/StructUtility.test.ts +2 -2
- package/project/.sdk/tm/ts/test/utility.ts +122 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +692 -0
- package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +238 -0
- package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +188 -0
- package/project/.sdk/tm/ts/test/vendor/omni/index.ts +38 -0
- package/project/.sdk/tm/zig/core/utility.zig +23 -0
- package/project/sdkgen-package.json +2 -1
- package/src/action/check.ts +18 -0
- package/src/cmp/AgentGuide.ts +1 -1
- package/src/cmp/AgentGuideContent.ts +9 -2
- package/src/cmp/Feature.ts +19 -0
- package/src/cmp/FeatureDocs.ts +7 -1
- package/src/cmp/ReadmeExplanation.ts +4 -1
- package/src/cmp/ReadmeFeatures.ts +1 -1
- package/src/cmp/ReadmeRefFeatures.ts +1 -1
- package/src/helpers/applicability.ts +123 -0
- package/src/helpers/collectDeps.ts +5 -1
- package/src/helpers/serverVars.ts +19 -0
- package/src/sdkgen.ts +9 -1
- package/src/utility.ts +10 -1
- package/project/.sdk/tm/ts/test/runner.ts +0 -425
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
cmp,
|
|
31
31
|
each,
|
|
32
32
|
isAuthActive,
|
|
33
|
+
serverVarEnv,
|
|
34
|
+
serverVariables,
|
|
33
35
|
isHttpBasicAuth,
|
|
34
36
|
entityDataIdField, envName, envToken
|
|
35
37
|
} from '@voxgig/sdkgen'
|
|
@@ -67,7 +69,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
67
69
|
const authActive = isAuthActive(model)
|
|
68
70
|
const authBasic = authActive && isHttpBasicAuth(model)
|
|
69
71
|
const apikeyEnvEntry = authActive
|
|
70
|
-
? `\n '${PROJENVNAME}_APIKEY': '
|
|
72
|
+
? `\n '${PROJENVNAME}_APIKEY': '',${authBasic ? `\n '${PROJENVNAME}_SECRET': 'NONE',` : ''}`
|
|
71
73
|
: ''
|
|
72
74
|
const apikeyLiveField = authActive
|
|
73
75
|
? `
|
|
@@ -75,6 +77,19 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
75
77
|
secret: env.${PROJENVNAME}_SECRET,` : ''}`
|
|
76
78
|
: ''
|
|
77
79
|
|
|
80
|
+
// A templated server URL (OpenAPI server variables) makes a LIVE client
|
|
81
|
+
// impossible to construct without values: makeOptions raises rather than
|
|
82
|
+
// request a URL with a literal `{account_id}` in it. So the live suite
|
|
83
|
+
// takes them from the environment the same way it takes the apikey.
|
|
84
|
+
const svars = serverVariables(model)
|
|
85
|
+
const serverEnvEntry = svars
|
|
86
|
+
.map((v: any) => `\n '${serverVarEnv(PROJENVNAME, v.name)}': ${JSON.stringify(v.dflt)},`).join('')
|
|
87
|
+
const serverLiveField = 0 === svars.length ? '' : `
|
|
88
|
+
server: {${svars
|
|
89
|
+
.map((v: any) => `
|
|
90
|
+
${v.name}: env.${serverVarEnv(PROJENVNAME, v.name)},`).join('')}
|
|
91
|
+
},`
|
|
92
|
+
|
|
78
93
|
// TODO: should be a utility function
|
|
79
94
|
const ff = projectPath('src/cmp/ts/fragment/')
|
|
80
95
|
|
|
@@ -156,7 +171,7 @@ function basicSetup(extra?: any) {
|
|
|
156
171
|
const env = envOverride({
|
|
157
172
|
'${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID': idmap,
|
|
158
173
|
'${PROJENVNAME}_TEST_LIVE': 'FALSE',
|
|
159
|
-
'${PROJENVNAME}_TEST_EXPLAIN': 'FALSE',${apikeyEnvEntry}
|
|
174
|
+
'${PROJENVNAME}_TEST_EXPLAIN': 'FALSE',${apikeyEnvEntry}${serverEnvEntry}
|
|
160
175
|
})
|
|
161
176
|
|
|
162
177
|
idmap = env['${PROJENVNAME}_TEST_${ENTENVNAME}_ENTID']
|
|
@@ -165,9 +180,17 @@ function basicSetup(extra?: any) {
|
|
|
165
180
|
|
|
166
181
|
if (live) {
|
|
167
182
|
client = new ${model.Name}SDK(merge([
|
|
168
|
-
|
|
183
|
+
// FIRST, so the generated fields below win: sdk-test-control.json's
|
|
184
|
+
// test.client.options adds to the live client, it does not redirect it.
|
|
185
|
+
liveClientOptions(),
|
|
186
|
+
{${apikeyLiveField}${serverLiveField}
|
|
169
187
|
},
|
|
170
|
-
extra
|
|
188
|
+
// 'extra || {}', not a bare 'extra': struct.merge returns UNDEFINED when the
|
|
189
|
+
// last entry is undefined, and basicSetup is normally called with no
|
|
190
|
+
// argument at all - so a bare 'extra' silently discarded the apikey
|
|
191
|
+
// and server values above and handed the SDK undefined. Harmless
|
|
192
|
+
// while there was nothing in that object; not harmless now.
|
|
193
|
+
extra || {}
|
|
171
194
|
]))
|
|
172
195
|
}
|
|
173
196
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
const envlocal = __dirname + '/../../../.env.local'
|
|
3
|
-
require('dotenv').config({ quiet: true, path: [envlocal] })
|
|
4
2
|
|
|
5
3
|
import { test, describe, afterEach } from 'node:test'
|
|
6
4
|
import assert from 'node:assert'
|
|
@@ -10,12 +8,21 @@ import { ProjectNameSDK } from '../../..'
|
|
|
10
8
|
|
|
11
9
|
import {
|
|
12
10
|
envOverride,
|
|
11
|
+
liveClientOptions,
|
|
13
12
|
liveDelay,
|
|
13
|
+
loadEnvLocal,
|
|
14
14
|
maybeSkipControl,
|
|
15
15
|
skipIfMissingIds,
|
|
16
16
|
} from '../../utility'
|
|
17
17
|
|
|
18
18
|
|
|
19
|
+
// AFTER the imports on purpose: TypeScript hoists `import` above any
|
|
20
|
+
// statement in the emitted CommonJS, so a loader placed above them would
|
|
21
|
+
// run only after every imported module had already been evaluated - and
|
|
22
|
+
// anything reading process.env at module scope would miss these values.
|
|
23
|
+
loadEnvLocal(__dirname + '/../../../.env.local')
|
|
24
|
+
|
|
25
|
+
|
|
19
26
|
describe('EntityNameDirect', async () => {
|
|
20
27
|
|
|
21
28
|
// Per-test live pacing. Delay is read from sdk-test-control.json's
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
const envlocal = __dirname + '/../../../.env.local'
|
|
3
|
-
require('dotenv').config({ quiet: true, path: [envlocal] })
|
|
4
2
|
|
|
5
3
|
import Path from 'node:path'
|
|
6
4
|
import * as Fs from 'node:fs'
|
|
@@ -13,7 +11,9 @@ import { ProjectNameSDK, BaseFeature, stdutil } from '../../..'
|
|
|
13
11
|
|
|
14
12
|
import {
|
|
15
13
|
envOverride,
|
|
14
|
+
liveClientOptions,
|
|
16
15
|
liveDelay,
|
|
16
|
+
loadEnvLocal,
|
|
17
17
|
makeCtrl,
|
|
18
18
|
makeMatch,
|
|
19
19
|
makeReqdata,
|
|
@@ -23,6 +23,13 @@ import {
|
|
|
23
23
|
} from '../../utility'
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
// AFTER the imports on purpose: TypeScript hoists `import` above any
|
|
27
|
+
// statement in the emitted CommonJS, so a loader placed above them would
|
|
28
|
+
// run only after every imported module had already been evaluated - and
|
|
29
|
+
// anything reading process.env at module scope would miss these values.
|
|
30
|
+
loadEnvLocal(__dirname + '/../../../.env.local')
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
describe('EntityNameEntity', async () => {
|
|
27
34
|
|
|
28
35
|
// Per-test live pacing. Delay is read from sdk-test-control.json's
|
|
@@ -9,6 +9,7 @@ import { Utility } from './utility/Utility'
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
import { BaseFeature } from './feature/base/BaseFeature'
|
|
12
|
+
// #SecretsImport
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
const stdutil = new Utility()
|
|
@@ -20,6 +21,7 @@ class ProjectNameSDK {
|
|
|
20
21
|
_utility = new Utility()
|
|
21
22
|
_features: Feature[]
|
|
22
23
|
_rootctx: Context
|
|
24
|
+
// #SecretsField
|
|
23
25
|
|
|
24
26
|
constructor(options?: any) {
|
|
25
27
|
|
|
@@ -92,6 +94,8 @@ class ProjectNameSDK {
|
|
|
92
94
|
return this._utility.struct.clone(this._utility)
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
// #SecretsAccessor
|
|
98
|
+
|
|
95
99
|
|
|
96
100
|
async prepare(fetchargs?: any) {
|
|
97
101
|
const utility = this._utility
|
|
@@ -138,6 +142,8 @@ class ProjectNameSDK {
|
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
// #SecretsResolve
|
|
146
|
+
|
|
141
147
|
// Apply SDK auth (apikey, auth prefix, etc.)
|
|
142
148
|
const authResult = prepareAuth(ctx)
|
|
143
149
|
if (authResult instanceof Error) {
|
|
@@ -334,6 +340,7 @@ const SDK = ProjectNameSDK
|
|
|
334
340
|
export {
|
|
335
341
|
stdutil,
|
|
336
342
|
config,
|
|
343
|
+
// #SecretsExport
|
|
337
344
|
|
|
338
345
|
BaseFeature,
|
|
339
346
|
ProjectNameEntityBase,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
each,
|
|
9
9
|
isAuthActive,
|
|
10
10
|
isConfigData,
|
|
11
|
+
targetFeatures,
|
|
11
12
|
} from '@voxgig/sdkgen'
|
|
12
13
|
|
|
13
14
|
|
|
@@ -32,7 +33,10 @@ const Config = cmp(async function Config(props: any) {
|
|
|
32
33
|
|
|
33
34
|
const model: Model = ctx$.model
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
// Gated by the applicability tags, so this target never imports or
|
|
37
|
+
// registers a feature it has no source for. One rule, one place:
|
|
38
|
+
// helpers/applicability.
|
|
39
|
+
const feature = targetFeatures(model, target)
|
|
36
40
|
|
|
37
41
|
// The canonical config OBJECT and its JSON, from the shared helper. Both
|
|
38
42
|
// representations render from the same `def`, so they cannot describe
|
|
@@ -22,6 +22,23 @@ voxgig_value* make_options_util(Context* ctx) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/* `auth: null` is the documented way to disable auth outright, and
|
|
26
|
+
* prepare_auth honours it before it ever reads the apikey. validate would
|
|
27
|
+
* erase it: this port follows the Group A rule, so a stored null reads as
|
|
28
|
+
* "no value" and the optspec's `auth` default fires instead - transmitting
|
|
29
|
+
* the credential the caller withheld. Put the null back afterwards.
|
|
30
|
+
*
|
|
31
|
+
* Unlike js, no delete-before-validate is needed here: Group A means
|
|
32
|
+
* validate DEFAULTS rather than rejecting, so the key can travel through.
|
|
33
|
+
*
|
|
34
|
+
* Read the map DIRECTLY rather than through getp, which applies that same
|
|
35
|
+
* rule and so cannot tell an absent auth from a suppressed one. */
|
|
36
|
+
bool auth_suppressed = false;
|
|
37
|
+
if (voxgig_is_map(options)) {
|
|
38
|
+
voxgig_value* authval = voxgig_map_get(voxgig_as_map(options), "auth");
|
|
39
|
+
auth_suppressed = (NULL != authval && voxgig_is_null(authval));
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
voxgig_value* opts = voxgig_clone(options);
|
|
26
43
|
|
|
27
44
|
// Feature add-order. options.feature may be an ordered list of
|
|
@@ -93,6 +110,11 @@ voxgig_value* make_options_util(Context* ctx) {
|
|
|
93
110
|
opts = validated;
|
|
94
111
|
}
|
|
95
112
|
|
|
113
|
+
/* Restore the suppression the optspec default would otherwise erase. */
|
|
114
|
+
if (auth_suppressed) {
|
|
115
|
+
setp(opts, "auth", voxgig_new_null());
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
// Restore system.fetch.
|
|
97
119
|
if (!v_is_noval(sys_fetch)) {
|
|
98
120
|
voxgig_value* sys = getp(opts, "system");
|
|
@@ -988,7 +988,22 @@
|
|
|
988
988
|
(if (and member (not= member :custom) (contains? members member))
|
|
989
989
|
(oset! util member v)
|
|
990
990
|
(.put ^java.util.Map (oget util :custom) k v))))))
|
|
991
|
-
(let [
|
|
991
|
+
(let [;; `auth` nil is the documented way to disable auth outright, and
|
|
992
|
+
;; u-prepare-auth honours it before it ever reads the apikey. It
|
|
993
|
+
;; cannot survive validate: a stored null reads as "no value", so the
|
|
994
|
+
;; optspec's `auth` default fires and the suppression silently
|
|
995
|
+
;; becomes "use default auth" - transmitting the credential the
|
|
996
|
+
;; caller withheld. Withhold the key for validate, then put the nil
|
|
997
|
+
;; back. Same fix as ts/js/go makeOptions.
|
|
998
|
+
;;
|
|
999
|
+
;; `.containsKey` rather than a nil check on the value: the latter
|
|
1000
|
+
;; cannot tell an ABSENT auth from a suppressed one, and only the
|
|
1001
|
+
;; second is a suppression.
|
|
1002
|
+
auth-suppressed (and (vs/ismap options)
|
|
1003
|
+
(.containsKey ^java.util.Map options "auth")
|
|
1004
|
+
(nil? (.get ^java.util.Map options "auth")))
|
|
1005
|
+
opts0 (let [c (vs/clone options)] (if (vs/ismap c) c (vs/jm)))
|
|
1006
|
+
_ (when auth-suppressed (.remove ^java.util.Map opts0 "auth"))
|
|
992
1007
|
;; Feature add-order. options.feature may be given as an ordered
|
|
993
1008
|
;; ARRAY of {name active ...opts} entries (array position = add
|
|
994
1009
|
;; order) or a {name {opts}} map. Normalize an array to a map (so
|
|
@@ -1037,6 +1052,8 @@
|
|
|
1037
1052
|
merged (vs/merge (vs/jt (vs/jm) cfgopts opts0))
|
|
1038
1053
|
validated (vs/validate merged optspec)
|
|
1039
1054
|
opts (if (vs/ismap validated) validated (vs/jm))
|
|
1055
|
+
;; Restore the suppression the optspec default would otherwise erase.
|
|
1056
|
+
_ (when auth-suppressed (.put ^java.util.Map opts "auth" nil))
|
|
1040
1057
|
;; Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
1041
1058
|
;; Every placeholder must resolve to a non-empty value: from
|
|
1042
1059
|
;; options["server"] (user), else the config default. A placeholder
|
|
@@ -1107,8 +1107,26 @@ inline Value makeOptions(CtxPtr ctx) {
|
|
|
1107
1107
|
}
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
1110
|
+
// `auth: null` is the documented way to disable auth outright, and
|
|
1111
|
+
// prepareAuth honours it before it ever reads the apikey. It cannot survive
|
|
1112
|
+
// validate: this port follows the Group A rule, so a stored null reads as
|
|
1113
|
+
// "no value" and the optspec's `auth` default fires instead - transmitting
|
|
1114
|
+
// the credential the caller withheld. Withhold the key for validate, then
|
|
1115
|
+
// put the null back. Same fix as ts/js/go makeOptions.
|
|
1116
|
+
//
|
|
1117
|
+
// mapget rather than getp: getp applies Group A and collapses a stored null
|
|
1118
|
+
// to undef, so it cannot tell an ABSENT auth from a suppressed one. And the
|
|
1119
|
+
// test is is_null, NOT is_nullish - a present-but-UNDEF auth is a missing
|
|
1120
|
+
// value, which the ts/js reference lets the optspec default fill in. Only an
|
|
1121
|
+
// explicit null is a suppression.
|
|
1122
|
+
bool authSuppressed = mapget(options, "auth").is_null();
|
|
1123
|
+
|
|
1110
1124
|
Value opts = Struct::clone(options);
|
|
1111
1125
|
|
|
1126
|
+
if (authSuppressed) {
|
|
1127
|
+
map_remove(opts, "auth");
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1112
1130
|
// Feature add-order. options.feature may be an ordered list of
|
|
1113
1131
|
// { name, active, ...opts } entries (the list position IS the order in which
|
|
1114
1132
|
// features are added), or a { name: {opts} } map. Normalize a list to a map
|
|
@@ -1159,6 +1177,11 @@ inline Value makeOptions(CtxPtr ctx) {
|
|
|
1159
1177
|
Value validated = Struct::validate(merged, optspec, vopts);
|
|
1160
1178
|
opts = validated;
|
|
1161
1179
|
|
|
1180
|
+
// Restore the suppression the optspec default would otherwise erase.
|
|
1181
|
+
if (authSuppressed) {
|
|
1182
|
+
map_put(opts, "auth", Value(nullptr));
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1162
1185
|
if (!is_nullish(sysFetch)) {
|
|
1163
1186
|
Value sys = Helpers::toMapAny(getp(opts, "system"));
|
|
1164
1187
|
if (sys.is_map()) {
|
|
@@ -85,8 +85,13 @@ public class CustomUtilityTest
|
|
|
85
85
|
// transport: 'base' - it REPLACES the transport by design - so a client
|
|
86
86
|
// in test mode would shadow the scripted fetcher and this would assert
|
|
87
87
|
// nothing.
|
|
88
|
+
// ["test"] is the OPTION, not the `test` FEATURE: it says "this
|
|
89
|
+
// client is not live", so a REQUIRED OpenAPI server variable resolves
|
|
90
|
+
// to a deterministic test-<name> instead of failing construction. It
|
|
91
|
+
// installs no transport, so the override under test still stands.
|
|
88
92
|
var client = new ProjectNameSDK(new Dictionary<string, object?>
|
|
89
93
|
{
|
|
94
|
+
["test"] = new Dictionary<string, object?> { ["active"] = true },
|
|
90
95
|
["utility"] = new Dictionary<string, object?> { ["fetcher"] = scripted },
|
|
91
96
|
});
|
|
92
97
|
|
|
@@ -122,8 +127,13 @@ public class CustomUtilityTest
|
|
|
122
127
|
};
|
|
123
128
|
};
|
|
124
129
|
|
|
130
|
+
// ["test"] is the OPTION, not the `test` FEATURE: it says "this
|
|
131
|
+
// client is not live", so a REQUIRED OpenAPI server variable resolves
|
|
132
|
+
// to a deterministic test-<name> instead of failing construction. It
|
|
133
|
+
// installs no transport, so the override under test still stands.
|
|
125
134
|
var client = new ProjectNameSDK(new Dictionary<string, object?>
|
|
126
135
|
{
|
|
136
|
+
["test"] = new Dictionary<string, object?> { ["active"] = true },
|
|
127
137
|
["utility"] = new Dictionary<string, object?> { ["fetcher"] = scripted },
|
|
128
138
|
});
|
|
129
139
|
|
|
@@ -141,8 +151,13 @@ public class CustomUtilityTest
|
|
|
141
151
|
[Fact]
|
|
142
152
|
public void AnUnknownKeyIsStillAttached()
|
|
143
153
|
{
|
|
154
|
+
// ["test"] is the OPTION, not the `test` FEATURE: it says "this
|
|
155
|
+
// client is not live", so a REQUIRED OpenAPI server variable resolves
|
|
156
|
+
// to a deterministic test-<name> instead of failing construction. It
|
|
157
|
+
// installs no transport, so the override under test still stands.
|
|
144
158
|
var client = new ProjectNameSDK(new Dictionary<string, object?>
|
|
145
159
|
{
|
|
160
|
+
["test"] = new Dictionary<string, object?> { ["active"] = true },
|
|
146
161
|
["utility"] = new Dictionary<string, object?>
|
|
147
162
|
{
|
|
148
163
|
["notAUtilityMember"] = Util("EXTRA"),
|
|
@@ -114,9 +114,26 @@ public static partial class SdkUtility
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
// `auth: null` is the documented way to disable auth outright, and
|
|
118
|
+
// PrepareAuth honours it before it ever reads the apikey. It cannot
|
|
119
|
+
// survive validate: depending on the struct port a stored null is
|
|
120
|
+
// either REPLACED by the optspec default - transmitting the credential
|
|
121
|
+
// the caller withheld - or REJECTED outright. Withhold the key for
|
|
122
|
+
// validate, then put the null back. Same fix as ts/js/go makeOptions.
|
|
123
|
+
//
|
|
124
|
+
// Suppliedness cannot be recovered after validate, hence here, and it
|
|
125
|
+
// must tell an ABSENT auth from a present null: TryGetValue rather
|
|
126
|
+
// than an indexer null check, which cannot distinguish them.
|
|
127
|
+
var authSuppressed = options.TryGetValue("auth", out var authval) && null == authval;
|
|
128
|
+
|
|
117
129
|
var opts = StructUtils.Clone(options) as Dictionary<string, object?>
|
|
118
130
|
?? new Dictionary<string, object?>();
|
|
119
131
|
|
|
132
|
+
if (authSuppressed)
|
|
133
|
+
{
|
|
134
|
+
opts.Remove("auth");
|
|
135
|
+
}
|
|
136
|
+
|
|
120
137
|
// Feature add-order. options.feature may be given as an ordered LIST of
|
|
121
138
|
// { name, active, ...opts } entries (the list position IS the order in
|
|
122
139
|
// which features are added), or as a { name: {opts} } map. Normalize a
|
|
@@ -230,6 +247,12 @@ public static partial class SdkUtility
|
|
|
230
247
|
var validated = StructUtils.Validate(merged, optspec);
|
|
231
248
|
opts = validated as Dictionary<string, object?> ?? new Dictionary<string, object?>();
|
|
232
249
|
|
|
250
|
+
// Restore the suppression the optspec default would otherwise erase.
|
|
251
|
+
if (authSuppressed)
|
|
252
|
+
{
|
|
253
|
+
opts["auth"] = null;
|
|
254
|
+
}
|
|
255
|
+
|
|
233
256
|
// Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
234
257
|
// Every placeholder must resolve to a non-empty value: from
|
|
235
258
|
// options.server (user), else the Config default. A placeholder that
|
|
@@ -106,12 +106,34 @@ dynamic makeOptions(dynamic ctx) {
|
|
|
106
106
|
mergeOptions['feature'] = fmap;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
// `auth: null` is the documented way to disable auth outright, and
|
|
110
|
+
// prepareAuth honours it before it ever reads the apikey. It cannot survive
|
|
111
|
+
// validate: depending on the struct port a stored null is either REPLACED
|
|
112
|
+
// by the optspec default — transmitting the credential the caller withheld
|
|
113
|
+
// — or REJECTED outright. Withhold the key for validate, then put the null
|
|
114
|
+
// back. Same fix as ts/js/go makeOptions.
|
|
115
|
+
//
|
|
116
|
+
// Suppliedness cannot be recovered after validate, hence here, and it must
|
|
117
|
+
// tell an ABSENT auth from a present null: containsKey rather than a null
|
|
118
|
+
// check on the value, which cannot distinguish them.
|
|
119
|
+
final authSuppressed =
|
|
120
|
+
options is Map && options.containsKey('auth') && null == options['auth'];
|
|
121
|
+
|
|
109
122
|
// User option maps are cloned first — their (possibly narrow) literal
|
|
110
123
|
// types must not constrain the merged structures.
|
|
111
124
|
dynamic opts = vs.merge([{}, cfgopts, vs.clone(mergeOptions)]);
|
|
112
125
|
|
|
126
|
+
if (authSuppressed && opts is Map) {
|
|
127
|
+
opts.remove('auth');
|
|
128
|
+
}
|
|
129
|
+
|
|
113
130
|
opts = vs.validate(opts, optspec);
|
|
114
131
|
|
|
132
|
+
// Restore the suppression the optspec default would otherwise erase.
|
|
133
|
+
if (authSuppressed && opts is Map) {
|
|
134
|
+
opts['auth'] = null;
|
|
135
|
+
}
|
|
136
|
+
|
|
115
137
|
// Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
116
138
|
// Every placeholder must resolve to a non-empty value: from options.server
|
|
117
139
|
// (user), else the Config default. A placeholder that resolves to '' is a
|
|
@@ -243,8 +243,24 @@ defmodule ProjectName.Utility do
|
|
|
243
243
|
end
|
|
244
244
|
end
|
|
245
245
|
|
|
246
|
+
# `auth` nil is the documented way to disable auth outright, and
|
|
247
|
+
# prepare_auth honours it before it ever reads the apikey. It cannot
|
|
248
|
+
# survive validate: a stored null reads as "no value", so the optspec's
|
|
249
|
+
# `auth` default fires and the suppression silently becomes "use default
|
|
250
|
+
# auth" - transmitting the credential the caller withheld. Withhold the
|
|
251
|
+
# key for validate, then put the nil back. Same fix as ts/js/go
|
|
252
|
+
# makeOptions.
|
|
253
|
+
#
|
|
254
|
+
# keysof rather than S.haskey: haskey is `getprop(...) != nil`, which
|
|
255
|
+
# collapses a stored null and so cannot tell an ABSENT auth from a
|
|
256
|
+
# suppressed one. keysof lists the key either way.
|
|
257
|
+
auth_suppressed =
|
|
258
|
+
S.ismap(options) and "auth" in S.keysof(options) and
|
|
259
|
+
S.getprop(options, "auth") == nil
|
|
260
|
+
|
|
246
261
|
opts0 = S.clone(options)
|
|
247
262
|
opts0 = if S.ismap(opts0), do: opts0, else: S.jm([])
|
|
263
|
+
if auth_suppressed, do: S.delprop(opts0, "auth")
|
|
248
264
|
|
|
249
265
|
# Feature add-order. options.feature may be given as an ordered LIST of
|
|
250
266
|
# {name, active, ...opts} entries (list position = add order) or a
|
|
@@ -329,6 +345,9 @@ defmodule ProjectName.Utility do
|
|
|
329
345
|
validated = S.validate(merged, optspec)
|
|
330
346
|
opts = if S.ismap(validated), do: validated, else: S.jm([])
|
|
331
347
|
|
|
348
|
+
# Restore the suppression the optspec default would otherwise erase.
|
|
349
|
+
if auth_suppressed, do: S.setprop(opts, "auth", nil)
|
|
350
|
+
|
|
332
351
|
# Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
|
|
333
352
|
# Every placeholder must resolve to a non-empty value: from options.server
|
|
334
353
|
# (user), else the Config default. A placeholder that resolves to "" is a
|
|
@@ -245,7 +245,7 @@ func (ctx *Context) resolveOp(opname string) *Operation {
|
|
|
245
245
|
return NewOperation(map[string]any{})
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
opcfg := vs.GetPath([]any{"entity", entname, "op", opname}
|
|
248
|
+
opcfg := vs.GetPath(ctx.Config, []any{"entity", entname, "op", opname})
|
|
249
249
|
|
|
250
250
|
input := "match"
|
|
251
251
|
if opname == "update" || opname == "create" {
|
|
@@ -367,7 +367,7 @@ func (f *CostFeature) body(ctx *core.Context) (float64, bool) {
|
|
|
367
367
|
return 0, false
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
switch n := vs.GetPath(
|
|
370
|
+
switch n := vs.GetPath(ctx.Result.Body, path).(type) {
|
|
371
371
|
case int:
|
|
372
372
|
return float64(n) * f.perUnit(), true
|
|
373
373
|
case int64:
|
|
@@ -109,7 +109,7 @@ func (f *PagingFeature) graphqlPreRequest(ctx *core.Context, paging map[string]a
|
|
|
109
109
|
// Only bind variables the operation actually declares, or the server
|
|
110
110
|
// rejects the document.
|
|
111
111
|
declared := map[string]bool{}
|
|
112
|
-
if varlist, ok := vs.GetPath([]any{"graphql", "vars"}
|
|
112
|
+
if varlist, ok := vs.GetPath(ctx.Point, []any{"graphql", "vars"}).([]any); ok {
|
|
113
113
|
for _, v := range varlist {
|
|
114
114
|
if name, ok := vs.GetProp(v, "name").(string); ok {
|
|
115
115
|
declared[name] = true
|
|
@@ -160,7 +160,7 @@ func (f *PagingFeature) PreResult(ctx *core.Context) {
|
|
|
160
160
|
|
|
161
161
|
// Relay connections carry the cursor in pageInfo, at the path the model
|
|
162
162
|
// recorded for this op.
|
|
163
|
-
page := vs.GetPath([]any{"graphql", "page"}
|
|
163
|
+
page := vs.GetPath(ctx.Point, []any{"graphql", "page"})
|
|
164
164
|
if pm, ok := page.(map[string]any); ok {
|
|
165
165
|
if _, isMap := body.(map[string]any); isMap {
|
|
166
166
|
// `connpath` locates the connection object inside the response
|
|
@@ -168,18 +168,18 @@ func (f *PagingFeature) PreResult(ctx *core.Context) {
|
|
|
168
168
|
// to it.
|
|
169
169
|
conn := body
|
|
170
170
|
if cp, ok := pm["connpath"].(string); ok && cp != "" {
|
|
171
|
-
if sub := vs.GetPath(
|
|
171
|
+
if sub := vs.GetPath(body, cp); sub != nil {
|
|
172
172
|
conn = sub
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
if cp, ok := pm["cursor"].(string); ok && cp != "" {
|
|
177
|
-
if cursor := vs.GetPath(
|
|
177
|
+
if cursor := vs.GetPath(conn, cp); cursor != nil {
|
|
178
178
|
paging["cursor"] = cursor
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
if mp, ok := pm["more"].(string); ok && mp != "" {
|
|
182
|
-
if more, ok := vs.GetPath(
|
|
182
|
+
if more, ok := vs.GetPath(conn, mp).(bool); ok {
|
|
183
183
|
paging["hasMore"] = more
|
|
184
184
|
explicitMore = true
|
|
185
185
|
}
|
|
@@ -323,7 +323,7 @@ func (f *TestFeature) buildArgs(ctx *core.Context, op *core.Operation, args map[
|
|
|
323
323
|
// back to, so the seed-data query is built from the endpoint the request
|
|
324
324
|
// will actually be sent to: a terminal `{id}` marks a record route, and
|
|
325
325
|
// failing that the shallower path wins.
|
|
326
|
-
points := vs.GetPath([]any{"entity", ctx.Entity.GetName(), "op", opname, "points"}
|
|
326
|
+
points := vs.GetPath(ctx.Config, []any{"entity", ctx.Entity.GetName(), "op", opname, "points"})
|
|
327
327
|
point := vs.GetElem(points, 0)
|
|
328
328
|
if plist, ok := points.([]any); ok {
|
|
329
329
|
partsLen := func(p any) int {
|
|
@@ -355,12 +355,19 @@ func (f *TestFeature) buildArgs(ctx *core.Context, op *core.Operation, args map[
|
|
|
355
355
|
|
|
356
356
|
// Path AND query: a path-only read misses a query-addressed record
|
|
357
357
|
// (e.g. GET /result?trace_id=), which has no path param at all.
|
|
358
|
-
paramsPath := vs.GetPath([]any{"args", "params"}
|
|
358
|
+
paramsPath := vs.GetPath(point, []any{"args", "params"})
|
|
359
359
|
reqdParams := vs.Select(paramsPath, map[string]any{"reqd": true})
|
|
360
|
-
|
|
361
|
-
|
|
360
|
+
// The error return Transform gained in struct go 0.1.3 is discarded
|
|
361
|
+
// DELIBERATELY here, unlike in transform_request/response: these two
|
|
362
|
+
// transform a spec the SDK itself built from the point definition, not
|
|
363
|
+
// user data, so an error means a generator bug rather than something a
|
|
364
|
+
// caller can act on — and buildArgs has no error seam to route it
|
|
365
|
+
// through. An empty result is the conservative outcome: no required
|
|
366
|
+
// fields, which is what an absent spec already produces.
|
|
367
|
+
reqdFromParams, _ := vs.Transform(reqdParams, []any{"`$EACH`", "", "`$KEY.name`"})
|
|
368
|
+
queryPath := vs.GetPath(point, []any{"args", "query"})
|
|
362
369
|
reqdQuery := vs.Select(queryPath, map[string]any{"reqd": true})
|
|
363
|
-
reqdFromQuery := vs.Transform(reqdQuery, []any{"`$EACH`", "", "`$KEY.name`"})
|
|
370
|
+
reqdFromQuery, _ := vs.Transform(reqdQuery, []any{"`$EACH`", "", "`$KEY.name`"})
|
|
364
371
|
|
|
365
372
|
qand := []any{}
|
|
366
373
|
q := map[string]any{"`$AND`": &qand}
|
|
@@ -109,7 +109,12 @@ func TestCustomUtility(t *testing.T) {
|
|
|
109
109
|
// that was broken: a plain function literal in a map[string]any has the
|
|
110
110
|
// unnamed signature, so an override that only accepted the named type
|
|
111
111
|
// shelved it in Custom. Converting here would have hidden that.
|
|
112
|
+
// "test" is the OPTION, not the `test` FEATURE: it says "this client
|
|
113
|
+
// is not live", so a REQUIRED OpenAPI server variable resolves to a
|
|
114
|
+
// deterministic test-<name> instead of panicking at construction. It
|
|
115
|
+
// installs no transport, so the override under test still stands.
|
|
112
116
|
client := sdk.NewProjectNameSDK(map[string]any{
|
|
117
|
+
"test": map[string]any{"active": true},
|
|
113
118
|
"utility": map[string]any{"fetcher": scripted},
|
|
114
119
|
})
|
|
115
120
|
|
|
@@ -138,7 +143,12 @@ func TestCustomUtility(t *testing.T) {
|
|
|
138
143
|
// the subtest above, and broken by any fix that swaps one for the other.
|
|
139
144
|
t.Run("a converted fetcher is accepted too", func(t *testing.T) {
|
|
140
145
|
reached := 0
|
|
146
|
+
// "test" is the OPTION, not the `test` FEATURE: it says "this client
|
|
147
|
+
// is not live", so a REQUIRED OpenAPI server variable resolves to a
|
|
148
|
+
// deterministic test-<name> instead of panicking at construction. It
|
|
149
|
+
// installs no transport, so the override under test still stands.
|
|
141
150
|
client := sdk.NewProjectNameSDK(map[string]any{
|
|
151
|
+
"test": map[string]any{"active": true},
|
|
142
152
|
"utility": map[string]any{
|
|
143
153
|
"fetcher": sdk.FetcherFunc(func(
|
|
144
154
|
ctx *sdk.Context, fullurl string, fetchdef map[string]any,
|
|
@@ -171,7 +181,12 @@ func TestCustomUtility(t *testing.T) {
|
|
|
171
181
|
// An unknown key must still be attached rather than dropped, so the two
|
|
172
182
|
// halves cannot be satisfied by a switch that also swallows extras.
|
|
173
183
|
t.Run("an unknown key is still attached", func(t *testing.T) {
|
|
184
|
+
// "test" is the OPTION, not the `test` FEATURE: it says "this client
|
|
185
|
+
// is not live", so a REQUIRED OpenAPI server variable resolves to a
|
|
186
|
+
// deterministic test-<name> instead of panicking at construction. It
|
|
187
|
+
// installs no transport, so the override under test still stands.
|
|
174
188
|
client := sdk.NewProjectNameSDK(map[string]any{
|
|
189
|
+
"test": map[string]any{"active": true},
|
|
175
190
|
"utility": map[string]any{
|
|
176
191
|
"notAUtilityMember": func() string { return "EXTRA" },
|
|
177
192
|
},
|
|
@@ -114,7 +114,14 @@ func fcFetcher(res []any) sdk.FetcherFunc {
|
|
|
114
114
|
// REPLACES the transport, so a client in test mode would shadow the script.
|
|
115
115
|
func fcClient(kase map[string]any) *sdk.ProjectNameSDK {
|
|
116
116
|
res, _ := kase["res"].([]any)
|
|
117
|
+
// "test" here is the OPTION, not the `test` FEATURE. It says "this
|
|
118
|
+
// client is not live", which is what makes a REQUIRED OpenAPI server
|
|
119
|
+
// variable resolve to a deterministic test-<name> rather than panic at
|
|
120
|
+
// construction (see makeOptions). It installs no transport, so the
|
|
121
|
+
// scripted fetcher still stands — the FEATURE is transport: 'base' and
|
|
122
|
+
// would shadow it, which is why this cannot just turn the feature on.
|
|
117
123
|
opts := map[string]any{
|
|
124
|
+
"test": map[string]any{"active": true},
|
|
118
125
|
"utility": map[string]any{"fetcher": fcFetcher(res)},
|
|
119
126
|
}
|
|
120
127
|
if f, ok := kase["feature"]; ok {
|
|
@@ -156,6 +156,52 @@ func isControlSkipped(kind, name, mode string) (bool, string) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// liveDelayMs returns the configured per-test live delay in ms; default 500.
|
|
159
|
+
// liveClientOptions returns the extra SDK options every LIVE client is
|
|
160
|
+
// constructed with, from sdk-test-control.json `test.client.options`.
|
|
161
|
+
//
|
|
162
|
+
// The generated live client knows two things: the base URL (from the spec)
|
|
163
|
+
// and the credential (from the environment). Everything else about how a
|
|
164
|
+
// particular API wants to be talked to - which features to switch on, and
|
|
165
|
+
// with what settings - is a property of THAT API, known to the project and
|
|
166
|
+
// to nothing in the toolchain.
|
|
167
|
+
//
|
|
168
|
+
// Merged UNDER the generated fields, so the suite's own base/apikey/server
|
|
169
|
+
// values win: this ADDS to the live client, it does not redirect it.
|
|
170
|
+
//
|
|
171
|
+
// That contract is enforced HERE rather than left to each merge site: the
|
|
172
|
+
// generated map only names a field when the model calls for one, so a
|
|
173
|
+
// "base" in this block would face no competing value and would silently
|
|
174
|
+
// redirect the whole suite - credential included - to another host.
|
|
175
|
+
var liveReserved = map[string]bool{
|
|
176
|
+
"base": true, "prefix": true, "suffix": true,
|
|
177
|
+
"server": true, "apikey": true, "secret": true,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
func liveClientOptions() map[string]any {
|
|
181
|
+
ctrl := loadTestControl()
|
|
182
|
+
test, _ := ctrl["test"].(map[string]any)
|
|
183
|
+
if test == nil {
|
|
184
|
+
return map[string]any{}
|
|
185
|
+
}
|
|
186
|
+
client, _ := test["client"].(map[string]any)
|
|
187
|
+
if client == nil {
|
|
188
|
+
return map[string]any{}
|
|
189
|
+
}
|
|
190
|
+
opts, _ := client["options"].(map[string]any)
|
|
191
|
+
if opts == nil {
|
|
192
|
+
return map[string]any{}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
out := map[string]any{}
|
|
196
|
+
for k, v := range opts {
|
|
197
|
+
if !liveReserved[k] {
|
|
198
|
+
out[k] = v
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return out
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
159
205
|
func liveDelayMs() int {
|
|
160
206
|
ctrl := loadTestControl()
|
|
161
207
|
test, _ := ctrl["test"].(map[string]any)
|