Browse Source

modify final api of functions to recieve objects instead of a polyvariadic arguments

modified-method-api
ojizero 4 years ago
parent
commit
1e77e9c4a2
No known key found for this signature in database
GPG Key ID: FEBB7097845B0C7E
  1. 23
      README.md
  2. 27
      examples/sample-elasticsearch-client/index.js
  3. 98
      src/method.ts
  4. 4
      test/client.spec.ts
  5. 20
      test/method.spec.ts

23
README.md

@ -91,8 +91,8 @@ export const somePostMethodWithParam = client.route({ path: '/some/path/:withInn @@ -91,8 +91,8 @@ export const somePostMethodWithParam = client.route({ path: '/some/path/:withInn
import * as YourAPIClient from 'your-client-module'
const someGetMethodPromise = YourAPIClient.someGetMethod() // GET http://some.base.url/some/path
const someGetMethodWithParamPromise = YourAPIClient.someGetMethodWithParam(5) // GET http://some.base.url/some/path/5
const somePostMethodWithParamPromise = YourAPIClient.someGetMethodWithParam(5, { payload: { some: 'payload' } }) // POST http://some.base.url/some/path/5 { some: 'payload' }
const someGetMethodWithParamPromise = YourAPIClient.someGetMethodWithParam({ withInnerVariable: 5 }) // GET http://some.base.url/some/path/5
const somePostMethodWithParamPromise = YourAPIClient.someGetMethodWithParam({ withInnerVariable: 5, $payload: { some: 'payload' } }) // POST http://some.base.url/some/path/5 { some: 'payload' }
```
Examples can be found in the [`examples`](./examples) folder
@ -241,18 +241,25 @@ await postRoute({ payload: { some: 'payload' } }) // POST /some/base/path { some @@ -241,18 +241,25 @@ await postRoute({ payload: { some: 'payload' } }) // POST /some/base/path { some
###### RouteFunction
Type: `(...args: any[]): Promise<Response>`
Type: `(params: RequestConfig = {}): Promise<Response>`
A function that takes any number of arguments and performs the HTTP request.
A function that takes an object defining the parameters it's spec defined and performs the HTTP request.
The only special argument is the last one, which can be an object hodling any request options that can be used by the underlying client, as well as the payload (under the key `payload`) and query string (under the key `queryString`) objects if needed.
Keys provided, that begin with `$` prefix are special, those are
- `$payload`: holding any payload to be used in the body of the request
- `$headers`: headers to override or add in that specific call
- `$querystring`: query string parameters to add
Any other `$` prefixed key will be treated as an underlying options for the raw client, and will be passed unchanged (minus the removal of the `$` prefix)
Example calls would be
```js
getMethod(arg1, arg2, /*...,*/ argn)
getMethod(arg1, arg2, /*...,*/ argn, { queryString: { q: 'hello' } })
postMethod(arg1, arg2, /*...,*/ argn, { payload: { q: 'hello' } })
getMethod({ arg1, arg2, /*...,*/ argn })
getMethod({ arg1, arg2, /*...,*/ argn, $headers: { some: 'headers' } })
getMethod({ arg1, arg2, /*...,*/ argn, $querystring: { q: 'hello' } } })
postMethod({ arg1, arg2, /*...,*/ argn, $payload: { q: 'hello' } } })
```
All calls to a RouteFunction produce a promise that resolves to an oject of [`Response`](######response) type.

27
examples/sample-elasticsearch-client/index.js

@ -11,19 +11,19 @@ function makeESClient (host) { @@ -11,19 +11,19 @@ function makeESClient (host) {
return {
createIndex: client.route({
path: '/:indexName',
path: '/:indexName:',
method: 'PUT',
}),
deleteIndex: client.route({
path: '/:indexName',
path: '/:indexName:',
method: 'DELETE',
}),
getDocument: client.route({
path: '/:indexName/:docType/:docId',
path: '/:indexName:/:docType:/:docId:',
method: 'GET',
}),
addDocument: client.route({
path: '/:indexName/:docType/:docId',
path: '/:indexName:/:docType:/:docId:',
method: 'POST',
}),
bulkOperation: client.route({
@ -41,24 +41,33 @@ if (require.main === module) { @@ -41,24 +41,33 @@ if (require.main === module) {
const client = makeESClient('http://localhost:9200')
let response
response = await client.createIndex('test-index')
response = await client.createIndex({ indexName: 'test-index' })
delete response._rawResponse // This is a HUGE object
console.log({ createIndex: { stringifiedResponse: JSON.stringify(response) } })
response = await client.addDocument('test-index', 'test-type', 'test-id', { payload: { a: { test: 'document' } } })
response = await client.addDocument({
indexName: 'test-index',
docType: 'test-type',
docId: 'test-id',
$payload: { a: { test: 'document' } },
})
delete response._rawResponse // This is a HUGE object
console.log({ addDocument: { stringifiedResponse: JSON.stringify(response) } })
response = await client.getDocument('test-index', 'test-type', 'test-id')
response = await client.getDocument({
indexName: 'test-index',
docType: 'test-type',
docId: 'test-id',
})
delete response._rawResponse // This is a HUGE object
console.log({ getDocument: { stringifiedResponse: JSON.stringify(response) } })
response = await client.bulkOperation({
payload: `{"index":{"_index":"test-index","_type":"test-type","_id":"bulk-document-id"}}
$payload: `{"index":{"_index":"test-index","_type":"test-type","_id":"bulk-document-id"}}
{"a":{"test":"bulkoperation"}}
`
})
delete response._rawResponse // This is a HUGE object
console.log({ getDocument: { stringifiedResponse: JSON.stringify(response) } })
console.log({ bulkOperation: { stringifiedResponse: JSON.stringify(response) } })
})()
}

98
src/method.ts

@ -22,16 +22,47 @@ export interface MethodSpec { @@ -22,16 +22,47 @@ export interface MethodSpec {
headers?: OutgoingHttpHeaders,
}
export type RequestConfig = ClientRequestConfig & {
path?: any[] | { [p: string]: any },
payload?: any,
queryString?: { [q: string]: any },
export type RequestConfig = {
/**
* This following represents two separate values
* the first is the object keys contianed
* in the path arugments defined by
* the method specificition.
*
* The second is a list of `$` prefixed options
* that are the options that can configure
* the underlying client. Those had to
* be defined very loosely as there's
* no way to augment keys in type
* mapping. More details: https://github.com/Microsoft/TypeScript/issues/12754
*/
// [clientConfigButPrefixedWith$: string]: any,
[pathArgument: string]: any,
// Helper parameters to pass body, querystrings, and headers easily
$payload?: any,
$querystring?: { [q: string]: any },
$headers?: OutgoingHttpHeaders,
}
export type RouteFunction = (...args: any[]) => Promise<Response>
export type RouteFunction = (params: RequestConfig) => Promise<Response>
export type MethodFactory = (spec: MethodSpec) => RouteFunction
function isRequestConfig (arg: any): arg is RequestConfig {
return typeof arg === 'object'
function extractConfigs (args: { [k: string]: any }): ClientRequestConfig {
return Object.entries(args)
.filter(([key]) => key.startsWith('$'))
.reduce((acc, [key, value]) => ({
...acc,
[key]: value
}), {})
}
function extractPathArguments (args: { [k: string]: any }): { [k: string]: any } {
return Object.entries(args)
.filter(([key]) => !key.startsWith('$'))
.reduce((acc, [key, value]) => ({
...acc,
[key]: value
}), {})
}
export function methodGenerator (client: Client): MethodFactory {
@ -39,9 +70,9 @@ export function methodGenerator (client: Client): MethodFactory { @@ -39,9 +70,9 @@ export function methodGenerator (client: Client): MethodFactory {
const {
path,
method: _method = 'GET',
params = undefined,
body = undefined,
queryString = undefined,
params: paramsSpec = undefined,
body: bodySpec = undefined,
queryString: querySpec = undefined,
contentType = applicationJson,
accept = applicationJson,
headers = {},
@ -57,40 +88,31 @@ export function methodGenerator (client: Client): MethodFactory { @@ -57,40 +88,31 @@ export function methodGenerator (client: Client): MethodFactory {
}
}
return async function (...args: any[]): Promise<Response> {
let length = args.length
return async function (params: RequestConfig = {}): Promise<Response> {
const {
$payload: payload,
$querystring: querystring,
$headers: headers = {},
...args
} = params
let config: RequestConfig = {}
let query = querystring
if (isRequestConfig(args[length - 1])) {
config = args[length - 1]
args = args.slice(0, length - 1)
length -= 1
let options = {
headers,
...extractConfigs(args),
}
let {
queryString: query,
payload,
...options
} = config
ensureValidData(params, args, 'Parameters')
ensureValidData(body, payload, 'Payload')
ensureValidData(queryString, query, 'Query string')
// Regexp here is global we wanna
// match all avaialbel parameters
let paramsCount: number = (path.match(/:[^\/:&?]+/g) || []).length
const pathArguments = extractPathArguments(args)
let fullPath: string = args.reduce((acc, arg) => {
paramsCount -= 1
ensureValidData(paramsSpec, pathArguments, 'Parameters')
ensureValidData(bodySpec, payload, 'Payload')
ensureValidData(querySpec, query, 'Query string')
// Regexp here isn't global we wanna
// match the first parameter only
return acc.replace(/:[^\/:&?]+/, arg)
}, path)
let fullPath = Object.entries(pathArguments)
.reduce((acc, [key, value]) => acc.replace(`:${key}:`, value), path)
if (paramsCount !== 0) throw new Error('Number of provided parameters does not macth request path arguments')
if (fullPath.includes(':')) throw new Error('Provided arguments do not match full arguments required by path')
if (query) {
let attachedQuery
@ -104,7 +126,7 @@ export function methodGenerator (client: Client): MethodFactory { @@ -104,7 +126,7 @@ export function methodGenerator (client: Client): MethodFactory {
fullPath = `${fullPath}?${stringifyQuery(query)}`
}
options = defaults({}, defaultOptions, options)
options = defaults({}, options, defaultOptions)
return client.request(method, fullPath, payload, options)
}

4
test/client.spec.ts

@ -53,7 +53,7 @@ describe('Client', () => { @@ -53,7 +53,7 @@ describe('Client', () => {
expect(requestOptions).to.deep.equal({
baseUrl: "https://dummy.domain",
body: "{}",
body: {},
headers: {},
json: false,
method: "GET",
@ -73,7 +73,7 @@ describe('Client', () => { @@ -73,7 +73,7 @@ describe('Client', () => {
expect(requestOptions).to.deep.equal({
baseUrl: "https://dummy.domain",
body: "{}",
body: {},
headers: { some: 'mock' },
json: false,
method: "GET",

20
test/method.spec.ts

@ -12,7 +12,7 @@ const mockGetMethodNoParams: MethodSpec = { @@ -12,7 +12,7 @@ const mockGetMethodNoParams: MethodSpec = {
}
const mockGetMethodWithParams: MethodSpec = {
path: '/mock-path/:param',
path: '/mock-path/:param:',
accept: 'mock/type',
contentType: 'mock/type',
}
@ -28,7 +28,7 @@ const mockPostMethodNoParams: MethodSpec = { @@ -28,7 +28,7 @@ const mockPostMethodNoParams: MethodSpec = {
}
const mockPostMethodWithParams: MethodSpec = {
path: '/mock-path/:param',
path: '/mock-path/:param:',
method: 'POST',
// TODO: specify body spec
accept: 'mock/type',
@ -36,7 +36,7 @@ const mockPostMethodWithParams: MethodSpec = { @@ -36,7 +36,7 @@ const mockPostMethodWithParams: MethodSpec = {
}
const mockGetMethodWithQueryString: MethodSpec = {
path: '/mock-path/:param',
path: '/mock-path/:param:',
accept: 'mock/type',
contentType: 'mock/type',
queryString: Joi.object({
@ -99,7 +99,7 @@ describe('Method', async () => { @@ -99,7 +99,7 @@ describe('Method', async () => {
it('passes path parameters if specified', async () => {
methodFunction = methodGenerator(mockGetMethodWithParams)
await methodFunction(10)
await methodFunction({ param: 10 })
expect(client.request)
.to.have.been.calledOnceWithExactly(
@ -125,7 +125,7 @@ describe('Method', async () => { @@ -125,7 +125,7 @@ describe('Method', async () => {
methodFunction = methodGenerator(mockPostMethodNoParams)
const payload = { some: { mock: 'payload '} }
await methodFunction({ payload })
await methodFunction({ $payload: payload })
expect(client.request)
.to.have.been.calledOnceWithExactly(
@ -145,7 +145,10 @@ describe('Method', async () => { @@ -145,7 +145,10 @@ describe('Method', async () => {
methodFunction = methodGenerator(mockPostMethodWithParams)
const payload = { some: { mock: 'payload '} }
await methodFunction(10, { payload })
await methodFunction({
param: 10,
$payload: payload,
})
expect(client.request)
.to.have.been.calledOnceWithExactly(
@ -171,7 +174,10 @@ describe('Method', async () => { @@ -171,7 +174,10 @@ describe('Method', async () => {
methodFunction = methodGenerator(mockGetMethodWithQueryString)
const queryString = { some_arg: 'a-string' }
await methodFunction(10, { queryString })
await methodFunction({
param: 10,
$querystring: queryString,
})
expect(client.request)
.to.have.been.calledOnceWithExactly(

Loading…
Cancel
Save