Skip to content

Commit

Permalink
feta: add handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
kaysonwu committed Mar 17, 2022
1 parent 250ace8 commit 020f220
Show file tree
Hide file tree
Showing 20 changed files with 306 additions and 177 deletions.
92 changes: 66 additions & 26 deletions README-zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
- [安装](#安装)
- [使用](#使用)
- [webpack-dev-server](#webpack-dev-server)
- [响应处理器](#响应处理器)
- [Utils](#utils)
- [休眠](#休眠)
- [延迟](#延迟)
- [资源](#资源)
- [资源选项](#资源选项)
- [部分资源](#部分资源)
- [自定义分页](#自定义分页)
- [自定义验证器](#自定义验证器)
- [自定义响应](#自定义响应)
- [Typescript](#typescript)

## 安装
Expand Down Expand Up @@ -73,8 +74,55 @@ module.exports = {
};
```

### 响应处理器

`Serve mock` 提供了两种状态的响应处理器,分别是:`errorHandler``successHandler`,可以通过对响应处理器的修改已满足定制化需求。

```js
function errorHandler(_: IncomingMessage, res: ServerResponse, error: Error): void {
if (error instanceof HttpError) {
res.writeHead(error.getStatusCode(), error.getHeaders());
res.write(error.message);
} else if (error instanceof Error) {
res.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' });
res.write(JSON.stringify({ status: 400, message: error.message }));
}

res.end();
}

function successHandler(_: IncomingMessage, res: ServerResponse, data: unknown): void {
if (typeof data === 'string') {
res.write(data);
} else if (data !== undefined) {
res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.write(JSON.stringify(data));
}

res.end();
}

createServe(resolve(__dirname, 'mocks'), { errorHandler, successHandler })
```

## Utils

### 休眠

可以通过 `sleep` 在流程中实现延迟需求

```js
const { sleep, rand } = require('serve-mock');

module.exports = {
'GET /api/currentUser': async (req, res) => {
await sleep(rand(1000, 2000));

// do thing.
},
};
```

### 延迟

有时我们需要模拟网络延迟:
Expand Down Expand Up @@ -112,16 +160,23 @@ function resource<T extends Record<string, unknown> = Record<string, unknown>>(n

```typescript
interface ResourceOptions<T extends Record<string, unknown> = Record<string, unknown>> = {
rowKey?: string; // 数据行的键名
initialData?: T[]; // 初始化数据
only?: ResourceAction[]; // 仅模拟给定的资源 API
except?: ResourceAction[]; // 模拟除给定外的资源 API
validator?(data: T, req: IncomingMessage, records: T[], type: 'create' | 'update'): T; // 自定义创建和更新时的数据验证
validator?(data: string[], req: IncomingMessage, records: T[], type: 'delete'): void;
pagination?(data: T[], query: ParsedUrlQuery): WithPagination<T>; // 自定义查询结果分页
filter?(data: T[], query: ParsedUrlQuery, req: IncomingMessage): T[]; // 自定义查询结果过滤器
responder?(req: IncomingMessage, res: ServerResponse, data: T | T[], type: ResourceAction): void; // 自定义数据响应
errorHandler?(req: IncomingMessage, res: ServerResponse, error: Error): void; // 自定义错误处理程序
/** 数据行的键名,默认:id */
rowKey: string;
/** 初始数据 */
initialData: T[];
/** 允许创建的资源请求类型 */
only?: ResourceAction[];
/** 创建资源请求时需要排除掉的资源类型 */
except?: ResourceAction[];
/** 数据验证器,仅对:create、update、delete 资源类型有效 */
validator(data: T, req: IncomingMessage, records: T[], type: 'create' | 'update'): T;
validator(data: string[], req: IncomingMessage, records: T[], type: 'delete'): void;
/** 分页器,仅对:index 资源类型有效 */
pagination(data: T[], query: ParsedUrlQuery): T[] | { data: T[]; [key: string]: unknown };
/** 过滤器,仅对:index 资源类型有效 */
filter(data: T[], query: ParsedUrlQuery, req: IncomingMessage): T[];
/** 在响应前对数据进行处理,如果返回 undefined 则不应答内容 */
normalize(data: T | T [], type: ResourceAction): T | T[] | void;
}
```

Expand Down Expand Up @@ -207,21 +262,6 @@ const validator: ResourceOptions['validator'] = (data, req, records, type) => {
module.exports = resource('/api/users', { validator });
```

#### 自定义响应

如果你对某些 api 响应不满意,则可以自定义它:

```js
const { resource, ResourceOptions } = require('server-mock');
const responder: ResourceOptions['responder'] = (req, res, data, type) => {
// 在这里开始你的响应逻辑
};
module.exports = resource('/api/users', { responder });
```


## Typescript

如果你的 `mock` 文件需要使用 [Typescript](https://www.typescriptlang.org/), 则可以使用 [@babel/register](https://babeljs.io/docs/en/next/babel-register.html) 来提供编译服务:
Expand Down
75 changes: 65 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ English | [中文](README-zh_CN.md)
- [Installation](#installation)
- [Usage](#usage)
- [webpack-dev-server](#webpack-dev-server)
- [Handler](#handler)
- [Utils](#utils)
- [Sleep](#sleep)
- [Delay](#delay)
- [Resource](#resource)
- [Resource Options](#resource-options)
- [Partial Resource](#partial-resource)
- [Custom Pagination](#custom-pagination)
- [Custom Validator](#custom-validator)
- [Custom Responder](#custom-responder)
- [Typescript](#typescript)

## Installation
Expand Down Expand Up @@ -73,8 +74,55 @@ module.exports = {
};
```

### Handler

`Serve mock` provide response handlers in two states, namely `errorHandler` and `successHandler`, which can meet customization requirements by modifying the response handlers.

```js
function errorHandler(_: IncomingMessage, res: ServerResponse, error: Error): void {
if (error instanceof HttpError) {
res.writeHead(error.getStatusCode(), error.getHeaders());
res.write(error.message);
} else if (error instanceof Error) {
res.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' });
res.write(JSON.stringify({ status: 400, message: error.message }));
}

res.end();
}

function successHandler(_: IncomingMessage, res: ServerResponse, data: unknown): void {
if (typeof data === 'string') {
res.write(data);
} else if (data !== undefined) {
res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.write(JSON.stringify(data));
}

res.end();
}

createServe(resolve(__dirname, 'mocks'), { errorHandler, successHandler })
```

## Utils

### Sleep

Use `sleep` to delay in the process:

```js
const { sleep, rand } = require('serve-mock');

module.exports = {
'GET /api/currentUser': async (req, res) => {
await sleep(rand(1000, 2000));

// do thing.
},
};
```

### Delay

Sometimes we need to simulate network delay:
Expand Down Expand Up @@ -112,16 +160,23 @@ function resource<T extends Record<string, unknown> = Record<string, unknown>>(n

```typescript
type ResourceOptions<T extends Record<string, unknown> = Record<string, unknown>> = {
rowKey?: string; // Key of data row
initialData?: T[]; // Initialization data
only?: ResourceAction[]; // Mock only given resource API
except?: ResourceAction[]; // Mock API except for a given resource
validator?(data: T, req: IncomingMessage, records: T[], type: 'create' | 'update'): T; // Custom data validation when creating and updating
/** The key of data row. */
rowKey?: string;
/** Initialization data. */
initialData?: T[];
/** Mock only given resource API */
only?: ResourceAction[];
/** Mock API except for a given resource */
except?: ResourceAction[];
/** Custom data validation when creating and updating, only valid for create,update and delete resource APIs */
validator?(data: T, req: IncomingMessage, records: T[], type: 'create' | 'update'): T;
validator?(data: string[], req: IncomingMessage, records: T[], type: 'delete'): void;
pagination?(data: T[], query: ParsedUrlQuery): WithPagination<T>; // Custom query result pagination
filter?(data: T[], query: ParsedUrlQuery, req: IncomingMessage): T[]; // Custom query result filter
responder?(req: IncomingMessage, res: ServerResponse, data: T | T[], type: ResourceAction): void; // Data response
errorHandler?(req: IncomingMessage, res: ServerResponse, error: Error): void; // Custom error handler
/** Custom query result pagination, only valid for index resource API */
pagination?(data: T[], query: ParsedUrlQuery): WithPagination<T>;
/** Custom query result filter, only valid for index resource API */
filter?(data: T[], query: ParsedUrlQuery, req: IncomingMessage): T[];
/** Process the data before response. */
normalize(data: T | T [], type: ResourceAction): T | T[] | void;
}
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serve-mock",
"version": "0.0.8",
"version": "0.0.9",
"description": "Serve mock files",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down
20 changes: 18 additions & 2 deletions src/createServe.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { FSWatcher } from 'chokidar';
import Store from './store';
import { Mock, Serve, ServeOptions } from './types';
import defaultErrorHandler from './utils/errorHandler';
import find from './utils/find';
import isPlainObject from './utils/isPlainObject';
import requireModule from './utils/require';
import send from './utils/send';
import defaultSuccessHandler from './utils/successHandler';

export default function createServe(paths: string | string[], options: ServeOptions = {}): Serve {
const mocks: Record<string, Mock> = {};
const store = new Store();

const { sensitive = true, onWatch, ...watchOptions } = options;
const {
sensitive = true,
errorHandler = defaultErrorHandler,
successHandler = defaultSuccessHandler,
onWatch,
...watchOptions
} = options;

const watcher = new FSWatcher(watchOptions);

watcher
Expand Down Expand Up @@ -40,6 +49,13 @@ export default function createServe(paths: string | string[], options: ServeOpti

return (req, res, next) => {
const value = find(req, mocks, { sensitive });
return value ? send(req, res, value, store) : next();

if (value === undefined) {
return next();
}

return send(req, res, value, store)
.then(data => successHandler(req, res, data))
.catch(e => errorHandler(req, res, e));
};
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export { default as rand } from './utils/rand';
export { default as resource } from './utils/resource';
export { default as parser } from './utils/parser';
export { getKeyFromUrl, getKeysFromUrl } from './utils/getKeyFromUrl';
export { default as sleep } from './utils/sleep';
27 changes: 21 additions & 6 deletions src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// unknown 对接口不兼容,对类型兼容,因此使用 any,具体详见:https:/microsoft/TypeScript/issues/45237
import { Stats } from 'fs';
import { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'http';
import { ParsedUrlQuery } from 'querystring';
Expand Down Expand Up @@ -47,14 +49,18 @@ export interface Store {
flush(): void;
}

export type MockFunctionValue = (req: IncomingMessage, res: ServerResponse, store: Store) => void;
// unknown 对接口不兼容,对类型兼容,因此使用 any,具体详见:https:/microsoft/TypeScript/issues/45237
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type MockValue = string | Array<unknown> | Record<string, any> | MockFunctionValue;
export type MockFunctionValue = (req: IncomingMessage, res: ServerResponse, store: Store) => Promise<any> | any;
export type MockValue = string | Array<any> | Record<string, any> | MockFunctionValue;
export type Mock<V = MockValue> = Record<string, V>;

export interface ServeOptions extends WatchOptions {
/** 是否区分大小写 */
sensitive?: boolean;
/** 失败响应处理器 */
errorHandler?: (req: IncomingMessage, res: ServerResponse, error: Error) => void;
/** 成功响应处理器 */
successHandler?: (req: IncomingMessage, res: ServerResponse, data: any) => void;
/** 文件监听事件 */
onWatch?: (eventName: 'add' | 'change' | 'unlink', path: string, stats?: Stats) => void;
}

Expand All @@ -68,16 +74,23 @@ export function rand(min: number, max: number): number;

export type ResourceAction = 'index' | 'create' | 'show' | 'update' | 'delete';
export interface ResourceOptions<T = Record<string, unknown>> {
/** 数据行的键名,默认:id */
rowKey: string;
/** 初始数据 */
initialData: T[];
/** 允许创建的资源请求类型 */
only?: ResourceAction[];
/** 创建资源请求时需要排除掉的资源类型 */
except?: ResourceAction[];
/** 数据验证器,仅对:create、update、delete 资源类型有效 */
validator(data: T, req: IncomingMessage, records: T[], type: 'create' | 'update'): T;
validator(data: string[], req: IncomingMessage, records: T[], type: 'delete'): void;
/** 分页器,仅对:index 资源类型有效 */
pagination(data: T[], query: ParsedUrlQuery): T[] | { data: T[]; [key: string]: unknown };
/** 过滤器,仅对:index 资源类型有效 */
filter(data: T[], query: ParsedUrlQuery, req: IncomingMessage): T[];
responder(req: IncomingMessage, res: ServerResponse, data: T | T[], type: ResourceAction): void;
errorHandler(req: IncomingMessage, res: ServerResponse, error: Error): void;
/** 在响应前对数据进行处理,如果返回 undefined 则不应答内容 */
normalize(data: T | T [], type: ResourceAction): T | T[] | void;
}

export function resource<T = Record<string, unknown>>(
Expand All @@ -93,6 +106,8 @@ export function parser<T = Record<string, unknown> | string>(
export function getKeyFromUrl(url: string): string;
export function getKeysFromUrl(url: string): string[];

export function sleep(ms: number): Promise<void>;

export class HttpError extends Error {
constructor(
statusCode: number,
Expand Down
6 changes: 4 additions & 2 deletions src/utils/delay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import { Mock, MockFunctionValue, MockValue } from '../types';
import rand from './rand';
import send from './send';
import sleep from './sleep';

export function delay(value: MockValue, min: number, max?: number): MockFunctionValue {
return (req, res, store) => {
setTimeout(() => send(req, res, value, store), max && max > min ? rand(min, max) : min);
return async (req, res, store) => {
await sleep(max && max > min ? rand(min, max) : min);
return send(req, res, value, store);
};
}

Expand Down
18 changes: 18 additions & 0 deletions src/utils/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { IncomingMessage, ServerResponse } from 'http';
import HttpError from '../errors/HttpError';

export default function errorHandler(
_: IncomingMessage,
res: ServerResponse,
error: Error
): void {
if (error instanceof HttpError) {
res.writeHead(error.getStatusCode(), error.getHeaders());
res.write(error.message);
} else if (error instanceof Error) {
res.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' });
res.write(JSON.stringify({ status: 400, message: error.message }));
}

res.end();
}
Loading

0 comments on commit 020f220

Please sign in to comment.