Skip to content

Commit

Permalink
Merge pull request #1340 from hey-api/feat/reusable-request-body
Browse files Browse the repository at this point in the history
fix: experimental parser exports reusable request bodies
  • Loading branch information
mrlubos authored Nov 25, 2024
2 parents 8b5663e + c8511e0 commit 0278fcf
Show file tree
Hide file tree
Showing 58 changed files with 849 additions and 338 deletions.
5 changes: 5 additions & 0 deletions .changeset/poor-parrots-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': patch
---

fix: experimental parser exports reusable request bodies
6 changes: 6 additions & 0 deletions packages/openapi-ts/src/ir/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
IROperationObject,
IRParameterObject,
IRPathItemObject,
IRRequestBodyObject,
IRSchemaObject,
} from './ir';

Expand Down Expand Up @@ -44,6 +45,11 @@ interface Events {
name: string;
parameter: IRParameterObject;
}) => void;
requestBody: (args: {
$ref: string;
name: string;
requestBody: IRRequestBodyObject;
}) => void;
schema: (args: {
$ref: string;
name: string;
Expand Down
10 changes: 9 additions & 1 deletion packages/openapi-ts/src/ir/ir.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface IR {

interface IRComponentsObject {
parameters?: Record<string, IRParameterObject>;
requestBodies?: Record<string, IRRequestBodyObject>;
schemas?: Record<string, IRSchemaObject>;
}

Expand Down Expand Up @@ -61,7 +62,8 @@ export interface IRParametersObject {
query?: Record<string, IRParameterObject>;
}

export interface IRParameterObject {
export interface IRParameterObject
extends Pick<JsonSchemaDraft2020_12, 'deprecated' | 'description'> {
/**
* Determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986 `:/?#[]@!$&'()*+,;=` to be included without percent-encoding. The default value is `false`. This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded` or `multipart/form-data`. If a value is explicitly defined, then the value of `contentType` (implicit or explicit) SHALL be ignored.
*/
Expand Down Expand Up @@ -95,6 +97,12 @@ export interface IRParameterObject {
| 'spaceDelimited';
}

export interface IRRequestBodyObject
extends Pick<JsonSchemaDraft2020_12, 'description'> {
required?: boolean;
schema: IRSchemaObject;
}

export interface IRResponsesObject {
/**
* Any {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#http-status-codes HTTP status code} can be used as the property name, but only one property per code, to describe the expected response for that HTTP status code. This field MUST be enclosed in quotation marks (for example, "200") for compatibility between JSON and YAML. To define a range of response codes, this field MAY contain the uppercase wildcard character `X`. For example, `2XX` represents all response codes between `[200-299]`. Only the following range definitions are allowed: `1XX`, `2XX`, `3XX`, `4XX`, and `5XX`. If a response is defined using an explicit code, the explicit code definition takes precedence over the range definition for that code.
Expand Down
14 changes: 11 additions & 3 deletions packages/openapi-ts/src/ir/operation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { IRContext } from './context';
import type { IROperationObject, IRResponseObject, IRSchemaObject } from './ir';
import type { IRRequestBodyObject } from './ir';
import {
type IROperationObject,
type IRResponseObject,
type IRSchemaObject,
} from './ir';
import type { Pagination } from './pagination';
import {
hasParametersObjectRequired,
Expand Down Expand Up @@ -39,12 +44,15 @@ export const operationPagination = ({
}

const schema = operation.body.schema.$ref
? context.resolveIrRef<IRSchemaObject>(operation.body.schema.$ref)
? context.resolveIrRef<IRRequestBodyObject | IRSchemaObject>(
operation.body.schema.$ref,
)
: operation.body.schema;
const finalSchema = 'schema' in schema ? schema.schema : schema;
return {
in: 'body',
name: operation.body.pagination,
schema: schema.properties![operation.body.pagination],
schema: finalSchema.properties![operation.body.pagination],
};
}

Expand Down
6 changes: 6 additions & 0 deletions packages/openapi-ts/src/ir/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const parseIR = async ({ context }: { context: IRContext }) => {
const $ref = `#/components/parameters/${name}`;
await context.broadcast('parameter', { $ref, name, parameter });
}

for (const name in context.ir.components.requestBodies) {
const requestBody = context.ir.components.requestBodies[name];
const $ref = `#/components/requestBodies/${name}`;
await context.broadcast('requestBody', { $ref, name, requestBody });
}
}

for (const path in context.ir.paths) {
Expand Down
187 changes: 74 additions & 113 deletions packages/openapi-ts/src/openApi/3.0.x/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import type {
ParameterObject,
PathItemObject,
PathsObject,
RequestBodyObject,
} from '../types/spec';
import { parseOperation } from './operation';
import {
mergeParametersObjects,
parametersArrayToObject,
parseParameter,
} from './parameter';
import { parseRequestBody } from './requestBody';
import { parseSchema } from './schema';

export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
Expand All @@ -24,6 +26,70 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
? new RegExp(context.config.input.include)
: undefined;

const shouldProcessRef = ($ref: string) =>
canProcessRef({
$ref,
excludeRegExp,
includeRegExp,
});

// TODO: parser - handle more component types, old parser handles only parameters and schemas
if (context.spec.components) {
for (const name in context.spec.components.parameters) {
const $ref = `#/components/parameters/${name}`;
if (!shouldProcessRef($ref)) {
continue;
}

const parameterOrReference = context.spec.components.parameters[name];
const parameter =
'$ref' in parameterOrReference
? context.resolveRef<ParameterObject>(parameterOrReference.$ref)
: parameterOrReference;

parseParameter({
$ref,
context,
parameter,
});
}

for (const name in context.spec.components.requestBodies) {
const $ref = `#/components/requestBodies/${name}`;
if (!shouldProcessRef($ref)) {
continue;
}

const requestBodyOrReference =
context.spec.components.requestBodies[name];
const requestBody =
'$ref' in requestBodyOrReference
? context.resolveRef<RequestBodyObject>(requestBodyOrReference.$ref)
: requestBodyOrReference;

parseRequestBody({
$ref,
context,
requestBody,
});
}

for (const name in context.spec.components.schemas) {
const $ref = `#/components/schemas/${name}`;
if (!shouldProcessRef($ref)) {
continue;
}

const schema = context.spec.components.schemas[name];

parseSchema({
$ref,
context,
schema,
});
}
}

for (const path in context.spec.paths) {
const pathItem = context.spec.paths[path as keyof PathsObject];

Expand Down Expand Up @@ -59,14 +125,7 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
};

const $refDelete = `#/paths${path}/delete`;
if (
finalPathItem.delete &&
canProcessRef({
$ref: $refDelete,
excludeRegExp,
includeRegExp,
})
) {
if (finalPathItem.delete && shouldProcessRef($refDelete)) {
parseOperation({
...operationArgs,
method: 'delete',
Expand All @@ -85,14 +144,7 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
}

const $refGet = `#/paths${path}/get`;
if (
finalPathItem.get &&
canProcessRef({
$ref: $refGet,
excludeRegExp,
includeRegExp,
})
) {
if (finalPathItem.get && shouldProcessRef($refGet)) {
parseOperation({
...operationArgs,
method: 'get',
Expand All @@ -111,14 +163,7 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
}

const $refHead = `#/paths${path}/head`;
if (
finalPathItem.head &&
canProcessRef({
$ref: $refHead,
excludeRegExp,
includeRegExp,
})
) {
if (finalPathItem.head && shouldProcessRef($refHead)) {
parseOperation({
...operationArgs,
method: 'head',
Expand All @@ -137,14 +182,7 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
}

const $refOptions = `#/paths${path}/options`;
if (
finalPathItem.options &&
canProcessRef({
$ref: $refOptions,
excludeRegExp,
includeRegExp,
})
) {
if (finalPathItem.options && shouldProcessRef($refOptions)) {
parseOperation({
...operationArgs,
method: 'options',
Expand All @@ -163,14 +201,7 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
}

const $refPatch = `#/paths${path}/patch`;
if (
finalPathItem.patch &&
canProcessRef({
$ref: $refPatch,
excludeRegExp,
includeRegExp,
})
) {
if (finalPathItem.patch && shouldProcessRef($refPatch)) {
parseOperation({
...operationArgs,
method: 'patch',
Expand All @@ -189,14 +220,7 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
}

const $refPost = `#/paths${path}/post`;
if (
finalPathItem.post &&
canProcessRef({
$ref: $refPost,
excludeRegExp,
includeRegExp,
})
) {
if (finalPathItem.post && shouldProcessRef($refPost)) {
parseOperation({
...operationArgs,
method: 'post',
Expand All @@ -215,14 +239,7 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
}

const $refPut = `#/paths${path}/put`;
if (
finalPathItem.put &&
canProcessRef({
$ref: $refPut,
excludeRegExp,
includeRegExp,
})
) {
if (finalPathItem.put && shouldProcessRef($refPut)) {
parseOperation({
...operationArgs,
method: 'put',
Expand All @@ -241,14 +258,7 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
}

const $refTrace = `#/paths${path}/trace`;
if (
finalPathItem.trace &&
canProcessRef({
$ref: $refTrace,
excludeRegExp,
includeRegExp,
})
) {
if (finalPathItem.trace && shouldProcessRef($refTrace)) {
parseOperation({
...operationArgs,
method: 'trace',
Expand All @@ -266,53 +276,4 @@ export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
});
}
}

// TODO: parser - handle more component types, old parser handles only parameters and schemas
if (context.spec.components) {
for (const name in context.spec.components.parameters) {
const $ref = `#/components/parameters/${name}`;
if (
!canProcessRef({
$ref,
excludeRegExp,
includeRegExp,
})
) {
continue;
}

const parameterOrReference = context.spec.components.parameters[name];
const parameter =
'$ref' in parameterOrReference
? context.resolveRef<ParameterObject>(parameterOrReference.$ref)
: parameterOrReference;

parseParameter({
context,
name,
parameter,
});
}

for (const name in context.spec.components.schemas) {
const $ref = `#/components/schemas/${name}`;
if (
!canProcessRef({
$ref,
excludeRegExp,
includeRegExp,
})
) {
continue;
}

const schema = context.spec.components.schemas[name];

parseSchema({
$ref,
context,
schema,
});
}
}
};
Loading

0 comments on commit 0278fcf

Please sign in to comment.