@voxgig/create-sdkgen 0.23.0 → 0.25.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/dist/create-sdkgen.js +0 -20
- package/dist/create-sdkgen.js.map +1 -1
- package/dist/project/standard/CreateRoot.js +1 -103
- package/dist/project/standard/CreateRoot.js.map +1 -1
- package/package.json +3 -3
- package/project/standard/.sdk/admin/README.md +38 -3
- package/project/standard/.sdk/admin/check-drift.sh +97 -0
- package/project/standard/.sdk/model/config.aon +0 -5
- package/project/standard/.sdk/model/sdk.fragment.aon +1 -6
- package/project/standard/.sdk/package.json +6 -5
- package/project/standard/.sdk/src/BuildSDK.ts +5 -22
- package/project/standard/.sdk/src/Root.ts +0 -22
- package/project/standard/.sdk/src/Top.ts +0 -8
- package/project/standard/.sdk/test/feature/cost.aon +0 -43
- package/project/standard/.sdk/test/primary/clean.aon +0 -7
- package/project/standard/.sdk/test/primary/featureAdd.aon +0 -11
- package/project/standard/.sdk/test/primary/featureHook.aon +0 -10
- package/project/standard/.sdk/test/primary/featureInit.aon +0 -10
- package/project/standard/.sdk/test/primary/fetcher.aon +0 -10
- package/project/standard/.sdk/test/primary/makeFetchDef.aon +0 -10
- package/project/standard/.sdk/test/primary/makePoint.aon +0 -22
- package/project/standard/.sdk/test/primary/makeResult.aon +0 -14
- package/project/standard/.sdk/test/primary/prepareAuth.aon +0 -5
- package/project/standard/.sdk/test/primary/preparePath.aon +0 -7
- package/project/standard/.sdk/test/struct/nullsem.aon +2 -95
- package/project/standard/.sdk/test/struct/select.aon +79 -96
- package/src/create-sdkgen.ts +0 -35
- package/src/project/standard/CreateRoot.ts +1 -103
|
@@ -45,7 +45,6 @@ const Root = cmp(function Root(props: any) {
|
|
|
45
45
|
ctx$.util = ctx$.util || {}
|
|
46
46
|
ctx$.util.makeFlow = makeFlow
|
|
47
47
|
|
|
48
|
-
// TODO: move to @voxgig/util as duplicated
|
|
49
48
|
model.const = { name: model.name }
|
|
50
49
|
names(model.const, model.name)
|
|
51
50
|
model.const.year = new Date().getFullYear()
|
|
@@ -65,16 +64,13 @@ const Root = cmp(function Root(props: any) {
|
|
|
65
64
|
})
|
|
66
65
|
|
|
67
66
|
names(model, model.name)
|
|
68
|
-
// console.log('MODEL name', model.name, model.Name)
|
|
69
67
|
|
|
70
68
|
// Standard Replacements
|
|
71
69
|
ctx$.stdrep = {}
|
|
72
70
|
names(ctx$.stdrep, model.Name, 'Project' + 'Name')
|
|
73
|
-
// console.log('STDREP', stdrep)
|
|
74
71
|
|
|
75
72
|
Project({}, () => {
|
|
76
73
|
|
|
77
|
-
// TODO: jostraca should accept no props
|
|
78
74
|
Top({})
|
|
79
75
|
|
|
80
76
|
BuildSDK({})
|
|
@@ -84,22 +80,6 @@ const Root = cmp(function Root(props: any) {
|
|
|
84
80
|
|
|
85
81
|
Folder({ name: target.name }, () => {
|
|
86
82
|
|
|
87
|
-
// Per-generation-phase activation. A target's aontu model carries
|
|
88
|
-
// a `phase` map mirroring the feature pattern:
|
|
89
|
-
//
|
|
90
|
-
// phase: {
|
|
91
|
-
// entity: { active: false }
|
|
92
|
-
// feature: { active: false }
|
|
93
|
-
// readme: { active: false }
|
|
94
|
-
// agentguide: { active: false }
|
|
95
|
-
// test: { active: false }
|
|
96
|
-
// }
|
|
97
|
-
//
|
|
98
|
-
// Defaults are inclusive — when a phase entry is absent (or
|
|
99
|
-
// active is not explicitly false), the phase runs. Existing
|
|
100
|
-
// standard targets don't declare `phase` and keep current
|
|
101
|
-
// behaviour. A CLI-style target switches all five off and
|
|
102
|
-
// only emits Main.
|
|
103
83
|
const phase = target.phase || {}
|
|
104
84
|
const phaseActive = (name: string): boolean =>
|
|
105
85
|
false !== (phase[name] && phase[name].active)
|
|
@@ -145,7 +125,6 @@ function makeFlow(def: any, data: any, stepMakers: Record<string, any>) {
|
|
|
145
125
|
|
|
146
126
|
const steps: any = {}
|
|
147
127
|
each(stepMakers, (n: any) => {
|
|
148
|
-
// TODO: support after?
|
|
149
128
|
if ('function' === typeof n.val$) {
|
|
150
129
|
steps[n.key$] = (id: any, pdef: any) => makeFlowStep(id(), pdef, n.val$)
|
|
151
130
|
}
|
|
@@ -210,7 +189,6 @@ function makeFlow(def: any, data: any, stepMakers: Record<string, any>) {
|
|
|
210
189
|
names(step, step.entity, 'entity')
|
|
211
190
|
})
|
|
212
191
|
|
|
213
|
-
// console.log('STEPS', stepMakers, steps, def)
|
|
214
192
|
|
|
215
193
|
const spec = transform(def, {
|
|
216
194
|
p: ['`$EACH`', 'step', {
|
|
@@ -20,9 +20,6 @@ const Top = cmp(function Top(props: any) {
|
|
|
20
20
|
// any target Folder (same placement rule as ReadmeTop / Deploy).
|
|
21
21
|
AgentGuideTop({})
|
|
22
22
|
|
|
23
|
-
// Root governance files for a publishable, unofficial generated SDK:
|
|
24
|
-
// an MIT LICENSE (held by Voxgig), a SECURITY.md disclosure policy, and a
|
|
25
|
-
// seed CHANGELOG. Emitted at repo root, before the per-target folders.
|
|
26
23
|
License({})
|
|
27
24
|
Security({})
|
|
28
25
|
Changelog({})
|
|
@@ -32,11 +29,6 @@ const Top = cmp(function Top(props: any) {
|
|
|
32
29
|
// `make deploy-dry` rehearsal.
|
|
33
30
|
Deploy({})
|
|
34
31
|
|
|
35
|
-
// Publishing an npm target from CI with no token: a dispatch workflow per
|
|
36
|
-
// npm target using GitHub OIDC trusted publishing, and the one-time
|
|
37
|
-
// set-up a maintainer must do, written to `.sdk/PUBLISHING.md` — beside
|
|
38
|
-
// the generator rather than into the target's own docs, which describe
|
|
39
|
-
// the SDK to the people who install it.
|
|
40
32
|
PublishWorkflow({})
|
|
41
33
|
})
|
|
42
34
|
|
|
@@ -1,38 +1,3 @@
|
|
|
1
|
-
# Cost feature behaviour, as language-neutral cases.
|
|
2
|
-
#
|
|
3
|
-
# Every case is DATA: features are activated by name through the generated
|
|
4
|
-
# config, the options are plain JSON, the transport is scripted by `res`, and
|
|
5
|
-
# the assertion is a subset of the client's cost record. Nothing here needs a
|
|
6
|
-
# function, which is what makes it corpus-expressible where featureAdd /
|
|
7
|
-
# featureHook / featureInit are not (see their PENDING notes).
|
|
8
|
-
#
|
|
9
|
-
# CASE SHAPE
|
|
10
|
-
# name label for the runner
|
|
11
|
-
# feature activation list, IN ORDER. The array form is the one that fixes
|
|
12
|
-
# nesting: each transport feature wraps whatever is already
|
|
13
|
-
# installed, so position IS order. Entries are flat -
|
|
14
|
-
# { name, active, ...options } - as makeOptions expects.
|
|
15
|
-
# res scripted transport responses, consumed in order; the last is
|
|
16
|
-
# reused once exhausted. `throw: true` makes that attempt reject
|
|
17
|
-
# at the transport rather than return a response.
|
|
18
|
-
# op operations to run, in order. `err` marks an op that must FAIL:
|
|
19
|
-
# `true` for any failure, a string to pin the error code.
|
|
20
|
-
# out expected SUBSET of client._cost - runners match partially, so a
|
|
21
|
-
# case asserts only what it is about
|
|
22
|
-
#
|
|
23
|
-
# OPERATION TOKENS
|
|
24
|
-
# The corpus is shared by every generated SDK, and each has its own
|
|
25
|
-
# entities and operations, so no case may name one. `#OP1` and `#OP2` stand
|
|
26
|
-
# for two DISTINCT operations this SDK can actually perform; the runner
|
|
27
|
-
# discovers them from the generated client and substitutes them throughout
|
|
28
|
-
# the case, keys included. They resolve to the '<entity>.<op>' key the cost
|
|
29
|
-
# record attributes spend under, which is why a rate table can be keyed on
|
|
30
|
-
# one. A SDK that offers fewer operations than a case needs skips that case
|
|
31
|
-
# rather than failing it.
|
|
32
|
-
#
|
|
33
|
-
# Determinism comes from data alone: netsim is counter-driven, and retry runs
|
|
34
|
-
# with `minDelay: 1`, which leaves no room for jitter (it is drawn from
|
|
35
|
-
# [0, minDelay)) and costs no real time.
|
|
36
1
|
|
|
37
2
|
basic: set: [
|
|
38
3
|
|
|
@@ -171,10 +136,6 @@ basic: set: [
|
|
|
171
136
|
out: { total: { calls: 5, amount: 5 } }
|
|
172
137
|
}
|
|
173
138
|
|
|
174
|
-
# The case above supplies `budget: 0`; this one omits it. They are separate
|
|
175
|
-
# cases on purpose. An implementation that reads the option into a
|
|
176
|
-
# nullable, or treats "supplied" as "set a ceiling", passes one and fails
|
|
177
|
-
# the other, and a single case cannot tell which path it took.
|
|
178
139
|
{
|
|
179
140
|
name: 'an omitted budget means no ceiling either'
|
|
180
141
|
feature: [ { name: 'cost', active: true, unit: 1, onBudget: 'deny' } ]
|
|
@@ -183,8 +144,4 @@ basic: set: [
|
|
|
183
144
|
}
|
|
184
145
|
]
|
|
185
146
|
|
|
186
|
-
# NOT EXPRESSIBLE HERE, and deliberately left to the per-language suites.
|
|
187
|
-
# `partial`, not `basic: pending`: the section RUNS, so the deferral marker
|
|
188
|
-
# that means "this whole section is a hole" would be a lie here (and the
|
|
189
|
-
# corpus guard promotes any deferred section that has gained cases).
|
|
190
147
|
partial: 'the sink callback takes a function, which a JSON fixture cannot carry; covered per-language.'
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# clean(ctx, val): redact sensitive values before they reach a log sink.
|
|
3
|
-
#
|
|
4
|
-
# The masking body is currently commented out in every port, so clean is a
|
|
5
|
-
# PASSTHROUGH — and that is exactly what needs pinning across languages: a
|
|
6
|
-
# port that "helpfully" implements masking early, or that clones/reshapes the
|
|
7
|
-
# value on the way through, diverges from the reference without any test
|
|
8
|
-
# noticing (this fixture was an empty `set: []`).
|
|
9
2
|
|
|
10
3
|
basic: set: [
|
|
11
4
|
{ mark:10, args: [ {options:{}}, {a:'A'} ], out: {a:'A'} }
|
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# PENDING — this section is deliberately empty.
|
|
3
|
-
#
|
|
4
|
-
# featureAdd(ctx, f) mutates client._features, so a case would have to assert
|
|
5
|
-
# on the shared client every other section also uses, and undo itself
|
|
6
|
-
# afterwards. The runner constructs one client per spec (DEF.client) rather
|
|
7
|
-
# than one per entry, so there is no isolation to build that on yet.
|
|
8
|
-
#
|
|
9
|
-
# An empty `set` runs ZERO cases. Runners must not treat that as a pass by
|
|
10
|
-
# accident: `basic: pending` below is the MACHINE-READABLE form of this note,
|
|
11
|
-
# so the guard travels into test.json with the fixture instead of living only
|
|
12
|
-
# in a comment the consumer never sees.
|
|
13
2
|
|
|
14
3
|
basic: pending: 'featureAdd mutates client._features; the runner builds one client per spec, not per entry, so a case cannot isolate or undo itself.'
|
|
15
4
|
basic: set: []
|
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# PENDING — this section is deliberately empty.
|
|
3
|
-
#
|
|
4
|
-
# featureHook(ctx, name) calls f[name] on each registered feature. Asserting
|
|
5
|
-
# it needs features whose hooks are FUNCTIONS; a fixture compiles to JSON and
|
|
6
|
-
# cannot carry one. Covered per-language by the feature suites.
|
|
7
|
-
#
|
|
8
|
-
# An empty `set` runs ZERO cases. Runners must not treat that as a pass by
|
|
9
|
-
# accident: `basic: pending` below is the MACHINE-READABLE form of this note,
|
|
10
|
-
# so the guard travels into test.json with the fixture instead of living only
|
|
11
|
-
# in a comment the consumer never sees.
|
|
12
2
|
|
|
13
3
|
basic: pending: 'featureHook calls f[name] on each registered feature; a fixture compiles to JSON and cannot carry a function.'
|
|
14
4
|
basic: set: []
|
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# PENDING — this section is deliberately empty.
|
|
3
|
-
#
|
|
4
|
-
# featureInit(ctx, f) calls f.init(ctx, fopts). `init` is a function, and a
|
|
5
|
-
# fixture compiles to JSON, so the subject of the test cannot be expressed
|
|
6
|
-
# here at all. Covered per-language by the feature suites.
|
|
7
|
-
#
|
|
8
|
-
# An empty `set` runs ZERO cases. Runners must not treat that as a pass by
|
|
9
|
-
# accident: `basic: pending` below is the MACHINE-READABLE form of this note,
|
|
10
|
-
# so the guard travels into test.json with the fixture instead of living only
|
|
11
|
-
# in a comment the consumer never sees.
|
|
12
2
|
|
|
13
3
|
basic: pending: 'featureInit calls f.init(ctx, fopts); init is a function, and a fixture compiles to JSON, so the subject cannot be expressed here.'
|
|
14
4
|
basic: set: []
|
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# PENDING — this section is deliberately empty.
|
|
3
|
-
#
|
|
4
|
-
# Needs a live or mocked transport and a client instance; the shared runner
|
|
5
|
-
# has no transport-injection hook. Covered per-language by the netsim and
|
|
6
|
-
# feature suites until this fixture can express a transport.
|
|
7
|
-
#
|
|
8
|
-
# An empty `set` runs ZERO cases. Runners must not treat that as a pass by
|
|
9
|
-
# accident: `basic: pending` below is the MACHINE-READABLE form of this note,
|
|
10
|
-
# so the guard travels into test.json with the fixture instead of living only
|
|
11
|
-
# in a comment the consumer never sees.
|
|
12
2
|
|
|
13
3
|
basic: pending: 'Needs a live or mocked transport and a client instance; the shared runner has no transport-injection hook.'
|
|
14
4
|
basic: set: []
|
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# PENDING — this section is deliberately empty.
|
|
3
|
-
#
|
|
4
|
-
# Depends on a fully built ctx (spec + point + options) that the shared
|
|
5
|
-
# runner cannot assemble from a literal map. Covered indirectly by
|
|
6
|
-
# makeSpec/makeUrl, and by the per-language pipeline suites.
|
|
7
|
-
#
|
|
8
|
-
# An empty `set` runs ZERO cases. Runners must not treat that as a pass by
|
|
9
|
-
# accident: `basic: pending` below is the MACHINE-READABLE form of this note,
|
|
10
|
-
# so the guard travels into test.json with the fixture instead of living only
|
|
11
|
-
# in a comment the consumer never sees.
|
|
12
2
|
|
|
13
3
|
basic: pending: 'Depends on a fully built ctx (spec + point + options) that the runner cannot assemble from a literal map.'
|
|
14
4
|
basic: set: []
|
|
@@ -1,22 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# makePoint chooses which endpoint definition ("point") an operation call
|
|
3
|
-
# should use, and records it on ctx.point.
|
|
4
|
-
#
|
|
5
|
-
# The whole selection is driven by DATA: `op` is rebuilt by Context from
|
|
6
|
-
# `opname` + `entity.name` + `config.entity.<name>.op.<opname>.points`, and
|
|
7
|
-
# `options.allow.op` gates the call. So every branch below is expressible as
|
|
8
|
-
# a literal ctx, contrary to the deferral note this fixture used to carry,
|
|
9
|
-
# which claimed it needed a real client.
|
|
10
|
-
#
|
|
11
|
-
# Errors are RETURNED, not thrown, and their JSON embeds a random ctx id, so
|
|
12
|
-
# the error cases assert `match: out: code` rather than a whole-value `out`.
|
|
13
|
-
# Go returns the error as a second result instead, and its runner normalises
|
|
14
|
-
# it to the same `{code}` shape.
|
|
15
|
-
#
|
|
16
|
-
# `allow.op` is a COMMA-SEPARATED STRING, not a list — that is what the client
|
|
17
|
-
# actually carries. Go reads it with strings.Contains and type-asserts to
|
|
18
|
-
# string, so a list here silently denies every operation; TS's .includes()
|
|
19
|
-
# accepts either and would have hidden the mismatch.
|
|
20
2
|
|
|
21
3
|
basic: set: [
|
|
22
4
|
|
|
@@ -59,8 +41,6 @@ basic: set: [
|
|
|
59
41
|
match: { out: { code: 'point_no_points' } }
|
|
60
42
|
}
|
|
61
43
|
|
|
62
|
-
# Two points: `select.exist` picks the one whose required keys are present
|
|
63
|
-
# in the match. `id` is supplied, so the by-id point wins.
|
|
64
44
|
{
|
|
65
45
|
ctx: {
|
|
66
46
|
opname: 'list'
|
|
@@ -75,8 +55,6 @@ basic: set: [
|
|
|
75
55
|
out: { method: 'GET', path: ['byid'], select: { exist: ['id'] } }
|
|
76
56
|
}
|
|
77
57
|
|
|
78
|
-
# Same two points, but `id` is absent — the exist check fails and selection
|
|
79
|
-
# falls through to the unconstrained point.
|
|
80
58
|
{
|
|
81
59
|
ctx: {
|
|
82
60
|
opname: 'list'
|
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# PENDING — this section is deliberately empty.
|
|
3
|
-
#
|
|
4
|
-
# makeResult needs a ctx carrying a constructed Spec AND Result, built via the
|
|
5
|
-
# SDK's own NewSpec/NewResult constructors, plus the absent-error path for
|
|
6
|
-
# each. The runner builds ctx from a literal map and has no constructor hook,
|
|
7
|
-
# so these cannot be expressed here yet — covered per-language today (e.g.
|
|
8
|
-
# go's makeResult-basic / -no-spec / -no-result), which is exactly the drift
|
|
9
|
-
# this corpus exists to remove. Promoting it needs a runner change, not just
|
|
10
|
-
# fixture data.
|
|
11
|
-
#
|
|
12
|
-
# An empty `set` runs ZERO cases. Runners must not treat that as a pass by
|
|
13
|
-
# accident: `basic: pending` below is the MACHINE-READABLE form of this note,
|
|
14
|
-
# so the guard travels into test.json with the fixture instead of living only
|
|
15
|
-
# in a comment the consumer never sees.
|
|
16
2
|
|
|
17
3
|
basic: pending: 'Needs a ctx carrying a Spec AND Result built via the SDK constructors; the runner builds ctx from a literal map and has no constructor hook.'
|
|
18
4
|
basic: set: []
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
|
|
2
2
|
DEF: {
|
|
3
|
-
# auth pinned explicitly (not left to a spec's own derived prefix/scheme):
|
|
4
|
-
# this corpus tests the raw single-token mechanics in isolation, so it
|
|
5
|
-
# must not silently inherit whatever a given SDK's real spec declares
|
|
6
|
-
# (e.g. genuine HTTP Basic Auth, a non-empty prefix, two credentials) -
|
|
7
|
-
# that has its own dedicated coverage elsewhere.
|
|
8
3
|
setup: a: { apikey: 'APIKEY01', auth: { prefix: '', basic: false } }
|
|
9
4
|
}
|
|
10
5
|
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
# preparePath: join ctx.point.parts with '/', dropping empty segments.
|
|
3
|
-
#
|
|
4
|
-
# Path templating is the highest-risk request-shaping step, and this fixture
|
|
5
|
-
# was an empty `set: []` — so every language "passed" it while running zero
|
|
6
|
-
# cases. The cases below were lifted from the per-language hand-written tests
|
|
7
|
-
# (go/py) that had drifted out of the shared corpus, plus the empty/blank
|
|
8
|
-
# segment edges that `join(..., true)` is specifically responsible for.
|
|
9
2
|
|
|
10
3
|
basic: set: [
|
|
11
4
|
# Multi-segment path, placeholder left intact (params are substituted later,
|
|
@@ -1,102 +1,21 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
# One question, asked of every reader: when a key is PRESENT and holds a JSON
|
|
5
|
-
# null, is that "no value" (the same as an absent key) or is it a value?
|
|
6
|
-
#
|
|
7
|
-
# Nothing in the corpus asked it before. `minor.getprop` and `minor.haskey`
|
|
8
|
-
# have ~50 cases each and not one stores a null: every entry is an absent key
|
|
9
|
-
# or a non-null value. So the ports diverged silently, and they HAVE:
|
|
10
|
-
#
|
|
11
|
-
# port getprop({x:null},'x','ALT') haskey({x:null},'x')
|
|
12
|
-
# ──── ─────────────────────────── ────────────────────
|
|
13
|
-
# go 'ALT' false
|
|
14
|
-
# py 'ALT' false
|
|
15
|
-
# perl 'ALT' false
|
|
16
|
-
# rb null true
|
|
17
|
-
# php null true
|
|
18
|
-
# js null true
|
|
19
|
-
#
|
|
20
|
-
# That is not academic. It is the direct cause of the `auth: null` bug family:
|
|
21
|
-
# `auth: null` is the documented way to suppress auth outright, and where a
|
|
22
|
-
# stored null reads as "no value" the optspec `auth` default fires, the
|
|
23
|
-
# suppression becomes "use the default auth", and the credential the caller
|
|
24
|
-
# withheld goes out on the wire.
|
|
25
|
-
#
|
|
26
|
-
# 0.3.2 answers "no value" — the top three rows. This section pins that.
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# THIS SECTION IS OPT-IN, AND DELIBERATELY SO.
|
|
30
|
-
#
|
|
31
|
-
# Half the tree still vendors a struct that answers the other way. Putting
|
|
32
|
-
# these cases in `minor` would red those targets today and gate the whole
|
|
33
|
-
# corpus on finishing the struct migration everywhere first. Instead a target
|
|
34
|
-
# wires this section up as it migrates:
|
|
35
|
-
#
|
|
36
|
-
# runsetflags(nullsemSpec["getprop"], {"null": False}, getprop_wrapper)
|
|
37
|
-
#
|
|
38
|
-
# so the section becomes that target's null-semantics gate, and a target that
|
|
39
|
-
# has not migrated simply does not reference it yet.
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
# `null: false` IS NOT OPTIONAL HERE.
|
|
43
|
-
#
|
|
44
|
-
# The runner's default fixJSON rewrites every JSON null - on BOTH the `in` and
|
|
45
|
-
# `out` sides - to the string '__NULL__' before the subject ever sees it. Run
|
|
46
|
-
# without the flag, every case below would pass the STRING '__NULL__' as the
|
|
47
|
-
# stored value, which is not a null and is not the question. The section would
|
|
48
|
-
# then pass on every port while asserting nothing about null at all - the
|
|
49
|
-
# exact "green while checking nothing" failure the corpus guards exist to
|
|
50
|
-
# prevent. `minor.getelem` and `minor.clone` already run with the flag for the
|
|
51
|
-
# same reason.
|
|
52
|
-
#
|
|
53
|
-
# With the flag, an entry with NO `out` expects a null result, and `out:` with
|
|
54
|
-
# a value expects that value.
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# VERIFIED AGAINST THE REFERENCE, NOT GUESSED.
|
|
58
|
-
#
|
|
59
|
-
# All 33 cases below were run against upstream @voxgig/struct 0.3.2
|
|
60
|
-
# (typescript/src/StructUtility.ts, the canonical port) and all 33 pass. They
|
|
61
|
-
# are what the reference DOES, not what it was assumed to do.
|
|
62
|
-
#
|
|
63
|
-
# That mattered on the first run: `getprop([null], 0, 'ALT')` was assumed to
|
|
64
|
-
# be a formality and turned out to be a live defect in the py port's vendored
|
|
65
|
-
# copy. Canonical getprop tests `isnode(val)` - map AND list - and then
|
|
66
|
-
# applies the null rule once to whatever it read. The py copy branches on
|
|
67
|
-
# ismap/islist and its list branch does `return val[key]`, an early return
|
|
68
|
-
# that skips the null check entirely, so it hands back the stored null where
|
|
69
|
-
# every other reader would substitute the alt. Its map path is correct, which
|
|
70
|
-
# is why nothing noticed.
|
|
71
|
-
#
|
|
72
|
-
# So py cannot opt in to this section until its vendored struct is resynced.
|
|
73
|
-
# That is the section working as intended: it says "not yet" in a test result
|
|
74
|
-
# instead of in a comment.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# Does the ALT fire for a stored null, as it does for an absent key?
|
|
2
|
+
|
|
3
|
+
|
|
78
4
|
getprop: {
|
|
79
5
|
set: [
|
|
80
|
-
# THE CASE. Absent and stored-null must answer alike.
|
|
81
6
|
{ in: { val: {x:null}, key: x, alt: 'ALT' }, out: 'ALT' }
|
|
82
7
|
{ in: { val: {}, key: x, alt: 'ALT' }, out: 'ALT' }
|
|
83
8
|
|
|
84
|
-
# No alt: a stored null reads back as null either way, so this pins that
|
|
85
|
-
# the rule does not turn a null into something else.
|
|
86
9
|
{ in: { val: {x:null}, key: x } }
|
|
87
10
|
|
|
88
11
|
# A stored null must not leak past its own key.
|
|
89
12
|
{ in: { val: {x:null, y:1}, key: y, alt: 'ALT' }, out: 1 }
|
|
90
13
|
{ in: { val: {x:1, y:null}, key: x, alt: 'ALT' }, out: 1 }
|
|
91
14
|
|
|
92
|
-
# false, 0 and '' are VALUES, not absence. A port that collapses "nullish"
|
|
93
|
-
# to "falsy" passes every case above and fails these.
|
|
94
15
|
{ in: { val: {x:false}, key: x, alt: 'ALT' }, out: false }
|
|
95
16
|
{ in: { val: {x:0}, key: x, alt: 'ALT' }, out: 0 }
|
|
96
17
|
{ in: { val: {x:''}, key: x, alt: 'ALT' }, out: '' }
|
|
97
18
|
|
|
98
|
-
# Lists too: index 0 holding a null is the list-shaped form of the same
|
|
99
|
-
# question, and a port can easily fix one and not the other.
|
|
100
19
|
{ in: { val: [null], key: 0, alt: 'ALT' }, out: 'ALT' }
|
|
101
20
|
{ in: { val: [null, 1], key: 1, alt: 'ALT' }, out: 1 }
|
|
102
21
|
]
|
|
@@ -111,8 +30,6 @@ getelem: {
|
|
|
111
30
|
{ in: { val: [10, null, 30], key: 1, alt: 'ALT' }, out: 'ALT' }
|
|
112
31
|
{ in: { val: [10, null, 30], key: 2, alt: 'ALT' }, out: 30 }
|
|
113
32
|
|
|
114
|
-
# Negative indices are getelem's whole reason for existing; a stored null
|
|
115
|
-
# at a negative index is the case a port is most likely to miss.
|
|
116
33
|
{ in: { val: [10, null], key: -1, alt: 'ALT' }, out: 'ALT' }
|
|
117
34
|
{ in: { val: [null, 20], key: -1, alt: 'ALT' }, out: 20 }
|
|
118
35
|
|
|
@@ -137,15 +54,12 @@ getpath: {
|
|
|
137
54
|
}
|
|
138
55
|
|
|
139
56
|
|
|
140
|
-
# Presence, asked directly. This is the reader the auth-null fix needs in
|
|
141
|
-
# every port, and the one whose answer splits the tree.
|
|
142
57
|
haskey: {
|
|
143
58
|
set: [
|
|
144
59
|
{ in: { src: {x:null}, key: x }, out: false }
|
|
145
60
|
{ in: { src: {}, key: x }, out: false }
|
|
146
61
|
{ in: { src: {x:1}, key: x }, out: true }
|
|
147
62
|
|
|
148
|
-
# Again: falsy is not absent.
|
|
149
63
|
{ in: { src: {x:false}, key: x }, out: true }
|
|
150
64
|
{ in: { src: {x:0}, key: x }, out: true }
|
|
151
65
|
{ in: { src: {x:''}, key: x }, out: true }
|
|
@@ -156,13 +70,6 @@ haskey: {
|
|
|
156
70
|
}
|
|
157
71
|
|
|
158
72
|
|
|
159
|
-
# The other direction, and the reason `auth: null` is expressible at all:
|
|
160
|
-
# presence is STRUCTURAL. keysof lists a null-valued key, because the key IS
|
|
161
|
-
# there - it is only the READERS above that treat its value as no value.
|
|
162
|
-
#
|
|
163
|
-
# A port that made haskey false by DELETING the key would pass every case
|
|
164
|
-
# above and fail here, and it would also make the suppression unwritable:
|
|
165
|
-
# there would be nothing left for makeOptions to detect.
|
|
166
73
|
keysof: {
|
|
167
74
|
set: [
|
|
168
75
|
{ in: {a:null}, out: [a] }
|