Skip to content

Commit

Permalink
Update to Zod v3 (#7)
Browse files Browse the repository at this point in the history
* PickUnion

* Add response match

* Version 0.0.24

* Update to zod 3.5.1

* Fix deno build

* --amend

* --amend

* Fix ci

* Fix ci

* Fix ci

* Fix ci

* Add coverage

* Fix Ci

* Fix format

* Fix format

* Fix build format

* Rm compile files

* run test

* ts ignore

* ts ignore

* yarn

* yarn

* zod any

* zod any

* zod any

* zod any

* zod any

* version 0.1.0
  • Loading branch information
wilmveel authored Jul 10, 2021
1 parent d640d30 commit 6cdda76
Show file tree
Hide file tree
Showing 83 changed files with 11,184 additions and 7,397 deletions.
48 changes: 48 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module.exports = {
env: { browser: true, node: true },
root: true,
parser: "@typescript-eslint/parser",
plugins: [
"@typescript-eslint",
"import",
"simple-import-sort",
"unused-imports",
],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
],
rules: {
"import/order": 0, // turn off in favor of eslint-plugin-simple-import-sort
"import/no-unresolved": 0,
"import/no-duplicates": 1,

/**
* eslint-plugin-simple-import-sort @see https://github.com/lydell/eslint-plugin-simple-import-sort
*/
"sort-imports": 0, // we use eslint-plugin-import instead
"simple-import-sort/imports": 1,
"simple-import-sort/exports": 1,

/**
* @typescript-eslint/eslint-plugin @see https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin
*/
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-empty-interface": "off",
/**
* ESLint core rules @see https://eslint.org/docs/rules/
*/
"no-case-declarations": "off",
"no-empty": "off",
"no-useless-escape": "off",
"no-control-regex": "off",
},
};
12 changes: 5 additions & 7 deletions .github/workflows/ci.yml → .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ jobs:

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/setup-node@v2
with:
node-version: 12.x
node-version: 14.x
- uses: denolib/setup-deno@v2
with:
deno-version: v1.4.6
- run: npm ci
- run: npm run build
- run: npm run test
- run: npm run deno_build
- run: npm run deno_test
- run: yarn install
- run: yarn build
- run: yarn test
65 changes: 65 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Test and Lint

on:
push:
create:
pull_request:
schedule:
- cron: '44 4 * * SAT'
workflow_dispatch:

jobs:
test-node:
runs-on: Ubuntu-20.04
strategy:
matrix:
node: [ '14' ]
typescript: [ '4.1', '4.2' ]
name: Test with TypeScript ${{ matrix.typescript }} on Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn install
- run: yarn add typescript@${{ matrix.typescript }}
- run: yarn build
- run: yarn test

test-deno:
runs-on: Ubuntu-20.04
strategy:
matrix:
deno: [ "v1.8.1", "v1.x" ]
name: Test with Deno ${{ matrix.deno }}
steps:
- uses: actions/checkout@v2
- uses: denolib/setup-deno@v2
with:
deno-version: ${{ matrix.deno }}
- run: deno --version
- run: deno test
working-directory: ./deno/lib
- run: deno run ./index.ts
working-directory: ./deno/lib
- run: deno run ./mod.ts
working-directory: ./deno/lib
- run: |
deno bundle ./mod.ts ./bundle.js
deno run ./bundle.js
working-directory: ./deno/lib
lint:
runs-on: Ubuntu-20.04
strategy:
matrix:
node: [ '14' ]
name: Lint on Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: yarn install
- run: yarn check:format
- run: yarn check:lint
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<a href="./src/__tests__" rel="nofollow"><img src="./coverage.svg" alt="coverage"></a>


# Zod-endpoints
Contract first strictly typed endpoints. By defining endpoints as a zod schema, all the requests and responses can be checked and parsed at runtime. Moreover, the TypeScript compiler can check the in- and output types of the endpoints.

Expand Down
1 change: 1 addition & 0 deletions coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 88 additions & 0 deletions deno/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// This script expects to be run via `yarn build:deno`.
//
// Although this script generates code for use in Deno, this script itself is
// written for Node so that contributors do not need to install Deno to build.
//
// @ts-check

import {
mkdirSync,
readdirSync,
readFileSync,
statSync,
writeFileSync,
} from "fs";
import { dirname } from "path";

// Node's path.join() normalize explicitly-relative paths like "./index.ts" to
// paths like "index.ts" which don't work as relative ES imports, so we do this.
const join = (/** @type string[] */ ...parts) => parts.join("/");

const projectRoot = process.cwd();
const nodeSrcRoot = join(projectRoot, "src");
const denoLibRoot = join(projectRoot, "deno", "lib");

const walkAndBuild = (/** @type string */ dir) => {
for (const entry of readdirSync(join(nodeSrcRoot, dir), {
withFileTypes: true,
encoding: "utf-8",
})) {
if (entry.isDirectory()) {
walkAndBuild(join(dir, entry.name));
} else if (entry.isFile() && entry.name.endsWith(".ts")) {
const nodePath = join(nodeSrcRoot, dir, entry.name);
const denoPath = join(denoLibRoot, dir, entry.name);

const nodeSource = readFileSync(nodePath, { encoding: "utf-8" });

const denoSource = nodeSource.replace(
/^(?:import|export)[\s\S]*?from\s*['"]([^'"]*)['"];$/gm,
(line, target) => {
if (target === "@jest/globals") {
return `import { expect } from "https://deno.land/x/[email protected]/mod.ts";\nconst test = Deno.test;`;
}

if (target === "zod") {
return `export * from "https://raw.githubusercontent.com/colinhacks/zod/master/deno/lib/mod.ts";`;
}

const targetNodePath = join(dirname(nodePath), target);
const targetNodePathIfFile = targetNodePath + ".ts";
const targetNodePathIfDir = join(targetNodePath, "index.ts");
try {

if (statSync(targetNodePathIfFile)?.isFile()) {
return line.replace(target, target + ".ts");
}
} catch (error) {
if (error?.code !== "ENOENT") {
throw error;
}
}

try {
if (statSync(targetNodePathIfDir)?.isFile()) {
return line.replace(target, join(target, "index.ts"));
}
} catch (error) {
if (error?.code !== "ENOENT") {
throw error;
}
}

console.warn(`Skipping non-resolvable import:\n ${line}`);
return line;
}
);

mkdirSync(dirname(denoPath), { recursive: true });
writeFileSync(denoPath, denoSource, { encoding: "utf-8" });
}
}
};

walkAndBuild("");

writeFileSync(join(denoLibRoot, "mod.ts"), `export * from "./index.ts";\n`, {
encoding: "utf-8",
});
71 changes: 71 additions & 0 deletions deno/lib/__tests__/match.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// @ts-ignore TS6133
import { expect } from "https://deno.land/x/[email protected]/mod.ts";
const test = Deno.test;

import * as z from "../index.ts";

test("match requset", () => {
const a = z.endpoint({
name: "A",
method: "GET",
path: [z.literal("a")],
query: {
next: z.parameter(z.string()),
},
responses: [
z.response({
status: 200,
}),
],
});

const b = z.endpoint({
name: "B",
method: "POST",
path: [z.literal("b")],
query: {
next: z.parameter(z.string().optional()),
},
responses: [
z.response({
status: 200,
body: [
z.body({
type: "application/json",
content: z.object({
b: z.string(),
}),
}),
z.body({
type: "plain/text",
content: z.object({
c: z.string(),
}),
}),
],
}),
],
});
const schema = z.union([a, b]);

const reqA: z.MatchRequest = {
method: "GET",
path: ["a"],
query: {
next: "a",
},
headers: {},
};

const reqB: z.MatchRequest = {
method: "POST",
path: ["b"],
query: {
next: undefined,
},
headers: {},
};

expect(z.matchRequest(schema, reqA)).toEqual(a);
expect(z.matchRequest(schema, reqB)).toEqual(b);
});
43 changes: 43 additions & 0 deletions deno/lib/__tests__/param.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @ts-ignore TS6133
import { expect } from "https://deno.land/x/[email protected]/mod.ts";
const test = Deno.test;

import * as z from "../index.ts";

test("parameter with number", () => {
const n = z
.parameter(z.number().max(100))
.name("limit")
.description("How many items to return at one time (max 100)");

expect(n.parse(50)).toEqual(50);

try {
n.parse(400);
} catch (err) {
const zerr: z.ZodError = err;
expect(zerr.issues[0].code).toEqual(z.ZodIssueCode.too_big);
expect(zerr.issues[0].message).toEqual(
`Value should be less than or equal to 100`
);
}
});

test("parameter with string", () => {
const s = z
.parameter(z.string().max(7))
.name("limit")
.description("How many items to return at one time (max 100)");

expect(s.parse("123456")).toEqual("123456");

try {
s.parse("12345678");
} catch (err) {
const zerr: z.ZodError = err;
expect(zerr.issues[0].code).toEqual(z.ZodIssueCode.too_big);
expect(zerr.issues[0].message).toEqual(
`Should be at most 7 characters long`
);
}
});
30 changes: 30 additions & 0 deletions deno/lib/__tests__/parse.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// @ts-ignore TS6133
import { expect } from "https://deno.land/x/[email protected]/mod.ts";
const test = Deno.test;

import * as z from "../index.ts";

test("parse bigint", () => {
const bigint = z.bigint();
const res = bigint.parse(BigInt(2));
expect(res).toEqual(BigInt(2));
});

test("parse pet", () => {
const Pet = z.object({
id: z.bigint(),
name: z.string(),
tag: z.string().optional(),
});
const Pets = z.array(z.reference("Pet", Pet));

const arr = [
{ id: BigInt(0), name: "a", tag: "Test" },
{ id: BigInt(1), name: "b", tag: "Test" },
];

expect(Pets.parse(arr)).toEqual([
{ id: BigInt(0), name: "a", tag: "Test" },
{ id: BigInt(1), name: "b", tag: "Test" },
]);
});
Loading

0 comments on commit 6cdda76

Please sign in to comment.