Skip to content

Commit

Permalink
New overload
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed Jul 30, 2024
1 parent 2f789e5 commit df8f944
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 14 deletions.
4 changes: 4 additions & 0 deletions apps/website/scripts/jsdoc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ await Promise.all(
name = node.declaration.id.name;
kind = 'function';
break;
case 'TSDeclareFunction':
name = node.declaration.id.name;
kind = 'function';
break;
case 'VariableDeclaration':
name = node.declaration.declarations[0].id.name;
kind = 'variable';
Expand Down
3 changes: 2 additions & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"license": "MIT",
"scripts": {
"test:run": "vitest run --typecheck",
"test:watch": "vitest --typecheck",
"build": "vite build",
"size": "size-limit",
"publint": "node ../../tools/publint.mjs",
Expand Down Expand Up @@ -31,7 +32,7 @@
"size-limit": [
{
"path": "./dist/contracts.js",
"limit": "603 B"
"limit": "687 B"
}
]
}
29 changes: 26 additions & 3 deletions packages/contracts/src/contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ describe('str', () => {
expect(cntrctA.isData('b')).toBeFalsy();
expect(cntrct1.getErrorMessages('b')).toMatchInlineSnapshot(`
[
"expected 1, got \\"b\\"",
"expected 1, got "b"",
]
`);

Expand Down Expand Up @@ -266,13 +266,13 @@ describe('str', () => {
expect(cntrct.getErrorMessages('')).toMatchInlineSnapshot(`
[
"expected boolean, got string",
"expected 0, got \\"\\"",
"expected 0, got """,
]
`);
});
});

describe('rec', () => {
describe('rec, overload with fields', () => {
it('empty object', () => {
const cntrct = rec({});

Expand Down Expand Up @@ -321,6 +321,29 @@ describe('str', () => {
});
});

describe('rec, overload with types', () => {
it('empty object', () => {
const cntrct = rec(str, num);

expect(cntrct.isData({})).toBeTruthy();
expect(cntrct.getErrorMessages({})).toEqual([]);
});

it('invalid field type', () => {
const cntrct = rec(str, str);

expect(cntrct.isData({ a: 'a' })).toBeTruthy();
expect(cntrct.getErrorMessages({ a: 'a' })).toEqual([]);

expect(cntrct.isData({ a: 1, b: 'b' })).toBeFalsy();
expect(cntrct.getErrorMessages({ a: 1 })).toMatchInlineSnapshot(`
[
"a: expected string, got number",
]
`);
});
});

describe('arr', () => {
it('valid', () => {
const cntrctNum = arr(num);
Expand Down
53 changes: 44 additions & 9 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,20 @@ export function or<T extends Array<Contract<unknown, any>>>(
};
}

/**
* Function that creates a _Contract_ that checks if a value is object and every property is conform to the given _Contract_.
*
* @example
* const Ids = rec(str, num);
*
* Ids.isData({ id1: 1, id2: 2 }) === true;
* Ids.isData({ id1: 1, id2: '2' }) === false;
*/
export function rec<V>(
keys: typeof str,
values: Contract<unknown, V>
): Contract<unknown, Record<string, V>>;

/**
* Function that creates a _Contract_ that checks if a value is conform to an object with the given _Contracts_ as properties.
*
Expand All @@ -137,15 +151,27 @@ export function or<T extends Array<Contract<unknown, any>>>(
*/
export function rec<C extends Record<string, Contract<unknown, any>>>(
c: C
): Contract<unknown, { [key in keyof C]: UnContract<C[key]> }> {
const check = (x: unknown): x is { [key in keyof C]: UnContract<C[key]> } => {
): Contract<unknown, { [key in keyof C]: UnContract<C[key]> }>;

export function rec(shape: any, fieldContract?: any): any {
const check = (x: unknown) => {
if (typeof x !== 'object' || x === null) return false;

let valid = true;
for (const [key, val] of Object.entries(c)) {
if (!val.isData((x as any)[key])) {
valid = false;
break;
if (shape === str) {
for (const val of Object.values(x)) {
if (fieldContract.isData(val) === false) {
valid = false;
break;
}
}
} else {
for (const [key, val] of Object.entries(shape)) {
// @ts-expect-error
if (!val.isData((x as any)[key])) {
valid = false;
break;
}
}
}

Expand All @@ -160,9 +186,18 @@ export function rec<C extends Record<string, Contract<unknown, any>>>(
}
const errors = [] as string[];

for (const [key, val] of Object.entries(c)) {
const newErrors = val.getErrorMessages((x as any)[key]);
errors.push(...newErrors.map((msg) => `${key}: ${msg}`));
if (shape === str) {
for (const [key, val] of Object.entries(x)) {
if (fieldContract.isData(val) === false) {
errors.push(`${key}: ${fieldContract.getErrorMessages(val)}`);
}
}
} else {
for (const [key, val] of Object.entries(shape)) {
// @ts-expect-error
const newErrors = val.getErrorMessages((x as any)[key]);
errors.push(...newErrors.map((msg: string) => `${key}: ${msg}`));
}
}

return errors;
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/src/interop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { object, string } from 'superstruct';
import { runtypeContract } from '@farfetched/runtypes';
import { superstructContract } from '@farfetched/superstruct';

import { rec, arr } from './contracts';
import { rec, arr } from './index';

describe('runtypes', () => {
it('supports Runtype inside', () => {
Expand Down

0 comments on commit df8f944

Please sign in to comment.