Refactored type definitions (#1119)

* Updated types generation script

* Refactored api method definitions

* Updated test
- Removed old test code
- Added tsd dev dependency
- Rewritten test with tsd

* Removed unused dependencies

* Fixed definition

* Updated test

* Updated docs

* Improved events type definitions

* Updated test

* Minor fixes in the type definitons

* More type test

* Improved Transport type definitions

* Updated test

* Addressed comments

* Code generation

* Use RequestBody, Response and Context everywhere, also default Context to unknown

* Updated test

* body -> hasBody

* Fixed conflicts

* Updated code generation

* Improved request body type definition

* Updated code generation

* Use BodyType for both request and reponses generics
- Use extends for defining the RequestBody generic to force the user
  following the same shape.
- BodyType and NDBodyType now accepts a generics to allow injecting
  more specific types in the future

* API generation

* Updated test

* Updated docs

* Use BodyType also in ReponseError

* Removed useless client generics

* Renamed generics and types
- prefixed all generics with a T
- BodyType => RequestBody
- NDBodyType => RequestNDBody
- Added ResponseBody

* Updated test

* Updated docs

* Test ResponseBody as well

* Simplify overloads

* API generation

* Updated test

* Updated error types
This commit is contained in:
Tomas Della Vedova
2020-03-23 11:38:18 +01:00
committed by GitHub
parent a80f510a9a
commit 6c82a4967e
23 changed files with 3838 additions and 955 deletions

View File

@ -58,7 +58,7 @@ function start (opts) {
{ encoding: 'utf8' }
)
const { fn: factory, types } = genFactory(apiOutputFolder)
const { fn: factory, types } = genFactory(apiOutputFolder, [apiFolder, xPackFolder])
writeFileSync(
mainOutputFile,
factory,

View File

@ -543,3 +543,4 @@ function intersect (first, ...rest) {
}
module.exports = generate
module.exports.ndjsonApi = ndjsonApi

View File

@ -7,10 +7,20 @@
'use strict'
const { readdirSync } = require('fs')
const { join } = require('path')
const dedent = require('dedent')
const deepmerge = require('deepmerge')
const { ndjsonApi } = require('./generateApis')
function genFactory (folder) {
const ndjsonApiKey = ndjsonApi
.map(api => {
return api
.replace(/\.([a-z])/g, k => k[1].toUpperCase())
.replace(/_([a-z])/g, k => k[1].toUpperCase())
})
.map(toPascalCase)
function genFactory (folder, paths) {
// get all the API files
const apiFiles = readdirSync(folder)
const types = apiFiles
@ -25,15 +35,18 @@ function genFactory (folder) {
.split('.')
.reverse()
.reduce((acc, val) => {
const obj = {
[val]: acc === null
? `ApiMethod<RequestParams.${name[0].toUpperCase() + name.slice(1)}>`
: acc
}
if (isSnakeCased(val)) {
obj[camelify(val)] = acc === null
? `ApiMethod<RequestParams.${name[0].toUpperCase() + name.slice(1)}>`
: acc
const body = hasBody(paths, file.slice(0, -3))
const methods = acc === null ? buildMethodDefinition(val, name, body) : null
const obj = {}
if (methods) {
for (const m of methods) {
obj[m.key] = m.val
}
} else {
obj[val] = acc
if (isSnakeCased(val)) {
obj[camelify(val)] = acc
}
}
return obj
}, null)
@ -83,7 +96,7 @@ function genFactory (folder) {
.join('\n')
// remove useless quotes and commas
.replace(/"/g, '')
.replace(/,/g, '')
.replace(/,$/gm, '')
const fn = dedent`
// Licensed to Elasticsearch B.V under one or more agreements.
@ -160,4 +173,64 @@ function isSnakeCased (str) {
return !!~str.indexOf('_')
}
function toPascalCase (str) {
return str[0].toUpperCase() + str.slice(1)
}
function buildMethodDefinition (api, name, hasBody) {
const Name = toPascalCase(name)
const bodyType = ndjsonApiKey.includes(Name) ? 'RequestNDBody' : 'RequestBody'
if (hasBody) {
let methods = [
{ key: `${api}<TRequestBody extends ${bodyType}, TResponse = ResponseBody, TContext = unknown>(params?: RequestParams.${Name}<TRequestBody>, options?: TransportRequestOptions)`, val: `Promise<ApiResponse<TResponse, TContext>>` },
{ key: `${api}<TRequestBody extends ${bodyType}, TResponse = ResponseBody, TContext = unknown>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` },
{ key: `${api}<TRequestBody extends ${bodyType}, TResponse = ResponseBody, TContext = unknown>(params: RequestParams.${Name}<TRequestBody>, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` },
{ key: `${api}<TRequestBody extends ${bodyType}, TResponse = ResponseBody, TContext = unknown>(params: RequestParams.${Name}<TRequestBody>, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }
]
if (isSnakeCased(api)) {
methods = methods.concat([
{ key: `${camelify(api)}<TRequestBody extends ${bodyType}, TResponse = ResponseBody, TContext = unknown>(params?: RequestParams.${Name}<TRequestBody>, options?: TransportRequestOptions)`, val: `Promise<ApiResponse<TResponse, TContext>>` },
{ key: `${camelify(api)}<TRequestBody extends ${bodyType}, TResponse = ResponseBody, TContext = unknown>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` },
{ key: `${camelify(api)}<TRequestBody extends ${bodyType}, TResponse = ResponseBody, TContext = unknown>(params: RequestParams.${Name}<TRequestBody>, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` },
{ key: `${camelify(api)}<TRequestBody extends ${bodyType}, TResponse = ResponseBody, TContext = unknown>(params: RequestParams.${Name}<TRequestBody>, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }
])
}
return methods
} else {
let methods = [
{ key: `${api}<TResponse = ResponseBody, TContext = unknown>(params?: RequestParams.${Name}, options?: TransportRequestOptions)`, val: `Promise<ApiResponse<TResponse, TContext>>` },
{ key: `${api}<TResponse = ResponseBody, TContext = unknown>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` },
{ key: `${api}<TResponse = ResponseBody, TContext = unknown>(params: RequestParams.${Name}, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` },
{ key: `${api}<TResponse = ResponseBody, TContext = unknown>(params: RequestParams.${Name}, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }
]
if (isSnakeCased(api)) {
methods = methods.concat([
{ key: `${camelify(api)}<TResponse = ResponseBody, TContext = unknown>(params?: RequestParams.${Name}, options?: TransportRequestOptions)`, val: `Promise<ApiResponse<TResponse, TContext>>` },
{ key: `${camelify(api)}<TResponse = ResponseBody, TContext = unknown>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` },
{ key: `${camelify(api)}<TResponse = ResponseBody, TContext = unknown>(params: RequestParams.${Name}, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` },
{ key: `${camelify(api)}<TResponse = ResponseBody, TContext = unknown>(params: RequestParams.${Name}, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }
])
}
return methods
}
}
function hasBody (paths, file) {
const spec = readSpec()
return !!spec[file].body
function readSpec () {
try {
return require(join(paths[0], file))
} catch (err) {}
try {
return require(join(paths[1], file))
} catch (err) {}
throw new Error(`Cannot read spec file ${file}`)
}
}
module.exports = genFactory

View File

@ -6,6 +6,15 @@
const semver = require('semver')
const deprecatedParameters = require('./patch.json')
const { ndjsonApi } = require('./generateApis')
const ndjsonApiKey = ndjsonApi
.map(api => {
return api
.replace(/\.([a-z])/g, k => k[1].toUpperCase())
.replace(/_([a-z])/g, k => k[1].toUpperCase())
})
.map(toPascalCase)
function generate (version, api) {
const release = semver.valid(version) ? semver.major(version) : version
@ -13,6 +22,8 @@ function generate (version, api) {
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information
import { RequestBody, RequestNDBody } from '../lib/Transport'
export interface Generic {
method?: string;
ignore?: number | number[];
@ -83,8 +94,10 @@ export interface Generic {
return `${e.key}${optional}: ${getType(e.value.type, e.value.options)};`
}
const bodyGeneric = ndjsonApiKey.includes(toPascalCase(name)) ? 'RequestNDBody' : 'RequestBody'
const code = `
export interface ${name[0].toUpperCase() + name.slice(1)}${body ? '<T = any>' : ''} extends Generic {
export interface ${toPascalCase(name)}${body ? `<T = ${bodyGeneric}>` : ''} extends Generic {
${partsArr.map(genLine).join('\n ')}
${paramsArr.map(genLine).join('\n ')}
${body ? `body${body.required ? '' : '?'}: T;` : ''}
@ -122,4 +135,8 @@ function intersect (first, ...rest) {
}, first)
}
function toPascalCase (str) {
return str[0].toUpperCase() + str.slice(1)
}
module.exports = generate