From c5fc5d7da6c0f889e722da94bd100ee91472e5fb Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Sun, 23 Mar 2025 16:53:26 +0800 Subject: [PATCH] =?UTF-8?q?=E2=8F=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bin/generate-from-openapi.ts | 21 + .../fixtures/openapi/{ => 3.0}/firecrawl.json | 0 .../fixtures/openapi/{ => 3.0}/notion.json | 0 .../fixtures/openapi/{ => 3.0}/pet-store.json | 0 .../openapi/3.0/petstore-expanded.json | 235 ++ .../fixtures/openapi/3.0/readme.json | 2965 +++++++++++++++++ .../fixtures/openapi/3.0/security.json | 392 +++ .../fixtures/openapi/{ => 3.0}/stripe.json | 0 .../openapi/{ => 3.0}/tic-tac-toe.json | 0 .../fixtures/openapi/3.1/petstore.json | 988 ++++++ .../fixtures/openapi/3.1/security.json | 397 +++ packages/openapi-to-ts/package.json | 2 +- ...enerate.ts => generate-ts-from-openapi.ts} | 33 +- packages/openapi-to-ts/src/index.ts | 2 +- packages/openapi-to-ts/src/utils.ts | 8 +- packages/openapi-to-ts/tsconfig.json | 2 +- packages/openapi-to-ts/tsup.config.ts | 28 + 17 files changed, 5047 insertions(+), 26 deletions(-) create mode 100644 packages/openapi-to-ts/bin/generate-from-openapi.ts rename packages/openapi-to-ts/fixtures/openapi/{ => 3.0}/firecrawl.json (100%) rename packages/openapi-to-ts/fixtures/openapi/{ => 3.0}/notion.json (100%) rename packages/openapi-to-ts/fixtures/openapi/{ => 3.0}/pet-store.json (100%) create mode 100644 packages/openapi-to-ts/fixtures/openapi/3.0/petstore-expanded.json create mode 100644 packages/openapi-to-ts/fixtures/openapi/3.0/readme.json create mode 100644 packages/openapi-to-ts/fixtures/openapi/3.0/security.json rename packages/openapi-to-ts/fixtures/openapi/{ => 3.0}/stripe.json (100%) rename packages/openapi-to-ts/fixtures/openapi/{ => 3.0}/tic-tac-toe.json (100%) create mode 100644 packages/openapi-to-ts/fixtures/openapi/3.1/petstore.json create mode 100644 packages/openapi-to-ts/fixtures/openapi/3.1/security.json rename packages/openapi-to-ts/src/{generate.ts => generate-ts-from-openapi.ts} (96%) create mode 100644 packages/openapi-to-ts/tsup.config.ts diff --git a/packages/openapi-to-ts/bin/generate-from-openapi.ts b/packages/openapi-to-ts/bin/generate-from-openapi.ts new file mode 100644 index 0000000..daedcea --- /dev/null +++ b/packages/openapi-to-ts/bin/generate-from-openapi.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-template-curly-in-string */ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { assert } from '@agentic/core' + +import { generateTSFromOpenAPI } from '../src' + +const dirname = path.dirname(fileURLToPath(import.meta.url)) + +// TODO: Add proper CLI handling +async function main() { + const pathToOpenApiSpec = + process.argv[2] ?? + path.join(dirname, '..', 'fixtures', 'openapi', '3.0', 'notion.json') + assert(pathToOpenApiSpec, 'Missing path to OpenAPI spec') + + await generateTSFromOpenAPI(pathToOpenApiSpec) +} + +await main() diff --git a/packages/openapi-to-ts/fixtures/openapi/firecrawl.json b/packages/openapi-to-ts/fixtures/openapi/3.0/firecrawl.json similarity index 100% rename from packages/openapi-to-ts/fixtures/openapi/firecrawl.json rename to packages/openapi-to-ts/fixtures/openapi/3.0/firecrawl.json diff --git a/packages/openapi-to-ts/fixtures/openapi/notion.json b/packages/openapi-to-ts/fixtures/openapi/3.0/notion.json similarity index 100% rename from packages/openapi-to-ts/fixtures/openapi/notion.json rename to packages/openapi-to-ts/fixtures/openapi/3.0/notion.json diff --git a/packages/openapi-to-ts/fixtures/openapi/pet-store.json b/packages/openapi-to-ts/fixtures/openapi/3.0/pet-store.json similarity index 100% rename from packages/openapi-to-ts/fixtures/openapi/pet-store.json rename to packages/openapi-to-ts/fixtures/openapi/3.0/pet-store.json diff --git a/packages/openapi-to-ts/fixtures/openapi/3.0/petstore-expanded.json b/packages/openapi-to-ts/fixtures/openapi/3.0/petstore-expanded.json new file mode 100644 index 0000000..1f67c0f --- /dev/null +++ b/packages/openapi-to-ts/fixtures/openapi/3.0/petstore-expanded.json @@ -0,0 +1,235 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "description": "A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "Swagger API Team", + "email": "apiteam@swagger.io", + "url": "http://swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "servers": [ + { + "url": "http://petstore.swagger.io/api" + } + ], + "paths": { + "/pets": { + "get": { + "description": "Returns all pets from the system that the user has access to\nNam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.\n\nSed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.", + "operationId": "findPets", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "tags to filter by", + "required": false, + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "limit", + "in": "query", + "description": "maximum number of results to return", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "description": "Creates a new pet in the store. Duplicates are allowed", + "operationId": "addPet", + "requestBody": { + "description": "Pet to add to the store", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewPet" + } + } + } + }, + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/pets/{id}": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "find pet by id", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to fetch", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "description": "deletes a single pet based on the ID supplied", + "operationId": "deletePet", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "204": { + "description": "pet deleted" + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Pet": { + "allOf": [ + { + "$ref": "#/components/schemas/NewPet" + }, + { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "integer", + "format": "int64" + } + } + } + ] + }, + "NewPet": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "required": ["code", "message"], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } +} diff --git a/packages/openapi-to-ts/fixtures/openapi/3.0/readme.json b/packages/openapi-to-ts/fixtures/openapi/3.0/readme.json new file mode 100644 index 0000000..7d7961f --- /dev/null +++ b/packages/openapi-to-ts/fixtures/openapi/3.0/readme.json @@ -0,0 +1,2965 @@ +{ + "openapi": "3.0.2", + "info": { + "description": "Create beautiful product and API documentation with our developer friendly platform.", + "version": "4.355.0", + "title": "ReadMe API 🦉", + "contact": { + "name": "API Support", + "url": "https://docs.readme.com/main/docs/need-more-support", + "email": "support@readme.io" + } + }, + "servers": [ + { + "url": "https://dash.readme.com/api/v1" + } + ], + "tags": [ + { + "name": "API Registry" + }, + { + "name": "API Specification" + }, + { + "name": "Apply to ReadMe" + }, + { + "name": "Categories" + }, + { + "name": "Changelog" + }, + { + "name": "Custom Pages" + }, + { + "name": "Docs" + }, + { + "name": "Errors" + }, + { + "name": "Projects" + }, + { + "name": "Version" + } + ], + "paths": { + "/api-registry/{uuid}": { + "get": { + "operationId": "getAPIRegistry", + "summary": "Retrieve an entry from the API Registry", + "description": "Get an API definition file that's been uploaded to ReadMe.", + "tags": ["API Registry"], + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "An API Registry UUID. This can be found by navigating to your API Reference page and viewing code snippets for Node with the `api` library.", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully retrieved API registry entry.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "404": { + "$ref": "#/components/responses/error_REGISTRY_NOTFOUND" + } + } + } + }, + "/api-specification": { + "get": { + "operationId": "getAPISpecification", + "summary": "Get metadata", + "description": "Get API specification metadata.", + "tags": ["API Specification"], + "parameters": [ + { + "$ref": "#/components/parameters/perPage" + }, + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "responses": { + "200": { + "description": "Successfully retrieved API specification metadata.", + "headers": { + "Link": { + "$ref": "#/components/headers/link" + }, + "x-total-count": { + "$ref": "#/components/headers/x-total-count" + } + } + }, + "400": { + "$ref": "#/components/responses/error_VERSION_EMPTY" + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_VERSION_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "post": { + "operationId": "uploadAPISpecification", + "summary": "Upload specification", + "description": "Upload an API specification to ReadMe. Or, to use a newer solution see https://docs.readme.com/main/docs/rdme.", + "tags": ["API Specification"], + "parameters": [ + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "spec": { + "description": "OpenAPI/Swagger file. We accept JSON or YAML.", + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "The API specification was successfully uploaded." + }, + "400": { + "description": "There was a validation error during upload.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/error_SPEC_FILE_EMPTY" + }, + { + "$ref": "#/components/schemas/error_SPEC_INVALID" + }, + { + "$ref": "#/components/schemas/error_SPEC_INVALID_SCHEMA" + }, + { + "$ref": "#/components/schemas/error_SPEC_VERSION_NOTFOUND" + } + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "408": { + "$ref": "#/components/responses/error_SPEC_TIMEOUT" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/api-specification/{id}": { + "put": { + "operationId": "updateAPISpecification", + "summary": "Update specification", + "description": "Update an API specification in ReadMe.", + "tags": ["API Specification"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the API specification. The unique ID for each API can be found by navigating to your **API Definitions** page.", + "schema": { + "type": "string" + }, + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "spec": { + "description": "OpenAPI/Swagger file. We accept JSON or YAML.", + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The API specification was updated." + }, + "400": { + "description": "There was a validation error during upload.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/error_SPEC_FILE_EMPTY" + }, + { + "$ref": "#/components/schemas/error_SPEC_ID_DUPLICATE" + }, + { + "$ref": "#/components/schemas/error_SPEC_ID_INVALID" + }, + { + "$ref": "#/components/schemas/error_SPEC_INVALID" + }, + { + "$ref": "#/components/schemas/error_SPEC_INVALID_SCHEMA" + }, + { + "$ref": "#/components/schemas/error_SPEC_VERSION_NOTFOUND" + } + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "description": "There is no API specification with that ID." + }, + "408": { + "$ref": "#/components/responses/error_SPEC_TIMEOUT" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "delete": { + "operationId": "deleteAPISpecification", + "summary": "Delete specification", + "description": "Delete an API specification in ReadMe.", + "tags": ["API Specification"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the API specification. The unique ID for each API can be found by navigating to your **API Definitions** page.", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "The API specification was deleted." + }, + "400": { + "$ref": "#/components/responses/error_SPEC_ID_INVALID" + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_SPEC_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/apply": { + "get": { + "operationId": "getOpenRoles", + "summary": "Get open roles", + "description": "Returns all the roles we're hiring for at ReadMe!", + "tags": ["Apply to ReadMe"], + "responses": { + "200": { + "description": "All the roles that we're hiring for.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/jobOpening" + } + } + } + } + } + } + }, + "post": { + "operationId": "applyToReadMe", + "summary": "Submit your application!", + "description": "This endpoint will let you apply to a job at ReadMe programatically, without having to go through our UI!", + "tags": ["Apply to ReadMe"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/apply" + } + } + } + }, + "responses": { + "200": { + "description": "You did it!" + } + } + } + }, + "/categories": { + "get": { + "operationId": "getCategories", + "summary": "Get all categories", + "description": "Returns all the categories for a specified version.", + "tags": ["Categories"], + "parameters": [ + { + "$ref": "#/components/parameters/x-readme-version" + }, + { + "$ref": "#/components/parameters/perPage" + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "The list of categories.", + "headers": { + "Link": { + "$ref": "#/components/headers/link" + }, + "x-total-count": { + "$ref": "#/components/headers/x-total-count" + } + } + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "post": { + "operationId": "createCategory", + "summary": "Create category", + "description": "Create a new category inside of this project.", + "tags": ["Categories"], + "parameters": [ + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/category" + }, + { + "required": ["title"] + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The category has successfully been created." + }, + "400": { + "$ref": "#/components/responses/error_CATEGORY_INVALID" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/categories/{slug}": { + "get": { + "operationId": "getCategory", + "summary": "Get category", + "description": "Returns the category with this slug.", + "tags": ["Categories"], + "parameters": [ + { + "name": "slug", + "in": "path", + "description": "A URL-safe representation of the category title. Slugs must be all lowercase, and replace spaces with hyphens. For example, for the category \"Getting Started\", enter the slug \"getting-started\".", + "example": "getting-started", + "required": true, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "responses": { + "200": { + "description": "The category exists and has been returned." + }, + "404": { + "$ref": "#/components/responses/error_CATEGORY_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "put": { + "operationId": "updateCategory", + "summary": "Update category", + "description": "Change the properties of a category.", + "tags": ["Categories"], + "parameters": [ + { + "name": "slug", + "in": "path", + "description": "A URL-safe representation of the category title. Slugs must be all lowercase, and replace spaces with hyphens. For example, for the category \"Getting Started\", enter the slug \"getting-started\".", + "example": "getting-started", + "required": true, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/category" + } + } + } + }, + "responses": { + "200": { + "description": "The category was successfully updated." + }, + "400": { + "$ref": "#/components/responses/error_CATEGORY_INVALID" + }, + "404": { + "$ref": "#/components/responses/error_CATEGORY_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "delete": { + "operationId": "deleteCategory", + "summary": "Delete category", + "description": "Delete the category with this slug.\n>⚠️Heads Up!\n> This will also delete all of the docs within this category.", + "tags": ["Categories"], + "parameters": [ + { + "name": "slug", + "in": "path", + "description": "A URL-safe representation of the category title. Slugs must be all lowercase, and replace spaces with hyphens. For example, for the category \"Getting Started\", enter the slug \"getting-started\".", + "example": "getting-started", + "required": true, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "responses": { + "204": { + "description": "The category was deleted." + }, + "404": { + "$ref": "#/components/responses/error_CATEGORY_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/categories/{slug}/docs": { + "get": { + "operationId": "getCategoryDocs", + "summary": "Get docs for category", + "description": "Returns the docs and children docs within this category.", + "tags": ["Categories"], + "parameters": [ + { + "name": "slug", + "in": "path", + "description": "A URL-safe representation of the category title. Slugs must be all lowercase, and replace spaces with hyphens. For example, for the category \"Getting Started\", enter the slug \"getting-started\".", + "example": "getting-started", + "required": true, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "responses": { + "200": { + "description": "The category exists and all of the docs have been returned." + }, + "404": { + "$ref": "#/components/responses/error_CATEGORY_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/changelogs": { + "get": { + "operationId": "getChangelogs", + "summary": "Get changelogs", + "description": "Returns a list of changelogs.", + "tags": ["Changelog"], + "parameters": [ + { + "$ref": "#/components/parameters/perPage" + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "The list of changelogs.", + "headers": { + "Link": { + "$ref": "#/components/headers/link" + }, + "x-total-count": { + "$ref": "#/components/headers/x-total-count" + } + } + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "post": { + "operationId": "createChangelog", + "summary": "Create changelog", + "description": "Create a new changelog entry.", + "tags": ["Changelog"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/changelog" + } + } + } + }, + "responses": { + "201": { + "description": "The changelog was successfully created." + }, + "400": { + "description": "There was a validation error during creation." + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/changelogs/{slug}": { + "get": { + "operationId": "getChangelog", + "summary": "Get changelog", + "description": "Returns the changelog with this slug.", + "tags": ["Changelog"], + "parameters": [ + { + "name": "slug", + "in": "path", + "description": "A URL-safe representation of the changelog title. Slugs must be all lowercase, and replace spaces with hyphens. For example, for the changelog \"Owlet Update\", enter the slug \"owlet-update\".", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The changelog exists and has been returned." + }, + "404": { + "description": "There is no changelog with that slug." + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "put": { + "operationId": "updateChangelog", + "summary": "Update changelog", + "description": "Update a changelog with this slug.", + "tags": ["Changelog"], + "parameters": [ + { + "name": "slug", + "in": "path", + "description": "A URL-safe representation of the changelog title. Slugs must be all lowercase, and replace spaces with hyphens. For example, for the changelog \"Owlet Weekly Update\", enter the slug \"owlet-weekly-update\".", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/changelog" + } + } + } + }, + "responses": { + "200": { + "description": "The changelog was successfully updated." + }, + "400": { + "description": "There was a validation error during update." + }, + "404": { + "description": "There is no changelog with that slug." + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "delete": { + "operationId": "deleteChangelog", + "summary": "Delete changelog", + "description": "Delete the changelog with this slug.", + "tags": ["Changelog"], + "parameters": [ + { + "name": "slug", + "in": "path", + "description": "A URL-safe representation of the changelog title. Slugs must be all lowercase, and replace spaces with hyphens. For example, for the changelog \"Owlet Weekly Update\", enter the slug \"owlet-weekly-update\".", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The changelog was successfully updated." + }, + "404": { + "description": "There is no changelog with that slug." + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/custompages": { + "get": { + "operationId": "getCustomPages", + "summary": "Get custom pages", + "description": "Returns a list of custom pages.", + "tags": ["Custom Pages"], + "parameters": [ + { + "$ref": "#/components/parameters/perPage" + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "The list of custom pages.", + "headers": { + "Link": { + "$ref": "#/components/headers/link" + }, + "x-total-count": { + "$ref": "#/components/headers/x-total-count" + } + } + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "post": { + "operationId": "createCustomPage", + "summary": "Create custom page", + "description": "Create a new custom page inside of this project.", + "tags": ["Custom Pages"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/customPage" + } + } + } + }, + "responses": { + "201": { + "description": "The custom page was successfully created." + }, + "400": { + "$ref": "#/components/responses/error_CUSTOMPAGE_INVALID" + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/custompages/{slug}": { + "get": { + "operationId": "getCustomPage", + "summary": "Get custom page", + "description": "Returns the custom page with this slug.", + "tags": ["Custom Pages"], + "parameters": [ + { + "$ref": "#/components/parameters/slug" + } + ], + "responses": { + "200": { + "description": "The custom page exists and has been returned." + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_CUSTOMPAGE_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "put": { + "operationId": "updateCustomPage", + "summary": "Update custom page", + "description": "Update a custom page with this slug.", + "tags": ["Custom Pages"], + "parameters": [ + { + "$ref": "#/components/parameters/slug" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/customPage" + } + } + } + }, + "responses": { + "200": { + "description": "The custom page was successfully updated." + }, + "400": { + "$ref": "#/components/responses/error_CUSTOMPAGE_INVALID" + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_CUSTOMPAGE_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "delete": { + "operationId": "deleteCustomPage", + "summary": "Delete custom page", + "description": "Delete the custom page with this slug.", + "tags": ["Custom Pages"], + "parameters": [ + { + "$ref": "#/components/parameters/slug" + } + ], + "responses": { + "204": { + "description": "The custom page was successfully updated." + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_CUSTOMPAGE_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/docs/{slug}": { + "get": { + "operationId": "getDoc", + "summary": "Get doc", + "description": "Returns the doc with this slug.", + "tags": ["Docs"], + "parameters": [ + { + "$ref": "#/components/parameters/slug" + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "responses": { + "200": { + "description": "The doc exists and has been returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/docSchemaResponse" + } + } + } + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_DOC_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "put": { + "operationId": "updateDoc", + "summary": "Update doc", + "description": "Update a doc with this slug.", + "tags": ["Docs"], + "parameters": [ + { + "$ref": "#/components/parameters/slug" + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/docSchemaPut" + } + } + } + }, + "responses": { + "200": { + "description": "The doc was successfully updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/docSchemaResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/error_DOC_INVALID" + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_DOC_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "delete": { + "operationId": "deleteDoc", + "summary": "Delete doc", + "description": "Delete the doc with this slug.", + "tags": ["Docs"], + "parameters": [ + { + "$ref": "#/components/parameters/slug" + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "responses": { + "204": { + "description": "The doc was successfully updated." + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_DOC_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/docs/{slug}/production": { + "get": { + "operationId": "getProductionDoc", + "summary": "Get production doc", + "description": "This is intended for use by enterprise users with staging enabled. This endpoint will return the live version of your document, whereas the standard endpoint will always return staging.", + "tags": ["Docs"], + "parameters": [ + { + "$ref": "#/components/parameters/slug" + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "responses": { + "200": { + "description": "The doc exists and has been returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/docSchemaResponse" + } + } + } + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_DOC_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/docs": { + "post": { + "operationId": "createDoc", + "summary": "Create doc", + "description": "Create a new doc inside of this project.", + "tags": ["Docs"], + "parameters": [ + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/docSchemaPost" + } + } + } + }, + "responses": { + "201": { + "description": "The doc was successfully created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/docSchemaResponse" + } + } + } + }, + "400": { + "$ref": "#/components/responses/error_DOC_INVALID" + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/docs/search": { + "post": { + "operationId": "searchDocs", + "summary": "Search docs", + "description": "Returns all docs that match the search.", + "tags": ["Docs"], + "parameters": [ + { + "name": "search", + "in": "query", + "description": "Search string to look for.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/x-readme-version" + } + ], + "responses": { + "200": { + "description": "The search was successful and results were returned." + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/": { + "get": { + "operationId": "getProject", + "summary": "Get metadata about the current project", + "description": "Returns project data for the API key.", + "tags": ["Projects"], + "responses": { + "200": { + "description": "Project data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/condensedProjectData" + } + } + } + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/schema": { + "get": { + "operationId": "getAPISchema", + "summary": "Get our OpenAPI Definition", + "description": "Returns a copy of our OpenAPI Definition.", + "tags": ["API Specification"], + "responses": { + "200": { + "description": "OpenAPI Definition data", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + } + } + }, + "/version": { + "get": { + "operationId": "getVersions", + "summary": "Get versions", + "description": "Retrieve a list of versions associated with a project API key.", + "tags": ["Version"], + "responses": { + "200": { + "description": "A list of versions." + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "post": { + "operationId": "createVersion", + "summary": "Create version", + "description": "Create a new version.", + "tags": ["Version"], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/version" + } + } + } + }, + "responses": { + "200": { + "description": "The version was successfully created." + }, + "400": { + "description": "There was a validation error during creation.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/error_VERSION_EMPTY" + }, + { + "$ref": "#/components/schemas/error_VERSION_DUPLICATE" + }, + { + "$ref": "#/components/schemas/error_VERSION_FORK_EMPTY" + } + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_VERSION_FORK_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + }, + "/version/{versionId}": { + "get": { + "operationId": "getVersion", + "summary": "Get version", + "description": "Returns the version with this version ID.", + "tags": ["Version"], + "parameters": [ + { + "$ref": "#/components/parameters/versionId" + } + ], + "responses": { + "200": { + "description": "The version exists and has been returned." + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_VERSION_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "put": { + "operationId": "updateVersion", + "summary": "Update version", + "description": "Update an existing version.", + "tags": ["Version"], + "parameters": [ + { + "$ref": "#/components/parameters/versionId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/version" + } + } + } + }, + "responses": { + "200": { + "description": "The version was successfully updated." + }, + "400": { + "$ref": "#/components/responses/error_VERSION_CANT_DEMOTE_STABLE" + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_VERSION_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + }, + "delete": { + "operationId": "deleteVersion", + "summary": "Delete version", + "description": "Delete a version", + "tags": ["Version"], + "parameters": [ + { + "$ref": "#/components/parameters/versionId" + } + ], + "responses": { + "200": { + "description": "The version was successfully deleted." + }, + "400": { + "$ref": "#/components/responses/error_VERSION_CANT_REMOVE_STABLE" + }, + "401": { + "$ref": "#/components/responses/authUnauthorized" + }, + "403": { + "$ref": "#/components/responses/authForbidden" + }, + "404": { + "$ref": "#/components/responses/error_VERSION_NOTFOUND" + } + }, + "security": [ + { + "apiKey": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "apiKey": { + "type": "http", + "scheme": "basic" + } + }, + "headers": { + "link": { + "description": "Pagination information. See https://docs.readme.com/main/reference/pagination for more information.", + "schema": { + "type": "string" + } + }, + "x-total-count": { + "description": "The total amount of results, ignoring pagination. See https://docs.readme.com/main/reference/pagination for more information about pagination.", + "schema": { + "type": "string" + } + } + }, + "parameters": { + "slug": { + "name": "slug", + "in": "path", + "description": "A URL-safe representation of the page title. Slugs must be all lowercase, and replace spaces with hyphens. For example, for the title \"Getting Started\", enter the slug \"getting-started\".", + "required": true, + "schema": { + "type": "string" + } + }, + "page": { + "name": "page", + "in": "query", + "description": "Used to specify further pages (starts at 1).", + "schema": { + "type": "integer", + "default": 1, + "minimum": 1 + } + }, + "perPage": { + "name": "perPage", + "in": "query", + "description": "Number of items to include in pagination (up to 100, defaults to 10).", + "schema": { + "type": "integer", + "default": 10, + "minimum": 1, + "maximum": 100 + } + }, + "x-readme-version": { + "in": "header", + "name": "x-readme-version", + "description": "Version number of your docs project, for example, v3.0. By default the main project version is used. To see all valid versions for your docs project call https://docs.readme.com/main/reference/version#getversions.", + "example": "v3.0", + "required": false, + "schema": { + "type": "string" + } + }, + "versionId": { + "name": "versionId", + "in": "path", + "description": "Semver identifier for the project version. For best results, use the formatted `version_clean` value listed in the response from the [Get Versions endpoint](/reference/getversions).", + "example": "v1.0.0", + "required": true, + "schema": { + "type": "string" + } + } + }, + "responses": { + "authForbidden": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/error_APIKEY_MISMATCH" + } + ] + } + } + } + }, + "authUnauthorized": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/error_APIKEY_EMPTY" + }, + { + "$ref": "#/components/schemas/error_APIKEY_NOTFOUND" + } + ] + } + } + } + }, + "error_APIKEY_EMPTY": { + "description": "An API key was not supplied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_APIKEY_EMPTY" + } + } + } + }, + "error_APIKEY_MISMATCH": { + "description": "The API key doesn't match the project.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_APIKEY_MISMATCH" + } + } + } + }, + "error_APIKEY_NOTFOUND": { + "description": "The API key couldn't be located.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_APIKEY_NOTFOUND" + } + } + } + }, + "error_APPLY_INVALID_EMAIL": { + "description": "You need to provide a valid email.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_APPLY_INVALID_EMAIL" + } + } + } + }, + "error_APPLY_INVALID_JOB": { + "description": "You need to provide a job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_APPLY_INVALID_JOB" + } + } + } + }, + "error_APPLY_INVALID_NAME": { + "description": "You need to provide a name.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_APPLY_INVALID_NAME" + } + } + } + }, + "error_CATEGORY_INVALID": { + "description": "The category couldn't be saved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_CATEGORY_INVALID" + } + } + } + }, + "error_CATEGORY_NOTFOUND": { + "description": "The category couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_CATEGORY_NOTFOUND" + } + } + } + }, + "error_CHANGELOG_INVALID": { + "description": "The changelog couldn't be saved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_CHANGELOG_INVALID" + } + } + } + }, + "error_CHANGELOG_NOTFOUND": { + "description": "The changelog couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_CHANGELOG_NOTFOUND" + } + } + } + }, + "error_CUSTOMPAGE_INVALID": { + "description": "The page couldn't be saved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_CUSTOMPAGE_INVALID" + } + } + } + }, + "error_CUSTOMPAGE_NOTFOUND": { + "description": "The custom page couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_CUSTOMPAGE_NOTFOUND" + } + } + } + }, + "error_DOC_INVALID": { + "description": "The doc couldn't be saved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_DOC_INVALID" + } + } + } + }, + "error_DOC_NOTFOUND": { + "description": "The doc couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_DOC_NOTFOUND" + } + } + } + }, + "error_ENDPOINT_NOTFOUND": { + "description": "The endpoint doesn't exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_ENDPOINT_NOTFOUND" + } + } + } + }, + "error_INTERNAL_ERROR": { + "description": "An unknown error has occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_INTERNAL_ERROR" + } + } + } + }, + "error_PROJECT_NEEDSSTAGING": { + "description": "The project does not have staging enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_PROJECT_NEEDSSTAGING" + } + } + } + }, + "error_PROJECT_NOTFOUND": { + "description": "The project couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_PROJECT_NOTFOUND" + } + } + } + }, + "error_RATE_LIMITED": { + "description": "The request has been rate limited.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_RATE_LIMITED" + } + } + } + }, + "error_REGISTRY_INVALID": { + "description": "The registry entry couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_REGISTRY_INVALID" + } + } + } + }, + "error_REGISTRY_NOTFOUND": { + "description": "The registry entry couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_REGISTRY_NOTFOUND" + } + } + } + }, + "error_SPEC_FILE_EMPTY": { + "description": "A spec file wasn't included.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_SPEC_FILE_EMPTY" + } + } + } + }, + "error_SPEC_ID_DUPLICATE": { + "description": "The spec ID already tied to another version.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_SPEC_ID_DUPLICATE" + } + } + } + }, + "error_SPEC_ID_INVALID": { + "description": "The spec ID isn't valid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_SPEC_ID_INVALID" + } + } + } + }, + "error_SPEC_INVALID": { + "description": "The uploaded spec isn't valid JSON or YAML.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_SPEC_INVALID" + } + } + } + }, + "error_SPEC_INVALID_SCHEMA": { + "description": "The uploaded spec has OpenAPI validation errors.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_SPEC_INVALID_SCHEMA" + } + } + } + }, + "error_SPEC_NOTFOUND": { + "description": "The spec couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_SPEC_NOTFOUND" + } + } + } + }, + "error_SPEC_TIMEOUT": { + "description": "The spec upload timed out.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_SPEC_TIMEOUT" + } + } + } + }, + "error_SPEC_VERSION_NOTFOUND": { + "description": "The spec version couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_SPEC_VERSION_NOTFOUND" + } + } + } + }, + "error_UNEXPECTED_ERROR": { + "description": "An unknown error has occurred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_UNEXPECTED_ERROR" + } + } + } + }, + "error_VERSION_CANT_DEMOTE_STABLE": { + "description": "A stable version can't be demoted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_VERSION_CANT_DEMOTE_STABLE" + } + } + } + }, + "error_VERSION_CANT_REMOVE_STABLE": { + "description": "A stable version can't be removed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_VERSION_CANT_REMOVE_STABLE" + } + } + } + }, + "error_VERSION_DUPLICATE": { + "description": "The version already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_VERSION_DUPLICATE" + } + } + } + }, + "error_VERSION_EMPTY": { + "description": "No version was supplied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_VERSION_EMPTY" + } + } + } + }, + "error_VERSION_FORK_EMPTY": { + "description": "New versions need to be forked from an existing version.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_VERSION_FORK_EMPTY" + } + } + } + }, + "error_VERSION_FORK_NOTFOUND": { + "description": "The version couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_VERSION_FORK_NOTFOUND" + } + } + } + }, + "error_VERSION_INVALID": { + "description": "The version is invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_VERSION_INVALID" + } + } + } + }, + "error_VERSION_NOTFOUND": { + "description": "The version couldn't be found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error_VERSION_NOTFOUND" + } + } + } + } + }, + "schemas": { + "baseError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "An error code unique to the error received." + }, + "message": { + "type": "string", + "description": "The reason why the error occured." + }, + "suggestion": { + "type": "string", + "description": "A helpful suggestion for how to alleviate the error." + }, + "docs": { + "type": "string", + "format": "url", + "description": "A [ReadMe Metrics](https://readme.com/metrics/) log URL where you can see more information the request that you made. If we have metrics URLs unavailable for your request, this URL will be a URL to our API Reference.", + "example": "https://docs.readme.com/logs/6883d0ee-cf79-447a-826f-a48f7d5bdf5f" + }, + "help": { + "type": "string", + "description": "Information on where you can receive additional assistance from our wonderful support team.", + "example": "If you need help, email support@readme.io" + }, + "poem": { + "type": "array", + "description": "A short poem we wrote you about your error.", + "items": { + "type": "string" + }, + "example": [ + "If you're seeing this error,", + "Things didn't quite go the way we hoped.", + "When we tried to process your request,", + "Maybe trying again it'll work—who knows!" + ] + } + } + }, + "apply": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Your full name", + "default": "Your Name" + }, + "email": { + "type": "string", + "format": "email", + "description": "A valid email we can reach you at.", + "default": "you@example.com" + }, + "job": { + "type": "string", + "description": "The job you're looking to apply for (https://readme.com/careers).", + "enum": [ + "Front End Engineer", + "Full Stack Engineer", + "Head of Product", + "Head of Solutions Engineering", + "Product Designer" + ], + "default": "Front End Engineer" + }, + "pronouns": { + "type": "string", + "description": "Learn more at https://lgbtlifecenter.org/pronouns/" + }, + "linkedin": { + "type": "string", + "format": "url", + "description": "What have you been up to the past few years?" + }, + "github": { + "type": "string", + "description": "Or Bitbucket, Gitlab or anywhere else your code is hosted!", + "format": "url" + }, + "coverLetter": { + "type": "string", + "format": "blob", + "description": "What should we know about you?" + }, + "dontReallyApply": { + "type": "boolean", + "description": "Want to play with the API but not actually apply? Set this to true.", + "default": false + } + }, + "required": ["name", "email", "job"] + }, + "category": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "A short title for the category. This is what will show in the sidebar." + }, + "type": { + "type": "string", + "enum": ["reference", "guide"], + "default": "guide", + "description": "A category can be part of your reference or guide documentation, which is determined by this field." + } + } + }, + "changelog": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Title of the changelog." + }, + "type": { + "type": "string", + "default": "", + "enum": ["", "added", "fixed", "improved", "deprecated", "removed"] + }, + "body": { + "type": "string", + "description": "Body content of the changelog." + }, + "hidden": { + "type": "boolean", + "description": "Visibility of the changelog.", + "default": true + } + }, + "required": ["title", "body"] + }, + "condensedProjectData": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "subdomain": { + "type": "string" + }, + "jwtSecret": { + "type": "string" + }, + "baseUrl": { + "type": "string", + "format": "url", + "description": "The base URL for the project. If the project is not running under a custom domain, it will be `https://projectSubdomain.readme.io`, otherwise it can either be or `https://example.com` or, in the case of an enterprise child project `https://example.com/projectSubdomain`." + }, + "plan": { + "type": "string" + } + } + }, + "customPage": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Title of the custom page." + }, + "body": { + "type": "string", + "description": "Body formatted in Markdown (displayed by default)." + }, + "html": { + "type": "string", + "description": "Body formatted in HTML (sanitized, only displayed if `htmlmode` is **true**)." + }, + "htmlmode": { + "type": "boolean", + "description": "**true** if `html` should be displayed, **false** if `body` should be displayed.", + "default": false + }, + "hidden": { + "type": "boolean", + "description": "Visibility of the custom page.", + "default": true + } + }, + "required": ["title"] + }, + "docSchemaPost": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Title of the page." + }, + "type": { + "type": "string", + "description": "Type of the page. The available types all show up under the /docs/ URL path of your docs project (also known as the \"guides\" section). Can be \"basic\" (most common), \"error\" (page desribing an API error), or \"link\" (page that redirects to an external link).", + "enum": ["basic", "error", "link"] + }, + "body": { + "type": "string", + "description": "Body content of the page, formatted in [ReadMe-flavored Markdown](https://docs.readme.com/rdmd/docs)." + }, + "category": { + "type": "string", + "description": "Category ID of the page, which you can get through [the **Get all categories** endpoint](https://docs.readme.com/main/reference/getcategories)." + }, + "hidden": { + "type": "boolean", + "description": "Visibility of the page." + }, + "order": { + "type": "integer", + "description": "The position of the page in your project sidebar.", + "example": 999 + }, + "parentDoc": { + "type": "string", + "description": "The parent doc's ID, if the page is a subpage." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The error code for docs with the \"error\" type." + } + } + }, + "categorySlug": { + "type": "string", + "description": "The slug of the category this page is associated with. You can get this through [the **Get all categories** endpoint](https://docs.readme.com/main/reference/getcategories). This field is an alternative to the `category` field." + }, + "parentDocSlug": { + "type": "string", + "description": "If this page is a subpage, this field will be the slug of the parent document. You can get this through https://docs.readme.com/main/reference/docs#getdoc. This field is an alternative to the `parentDoc` field." + } + }, + "oneOf": [ + { + "required": ["title", "category"], + "title": "`category` Parameter" + }, + { + "required": ["title", "categorySlug"], + "title": "`categorySlug` Parameter" + } + ], + "additionalProperties": true + }, + "docSchemaPut": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Title of the page." + }, + "type": { + "type": "string", + "description": "Type of the page. The available types all show up under the /docs/ URL path of your docs project (also known as the \"guides\" section). Can be \"basic\" (most common), \"error\" (page desribing an API error), or \"link\" (page that redirects to an external link).", + "enum": ["basic", "error", "link"] + }, + "body": { + "type": "string", + "description": "Body content of the page, formatted in [ReadMe-flavored Markdown](https://docs.readme.com/rdmd/docs)." + }, + "category": { + "type": "string", + "description": "Category ID of the page, which you can get through [the **Get all categories** endpoint](https://docs.readme.com/main/reference/getcategories)." + }, + "hidden": { + "type": "boolean", + "description": "Visibility of the page." + }, + "order": { + "type": "integer", + "description": "The position of the page in your project sidebar.", + "example": 999 + }, + "parentDoc": { + "type": "string", + "description": "The parent doc's ID, if the page is a subpage." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The error code for docs with the \"error\" type." + } + } + }, + "categorySlug": { + "type": "string", + "description": "The slug of the category this page is associated with. You can get this through [the **Get all categories** endpoint](https://docs.readme.com/main/reference/getcategories). This field is an alternative to the `category` field." + }, + "parentDocSlug": { + "type": "string", + "description": "If this page is a subpage, this field will be the slug of the parent document. You can get this through https://docs.readme.com/main/reference/docs#getdoc. This field is an alternative to the `parentDoc` field." + } + }, + "additionalProperties": true + }, + "docSchemaResponse": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Title of the page." + }, + "type": { + "type": "string", + "description": "Type of the page. The available types all show up under the /docs/ URL path of your docs project (also known as the \"guides\" section). Can be \"basic\" (most common), \"error\" (page desribing an API error), or \"link\" (page that redirects to an external link).", + "enum": ["basic", "error", "link"] + }, + "body": { + "type": "string", + "description": "Body content of the page, formatted in [ReadMe-flavored Markdown](https://docs.readme.com/rdmd/docs)." + }, + "category": { + "type": "string", + "description": "Category ID of the page, which you can get through [the **Get all categories** endpoint](https://docs.readme.com/main/reference/getcategories)." + }, + "hidden": { + "type": "boolean", + "description": "Visibility of the page." + }, + "order": { + "type": "integer", + "description": "The position of the page in your project sidebar.", + "example": 999 + }, + "parentDoc": { + "type": "string", + "description": "The parent doc's ID, if the page is a subpage." + }, + "error": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The error code for docs with the \"error\" type." + } + } + } + }, + "additionalProperties": true + }, + "version": { + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "Semantic Version" + }, + "codename": { + "type": "string", + "description": "Dubbed name of version." + }, + "from": { + "type": "string", + "description": "Semantic Version to use as the base fork." + }, + "is_stable": { + "type": "boolean", + "description": "Should this be the **main** version?" + }, + "is_beta": { + "type": "boolean", + "default": true + }, + "is_hidden": { + "type": "boolean", + "description": "Should this be publically accessible?" + }, + "is_deprecated": { + "type": "boolean", + "description": "Should this be deprecated? Only allowed in PUT operations." + } + }, + "required": ["version", "from"] + }, + "jobOpening": { + "type": "object", + "properties": { + "slug": { + "type": "string", + "description": "A slugified version of the job opening title.", + "example": "api-engineer" + }, + "title": { + "type": "string", + "description": "The job opening position.", + "example": "API Engineer" + }, + "description": { + "type": "string", + "description": "The description for this open position. This content is formatted as HTML." + }, + "pullquote": { + "type": "string", + "description": "A short pullquote for the open position.", + "example": "Deeply knowledgeable of the web, HTTP, and the API space." + }, + "location": { + "type": "string", + "description": "Where this position is located at.", + "example": "Remote" + }, + "department": { + "type": "string", + "description": "The internal organization you'll be working in.", + "example": "Engineering" + }, + "url": { + "type": "string", + "format": "url", + "description": "The place where you can apply for the position!" + } + } + }, + "error_APIKEY_EMPTY": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "APIKEY_EMPTY" + } + } + } + ] + }, + "error_APIKEY_MISMATCH": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "APIKEY_MISMATCH" + } + } + } + ] + }, + "error_APIKEY_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "APIKEY_NOTFOUND" + } + } + } + ] + }, + "error_APPLY_INVALID_EMAIL": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "APPLY_INVALID_EMAIL" + } + } + } + ] + }, + "error_APPLY_INVALID_JOB": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "APPLY_INVALID_JOB" + } + } + } + ] + }, + "error_APPLY_INVALID_NAME": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "APPLY_INVALID_NAME" + } + } + } + ] + }, + "error_CATEGORY_INVALID": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "CATEGORY_INVALID" + } + } + } + ] + }, + "error_CATEGORY_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "CATEGORY_NOTFOUND" + } + } + } + ] + }, + "error_CHANGELOG_INVALID": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "CHANGELOG_INVALID" + } + } + } + ] + }, + "error_CHANGELOG_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "CHANGELOG_NOTFOUND" + } + } + } + ] + }, + "error_CUSTOMPAGE_INVALID": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "CUSTOMPAGE_INVALID" + } + } + } + ] + }, + "error_CUSTOMPAGE_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "CUSTOMPAGE_NOTFOUND" + } + } + } + ] + }, + "error_DOC_INVALID": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "DOC_INVALID" + } + } + } + ] + }, + "error_DOC_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "DOC_NOTFOUND" + } + } + } + ] + }, + "error_ENDPOINT_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "ENDPOINT_NOTFOUND" + } + } + } + ] + }, + "error_INTERNAL_ERROR": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "INTERNAL_ERROR" + } + } + } + ] + }, + "error_PROJECT_NEEDSSTAGING": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "PROJECT_NEEDSSTAGING" + } + } + } + ] + }, + "error_PROJECT_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "PROJECT_NOTFOUND" + } + } + } + ] + }, + "error_RATE_LIMITED": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "RATE_LIMITED" + } + } + } + ] + }, + "error_REGISTRY_INVALID": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "REGISTRY_INVALID" + } + } + } + ] + }, + "error_REGISTRY_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "REGISTRY_NOTFOUND" + } + } + } + ] + }, + "error_SPEC_FILE_EMPTY": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "SPEC_FILE_EMPTY" + } + } + } + ] + }, + "error_SPEC_ID_DUPLICATE": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "SPEC_ID_DUPLICATE" + } + } + } + ] + }, + "error_SPEC_ID_INVALID": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "SPEC_ID_INVALID" + } + } + } + ] + }, + "error_SPEC_INVALID": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "SPEC_INVALID" + } + } + } + ] + }, + "error_SPEC_INVALID_SCHEMA": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "SPEC_INVALID_SCHEMA" + } + } + } + ] + }, + "error_SPEC_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "SPEC_NOTFOUND" + } + } + } + ] + }, + "error_SPEC_TIMEOUT": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "SPEC_TIMEOUT" + } + } + } + ] + }, + "error_SPEC_VERSION_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "SPEC_VERSION_NOTFOUND" + } + } + } + ] + }, + "error_UNEXPECTED_ERROR": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "UNEXPECTED_ERROR" + } + } + } + ] + }, + "error_VERSION_CANT_DEMOTE_STABLE": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "VERSION_CANT_DEMOTE_STABLE" + } + } + } + ] + }, + "error_VERSION_CANT_REMOVE_STABLE": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "VERSION_CANT_REMOVE_STABLE" + } + } + } + ] + }, + "error_VERSION_DUPLICATE": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "VERSION_DUPLICATE" + } + } + } + ] + }, + "error_VERSION_EMPTY": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "VERSION_EMPTY" + } + } + } + ] + }, + "error_VERSION_FORK_EMPTY": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "VERSION_FORK_EMPTY" + } + } + } + ] + }, + "error_VERSION_FORK_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "VERSION_FORK_NOTFOUND" + } + } + } + ] + }, + "error_VERSION_INVALID": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "VERSION_INVALID" + } + } + } + ] + }, + "error_VERSION_NOTFOUND": { + "allOf": [ + { + "$ref": "#/components/schemas/baseError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "default": "VERSION_NOTFOUND" + } + } + } + ] + } + } + } +} diff --git a/packages/openapi-to-ts/fixtures/openapi/3.0/security.json b/packages/openapi-to-ts/fixtures/openapi/3.0/security.json new file mode 100644 index 0000000..a641f1b --- /dev/null +++ b/packages/openapi-to-ts/fixtures/openapi/3.0/security.json @@ -0,0 +1,392 @@ +{ + "openapi": "3.0.3", + "info": { + "version": "1.0.0", + "title": "Support for different security types", + "description": "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#securitySchemeObject" + }, + "servers": [ + { + "url": "https://httpbin.org" + } + ], + "tags": [ + { + "name": "API Key" + }, + { + "name": "HTTP" + }, + { + "name": "OAuth 2" + }, + { + "name": "OpenID Connect" + }, + { + "name": "Other" + } + ], + "paths": { + "/anything/apiKey": { + "get": { + "summary": "Query parameter", + "description": "`apiKey` auth will be supplied within an `apiKey` query parameter.", + "tags": ["API Key"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey_query": [] + } + ] + }, + "post": { + "summary": "Cookie", + "description": "`apiKey` auth will be supplied within an `api_key` cookie.", + "tags": ["API Key"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey_cookie": [] + } + ] + }, + "put": { + "summary": "Header", + "description": "`apiKey` auth will be supplied within an `X-API-KEY` header.", + "tags": ["API Key"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey_header": [] + } + ] + } + }, + "/anything/basic": { + "post": { + "summary": "Basic", + "description": "Authentication credentials will be supplied within a `Basic` `Authorization` header.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample", + "tags": ["HTTP"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "basic": [] + } + ] + } + }, + "/anything/bearer": { + "post": { + "summary": "Bearer", + "description": "Authentication credentials will be supplied within a `Bearer` `Authorization` header.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample", + "tags": ["HTTP"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "bearer": [] + } + ] + }, + "put": { + "summary": "Bearer (`jwt` format)", + "description": "Authentication credentials will be supplied within a `Bearer` `Authorization` header, but its data should be controlled as a JWT.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample\n\n> ℹ️\n> We currently do not support any special handling for this so they're handled as a standard `Bearer` authentication token.", + "tags": ["HTTP"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "bearer_jwt": [] + } + ] + } + }, + "/anything/oauth2": { + "post": { + "summary": "General support (all flow types)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2": ["write:things"] + } + ] + }, + "get": { + "summary": "General support (authorizationCode flow type)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2_authorizationCode": ["write:things"] + } + ] + }, + "put": { + "summary": "General support (clientCredentials flow type)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2_clientCredentials": ["write:things"] + } + ] + }, + "patch": { + "summary": "General support (implicit flow type)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2_implicit": ["write:things"] + } + ] + }, + "delete": { + "summary": "General support (password flow type)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2_password": ["write:things"] + } + ] + } + }, + "/anything/openIdConnect": { + "post": { + "summary": "General support", + "description": "🚧 This is not supported.", + "tags": ["OpenID Connect"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "openIdConnect": [] + } + ] + } + }, + "/anything/no-auth": { + "post": { + "summary": "No auth requirements", + "description": "This operation does not have any authentication requirements.", + "tags": ["Other"], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/anything/optional-auth": { + "get": { + "summary": "Optional auth", + "description": "The `apiKey` query parameter auth on this operation is optional.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object", + "tags": ["Other"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey_query": [] + }, + {} + ] + } + }, + "/status/401": { + "post": { + "summary": "Forced invalid authentication", + "description": "This endpoint requires an authentication header but making any request to it will forcefully return a 401 status code for invalid auth.", + "tags": ["Other"], + "responses": { + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "apiKey_header": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "apiKey_cookie": { + "type": "apiKey", + "in": "cookie", + "name": "api_key", + "description": "An API key that will be supplied in a named cookie. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object" + }, + "apiKey_header": { + "type": "apiKey", + "in": "header", + "name": "X-API-KEY", + "description": "An API key that will be supplied in a named header. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object" + }, + "apiKey_query": { + "type": "apiKey", + "in": "query", + "name": "apiKey", + "description": "An API key that will be supplied in a named query parameter. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object" + }, + "basic": { + "type": "http", + "scheme": "basic", + "description": "Basic auth that takes a base64'd combination of `user:password`. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample" + }, + "bearer": { + "type": "http", + "scheme": "bearer", + "description": "A bearer token that will be supplied within an `Authentication` header as `bearer `. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#basic-authentication-sample" + }, + "bearer_jwt": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "A bearer token that will be supplied within an `Authentication` header as `bearer `. In this case, the format of the token is specified as JWT. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#jwt-bearer-sample" + }, + "oauth2": { + "type": "oauth2", + "description": "An OAuth 2 security flow. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23", + "flows": { + "authorizationCode": { + "authorizationUrl": "http://example.com/oauth/dialog", + "tokenUrl": "http://example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + }, + "clientCredentials": { + "tokenUrl": "http://example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + }, + "implicit": { + "authorizationUrl": "http://example.com/oauth/dialog", + "scopes": { + "write:things": "Add things to your account" + } + }, + "password": { + "tokenUrl": "http://example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "oauth2_authorizationCode": { + "type": "oauth2", + "description": "An OAuth 2 security flow that only supports the `authorizationCode` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object", + "flows": { + "authorizationCode": { + "authorizationUrl": "http://alt.example.com/oauth/dialog", + "tokenUrl": "http://alt.example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "oauth2_clientCredentials": { + "type": "oauth2", + "description": "An OAuth 2 security flow that only supports the `clientCredentials` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object", + "flows": { + "clientCredentials": { + "tokenUrl": "http://alt.example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "oauth2_implicit": { + "type": "oauth2", + "description": "An OAuth 2 security flow that only supports the `implicit` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object", + "flows": { + "implicit": { + "authorizationUrl": "http://alt.example.com/oauth/dialog", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "oauth2_password": { + "type": "oauth2", + "description": "An OAuth 2 security flow that only supports the `password` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#oauth-flows-object", + "flows": { + "password": { + "tokenUrl": "http://alt.example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "openIdConnect": { + "type": "openIdConnect", + "openIdConnectUrl": "https://example.com/.well-known/openid-configuration", + "description": "OpenAPI authentication. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23" + } + } + } +} diff --git a/packages/openapi-to-ts/fixtures/openapi/stripe.json b/packages/openapi-to-ts/fixtures/openapi/3.0/stripe.json similarity index 100% rename from packages/openapi-to-ts/fixtures/openapi/stripe.json rename to packages/openapi-to-ts/fixtures/openapi/3.0/stripe.json diff --git a/packages/openapi-to-ts/fixtures/openapi/tic-tac-toe.json b/packages/openapi-to-ts/fixtures/openapi/3.0/tic-tac-toe.json similarity index 100% rename from packages/openapi-to-ts/fixtures/openapi/tic-tac-toe.json rename to packages/openapi-to-ts/fixtures/openapi/3.0/tic-tac-toe.json diff --git a/packages/openapi-to-ts/fixtures/openapi/3.1/petstore.json b/packages/openapi-to-ts/fixtures/openapi/3.1/petstore.json new file mode 100644 index 0000000..6c24125 --- /dev/null +++ b/packages/openapi-to-ts/fixtures/openapi/3.1/petstore.json @@ -0,0 +1,988 @@ +{ + "openapi": "3.1.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v2" + } + ], + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "paths": { + "/pet": { + "post": { + "tags": ["pet"], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "parameters": [], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Pet" + } + }, + "put": { + "tags": ["pet"], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "parameters": [], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/Pet" + } + } + }, + "/pet/findByStatus": { + "get": { + "tags": ["pet"], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": ["available", "pending", "sold"], + "default": "available" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": ["pet"], + "summary": "Finds Pets by tags", + "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ], + "deprecated": true + } + }, + "/pet/{petId}": { + "get": { + "tags": ["pet"], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "default": { + "description": "successful response" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": ["pet"], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "name": { + "description": "Updated name of the pet", + "type": "string" + }, + "status": { + "description": "Updated status of the pet", + "type": "string" + } + } + } + } + } + } + }, + "delete": { + "tags": ["pet"], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": ["pet"], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + } + }, + "security": [ + { + "petstore_auth": ["write:pets", "read:pets"] + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "/store/inventory": { + "get": { + "tags": ["store"], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "parameters": [], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": ["store"], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "parameters": [], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid Order" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + }, + "description": "order placed for purchasing the pet", + "required": true + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": ["store"], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 10 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": ["store"], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "minimum": 1 + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": ["user"], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "Created user object", + "required": true + } + } + }, + "/user/createWithArray": { + "post": { + "tags": ["user"], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/UserArray" + } + } + }, + "/user/createWithList": { + "post": { + "tags": ["user"], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/UserArray" + } + } + }, + "/user/login": { + "get": { + "tags": ["user"], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": ["user"], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": ["user"], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": ["user"], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be updated", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + }, + "description": "Updated user object", + "required": true + } + }, + "delete": { + "tags": ["user"], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": ["placed", "approved", "delivered"] + }, + "complete": { + "type": "boolean", + "default": false + } + }, + "xml": { + "name": "Order" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": ["name", "photoUrls"], + "properties": { + "id": { + "type": "integer", + "format": "int64", + "readOnly": true + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": ["available", "pending", "sold"] + } + }, + "xml": { + "name": "Pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "requestBodies": { + "Pet": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "description": "Pet object that needs to be added to the store", + "required": true + }, + "UserArray": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "description": "List of user object", + "required": true + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} diff --git a/packages/openapi-to-ts/fixtures/openapi/3.1/security.json b/packages/openapi-to-ts/fixtures/openapi/3.1/security.json new file mode 100644 index 0000000..e5fc59c --- /dev/null +++ b/packages/openapi-to-ts/fixtures/openapi/3.1/security.json @@ -0,0 +1,397 @@ +{ + "openapi": "3.1.0", + "info": { + "version": "1.0.0", + "title": "Support for different security types", + "description": "https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#securitySchemeObject" + }, + "servers": [ + { + "url": "https://httpbin.org" + } + ], + "tags": [ + { + "name": "API Key" + }, + { + "name": "HTTP" + }, + { + "name": "Mutual TLS" + }, + { + "name": "OAuth 2" + }, + { + "name": "OpenID Connect" + }, + { + "name": "Other" + } + ], + "paths": { + "/anything/apiKey": { + "get": { + "summary": "Query parameter", + "description": "`apiKey` auth will be supplied within an `apiKey` query parameter.", + "tags": ["API Key"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey_query": [] + } + ] + }, + "post": { + "summary": "Cookie", + "description": "`apiKey` auth will be supplied within an `api_key` cookie.", + "tags": ["API Key"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey_cookie": [] + } + ] + }, + "put": { + "summary": "Header", + "description": "`apiKey` auth will be supplied within an `X-API-KEY` header.", + "tags": ["API Key"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey_header": [] + } + ] + } + }, + "/anything/basic": { + "post": { + "summary": "Basic", + "description": "Authentication credentials will be supplied within a `Basic` `Authorization` header.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample", + "tags": ["HTTP"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "basic": [] + } + ] + } + }, + "/anything/bearer": { + "post": { + "summary": "Bearer", + "description": "Authentication credentials will be supplied within a `Bearer` `Authorization` header.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample", + "tags": ["HTTP"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "bearer": [] + } + ] + }, + "put": { + "summary": "Bearer (`jwt` format)", + "description": "Authentication credentials will be supplied within a `Bearer` `Authorization` header, but its data should be controlled as a JWT.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample\n\n> ℹ️We currently do not support any special handling for this so they're handled as a standard `Bearer` authentication token.", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "bearer_jwt": [] + } + ] + } + }, + "/anything/mutualTLS": { + "post": { + "summary": "`mutualTLS` auth", + "description": "🚧 This is not supported.", + "tags": ["Mutual TLS"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "mutualTLS": [] + } + ] + } + }, + "/anything/oauth2": { + "post": { + "summary": "General support (all flow types)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2": ["write:things"] + } + ] + }, + "get": { + "summary": "General support (authorizationCode flow type)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2_authorizationCode": ["write:things"] + } + ] + }, + "put": { + "summary": "General support (clientCredentials flow type)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2_clientCredentials": ["write:things"] + } + ] + }, + "patch": { + "summary": "General support (implicit flow type)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2_implicit": ["write:things"] + } + ] + }, + "delete": { + "summary": "General support (password flow type)", + "description": "> ℹ️\n> We currently do not handle OAuth 2 authentication flows so if an operation has an `oauth2` requirement we assume that the user, or the projects JWT, has a qualified `bearer` token and will use that.\n\nhttps://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23", + "tags": ["OAuth 2"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "oauth2_password": ["write:things"] + } + ] + } + }, + "/anything/openIdConnect": { + "post": { + "summary": "General support", + "description": "🚧 This is not supported.", + "tags": ["OpenID Connect"], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "openIdConnect": [] + } + ] + } + }, + "/anything/no-auth": { + "post": { + "summary": "No auth requirements", + "description": "This operation does not have any authentication requirements.", + "tags": ["Other"], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/status/401": { + "post": { + "summary": "Forced invalid authentication", + "description": "This endpoint requires an authentication header but making any request to it will forcefully return a 401 status code for invalid auth.", + "tags": ["Other"], + "responses": { + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "apiKey_header": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "apiKey_cookie": { + "type": "apiKey", + "in": "cookie", + "name": "api_key", + "description": "An API key that will be supplied in a named cookie. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object" + }, + "apiKey_header": { + "type": "apiKey", + "in": "header", + "name": "X-API-KEY", + "description": "An API key that will be supplied in a named header. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object" + }, + "apiKey_query": { + "type": "apiKey", + "in": "query", + "name": "apiKey", + "description": "An API key that will be supplied in a named query parameter. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object" + }, + "basic": { + "type": "http", + "scheme": "basic", + "description": "Basic auth that takes a base64'd combination of `user:password`. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample" + }, + "bearer": { + "type": "http", + "scheme": "bearer", + "description": "A bearer token that will be supplied within an `Authentication` header as `bearer `. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#basic-authentication-sample" + }, + "bearer_jwt": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "A bearer token that will be supplied within an `Authentication` header as `bearer `. In this case, the format of the token is specified as JWT. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#jwt-bearer-sample" + }, + "mutualTLS": { + "type": "mutualTLS", + "description": "Requires a specific mutual TLS certificate to use when making a HTTP request. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23" + }, + "oauth2": { + "type": "oauth2", + "description": "An OAuth 2 security flow. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23", + "flows": { + "authorizationCode": { + "authorizationUrl": "http://example.com/oauth/dialog", + "tokenUrl": "http://example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + }, + "clientCredentials": { + "tokenUrl": "http://example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + }, + "implicit": { + "authorizationUrl": "http://example.com/oauth/dialog", + "scopes": { + "write:things": "Add things to your account" + } + }, + "password": { + "tokenUrl": "http://example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "oauth2_authorizationCode": { + "type": "oauth2", + "description": "An OAuth 2 security flow that only supports the `authorizationCode` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oauth-flows-object", + "flows": { + "authorizationCode": { + "authorizationUrl": "http://alt.example.com/oauth/dialog", + "tokenUrl": "http://alt.example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "oauth2_clientCredentials": { + "type": "oauth2", + "description": "An OAuth 2 security flow that only supports the `clientCredentials` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oauth-flows-object", + "flows": { + "clientCredentials": { + "tokenUrl": "http://alt.example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "oauth2_implicit": { + "type": "oauth2", + "description": "An OAuth 2 security flow that only supports the `implicit` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oauth-flows-object", + "flows": { + "implicit": { + "authorizationUrl": "http://alt.example.com/oauth/dialog", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "oauth2_password": { + "type": "oauth2", + "description": "An OAuth 2 security flow that only supports the `password` flow type. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#oauth-flows-object", + "flows": { + "password": { + "tokenUrl": "http://alt.example.com/oauth/token", + "scopes": { + "write:things": "Add things to your account" + } + } + } + }, + "openIdConnect": { + "type": "openIdConnect", + "openIdConnectUrl": "https://example.com/.well-known/openid-configuration", + "description": "OpenAPI authentication. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-23" + } + } + } +} diff --git a/packages/openapi-to-ts/package.json b/packages/openapi-to-ts/package.json index f6525a4..73fbf8a 100644 --- a/packages/openapi-to-ts/package.json +++ b/packages/openapi-to-ts/package.json @@ -13,7 +13,7 @@ "types": "./dist/index.d.ts", "sideEffects": false, "bin": { - "openapi-to-ts": "./dist/index.js" + "openapi-to-ts": "./dist/generate-from-openapi.js" }, "files": [ "dist" diff --git a/packages/openapi-to-ts/src/generate.ts b/packages/openapi-to-ts/src/generate-ts-from-openapi.ts similarity index 96% rename from packages/openapi-to-ts/src/generate.ts rename to packages/openapi-to-ts/src/generate-ts-from-openapi.ts index affcb5f..fccb485 100644 --- a/packages/openapi-to-ts/src/generate.ts +++ b/packages/openapi-to-ts/src/generate-ts-from-openapi.ts @@ -11,6 +11,7 @@ import { execa } from 'execa' import { convertParametersToJSONSchema } from './openapi-parameters-to-json-schema' import { + camelCase, dereference, dereferenceFull, getAndResolve, @@ -40,26 +41,22 @@ const httpMethods = [ 'trace' ] as const -async function main() { - const pathToOpenApiSpec = - process.argv[2] ?? - path.join(dirname, '..', 'fixtures', 'openapi', 'notion.json') - assert(pathToOpenApiSpec, 'Missing path to OpenAPI spec') - +export async function generateTSFromOpenAPI(openapiFilePath: string) { const parser = new SwaggerParser() - const spec = (await parser.bundle(pathToOpenApiSpec)) as OpenAPIV3.Document + const spec = (await parser.bundle(openapiFilePath)) as OpenAPIV3.Document // | OpenAPIV3_1.Document if ( // TODO: make this less brittle spec.openapi !== '3.0.0' && spec.openapi !== '3.1.0' && - spec.openapi !== '3.1.1' + spec.openapi !== '3.1.1' && + spec.openapi.split('.')[0] !== '3' ) { throw new Error(`Unexpected OpenAPI version "${spec.openapi}"`) } - const openapiSpecName = path.basename(pathToOpenApiSpec, '.json') + const openapiSpecName = path.basename(openapiFilePath, '.json') assert( openapiSpecName.toLowerCase() === openapiSpecName, `OpenAPI spec name "${openapiSpecName}" must be in kebab case` @@ -148,7 +145,7 @@ async function main() { requestBodyFormDataJSONSchemaPaths ] - const operationIds = new Set() + const operationNames = new Set() const operationSchemas: Record = {} const componentSchemas: Record = {} const aiClientMethods: string[] = [] @@ -168,18 +165,19 @@ async function main() { continue } - const operationName = - // TODO: better camelCase fallback + const operationId = operation.operationId || `${method}${path.replaceAll(/\W+/g, '_')}` assert( - operationName, - `Invalid operation name ${operationName} for path "${method} ${path}"` + operationId, + `Invalid operation id ${operationId} for path "${method} ${path}"` ) + + const operationName = camelCase(operationId) assert( - !operationIds.has(operationName), + !operationNames.has(operationName), `Duplicate operation name "${operationName}"` ) - operationIds.add(operationName) + operationNames.add(operationName) const operationNameSnakeCase = decamelize(operationName) // if (path !== '/comments' || method !== 'post') continue @@ -611,7 +609,6 @@ import { z } from 'zod'`.trim() [ outputTypes, ` - /** * Agentic ${name} client.${description ? `\n *\n * ${description}` : ''} */ @@ -667,5 +664,3 @@ export class ${clientName} extends AIFunctionsProvider { await fs.writeFile(destFileClient, output) await execa('npx', ['eslint', '--fix', '--no-ignore', destFileClient]) } - -await main() diff --git a/packages/openapi-to-ts/src/index.ts b/packages/openapi-to-ts/src/index.ts index a1094e8..6ae7d18 100644 --- a/packages/openapi-to-ts/src/index.ts +++ b/packages/openapi-to-ts/src/index.ts @@ -1 +1 @@ -export * from './generate' +export * from './generate-ts-from-openapi' diff --git a/packages/openapi-to-ts/src/utils.ts b/packages/openapi-to-ts/src/utils.ts index 9330589..87c0844 100644 --- a/packages/openapi-to-ts/src/utils.ts +++ b/packages/openapi-to-ts/src/utils.ts @@ -1,7 +1,7 @@ import type SwaggerParser from '@apidevtools/swagger-parser' import type { IJsonSchema, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types' import { assert } from '@agentic/core' -import camelcase from 'camelcase' +import camelCaseImpl from 'camelcase' import { type JsonSchema, jsonSchemaToZod as jsonSchemaToZodImpl, @@ -23,11 +23,11 @@ export function prettify(source: string): Promise { } export function pascalCase(identifier: string): string { - return camelcase(identifier, { pascalCase: true }) + return camelCaseImpl(identifier, { pascalCase: true }) } -export function unTitleCase(identifier: string): string { - return `${identifier.slice(0, 1).toLowerCase()}${identifier.slice(1)}` +export function camelCase(identifier: string): string { + return camelCaseImpl(identifier) } export function getAndResolve( diff --git a/packages/openapi-to-ts/tsconfig.json b/packages/openapi-to-ts/tsconfig.json index 6c8d720..a05348a 100644 --- a/packages/openapi-to-ts/tsconfig.json +++ b/packages/openapi-to-ts/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "@agentic/tsconfig/base.json", - "include": ["src"], + "include": ["src", "bin"], "exclude": ["node_modules", "dist"] } diff --git a/packages/openapi-to-ts/tsup.config.ts b/packages/openapi-to-ts/tsup.config.ts new file mode 100644 index 0000000..8c59256 --- /dev/null +++ b/packages/openapi-to-ts/tsup.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from 'tsup' + +export default defineConfig([ + { + entry: ['src/index.ts'], + outDir: 'dist', + target: 'node18', + platform: 'node', + format: ['esm'], + splitting: false, + sourcemap: true, + minify: false, + shims: true, + dts: true + }, + { + entry: ['bin/generate-from-openapi.ts'], + outDir: 'dist', + target: 'node18', + platform: 'node', + format: ['esm'], + splitting: false, + sourcemap: true, + minify: false, + shims: true, + dts: true + } +])