Skip to content
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

fix: handle application/json content type in parameter definitions #614

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@

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