Skip to content

Commit

Permalink
Merge pull request #1230 from hey-api/feat/parser-openapi-3-0-x
Browse files Browse the repository at this point in the history
feat: add OpenAPI 3.0.x experimental parser
  • Loading branch information
mrlubos authored Nov 5, 2024
2 parents 2819097 + 3e65ae0 commit df3a641
Show file tree
Hide file tree
Showing 16 changed files with 1,727 additions and 148 deletions.
28 changes: 28 additions & 0 deletions .changeset/twelve-papayas-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'@hey-api/openapi-ts': patch
---

feat: add OpenAPI 3.0.x experimental parser

This release adds an experimental parser for OpenAPI versions 3.0.x. In the future, this will become the default parser. If you're using OpenAPI 3.0 or newer, we encourage you to try it out today.

You can enable the experimental parser by setting the `experimentalParser` boolean flag to `true` in your configuration file or CLI.

```js
export default {
client: '@hey-api/client-fetch',
input: 'path/to/openapi.json',
output: 'src/client',
experimentalParser: true,
};
```

```sh
npx @hey-api/openapi-ts -i path/to/openapi.json -o src/client -c @hey-api/client-fetch -e
```

The generated output should not structurally change, despite few things being generated in a different order. In fact, the output should be cleaner! That's the immediate side effect you should notice. If that's not true, please leave feedback in [GitHub issues](https://github.com/hey-api/openapi-ts/issues).

Hey API parser marks an important milestone towards v1 of `@hey-api/openapi-ts`. More features will be added to the parser in the future and the original parser from `openapi-typescript-codegen` will be deprecated and used only for generating legacy clients.

If you'd like to work with the parser more closely (e.g. to generate code not natively supported by this package), feel free to reach out with any feedback or suggestions. Happy parsing! 🎉
223 changes: 220 additions & 3 deletions packages/openapi-ts/src/openApi/3.0.x/parser/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,224 @@
import type { IRContext } from '../../../ir/context';
import type { OpenApiV3_0_X } from '../types/spec';
import type {
OpenApiV3_0_X,
ParameterObject,
PathItemObject,
PathsObject,
} from '../types/spec';
import { parseOperation } from './operation';
import {
mergeParametersObjects,
parametersArrayToObject,
parseParameter,
} from './parameter';
import { parseSchema } from './schema';

export const parseV3_0_X = (context: IRContext<OpenApiV3_0_X>) => {
// TODO
console.log(context.spec);
const operationIds = new Map<string, string>();

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

const finalPathItem = pathItem.$ref
? {
...context.resolveRef<PathItemObject>(pathItem.$ref),
...pathItem,
}
: pathItem;

const operationArgs: Omit<
Parameters<typeof parseOperation>[0],
'method' | 'operation'
> & {
operation: Omit<
Parameters<typeof parseOperation>[0]['operation'],
'responses'
>;
} = {
context,
operation: {
description: finalPathItem.description,
id: '',
parameters: parametersArrayToObject({
context,
parameters: finalPathItem.parameters,
}),
servers: finalPathItem.servers,
summary: finalPathItem.summary,
},
operationIds,
path: path as keyof PathsObject,
};

if (finalPathItem.delete) {
parseOperation({
...operationArgs,
method: 'delete',
operation: {
...operationArgs.operation,
...finalPathItem.delete,
parameters: mergeParametersObjects({
source: parametersArrayToObject({
context,
parameters: finalPathItem.delete.parameters,
}),
target: operationArgs.operation.parameters,
}),
},
});
}

if (finalPathItem.get) {
parseOperation({
...operationArgs,
method: 'get',
operation: {
...operationArgs.operation,
...finalPathItem.get,
parameters: mergeParametersObjects({
source: parametersArrayToObject({
context,
parameters: finalPathItem.get.parameters,
}),
target: operationArgs.operation.parameters,
}),
},
});
}

if (finalPathItem.head) {
parseOperation({
...operationArgs,
method: 'head',
operation: {
...operationArgs.operation,
...finalPathItem.head,
parameters: mergeParametersObjects({
source: parametersArrayToObject({
context,
parameters: finalPathItem.head.parameters,
}),
target: operationArgs.operation.parameters,
}),
},
});
}

if (finalPathItem.options) {
parseOperation({
...operationArgs,
method: 'options',
operation: {
...operationArgs.operation,
...finalPathItem.options,
parameters: mergeParametersObjects({
source: parametersArrayToObject({
context,
parameters: finalPathItem.options.parameters,
}),
target: operationArgs.operation.parameters,
}),
},
});
}

if (finalPathItem.patch) {
parseOperation({
...operationArgs,
method: 'patch',
operation: {
...operationArgs.operation,
...finalPathItem.patch,
parameters: mergeParametersObjects({
source: parametersArrayToObject({
context,
parameters: finalPathItem.patch.parameters,
}),
target: operationArgs.operation.parameters,
}),
},
});
}

if (finalPathItem.post) {
parseOperation({
...operationArgs,
method: 'post',
operation: {
...operationArgs.operation,
...finalPathItem.post,
parameters: mergeParametersObjects({
source: parametersArrayToObject({
context,
parameters: finalPathItem.post.parameters,
}),
target: operationArgs.operation.parameters,
}),
},
});
}

if (finalPathItem.put) {
parseOperation({
...operationArgs,
method: 'put',
operation: {
...operationArgs.operation,
...finalPathItem.put,
parameters: mergeParametersObjects({
source: parametersArrayToObject({
context,
parameters: finalPathItem.put.parameters,
}),
target: operationArgs.operation.parameters,
}),
},
});
}

if (finalPathItem.trace) {
parseOperation({
...operationArgs,
method: 'trace',
operation: {
...operationArgs.operation,
...finalPathItem.trace,
parameters: mergeParametersObjects({
source: parametersArrayToObject({
context,
parameters: finalPathItem.trace.parameters,
}),
target: operationArgs.operation.parameters,
}),
},
});
}
}

// 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 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 schema = context.spec.components.schemas[name];

parseSchema({
context,
name,
schema,
});
}
}
};
28 changes: 28 additions & 0 deletions packages/openapi-ts/src/openApi/3.0.x/parser/mediaType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { IRMediaType } from '../../../ir/mediaType';
import { mediaTypeToIrMediaType } from '../../../ir/mediaType';
import type {
MediaTypeObject,
ReferenceObject,
SchemaObject,
} from '../types/spec';

interface Content {
mediaType: string;
schema: SchemaObject | ReferenceObject | undefined;
type: IRMediaType | undefined;
}

export const mediaTypeObject = ({
content,
}: {
content: Record<string, MediaTypeObject> | undefined;
}): Content | undefined => {
// return the first supported MIME type
for (const mediaType in content) {
return {
mediaType,
schema: content[mediaType].schema,
type: mediaTypeToIrMediaType({ mediaType }),
};
}
};
Loading

0 comments on commit df3a641

Please sign in to comment.