Skip to content

Commit

Permalink
new: Show value in error messages.
Browse files Browse the repository at this point in the history
  • Loading branch information
milesj committed Sep 21, 2021
1 parent ff5624c commit fbdde11
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 82 deletions.
51 changes: 0 additions & 51 deletions optimal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

# 5.0.0-alpha.3 - 2021-09-21

#### 💥 Breaking

- Implement an immutable API. (#37) ([d1f12ba](https://github.com/milesj/optimal/commit/d1f12ba)), closes [#37](https://github.com/milesj/optimal/issues/37)
- Migrate to ESM only. ([5a7b10a](https://github.com/milesj/optimal/commit/5a7b10a))
- Rework optimal() API. ([e1edef8](https://github.com/milesj/optimal/commit/e1edef8))

#### 🚀 Updates

- Add default value callbacks for lazy init. ([89115c0](https://github.com/milesj/optimal/commit/89115c0))
- Add lazy() schema. ([0fca859](https://github.com/milesj/optimal/commit/0fca859))
- Add new Infer type. ([b0c42db](https://github.com/milesj/optimal/commit/b0c42db))
- Add when() criteria. ([8ffb9c8](https://github.com/milesj/optimal/commit/8ffb9c8))
- Added keysOf() to objects. ([186d509](https://github.com/milesj/optimal/commit/186d509))
- Collect all errors instead of failing on the first. (#38) ([61ce05f](https://github.com/milesj/optimal/commit/61ce05f)), closes [#38](https://github.com/milesj/optimal/issues/38)

#### 🐞 Fixes

- Allow empty strings for oneOf(). ([67fddaf](https://github.com/milesj/optimal/commit/67fddaf))
- Pass path to custom callbacks. ([fa2800a](https://github.com/milesj/optimal/commit/fa2800a))
- Remove default value from shape(). ([96905e2](https://github.com/milesj/optimal/commit/96905e2))
- Remove default value from tuple(). ([97ec98b](https://github.com/milesj/optimal/commit/97ec98b))
- Remove schemas from union() call. ([c71191e](https://github.com/milesj/optimal/commit/c71191e))
- Switch to any function for consumers. ([628e513](https://github.com/milesj/optimal/commit/628e513))

#### ⚙️ Types

- Improve deep partial. ([f36807b](https://github.com/milesj/optimal/commit/f36807b))
- Update of to use generics for value. ([a582bf6](https://github.com/milesj/optimal/commit/a582bf6))
- Update schema validation to use unknown. ([72269c4](https://github.com/milesj/optimal/commit/72269c4))
- Update to use Required. ([cdce9b7](https://github.com/milesj/optimal/commit/cdce9b7))
- Use new 'abstract new' constructor type. ([a2a1e4f](https://github.com/milesj/optimal/commit/a2a1e4f))

#### 📦 Dependencies

- **[packemon]** Update to v1.5. ([06bea75](https://github.com/milesj/optimal/commit/06bea75))

#### 🛠 Internals

- Add Lerna for releasing. ([afa13e4](https://github.com/milesj/optimal/commit/afa13e4))
- Remove __DEV__ checks. ([4887f65](https://github.com/milesj/optimal/commit/4887f65))
- Revert esm migration. ([ec46014](https://github.com/milesj/optimal/commit/ec46014))
- Switch to a monorepo. (#36) ([52b7140](https://github.com/milesj/optimal/commit/52b7140)), closes [#36](https://github.com/milesj/optimal/issues/36)

**Note:** Version bump only for package optimal





# 5.0.0-alpha - 2020-09-08

Ground-up rewrite that migrates to a more composable API. Under the hood, classes were refactored
Expand Down
11 changes: 7 additions & 4 deletions optimal/src/ValidationError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pathKey } from './helpers';
import { pathKey, prettyValue } from './helpers';

export class ValidationError extends Error {
errors: ValidationError[] = [];
Expand All @@ -16,10 +16,13 @@ export class ValidationError extends Error {

if (path) {
const key = pathKey(path);
const valueLabel = prettyValue(value);
const typeLabel = key.includes('[') ? 'member' : 'field';
const label = valueLabel
? `Invalid ${typeLabel} "${key}" with value ${valueLabel}.`
: `Invalid ${typeLabel} "${key}".`;

this.message = `${
key.includes('[') ? `Invalid member "${key}".` : `Invalid field "${key}".`
} ${this.message}`;
this.message = `${label} ${this.message}`;
}
}

Expand Down
4 changes: 2 additions & 2 deletions optimal/src/createSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function validate<T>(
? (defaultValue as DefaultValueInitializer<T>)(path, currentObject, rootObject)
: defaultValue;

invalid(!state.required, 'Field is required and must be defined.', path);
invalid(!state.required, 'Field is required and must be defined.', path, undefined);
} else {
if (__DEV__ && metadata.deprecatedMessage) {
// eslint-disable-next-line no-console
Expand All @@ -51,7 +51,7 @@ function validate<T>(

// Handle null
if (value === null) {
invalid(state.nullable, 'Null is not allowed.', path);
invalid(state.nullable, 'Null is not allowed.', path, null);
}

// Run validations and produce a new value
Expand Down
26 changes: 26 additions & 0 deletions optimal/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,32 @@ export function pathKey(path: string): string {
return index > 0 ? path.slice(index + 1) : path;
}

export function prettyValue(value: unknown): string | null {
switch (typeof value) {
case 'string':
return `"${value}"`;

case 'number':
case 'function':
return String(value);

case 'object': {
if (value === null) {
return `\`null\``;
}

if (value.constructor !== Object) {
return value.constructor.name === 'Array' ? null : `\`${value.constructor.name}\``;
}

return null;
}

default:
return `\`${value}\``;
}
}

export function tryAndCollect(
validator: () => boolean | void,
validError: ValidationError,
Expand Down
16 changes: 8 additions & 8 deletions optimal/tests/optimal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ describe('Optimal', () => {
});
}).toThrowErrorMatchingInlineSnapshot(`
"The following validations have failed:
- Invalid field \\"entry\\". Value must be one of: string, array<string>, object<string | array<string>>, function."
- Invalid field \\"entry\\" with value 123. Value must be one of: string, array<string>, object<string | array<string>>, function."
`);
});

Expand All @@ -263,7 +263,7 @@ describe('Optimal', () => {
}).toThrowErrorMatchingInlineSnapshot(`
"The following validations have failed:
- Invalid field \\"output\\". The following validations have failed:
- Invalid field \\"crossOriginLoading\\". Received string with the following failures:
- Invalid field \\"crossOriginLoading\\" with value \\"not-anonymous\\". Received string with the following failures:
- String must be one of: anonymous, use-credentials"
`);
});
Expand All @@ -286,21 +286,21 @@ describe('Optimal', () => {
optimal(blueprint).validate(data);
}).toThrowErrorMatchingInlineSnapshot(`
"The following validations have failed:
- Invalid field \\"entry\\". Value must be one of: string, array<string>, object<string | array<string>>, function.
- Invalid field \\"entry\\" with value 123. Value must be one of: string, array<string>, object<string | array<string>>, function.
- Invalid field \\"output\\". The following validations have failed:
- Invalid field \\"crossOriginLoading\\". Received string with the following failures:
- Invalid field \\"crossOriginLoading\\" with value \\"not-anonymous\\". Received string with the following failures:
- String must be one of: anonymous, use-credentials
- Invalid field \\"publicPath\\". Must be a string.
- Invalid field \\"publicPath\\" with value 123. Must be a string.
- Invalid field \\"resolve\\". The following validations have failed:
- Invalid field \\"alias\\". Must be a plain object.
- Invalid field \\"target\\". String must be one of: async-node, electron-main, electron-renderer, node, node-webkit, web, webworker"
- Invalid field \\"target\\" with value \\"what\\". String must be one of: async-node, electron-main, electron-renderer, node, node-webkit, web, webworker"
`);

expect(() => {
// @ts-expect-error Invalid types
optimal(blueprint).validate(data, { collectErrors: false });
}).toThrowErrorMatchingInlineSnapshot(
`"Invalid field \\"entry\\". Value must be one of: string, array<string>, object<string | array<string>>, function."`,
`"Invalid field \\"entry\\" with value 123. Value must be one of: string, array<string>, object<string | array<string>>, function."`,
);
});

Expand All @@ -314,7 +314,7 @@ describe('Optimal', () => {
});
}).toThrowErrorMatchingInlineSnapshot(`
"The following validations have failed:
- Invalid field \\"entry\\". Value must be one of: string, array<string>, object<string | array<string>>, function."
- Invalid field \\"entry\\" with value 123. Value must be one of: string, array<string>, object<string | array<string>>, function."
`);
});

Expand Down
33 changes: 16 additions & 17 deletions optimal/tests/schemas/union.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('union()', () => {
schema.validate([123]);
}).toThrowErrorMatchingInlineSnapshot(`
"Received array/tuple with the following failures:
- Invalid member \\"[0]\\". Must be a string."
- Invalid member \\"[0]\\" with value 123. Must be a string."
`);
});

Expand Down Expand Up @@ -145,7 +145,7 @@ describe('union()', () => {
schema.validate({ foo: 'foo' });
}).toThrowErrorMatchingInlineSnapshot(`
"Received object/shape with the following failures:
- Invalid field \\"foo\\". Must be a number."
- Invalid field \\"foo\\" with value \\"foo\\". Must be a number."
`);
});

Expand All @@ -158,12 +158,11 @@ describe('union()', () => {
bar: number(),
}),
])

.validate({ foo: 123 });
}).toThrowErrorMatchingInlineSnapshot(`
"Received object/shape with the following failures:
- The following validations have failed:
- Invalid field \\"foo\\". Must be a string."
- Invalid field \\"foo\\" with value 123. Must be a string."
`);
});

Expand All @@ -183,7 +182,7 @@ describe('union()', () => {
.validate([1]);
}).toThrowErrorMatchingInlineSnapshot(`
"Received array/tuple with the following failures:
- Invalid member \\"[0]\\". Must be a string."
- Invalid member \\"[0]\\" with value 1. Must be a string."
`);
});

Expand All @@ -203,8 +202,8 @@ describe('union()', () => {
arrayUnion.validate([true]);
}).toThrowErrorMatchingInlineSnapshot(`
"Received array/tuple with the following failures:
- Invalid member \\"[0]\\". Must be a string.
- Invalid member \\"[0]\\". Must be a number."
- Invalid member \\"[0]\\" with value \`true\`. Must be a string.
- Invalid member \\"[0]\\" with value \`true\`. Must be a number."
`);

expect(() => {
Expand All @@ -226,8 +225,8 @@ describe('union()', () => {
objectUnion.validate({ foo: true });
}).toThrowErrorMatchingInlineSnapshot(`
"Received object/shape with the following failures:
- Invalid field \\"foo\\". Must be a string.
- Invalid field \\"foo\\". Must be a number."
- Invalid field \\"foo\\" with value \`true\`. Must be a string.
- Invalid field \\"foo\\" with value \`true\`. Must be a number."
`);

expect(() => {
Expand Down Expand Up @@ -300,12 +299,12 @@ describe('union()', () => {
.toThrowErrorMatchingInlineSnapshot(`
"Received array/tuple with the following failures:
- Invalid member \\"[1]\\". Received array/tuple with the following failures:
- Invalid member \\"[1]\\". Null is not allowed."
- Invalid member \\"[1]\\" with value \`null\`. Null is not allowed."
`);
expect(() => complexUnion.validate({ a: true, b: 123, c: {} }))
.toThrowErrorMatchingInlineSnapshot(`
"Received object/shape with the following failures:
- Invalid field \\"b\\". Value must be one of: boolean, object."
- Invalid field \\"b\\" with value 123. Value must be one of: boolean, object."
`);
});

Expand All @@ -324,26 +323,26 @@ describe('union()', () => {
}).toThrowErrorMatchingInlineSnapshot(`
"Received object/shape with the following failures:
- Unknown fields: unknown.
- Invalid field \\"unknown\\". Must be a string."
- Invalid field \\"unknown\\" with value \`true\`. Must be a string."
`);

expect(() => {
mixedUnion.validate({ foo: 123 });
}).toThrowErrorMatchingInlineSnapshot(`
"Received object/shape with the following failures:
- The following validations have failed:
- Invalid field \\"foo\\". Must be a string.
- Invalid field \\"foo\\". Must be a string."
- Invalid field \\"foo\\" with value 123. Must be a string.
- Invalid field \\"foo\\" with value 123. Must be a string."
`);

expect(() => {
mixedUnion.validate({ foo: 'abc', bar: 'abc', baz: 123 });
}).toThrowErrorMatchingInlineSnapshot(`
"Received object/shape with the following failures:
- The following validations have failed:
- Invalid field \\"bar\\". Must be a number.
- Invalid field \\"baz\\". Must be a boolean.
- Invalid field \\"baz\\". Must be a string."
- Invalid field \\"bar\\" with value \\"abc\\". Must be a number.
- Invalid field \\"baz\\" with value 123. Must be a boolean.
- Invalid field \\"baz\\" with value 123. Must be a string."
`);

expect(() => {
Expand Down

0 comments on commit fbdde11

Please sign in to comment.