-
Notifications
You must be signed in to change notification settings - Fork 726
Added Request/Response types definitions #970
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ed96a9d
fd70174
0892ecb
9380adc
511db34
14d6401
9f04d26
36cb500
08b2827
e34aa55
d53b68c
b083db6
e82cfe3
728254b
911e883
5c6131d
1673020
41529a4
acc9346
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,132 @@ | ||||||
// Licensed to Elasticsearch B.V under one or more agreements. | ||||||
// 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 | ||||||
|
||||||
interface SearchResponse<TSource = unknown, TAggregations = unknown> { | ||||||
took: number | ||||||
timed_out: boolean | ||||||
_scroll_id?: string | ||||||
_shards: Shards | ||||||
hits: { | ||||||
total: { | ||||||
value: number | ||||||
relation: string | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm surprised that we are able to generate request types from the API spec, but no responses. https://github.com/elastic/elasticsearch/blob/d7bbc9df1d3edf47f8d1c2f0983c88c64d78a4b4/rest-api-spec/src/main/resources/rest-api-spec/api/search.json#L231 |
||||||
} | ||||||
max_score: number | ||||||
hits: Array<{ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @delvedor I'd suggest extracting this inline object literal type into an interface. I in my code usually use not only the interface Doc<TSource> {
_id: string;
_source: TSource;
} If we don't extract this into an interface we won't be able to write the following code: interface MyDocSource { foo: number }
// The type of hit should be Doc<MyDocSource> but we don't have the Doc type as per this PR right now
function processHit(hit: ??) {
console.log("doc with id", hit._id, "has foo:", hit._source.foo);
}
const res = await client.search<MyDocSource>({ ... });
// notice that we cannot annotate the docHit param type with an interface name here too
res.body.hits.hits.forEach(docHit => processHit(docHit)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 here. I hit this problem a couple of days ago. An example from Kibana codebase: |
||||||
_index: string | ||||||
_type: string | ||||||
_id: string | ||||||
_score: number | ||||||
_source: TSource | ||||||
_version?: number | ||||||
_explanation?: Explanation | ||||||
fields?: Record<string, unknown> | ||||||
highlight?: Record<string, string[]> | ||||||
inner_hits?: Record<string, unknown> | ||||||
matched_queries?: string[] | ||||||
sort?: any[] | ||||||
}> | ||||||
} | ||||||
aggregations?: TAggregations | ||||||
} | ||||||
|
||||||
interface Shards { | ||||||
total: number | ||||||
successful: number | ||||||
failed: number | ||||||
skipped: number | ||||||
} | ||||||
|
||||||
interface Explanation { | ||||||
value: number | ||||||
description: string | ||||||
details: Explanation[] | ||||||
} | ||||||
|
||||||
interface MSearchResponse<TSource = unknown, TAggregations = unknown> { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't see it's used in |
||||||
responses: Array<SearchResponse<TSource, TAggregations>> | ||||||
} | ||||||
|
||||||
interface CreateResponse { | ||||||
_shards: Shards | ||||||
_index: string | ||||||
_type: string | ||||||
_id: string | ||||||
_version: number | ||||||
_seq_no: number | ||||||
_primary_term: number | ||||||
result: string | ||||||
} | ||||||
|
||||||
interface IndexResponse extends CreateResponse {} | ||||||
|
||||||
interface DeleteResponse { | ||||||
_shards: Shards | ||||||
_index: string | ||||||
_type: string | ||||||
_id: string | ||||||
_version: number | ||||||
_seq_no: number | ||||||
_primary_term: number | ||||||
result: string | ||||||
} | ||||||
|
||||||
interface UpdateResponse { | ||||||
_shards: Shards | ||||||
_index: string | ||||||
_type: string | ||||||
_id: string | ||||||
_version: number | ||||||
result: string | ||||||
} | ||||||
Comment on lines
+75
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Elasticsearch 7 supports returning the document source in update requests, e.g. the following request POST /<index>/_update/<id>
{
"_source": "*",
"doc": {
"field": "val"
}
} gives the following response {
"_index" : "<index>",
"_type" : "_doc",
"_id" : "<id>",
"_version" : 5,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 53,
"_primary_term" : 1,
"get" : {
"_seq_no" : 53,
"_primary_term" : 1,
"found" : true,
"_source" : {
"field": "val"
}
}
} So the user should be able to specify the type of that @delvedor also note that the shards object I got from Elasticsearch doesn't contain Also note that there are two more fields: {
"_seq_no" : 53,
"_primary_term" : 1
} I think they also should be included in |
||||||
|
||||||
interface GetResponse<TSource = unknown> { | ||||||
_index: string | ||||||
_type: string | ||||||
_id: string | ||||||
_version: number | ||||||
_seq_no: number | ||||||
_primary_term: number | ||||||
found: boolean | ||||||
_source: TSource | ||||||
} | ||||||
|
||||||
interface BulkResponse { | ||||||
took: number | ||||||
errors: boolean | ||||||
items: BulkItem[] | ||||||
} | ||||||
|
||||||
type BulkItem = | ||||||
| { index: BulkIndex } | ||||||
| { create: BulkCreate } | ||||||
| { update: BulkUpdate } | ||||||
| { delete: BulkDelete } | ||||||
|
||||||
interface BulkIndex extends IndexResponse { | ||||||
status: number | ||||||
} | ||||||
|
||||||
interface BulkCreate extends CreateResponse { | ||||||
status: number | ||||||
} | ||||||
|
||||||
interface BulkUpdate extends UpdateResponse { | ||||||
status: number | ||||||
} | ||||||
|
||||||
interface BulkDelete extends DeleteResponse { | ||||||
status: number | ||||||
} | ||||||
|
||||||
export { | ||||||
SearchResponse, | ||||||
MSearchResponse, | ||||||
CreateResponse, | ||||||
IndexResponse, | ||||||
DeleteResponse, | ||||||
UpdateResponse, | ||||||
GetResponse, | ||||||
BulkResponse | ||||||
} | ||||||
delvedor marked this conversation as resolved.
Show resolved
Hide resolved
|
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,17 @@ const dedent = require('dedent') | |
const deepmerge = require('deepmerge') | ||
const { ndjsonApi } = require('./generateApis') | ||
|
||
const supportedResponses = [ | ||
'Search', | ||
'MSearch', | ||
'Create', | ||
'Index', | ||
'Delete', | ||
'Update', | ||
'Get', | ||
'Bulk' | ||
] | ||
|
||
const ndjsonApiKey = ndjsonApi | ||
.map(api => { | ||
return api | ||
|
@@ -220,48 +231,49 @@ function buildMethodDefinition (opts, api, name, hasBody) { | |
const Name = toPascalCase(name) | ||
const bodyType = ndjsonApiKey.includes(Name) ? 'RequestNDBody' : 'RequestBody' | ||
const defaultBodyType = ndjsonApiKey.includes(Name) ? 'Record<string, any>[]' : 'Record<string, any>' | ||
const responseType = supportedResponses.includes(Name) ? `Res.${Name}Response` : 'Record<string, any>' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. optional: why not export responses used |
||
|
||
if (opts.kibana) { | ||
if (hasBody) { | ||
return [ | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params?: RequestParams.${Name}<TRequestBody>, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` } | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params?: Req.${Name}Request<TRequestBody>, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` } | ||
] | ||
} else { | ||
return [ | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TContext = Context>(params?: RequestParams.${Name}, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` } | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TContext = Context>(params?: Req.${Name}Request, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` } | ||
] | ||
} | ||
} | ||
|
||
if (hasBody) { | ||
let methods = [ | ||
{ key: `${api}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params?: RequestParams.${Name}<TRequestBody>, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }, | ||
{ key: `${api}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${api}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params: RequestParams.${Name}<TRequestBody>, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${api}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params: RequestParams.${Name}<TRequestBody>, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` } | ||
{ key: `${api}<TResponse = ${responseType}, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params?: Req.${Name}Request<TRequestBody>, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }, | ||
{ key: `${api}<TResponse = ${responseType}, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${api}<TResponse = ${responseType}, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params: Req.${Name}Request<TRequestBody>, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${api}<TResponse = ${responseType}, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params: Req.${Name}Request<TRequestBody>, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` } | ||
] | ||
if (isSnakeCased(api)) { | ||
methods = methods.concat([ | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params?: RequestParams.${Name}<TRequestBody>, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }, | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params: RequestParams.${Name}<TRequestBody>, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params: RequestParams.${Name}<TRequestBody>, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` } | ||
{ key: `${camelify(api)}<TResponse = ${responseType}, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params?: Req.${Name}Request<TRequestBody>, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }, | ||
{ key: `${camelify(api)}<TResponse = ${responseType}, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${camelify(api)}<TResponse = ${responseType}, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params: Req.${Name}Request<TRequestBody>, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${camelify(api)}<TResponse = ${responseType}, TRequestBody extends ${bodyType} = ${defaultBodyType}, TContext = Context>(params: Req.${Name}Request<TRequestBody>, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` } | ||
]) | ||
} | ||
return methods | ||
} else { | ||
let methods = [ | ||
{ key: `${api}<TResponse = Record<string, any>, TContext = Context>(params?: RequestParams.${Name}, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }, | ||
{ key: `${api}<TResponse = Record<string, any>, TContext = Context>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${api}<TResponse = Record<string, any>, TContext = Context>(params: RequestParams.${Name}, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${api}<TResponse = Record<string, any>, TContext = Context>(params: RequestParams.${Name}, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` } | ||
{ key: `${api}<TResponse = ${responseType}, TContext = Context>(params?: Req.${Name}Request, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }, | ||
{ key: `${api}<TResponse = ${responseType}, TContext = Context>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${api}<TResponse = ${responseType}, TContext = Context>(params: Req.${Name}Request, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${api}<TResponse = ${responseType}, TContext = Context>(params: Req.${Name}Request, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` } | ||
] | ||
if (isSnakeCased(api)) { | ||
methods = methods.concat([ | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TContext = Context>(params?: RequestParams.${Name}, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }, | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TContext = Context>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TContext = Context>(params: RequestParams.${Name}, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${camelify(api)}<TResponse = Record<string, any>, TContext = Context>(params: RequestParams.${Name}, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` } | ||
{ key: `${camelify(api)}<TResponse = ${responseType}, TContext = Context>(params?: Req.${Name}Request, options?: TransportRequestOptions)`, val: `TransportRequestPromise<ApiResponse<TResponse, TContext>>` }, | ||
{ key: `${camelify(api)}<TResponse = ${responseType}, TContext = Context>(callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${camelify(api)}<TResponse = ${responseType}, TContext = Context>(params: Req.${Name}Request, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` }, | ||
{ key: `${camelify(api)}<TResponse = ${responseType}, TContext = Context>(params: Req.${Name}Request, options: TransportRequestOptions, callback: callbackFn<TResponse, TContext>)`, val: `TransportRequestCallback` } | ||
]) | ||
} | ||
return methods | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,14 +17,15 @@ const ndjsonApiKey = ndjsonApi | |
.map(toPascalCase) | ||
|
||
function generate (version, api) { | ||
var exportNewLineCount = 0 | ||
const release = semver.valid(version) ? semver.major(version) : version | ||
var types = `// Licensed to Elasticsearch B.V under one or more agreements. | ||
// 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 { | ||
interface GenericRequest { | ||
method?: string; | ||
filter_path?: string | string[]; | ||
pretty?: boolean; | ||
|
@@ -35,6 +36,11 @@ export interface Generic { | |
` | ||
|
||
api.forEach(generateRequestType) | ||
types += '\nexport {\n' | ||
api.forEach(generateExport) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// removes last comma | ||
types = types.slice(0, -2) + '\n' | ||
types += '}' | ||
return types | ||
|
||
function generateRequestType (spec) { | ||
|
@@ -96,7 +102,7 @@ export interface Generic { | |
const bodyGeneric = ndjsonApiKey.includes(toPascalCase(name)) ? 'RequestNDBody' : 'RequestBody' | ||
|
||
const code = ` | ||
export interface ${toPascalCase(name)}${body ? `<T = ${bodyGeneric}>` : ''} extends Generic { | ||
interface ${toPascalCase(name)}Request${body ? `<T = ${bodyGeneric}>` : ''} extends GenericRequest { | ||
${partsArr.map(genLine).join('\n ')} | ||
${paramsArr.map(genLine).join('\n ')} | ||
${body ? `body${body.required ? '' : '?'}: T;` : ''} | ||
|
@@ -152,6 +158,18 @@ export interface ${toPascalCase(name)}${body ? `<T = ${bodyGeneric}>` : ''} exte | |
return type | ||
} | ||
} | ||
|
||
function generateExport (spec) { | ||
const api = Object.keys(spec)[0] | ||
const name = api | ||
.replace(/\.([a-z])/g, k => k[1].toUpperCase()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We repeat the same operation across several |
||
.replace(/_([a-z])/g, k => k[1].toUpperCase()) | ||
types += exportNewLineCount === 0 ? ' ' : '' | ||
types += toPascalCase(name) + 'Request,' | ||
types += exportNewLineCount < 3 ? ' ' : '\n' | ||
exportNewLineCount += 1 | ||
if (exportNewLineCount > 3) exportNewLineCount = 0 | ||
} | ||
} | ||
|
||
function intersect (first, ...rest) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not meant to be used in
api/kibana.d.ts
?