zarr-metadata 0.2.0 → 0.4.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/README.md +7 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/semantics.d.ts +42 -0
- package/dist/semantics.d.ts.map +1 -0
- package/dist/semantics.js +303 -0
- package/dist/semantics.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/semantics.ts +325 -0
package/README.md
CHANGED
|
@@ -76,9 +76,13 @@ package's flat problem lists; `flattenTree`/`treeOf` convert between the
|
|
|
76
76
|
two, and the flat path+kind form remains the cross-language interchange
|
|
77
77
|
format the conformance corpus asserts on.
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
The structural validators check key presence, value shapes, and fixed
|
|
80
|
+
literals without interpreting extension points, matching the Python
|
|
81
|
+
package's layering. `validateArraySemanticsV3` is a separate,
|
|
82
|
+
TS-only semantic layer on top: it interprets the well-known core extension
|
|
83
|
+
points to enforce the specs' cross-field prose rules (regular chunk grid
|
|
84
|
+
arity, transpose permutations, sharding divisibility, fill_value vs data
|
|
85
|
+
type), skipping unrecognized names — the extension name space is open.
|
|
82
86
|
|
|
83
87
|
Consumers include the
|
|
84
88
|
[Zarr Metadata VS Code extension](https://github.com/d-v-b/vscode-zarr).
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export type { JSONValue, ZarrV3MetadataFieldJSON, ZarrV3NamedConfigJSON } from "./common.js";
|
|
9
9
|
export { flattenTree, formatTree, isEmptyTree, MetadataValidationError, treeOf, type ErrorTree, type Issue, type IssueKind, type IssuePath, type ParseResult, type PathedIssue, } from "./errors.js";
|
|
10
10
|
export * from "./schemas.js";
|
|
11
|
+
export * from "./semantics.js";
|
|
11
12
|
export type { StandardSchemaV1 } from "./standard-schema.js";
|
|
12
13
|
export * from "./v2.js";
|
|
13
14
|
export * from "./v3.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EAAE,SAAS,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,uBAAuB,EACvB,MAAM,EACN,KAAK,SAAS,EACd,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EAAE,SAAS,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,uBAAuB,EACvB,MAAM,EACN,KAAK,SAAS,EACd,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AACrB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { flattenTree, formatTree, isEmptyTree, MetadataValidationError, treeOf, } from "./errors.js";
|
|
9
9
|
export * from "./schemas.js";
|
|
10
|
+
export * from "./semantics.js";
|
|
10
11
|
export * from "./v2.js";
|
|
11
12
|
export * from "./v3.js";
|
|
12
13
|
export * from "./validation.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,uBAAuB,EACvB,MAAM,GAOP,MAAM,aAAa,CAAC;AACrB,cAAc,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,uBAAuB,EACvB,MAAM,GAOP,MAAM,aAAa,CAAC;AACrB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic (cross-field) validation for v3 array metadata.
|
|
3
|
+
*
|
|
4
|
+
* The structural validators never interpret what an extension point names;
|
|
5
|
+
* this module deliberately interprets a small set of WELL-KNOWN core
|
|
6
|
+
* extension points to enforce the cross-field rules the specs state in
|
|
7
|
+
* prose:
|
|
8
|
+
*
|
|
9
|
+
* - the `regular` chunk grid's `chunk_shape` has one length per dimension
|
|
10
|
+
* of `shape`;
|
|
11
|
+
* - the `rectilinear` chunk grid's `chunk_shapes` has one entry per
|
|
12
|
+
* dimension, and each explicit chunk list (integers and `[size, count]`
|
|
13
|
+
* run-length pairs) sums exactly to that dimension's length (the
|
|
14
|
+
* bare-integer uniform shorthand carries no sum constraint, like the
|
|
15
|
+
* regular grid);
|
|
16
|
+
* - a `transpose` codec's `order` is a permutation of `0..n-1`, with one
|
|
17
|
+
* entry per array dimension;
|
|
18
|
+
* - a `sharding_indexed` codec's inner `chunk_shape` matches the array's
|
|
19
|
+
* dimensionality and evenly divides every chunk it shards — the grid's
|
|
20
|
+
* chunk at the top level (each distinct per-dimension size, for
|
|
21
|
+
* rectilinear grids), the parent shard's inner chunk when sharding
|
|
22
|
+
* nests;
|
|
23
|
+
* - `fill_value` has a JSON shape permitted for the named core data type
|
|
24
|
+
* (booleans for `bool`, ranged integers for the int types, numbers /
|
|
25
|
+
* the "NaN"-family sentinels / width-checked "0x…" strings for the
|
|
26
|
+
* float types, two-element arrays for the complex types).
|
|
27
|
+
*
|
|
28
|
+
* Unrecognized names are skipped everywhere — the extension name space is
|
|
29
|
+
* open, and a rule that guessed would lie. This layer has no counterpart
|
|
30
|
+
* in the Python reference implementation (which stops at structure), so it
|
|
31
|
+
* is covered by this package's own tests rather than the conformance
|
|
32
|
+
* corpus.
|
|
33
|
+
*/
|
|
34
|
+
import { type ErrorTree } from "./errors.js";
|
|
35
|
+
/**
|
|
36
|
+
* Every semantic (cross-field) problem in a v3 array metadata document, as
|
|
37
|
+
* an error tree; an empty tree means no rule found a violation. Values that
|
|
38
|
+
* are not v3 array documents yield an empty tree; run the structural
|
|
39
|
+
* validators for structure.
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateArraySemanticsV3(value: unknown): ErrorTree;
|
|
42
|
+
//# sourceMappingURL=semantics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantics.d.ts","sourceRoot":"","sources":["../src/semantics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAU,KAAK,SAAS,EAAoB,MAAM,aAAa,CAAC;AAuMvE;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAqFlE"}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic (cross-field) validation for v3 array metadata.
|
|
3
|
+
*
|
|
4
|
+
* The structural validators never interpret what an extension point names;
|
|
5
|
+
* this module deliberately interprets a small set of WELL-KNOWN core
|
|
6
|
+
* extension points to enforce the cross-field rules the specs state in
|
|
7
|
+
* prose:
|
|
8
|
+
*
|
|
9
|
+
* - the `regular` chunk grid's `chunk_shape` has one length per dimension
|
|
10
|
+
* of `shape`;
|
|
11
|
+
* - the `rectilinear` chunk grid's `chunk_shapes` has one entry per
|
|
12
|
+
* dimension, and each explicit chunk list (integers and `[size, count]`
|
|
13
|
+
* run-length pairs) sums exactly to that dimension's length (the
|
|
14
|
+
* bare-integer uniform shorthand carries no sum constraint, like the
|
|
15
|
+
* regular grid);
|
|
16
|
+
* - a `transpose` codec's `order` is a permutation of `0..n-1`, with one
|
|
17
|
+
* entry per array dimension;
|
|
18
|
+
* - a `sharding_indexed` codec's inner `chunk_shape` matches the array's
|
|
19
|
+
* dimensionality and evenly divides every chunk it shards — the grid's
|
|
20
|
+
* chunk at the top level (each distinct per-dimension size, for
|
|
21
|
+
* rectilinear grids), the parent shard's inner chunk when sharding
|
|
22
|
+
* nests;
|
|
23
|
+
* - `fill_value` has a JSON shape permitted for the named core data type
|
|
24
|
+
* (booleans for `bool`, ranged integers for the int types, numbers /
|
|
25
|
+
* the "NaN"-family sentinels / width-checked "0x…" strings for the
|
|
26
|
+
* float types, two-element arrays for the complex types).
|
|
27
|
+
*
|
|
28
|
+
* Unrecognized names are skipped everywhere — the extension name space is
|
|
29
|
+
* open, and a rule that guessed would lie. This layer has no counterpart
|
|
30
|
+
* in the Python reference implementation (which stops at structure), so it
|
|
31
|
+
* is covered by this package's own tests rather than the conformance
|
|
32
|
+
* corpus.
|
|
33
|
+
*/
|
|
34
|
+
import { treeOf } from "./errors.js";
|
|
35
|
+
function isPlainObject(value) {
|
|
36
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
37
|
+
return false;
|
|
38
|
+
const proto = Object.getPrototypeOf(value);
|
|
39
|
+
return proto === null || proto === Object.prototype;
|
|
40
|
+
}
|
|
41
|
+
function isIntArray(value) {
|
|
42
|
+
return (Array.isArray(value) &&
|
|
43
|
+
Object.keys(value).length === value.length &&
|
|
44
|
+
value.every((item) => Number.isInteger(item)));
|
|
45
|
+
}
|
|
46
|
+
/** The `{name, configuration}` of a metadata field, when extractable. */
|
|
47
|
+
function fieldParts(field) {
|
|
48
|
+
if (typeof field === "string")
|
|
49
|
+
return { name: field, configuration: undefined };
|
|
50
|
+
if (isPlainObject(field) && typeof field["name"] === "string") {
|
|
51
|
+
const configuration = field["configuration"];
|
|
52
|
+
return {
|
|
53
|
+
name: field["name"],
|
|
54
|
+
configuration: isPlainObject(configuration) ? configuration : undefined,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
// --- fill_value vs data_type ----------------------------------------------
|
|
60
|
+
const INT_RANGES = {
|
|
61
|
+
int8: [-128, 127],
|
|
62
|
+
int16: [-32768, 32767],
|
|
63
|
+
int32: [-2147483648, 2147483647],
|
|
64
|
+
int64: [-9223372036854775808, 9223372036854775807],
|
|
65
|
+
uint8: [0, 255],
|
|
66
|
+
uint16: [0, 65535],
|
|
67
|
+
uint32: [0, 4294967295],
|
|
68
|
+
uint64: [0, 18446744073709551615],
|
|
69
|
+
};
|
|
70
|
+
const FLOAT_HEX_DIGITS = {
|
|
71
|
+
float16: 4,
|
|
72
|
+
float32: 8,
|
|
73
|
+
float64: 16,
|
|
74
|
+
};
|
|
75
|
+
/** Hex digit width of each complex type's component floats. */
|
|
76
|
+
const COMPLEX_COMPONENT_HEX_DIGITS = {
|
|
77
|
+
complex64: 8,
|
|
78
|
+
complex128: 16,
|
|
79
|
+
};
|
|
80
|
+
function isFloatFill(value, hexDigits) {
|
|
81
|
+
if (typeof value === "number")
|
|
82
|
+
return true;
|
|
83
|
+
if (typeof value !== "string")
|
|
84
|
+
return false;
|
|
85
|
+
if (value === "NaN" || value === "Infinity" || value === "-Infinity")
|
|
86
|
+
return true;
|
|
87
|
+
return new RegExp(`^0x[0-9a-fA-F]{${hexDigits}}$`).test(value);
|
|
88
|
+
}
|
|
89
|
+
function floatFillMessage(name, hexDigits) {
|
|
90
|
+
return (`expected a number, "NaN", "Infinity", "-Infinity", or a ` +
|
|
91
|
+
`${hexDigits}-hex-digit "0x..." string for data type ${JSON.stringify(name)}`);
|
|
92
|
+
}
|
|
93
|
+
/** The fill_value issue for a KNOWN core data type, if any; undefined otherwise. */
|
|
94
|
+
function fillValueIssue(dataTypeName, fill) {
|
|
95
|
+
if (dataTypeName === "bool") {
|
|
96
|
+
return typeof fill === "boolean"
|
|
97
|
+
? undefined
|
|
98
|
+
: 'expected a boolean fill value for data type "bool"';
|
|
99
|
+
}
|
|
100
|
+
const intRange = INT_RANGES[dataTypeName];
|
|
101
|
+
if (intRange !== undefined) {
|
|
102
|
+
const [low, high] = intRange;
|
|
103
|
+
return Number.isInteger(fill) && fill >= low && fill <= high
|
|
104
|
+
? undefined
|
|
105
|
+
: `expected an integer in [${low}, ${high}] for data type ${JSON.stringify(dataTypeName)}`;
|
|
106
|
+
}
|
|
107
|
+
const floatDigits = FLOAT_HEX_DIGITS[dataTypeName];
|
|
108
|
+
if (floatDigits !== undefined) {
|
|
109
|
+
return isFloatFill(fill, floatDigits)
|
|
110
|
+
? undefined
|
|
111
|
+
: floatFillMessage(dataTypeName, floatDigits);
|
|
112
|
+
}
|
|
113
|
+
const complexDigits = COMPLEX_COMPONENT_HEX_DIGITS[dataTypeName];
|
|
114
|
+
if (complexDigits !== undefined) {
|
|
115
|
+
const ok = Array.isArray(fill) &&
|
|
116
|
+
fill.length === 2 &&
|
|
117
|
+
fill.every((part) => isFloatFill(part, complexDigits));
|
|
118
|
+
return ok
|
|
119
|
+
? undefined
|
|
120
|
+
: `expected a two-element [real, imaginary] array for data type ${JSON.stringify(dataTypeName)}`;
|
|
121
|
+
}
|
|
122
|
+
return undefined; // unrecognized data type: no verdict
|
|
123
|
+
}
|
|
124
|
+
// --- codec pipelines (transpose / sharding) --------------------------------
|
|
125
|
+
function isPermutation(order) {
|
|
126
|
+
const seen = new Set(order);
|
|
127
|
+
return seen.size === order.length && order.every((entry) => entry >= 0 && entry < order.length);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Walk one codec pipeline. `dims` is the array dimensionality at this point
|
|
131
|
+
* (undefined when unknowable); `outerChunkSizes` holds the distinct chunk
|
|
132
|
+
* lengths per dimension this pipeline may encode — what a sharding codec's
|
|
133
|
+
* inner chunks must divide. A regular grid contributes one size per
|
|
134
|
+
* dimension; a rectilinear grid may contribute several.
|
|
135
|
+
*/
|
|
136
|
+
function pipelineIssues(pipeline, path, dims, outerChunkSizes) {
|
|
137
|
+
const issues = [];
|
|
138
|
+
pipeline.forEach((entry, index) => {
|
|
139
|
+
const parts = fieldParts(entry);
|
|
140
|
+
if (parts === undefined)
|
|
141
|
+
return;
|
|
142
|
+
const { name, configuration } = parts;
|
|
143
|
+
if (name === "transpose") {
|
|
144
|
+
const order = configuration?.["order"];
|
|
145
|
+
if (!isIntArray(order))
|
|
146
|
+
return; // shape errors are the schema layer's
|
|
147
|
+
const orderPath = [...path, index, "configuration", "order"];
|
|
148
|
+
if (!isPermutation(order)) {
|
|
149
|
+
issues.push({
|
|
150
|
+
path: orderPath,
|
|
151
|
+
message: `expected a permutation of the integers 0..${order.length - 1}`,
|
|
152
|
+
kind: "invalid_value",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (dims !== undefined && order.length !== dims) {
|
|
156
|
+
issues.push({
|
|
157
|
+
path: orderPath,
|
|
158
|
+
message: `expected one entry per array dimension (${dims})`,
|
|
159
|
+
kind: "invalid_value",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (name === "sharding_indexed") {
|
|
164
|
+
const chunkShape = configuration?.["chunk_shape"];
|
|
165
|
+
if (!isIntArray(chunkShape))
|
|
166
|
+
return;
|
|
167
|
+
const chunkShapePath = [...path, index, "configuration", "chunk_shape"];
|
|
168
|
+
if (dims !== undefined && chunkShape.length !== dims) {
|
|
169
|
+
issues.push({
|
|
170
|
+
path: chunkShapePath,
|
|
171
|
+
message: `expected one length per array dimension (${dims})`,
|
|
172
|
+
kind: "invalid_value",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
else if (outerChunkSizes !== undefined &&
|
|
176
|
+
chunkShape.length === outerChunkSizes.length &&
|
|
177
|
+
chunkShape.every((length) => length > 0)) {
|
|
178
|
+
let violation;
|
|
179
|
+
outerChunkSizes.forEach((sizes, axis) => {
|
|
180
|
+
if (violation !== undefined)
|
|
181
|
+
return;
|
|
182
|
+
const bad = sizes.find((size) => size % chunkShape[axis] !== 0);
|
|
183
|
+
if (bad !== undefined)
|
|
184
|
+
violation = { axis, size: bad };
|
|
185
|
+
});
|
|
186
|
+
if (violation !== undefined) {
|
|
187
|
+
const { axis, size } = violation;
|
|
188
|
+
const uniform = outerChunkSizes.every((sizes) => sizes.length === 1);
|
|
189
|
+
issues.push({
|
|
190
|
+
path: chunkShapePath,
|
|
191
|
+
message: uniform
|
|
192
|
+
? `expected ${JSON.stringify(chunkShape)} to evenly divide the outer chunk shape ${JSON.stringify(outerChunkSizes.map((sizes) => sizes[0]))}`
|
|
193
|
+
: `expected ${JSON.stringify(chunkShape)} to evenly divide every chunk size of the grid (dimension ${axis} has chunk size ${size})`,
|
|
194
|
+
kind: "invalid_value",
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const inner = configuration?.["codecs"];
|
|
199
|
+
if (Array.isArray(inner)) {
|
|
200
|
+
issues.push(...pipelineIssues(inner, [...path, index, "configuration", "codecs"], dims, chunkShape.map((length) => [length])));
|
|
201
|
+
}
|
|
202
|
+
// index_codecs encode the shard index, whose shape differs from the
|
|
203
|
+
// array's — no dimensional context applies there.
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
return issues;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Every semantic (cross-field) problem in a v3 array metadata document, as
|
|
210
|
+
* an error tree; an empty tree means no rule found a violation. Values that
|
|
211
|
+
* are not v3 array documents yield an empty tree; run the structural
|
|
212
|
+
* validators for structure.
|
|
213
|
+
*/
|
|
214
|
+
export function validateArraySemanticsV3(value) {
|
|
215
|
+
if (!isPlainObject(value) || value["node_type"] !== "array")
|
|
216
|
+
return treeOf([]);
|
|
217
|
+
const issues = [];
|
|
218
|
+
const shape = isIntArray(value["shape"]) ? value["shape"] : undefined;
|
|
219
|
+
const dims = shape?.length;
|
|
220
|
+
const grid = fieldParts(value["chunk_grid"]);
|
|
221
|
+
let chunkSizes;
|
|
222
|
+
if (grid?.name === "regular") {
|
|
223
|
+
const configured = grid.configuration?.["chunk_shape"];
|
|
224
|
+
if (isIntArray(configured)) {
|
|
225
|
+
if (dims !== undefined && configured.length !== dims) {
|
|
226
|
+
issues.push({
|
|
227
|
+
path: ["chunk_grid", "configuration", "chunk_shape"],
|
|
228
|
+
message: `expected one length per dimension of shape (${dims})`,
|
|
229
|
+
kind: "invalid_value",
|
|
230
|
+
});
|
|
231
|
+
// wrong arity: unusable as division context
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
chunkSizes = configured.map((length) => [length]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if (grid?.name === "rectilinear") {
|
|
239
|
+
const specs = grid.configuration?.["chunk_shapes"];
|
|
240
|
+
if (Array.isArray(specs)) {
|
|
241
|
+
if (dims !== undefined && specs.length !== dims) {
|
|
242
|
+
issues.push({
|
|
243
|
+
path: ["chunk_grid", "configuration", "chunk_shapes"],
|
|
244
|
+
message: `expected one entry per dimension of shape (${dims})`,
|
|
245
|
+
kind: "invalid_value",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const perDim = specs.map((spec, dim) => {
|
|
250
|
+
// Bare integer: uniform shorthand, no sum constraint (edge chunks
|
|
251
|
+
// are permitted, as with the regular grid).
|
|
252
|
+
if (Number.isInteger(spec))
|
|
253
|
+
return spec > 0 ? [spec] : undefined;
|
|
254
|
+
if (!Array.isArray(spec))
|
|
255
|
+
return undefined; // registry schema's problem
|
|
256
|
+
const sizes = new Set();
|
|
257
|
+
let total = 0;
|
|
258
|
+
for (const entry of spec) {
|
|
259
|
+
if (Number.isInteger(entry)) {
|
|
260
|
+
sizes.add(entry);
|
|
261
|
+
total += entry;
|
|
262
|
+
}
|
|
263
|
+
else if (Array.isArray(entry) &&
|
|
264
|
+
entry.length === 2 &&
|
|
265
|
+
Number.isInteger(entry[0]) &&
|
|
266
|
+
Number.isInteger(entry[1])) {
|
|
267
|
+
sizes.add(entry[0]);
|
|
268
|
+
total += entry[0] * entry[1];
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
return undefined; // malformed entry: registry schema's problem
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const extent = shape?.[dim];
|
|
275
|
+
if (extent !== undefined && total !== extent) {
|
|
276
|
+
issues.push({
|
|
277
|
+
path: ["chunk_grid", "configuration", "chunk_shapes", dim],
|
|
278
|
+
message: `expected chunk sizes summing to ${extent} along dimension ${dim}, got ${total}`,
|
|
279
|
+
kind: "invalid_value",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return [...sizes];
|
|
283
|
+
});
|
|
284
|
+
if (perDim.every((sizes) => sizes !== undefined)) {
|
|
285
|
+
chunkSizes = perDim;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const dataType = fieldParts(value["data_type"]);
|
|
291
|
+
if (dataType !== undefined && "fill_value" in value) {
|
|
292
|
+
const message = fillValueIssue(dataType.name, value["fill_value"]);
|
|
293
|
+
if (message !== undefined) {
|
|
294
|
+
issues.push({ path: ["fill_value"], message, kind: "invalid_value" });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const codecs = value["codecs"];
|
|
298
|
+
if (Array.isArray(codecs)) {
|
|
299
|
+
issues.push(...pipelineIssues(codecs, ["codecs"], dims, chunkSizes));
|
|
300
|
+
}
|
|
301
|
+
return treeOf(issues);
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=semantics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantics.js","sourceRoot":"","sources":["../src/semantics.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
package/src/semantics.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic (cross-field) validation for v3 array metadata.
|
|
3
|
+
*
|
|
4
|
+
* The structural validators never interpret what an extension point names;
|
|
5
|
+
* this module deliberately interprets a small set of WELL-KNOWN core
|
|
6
|
+
* extension points to enforce the cross-field rules the specs state in
|
|
7
|
+
* prose:
|
|
8
|
+
*
|
|
9
|
+
* - the `regular` chunk grid's `chunk_shape` has one length per dimension
|
|
10
|
+
* of `shape`;
|
|
11
|
+
* - the `rectilinear` chunk grid's `chunk_shapes` has one entry per
|
|
12
|
+
* dimension, and each explicit chunk list (integers and `[size, count]`
|
|
13
|
+
* run-length pairs) sums exactly to that dimension's length (the
|
|
14
|
+
* bare-integer uniform shorthand carries no sum constraint, like the
|
|
15
|
+
* regular grid);
|
|
16
|
+
* - a `transpose` codec's `order` is a permutation of `0..n-1`, with one
|
|
17
|
+
* entry per array dimension;
|
|
18
|
+
* - a `sharding_indexed` codec's inner `chunk_shape` matches the array's
|
|
19
|
+
* dimensionality and evenly divides every chunk it shards — the grid's
|
|
20
|
+
* chunk at the top level (each distinct per-dimension size, for
|
|
21
|
+
* rectilinear grids), the parent shard's inner chunk when sharding
|
|
22
|
+
* nests;
|
|
23
|
+
* - `fill_value` has a JSON shape permitted for the named core data type
|
|
24
|
+
* (booleans for `bool`, ranged integers for the int types, numbers /
|
|
25
|
+
* the "NaN"-family sentinels / width-checked "0x…" strings for the
|
|
26
|
+
* float types, two-element arrays for the complex types).
|
|
27
|
+
*
|
|
28
|
+
* Unrecognized names are skipped everywhere — the extension name space is
|
|
29
|
+
* open, and a rule that guessed would lie. This layer has no counterpart
|
|
30
|
+
* in the Python reference implementation (which stops at structure), so it
|
|
31
|
+
* is covered by this package's own tests rather than the conformance
|
|
32
|
+
* corpus.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { treeOf, type ErrorTree, type PathedIssue } from "./errors.js";
|
|
36
|
+
|
|
37
|
+
type Path = ReadonlyArray<string | number>;
|
|
38
|
+
|
|
39
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
40
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
41
|
+
const proto: unknown = Object.getPrototypeOf(value);
|
|
42
|
+
return proto === null || proto === Object.prototype;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isIntArray(value: unknown): value is number[] {
|
|
46
|
+
return (
|
|
47
|
+
Array.isArray(value) &&
|
|
48
|
+
Object.keys(value).length === value.length &&
|
|
49
|
+
value.every((item) => Number.isInteger(item))
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The `{name, configuration}` of a metadata field, when extractable. */
|
|
54
|
+
function fieldParts(
|
|
55
|
+
field: unknown,
|
|
56
|
+
): { name: string; configuration: Record<string, unknown> | undefined } | undefined {
|
|
57
|
+
if (typeof field === "string") return { name: field, configuration: undefined };
|
|
58
|
+
if (isPlainObject(field) && typeof field["name"] === "string") {
|
|
59
|
+
const configuration = field["configuration"];
|
|
60
|
+
return {
|
|
61
|
+
name: field["name"],
|
|
62
|
+
configuration: isPlainObject(configuration) ? configuration : undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- fill_value vs data_type ----------------------------------------------
|
|
69
|
+
|
|
70
|
+
const INT_RANGES: Record<string, readonly [number, number]> = {
|
|
71
|
+
int8: [-128, 127],
|
|
72
|
+
int16: [-32768, 32767],
|
|
73
|
+
int32: [-2147483648, 2147483647],
|
|
74
|
+
int64: [-9223372036854775808, 9223372036854775807],
|
|
75
|
+
uint8: [0, 255],
|
|
76
|
+
uint16: [0, 65535],
|
|
77
|
+
uint32: [0, 4294967295],
|
|
78
|
+
uint64: [0, 18446744073709551615],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const FLOAT_HEX_DIGITS: Record<string, number> = {
|
|
82
|
+
float16: 4,
|
|
83
|
+
float32: 8,
|
|
84
|
+
float64: 16,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Hex digit width of each complex type's component floats. */
|
|
88
|
+
const COMPLEX_COMPONENT_HEX_DIGITS: Record<string, number> = {
|
|
89
|
+
complex64: 8,
|
|
90
|
+
complex128: 16,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function isFloatFill(value: unknown, hexDigits: number): boolean {
|
|
94
|
+
if (typeof value === "number") return true;
|
|
95
|
+
if (typeof value !== "string") return false;
|
|
96
|
+
if (value === "NaN" || value === "Infinity" || value === "-Infinity") return true;
|
|
97
|
+
return new RegExp(`^0x[0-9a-fA-F]{${hexDigits}}$`).test(value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function floatFillMessage(name: string, hexDigits: number): string {
|
|
101
|
+
return (
|
|
102
|
+
`expected a number, "NaN", "Infinity", "-Infinity", or a ` +
|
|
103
|
+
`${hexDigits}-hex-digit "0x..." string for data type ${JSON.stringify(name)}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The fill_value issue for a KNOWN core data type, if any; undefined otherwise. */
|
|
108
|
+
function fillValueIssue(dataTypeName: string, fill: unknown): string | undefined {
|
|
109
|
+
if (dataTypeName === "bool") {
|
|
110
|
+
return typeof fill === "boolean"
|
|
111
|
+
? undefined
|
|
112
|
+
: 'expected a boolean fill value for data type "bool"';
|
|
113
|
+
}
|
|
114
|
+
const intRange = INT_RANGES[dataTypeName];
|
|
115
|
+
if (intRange !== undefined) {
|
|
116
|
+
const [low, high] = intRange;
|
|
117
|
+
return Number.isInteger(fill) && (fill as number) >= low && (fill as number) <= high
|
|
118
|
+
? undefined
|
|
119
|
+
: `expected an integer in [${low}, ${high}] for data type ${JSON.stringify(dataTypeName)}`;
|
|
120
|
+
}
|
|
121
|
+
const floatDigits = FLOAT_HEX_DIGITS[dataTypeName];
|
|
122
|
+
if (floatDigits !== undefined) {
|
|
123
|
+
return isFloatFill(fill, floatDigits)
|
|
124
|
+
? undefined
|
|
125
|
+
: floatFillMessage(dataTypeName, floatDigits);
|
|
126
|
+
}
|
|
127
|
+
const complexDigits = COMPLEX_COMPONENT_HEX_DIGITS[dataTypeName];
|
|
128
|
+
if (complexDigits !== undefined) {
|
|
129
|
+
const ok =
|
|
130
|
+
Array.isArray(fill) &&
|
|
131
|
+
fill.length === 2 &&
|
|
132
|
+
fill.every((part) => isFloatFill(part, complexDigits));
|
|
133
|
+
return ok
|
|
134
|
+
? undefined
|
|
135
|
+
: `expected a two-element [real, imaginary] array for data type ${JSON.stringify(dataTypeName)}`;
|
|
136
|
+
}
|
|
137
|
+
return undefined; // unrecognized data type: no verdict
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// --- codec pipelines (transpose / sharding) --------------------------------
|
|
141
|
+
|
|
142
|
+
function isPermutation(order: number[]): boolean {
|
|
143
|
+
const seen = new Set(order);
|
|
144
|
+
return seen.size === order.length && order.every((entry) => entry >= 0 && entry < order.length);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Walk one codec pipeline. `dims` is the array dimensionality at this point
|
|
149
|
+
* (undefined when unknowable); `outerChunkSizes` holds the distinct chunk
|
|
150
|
+
* lengths per dimension this pipeline may encode — what a sharding codec's
|
|
151
|
+
* inner chunks must divide. A regular grid contributes one size per
|
|
152
|
+
* dimension; a rectilinear grid may contribute several.
|
|
153
|
+
*/
|
|
154
|
+
function pipelineIssues(
|
|
155
|
+
pipeline: unknown[],
|
|
156
|
+
path: Path,
|
|
157
|
+
dims: number | undefined,
|
|
158
|
+
outerChunkSizes: number[][] | undefined,
|
|
159
|
+
): PathedIssue[] {
|
|
160
|
+
const issues: PathedIssue[] = [];
|
|
161
|
+
pipeline.forEach((entry, index) => {
|
|
162
|
+
const parts = fieldParts(entry);
|
|
163
|
+
if (parts === undefined) return;
|
|
164
|
+
const { name, configuration } = parts;
|
|
165
|
+
if (name === "transpose") {
|
|
166
|
+
const order = configuration?.["order"];
|
|
167
|
+
if (!isIntArray(order)) return; // shape errors are the schema layer's
|
|
168
|
+
const orderPath = [...path, index, "configuration", "order"];
|
|
169
|
+
if (!isPermutation(order)) {
|
|
170
|
+
issues.push({
|
|
171
|
+
path: orderPath,
|
|
172
|
+
message: `expected a permutation of the integers 0..${order.length - 1}`,
|
|
173
|
+
kind: "invalid_value",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (dims !== undefined && order.length !== dims) {
|
|
177
|
+
issues.push({
|
|
178
|
+
path: orderPath,
|
|
179
|
+
message: `expected one entry per array dimension (${dims})`,
|
|
180
|
+
kind: "invalid_value",
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
} else if (name === "sharding_indexed") {
|
|
184
|
+
const chunkShape = configuration?.["chunk_shape"];
|
|
185
|
+
if (!isIntArray(chunkShape)) return;
|
|
186
|
+
const chunkShapePath = [...path, index, "configuration", "chunk_shape"];
|
|
187
|
+
if (dims !== undefined && chunkShape.length !== dims) {
|
|
188
|
+
issues.push({
|
|
189
|
+
path: chunkShapePath,
|
|
190
|
+
message: `expected one length per array dimension (${dims})`,
|
|
191
|
+
kind: "invalid_value",
|
|
192
|
+
});
|
|
193
|
+
} else if (
|
|
194
|
+
outerChunkSizes !== undefined &&
|
|
195
|
+
chunkShape.length === outerChunkSizes.length &&
|
|
196
|
+
chunkShape.every((length) => length > 0)
|
|
197
|
+
) {
|
|
198
|
+
let violation: { axis: number; size: number } | undefined;
|
|
199
|
+
outerChunkSizes.forEach((sizes, axis) => {
|
|
200
|
+
if (violation !== undefined) return;
|
|
201
|
+
const bad = sizes.find((size) => size % (chunkShape[axis] as number) !== 0);
|
|
202
|
+
if (bad !== undefined) violation = { axis, size: bad };
|
|
203
|
+
});
|
|
204
|
+
if (violation !== undefined) {
|
|
205
|
+
const { axis, size } = violation;
|
|
206
|
+
const uniform = outerChunkSizes.every((sizes) => sizes.length === 1);
|
|
207
|
+
issues.push({
|
|
208
|
+
path: chunkShapePath,
|
|
209
|
+
message: uniform
|
|
210
|
+
? `expected ${JSON.stringify(chunkShape)} to evenly divide the outer chunk shape ${JSON.stringify(outerChunkSizes.map((sizes) => sizes[0]))}`
|
|
211
|
+
: `expected ${JSON.stringify(chunkShape)} to evenly divide every chunk size of the grid (dimension ${axis} has chunk size ${size})`,
|
|
212
|
+
kind: "invalid_value",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const inner = configuration?.["codecs"];
|
|
217
|
+
if (Array.isArray(inner)) {
|
|
218
|
+
issues.push(
|
|
219
|
+
...pipelineIssues(
|
|
220
|
+
inner,
|
|
221
|
+
[...path, index, "configuration", "codecs"],
|
|
222
|
+
dims,
|
|
223
|
+
chunkShape.map((length) => [length]),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
// index_codecs encode the shard index, whose shape differs from the
|
|
228
|
+
// array's — no dimensional context applies there.
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
return issues;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Every semantic (cross-field) problem in a v3 array metadata document, as
|
|
236
|
+
* an error tree; an empty tree means no rule found a violation. Values that
|
|
237
|
+
* are not v3 array documents yield an empty tree; run the structural
|
|
238
|
+
* validators for structure.
|
|
239
|
+
*/
|
|
240
|
+
export function validateArraySemanticsV3(value: unknown): ErrorTree {
|
|
241
|
+
if (!isPlainObject(value) || value["node_type"] !== "array") return treeOf([]);
|
|
242
|
+
const issues: PathedIssue[] = [];
|
|
243
|
+
const shape = isIntArray(value["shape"]) ? value["shape"] : undefined;
|
|
244
|
+
const dims = shape?.length;
|
|
245
|
+
|
|
246
|
+
const grid = fieldParts(value["chunk_grid"]);
|
|
247
|
+
let chunkSizes: number[][] | undefined;
|
|
248
|
+
if (grid?.name === "regular") {
|
|
249
|
+
const configured = grid.configuration?.["chunk_shape"];
|
|
250
|
+
if (isIntArray(configured)) {
|
|
251
|
+
if (dims !== undefined && configured.length !== dims) {
|
|
252
|
+
issues.push({
|
|
253
|
+
path: ["chunk_grid", "configuration", "chunk_shape"],
|
|
254
|
+
message: `expected one length per dimension of shape (${dims})`,
|
|
255
|
+
kind: "invalid_value",
|
|
256
|
+
});
|
|
257
|
+
// wrong arity: unusable as division context
|
|
258
|
+
} else {
|
|
259
|
+
chunkSizes = configured.map((length) => [length]);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} else if (grid?.name === "rectilinear") {
|
|
263
|
+
const specs = grid.configuration?.["chunk_shapes"];
|
|
264
|
+
if (Array.isArray(specs)) {
|
|
265
|
+
if (dims !== undefined && specs.length !== dims) {
|
|
266
|
+
issues.push({
|
|
267
|
+
path: ["chunk_grid", "configuration", "chunk_shapes"],
|
|
268
|
+
message: `expected one entry per dimension of shape (${dims})`,
|
|
269
|
+
kind: "invalid_value",
|
|
270
|
+
});
|
|
271
|
+
} else {
|
|
272
|
+
const perDim: (number[] | undefined)[] = specs.map((spec, dim) => {
|
|
273
|
+
// Bare integer: uniform shorthand, no sum constraint (edge chunks
|
|
274
|
+
// are permitted, as with the regular grid).
|
|
275
|
+
if (Number.isInteger(spec)) return spec as number > 0 ? [spec as number] : undefined;
|
|
276
|
+
if (!Array.isArray(spec)) return undefined; // registry schema's problem
|
|
277
|
+
const sizes = new Set<number>();
|
|
278
|
+
let total = 0;
|
|
279
|
+
for (const entry of spec) {
|
|
280
|
+
if (Number.isInteger(entry)) {
|
|
281
|
+
sizes.add(entry as number);
|
|
282
|
+
total += entry as number;
|
|
283
|
+
} else if (
|
|
284
|
+
Array.isArray(entry) &&
|
|
285
|
+
entry.length === 2 &&
|
|
286
|
+
Number.isInteger(entry[0]) &&
|
|
287
|
+
Number.isInteger(entry[1])
|
|
288
|
+
) {
|
|
289
|
+
sizes.add(entry[0] as number);
|
|
290
|
+
total += (entry[0] as number) * (entry[1] as number);
|
|
291
|
+
} else {
|
|
292
|
+
return undefined; // malformed entry: registry schema's problem
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const extent = shape?.[dim];
|
|
296
|
+
if (extent !== undefined && total !== extent) {
|
|
297
|
+
issues.push({
|
|
298
|
+
path: ["chunk_grid", "configuration", "chunk_shapes", dim],
|
|
299
|
+
message: `expected chunk sizes summing to ${extent} along dimension ${dim}, got ${total}`,
|
|
300
|
+
kind: "invalid_value",
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return [...sizes];
|
|
304
|
+
});
|
|
305
|
+
if (perDim.every((sizes) => sizes !== undefined)) {
|
|
306
|
+
chunkSizes = perDim as number[][];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const dataType = fieldParts(value["data_type"]);
|
|
313
|
+
if (dataType !== undefined && "fill_value" in value) {
|
|
314
|
+
const message = fillValueIssue(dataType.name, value["fill_value"]);
|
|
315
|
+
if (message !== undefined) {
|
|
316
|
+
issues.push({ path: ["fill_value"], message, kind: "invalid_value" });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const codecs = value["codecs"];
|
|
321
|
+
if (Array.isArray(codecs)) {
|
|
322
|
+
issues.push(...pipelineIssues(codecs, ["codecs"], dims, chunkSizes));
|
|
323
|
+
}
|
|
324
|
+
return treeOf(issues);
|
|
325
|
+
}
|