-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f5c1944
commit dcd9d68
Showing
15 changed files
with
372 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# zod | ||
|
||
Compatibility layer for [Zod](https://zod.dev/) and [_Contract_](/protocols/contract). You need to install it and its peer dependencies before usage: | ||
|
||
::: code-group | ||
|
||
```sh [pnpm] | ||
pnpm install zod @withease/zod | ||
``` | ||
|
||
```sh [yarn] | ||
yarn add zod @withease/zod | ||
``` | ||
|
||
```sh [npm] | ||
npm install zod @withease/zod | ||
``` | ||
|
||
::: | ||
|
||
## `zodContract` | ||
|
||
Creates a [_Contract_](/protocols/contract) based on given `ZodType`. | ||
|
||
```ts | ||
import { z } from 'zod'; | ||
import { zodContract } from '@farfetched/zod'; | ||
|
||
const Asteroid = z.object({ | ||
type: z.literal('asteroid'), | ||
mass: z.number(), | ||
}); | ||
|
||
const asteroidContract = zodContract(Asteroid); | ||
|
||
/* typeof asteroidContract === Contract< | ||
* unknown, 👈 it accepts something unknown | ||
* { type: 'asteriod', mass: number }, 👈 and validates if it is an asteroid | ||
* > | ||
*/ | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# @withease/zod |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# @withease/zod | ||
|
||
Read documentation [here](https://withease.effector.dev/zod/). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
"name": "@withease/zod", | ||
"version": "1.1.0", | ||
"license": "MIT", | ||
"scripts": { | ||
"test:run": "vitest run --typecheck", | ||
"test:watch": "vitest --typecheck", | ||
"build": "vite build", | ||
"size": "size-limit", | ||
"publint": "node ../../tools/publint.mjs", | ||
"typelint": "attw --pack" | ||
}, | ||
"devDependencies": { | ||
"zod": "^3.19" | ||
}, | ||
"peerDependencies": { | ||
"zod": "^3.19" | ||
}, | ||
"type": "module", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"main": "./dist/zod.cjs", | ||
"module": "./dist/zod.js", | ||
"types": "./dist/zod.d.ts", | ||
"exports": { | ||
".": { | ||
"import": { | ||
"types": "./dist/zod.d.ts", | ||
"default": "./dist/zod.js" | ||
}, | ||
"require": { | ||
"types": "./dist/zod.d.cts", | ||
"default": "./dist/zod.cjs" | ||
} | ||
} | ||
}, | ||
"size-limit": [ | ||
{ | ||
"path": "./dist/zod.js", | ||
"limit": "231 B" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { describe, test, expectTypeOf } from 'vitest'; | ||
import { z as zod } from 'zod'; | ||
|
||
import { zodContract } from './index'; | ||
|
||
describe('zodContract', () => { | ||
test('string', () => { | ||
const stringContract = zodContract(zod.string()); | ||
|
||
const smth: unknown = null; | ||
|
||
if (stringContract.isData(smth)) { | ||
expectTypeOf(smth).toEqualTypeOf<string>(); | ||
expectTypeOf(smth).not.toEqualTypeOf<number>(); | ||
} | ||
}); | ||
|
||
test('complex object', () => { | ||
const complexContract = zodContract( | ||
zod.tuple([ | ||
zod.object({ | ||
x: zod.number(), | ||
y: zod.literal(false), | ||
k: zod.set(zod.string()), | ||
}), | ||
zod.literal('literal'), | ||
zod.literal(42), | ||
]) | ||
); | ||
|
||
const smth: unknown = null; | ||
|
||
if (complexContract.isData(smth)) { | ||
expectTypeOf(smth).toEqualTypeOf< | ||
[ | ||
{ | ||
x: number; | ||
y: false; | ||
k: Set<string>; | ||
}, | ||
'literal', | ||
42 | ||
] | ||
>(); | ||
|
||
expectTypeOf(smth).not.toEqualTypeOf<number>(); | ||
|
||
expectTypeOf(smth).not.toEqualTypeOf< | ||
[ | ||
{ | ||
x: string; | ||
y: false; | ||
k: Set<string>; | ||
}, | ||
'literal', | ||
42 | ||
] | ||
>(); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { z as zod } from 'zod'; | ||
import { describe, test, expect } from 'vitest'; | ||
|
||
import { zodContract } from './index'; | ||
|
||
describe('zod/zodContract short', () => { | ||
test('interprets invalid response as error', () => { | ||
const contract = zodContract(zod.string()); | ||
|
||
expect(contract.getErrorMessages(2)).toMatchInlineSnapshot(` | ||
[ | ||
"Expected string, received number", | ||
] | ||
`); | ||
}); | ||
|
||
test('passes valid data', () => { | ||
const contract = zodContract(zod.string()); | ||
|
||
expect(contract.getErrorMessages('foo')).toEqual([]); | ||
}); | ||
|
||
test('isData passes for valid data', () => { | ||
const contract = zodContract( | ||
zod.object({ | ||
x: zod.number(), | ||
y: zod.string(), | ||
}) | ||
); | ||
|
||
expect( | ||
contract.isData({ | ||
x: 42, | ||
y: 'answer', | ||
}) | ||
).toEqual(true); | ||
}); | ||
|
||
test('isData does not pass for invalid data', () => { | ||
const contract = zodContract( | ||
zod.object({ | ||
x: zod.number(), | ||
y: zod.string(), | ||
}) | ||
); | ||
|
||
expect( | ||
contract.isData({ | ||
42: 'x', | ||
answer: 'y', | ||
}) | ||
).toEqual(false); | ||
}); | ||
|
||
test('interprets complex invalid response as error', () => { | ||
const contract = zodContract( | ||
zod.tuple([ | ||
zod.object({ | ||
x: zod.number(), | ||
y: zod.literal(true), | ||
k: zod | ||
.set(zod.string()) | ||
.nonempty('Invalid set, expected set of strings'), | ||
}), | ||
zod.literal('Uhm?'), | ||
zod.literal(42), | ||
]) | ||
); | ||
|
||
expect( | ||
contract.getErrorMessages([ | ||
{ | ||
x: 456, | ||
y: false, | ||
k: new Set(), | ||
}, | ||
'Answer is:', | ||
'42', | ||
]) | ||
).toMatchInlineSnapshot(` | ||
[ | ||
"Invalid literal value, expected true, path: 0.y", | ||
"Invalid set, expected set of strings, path: 0.k", | ||
"Invalid literal value, expected "Uhm?", path: 1", | ||
"Invalid literal value, expected 42, path: 2", | ||
] | ||
`); | ||
}); | ||
|
||
test('path from original zod error included in final message', () => { | ||
const contract = zodContract( | ||
zod.object({ | ||
x: zod.number(), | ||
y: zod.object({ | ||
z: zod.string(), | ||
k: zod.object({ | ||
j: zod.boolean(), | ||
}), | ||
}), | ||
}) | ||
); | ||
|
||
expect( | ||
contract.getErrorMessages({ | ||
x: '42', | ||
y: { | ||
z: 123, | ||
k: { | ||
j: new Map(), | ||
}, | ||
}, | ||
}) | ||
).toMatchInlineSnapshot(` | ||
[ | ||
"Expected number, received string, path: x", | ||
"Expected string, received number, path: y.z", | ||
"Expected boolean, received map, path: y.k.j", | ||
] | ||
`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* A _Contract_ is a type that allows to check if a value is conform to a given structure. | ||
*/ | ||
export type Contract<Raw, Data extends Raw> = { | ||
/** | ||
* Checks if Raw is Data | ||
*/ | ||
isData: (prepared: Raw) => prepared is Data; | ||
/** | ||
* - empty array is dedicated for valid response | ||
* - array of string with validation errors for invalidDataError | ||
*/ | ||
getErrorMessages: (prepared: Raw) => string[]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { type ZodType } from 'zod'; | ||
import { type Contract } from './contract_protocol'; | ||
|
||
/** | ||
* Transforms Zod contracts for `data` to internal Contract. | ||
* Any response which does not conform to `data` will be treated as error. | ||
* | ||
* @param {ZodType} data Zod Contract for valid data | ||
*/ | ||
export function zodContract<D>(data: ZodType<D>): Contract<unknown, D> { | ||
function isData(prepared: unknown): prepared is D { | ||
return data.safeParse(prepared).success; | ||
} | ||
|
||
return { | ||
isData, | ||
getErrorMessages(raw) { | ||
const validation = data.safeParse(raw); | ||
if (validation.success) { | ||
return []; | ||
} | ||
|
||
return validation.error.errors.map((e) => { | ||
const path = e.path.join('.'); | ||
return path !== '' ? `${e.message}, path: ${path}` : e.message; | ||
}); | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": "../../tsconfig.base.json", | ||
"compilerOptions": { | ||
"declaration": true, | ||
"types": ["node"], | ||
"outDir": "dist", | ||
"rootDir": "src", | ||
"baseUrl": "src" | ||
}, | ||
"include": ["src/**/*.ts"] | ||
} |
Oops, something went wrong.