From 45a79cb4155a944d30e3e3d590c6388898e5e571 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Fri, 21 Jul 2023 11:37:15 -0600 Subject: [PATCH] Improve oneOf and enum handling --- .changeset/rich-dodos-visit.md | 5 + .../resources/apps/apps_get_logsAggregate.yml | 45 --------- .../examples/curl/apps_get_logsAggregate.yml | 6 -- .../apps/responses/list_logs_aggregate.yml | 16 ---- .../examples/github-api-next.ts | 96 +++++++++++++++++-- .../openapi-typescript/examples/github-api.ts | 96 +++++++++++++++++-- .../src/transform/schema-object.ts | 28 ++++-- packages/openapi-typescript/src/types.ts | 6 ++ .../test/schema-object.test.ts | 33 +++++++ 9 files changed, 244 insertions(+), 87 deletions(-) create mode 100644 .changeset/rich-dodos-visit.md delete mode 100755 packages/openapi-typescript/examples/digital-ocean-api/resources/apps/apps_get_logsAggregate.yml delete mode 100644 packages/openapi-typescript/examples/digital-ocean-api/resources/apps/examples/curl/apps_get_logsAggregate.yml delete mode 100644 packages/openapi-typescript/examples/digital-ocean-api/resources/apps/responses/list_logs_aggregate.yml diff --git a/.changeset/rich-dodos-visit.md b/.changeset/rich-dodos-visit.md new file mode 100644 index 000000000..f2a89c514 --- /dev/null +++ b/.changeset/rich-dodos-visit.md @@ -0,0 +1,5 @@ +--- +"openapi-typescript": patch +--- + +Improve oneOf and enum handling diff --git a/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/apps_get_logsAggregate.yml b/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/apps_get_logsAggregate.yml deleted file mode 100755 index f8c55ddfc..000000000 --- a/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/apps_get_logsAggregate.yml +++ /dev/null @@ -1,45 +0,0 @@ -operationId: apps_get_logsAggregate - -summary: Retrieve Aggregate Deployment Logs - -description: Retrieve the logs of a past, in-progress, or active deployment. If a - component name is specified, the logs will be limited to only that component. The - response will include links to either real-time logs of an in-progress or active - deployment or archived logs of a past deployment. - -tags: -- Apps - -parameters: - - $ref: parameters.yml#/app_id - - $ref: parameters.yml#/deployment_id - - $ref: parameters.yml#/live_updates - - $ref: parameters.yml#/log_type - - $ref: parameters.yml#/time_wait - -responses: - "200": - $ref: responses/list_logs_aggregate.yml - - "401": - $ref: ../../shared/responses/unauthorized.yml - - '404': - $ref: '../../shared/responses/not_found.yml' - - "429": - $ref: "../../shared/responses/too_many_requests.yml" - - "500": - $ref: ../../shared/responses/server_error.yml - - default: - $ref: ../../shared/responses/unexpected_error.yml - -x-codeSamples: - - $ref: 'examples/curl/apps_get_logsAggregate.yml' - -security: - - bearer_auth: - - 'read' - diff --git a/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/examples/curl/apps_get_logsAggregate.yml b/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/examples/curl/apps_get_logsAggregate.yml deleted file mode 100644 index c9d459a9e..000000000 --- a/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/examples/curl/apps_get_logsAggregate.yml +++ /dev/null @@ -1,6 +0,0 @@ -lang: cURL -source: |- - curl -X GET \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ - "https://api.digitalocean.com/v2/apps/{app_id}/deployments/{deployment_id}/logs" diff --git a/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/responses/list_logs_aggregate.yml b/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/responses/list_logs_aggregate.yml deleted file mode 100644 index 72c572b70..000000000 --- a/packages/openapi-typescript/examples/digital-ocean-api/resources/apps/responses/list_logs_aggregate.yml +++ /dev/null @@ -1,16 +0,0 @@ -description: A JSON object with urls that point to archived logs - -content: - application/json: - schema: - $ref: ../models/apps_get_logs_response.yml - examples: - logs: - $ref: examples.yml#/logs -headers: - ratelimit-limit: - $ref: ../../../shared/headers.yml#/ratelimit-limit - ratelimit-remaining: - $ref: ../../../shared/headers.yml#/ratelimit-remaining - ratelimit-reset: - $ref: ../../../shared/headers.yml#/ratelimit-reset diff --git a/packages/openapi-typescript/examples/github-api-next.ts b/packages/openapi-typescript/examples/github-api-next.ts index 41d48890f..fbf1ddd1e 100644 --- a/packages/openapi-typescript/examples/github-api-next.ts +++ b/packages/openapi-typescript/examples/github-api-next.ts @@ -13711,7 +13711,9 @@ export interface components { * @description The assignee that has been granted access to GitHub Copilot. * @enum {object} */ - assignee: [object Object]; + assignee: { + [key: string]: unknown; + } & (components["schemas"]["simple-user"] | components["schemas"]["team"] | components["schemas"]["organization"]); /** @description The team that granted access to GitHub Copilot to the assignee. This will be null if the user was assigned a seat individually. */ assigning_team?: components["schemas"]["team"]; /** @@ -14124,7 +14126,7 @@ export interface components { * Organization ruleset conditions * @description Conditions for an organization ruleset */ - "org-ruleset-conditions": (components["schemas"]["repository-ruleset-conditions"] & components["schemas"]["repository-ruleset-conditions-repository-name-target"]) | (components["schemas"]["repository-ruleset-conditions"] & components["schemas"]["repository-ruleset-conditions-repository-id-target"]); + "org-ruleset-conditions": Record & ((components["schemas"]["repository-ruleset-conditions"] & components["schemas"]["repository-ruleset-conditions-repository-name-target"]) | (components["schemas"]["repository-ruleset-conditions"] & components["schemas"]["repository-ruleset-conditions-repository-id-target"])); /** * creation * @description Only allow users with bypass permission to create matching refs. @@ -14342,7 +14344,7 @@ export interface components { * Repository Rule * @description A repository rule. */ - "repository-rule": components["schemas"]["repository-rule-creation"] | components["schemas"]["repository-rule-update"] | components["schemas"]["repository-rule-deletion"] | components["schemas"]["repository-rule-required-linear-history"] | components["schemas"]["repository-rule-required-deployments"] | components["schemas"]["repository-rule-required-signatures"] | components["schemas"]["repository-rule-pull-request"] | components["schemas"]["repository-rule-required-status-checks"] | components["schemas"]["repository-rule-non-fast-forward"] | components["schemas"]["repository-rule-commit-message-pattern"] | components["schemas"]["repository-rule-commit-author-email-pattern"] | components["schemas"]["repository-rule-committer-email-pattern"] | components["schemas"]["repository-rule-branch-name-pattern"] | components["schemas"]["repository-rule-tag-name-pattern"]; + "repository-rule": Record & (components["schemas"]["repository-rule-creation"] | components["schemas"]["repository-rule-update"] | components["schemas"]["repository-rule-deletion"] | components["schemas"]["repository-rule-required-linear-history"] | components["schemas"]["repository-rule-required-deployments"] | components["schemas"]["repository-rule-required-signatures"] | components["schemas"]["repository-rule-pull-request"] | components["schemas"]["repository-rule-required-status-checks"] | components["schemas"]["repository-rule-non-fast-forward"] | components["schemas"]["repository-rule-commit-message-pattern"] | components["schemas"]["repository-rule-commit-author-email-pattern"] | components["schemas"]["repository-rule-committer-email-pattern"] | components["schemas"]["repository-rule-branch-name-pattern"] | components["schemas"]["repository-rule-tag-name-pattern"]); /** * Repository ruleset * @description A set of rules to apply when specified conditions are met. @@ -19266,7 +19268,7 @@ export interface components { * Repository Rule * @description A repository rule with ruleset details. */ - "repository-rule-detailed": (components["schemas"]["repository-rule-creation"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-update"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-deletion"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-linear-history"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-deployments"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-signatures"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-pull-request"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-status-checks"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-non-fast-forward"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-commit-message-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-commit-author-email-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-committer-email-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-branch-name-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-tag-name-pattern"] & components["schemas"]["repository-rule-ruleset-info"]); + "repository-rule-detailed": Record & ((components["schemas"]["repository-rule-creation"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-update"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-deletion"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-linear-history"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-deployments"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-signatures"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-pull-request"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-status-checks"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-non-fast-forward"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-commit-message-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-commit-author-email-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-committer-email-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-branch-name-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-tag-name-pattern"] & components["schemas"]["repository-rule-ruleset-info"])); "secret-scanning-alert": { number?: components["schemas"]["alert-number"]; created_at?: components["schemas"]["alert-created-at"]; @@ -90333,7 +90335,89 @@ export interface operations { }; requestBody: { content: { - "application/json": OneOf<[{ + "application/json": ({ + /** @description The name of the check. For example, "code-coverage". */ + name: string; + /** @description The SHA of the commit. */ + head_sha: string; + /** @description The URL of the integrator's site that has the full details of the check. If the integrator does not provide this, then the homepage of the GitHub app is used. */ + details_url?: string; + /** @description A reference for the run on the integrator's system. */ + external_id?: string; + /** + * @description The current status. + * @default queued + * @enum {string} + */ + status?: "queued" | "in_progress" | "completed"; + /** + * Format: date-time + * @description The time that the check run began. This is a timestamp in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format: `YYYY-MM-DDTHH:MM:SSZ`. + */ + started_at?: string; + /** + * @description **Required if you provide `completed_at` or a `status` of `completed`**. The final conclusion of the check. + * **Note:** Providing `conclusion` will automatically set the `status` parameter to `completed`. You cannot change a check run conclusion to `stale`, only GitHub can set this. + * @enum {string} + */ + conclusion?: "action_required" | "cancelled" | "failure" | "neutral" | "success" | "skipped" | "stale" | "timed_out"; + /** + * Format: date-time + * @description The time the check completed. This is a timestamp in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format: `YYYY-MM-DDTHH:MM:SSZ`. + */ + completed_at?: string; + /** @description Check runs can accept a variety of data in the `output` object, including a `title` and `summary` and can optionally provide descriptive details about the run. */ + output?: { + /** @description The title of the check run. */ + title: string; + /** @description The summary of the check run. This parameter supports Markdown. **Maximum length**: 65535 characters. */ + summary: string; + /** @description The details of the check run. This parameter supports Markdown. **Maximum length**: 65535 characters. */ + text?: string; + /** @description Adds information from your analysis to specific lines of code. Annotations are visible on GitHub in the **Checks** and **Files changed** tab of the pull request. The Checks API limits the number of annotations to a maximum of 50 per API request. To create more than 50 annotations, you have to make multiple requests to the [Update a check run](https://docs.github.com/rest/checks/runs#update-a-check-run) endpoint. Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. GitHub Actions are limited to 10 warning annotations and 10 error annotations per step. For details about how you can view annotations on GitHub, see "[About status checks](https://docs.github.com/articles/about-status-checks#checks)". */ + annotations?: ({ + /** @description The path of the file to add an annotation to. For example, `assets/css/main.css`. */ + path: string; + /** @description The start line of the annotation. Line numbers start at 1. */ + start_line: number; + /** @description The end line of the annotation. */ + end_line: number; + /** @description The start column of the annotation. Annotations only support `start_column` and `end_column` on the same line. Omit this parameter if `start_line` and `end_line` have different values. Column numbers start at 1. */ + start_column?: number; + /** @description The end column of the annotation. Annotations only support `start_column` and `end_column` on the same line. Omit this parameter if `start_line` and `end_line` have different values. */ + end_column?: number; + /** + * @description The level of the annotation. + * @enum {string} + */ + annotation_level: "notice" | "warning" | "failure"; + /** @description A short description of the feedback for these lines of code. The maximum size is 64 KB. */ + message: string; + /** @description The title that represents the annotation. The maximum size is 255 characters. */ + title?: string; + /** @description Details about this annotation. The maximum size is 64 KB. */ + raw_details?: string; + })[]; + /** @description Adds images to the output displayed in the GitHub pull request UI. */ + images?: { + /** @description The alternative text for the image. */ + alt: string; + /** @description The full URL of the image. */ + image_url: string; + /** @description A short image description. */ + caption?: string; + }[]; + }; + /** @description Displays a button on GitHub that can be clicked to alert your app to do additional tasks. For example, a code linting app can display a button that automatically fixes detected errors. The button created in this object is displayed after the check run completes. When a user clicks the button, GitHub sends the [`check_run.requested_action` webhook](https://docs.github.com/webhooks/event-payloads/#check_run) to your app. Each action includes a `label`, `identifier` and `description`. A maximum of three actions are accepted. To learn more about check runs and requested actions, see "[Check runs and requested actions](https://docs.github.com/rest/reference/checks#check-runs-and-requested-actions)." */ + actions?: { + /** @description The text to be displayed on a button in the web UI. The maximum size is 20 characters. */ + label: string; + /** @description A short explanation of what this action would do. The maximum size is 40 characters. */ + description: string; + /** @description A reference for the action on the integrator's system. The maximum size is 20 characters. */ + identifier: string; + }[]; + }) & (OneOf<[{ /** @enum {unknown} */ status: "completed"; [key: string]: unknown; @@ -90341,7 +90425,7 @@ export interface operations { /** @enum {unknown} */ status?: "queued" | "in_progress"; [key: string]: unknown; - }]>; + }]>); }; }; responses: { diff --git a/packages/openapi-typescript/examples/github-api.ts b/packages/openapi-typescript/examples/github-api.ts index d3e018bae..cbcda8410 100644 --- a/packages/openapi-typescript/examples/github-api.ts +++ b/packages/openapi-typescript/examples/github-api.ts @@ -12558,7 +12558,9 @@ export interface components { * @description The assignee that has been granted access to GitHub Copilot. * @enum {object} */ - assignee: [object Object]; + assignee: { + [key: string]: unknown; + } & (components["schemas"]["simple-user"] | components["schemas"]["team"] | components["schemas"]["organization"]); /** @description The team that granted access to GitHub Copilot to the assignee. This will be null if the user was assigned a seat individually. */ assigning_team?: components["schemas"]["team"]; /** @@ -13321,7 +13323,7 @@ export interface components { * Organization ruleset conditions * @description Conditions for an organization ruleset */ - "org-ruleset-conditions": (components["schemas"]["repository-ruleset-conditions"] & components["schemas"]["repository-ruleset-conditions-repository-name-target"]) | (components["schemas"]["repository-ruleset-conditions"] & components["schemas"]["repository-ruleset-conditions-repository-id-target"]); + "org-ruleset-conditions": Record & ((components["schemas"]["repository-ruleset-conditions"] & components["schemas"]["repository-ruleset-conditions-repository-name-target"]) | (components["schemas"]["repository-ruleset-conditions"] & components["schemas"]["repository-ruleset-conditions-repository-id-target"])); /** * creation * @description Only allow users with bypass permission to create matching refs. @@ -13539,7 +13541,7 @@ export interface components { * Repository Rule * @description A repository rule. */ - "repository-rule": components["schemas"]["repository-rule-creation"] | components["schemas"]["repository-rule-update"] | components["schemas"]["repository-rule-deletion"] | components["schemas"]["repository-rule-required-linear-history"] | components["schemas"]["repository-rule-required-deployments"] | components["schemas"]["repository-rule-required-signatures"] | components["schemas"]["repository-rule-pull-request"] | components["schemas"]["repository-rule-required-status-checks"] | components["schemas"]["repository-rule-non-fast-forward"] | components["schemas"]["repository-rule-commit-message-pattern"] | components["schemas"]["repository-rule-commit-author-email-pattern"] | components["schemas"]["repository-rule-committer-email-pattern"] | components["schemas"]["repository-rule-branch-name-pattern"] | components["schemas"]["repository-rule-tag-name-pattern"]; + "repository-rule": Record & (components["schemas"]["repository-rule-creation"] | components["schemas"]["repository-rule-update"] | components["schemas"]["repository-rule-deletion"] | components["schemas"]["repository-rule-required-linear-history"] | components["schemas"]["repository-rule-required-deployments"] | components["schemas"]["repository-rule-required-signatures"] | components["schemas"]["repository-rule-pull-request"] | components["schemas"]["repository-rule-required-status-checks"] | components["schemas"]["repository-rule-non-fast-forward"] | components["schemas"]["repository-rule-commit-message-pattern"] | components["schemas"]["repository-rule-commit-author-email-pattern"] | components["schemas"]["repository-rule-committer-email-pattern"] | components["schemas"]["repository-rule-branch-name-pattern"] | components["schemas"]["repository-rule-tag-name-pattern"]); /** * Repository ruleset * @description A set of rules to apply when specified conditions are met. @@ -21108,7 +21110,7 @@ export interface components { * Repository Rule * @description A repository rule with ruleset details. */ - "repository-rule-detailed": (components["schemas"]["repository-rule-creation"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-update"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-deletion"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-linear-history"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-deployments"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-signatures"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-pull-request"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-status-checks"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-non-fast-forward"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-commit-message-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-commit-author-email-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-committer-email-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-branch-name-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-tag-name-pattern"] & components["schemas"]["repository-rule-ruleset-info"]); + "repository-rule-detailed": Record & ((components["schemas"]["repository-rule-creation"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-update"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-deletion"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-linear-history"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-deployments"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-signatures"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-pull-request"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-required-status-checks"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-non-fast-forward"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-commit-message-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-commit-author-email-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-committer-email-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-branch-name-pattern"] & components["schemas"]["repository-rule-ruleset-info"]) | (components["schemas"]["repository-rule-tag-name-pattern"] & components["schemas"]["repository-rule-ruleset-info"])); "secret-scanning-alert": { number?: components["schemas"]["alert-number"]; created_at?: components["schemas"]["alert-created-at"]; @@ -92610,7 +92612,89 @@ export interface operations { }; requestBody: { content: { - "application/json": OneOf<[{ + "application/json": ({ + /** @description The name of the check. For example, "code-coverage". */ + name: string; + /** @description The SHA of the commit. */ + head_sha: string; + /** @description The URL of the integrator's site that has the full details of the check. If the integrator does not provide this, then the homepage of the GitHub app is used. */ + details_url?: string; + /** @description A reference for the run on the integrator's system. */ + external_id?: string; + /** + * @description The current status. + * @default queued + * @enum {string} + */ + status?: "queued" | "in_progress" | "completed"; + /** + * Format: date-time + * @description The time that the check run began. This is a timestamp in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format: `YYYY-MM-DDTHH:MM:SSZ`. + */ + started_at?: string; + /** + * @description **Required if you provide `completed_at` or a `status` of `completed`**. The final conclusion of the check. + * **Note:** Providing `conclusion` will automatically set the `status` parameter to `completed`. You cannot change a check run conclusion to `stale`, only GitHub can set this. + * @enum {string} + */ + conclusion?: "action_required" | "cancelled" | "failure" | "neutral" | "success" | "skipped" | "stale" | "timed_out"; + /** + * Format: date-time + * @description The time the check completed. This is a timestamp in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format: `YYYY-MM-DDTHH:MM:SSZ`. + */ + completed_at?: string; + /** @description Check runs can accept a variety of data in the `output` object, including a `title` and `summary` and can optionally provide descriptive details about the run. */ + output?: { + /** @description The title of the check run. */ + title: string; + /** @description The summary of the check run. This parameter supports Markdown. **Maximum length**: 65535 characters. */ + summary: string; + /** @description The details of the check run. This parameter supports Markdown. **Maximum length**: 65535 characters. */ + text?: string; + /** @description Adds information from your analysis to specific lines of code. Annotations are visible on GitHub in the **Checks** and **Files changed** tab of the pull request. The Checks API limits the number of annotations to a maximum of 50 per API request. To create more than 50 annotations, you have to make multiple requests to the [Update a check run](https://docs.github.com/rest/checks/runs#update-a-check-run) endpoint. Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. GitHub Actions are limited to 10 warning annotations and 10 error annotations per step. For details about how you can view annotations on GitHub, see "[About status checks](https://docs.github.com/articles/about-status-checks#checks)". */ + annotations?: ({ + /** @description The path of the file to add an annotation to. For example, `assets/css/main.css`. */ + path: string; + /** @description The start line of the annotation. Line numbers start at 1. */ + start_line: number; + /** @description The end line of the annotation. */ + end_line: number; + /** @description The start column of the annotation. Annotations only support `start_column` and `end_column` on the same line. Omit this parameter if `start_line` and `end_line` have different values. Column numbers start at 1. */ + start_column?: number; + /** @description The end column of the annotation. Annotations only support `start_column` and `end_column` on the same line. Omit this parameter if `start_line` and `end_line` have different values. */ + end_column?: number; + /** + * @description The level of the annotation. + * @enum {string} + */ + annotation_level: "notice" | "warning" | "failure"; + /** @description A short description of the feedback for these lines of code. The maximum size is 64 KB. */ + message: string; + /** @description The title that represents the annotation. The maximum size is 255 characters. */ + title?: string; + /** @description Details about this annotation. The maximum size is 64 KB. */ + raw_details?: string; + })[]; + /** @description Adds images to the output displayed in the GitHub pull request UI. */ + images?: { + /** @description The alternative text for the image. */ + alt: string; + /** @description The full URL of the image. */ + image_url: string; + /** @description A short image description. */ + caption?: string; + }[]; + }; + /** @description Displays a button on GitHub that can be clicked to alert your app to do additional tasks. For example, a code linting app can display a button that automatically fixes detected errors. The button created in this object is displayed after the check run completes. When a user clicks the button, GitHub sends the [`check_run.requested_action` webhook](https://docs.github.com/webhooks/event-payloads/#check_run) to your app. Each action includes a `label`, `identifier` and `description`. A maximum of three actions are accepted. To learn more about check runs and requested actions, see "[Check runs and requested actions](https://docs.github.com/rest/reference/checks#check-runs-and-requested-actions)." */ + actions?: { + /** @description The text to be displayed on a button in the web UI. The maximum size is 20 characters. */ + label: string; + /** @description A short explanation of what this action would do. The maximum size is 40 characters. */ + description: string; + /** @description A reference for the action on the integrator's system. The maximum size is 20 characters. */ + identifier: string; + }[]; + }) & (OneOf<[{ /** @enum {unknown} */ status: "completed"; [key: string]: unknown; @@ -92618,7 +92702,7 @@ export interface operations { /** @enum {unknown} */ status?: "queued" | "in_progress"; [key: string]: unknown; - }]>; + }]>); }; }; responses: { diff --git a/packages/openapi-typescript/src/transform/schema-object.ts b/packages/openapi-typescript/src/transform/schema-object.ts index ab11d7763..25d2b9273 100644 --- a/packages/openapi-typescript/src/transform/schema-object.ts +++ b/packages/openapi-typescript/src/transform/schema-object.ts @@ -51,11 +51,13 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere }); } - // enum (valid for any type) - if (schemaObject.enum) { - let items = schemaObject.enum as any[]; + // enum (valid for any type, but for objects, treat as oneOf below) + if (typeof schemaObject === "object" && !!(schemaObject as any).enum && (schemaObject as any).type !== "object") { + let items = (schemaObject as any).enum as any[]; if ("type" in schemaObject) { - if (schemaObject.type === "string" || (Array.isArray(schemaObject.type) && schemaObject.type.includes("string" as any))) items = items.map((t) => escStr(t)); + if (schemaObject.type === "string" || (Array.isArray(schemaObject.type) && schemaObject.type.includes("string" as any))) { + items = items.map((t) => escStr(t)); + } } // if no type, assume "string" else { @@ -65,10 +67,20 @@ export function defaultSchemaObjectTransform(schemaObject: SchemaObject | Refere } // oneOf (no discriminator) - if ("oneOf" in schemaObject && !schemaObject.oneOf.some((t) => "$ref" in t && ctx.discriminators[t.$ref])) { - const maybeTypes = schemaObject.oneOf.map((item) => transformSchemaObject(item, { path, ctx })); - if (maybeTypes.some((t) => typeof t === "string" && t.includes("{"))) return tsOneOf(...maybeTypes); // OneOf<> helper needed if any objects present ("{") - return tsUnionOf(...maybeTypes); // otherwise, TS union works for primitives + const oneOf = ((typeof schemaObject === "object" && (schemaObject as any).oneOf) || (schemaObject as any).enum || undefined) as (SchemaObject | ReferenceObject)[] | undefined; // note: for objects, treat enum as oneOf + if (oneOf && !oneOf.some((t) => "$ref" in t && ctx.discriminators[t.$ref])) { + const oneOfNormalized = oneOf.map((item) => transformSchemaObject(item, { path, ctx })); + // OneOf<> helper needed if any objects present ("{") + const oneOfTypes = oneOfNormalized.some((t) => typeof t === "string" && t.includes("{")) ? tsOneOf(...oneOfNormalized) : tsUnionOf(...oneOfNormalized); + + if ("type" in schemaObject && schemaObject.type === "object") { + const coreSchema = { ...schemaObject }; + delete (coreSchema as any).oneOf; + delete coreSchema.enum; + return tsIntersectionOf(transformSchemaObject(coreSchema, { path, ctx }), oneOfTypes); + } else { + return oneOfTypes; + } } if ("type" in schemaObject) { diff --git a/packages/openapi-typescript/src/types.ts b/packages/openapi-typescript/src/types.ts index 519a72b5f..31970a0db 100644 --- a/packages/openapi-typescript/src/types.ts +++ b/packages/openapi-typescript/src/types.ts @@ -447,18 +447,21 @@ export type SchemaObject = { export interface StringSubtype { type: "string"; + enum?: (string | ReferenceObject)[]; } export interface NumberSubtype { type: "number"; minimum?: number; maximum?: number; + enum?: (number | ReferenceObject)[]; } export interface IntegerSubtype { type: "integer"; minimum?: number; maximum?: number; + enum?: (number | ReferenceObject)[]; } export interface ArraySubtype { @@ -467,10 +470,12 @@ export interface ArraySubtype { items?: SchemaObject | ReferenceObject | (SchemaObject | ReferenceObject)[]; minItems?: number; maxItems?: number; + enum?: (SchemaObject | ReferenceObject)[]; } export interface BooleanSubtype { type: "boolean"; + enum?: (boolean | ReferenceObject)[]; } export interface NullSubtype { @@ -484,6 +489,7 @@ export interface ObjectSubtype { required?: string[]; allOf?: (SchemaObject | ReferenceObject)[]; anyOf?: (SchemaObject | ReferenceObject)[]; + enum?: (SchemaObject | ReferenceObject)[]; } /** diff --git a/packages/openapi-typescript/test/schema-object.test.ts b/packages/openapi-typescript/test/schema-object.test.ts index b68236ab5..8be3abf72 100644 --- a/packages/openapi-typescript/test/schema-object.test.ts +++ b/packages/openapi-typescript/test/schema-object.test.ts @@ -386,6 +386,39 @@ describe("Schema Object", () => { }]>`); }); + test("oneOf + properties", () => { + const schema = { + type: "object", + oneOf: [ + { type: "object", properties: { foo: { type: "string" } } }, + { type: "object", properties: { bar: { type: "string" } } }, + ], + properties: { + baz: { type: "string" }, + }, + } as SchemaObject; + const generated = transformSchemaObject(schema, options); + expect(generated).toBe(`{ + baz?: string; +} & OneOf<[{ + foo?: string; +}, { + bar?: string; +}]>`); + }); + + test("enum (acting as oneOf)", () => { + const schema: SchemaObject = { + type: "object", + additionalProperties: true, + enum: [{ $ref: "#/components/schemas/simple-user" }, { $ref: "#/components/schemas/team" }, { $ref: "#/components/schemas/organization" }], + }; + const generated = transformSchemaObject(schema, options); + expect(generated).toBe(`{ + [key: string]: unknown; +} & (#/components/schemas/simple-user | #/components/schemas/team | #/components/schemas/organization)`); + }); + test("falls back to union at high complexity", () => { const schema: SchemaObject = { oneOf: [