Skip to content

Commit

Permalink
fixup! WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ezzatron committed Mar 24, 2024
1 parent 06e5b36 commit 5459337
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/constraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export type ExtrinsicConstraint<T> = {
readonly constrain: Constrain<T>;
};

export type Constrain<T> = (v: T) => string | undefined | void;
export type Constrain<T> = (v: T) => string | undefined | void | true;

export function applyConstraints<T>(
constraints: Constraint<T>[],
Expand Down
8 changes: 6 additions & 2 deletions src/declaration/big-integer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Constraint, ExtrinsicConstraint } from "../constraint.js";
import {
createRangeConstraint,
hasBigintRangeConstraint,
Expand All @@ -22,7 +23,9 @@ import { ScalarSchema, createScalar, toString } from "../schema.js";

export type Options = DeclarationOptions<bigint> &
DeclarationExampleOptions<bigint> &
Partial<RangeConstraintSpec<bigint>>;
Partial<RangeConstraintSpec<bigint>> & {
readonly constraints?: ExtrinsicConstraint<bigint>[];
};

export function bigInteger<O extends Options>(
name: string,
Expand Down Expand Up @@ -59,7 +62,8 @@ function createSchema(name: string, options: Options): ScalarSchema<bigint> {
}
}

const constraints = [];
const { constraints: explicitConstraints = [] } = options;
const constraints: Constraint<bigint>[] = [...explicitConstraints];

try {
if (hasBigintRangeConstraint(options)) {
Expand Down
16 changes: 6 additions & 10 deletions src/declaration/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,10 @@ export function binary<O extends Options>(
description: string,
options: ExactOptions<O, Options> = {} as ExactOptions<O, Options>,
): Declaration<Buffer, O> {
const {
constraints = [],
encoding = "base64",
examples,
isSensitive = false,
length,
} = options;
const { encoding = "base64", examples, isSensitive = false } = options;

const def = defaultFromOptions(options);
const schema = createSchema(name, encoding, length, [...constraints]);
const schema = createSchema(name, encoding, options);

const v = registerVariable({
name,
Expand All @@ -73,13 +67,15 @@ export function binary<O extends Options>(
function createSchema(
name: string,
encoding: BufferEncoding,
length: LengthConstraintSpec | undefined,
constraints: Constraint<Buffer>[],
options: Options,
): ScalarSchema<Buffer> {
function marshal(v: Buffer): string {
return v.toString(encoding);
}

const { constraints: explicitConstraints = [], length } = options;
const constraints: Constraint<Buffer>[] = [...explicitConstraints];

try {
if (typeof length !== "undefined") {
constraints.push(createLengthConstraint("decoded length", length));
Expand Down
32 changes: 19 additions & 13 deletions src/declaration/string.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ExtrinsicConstraint } from "../constraint.js";
import type { Constraint, ExtrinsicConstraint } from "../constraint.js";
import {
createLengthConstraint,
type LengthConstraintSpec,
Expand All @@ -18,7 +18,7 @@ import {
type Example,
} from "../example.js";
import { resolve } from "../maybe.js";
import { createString } from "../schema.js";
import { createString, type ScalarSchema } from "../schema.js";

export type Options = DeclarationOptions<string> &
DeclarationExampleOptions<string> & {
Expand All @@ -31,19 +31,10 @@ export function string<O extends Options>(
description: string,
options: ExactOptions<O, Options> = {} as ExactOptions<O, Options>,
): Declaration<string, O> {
const { constraints = [], examples, isSensitive = false, length } = options;
const { examples, isSensitive = false } = options;

const def = defaultFromOptions(options);

try {
if (typeof length !== "undefined") {
constraints.push(createLengthConstraint("length", length));
}
} catch (error) {
throw new SpecError(name, normalize(error));
}

const schema = createString("string", [...constraints]);
const schema = createSchema(name, options);

const v = registerVariable({
name,
Expand All @@ -61,6 +52,21 @@ export function string<O extends Options>(
};
}

function createSchema(name: string, options: Options): ScalarSchema<string> {
const { constraints: explicitConstraints = [], length } = options;
const constraints: Constraint<string>[] = [...explicitConstraints];

try {
if (typeof length !== "undefined") {
constraints.push(createLengthConstraint("length", length));
}
} catch (error) {
throw new SpecError(name, normalize(error));
}

return createString("string", [...constraints]);
}

function buildExamples(): Example<string>[] {
return [
{
Expand Down
67 changes: 67 additions & 0 deletions test/suite/declaration/big-integer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,71 @@ describe("Big integer declarations", () => {
});
});
});

describe("when the declaration has constraints", () => {
beforeEach(() => {
declaration = bigInteger("AUSTENITE_INTEGER", "<description>", {
constraints: [
{
description: "<constraint A>",
constrain: (v) => v % 2n === 0n || "must be divisible by 2",
},
{
description: "<constraint B>",
constrain: (v) => v % 3n === 0n || "must be divisible by 3",
},
],
});
});

describe("when the value satisfies the constraints", () => {
beforeEach(() => {
process.env.AUSTENITE_INTEGER = "6";

initialize({ onInvalid: noop });
});

describe(".value()", () => {
it("returns the value", () => {
expect(declaration.value()).toBe(6n);
});
});
});

describe("when the value violates the first constraint", () => {
beforeEach(() => {
process.env.AUSTENITE_INTEGER = "3";

initialize({ onInvalid: noop });
});

describe(".value()", () => {
it("throws", () => {
expect(() => {
declaration.value();
}).toThrow(
"value of AUSTENITE_INTEGER (3) is invalid: must be divisible by 2",
);
});
});
});

describe("when the value violates the second constraint", () => {
beforeEach(() => {
process.env.AUSTENITE_INTEGER = "2";

initialize({ onInvalid: noop });
});

describe(".value()", () => {
it("throws", () => {
expect(() => {
declaration.value();
}).toThrow(
"value of AUSTENITE_INTEGER (2) is invalid: must be divisible by 3",
);
});
});
});
});
});
14 changes: 10 additions & 4 deletions test/suite/declaration/binary.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,13 @@ describe("Binary declarations", () => {
constraints: [
{
description: "<constraint A>",
constrain: (v) => (v.length % 2 === 0 ? undefined : "<error A>"),
constrain: (v) =>
v.length % 2 === 0 || "length must be divisible by 2",
},
{
description: "<constraint B>",
constrain: (v) => (v.length % 3 === 0 ? undefined : "<error B>"),
constrain: (v) =>
v.length % 3 === 0 || "length must be divisible by 3",
},
],
});
Expand Down Expand Up @@ -268,7 +270,9 @@ describe("Binary declarations", () => {
it("throws", () => {
expect(() => {
declaration.value();
}).toThrow("value of AUSTENITE_BINARY (YWJj) is invalid: <error A>");
}).toThrow(
"value of AUSTENITE_BINARY (YWJj) is invalid: length must be divisible by 2",
);
});
});
});
Expand All @@ -284,7 +288,9 @@ describe("Binary declarations", () => {
it("throws", () => {
expect(() => {
declaration.value();
}).toThrow("value of AUSTENITE_BINARY (YWI=) is invalid: <error B>");
}).toThrow(
"value of AUSTENITE_BINARY (YWI=) is invalid: length must be divisible by 3",
);
});
});
});
Expand Down
69 changes: 69 additions & 0 deletions test/suite/declaration/string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,73 @@ describe("String declarations", () => {
});
});
});

describe("when the declaration has constraints", () => {
beforeEach(() => {
declaration = string("AUSTENITE_STRING", "<description>", {
constraints: [
{
description: "<constraint A>",
constrain: (v) =>
v.length % 2 === 0 || "length must be divisible by 2",
},
{
description: "<constraint B>",
constrain: (v) =>
v.length % 3 === 0 || "length must be divisible by 3",
},
],
});
});

describe("when the value satisfies the constraints", () => {
beforeEach(() => {
process.env.AUSTENITE_STRING = "abcdef";

initialize({ onInvalid: noop });
});

describe(".value()", () => {
it("returns the value", () => {
expect(declaration.value()).toBe("abcdef");
});
});
});

describe("when the value violates the first constraint", () => {
beforeEach(() => {
process.env.AUSTENITE_STRING = "abc";

initialize({ onInvalid: noop });
});

describe(".value()", () => {
it("throws", () => {
expect(() => {
declaration.value();
}).toThrow(
"value of AUSTENITE_STRING (abc) is invalid: length must be divisible by 2",
);
});
});
});

describe("when the value violates the second constraint", () => {
beforeEach(() => {
process.env.AUSTENITE_STRING = "ab";

initialize({ onInvalid: noop });
});

describe(".value()", () => {
it("throws", () => {
expect(() => {
declaration.value();
}).toThrow(
"value of AUSTENITE_STRING (ab) is invalid: length must be divisible by 3",
);
});
});
});
});
});

0 comments on commit 5459337

Please sign in to comment.