Created
December 27, 2021 06:42
-
-
Save NWYLZW/ec18dc7cee3e14812ad3f5029c31b276 to your computer and use it in GitHub Desktop.
Rester
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
import MockAdapter from 'axios-mock-adapter' | |
import { AxiosInstance } from 'axios' | |
import { expect, use } from 'chai' | |
import cap from 'chai-as-promised' | |
import { Api, attachApi } from '../src/api' | |
use(cap) | |
describe('Api', function () { | |
interface Foo { | |
get guilds(): Promise<any[]> & { | |
add(nGuild: string): Promise<string> | |
} | |
guild(id: string): Promise<any> & { | |
upd(nGuild: string): Promise<string> | |
del(): Promise<string> | |
get members(): Promise<any[]> | |
member(id: string): Promise<any> | |
} | |
} | |
class Foo extends Api { | |
constructor() { | |
super('http://www.example.com') | |
return attachApi(this) | |
} | |
} | |
const foo = new Foo() | |
it('should travel simple property and simple method.', async () => { | |
const id = '123' | |
new MockAdapter(foo.$request as AxiosInstance) | |
.onGet('/guilds') | |
.replyOnce(200, 'test0') | |
.onGet(`/guilds/${ id }`) | |
.replyOnce(200, 'test1') | |
expect(await foo.guilds) | |
.to.equal('test0') | |
expect(await foo.guild(id)) | |
.to.equal('test1') | |
}) | |
it('should travel nest property and nest method.', async () => { | |
const guildId = '123', memberId = '456' | |
new MockAdapter(foo.$request as AxiosInstance) | |
.onGet(`/guilds/${ guildId }/members`) | |
.replyOnce(200, 'test0') | |
.onGet(`/guilds/${ guildId }/members/${ memberId }`) | |
.replyOnce(200, 'test1') | |
expect(await foo.guild(guildId).members) | |
.to.equal('test0') | |
expect(await foo.guild(guildId).member(memberId)) | |
.to.equal('test1') | |
}) | |
it('should travel other method request.', async () => { | |
const reqAdd = 'content' | |
const guildId = '123' | |
new MockAdapter(foo.$request as AxiosInstance) | |
.onPost('/guilds', reqAdd) | |
.replyOnce(200, 'test0') | |
.onPatch(`/guilds/${ guildId }`, reqAdd) | |
.replyOnce(200, 'test1') | |
.onDelete(`/guilds/${ guildId }`) | |
.replyOnce(200, 'test2') | |
expect(await foo.guilds.add(reqAdd)) | |
.to.equal('test0') | |
expect(await foo.guild(guildId).upd(reqAdd)) | |
.to.equal('test1') | |
expect(await foo.guild(guildId).del()) | |
.to.equal('test2') | |
}) | |
it('should listen `resp.fulfilled` and `resp.rejected` event.', async () => { | |
new MockAdapter(foo.$request as AxiosInstance) | |
.onGet('/guilds') | |
.replyOnce(200, 'test0') | |
.onGet('/guilds') | |
.replyOnce(403, 'test1') | |
.onGet('/guilds') | |
.replyOnce(404, 'test2') | |
foo.on('resp.fulfilled', resp => { | |
return resp.data + '-event' | |
}) | |
foo.on('resp.rejected', error => { | |
switch (error.response?.status) { | |
case 403: | |
return error.response.data + '-403event' | |
case 404: | |
console.log(error.response.data) | |
throw new Error(error.response.data + '-404event') | |
} | |
}) | |
expect(await foo.guilds) | |
.to.equal('test0-event') | |
expect(await foo.guilds) | |
.to.equal('test1-403event') | |
await expect(foo.guilds) | |
.to.eventually.rejectedWith('test2-404event') | |
await foo.guilds.catch(error => { | |
expect(error.message).to.equal('test2-404event') | |
}) | |
}) | |
}) |
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
import { Utils } from './utils' | |
import axios, { AxiosResponse, AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios' | |
type TwoParamsMethod = 'get' | 'delete' | 'head' | 'options' | |
type ThreeParamsMethod = 'post' | 'put' | 'patch' | |
interface TwoParamsRequest { | |
<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<T> | |
} | |
interface ThreeParamsRequest { | |
<T, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<T> | |
} | |
export type InnerAxiosInstance = Omit<AxiosInstance, 'request' | TwoParamsMethod | ThreeParamsMethod> & { | |
request<T = any, D = any>(config: AxiosRequestConfig<D>): Promise<T> | |
} & { | |
[K in TwoParamsMethod]: TwoParamsRequest | |
} & { | |
[K in ThreeParamsMethod]: ThreeParamsRequest | |
} | |
type promiseMethod = 'then' | 'catch' | 'finally' | |
const promiseMethods = [ 'then', 'catch', 'finally' ] | |
const getPromiseProp = (p: Promise<any>, prop: promiseMethod) => p[prop].bind(p) | |
const requestProxy = (a: Api, path: string, cPath = ''): Function => new Proxy(() => {}, { | |
get(_, prop: string) { | |
if (promiseMethods.includes(prop)) | |
return getPromiseProp( | |
a.$request.get(path + cPath), prop as promiseMethod) | |
else { | |
switch (prop as 'add' | 'del' | 'upd') { | |
case 'add': | |
return (d: any) => a.$request.post(path + cPath, d) | |
case 'del': | |
return () => a.$request.delete(path + cPath) | |
case 'upd': | |
return (d: any) => a.$request.patch(path + cPath, d) | |
} | |
return requestProxy(a, path, `/${prop}`) | |
} | |
}, | |
apply(_, __, [id, ..._args]) { | |
return requestProxy(a, `${path + Utils.String.pluralize(cPath)}/${id}`) | |
} | |
}) | |
export const attachApi = <T extends Api>(a: T) => new Proxy(a, { | |
get(target, path: keyof Api) { | |
if (path in target) return target[path] | |
return requestProxy(a, `/${path}`) | |
} | |
}) | |
export class Api { | |
readonly host: string | |
readonly events = <{ | |
[K in keyof Api.EventMap]: Api.EventMap[K] | |
}>{} | |
$request: InnerAxiosInstance | |
constructor(host: string) { | |
this.host = host | |
this.$request = this.getRequest() | |
} | |
on<E extends keyof Api.EventMap>(event: E, cb: Api.EventMap[E]) { | |
this.events[event] = cb | |
} | |
emit<E extends keyof Api.EventMap>(event: E, ...args: Parameters<Api.EventMap[E]>) { | |
if (event in this.events) | |
// @ts-ignore | |
return this.events[event](...args) | |
switch (event) { | |
case 'resp.fulfilled': | |
return (<AxiosResponse>args[0]).data | |
case 'resp.rejected': | |
throw args[0] | |
default: | |
return | |
} | |
} | |
protected getRequest() { | |
const a = axios.create({ | |
baseURL: this.host, | |
headers: { | |
'Content-Type': 'application/json' | |
} | |
}) | |
// @ts-ignore | |
a.interceptors.response.use(this.emit.bind(this, 'resp.fulfilled'), this.emit.bind(this, 'resp.rejected')) | |
return a | |
} | |
} | |
export namespace Api { | |
export interface EventMap { | |
'resp.fulfilled': (resp: AxiosResponse) => void | |
'resp.rejected': (error: AxiosError<{}>) => void | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment