Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Add assert utility function #244

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions packages/core/src/codecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Crc32cCodec } from "./codecs/crc32c.js";
import { JsonCodec } from "./codecs/json2.js";
import { TransposeCodec } from "./codecs/transpose.js";
import { VLenUTF8 } from "./codecs/vlen-utf8.js";
import { assert } from "./util.js";

type ChunkMetadata<D extends DataType> = {
data_type: D;
Expand Down Expand Up @@ -90,9 +91,7 @@ type BytesToBytesCodec = {
async function load_codecs<D extends DataType>(chunk_meta: ChunkMetadata<D>) {
let promises = chunk_meta.codecs.map(async (meta) => {
let Codec = await registry.get(meta.name)?.();
if (!Codec) {
throw new Error(`Unknown codec: ${meta.name}`);
}
assert(Codec, `Unknown codec: ${meta.name}`);
return { Codec, meta };
});
let array_to_array: ArrayToArrayCodec<D>[] = [];
Expand All @@ -112,11 +111,10 @@ async function load_codecs<D extends DataType>(chunk_meta: ChunkMetadata<D>) {
}
}
if (!array_to_bytes) {
if (!is_typed_array_like_meta(chunk_meta)) {
throw new Error(
`Cannot encode ${chunk_meta.data_type} to bytes without a codec`,
);
}
assert(
is_typed_array_like_meta(chunk_meta),
`Cannot encode ${chunk_meta.data_type} to bytes without a codec`,
);
array_to_bytes = BytesCodec.fromConfig({ endian: "little" }, chunk_meta);
}
return { array_to_array, array_to_bytes, bytes_to_bytes };
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/codecs/bitround.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Chunk, Float32, Float64 } from "../metadata.js";
import { assert } from "../util.js";

/**
* A codec for bit-rounding.
Expand All @@ -20,9 +21,7 @@ export class BitroundCodec<D extends Float64 | Float32> {
kind = "array_to_array";

constructor(configuration: { keepbits: number }, _meta: { data_type: D }) {
if (configuration.keepbits < 0) {
throw new Error("keepbits must be zero or positive");
}
assert(configuration.keepbits >= 0, "keepbits must be zero or positive");
}

static fromConfig<D extends Float32 | Float64>(
Expand Down
67 changes: 31 additions & 36 deletions packages/core/src/codecs/json2.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Adapted from https://github.com/hms-dbmi/vizarr/blob/5b0e3ea6fbb42d19d0e38e60e49bb73d1aca0693/src/utils.ts#L26
import type { Chunk, ObjectType } from "../metadata.js";
import { get_strides, json_decode_object } from "../util.js";
import { assert, get_strides, json_decode_object } from "../util.js";

type EncoderConfig = {
encoding?: "utf-8";
Expand All @@ -24,23 +24,18 @@ type ReplacerFunction = (key: string | number, value: any) => any;

// Reference: https://stackoverflow.com/a/21897413
function throw_on_nan_replacer(_key: string | number, value: number): number {
if (Number.isNaN(value)) {
throw new Error(
"JsonCodec allow_nan is false but NaN was encountered during encoding.",
);
}

if (value === Number.POSITIVE_INFINITY) {
throw new Error(
"JsonCodec allow_nan is false but Infinity was encountered during encoding.",
);
}

if (value === Number.NEGATIVE_INFINITY) {
throw new Error(
"JsonCodec allow_nan is false but -Infinity was encountered during encoding.",
);
}
assert(
!Number.isNaN(value),
"JsonCodec allow_nan is false but NaN was encountered during encoding.",
);
assert(
value !== Number.POSITIVE_INFINITY,
"JsonCodec allow_nan is false but Infinity was encountered during encoding.",
);
assert(
value !== Number.NEGATIVE_INFINITY,
"JsonCodec allow_nan is false but -Infinity was encountered during encoding.",
);
return value;
}

Expand Down Expand Up @@ -117,17 +112,19 @@ export class JsonCodec {
allow_nan,
sort_keys,
} = this.#encoder_config;
if (encoding !== "utf-8") {
throw new Error("JsonCodec does not yet support non-utf-8 encoding.");
}
assert(
encoding === "utf-8",
"JsonCodec does not yet support non-utf-8 encoding.",
);
const replacer_functions: ReplacerFunction[] = [];
if (!check_circular) {
// By default, for JSON.stringify,
// a TypeError will be thrown if one attempts to encode an object with circular references
throw new Error(
"JsonCodec does not yet support skipping the check for circular references during encoding.",
);
}

// By default, for JSON.stringify,
// a TypeError will be thrown if one attempts to encode an object with circular references
assert(
check_circular,
"JsonCodec does not yet support skipping the check for circular references during encoding.",
);

if (!allow_nan) {
// Throw if NaN/Infinity/-Infinity are encountered during encoding.
replacer_functions.push(throw_on_nan_replacer);
Expand Down Expand Up @@ -170,17 +167,15 @@ export class JsonCodec {

decode(bytes: Uint8Array): Chunk<ObjectType> {
const { strict } = this.#decoder_config;
if (!strict) {
// (i.e., allowing control characters inside strings)
throw new Error("JsonCodec does not yet support non-strict decoding.");
}
// (i.e., allowing control characters inside strings)
assert(strict, "JsonCodec does not yet support non-strict decoding.");

const items = json_decode_object(bytes);
const shape = items.pop();
items.pop(); // Pop off dtype (unused)
if (!shape) {
// O-d case
throw new Error("0D not implemented for JsonCodec.");
}

// O-d case
assert(shape, "0D not implemented for JsonCodec.");
const stride = get_strides(shape, "C");
const data = items;
return { data, shape, stride };
Expand Down
6 changes: 2 additions & 4 deletions packages/core/src/codecs/sharding.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Location } from "../hierarchy.js";
import type { Chunk } from "../metadata.js";
import type { ShardingCodecMetadata } from "../util.js";
import { assert, type ShardingCodecMetadata } from "../util.js";

import type { Readable } from "@zarrita/storage";
import { create_codec_pipeline } from "../codecs.js";
Expand All @@ -13,9 +13,7 @@ export function create_sharded_chunk_getter<Store extends Readable>(
encode_shard_key: (coord: number[]) => string,
sharding_config: ShardingCodecMetadata["configuration"],
) {
if (location.store.getRange === undefined) {
throw new Error("Store does not support range requests");
}
assert(location.store.getRange, "Store does not support range requests");
let get_range = location.store.getRange.bind(location.store);
let index_shape = shard_shape.map(
(d, i) => d / sharding_config.chunk_shape[i],
Expand Down
8 changes: 5 additions & 3 deletions packages/core/src/consolidated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
GroupMetadataV2,
} from "./metadata.js";
import {
assert,
json_decode_object,
json_encode_object,
rethrow_unless,
Expand Down Expand Up @@ -38,9 +39,10 @@ async function get_consolidated_metadata(
});
}
let meta: ConsolidatedMetadata = json_decode_object(bytes);
if (meta.zarr_consolidated_format !== 1) {
throw new Error("Unsupported consolidated format.");
}
assert(
meta.zarr_consolidated_format === 1,
"Unsupported consolidated format.",
);
return meta;
}

Expand Down
39 changes: 29 additions & 10 deletions packages/core/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,8 @@ export function get_ctr<D extends DataType>(
);
}
// @ts-expect-error - We've checked that the key exists
let ctr: TypedArrayConstructor<D> = CONSTRUCTORS[data_type];
if (!ctr) {
throw new Error(`Unknown or unsupported data_type: ${data_type}`);
}
let ctr: TypedArrayConstructor<D> | undefined = CONSTRUCTORS[data_type];
assert(ctr, `Unknown or unsupported data_type: ${data_type}`);
return ctr;
}

Expand Down Expand Up @@ -136,9 +134,8 @@ function coerce_dtype(
}

let match = dtype.match(endian_regex);
if (!match) {
throw new Error(`Invalid dtype: ${dtype}`);
}
assert(match, `Invalid dtype: ${dtype}`);

let [, endian, rest] = match;
let data_type =
{
Expand All @@ -155,9 +152,7 @@ function coerce_dtype(
f8: "float64",
}[rest] ??
(rest.startsWith("S") || rest.startsWith("U") ? `v2:${rest}` : undefined);
if (!data_type) {
throw new Error(`Unsupported or unknown dtype: ${dtype}`);
}
assert(data_type, `Unsupported or unknown dtype: ${dtype}`);
if (endian === "|") {
return { data_type } as { data_type: DataType };
}
Expand Down Expand Up @@ -332,3 +327,27 @@ export function rethrow_unless<E extends ReadonlyArray<ErrorConstructor>>(
throw error;
}
}

/**
* Make an assertion.
*
* Usage
* @example
* ```ts
* const value: boolean = Math.random() <= 0.5;
* assert(value, "value is greater than than 0.5!");
* value // true
* ```
*
* @param expression - The expression to test.
* @param msg - The optional message to display if the assertion fails.
* @throws an {@link Error} if `expression` is not truthy.
*/
export function assert(
expression: unknown,
msg: string | undefined = "",
): asserts expression {
if (!expression) {
throw new Error(msg);
}
}
22 changes: 22 additions & 0 deletions packages/storage/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,25 @@ export function merge_init(
},
};
}

/**
* Make an assertion.
*
* Usage
* @example
* ```ts
* const value: boolean = Math.random() <= 0.5;
* assert(value, "value is greater than than 0.5!");
* value // true
* ```
*
* @param expression - The expression to test.
* @param msg - The optional message to display if the assertion fails.
* @throws an {@link Error} if `expression` is not truthy.
*/
export function assert(
expression: unknown,
msg: string | undefined = "",
): asserts expression {
if (!expression) throw new Error(msg);
}
20 changes: 9 additions & 11 deletions packages/storage/src/zip.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { unzip } from "unzipit";
import { fetch_range, strip_prefix } from "./util.js";
import { assert, fetch_range, strip_prefix } from "./util.js";

import type { Reader, ZipInfo } from "unzipit";
import type { AbsolutePath, AsyncReadable } from "./types.js";
Expand Down Expand Up @@ -35,11 +35,10 @@ export class HTTPRangeReader implements Reader {
...this.#overrides,
method: "HEAD",
});
if (!req.ok) {
throw new Error(
`failed http request ${this.url}, status: ${req.status}: ${req.statusText}`,
);
}
assert(
req.ok,
`failed http request ${this.url}, status: ${req.status}: ${req.statusText}`,
);
this.length = Number(req.headers.get("content-length"));
if (Number.isNaN(this.length)) {
throw Error("could not get length");
Expand All @@ -53,11 +52,10 @@ export class HTTPRangeReader implements Reader {
return new Uint8Array(0);
}
const req = await fetch_range(this.url, offset, size, this.#overrides);
if (!req.ok) {
throw new Error(
`failed http request ${this.url}, status: ${req.status} offset: ${offset} size: ${size}: ${req.statusText}`,
);
}
assert(
req.ok,
`failed http request ${this.url}, status: ${req.status} offset: ${offset} size: ${size}: ${req.statusText}`,
);
return new Uint8Array(await req.arrayBuffer());
}
}
Expand Down
Loading