Implement GET method for idempotent methods

This commit is contained in:
Jerko Steiner 2019-08-04 13:52:07 +07:00
parent 7edbe354a9
commit 81225b77a8
5 changed files with 58 additions and 47 deletions

View File

@ -1,7 +1,7 @@
import express, {ErrorRequestHandler} from 'express'
import {FunctionPropertyNames} from './types'
import {IDEMPOTENT_METHOD_REGEX} from './idempotent'
import {IErrorResponse} from './jsonrpc'
import {IErrorResponse} from './error'
import {ILogger} from '@rondo/common'
import {ISuccessResponse} from './jsonrpc'
import {NextFunction, Request, Response, Router} from 'express'

View File

@ -1,7 +1,7 @@
export type TId = number | string
import {ArgumentTypes, FunctionPropertyNames, RetType} from './types'
import {isPromise} from './isPromise'
import {createError, IErrorWithData} from './error'
import {createError, IErrorResponse, IErrorWithData} from './error'
export const ERROR_PARSE = {
code: -32700,
@ -43,7 +43,7 @@ export function pick<T, K extends FunctionPropertyNames<T>>(t: T, keys: K[])
}, {} as Pick<T, K>)
}
export interface IRequest<M extends string | symbol | number = any, A = any[]> {
export interface IRequest<M extends string = any, A = any[]> {
jsonrpc: '2.0'
id: TId | null
method: M
@ -57,13 +57,6 @@ export interface ISuccessResponse<T> {
error: null
}
export interface IErrorResponse<T> {
jsonrpc: '2.0'
id: TId | null
result: null
error: IErrorWithData<T>
}
export type IResponse<T = any> = ISuccessResponse<T> | IErrorResponse<T>
export function createSuccessResponse<T>(id: number | string, result: T)
@ -76,19 +69,6 @@ export function createSuccessResponse<T>(id: number | string, result: T)
}
}
export function createErrorResponse<T>(
id: number | string | null, error: IErrorWithData<T>)
: IErrorResponse<T> {
return {
jsonrpc: '2.0',
id,
result: null,
error,
}
}
export type TGetContext<Context> = (req: Request) => Context
export const createRpcService =
<T, M extends FunctionPropertyNames<T>>(
service: T,

View File

@ -15,6 +15,7 @@ describe('remote', () => {
interface IService {
add(a: number, b: number): number
fetchItem(id: number): Promise<string>
}
const IServiceKeys = keys<IService>()
@ -22,6 +23,9 @@ describe('remote', () => {
add(a: number, b: number) {
return a + b
}
async fetchItem(id: number): Promise<string> {
return Promise.resolve('id:' + id)
}
}
const service = new Service()
@ -52,11 +56,20 @@ describe('remote', () => {
})
})
describe('method invocation', () => {
describe('idempotent method invocation (GET)', () => {
it('creates a proxy for remote service', async () => {
const s = createRemoteClient<IService>(
const rpc = createRemoteClient<IService>(
baseUrl, '/myService', IServiceKeys)
const result = await s.add(3, 7)
const result = await rpc.fetchItem(5)
expect(result).toBe('id:5')
})
})
describe('method invocation (POST)', () => {
it('creates a proxy for remote service', async () => {
const rpc = createRemoteClient<IService>(
baseUrl, '/myService', IServiceKeys)
const result = await rpc.add(3, 7)
expect(result).toBe(3 + 7)
})
})

View File

@ -1,37 +1,51 @@
import Axios from 'axios'
import {FunctionPropertyNames, TAsyncified} from './types'
import {IDEMPOTENT_METHOD_REGEX} from './idempotent'
export type TRequestIdGenerator<T extends string | number> = () => T
export const createNumberGenerator = (val: number) => () => ++val
export const constantId = (val: string) => () => val
export function createRemoteClient<T>(
baseUrl: string,
url: string,
methods: Array<FunctionPropertyNames<T>>,
getNextRequestId: TRequestIdGenerator<string | number> = constantId('c'),
idempotentMethodRegex = IDEMPOTENT_METHOD_REGEX,
) {
const axios = Axios.create({
baseURL: baseUrl,
})
let id = 0
async function createRequest(
id: string | number | null,
method: string,
params: any[],
) {
const reqMethod = IDEMPOTENT_METHOD_REGEX.test(method) ? 'get' : 'post'
const payloadKey = reqMethod === 'post' ? 'data' : 'params'
const response = await axios({
method: reqMethod,
url,
[payloadKey]: {
id,
jsonrpc: '2.0',
method,
params,
},
})
if (response.data.error) {
// TODO create an actual error
throw response.data.error
}
return response.data.result
}
const service = methods.reduce((obj, method) => {
obj[method] = async function makeRequest(...args: any[]) {
id++
const response = await axios({
method: 'post',
url,
headers: {
'content-type': 'application/json',
},
data: {
id,
jsonrpc: '2.0',
method,
params: args,
},
})
if (response.data.error) {
// TODO create an actual error
throw response.data.error
}
return response.data.result
return createRequest(getNextRequestId(), method, args)
}
return obj
}, {} as any)

View File

@ -10,7 +10,11 @@ type PromisifyReturnType<T> = (...a: ArgumentTypes<T>) =>
RetProm<UnwrapHOC<RetType<T>>>
export type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
[K in keyof T]: K extends string
? T[K] extends (...args: any[]) => any
? K
: never
: never
}[keyof T]
export type TAsyncified<T> = {