-
-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1230 from hey-api/feat/parser-openapi-3-0-x
feat: add OpenAPI 3.0.x experimental parser
- Loading branch information
Showing
16 changed files
with
1,727 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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! 🎉 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}); | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }), | ||
}; | ||
} | ||
}; |
Oops, something went wrong.