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 express, {ErrorRequestHandler} from 'express'
import {FunctionPropertyNames} from './types' import {FunctionPropertyNames} from './types'
import {IDEMPOTENT_METHOD_REGEX} from './idempotent' import {IDEMPOTENT_METHOD_REGEX} from './idempotent'
import {IErrorResponse} from './jsonrpc' import {IErrorResponse} from './error'
import {ILogger} from '@rondo/common' import {ILogger} from '@rondo/common'
import {ISuccessResponse} from './jsonrpc' import {ISuccessResponse} from './jsonrpc'
import {NextFunction, Request, Response, Router} from 'express' import {NextFunction, Request, Response, Router} from 'express'

View File

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

View File

@ -15,6 +15,7 @@ describe('remote', () => {
interface IService { interface IService {
add(a: number, b: number): number add(a: number, b: number): number
fetchItem(id: number): Promise<string>
} }
const IServiceKeys = keys<IService>() const IServiceKeys = keys<IService>()
@ -22,6 +23,9 @@ describe('remote', () => {
add(a: number, b: number) { add(a: number, b: number) {
return a + b return a + b
} }
async fetchItem(id: number): Promise<string> {
return Promise.resolve('id:' + id)
}
} }
const service = new Service() 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 () => { it('creates a proxy for remote service', async () => {
const s = createRemoteClient<IService>( const rpc = createRemoteClient<IService>(
baseUrl, '/myService', IServiceKeys) 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) expect(result).toBe(3 + 7)
}) })
}) })

View File

@ -1,37 +1,51 @@
import Axios from 'axios' import Axios from 'axios'
import {FunctionPropertyNames, TAsyncified} from './types' 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>( export function createRemoteClient<T>(
baseUrl: string, baseUrl: string,
url: string, url: string,
methods: Array<FunctionPropertyNames<T>>, methods: Array<FunctionPropertyNames<T>>,
getNextRequestId: TRequestIdGenerator<string | number> = constantId('c'),
idempotentMethodRegex = IDEMPOTENT_METHOD_REGEX,
) { ) {
const axios = Axios.create({ const axios = Axios.create({
baseURL: baseUrl, 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) => { const service = methods.reduce((obj, method) => {
obj[method] = async function makeRequest(...args: any[]) { obj[method] = async function makeRequest(...args: any[]) {
id++ return createRequest(getNextRequestId(), method, args)
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 obj return obj
}, {} as any) }, {} as any)

View File

@ -10,7 +10,11 @@ type PromisifyReturnType<T> = (...a: ArgumentTypes<T>) =>
RetProm<UnwrapHOC<RetType<T>>> RetProm<UnwrapHOC<RetType<T>>>
export type FunctionPropertyNames<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] }[keyof T]
export type TAsyncified<T> = { export type TAsyncified<T> = {