Skip to content

Commit

Permalink
fix: handle application/json content type in parameter definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos committed May 23, 2024
1 parent 0ac9401 commit 93adfc3
Show file tree
Hide file tree
Showing 21 changed files with 193 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/olive-spies-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': patch
---

fix: handle application/json content type in parameter definitions
38 changes: 29 additions & 9 deletions packages/openapi-ts/src/openApi/v3/interfaces/OpenApiParameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,41 @@ import type { OpenApiExample } from './OpenApiExample';
import type { OpenApiReference } from './OpenApiReference';
import type { OpenApiSchema } from './OpenApiSchema';

/**
* add only one type for now as that's needed to resolve the reported issue,
* more types should be added though
* {@link https://github.com/hey-api/openapi-ts/issues/612}
*/
type MediaType = 'application/json';

/**
* encoding interface should be added, not adding it for now as it's not needed
* to resolve the issue reported
* {@link https://github.com/hey-api/openapi-ts/issues/612}
*/
interface MediaTypeObject {
example?: unknown;
examples?: Dictionary<OpenApiExample>;
schema: OpenApiSchema;
// encoding?
}

/**
* {@link} https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#parameter-object
*/
export interface OpenApiParameter extends OpenApiReference {
name: string;
in: 'path' | 'query' | 'header' | 'formData' | 'cookie';
description?: string;
required?: boolean;
nullable?: boolean;
deprecated?: boolean;
allowEmptyValue?: boolean;
style?: string;
explode?: boolean;
allowReserved?: boolean;
schema?: OpenApiSchema;
content?: Record<MediaType, MediaTypeObject>;
deprecated?: boolean;
description?: string;
example?: unknown;
examples?: Dictionary<OpenApiExample>;
explode?: boolean;
in: 'cookie' | 'formData' | 'header' | 'path' | 'query';
name: string;
nullable?: boolean;
required?: boolean;
schema?: OpenApiSchema;
style?: string;
}
9 changes: 8 additions & 1 deletion packages/openapi-ts/src/openApi/v3/parser/getModels.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type { Client } from '../../../types/client';
import { getConfig } from '../../../utils/config';
import { reservedWords } from '../../common/parser/reservedWords';
import { getType } from '../../common/parser/type';
import type { OpenApi } from '../interfaces/OpenApi';
import { getModel } from './getModel';
import { getParameterSchema } from './parameter';

export const getModels = (
openApi: OpenApi,
): Pick<Client, 'models' | 'types'> => {
const config = getConfig();

const types: Client['types'] = {};
let models: Client['models'] = [];

Expand Down Expand Up @@ -39,8 +43,11 @@ export const getModels = (

Object.entries(openApi.components.parameters ?? {}).forEach(
([definitionName, definition]) => {
const schema = definition.schema;
const schema = getParameterSchema(definition);
if (!schema) {
if (config.debug) {
console.warn('Skipping generating parameter:', definitionName);
}

Check warning on line 50 in packages/openapi-ts/src/openApi/v3/parser/getModels.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/v3/parser/getModels.ts#L48-L50

Added lines #L48 - L50 were not covered by tests
return;
}

Expand Down
87 changes: 46 additions & 41 deletions packages/openapi-ts/src/openApi/v3/parser/getOperationParameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { getModel } from './getModel';
import { isDefinitionNullable } from './inferType';
import { getParameterSchema } from './parameter';

export const getOperationParameter = ({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
debug,
openApi,
parameter,
types,
}: {
debug?: boolean;
openApi: OpenApi;
parameter: OpenApiParameter;
types: Client['types'];
Expand Down Expand Up @@ -61,11 +65,12 @@ export const getOperationParameter = ({
return operationParameter;
}

let schema = parameter.schema;
let schema = getParameterSchema(parameter);
if (schema) {
if (schema.$ref?.startsWith('#/components/parameters/')) {
schema = getRef<OpenApiSchema>(openApi, schema);
}

if (schema.$ref) {
const model = getType({ type: schema.$ref });
operationParameter.export = 'reference';
Expand All @@ -79,47 +84,47 @@ export const getOperationParameter = ({
];
operationParameter.default = getDefault(schema);
return operationParameter;
} else {
const model = getModel({ definition: schema, openApi, types });
operationParameter = {
...operationParameter,
$refs: [...operationParameter.$refs, ...model.$refs],
base: model.base,
enum: [...operationParameter.enum, ...model.enum],
enums: [...operationParameter.enums, ...model.enums],
exclusiveMaximum: model.exclusiveMaximum,
exclusiveMinimum: model.exclusiveMinimum,
export: model.export,
format: model.format,
imports: [...operationParameter.imports, ...model.imports],
isNullable: operationParameter.isNullable || model.isNullable,
isReadOnly: model.isReadOnly,
isRequired: operationParameter.isRequired || model.isRequired,
link: model.link,
maxItems: model.maxItems,
maxLength: model.maxLength,
maxProperties: model.maxProperties,
maximum: model.maximum,
minItems: model.minItems,
minLength: model.minLength,
minProperties: model.minProperties,
minimum: model.minimum,
multipleOf: model.multipleOf,
pattern: getPattern(model.pattern),
properties: [...operationParameter.properties, ...model.properties],
template: model.template,
type: model.type,
uniqueItems: model.uniqueItems,
};
if (
(operationParameter.enum.length || operationParameter.enums.length) &&
!operationParameter.meta
) {
operationParameter.meta = enumMeta(operationParameter);
}
operationParameter.default = model.default;
return operationParameter;
}

const model = getModel({ definition: schema, openApi, types });
operationParameter = {
...operationParameter,
$refs: [...operationParameter.$refs, ...model.$refs],
base: model.base,
enum: [...operationParameter.enum, ...model.enum],
enums: [...operationParameter.enums, ...model.enums],
exclusiveMaximum: model.exclusiveMaximum,
exclusiveMinimum: model.exclusiveMinimum,
export: model.export,
format: model.format,
imports: [...operationParameter.imports, ...model.imports],
isNullable: operationParameter.isNullable || model.isNullable,
isReadOnly: model.isReadOnly,
isRequired: operationParameter.isRequired || model.isRequired,
link: model.link,
maxItems: model.maxItems,
maxLength: model.maxLength,
maxProperties: model.maxProperties,
maximum: model.maximum,
minItems: model.minItems,
minLength: model.minLength,
minProperties: model.minProperties,
minimum: model.minimum,
multipleOf: model.multipleOf,
pattern: getPattern(model.pattern),
properties: [...operationParameter.properties, ...model.properties],
template: model.template,
type: model.type,
uniqueItems: model.uniqueItems,
};
if (
(operationParameter.enum.length || operationParameter.enums.length) &&
!operationParameter.meta
) {
operationParameter.meta = enumMeta(operationParameter);
}
operationParameter.default = model.default;
return operationParameter;
}

return operationParameter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { getOperationParameter } from './getOperationParameter';
const allowedIn = ['cookie', 'formData', 'header', 'path', 'query'] as const;

export const getOperationParameters = ({
debug,
openApi,
parameters,
types,
}: {
debug?: boolean;
openApi: OpenApi;
parameters: OpenApiParameter[];
types: Client['types'];
Expand All @@ -34,20 +36,22 @@ export const getOperationParameters = ({
parameterOrReference,
);
const parameter = getOperationParameter({
debug,
openApi,
parameter: parameterDef,
types,
});

const defIn = parameterDef.in as (typeof allowedIn)[number];

// ignore the "api-version" param since we do not want to add it
// as the first/default parameter for each of the service calls
if (parameter.prop === 'api-version' || !allowedIn.includes(defIn)) {
if (
parameter.prop === 'api-version' ||
!allowedIn.includes(parameterDef.in)
) {
return;
}

switch (defIn) {
switch (parameterDef.in) {
case 'cookie':
operationParameters.parametersCookie = [
...operationParameters.parametersCookie,
Expand Down
4 changes: 2 additions & 2 deletions packages/openapi-ts/src/openApi/v3/parser/getServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const getServices = ({

for (const url in openApi.paths) {
const path = openApi.paths[url];
const pathParams = getOperationParameters({
const pathParameters = getOperationParameters({
openApi,
parameters: path.parameters ?? [],
types,
Expand All @@ -57,7 +57,7 @@ export const getServices = ({
method,
op,
openApi,
pathParams,
pathParams: pathParameters,
tag,
types,
url,
Expand Down
23 changes: 23 additions & 0 deletions packages/openapi-ts/src/openApi/v3/parser/parameter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';

export const getParameterSchema = (
definition: OpenApiParameter,
): OpenApiSchema | undefined => {
if (definition.schema) {
return definition.schema;
}

if (definition.content) {
// treat every media type the same for now, types should be modified to
// preserve this data so client knows which headers to use and how to
// parse response bodies
const contents = Object.entries(definition.content);
for (const [key, mediaTypeObject] of contents) {
if (mediaTypeObject.schema) {
const mediaType = key as keyof Required<OpenApiParameter>['content'];
return definition.content[mediaType].schema;
}
}
}

Check warning on line 22 in packages/openapi-ts/src/openApi/v3/parser/parameter.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/openApi/v3/parser/parameter.ts#L22

Added line #L22 was not covered by tests
};
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export class ParametersService {
* @param data The data for the request.
* @param data.foo foo in method
* @param data.bar bar in method
* @param data.xFooBar Parameter with illegal characters
* @throws ApiError
*/
public static deleteFoo(data: DeleteFooData3): CancelablePromise<void> {
Expand All @@ -131,6 +132,9 @@ export class ParametersService {
path: {
foo: data.foo,
bar: data.bar
},
headers: {
'x-Foo-Bar': data.xFooBar
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ export type ParameterSimpleParameter = string;
/**
* Parameter with illegal characters
*/
export type Parameterx_Foo_Bar = string;
export type Parameterx_Foo_Bar = ModelWithString;

export type PostServiceWithEmptyTagData = {
requestBody: ModelWithReadOnlyAndWriteOnly | ModelWithArrayReadOnlyAndWriteOnly;
Expand All @@ -1004,6 +1004,10 @@ export type DeleteFooData3 = {
* foo in method
*/
foo: string;
/**
* Parameter with illegal characters
*/
xFooBar: ModelWithString;
};

export type CallWithParametersData = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export class ParametersService {
* @param data The data for the request.
* @param data.foo foo in method
* @param data.bar bar in method
* @param data.xFooBar Parameter with illegal characters
* @throws ApiError
*/
public deleteFoo(data: DeleteFooData3): Observable<void> {
Expand All @@ -148,6 +149,9 @@ export class ParametersService {
path: {
foo: data.foo,
bar: data.bar
},
headers: {
'x-Foo-Bar': data.xFooBar
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,7 @@ export type ParameterSimpleParameter = string;
/**
* Parameter with illegal characters
*/
export type Parameterx_Foo_Bar = string;
export type Parameterx_Foo_Bar = ModelWithString;

export type PostServiceWithEmptyTagData = {
requestBody: ModelWithReadOnlyAndWriteOnly | ModelWithArrayReadOnlyAndWriteOnly;
Expand All @@ -881,6 +881,10 @@ export type DeleteFooData3 = {
* foo in method
*/
foo: string;
/**
* Parameter with illegal characters
*/
xFooBar: ModelWithString;
};

export type CallWithParametersData = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export class ParametersService {
* @param data The data for the request.
* @param data.foo foo in method
* @param data.bar bar in method
* @param data.xFooBar Parameter with illegal characters
* @throws ApiError
*/
public deleteFoo(data: DeleteFooData3): CancelablePromise<void> {
Expand All @@ -136,6 +137,9 @@ export class ParametersService {
path: {
foo: data.foo,
bar: data.bar
},
headers: {
'x-Foo-Bar': data.xFooBar
}
});
}
Expand Down
Loading

0 comments on commit 93adfc3

Please sign in to comment.