From 6dbaa3fd604bdc86371a20e1948ca0b3a97c64bb Mon Sep 17 00:00:00 2001 From: anymaniax Date: Wed, 6 Jul 2022 16:15:45 +0200 Subject: [PATCH] feat(core): add possibility to generate headers --- .../pages/reference/configuration/output.md | 18 ++++++++- src/core/generators/angular.ts | 3 ++ src/core/generators/api.ts | 5 ++- src/core/generators/axios.ts | 3 ++ src/core/generators/imports.ts | 2 + src/core/generators/options.ts | 34 +++++++++++++--- src/core/generators/query.ts | 36 ++++++++++++----- src/core/generators/swr.ts | 39 ++++++++++++------ src/core/generators/verbsOptions.ts | 12 +++++- src/core/getters/parameters.ts | 9 ++++- src/core/getters/props.ts | 21 +++++++++- src/core/getters/queryParams.ts | 4 +- src/types/generator.ts | 1 + src/types/getters.ts | 6 ++- src/types/index.ts | 2 + src/utils/options.ts | 1 + tests/configs/react-query.config.ts | 2 + tests/specifications/petstore.yaml | 40 +++++++++++++++++++ 18 files changed, 200 insertions(+), 38 deletions(-) diff --git a/docs/src/pages/reference/configuration/output.md b/docs/src/pages/reference/configuration/output.md index dec9014fb..aa035a8cc 100644 --- a/docs/src/pages/reference/configuration/output.md +++ b/docs/src/pages/reference/configuration/output.md @@ -271,11 +271,25 @@ Default Value: `false`. Can be used to specify `tslint` ([TSLint is deprecated in favour of eslint + plugins](https://github.com/palantir/tslint#tslint)) as typescript linter instead of `eslint`. You need to have tslint in your dependencies. -### tsconfig +### headers Type: `Boolean`. -Can be used to specify the path to your `tsconfig`. +Use to enable the generation of the headers + +### tsconfig + +Type: `String | Tsconfig`. + +Should be automatically found and transparent for you. +Can be used to specify the path to your `tsconfig` or directly your config. + +### packageJson + +Type: `String`. + +Should be automatically found and transparent for you. +Can be used to specify the path to your `package.json`. ### override diff --git a/src/core/generators/angular.ts b/src/core/generators/angular.ts index 718ba5d76..956a8ba90 100644 --- a/src/core/generators/angular.ts +++ b/src/core/generators/angular.ts @@ -106,6 +106,7 @@ export const generateAngularFooter = () => const generateImplementation = ( { + headers, queryParams, operationName, response, @@ -146,6 +147,7 @@ const generateImplementation = ( const mutatorConfig = generateMutatorConfig({ route, body, + headers, queryParams, response, verb, @@ -182,6 +184,7 @@ const generateImplementation = ( const options = generateOptions({ route, body, + headers, queryParams, response, verb, diff --git a/src/core/generators/api.ts b/src/core/generators/api.ts index 802d26201..ec16a58e7 100644 --- a/src/core/generators/api.ts +++ b/src/core/generators/api.ts @@ -46,10 +46,13 @@ export const generateApi = async ({ }); const schemas = verbsOptions.reduce( - (acc, { queryParams, body, response }) => { + (acc, { queryParams, headers, body, response }) => { if (queryParams) { acc.push(queryParams.schema, ...queryParams.deps); } + if (headers) { + acc.push(headers.schema, ...headers.deps); + } acc.push(...body.schemas); acc.push(...response.schemas); diff --git a/src/core/generators/axios.ts b/src/core/generators/axios.ts index 03b378de3..bf2fc5bac 100644 --- a/src/core/generators/axios.ts +++ b/src/core/generators/axios.ts @@ -41,6 +41,7 @@ export const getAxiosDependencies = (hasGlobalMutator: boolean) => [ const generateAxiosImplementation = ( { + headers, queryParams, operationName, response, @@ -78,6 +79,7 @@ const generateAxiosImplementation = ( const mutatorConfig = generateMutatorConfig({ route, body, + headers, queryParams, response, verb, @@ -125,6 +127,7 @@ const generateAxiosImplementation = ( const options = generateOptions({ route, body, + headers, queryParams, response, verb, diff --git a/src/core/generators/imports.ts b/src/core/generators/imports.ts index 0c48e4e19..90aa1e8ae 100644 --- a/src/core/generators/imports.ts +++ b/src/core/generators/imports.ts @@ -247,10 +247,12 @@ export const generateVerbImports = ({ response, body, queryParams, + headers, params, }: GeneratorVerbOptions): GeneratorImport[] => [ ...response.imports, ...body.imports, ...(queryParams ? [{ name: queryParams.schema.name }] : []), + ...(headers ? [{ name: headers.schema.name }] : []), ...params.flatMap(({ imports }) => imports), ]; diff --git a/src/core/generators/options.ts b/src/core/generators/options.ts index c789a35c0..2010ec3c6 100644 --- a/src/core/generators/options.ts +++ b/src/core/generators/options.ts @@ -32,17 +32,24 @@ export const generateBodyOptions = ( export const generateAxiosOptions = ( response: GetterResponse, queryParams?: GeneratorSchema, + headers?: GeneratorSchema, requestOptions?: object | boolean, ) => { const isRequestOptions = requestOptions !== false; - if (!queryParams && !response.isBlob) { + if (!queryParams && !headers && !response.isBlob) { return isRequestOptions ? 'options' : ''; } let value = ''; - if (queryParams) { - value += '\n params,'; + if (!isRequestOptions) { + if (queryParams) { + value += '\n params,'; + } + + if (headers) { + value += '\n headers,'; + } } if ( @@ -58,7 +65,15 @@ export const generateAxiosOptions = ( } if (isRequestOptions) { - value += '\n ...options'; + value += '\n ...options,'; + + if (queryParams) { + value += '\n params: {...params, ...options?.params},'; + } + + if (headers) { + value += '\n headers: {...headers, ...options?.headers},'; + } } return value; @@ -67,6 +82,7 @@ export const generateAxiosOptions = ( export const generateOptions = ({ route, body, + headers, queryParams, response, verb, @@ -77,6 +93,7 @@ export const generateOptions = ({ }: { route: string; body: GetterBody; + headers?: GetterQueryParam; queryParams?: GetterQueryParam; response: GetterResponse; verb: Verbs; @@ -93,6 +110,7 @@ export const generateOptions = ({ const axiosOptions = generateAxiosOptions( response, queryParams?.schema, + headers?.schema, requestOptions, ); @@ -159,6 +177,7 @@ export const generateQueryParamsAxiosConfig = ( export const generateMutatorConfig = ({ route, body, + headers, queryParams, response, verb, @@ -170,6 +189,7 @@ export const generateMutatorConfig = ({ }: { route: string; body: GetterBody; + headers?: GetterQueryParam; queryParams?: GetterQueryParam; response: GetterResponse; verb: Verbs; @@ -189,7 +209,11 @@ export const generateMutatorConfig = ({ ); const headerOptions = body.contentType - ? `,\n headers: {'Content-Type': '${body.contentType}'}` + ? `,\n headers: {'Content-Type': '${body.contentType}', ${ + headers ? '...headers' : '' + }}` + : headers + ? ',\n headers' : ''; return `{url: \`${route}\`, method: '${verb}'${headerOptions}${bodyOptions}${queryParamsOptions}${ diff --git a/src/core/generators/query.ts b/src/core/generators/query.ts index 0a884d6c7..18f5e8de2 100644 --- a/src/core/generators/query.ts +++ b/src/core/generators/query.ts @@ -127,6 +127,7 @@ export const getVueQueryDependencies = (hasGlobalMutator: boolean) => [ const generateQueryRequestFunction = ( { + headers, queryParams, operationName, response, @@ -163,6 +164,7 @@ const generateQueryRequestFunction = ( const mutatorConfig = generateMutatorConfig({ route, body, + headers, queryParams, response, verb, @@ -224,6 +226,7 @@ const generateQueryRequestFunction = ( const options = generateOptions({ route, body, + headers, queryParams, response, verb, @@ -422,9 +425,9 @@ const getHookOptions = ({ const generateQueryImplementation = ({ queryOption: { name, queryParam, options, type }, operationName, - queryProps, queryKeyFnName, - properties, + queryProperties, + queryKeyProperties, params, props, mutator, @@ -441,9 +444,9 @@ const generateQueryImplementation = ({ }; isRequestOptions: boolean; operationName: string; - queryProps: string; queryKeyFnName: string; - properties: string; + queryProperties: string; + queryKeyProperties: string; params: GetterParams; props: GetterProps; response: GetterResponse; @@ -451,13 +454,14 @@ const generateQueryImplementation = ({ outputClient: OutputClient | OutputClientFunc; isExactOptionalPropertyTypes: boolean; }) => { + const queryProps = toObjectString(props, 'implementation'); const httpFunctionProps = queryParam ? props .map(({ name }) => name === 'params' ? `{ ${queryParam}: pageParam, ...params }` : name, ) .join(',') - : properties; + : queryProperties; const returnType = generateQueryReturnType({ outputClient, @@ -511,7 +515,7 @@ export const ${camel( ${hookOptions} - const queryKey = queryOptions?.queryKey ?? ${queryKeyFnName}(${properties}); + const queryKey = queryOptions?.queryKey ?? ${queryKeyFnName}(${queryKeyProperties}); ${ mutator?.isHook @@ -574,7 +578,14 @@ const generateQueryHook = ( operationQueryOptions?.useInfinite || operationQueryOptions?.useQuery ) { - const properties = props + const queryProperties = props + .map(({ name, type }) => + type === GetterPropType.BODY ? body.implementation : name, + ) + .join(','); + + const queryKeyProperties = props + .filter((prop) => prop.type !== GetterPropType.HEADER) .map(({ name, type }) => type === GetterPropType.BODY ? body.implementation : name, ) @@ -603,9 +614,12 @@ const generateQueryHook = ( ]; const queryKeyFnName = camel(`get-${operationName}-queryKey`); - const queryProps = toObjectString(props, 'implementation'); + const queryKeyProps = toObjectString( + props.filter((prop) => prop.type !== GetterPropType.HEADER), + 'implementation', + ); - return `export const ${queryKeyFnName} = (${queryProps}) => [\`${route}\`${ + return `export const ${queryKeyFnName} = (${queryKeyProps}) => [\`${route}\`${ queryParams ? ', ...(params ? [params]: [])' : '' }${body.implementation ? `, ${body.implementation}` : ''}]; @@ -615,9 +629,9 @@ const generateQueryHook = ( generateQueryImplementation({ queryOption, operationName, - queryProps, queryKeyFnName, - properties, + queryProperties, + queryKeyProperties, params, props, mutator, diff --git a/src/core/generators/swr.ts b/src/core/generators/swr.ts index 519b5e44c..f030b2af7 100644 --- a/src/core/generators/swr.ts +++ b/src/core/generators/swr.ts @@ -58,6 +58,7 @@ export const getSwrDependencies = (hasGlobalMutator: boolean) => [ const generateSwrRequestFunction = ( { + headers, queryParams, operationName, response, @@ -94,6 +95,7 @@ const generateSwrRequestFunction = ( const mutatorConfig = generateMutatorConfig({ route, body, + headers, queryParams, response, verb, @@ -129,6 +131,7 @@ const generateSwrRequestFunction = ( const options = generateOptions({ route, body, + headers, queryParams, response, verb, @@ -178,32 +181,34 @@ const generateSwrArguments = ({ const generateSwrImplementation = ({ operationName, - swrProps, swrKeyFnName, - properties, + swrProperties, + swrKeyProperties, params, mutator, isRequestOptions, response, swrOptions, + props, }: { isRequestOptions: boolean; operationName: string; - swrProps: string; swrKeyFnName: string; - properties: string; + swrProperties: string; + swrKeyProperties: string; params: GetterParams; props: GetterProps; response: GetterResponse; mutator?: GeneratorMutator; swrOptions: { options?: any }; }) => { - const httpFunctionProps = properties; + const swrProps = toObjectString(props, 'implementation'); + const httpFunctionProps = swrProperties; const swrKeyImplementation = params.length ? `const isEnable = !!(${params.map(({ name }) => name).join(' && ')}) - const swrKey = swrOptions?.swrKey ?? (() => isEnable ? ${swrKeyFnName}(${properties}) : null);` - : `const swrKey = swrOptions?.swrKey ?? (() => ${swrKeyFnName}(${properties}))`; + const swrKey = swrOptions?.swrKey ?? (() => isEnable ? ${swrKeyFnName}(${swrKeyProperties}) : null);` + : `const swrKey = swrOptions?.swrKey ?? (() => ${swrKeyFnName}(${swrKeyProperties}))`; let errorType = `AxiosError<${response.definition.errors || 'unknown'}>`; @@ -288,24 +293,34 @@ const generateSwrHook = ( return ''; } - const properties = props + const swrProperties = props + .map(({ name, type }) => + type === GetterPropType.BODY ? body.implementation : name, + ) + .join(','); + + const swrKeyProperties = props + .filter((prop) => prop.type !== GetterPropType.HEADER) .map(({ name, type }) => type === GetterPropType.BODY ? body.implementation : name, ) .join(','); const swrKeyFnName = camel(`get-${operationName}-key`); - const swrProps = toObjectString(props, 'implementation'); + const queryKeyProps = toObjectString( + props.filter((prop) => prop.type !== GetterPropType.HEADER), + 'implementation', + ); - return `export const ${swrKeyFnName} = (${swrProps}) => [\`${route}\`${ + return `export const ${swrKeyFnName} = (${queryKeyProps}) => [\`${route}\`${ queryParams ? ', ...(params ? [params]: [])' : '' }${body.implementation ? `, ${body.implementation}` : ''}]; ${generateSwrImplementation({ operationName, - swrProps, swrKeyFnName, - properties, + swrProperties, + swrKeyProperties, params, props, mutator, diff --git a/src/core/generators/verbsOptions.ts b/src/core/generators/verbsOptions.ts index 657db78bb..5900b9ce9 100644 --- a/src/core/generators/verbsOptions.ts +++ b/src/core/generators/verbsOptions.ts @@ -94,6 +94,15 @@ const generateVerbOptions = async ({ context, }); + const headers = output.headers + ? await getQueryParams({ + queryParams: parameters.header, + operationName, + context, + suffix: 'headers', + }) + : undefined; + const params = await getParams({ route, pathParams: parameters.path, @@ -101,7 +110,7 @@ const generateVerbOptions = async ({ context, }); - const props = getProps({ body, queryParams, params }); + const props = getProps({ body, queryParams, params, headers }); const mutator = await generateMutator({ output: output.target, @@ -143,6 +152,7 @@ const generateVerbOptions = async ({ operationName, response, body, + headers, queryParams, params, props, diff --git a/src/core/getters/parameters.ts b/src/core/getters/parameters.ts index 4628a5866..6c7080c25 100644 --- a/src/core/getters/parameters.ts +++ b/src/core/getters/parameters.ts @@ -19,11 +19,15 @@ export const getParameters = async ({ const { schema: parameter, imports } = await resolveRef(p, context); - if (parameter.in === 'path' || parameter.in === 'query') { + if ( + parameter.in === 'path' || + parameter.in === 'query' || + parameter.in === 'header' + ) { acc[parameter.in].push({ parameter, imports }); } } else { - if (p.in === 'query' || p.in === 'path') { + if (p.in === 'query' || p.in === 'path' || p.in === 'header') { acc[p.in].push({ parameter: p, imports: [] }); } } @@ -33,6 +37,7 @@ export const getParameters = async ({ { path: [], query: [], + header: [], } as GetterParameters, ); }; diff --git a/src/core/getters/props.ts b/src/core/getters/props.ts index b2bb9a802..93bfc4390 100644 --- a/src/core/getters/props.ts +++ b/src/core/getters/props.ts @@ -5,16 +5,19 @@ import { GetterPropType, GetterQueryParam, } from '../../types/getters'; +import { isUndefined } from '../../utils/is'; import { sortByPriority } from '../../utils/sort'; export const getProps = ({ body, queryParams, params, + headers, }: { body: GetterBody; queryParams?: GetterQueryParam; params: GetterParams; + headers?: GetterQueryParam; }): GetterProps => { const bodyProp = { name: body.implementation, @@ -34,14 +37,30 @@ export const getProps = ({ queryParams?.schema.name }`, default: false, - required: false, + required: !isUndefined(queryParams?.isOptional) + ? !queryParams?.isOptional + : false, type: GetterPropType.QUERY_PARAM, }; + const headersProp = { + name: 'headers', + definition: `headers${headers?.isOptional ? '?' : ''}: ${ + headers?.schema.name + }`, + implementation: `headers${headers?.isOptional ? '?' : ''}: ${ + headers?.schema.name + }`, + default: false, + required: !isUndefined(headers?.isOptional) ? !headers?.isOptional : false, + type: GetterPropType.HEADER, + }; + const props = [ ...params.map((param) => ({ ...param, type: GetterPropType.PARAM })), ...(body.definition ? [bodyProp] : []), ...(queryParams ? [queryParamsProp] : []), + ...(headers ? [headersProp] : []), ]; const sortedProps = sortByPriority(props); diff --git a/src/core/getters/queryParams.ts b/src/core/getters/queryParams.ts index 5677a451a..6e48bc39e 100644 --- a/src/core/getters/queryParams.ts +++ b/src/core/getters/queryParams.ts @@ -76,10 +76,12 @@ export const getQueryParams = async ({ queryParams = [], operationName, context, + suffix = 'params', }: { queryParams: GetterParameters['query']; operationName: string; context: ContextSpecs; + suffix?: string; }): Promise => { if (!queryParams.length) { return; @@ -87,7 +89,7 @@ export const getQueryParams = async ({ const types = await getQueryParamsTypes(queryParams, operationName, context); const imports = types.flatMap(({ imports }) => imports); const schemas = types.flatMap(({ schemas }) => schemas); - const name = `${pascal(operationName)}Params`; + const name = `${pascal(operationName)}${pascal(suffix)}`; const type = types.map(({ definition }) => definition).join('; '); const allOptional = queryParams.every(({ parameter }) => !parameter.required); diff --git a/src/types/generator.ts b/src/types/generator.ts index fa7587975..f2d72b0e6 100644 --- a/src/types/generator.ts +++ b/src/types/generator.ts @@ -87,6 +87,7 @@ export type GeneratorVerbOptions = { operationName: string; response: GetterResponse; body: GetterBody; + headers?: GetterQueryParam; queryParams?: GetterQueryParam; params: GetterParams; props: GetterProps; diff --git a/src/types/getters.ts b/src/types/getters.ts index 1bf23ae74..83ef45cb1 100644 --- a/src/types/getters.ts +++ b/src/types/getters.ts @@ -30,6 +30,7 @@ export type GetterBody = { export type GetterParameters = { query: { parameter: ParameterObject; imports: GeneratorImport[] }[]; path: { parameter: ParameterObject; imports: GeneratorImport[] }[]; + header: { parameter: ParameterObject; imports: GeneratorImport[] }[]; }; export type GetterParam = { @@ -48,12 +49,13 @@ export type GetterQueryParam = { isOptional: boolean; }; -export type GetterPropType = 'param' | 'body' | 'queryParam'; +export type GetterPropType = 'param' | 'body' | 'queryParam' | 'header'; export const GetterPropType = { PARAM: 'param' as GetterPropType, BODY: 'body' as GetterPropType, QUERY_PARAM: 'queryParam' as GetterPropType, + HEADER: 'header' as GetterPropType, }; export type GetterProp = { @@ -62,7 +64,7 @@ export type GetterProp = { implementation: string; default: boolean; required: boolean; - type: 'param' | 'body' | 'queryParam'; + type: GetterPropType; }; export type GetterProps = GetterProp[]; diff --git a/src/types/index.ts b/src/types/index.ts index c3c041e4b..eea9a47c3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -47,6 +47,7 @@ export type NormalizedOutputOptions = { tslint: boolean; tsconfig?: Tsconfig; packageJson?: PackageJson; + headers: boolean; }; export type NormalizedOverrideOutput = { @@ -147,6 +148,7 @@ export type OutputOptions = { tslint?: boolean; tsconfig?: string | Tsconfig; packageJson?: string; + headers?: boolean; }; export type SwaggerParserOptions = Omit & { diff --git a/src/utils/options.ts b/src/utils/options.ts index bb0952dca..b2594ee94 100644 --- a/src/utils/options.ts +++ b/src/utils/options.ts @@ -111,6 +111,7 @@ export const normalizeOptions = async ( tslint: outputOptions.tslint ?? tslint ?? false, tsconfig, packageJson, + headers: outputOptions.headers ?? false, override: { ...outputOptions.override, operations: normalizeOperationsAndTags( diff --git a/tests/configs/react-query.config.ts b/tests/configs/react-query.config.ts index a8da31e25..aae4610ce 100644 --- a/tests/configs/react-query.config.ts +++ b/tests/configs/react-query.config.ts @@ -7,6 +7,7 @@ export default defineConfig({ schemas: '../generated/react-query/basic/model', client: 'react-query', mock: true, + headers: true, }, input: { target: '../specifications/petstore.yaml', @@ -43,6 +44,7 @@ export default defineConfig({ schemas: '../generated/react-query/mutator-client/model', client: 'react-query', mock: true, + headers: true, override: { mutator: { path: '../mutators/custom-client.ts', diff --git a/tests/specifications/petstore.yaml b/tests/specifications/petstore.yaml index e85485b64..4428584c2 100644 --- a/tests/specifications/petstore.yaml +++ b/tests/specifications/petstore.yaml @@ -33,6 +33,16 @@ paths: - -name - email - -email + - description: Header parameters + in: header + name: X-EXAMPLE + required: true + schema: + type: string + enum: + - ONE + - TWO + - THREE responses: '200': description: A paged array of pets @@ -56,6 +66,36 @@ paths: operationId: createPets tags: - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: string + - name: sort + in: query + description: | + Which property to sort by? + Example: name sorts ASC while -name sorts DESC. + required: true + schema: + type: string + enum: + - name + - -name + - email + - -email + - description: Header parameters + in: header + name: X-EXAMPLE + required: true + schema: + type: string + enum: + - ONE + - TWO + - THREE requestBody: required: true content: