feat(codegen): openapi-typescript-codegen fork (#3272)
## What Allow `medusa-oas` CLI to generate API client code from an OAS json file. ## Why Manually maintaining API clients is time consuming and error prone. We wish to automate the process by using code generation strategies. We also wish to eliminate direct import from `@medusajs/medusa` in clients which can lead to unwanted side effects like importing unnecessary dependencies. ## How Fork and customize an OAS code generator library that is TypeScript friendly. Attempt to match the interface and signature of our current client packages: `@medusajs/medusa-js`, `medusa-react` Add a new `client` command to the `medusa-oas` CLI as the main interface to interact with the code generation tooling. ## Test ### Prerequisites * From the root of the monorepo: * `yarn install` * `yarn build` ### Case - all in one build * From the root of the monorepo: * Run `yarn medusa-oas oas --out-dir ~/tmp/oas --type store` * Run `yarn medusa-oas client --src-file ~/tmp/oas/store.oas.json --out-dir ~/tmp/client-store --type store` * Expect `~/tmp/client-store` to contain the following files and directories: ``` core/ hooks/ models/ services/ index.ts MedusaStore.ts useMedusaStore.tsx ``` ### Case - types only * From the root of the monorepo: * Run `yarn medusa-oas oas --out-dir ~/tmp/oas --type store` * Run `yarn medusa-oas client --src-file ~/tmp/oas/store.oas.json --out-dir ~/tmp/client-types --type store --component types` * Expect `~/tmp/client-types` to contain the following files and directories: ``` models/ index.ts ``` ### Case - client only * From the root of the monorepo: * Run `yarn medusa-oas oas --out-dir ~/tmp/oas --type store` * Run `yarn medusa-oas client --src-file ~/tmp/oas/store.oas.json --out-dir ~/tmp/client-only --type store --component client --types-package @medusajs/client-types` * Expect `~/tmp/client-only` to contain the following files and directories: ``` core/ services/ index.ts MedusaStore.ts ```
This commit is contained in:
6
.changeset/rude-guests-try.md
Normal file
6
.changeset/rude-guests-try.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/openapi-typescript-codegen": patch
|
||||
"@medusajs/medusa-oas-cli": patch
|
||||
---
|
||||
|
||||
feat(codegen): openapi-typescript-codegen fork
|
||||
@@ -12,7 +12,6 @@ const fallback = (filename: string) => {
|
||||
}
|
||||
|
||||
// Polyfill Node's `Module.createRequireFromPath` if not present (added in Node v10.12.0)
|
||||
const createRequireFromPath =
|
||||
Module.createRequire || Module.createRequireFromPath || fallback
|
||||
const createRequireFromPath = Module.createRequire || fallback
|
||||
|
||||
export default createRequireFromPath
|
||||
|
||||
@@ -19,6 +19,8 @@ N/A
|
||||
yarn medusa-oas <command>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Command - `oas`
|
||||
|
||||
This command will scan the `@medusajs/medusa` package in order to extract JSDoc OAS into a json file.
|
||||
@@ -69,4 +71,52 @@ Ignore OAS errors and attempt to output generated OAS files.
|
||||
|
||||
```bash
|
||||
yarn medusa-oas oas --force
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Command - `client`
|
||||
|
||||
Will generate API client files from a given OAS file.
|
||||
|
||||
#### `--src-file <path>`
|
||||
|
||||
Specify the path to the OAS JSON file.
|
||||
|
||||
`yarm medusa-oas client --src-file ./store.oas.json`
|
||||
|
||||
#### `--name <name>`
|
||||
|
||||
Namespace for the generated client. Usually `admin` or `store`.
|
||||
|
||||
`yarm medusa-oas client --name admin`
|
||||
|
||||
#### `--out-dir <path>`
|
||||
|
||||
Specify in which directory should the files be outputted. Accepts relative and absolute path. It the directory doesn't
|
||||
exist, it will be created. Defaults to `./`.
|
||||
|
||||
`yarm medusa-oas client --out-dir ./client`
|
||||
|
||||
#### `--type <type>`
|
||||
|
||||
Client component types to generate. Accepts `all`, `types`, `client`, `hooks`.
|
||||
Defaults to `all`.
|
||||
|
||||
`yarn medusa-oas client --type types`
|
||||
|
||||
#### `--types-packages <name>`
|
||||
|
||||
Replace relative import statements by types package name. Mandatory when using `--type client` or `--type hooks`.
|
||||
|
||||
#### `--client-packages <name>`
|
||||
|
||||
Replace relative import statements by client package name. Mandatory when using `--type hooks`.
|
||||
|
||||
`yarn medusa-oas client --type types`
|
||||
|
||||
#### `--clean`
|
||||
|
||||
Delete destination directory content before generating client.
|
||||
|
||||
`yarn medusa-oas --clean`
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/medusa": "*",
|
||||
"@medusajs/openapi-typescript-codegen": "*",
|
||||
"@readme/openapi-parser": "^2.4.0",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"commander": "^10.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"openapi3-ts": "^3.1.2",
|
||||
"swagger-inline": "^6.1.0"
|
||||
}
|
||||
|
||||
175
packages/oas/medusa-oas-cli/src/command-client.ts
Normal file
175
packages/oas/medusa-oas-cli/src/command-client.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import path from "path"
|
||||
import {
|
||||
generate,
|
||||
HttpClient,
|
||||
Indent,
|
||||
PackageNames,
|
||||
} from "@medusajs/openapi-typescript-codegen"
|
||||
import { upperFirst } from "lodash"
|
||||
import fs, { mkdir, readFile } from "fs/promises"
|
||||
import { OpenAPIObject } from "openapi3-ts"
|
||||
import { Command, Option, OptionValues } from "commander"
|
||||
|
||||
/**
|
||||
* CLI Command declaration
|
||||
*/
|
||||
export const commandName = "client"
|
||||
export const commandDescription = "Generate API clients from OAS."
|
||||
export const commandOptions: Option[] = [
|
||||
new Option(
|
||||
"-t, --type <type>",
|
||||
"Namespace for the generated client. Usually `admin` or `store`."
|
||||
).makeOptionMandatory(),
|
||||
|
||||
new Option(
|
||||
"-s, --src-file <srcFile>",
|
||||
"Path to source OAS JSON file."
|
||||
).makeOptionMandatory(),
|
||||
|
||||
new Option(
|
||||
"-o, --out-dir <outDir>",
|
||||
"Output directory for generated client files."
|
||||
).default(path.resolve(process.cwd(), "client")),
|
||||
|
||||
new Option(
|
||||
"-c, --component <component>",
|
||||
"Client component types to generate."
|
||||
)
|
||||
.choices(["all", "types", "client", "hooks"])
|
||||
.default("all"),
|
||||
|
||||
new Option(
|
||||
"--types-package <name>",
|
||||
"Replace relative import statements by types package name."
|
||||
),
|
||||
|
||||
new Option(
|
||||
"--client-package <name>",
|
||||
"Replace relative import statements by client package name."
|
||||
),
|
||||
|
||||
new Option(
|
||||
"--clean",
|
||||
"Delete destination directory content before generating client."
|
||||
),
|
||||
]
|
||||
|
||||
export function getCommand() {
|
||||
const command = new Command(commandName)
|
||||
command.description(commandDescription)
|
||||
for (const opt of commandOptions) {
|
||||
command.addOption(opt)
|
||||
}
|
||||
command.action(async (options) => await execute(options))
|
||||
return command
|
||||
}
|
||||
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
export async function execute(cliParams: OptionValues) {
|
||||
/**
|
||||
* Process CLI options
|
||||
*/
|
||||
if (
|
||||
["client", "hooks"].includes(cliParams.component) &&
|
||||
!cliParams.typesPackage
|
||||
) {
|
||||
throw new Error(
|
||||
`--types-package must be declared when using --component=${cliParams.component}`
|
||||
)
|
||||
}
|
||||
if (cliParams.component === "hooks" && !cliParams.clientPackage) {
|
||||
throw new Error(
|
||||
`--client-package must be declared when using --component=${cliParams.component}`
|
||||
)
|
||||
}
|
||||
|
||||
const shouldClean = !!cliParams.clean
|
||||
const srcFile = path.resolve(cliParams.srcFile)
|
||||
const outDir = path.resolve(cliParams.outDir)
|
||||
const apiName = cliParams.type
|
||||
const packageNames: PackageNames = {
|
||||
models: cliParams.typesPackage,
|
||||
client: cliParams.clientPackage,
|
||||
}
|
||||
const exportComponent = cliParams.component
|
||||
|
||||
/**
|
||||
* Command execution
|
||||
*/
|
||||
console.log(`🟣 Generating client - ${apiName} - ${exportComponent}`)
|
||||
|
||||
if (shouldClean) {
|
||||
console.log(`🟠 Cleaning output directory`)
|
||||
await fs.rm(outDir, { recursive: true, force: true })
|
||||
}
|
||||
await mkdir(outDir, { recursive: true })
|
||||
|
||||
const oas = await getOASFromFile(srcFile)
|
||||
await generateClientSDK(oas, outDir, apiName, exportComponent, packageNames)
|
||||
|
||||
console.log(
|
||||
`🟢 Client generated - ${apiName} - ${exportComponent} - ${outDir}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods
|
||||
*/
|
||||
const getOASFromFile = async (jsonFile: string): Promise<OpenAPIObject> => {
|
||||
const jsonString = await readFile(jsonFile, "utf8")
|
||||
return JSON.parse(jsonString)
|
||||
}
|
||||
|
||||
const generateClientSDK = async (
|
||||
oas: OpenAPIObject,
|
||||
targetDir: string,
|
||||
apiName: string,
|
||||
exportComponent: "all" | "types" | "client" | "hooks",
|
||||
packageNames: PackageNames = {}
|
||||
) => {
|
||||
const exports = {
|
||||
exportCore: false,
|
||||
exportServices: false,
|
||||
exportModels: false,
|
||||
exportHooks: false,
|
||||
}
|
||||
|
||||
switch (exportComponent) {
|
||||
case "types":
|
||||
exports.exportModels = true
|
||||
break
|
||||
case "client":
|
||||
exports.exportCore = true
|
||||
exports.exportServices = true
|
||||
break
|
||||
case "hooks":
|
||||
exports.exportHooks = true
|
||||
break
|
||||
default:
|
||||
exports.exportCore = true
|
||||
exports.exportServices = true
|
||||
exports.exportModels = true
|
||||
exports.exportHooks = true
|
||||
}
|
||||
|
||||
await generate({
|
||||
input: oas,
|
||||
output: targetDir,
|
||||
httpClient: HttpClient.AXIOS,
|
||||
useOptions: true,
|
||||
useUnionTypes: true,
|
||||
exportCore: exports.exportCore,
|
||||
exportServices: exports.exportServices,
|
||||
exportModels: exports.exportModels,
|
||||
exportHooks: exports.exportHooks,
|
||||
exportSchemas: false,
|
||||
indent: Indent.SPACE_2,
|
||||
postfixServices: "Service",
|
||||
postfixModels: "",
|
||||
clientName: `Medusa${upperFirst(apiName)}`,
|
||||
request: undefined,
|
||||
packageNames,
|
||||
})
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Command } from "commander"
|
||||
import { getCommand as oasGetCommand } from "./command-oas"
|
||||
import { getCommand as clientGetCommand } from "./command-client"
|
||||
|
||||
const run = async () => {
|
||||
const program = new Command()
|
||||
@@ -11,6 +12,11 @@ const run = async () => {
|
||||
*/
|
||||
program.addCommand(oasGetCommand())
|
||||
|
||||
/**
|
||||
* Alias to command-client.ts
|
||||
*/
|
||||
program.addCommand(clientGetCommand())
|
||||
|
||||
/**
|
||||
* Run CLI
|
||||
*/
|
||||
|
||||
7
packages/oas/openapi-typescript-codegen/.gitignore
vendored
Normal file
7
packages/oas/openapi-typescript-codegen/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/dist
|
||||
node_modules
|
||||
.DS_store
|
||||
.env*
|
||||
.env
|
||||
*.sql
|
||||
/bin
|
||||
1
packages/oas/openapi-typescript-codegen/.prettierignore
Normal file
1
packages/oas/openapi-typescript-codegen/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
*.hbs
|
||||
3
packages/oas/openapi-typescript-codegen/CHANGELOG.md
Normal file
3
packages/oas/openapi-typescript-codegen/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# @medusajs/openapi-typescript-codegen
|
||||
|
||||
## 0.1.0
|
||||
39
packages/oas/openapi-typescript-codegen/README.md
Normal file
39
packages/oas/openapi-typescript-codegen/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# OpenAPI Typescript Codegen - 0.1.0 - experimental
|
||||
|
||||
Node.js library that generates Typescript clients based on the OpenAPI specification.
|
||||
|
||||
## About this fork
|
||||
|
||||
This package is a significantly customized fork of
|
||||
the amazing npm [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) package 💜.
|
||||
|
||||
### Brief reasoning
|
||||
|
||||
We wanted a level of customization that was not achievable through the source package's interface.
|
||||
We started with a conventional fork but the development workflow was hindering our ability to iterate quickly.
|
||||
We decided to fold the package within our monorepo to hopefully accelerate development and innovation.
|
||||
|
||||
### Noteworthy differences
|
||||
|
||||
* Added ability to generate React hooks in the style of `medusa-react`.
|
||||
* Added access to the raw OAS on parsed element from the parser.
|
||||
* Added access `operationId` from path.
|
||||
* Added access `x-codegen` from path.
|
||||
* Added renaming of service method when `x-codegen.method` is declared.
|
||||
* Added bundling of query params into a typed object when `x-codegen.queryParams` is declared.
|
||||
* Added parameterization of import path for the client and models.
|
||||
* Added generated `index.ts` files in directories to simplify imports.
|
||||
* Updated npm dependencies' version to match existing versions found in the monorepo.
|
||||
* Updated coding style to match the convention of the monorepo.
|
||||
* Removed support for Angular client generation.
|
||||
* Removed support for OAS v2 parsing.
|
||||
* Removed documentation.
|
||||
* Removed tests (temporarily).
|
||||
|
||||
## Install
|
||||
|
||||
`yarn add --dev @medusajs/openapi-typescript-codegen`
|
||||
|
||||
## How to use
|
||||
|
||||
See `@medusajs/medusa-oas-cli` for usage.
|
||||
18
packages/oas/openapi-typescript-codegen/babel.config.json
Normal file
18
packages/oas/openapi-typescript-codegen/babel.config.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "12"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@babel/preset-typescript",
|
||||
{
|
||||
"onlyRemoveTypeImports": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
62
packages/oas/openapi-typescript-codegen/package.json
Normal file
62
packages/oas/openapi-typescript-codegen/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@medusajs/openapi-typescript-codegen",
|
||||
"version": "0.1.0",
|
||||
"description": "Library that generates Typescript clients based on the OpenAPI specification.",
|
||||
"main": "dist/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"dist/index.js",
|
||||
"types/index.d.ts"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/oas/openapi-typescript-codegen"
|
||||
},
|
||||
"author": "Medusa",
|
||||
"contributors": [
|
||||
"Ferdi Koomen"
|
||||
],
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepare": "cross-env NODE_ENV=production yarn run release",
|
||||
"build": "rollup --config --environment NODE_ENV:development",
|
||||
"release": "rollup --config --environment NODE_ENV:production",
|
||||
"test": "jest --passWithNoTests",
|
||||
"test:unit": "jest --passWithNoTests"
|
||||
},
|
||||
"dependencies": {
|
||||
"camelcase": "^6.3.0",
|
||||
"commander": "^9.4.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"json-schema-ref-parser": "^9.0.9",
|
||||
"pascalcase": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.14.3",
|
||||
"@babel/core": "7.14.3",
|
||||
"@babel/preset-env": "7.11.5",
|
||||
"@babel/preset-typescript": "7.18.6",
|
||||
"@rollup/plugin-commonjs": "24.0.0",
|
||||
"@rollup/plugin-node-resolve": "15.0.1",
|
||||
"@rollup/plugin-typescript": "9.0.2",
|
||||
"@types/fs-extra": "^9.0.12",
|
||||
"@types/jest": "27.5.0",
|
||||
"@types/node": "18.11.9",
|
||||
"@types/node-fetch": "2.6.2",
|
||||
"@types/pascalcase": "^1.0.1",
|
||||
"@types/qs": "6.9.7",
|
||||
"abort-controller": "3.0.0",
|
||||
"axios": "1.2.0",
|
||||
"form-data": "4.0.0",
|
||||
"jest": "26.6.3",
|
||||
"jest-cli": "26.6.3",
|
||||
"node-fetch": "2.6.7",
|
||||
"qs": "6.10.3",
|
||||
"rollup": "3.9.1",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"tslib": "2.3.1",
|
||||
"typescript": "4.9.5"
|
||||
}
|
||||
}
|
||||
83
packages/oas/openapi-typescript-codegen/rollup.config.mjs
Normal file
83
packages/oas/openapi-typescript-codegen/rollup.config.mjs
Normal file
@@ -0,0 +1,83 @@
|
||||
import commonjs from "@rollup/plugin-commonjs"
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve"
|
||||
import typescript from "@rollup/plugin-typescript"
|
||||
import { readFileSync } from "fs"
|
||||
import handlebars from "handlebars"
|
||||
import { dirname, extname, resolve } from "path"
|
||||
import { terser } from "rollup-plugin-terser"
|
||||
|
||||
const { precompile } = handlebars
|
||||
|
||||
/**
|
||||
* Custom plugin to parse handlebar imports and precompile
|
||||
* the template on the fly. This reduces runtime by about
|
||||
* half on large projects.
|
||||
*/
|
||||
const handlebarsPlugin = () => ({
|
||||
resolveId: (file, importer) => {
|
||||
if (extname(file) === ".hbs") {
|
||||
return resolve(dirname(importer), file)
|
||||
}
|
||||
return null
|
||||
},
|
||||
load: (file) => {
|
||||
if (extname(file) === ".hbs") {
|
||||
const template = readFileSync(file, "utf8").toString().trim()
|
||||
const templateSpec = precompile(template, {
|
||||
strict: true,
|
||||
noEscape: true,
|
||||
preventIndent: true,
|
||||
knownHelpersOnly: true,
|
||||
knownHelpers: {
|
||||
ifdef: true,
|
||||
equals: true,
|
||||
notEquals: true,
|
||||
containsSpaces: true,
|
||||
union: true,
|
||||
intersection: true,
|
||||
enumerator: true,
|
||||
escapeComment: true,
|
||||
escapeDescription: true,
|
||||
camelCase: true,
|
||||
pascalCase: true,
|
||||
},
|
||||
})
|
||||
return `export default ${templateSpec};`
|
||||
}
|
||||
return null
|
||||
},
|
||||
})
|
||||
|
||||
const getPlugins = () => {
|
||||
const plugins = [
|
||||
nodeResolve(),
|
||||
commonjs({
|
||||
sourceMap: false,
|
||||
}),
|
||||
handlebarsPlugin(),
|
||||
typescript({
|
||||
module: "esnext",
|
||||
}),
|
||||
]
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return plugins
|
||||
}
|
||||
return [...plugins, terser()]
|
||||
}
|
||||
|
||||
export default {
|
||||
input: "./src/index.ts",
|
||||
output: {
|
||||
exports: "named",
|
||||
file: "./dist/index.js",
|
||||
format: "cjs",
|
||||
},
|
||||
external: [
|
||||
"camelcase",
|
||||
"commander",
|
||||
"fs-extra",
|
||||
"handlebars",
|
||||
"json-schema-ref-parser",
|
||||
],
|
||||
plugins: getPlugins(),
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum HttpClient {
|
||||
FETCH = "fetch",
|
||||
XHR = "xhr",
|
||||
NODE = "node",
|
||||
AXIOS = "axios",
|
||||
}
|
||||
5
packages/oas/openapi-typescript-codegen/src/Indent.ts
Normal file
5
packages/oas/openapi-typescript-codegen/src/Indent.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum Indent {
|
||||
SPACE_4 = "4",
|
||||
SPACE_2 = "2",
|
||||
TAB = "tab",
|
||||
}
|
||||
9
packages/oas/openapi-typescript-codegen/src/client/interfaces/Client.d.ts
vendored
Normal file
9
packages/oas/openapi-typescript-codegen/src/client/interfaces/Client.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { Model } from "./Model"
|
||||
import type { Service } from "./Service"
|
||||
|
||||
export interface Client {
|
||||
version: string
|
||||
server: string
|
||||
models: Model[]
|
||||
services: Service[]
|
||||
}
|
||||
6
packages/oas/openapi-typescript-codegen/src/client/interfaces/Enum.d.ts
vendored
Normal file
6
packages/oas/openapi-typescript-codegen/src/client/interfaces/Enum.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Enum {
|
||||
name: string
|
||||
value: string
|
||||
type: string
|
||||
description: string | null
|
||||
}
|
||||
27
packages/oas/openapi-typescript-codegen/src/client/interfaces/Model.d.ts
vendored
Normal file
27
packages/oas/openapi-typescript-codegen/src/client/interfaces/Model.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { Enum } from "./Enum"
|
||||
import type { Schema } from "./Schema"
|
||||
|
||||
export interface Model extends Schema {
|
||||
name: string
|
||||
export:
|
||||
| "reference"
|
||||
| "generic"
|
||||
| "enum"
|
||||
| "array"
|
||||
| "dictionary"
|
||||
| "interface"
|
||||
| "one-of"
|
||||
| "any-of"
|
||||
| "all-of"
|
||||
type: string
|
||||
base: string
|
||||
template: string | null
|
||||
link: Model | null
|
||||
description: string | null
|
||||
deprecated?: boolean
|
||||
default?: string
|
||||
imports: string[]
|
||||
enum: Enum[]
|
||||
enums: Model[]
|
||||
properties: Model[]
|
||||
}
|
||||
8
packages/oas/openapi-typescript-codegen/src/client/interfaces/ModelComposition.d.ts
vendored
Normal file
8
packages/oas/openapi-typescript-codegen/src/client/interfaces/ModelComposition.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Model } from "./Model"
|
||||
|
||||
export interface ModelComposition {
|
||||
type: "one-of" | "any-of" | "all-of"
|
||||
imports: string[]
|
||||
enums: Model[]
|
||||
properties: Model[]
|
||||
}
|
||||
19
packages/oas/openapi-typescript-codegen/src/client/interfaces/Operation.d.ts
vendored
Normal file
19
packages/oas/openapi-typescript-codegen/src/client/interfaces/Operation.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { OperationError } from "./OperationError"
|
||||
import type { OperationParameters } from "./OperationParameters"
|
||||
import type { OperationResponse } from "./OperationResponse"
|
||||
import type { OperationCodegen } from "./OperationCodegen"
|
||||
|
||||
export interface Operation extends OperationParameters {
|
||||
service: string
|
||||
name: string
|
||||
operationId: string | null
|
||||
summary: string | null
|
||||
description: string | null
|
||||
deprecated: boolean
|
||||
method: string
|
||||
path: string
|
||||
errors: OperationError[]
|
||||
results: OperationResponse[]
|
||||
responseHeader: string | null
|
||||
codegen: OperationCodegen
|
||||
}
|
||||
4
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationCodegen.d.ts
vendored
Normal file
4
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationCodegen.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface OperationCodegen {
|
||||
method?: string
|
||||
queryParams?: string
|
||||
}
|
||||
4
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationError.d.ts
vendored
Normal file
4
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationError.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface OperationError {
|
||||
code: number
|
||||
description: string
|
||||
}
|
||||
10
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameter.d.ts
vendored
Normal file
10
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameter.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { Model } from "./Model"
|
||||
import { OpenApiParameter } from "../../openApi/v3/interfaces/OpenApiParameter"
|
||||
import { OpenApiRequestBody } from "../../openApi/v3/interfaces/OpenApiRequestBody"
|
||||
|
||||
export interface OperationParameter extends Model {
|
||||
spec: OpenApiParameter | OpenApiRequestBody
|
||||
in: "path" | "query" | "header" | "formData" | "body" | "cookie"
|
||||
prop: string
|
||||
mediaType: string | null
|
||||
}
|
||||
12
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameters.d.ts
vendored
Normal file
12
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationParameters.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { OperationParameter } from "./OperationParameter"
|
||||
|
||||
export interface OperationParameters {
|
||||
imports: string[]
|
||||
parameters: OperationParameter[]
|
||||
parametersPath: OperationParameter[]
|
||||
parametersQuery: OperationParameter[]
|
||||
parametersForm: OperationParameter[]
|
||||
parametersCookie: OperationParameter[]
|
||||
parametersHeader: OperationParameter[]
|
||||
parametersBody: OperationParameter | null
|
||||
}
|
||||
8
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationResponse.d.ts
vendored
Normal file
8
packages/oas/openapi-typescript-codegen/src/client/interfaces/OperationResponse.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Model } from "./Model"
|
||||
import { OpenApiResponse } from "../../openApi/v3/interfaces/OpenApiResponse"
|
||||
|
||||
export interface OperationResponse extends Model {
|
||||
spec: OpenApiResponse
|
||||
in: "response" | "header"
|
||||
code: number
|
||||
}
|
||||
34
packages/oas/openapi-typescript-codegen/src/client/interfaces/Schema.d.ts
vendored
Normal file
34
packages/oas/openapi-typescript-codegen/src/client/interfaces/Schema.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import { OpenApiSchema } from "../../openApi/v3/interfaces/OpenApiSchema"
|
||||
|
||||
export interface Schema {
|
||||
spec: OpenApiSchema
|
||||
isDefinition: boolean
|
||||
isReadOnly: boolean
|
||||
isRequired: boolean
|
||||
isNullable: boolean
|
||||
format?:
|
||||
| "int32"
|
||||
| "int64"
|
||||
| "float"
|
||||
| "double"
|
||||
| "string"
|
||||
| "boolean"
|
||||
| "byte"
|
||||
| "binary"
|
||||
| "date"
|
||||
| "date-time"
|
||||
| "password"
|
||||
maximum?: number
|
||||
exclusiveMaximum?: boolean
|
||||
minimum?: number
|
||||
exclusiveMinimum?: boolean
|
||||
multipleOf?: number
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
pattern?: string
|
||||
maxItems?: number
|
||||
minItems?: number
|
||||
uniqueItems?: boolean
|
||||
maxProperties?: number
|
||||
minProperties?: number
|
||||
}
|
||||
7
packages/oas/openapi-typescript-codegen/src/client/interfaces/Service.d.ts
vendored
Normal file
7
packages/oas/openapi-typescript-codegen/src/client/interfaces/Service.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Operation } from "./Operation"
|
||||
|
||||
export interface Service {
|
||||
name: string
|
||||
operations: Operation[]
|
||||
imports: string[]
|
||||
}
|
||||
7
packages/oas/openapi-typescript-codegen/src/client/interfaces/Type.d.ts
vendored
Normal file
7
packages/oas/openapi-typescript-codegen/src/client/interfaces/Type.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface Type {
|
||||
type: string
|
||||
base: string
|
||||
template: string | null
|
||||
imports: string[]
|
||||
isNullable: boolean
|
||||
}
|
||||
116
packages/oas/openapi-typescript-codegen/src/index.ts
Normal file
116
packages/oas/openapi-typescript-codegen/src/index.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { HttpClient } from "./HttpClient"
|
||||
import { Indent } from "./Indent"
|
||||
import { parse } from "./openApi/v3"
|
||||
import { getOpenApiSpec } from "./utils/getOpenApiSpec"
|
||||
import { getOpenApiVersion } from "./utils/getOpenApiVersion"
|
||||
import { isString } from "./utils/isString"
|
||||
import { postProcessClient } from "./utils/postProcessClient"
|
||||
import { registerHandlebarTemplates } from "./utils/registerHandlebarTemplates"
|
||||
import { writeClient } from "./utils/writeClient"
|
||||
|
||||
export { HttpClient } from "./HttpClient"
|
||||
export { Indent } from "./Indent"
|
||||
|
||||
export type PackageNames = {
|
||||
models?: string
|
||||
client?: string
|
||||
}
|
||||
|
||||
export type Options = {
|
||||
input: string | Record<string, any>
|
||||
output: string
|
||||
httpClient?: HttpClient
|
||||
clientName?: string
|
||||
useOptions?: boolean
|
||||
useUnionTypes?: boolean
|
||||
exportCore?: boolean
|
||||
exportServices?: boolean
|
||||
exportModels?: boolean
|
||||
exportHooks?: boolean
|
||||
exportSchemas?: boolean
|
||||
indent?: Indent
|
||||
packageNames?: PackageNames
|
||||
postfixServices?: string
|
||||
postfixModels?: string
|
||||
request?: string
|
||||
write?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the OpenAPI client. This method will read the OpenAPI specification and based on the
|
||||
* given language it will generate the client, including the typed models, validation schemas,
|
||||
* service layer, etc.
|
||||
* @param input The relative location of the OpenAPI spec
|
||||
* @param output The relative location of the output directory
|
||||
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
|
||||
* @param clientName Custom client class name
|
||||
* @param useOptions Use options or arguments functions
|
||||
* @param useUnionTypes Use union types instead of enums
|
||||
* @param exportCore Generate core client classes
|
||||
* @param exportServices Generate services
|
||||
* @param exportModels Generate models
|
||||
* @param exportHooks Generate hooks
|
||||
* @param exportSchemas Generate schemas
|
||||
* @param indent Indentation options (4, 2 or tab)
|
||||
* @param packageNames Package name to use in import statements.
|
||||
* @param postfixServices Service name postfix
|
||||
* @param postfixModels Model name postfix
|
||||
* @param request Path to custom request file
|
||||
* @param write Write the files to disk (true or false)
|
||||
*/
|
||||
export const generate = async ({
|
||||
input,
|
||||
output,
|
||||
httpClient = HttpClient.FETCH,
|
||||
clientName,
|
||||
useOptions = false,
|
||||
useUnionTypes = false,
|
||||
exportCore = true,
|
||||
exportServices = true,
|
||||
exportModels = true,
|
||||
exportHooks = true,
|
||||
exportSchemas = false,
|
||||
indent = Indent.SPACE_4,
|
||||
packageNames = {},
|
||||
postfixServices = "Service",
|
||||
postfixModels = "",
|
||||
request,
|
||||
write = true,
|
||||
}: Options): Promise<void> => {
|
||||
const openApi = isString(input) ? await getOpenApiSpec(input) : input
|
||||
const openApiVersion = getOpenApiVersion(openApi)
|
||||
const templates = registerHandlebarTemplates({
|
||||
httpClient,
|
||||
useUnionTypes,
|
||||
useOptions,
|
||||
})
|
||||
|
||||
const client = parse(openApi)
|
||||
const clientFinal = postProcessClient(client)
|
||||
if (write) {
|
||||
await writeClient(
|
||||
clientFinal,
|
||||
templates,
|
||||
output,
|
||||
httpClient,
|
||||
useOptions,
|
||||
useUnionTypes,
|
||||
exportCore,
|
||||
exportServices,
|
||||
exportModels,
|
||||
exportHooks,
|
||||
exportSchemas,
|
||||
indent,
|
||||
packageNames,
|
||||
postfixServices,
|
||||
postfixModels,
|
||||
clientName,
|
||||
request
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
HttpClient,
|
||||
generate,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Client } from "../../client/interfaces/Client"
|
||||
import type { OpenApi } from "./interfaces/OpenApi"
|
||||
import { getModels } from "./parser/getModels"
|
||||
import { getServer } from "./parser/getServer"
|
||||
import { getServices } from "./parser/getServices"
|
||||
import { getServiceVersion } from "./parser/getServiceVersion"
|
||||
|
||||
/**
|
||||
* Parse the OpenAPI specification to a Client model that contains
|
||||
* all the models, services and schema's we should output.
|
||||
* @param openApi The OpenAPI spec that we have loaded from disk.
|
||||
*/
|
||||
export const parse = (openApi: OpenApi): Client => {
|
||||
const version = getServiceVersion(openApi.info.version)
|
||||
const server = getServer(openApi)
|
||||
const models = getModels(openApi)
|
||||
const services = getServices(openApi)
|
||||
|
||||
return { version, server, models, services }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface WithEnumExtension {
|
||||
"x-enum-varnames"?: string[]
|
||||
"x-enum-descriptions"?: string[]
|
||||
}
|
||||
21
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApi.d.ts
vendored
Normal file
21
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApi.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { OpenApiComponents } from "./OpenApiComponents"
|
||||
import type { OpenApiExternalDocs } from "./OpenApiExternalDocs"
|
||||
import type { OpenApiInfo } from "./OpenApiInfo"
|
||||
import type { OpenApiPaths } from "./OpenApiPaths"
|
||||
import type { OpenApiSecurityRequirement } from "./OpenApiSecurityRequirement"
|
||||
import type { OpenApiServer } from "./OpenApiServer"
|
||||
import type { OpenApiTag } from "./OpenApiTag"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md
|
||||
*/
|
||||
export interface OpenApi {
|
||||
openapi: string
|
||||
info: OpenApiInfo
|
||||
servers?: OpenApiServer[]
|
||||
paths: OpenApiPaths
|
||||
components?: OpenApiComponents
|
||||
security?: OpenApiSecurityRequirement[]
|
||||
tags?: OpenApiTag[]
|
||||
externalDocs?: OpenApiExternalDocs
|
||||
}
|
||||
9
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiCallback.d.ts
vendored
Normal file
9
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiCallback.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { OpenApiPath } from "./OpenApiPath"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#callbackObject
|
||||
*/
|
||||
export interface OpenApiCallback extends OpenApiReference {
|
||||
[key: string]: OpenApiPath
|
||||
}
|
||||
25
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiComponents.d.ts
vendored
Normal file
25
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiComponents.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiCallback } from "./OpenApiCallback"
|
||||
import type { OpenApiExample } from "./OpenApiExample"
|
||||
import type { OpenApiHeader } from "./OpenApiHeader"
|
||||
import type { OpenApiLink } from "./OpenApiLink"
|
||||
import type { OpenApiParameter } from "./OpenApiParameter"
|
||||
import type { OpenApiRequestBody } from "./OpenApiRequestBody"
|
||||
import type { OpenApiResponses } from "./OpenApiResponses"
|
||||
import type { OpenApiSchema } from "./OpenApiSchema"
|
||||
import type { OpenApiSecurityScheme } from "./OpenApiSecurityScheme"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#componentsObject
|
||||
*/
|
||||
export interface OpenApiComponents {
|
||||
schemas?: Dictionary<OpenApiSchema>
|
||||
responses?: Dictionary<OpenApiResponses>
|
||||
parameters?: Dictionary<OpenApiParameter>
|
||||
examples?: Dictionary<OpenApiExample>
|
||||
requestBodies?: Dictionary<OpenApiRequestBody>
|
||||
headers?: Dictionary<OpenApiHeader>
|
||||
securitySchemes?: Dictionary<OpenApiSecurityScheme>
|
||||
links?: Dictionary<OpenApiLink>
|
||||
callbacks?: Dictionary<OpenApiCallback>
|
||||
}
|
||||
8
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiContact.d.ts
vendored
Normal file
8
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiContact.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#contactObject
|
||||
*/
|
||||
export interface OpenApiContact {
|
||||
name?: string
|
||||
url?: string
|
||||
email?: string
|
||||
}
|
||||
9
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiDiscriminator.d.ts
vendored
Normal file
9
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiDiscriminator.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#discriminatorObject
|
||||
*/
|
||||
export interface OpenApiDiscriminator {
|
||||
propertyName: string
|
||||
mapping?: Dictionary<string>
|
||||
}
|
||||
13
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiEncoding.d.ts
vendored
Normal file
13
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiEncoding.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiHeader } from "./OpenApiHeader"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encodingObject
|
||||
*/
|
||||
export interface OpenApiEncoding {
|
||||
contentType?: string
|
||||
headers?: Dictionary<OpenApiHeader>
|
||||
style?: string
|
||||
explode?: boolean
|
||||
allowReserved?: boolean
|
||||
}
|
||||
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExample.d.ts
vendored
Normal file
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExample.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#exampleObject
|
||||
*/
|
||||
export interface OpenApiExample extends OpenApiReference {
|
||||
summary?: string
|
||||
description?: string
|
||||
value?: any
|
||||
externalValue?: string
|
||||
}
|
||||
7
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExternalDocs.d.ts
vendored
Normal file
7
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiExternalDocs.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#externalDocumentationObject
|
||||
*/
|
||||
export interface OpenApiExternalDocs {
|
||||
description?: string
|
||||
url: string
|
||||
}
|
||||
20
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiHeader.d.ts
vendored
Normal file
20
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiHeader.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiExample } from "./OpenApiExample"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
import type { OpenApiSchema } from "./OpenApiSchema"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#headerObject
|
||||
*/
|
||||
export interface OpenApiHeader extends OpenApiReference {
|
||||
description?: string
|
||||
required?: boolean
|
||||
deprecated?: boolean
|
||||
allowEmptyValue?: boolean
|
||||
style?: string
|
||||
explode?: boolean
|
||||
allowReserved?: boolean
|
||||
schema?: OpenApiSchema
|
||||
example?: any
|
||||
examples?: Dictionary<OpenApiExample>
|
||||
}
|
||||
14
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiInfo.d.ts
vendored
Normal file
14
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiInfo.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { OpenApiContact } from "./OpenApiContact"
|
||||
import type { OpenApiLicense } from "./OpenApiLicense"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#infoObject
|
||||
*/
|
||||
export interface OpenApiInfo {
|
||||
title: string
|
||||
description?: string
|
||||
termsOfService?: string
|
||||
contact?: OpenApiContact
|
||||
license?: OpenApiLicense
|
||||
version: string
|
||||
}
|
||||
7
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLicense.d.ts
vendored
Normal file
7
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLicense.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#licenseObject
|
||||
*/
|
||||
export interface OpenApiLicense {
|
||||
name: string
|
||||
url?: string
|
||||
}
|
||||
15
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLink.d.ts
vendored
Normal file
15
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiLink.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
import type { OpenApiServer } from "./OpenApiServer"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#linkObject
|
||||
*/
|
||||
export interface OpenApiLink extends OpenApiReference {
|
||||
operationRef?: string
|
||||
operationId?: string
|
||||
parameters?: Dictionary<any>
|
||||
requestBody?: any
|
||||
description?: string
|
||||
server?: OpenApiServer
|
||||
}
|
||||
15
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiMediaType.d.ts
vendored
Normal file
15
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiMediaType.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiEncoding } from "./OpenApiEncoding"
|
||||
import type { OpenApiExample } from "./OpenApiExample"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
import type { OpenApiSchema } from "./OpenApiSchema"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#mediaTypeObject
|
||||
*/
|
||||
export interface OpenApiMediaType extends OpenApiReference {
|
||||
schema?: OpenApiSchema
|
||||
example?: any
|
||||
examples?: Dictionary<OpenApiExample>
|
||||
encoding?: Dictionary<OpenApiEncoding>
|
||||
}
|
||||
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlow.d.ts
vendored
Normal file
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlow.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#oauthFlowObject
|
||||
*/
|
||||
export interface OpenApiOAuthFlow {
|
||||
authorizationUrl: string
|
||||
tokenUrl: string
|
||||
refreshUrl?: string
|
||||
scopes: Dictionary<string>
|
||||
}
|
||||
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlows.d.ts
vendored
Normal file
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOAuthFlows.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { OpenApiOAuthFlow } from "./OpenApiOAuthFlow"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#oauthFlowsObject
|
||||
*/
|
||||
export interface OpenApiOAuthFlows {
|
||||
implicit?: OpenApiOAuthFlow
|
||||
password?: OpenApiOAuthFlow
|
||||
clientCredentials?: OpenApiOAuthFlow
|
||||
authorizationCode?: OpenApiOAuthFlow
|
||||
}
|
||||
27
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOperation.d.ts
vendored
Normal file
27
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiOperation.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiCallback } from "./OpenApiCallback"
|
||||
import type { OpenApiExternalDocs } from "./OpenApiExternalDocs"
|
||||
import type { OpenApiParameter } from "./OpenApiParameter"
|
||||
import type { OpenApiRequestBody } from "./OpenApiRequestBody"
|
||||
import type { OpenApiResponses } from "./OpenApiResponses"
|
||||
import type { OpenApiSecurityRequirement } from "./OpenApiSecurityRequirement"
|
||||
import type { OpenApiServer } from "./OpenApiServer"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject
|
||||
*/
|
||||
export interface OpenApiOperation {
|
||||
tags?: string[]
|
||||
summary?: string
|
||||
description?: string
|
||||
externalDocs?: OpenApiExternalDocs
|
||||
operationId?: string
|
||||
parameters?: OpenApiParameter[]
|
||||
requestBody?: OpenApiRequestBody
|
||||
responses: OpenApiResponses
|
||||
callbacks?: Dictionary<OpenApiCallback>
|
||||
deprecated?: boolean
|
||||
security?: OpenApiSecurityRequirement[]
|
||||
servers?: OpenApiServer[]
|
||||
"x-codegen"?: Record<string, unknown>
|
||||
}
|
||||
23
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiParameter.d.ts
vendored
Normal file
23
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiParameter.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiExample } from "./OpenApiExample"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
import type { OpenApiSchema } from "./OpenApiSchema"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject
|
||||
*/
|
||||
export interface OpenApiParameter extends OpenApiReference {
|
||||
name: string
|
||||
in: "path" | "query" | "header" | "formData" | "cookie"
|
||||
description?: string
|
||||
required?: boolean
|
||||
nullable?: boolean
|
||||
deprecated?: boolean
|
||||
allowEmptyValue?: boolean
|
||||
style?: string
|
||||
explode?: boolean
|
||||
allowReserved?: boolean
|
||||
schema?: OpenApiSchema
|
||||
example?: any
|
||||
examples?: Dictionary<OpenApiExample>
|
||||
}
|
||||
21
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPath.d.ts
vendored
Normal file
21
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPath.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { OpenApiOperation } from "./OpenApiOperation"
|
||||
import type { OpenApiParameter } from "./OpenApiParameter"
|
||||
import type { OpenApiServer } from "./OpenApiServer"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathItemObject
|
||||
*/
|
||||
export interface OpenApiPath {
|
||||
summary?: string
|
||||
description?: string
|
||||
get?: OpenApiOperation
|
||||
put?: OpenApiOperation
|
||||
post?: OpenApiOperation
|
||||
delete?: OpenApiOperation
|
||||
options?: OpenApiOperation
|
||||
head?: OpenApiOperation
|
||||
patch?: OpenApiOperation
|
||||
trace?: OpenApiOperation
|
||||
servers?: OpenApiServer[]
|
||||
parameters?: OpenApiParameter[]
|
||||
}
|
||||
8
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPaths.d.ts
vendored
Normal file
8
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiPaths.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { OpenApiPath } from "./OpenApiPath"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#pathsObject
|
||||
*/
|
||||
export interface OpenApiPaths {
|
||||
[path: string]: OpenApiPath
|
||||
}
|
||||
6
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiReference.d.ts
vendored
Normal file
6
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiReference.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject
|
||||
*/
|
||||
export interface OpenApiReference {
|
||||
$ref?: string
|
||||
}
|
||||
13
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts
vendored
Normal file
13
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiRequestBody.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiMediaType } from "./OpenApiMediaType"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#requestBodyObject
|
||||
*/
|
||||
export interface OpenApiRequestBody extends OpenApiReference {
|
||||
description?: string
|
||||
content: Dictionary<OpenApiMediaType>
|
||||
required?: boolean
|
||||
nullable?: boolean
|
||||
}
|
||||
15
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponse.d.ts
vendored
Normal file
15
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponse.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiHeader } from "./OpenApiHeader"
|
||||
import type { OpenApiLink } from "./OpenApiLink"
|
||||
import type { OpenApiMediaType } from "./OpenApiMediaType"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responseObject
|
||||
*/
|
||||
export interface OpenApiResponse extends OpenApiReference {
|
||||
description: string
|
||||
headers?: Dictionary<OpenApiHeader>
|
||||
content?: Dictionary<OpenApiMediaType>
|
||||
links?: Dictionary<OpenApiLink>
|
||||
}
|
||||
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponses.d.ts
vendored
Normal file
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiResponses.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
import type { OpenApiResponse } from "./OpenApiResponse"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#responsesObject
|
||||
*/
|
||||
export interface OpenApiResponses extends OpenApiReference {
|
||||
default: OpenApiResponse
|
||||
|
||||
[httpcode: string]: OpenApiResponse
|
||||
}
|
||||
58
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSchema.d.ts
vendored
Normal file
58
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSchema.d.ts
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { WithEnumExtension } from "./Extensions/WithEnumExtension"
|
||||
import type { OpenApiDiscriminator } from "./OpenApiDiscriminator"
|
||||
import type { OpenApiExternalDocs } from "./OpenApiExternalDocs"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
import type { OpenApiXml } from "./OpenApiXml"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject
|
||||
*/
|
||||
export interface OpenApiSchema extends OpenApiReference, WithEnumExtension {
|
||||
title?: string
|
||||
multipleOf?: number
|
||||
maximum?: number
|
||||
exclusiveMaximum?: boolean
|
||||
minimum?: number
|
||||
exclusiveMinimum?: boolean
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
pattern?: string
|
||||
maxItems?: number
|
||||
minItems?: number
|
||||
uniqueItems?: boolean
|
||||
maxProperties?: number
|
||||
minProperties?: number
|
||||
required?: string[]
|
||||
enum?: (string | number)[]
|
||||
type?: string | string[]
|
||||
allOf?: OpenApiSchema[]
|
||||
oneOf?: OpenApiSchema[]
|
||||
anyOf?: OpenApiSchema[]
|
||||
not?: OpenApiSchema[]
|
||||
items?: OpenApiSchema
|
||||
properties?: Dictionary<OpenApiSchema>
|
||||
additionalProperties?: boolean | OpenApiSchema
|
||||
description?: string
|
||||
format?:
|
||||
| "int32"
|
||||
| "int64"
|
||||
| "float"
|
||||
| "double"
|
||||
| "string"
|
||||
| "boolean"
|
||||
| "byte"
|
||||
| "binary"
|
||||
| "date"
|
||||
| "date-time"
|
||||
| "password"
|
||||
default?: any
|
||||
nullable?: boolean
|
||||
discriminator?: OpenApiDiscriminator
|
||||
readOnly?: boolean
|
||||
writeOnly?: boolean
|
||||
xml?: OpenApiXml
|
||||
externalDocs?: OpenApiExternalDocs
|
||||
example?: any
|
||||
deprecated?: boolean
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securityRequirementObject
|
||||
*/
|
||||
export interface OpenApiSecurityRequirement {
|
||||
[name: string]: string
|
||||
}
|
||||
16
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSecurityScheme.d.ts
vendored
Normal file
16
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiSecurityScheme.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { OpenApiOAuthFlows } from "./OpenApiOAuthFlows"
|
||||
import type { OpenApiReference } from "./OpenApiReference"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securitySchemeObject
|
||||
*/
|
||||
export interface OpenApiSecurityScheme extends OpenApiReference {
|
||||
type: "apiKey" | "http" | "oauth2" | "openIdConnect"
|
||||
description?: string
|
||||
name?: string
|
||||
in?: "query" | "header" | "cookie"
|
||||
scheme?: string
|
||||
bearerFormat?: string
|
||||
flows?: OpenApiOAuthFlows
|
||||
openIdConnectUrl?: string
|
||||
}
|
||||
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServer.d.ts
vendored
Normal file
11
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServer.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApiServerVariable } from "./OpenApiServerVariable"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#serverObject
|
||||
*/
|
||||
export interface OpenApiServer {
|
||||
url: string
|
||||
description?: string
|
||||
variables?: Dictionary<OpenApiServerVariable>
|
||||
}
|
||||
10
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServerVariable.d.ts
vendored
Normal file
10
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiServerVariable.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { WithEnumExtension } from "./Extensions/WithEnumExtension"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#serverVariableObject
|
||||
*/
|
||||
export interface OpenApiServerVariable extends WithEnumExtension {
|
||||
enum?: (string | number)[]
|
||||
default: string
|
||||
description?: string
|
||||
}
|
||||
10
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiTag.d.ts
vendored
Normal file
10
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiTag.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { OpenApiExternalDocs } from "./OpenApiExternalDocs"
|
||||
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#tagObject
|
||||
*/
|
||||
export interface OpenApiTag {
|
||||
name: string
|
||||
description?: string
|
||||
externalDocs?: OpenApiExternalDocs
|
||||
}
|
||||
10
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiXml.d.ts
vendored
Normal file
10
packages/oas/openapi-typescript-codegen/src/openApi/v3/interfaces/OpenApiXml.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#xmlObject
|
||||
*/
|
||||
export interface OpenApiXml {
|
||||
name?: string
|
||||
namespace?: string
|
||||
prefix?: string
|
||||
attribute?: boolean
|
||||
wrapped?: boolean
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export const escapeName = (value: string): string => {
|
||||
if (value || value === "") {
|
||||
const validName = /^[a-zA-Z_$][\w$]+$/g.test(value)
|
||||
if (!validName) {
|
||||
return `'${value}'`
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Enum } from "../../../client/interfaces/Enum"
|
||||
import { isString } from "../../../utils/isString"
|
||||
import type { WithEnumExtension } from "../interfaces/Extensions/WithEnumExtension"
|
||||
|
||||
/**
|
||||
* Extend the enum with the x-enum properties. This adds the capability
|
||||
* to use names and descriptions inside the generated enums.
|
||||
* @param enumerators
|
||||
* @param definition
|
||||
*/
|
||||
export const extendEnum = (
|
||||
enumerators: Enum[],
|
||||
definition: WithEnumExtension
|
||||
): Enum[] => {
|
||||
const names = definition["x-enum-varnames"]?.filter(isString)
|
||||
const descriptions = definition["x-enum-descriptions"]?.filter(isString)
|
||||
|
||||
return enumerators.map((enumerator, index) => ({
|
||||
name: names?.[index] || enumerator.name,
|
||||
description: descriptions?.[index] || enumerator.description,
|
||||
value: enumerator.value,
|
||||
type: enumerator.type,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { isDefined } from "../../../utils/isDefined"
|
||||
import type { Dictionary } from "../../../utils/types"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiMediaType } from "../interfaces/OpenApiMediaType"
|
||||
import type { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
|
||||
export interface Content {
|
||||
mediaType: string
|
||||
schema: OpenApiSchema
|
||||
}
|
||||
|
||||
const BASIC_MEDIA_TYPES = [
|
||||
"application/json-patch+json",
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded",
|
||||
"text/json",
|
||||
"text/plain",
|
||||
"multipart/form-data",
|
||||
"multipart/mixed",
|
||||
"multipart/related",
|
||||
"multipart/batch",
|
||||
]
|
||||
|
||||
export const getContent = (
|
||||
openApi: OpenApi,
|
||||
content: Dictionary<OpenApiMediaType>
|
||||
): Content | null => {
|
||||
const basicMediaTypeWithSchema = Object.keys(content)
|
||||
.filter((mediaType) => {
|
||||
const cleanMediaType = mediaType.split(";")[0].trim()
|
||||
return BASIC_MEDIA_TYPES.includes(cleanMediaType)
|
||||
})
|
||||
.find((mediaType) => isDefined(content[mediaType]?.schema))
|
||||
if (basicMediaTypeWithSchema) {
|
||||
return {
|
||||
mediaType: basicMediaTypeWithSchema,
|
||||
schema: content[basicMediaTypeWithSchema].schema as OpenApiSchema,
|
||||
}
|
||||
}
|
||||
|
||||
const firstMediaTypeWithSchema = Object.keys(content).find((mediaType) =>
|
||||
isDefined(content[mediaType]?.schema)
|
||||
)
|
||||
if (firstMediaTypeWithSchema) {
|
||||
return {
|
||||
mediaType: firstMediaTypeWithSchema,
|
||||
schema: content[firstMediaTypeWithSchema].schema as OpenApiSchema,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Enum } from "../../../client/interfaces/Enum"
|
||||
|
||||
export const getEnum = (values?: (string | number)[]): Enum[] => {
|
||||
if (Array.isArray(values)) {
|
||||
return values
|
||||
.filter((value, index, arr) => {
|
||||
return arr.indexOf(value) === index
|
||||
})
|
||||
.filter((value: any) => {
|
||||
return typeof value === "number" || typeof value === "string"
|
||||
})
|
||||
.map((value) => {
|
||||
if (typeof value === "number") {
|
||||
return {
|
||||
name: `'_${value}'`,
|
||||
value: String(value),
|
||||
type: "number",
|
||||
description: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: String(value)
|
||||
.replace(/\W+/g, "_")
|
||||
.replace(/^(\d+)/g, "_$1")
|
||||
.replace(/([a-z])([A-Z]+)/g, "$1_$2")
|
||||
.toUpperCase(),
|
||||
value: `'${value.replace(/'/g, "\\'")}'`,
|
||||
type: "string",
|
||||
description: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
return []
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
const TYPE_MAPPINGS = new Map<string, string>([
|
||||
["file", "binary"],
|
||||
["any", "any"],
|
||||
["object", "any"],
|
||||
["array", "any[]"],
|
||||
["boolean", "boolean"],
|
||||
["byte", "number"],
|
||||
["int", "number"],
|
||||
["integer", "number"],
|
||||
["float", "number"],
|
||||
["double", "number"],
|
||||
["short", "number"],
|
||||
["long", "number"],
|
||||
["number", "number"],
|
||||
["char", "string"],
|
||||
["date", "string"],
|
||||
["date-time", "string"],
|
||||
["password", "string"],
|
||||
["string", "string"],
|
||||
["void", "void"],
|
||||
["null", "null"],
|
||||
])
|
||||
|
||||
/**
|
||||
* Get mapped type for given type to any basic Typescript/Javascript type.
|
||||
*/
|
||||
export const getMappedType = (
|
||||
type: string,
|
||||
format?: string
|
||||
): string | undefined => {
|
||||
if (format === "binary") {
|
||||
return "binary"
|
||||
}
|
||||
return TYPE_MAPPINGS.get(type)
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
import type { Model } from "../../../client/interfaces/Model"
|
||||
import { getPattern } from "../../../utils/getPattern"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
import { extendEnum } from "./extendEnum"
|
||||
import { getEnum } from "./getEnum"
|
||||
import { getModelComposition } from "./getModelComposition"
|
||||
import { getModelDefault } from "./getModelDefault"
|
||||
import { getModelProperties } from "./getModelProperties"
|
||||
import { getType } from "./getType"
|
||||
|
||||
export const getModel = (
|
||||
openApi: OpenApi,
|
||||
definition: OpenApiSchema,
|
||||
isDefinition: boolean = false,
|
||||
name: string = ""
|
||||
): Model => {
|
||||
const model: Model = {
|
||||
spec: definition,
|
||||
name,
|
||||
export: "interface",
|
||||
type: "any",
|
||||
base: "any",
|
||||
template: null,
|
||||
link: null,
|
||||
description: definition.description || null,
|
||||
deprecated: definition.deprecated === true,
|
||||
isDefinition,
|
||||
isReadOnly: definition.readOnly === true,
|
||||
isNullable: definition.nullable === true,
|
||||
isRequired: false,
|
||||
format: definition.format,
|
||||
maximum: definition.maximum,
|
||||
exclusiveMaximum: definition.exclusiveMaximum,
|
||||
minimum: definition.minimum,
|
||||
exclusiveMinimum: definition.exclusiveMinimum,
|
||||
multipleOf: definition.multipleOf,
|
||||
maxLength: definition.maxLength,
|
||||
minLength: definition.minLength,
|
||||
maxItems: definition.maxItems,
|
||||
minItems: definition.minItems,
|
||||
uniqueItems: definition.uniqueItems,
|
||||
maxProperties: definition.maxProperties,
|
||||
minProperties: definition.minProperties,
|
||||
pattern: getPattern(definition.pattern),
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
}
|
||||
|
||||
if (definition.$ref) {
|
||||
const definitionRef = getType(definition.$ref)
|
||||
model.export = "reference"
|
||||
model.type = definitionRef.type
|
||||
model.base = definitionRef.base
|
||||
model.template = definitionRef.template
|
||||
model.imports.push(...definitionRef.imports)
|
||||
model.default = getModelDefault(definition, model)
|
||||
return model
|
||||
}
|
||||
|
||||
if (definition.enum && definition.type !== "boolean") {
|
||||
const enumerators = getEnum(definition.enum)
|
||||
const extendedEnumerators = extendEnum(enumerators, definition)
|
||||
if (extendedEnumerators.length) {
|
||||
model.export = "enum"
|
||||
model.type = "string"
|
||||
model.base = "string"
|
||||
model.enum.push(...extendedEnumerators)
|
||||
model.default = getModelDefault(definition, model)
|
||||
return model
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type === "array" && definition.items) {
|
||||
if (definition.items.$ref) {
|
||||
const arrayItems = getType(definition.items.$ref)
|
||||
model.export = "array"
|
||||
model.type = arrayItems.type
|
||||
model.base = arrayItems.base
|
||||
model.template = arrayItems.template
|
||||
model.imports.push(...arrayItems.imports)
|
||||
model.default = getModelDefault(definition, model)
|
||||
return model
|
||||
} else {
|
||||
const arrayItems = getModel(openApi, definition.items)
|
||||
model.export = "array"
|
||||
model.type = arrayItems.type
|
||||
model.base = arrayItems.base
|
||||
model.template = arrayItems.template
|
||||
model.link = arrayItems
|
||||
model.imports.push(...arrayItems.imports)
|
||||
model.default = getModelDefault(definition, model)
|
||||
return model
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
definition.type === "object" &&
|
||||
(typeof definition.additionalProperties === "object" ||
|
||||
definition.additionalProperties === true)
|
||||
) {
|
||||
const ap =
|
||||
typeof definition.additionalProperties === "object"
|
||||
? definition.additionalProperties
|
||||
: {}
|
||||
if (ap.$ref) {
|
||||
const additionalProperties = getType(ap.$ref)
|
||||
model.export = "dictionary"
|
||||
model.type = additionalProperties.type
|
||||
model.base = additionalProperties.base
|
||||
model.template = additionalProperties.template
|
||||
model.imports.push(...additionalProperties.imports)
|
||||
model.default = getModelDefault(definition, model)
|
||||
return model
|
||||
} else {
|
||||
const additionalProperties = getModel(openApi, ap)
|
||||
model.export = "dictionary"
|
||||
model.type = additionalProperties.type
|
||||
model.base = additionalProperties.base
|
||||
model.template = additionalProperties.template
|
||||
model.link = additionalProperties
|
||||
model.imports.push(...additionalProperties.imports)
|
||||
model.default = getModelDefault(definition, model)
|
||||
return model
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.oneOf?.length) {
|
||||
const composition = getModelComposition(
|
||||
openApi,
|
||||
definition,
|
||||
definition.oneOf,
|
||||
"one-of",
|
||||
getModel
|
||||
)
|
||||
model.export = composition.type
|
||||
model.imports.push(...composition.imports)
|
||||
model.properties.push(...composition.properties)
|
||||
model.enums.push(...composition.enums)
|
||||
return model
|
||||
}
|
||||
|
||||
if (definition.anyOf?.length) {
|
||||
const composition = getModelComposition(
|
||||
openApi,
|
||||
definition,
|
||||
definition.anyOf,
|
||||
"any-of",
|
||||
getModel
|
||||
)
|
||||
model.export = composition.type
|
||||
model.imports.push(...composition.imports)
|
||||
model.properties.push(...composition.properties)
|
||||
model.enums.push(...composition.enums)
|
||||
return model
|
||||
}
|
||||
|
||||
if (definition.allOf?.length) {
|
||||
const composition = getModelComposition(
|
||||
openApi,
|
||||
definition,
|
||||
definition.allOf,
|
||||
"all-of",
|
||||
getModel
|
||||
)
|
||||
model.export = composition.type
|
||||
model.imports.push(...composition.imports)
|
||||
model.properties.push(...composition.properties)
|
||||
model.enums.push(...composition.enums)
|
||||
return model
|
||||
}
|
||||
|
||||
if (definition.type === "object") {
|
||||
if (definition.properties) {
|
||||
model.export = "interface"
|
||||
model.type = "any"
|
||||
model.base = "any"
|
||||
model.default = getModelDefault(definition, model)
|
||||
|
||||
const modelProperties = getModelProperties(
|
||||
openApi,
|
||||
definition,
|
||||
getModel,
|
||||
model
|
||||
)
|
||||
modelProperties.forEach((modelProperty) => {
|
||||
model.imports.push(...modelProperty.imports)
|
||||
model.enums.push(...modelProperty.enums)
|
||||
model.properties.push(modelProperty)
|
||||
if (modelProperty.export === "enum") {
|
||||
model.enums.push(modelProperty)
|
||||
}
|
||||
})
|
||||
return model
|
||||
} else {
|
||||
const additionalProperties = getModel(openApi, {})
|
||||
model.export = "dictionary"
|
||||
model.type = additionalProperties.type
|
||||
model.base = additionalProperties.base
|
||||
model.template = additionalProperties.template
|
||||
model.link = additionalProperties
|
||||
model.imports.push(...additionalProperties.imports)
|
||||
model.default = getModelDefault(definition, model)
|
||||
return model
|
||||
}
|
||||
}
|
||||
|
||||
// If the schema has a type than it can be a basic or generic type.
|
||||
if (definition.type) {
|
||||
const definitionType = getType(definition.type, definition.format)
|
||||
model.export = "generic"
|
||||
model.type = definitionType.type
|
||||
model.base = definitionType.base
|
||||
model.template = definitionType.template
|
||||
model.isNullable = definitionType.isNullable || model.isNullable
|
||||
model.imports.push(...definitionType.imports)
|
||||
model.default = getModelDefault(definition, model)
|
||||
return model
|
||||
}
|
||||
|
||||
return model
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { Model } from "../../../client/interfaces/Model"
|
||||
import type { ModelComposition } from "../../../client/interfaces/ModelComposition"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
import type { getModel } from "./getModel"
|
||||
import { getModelProperties } from "./getModelProperties"
|
||||
import { getRequiredPropertiesFromComposition } from "./getRequiredPropertiesFromComposition"
|
||||
|
||||
// Fix for circular dependency
|
||||
export type GetModelFn = typeof getModel
|
||||
|
||||
export const getModelComposition = (
|
||||
openApi: OpenApi,
|
||||
definition: OpenApiSchema,
|
||||
definitions: OpenApiSchema[],
|
||||
type: "one-of" | "any-of" | "all-of",
|
||||
getModel: GetModelFn
|
||||
): ModelComposition => {
|
||||
const composition: ModelComposition = {
|
||||
type,
|
||||
imports: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
}
|
||||
|
||||
const properties: Model[] = []
|
||||
|
||||
definitions
|
||||
.map((definition) => getModel(openApi, definition))
|
||||
.filter((model) => {
|
||||
const hasProperties = model.properties.length
|
||||
const hasEnums = model.enums.length
|
||||
const isObject = model.type === "any"
|
||||
const isDictionary = model.export === "dictionary"
|
||||
const isEmpty = isObject && !hasProperties && !hasEnums
|
||||
return !isEmpty || isDictionary
|
||||
})
|
||||
.forEach((model) => {
|
||||
composition.imports.push(...model.imports)
|
||||
composition.enums.push(...model.enums)
|
||||
composition.properties.push(model)
|
||||
})
|
||||
|
||||
if (definition.required) {
|
||||
const requiredProperties = getRequiredPropertiesFromComposition(
|
||||
openApi,
|
||||
definition.required,
|
||||
definitions,
|
||||
getModel
|
||||
)
|
||||
requiredProperties.forEach((requiredProperty) => {
|
||||
composition.imports.push(...requiredProperty.imports)
|
||||
composition.enums.push(...requiredProperty.enums)
|
||||
})
|
||||
properties.push(...requiredProperties)
|
||||
}
|
||||
|
||||
if (definition.properties) {
|
||||
const modelProperties = getModelProperties(openApi, definition, getModel)
|
||||
modelProperties.forEach((modelProperty) => {
|
||||
composition.imports.push(...modelProperty.imports)
|
||||
composition.enums.push(...modelProperty.enums)
|
||||
if (modelProperty.export === "enum") {
|
||||
composition.enums.push(modelProperty)
|
||||
}
|
||||
})
|
||||
properties.push(...modelProperties)
|
||||
}
|
||||
|
||||
if (properties.length) {
|
||||
composition.properties.push({
|
||||
spec: definition,
|
||||
name: "properties",
|
||||
export: "interface",
|
||||
type: "any",
|
||||
base: "any",
|
||||
template: null,
|
||||
link: null,
|
||||
description: "",
|
||||
isDefinition: false,
|
||||
isReadOnly: false,
|
||||
isNullable: false,
|
||||
isRequired: false,
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties,
|
||||
})
|
||||
}
|
||||
|
||||
return composition
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { Model } from "../../../client/interfaces/Model"
|
||||
import type { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
|
||||
export const getModelDefault = (
|
||||
definition: OpenApiSchema,
|
||||
model?: Model
|
||||
): string | undefined => {
|
||||
if (definition.default === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (definition.default === null) {
|
||||
return "null"
|
||||
}
|
||||
|
||||
const type = definition.type || typeof definition.default
|
||||
|
||||
switch (type) {
|
||||
case "int":
|
||||
case "integer":
|
||||
case "number":
|
||||
if (model?.export === "enum" && model.enum?.[definition.default]) {
|
||||
return model.enum[definition.default].value
|
||||
}
|
||||
return definition.default
|
||||
|
||||
case "boolean":
|
||||
return JSON.stringify(definition.default)
|
||||
|
||||
case "string":
|
||||
return `'${definition.default}'`
|
||||
|
||||
case "object":
|
||||
try {
|
||||
return JSON.stringify(definition.default, null, 4)
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { Model } from "../../../client/interfaces/Model"
|
||||
import {
|
||||
findOneOfParentDiscriminator,
|
||||
mapPropertyValue,
|
||||
} from "../../../utils/discriminator"
|
||||
import { getPattern } from "../../../utils/getPattern"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
import { escapeName } from "./escapeName"
|
||||
import type { getModel } from "./getModel"
|
||||
import { getType } from "./getType"
|
||||
|
||||
// Fix for circular dependency
|
||||
export type GetModelFn = typeof getModel
|
||||
|
||||
export const getModelProperties = (
|
||||
openApi: OpenApi,
|
||||
definition: OpenApiSchema,
|
||||
getModel: GetModelFn,
|
||||
parent?: Model
|
||||
): Model[] => {
|
||||
const models: Model[] = []
|
||||
const discriminator = findOneOfParentDiscriminator(openApi, parent)
|
||||
for (const propertyName in definition.properties) {
|
||||
if (definition.properties.hasOwnProperty(propertyName)) {
|
||||
const property = definition.properties[propertyName]
|
||||
const propertyRequired = !!definition.required?.includes(propertyName)
|
||||
const propertyValues: Omit<
|
||||
Model,
|
||||
| "export"
|
||||
| "type"
|
||||
| "base"
|
||||
| "template"
|
||||
| "link"
|
||||
| "isNullable"
|
||||
| "imports"
|
||||
| "enum"
|
||||
| "enums"
|
||||
| "properties"
|
||||
> = {
|
||||
spec: definition,
|
||||
name: escapeName(propertyName),
|
||||
description: property.description || null,
|
||||
deprecated: property.deprecated === true,
|
||||
isDefinition: false,
|
||||
isReadOnly: property.readOnly === true,
|
||||
isRequired: propertyRequired,
|
||||
format: property.format,
|
||||
maximum: property.maximum,
|
||||
exclusiveMaximum: property.exclusiveMaximum,
|
||||
minimum: property.minimum,
|
||||
exclusiveMinimum: property.exclusiveMinimum,
|
||||
multipleOf: property.multipleOf,
|
||||
maxLength: property.maxLength,
|
||||
minLength: property.minLength,
|
||||
maxItems: property.maxItems,
|
||||
minItems: property.minItems,
|
||||
uniqueItems: property.uniqueItems,
|
||||
maxProperties: property.maxProperties,
|
||||
minProperties: property.minProperties,
|
||||
pattern: getPattern(property.pattern),
|
||||
}
|
||||
if (parent && discriminator?.propertyName == propertyName) {
|
||||
models.push({
|
||||
export: "reference",
|
||||
type: "string",
|
||||
base: `'${mapPropertyValue(discriminator, parent)}'`,
|
||||
template: null,
|
||||
isNullable: property.nullable === true,
|
||||
link: null,
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
...propertyValues,
|
||||
})
|
||||
} else if (property.$ref) {
|
||||
const model = getType(property.$ref)
|
||||
models.push({
|
||||
export: "reference",
|
||||
type: model.type,
|
||||
base: model.base,
|
||||
template: model.template,
|
||||
link: null,
|
||||
isNullable: model.isNullable || property.nullable === true,
|
||||
imports: model.imports,
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
...propertyValues,
|
||||
})
|
||||
} else {
|
||||
const model = getModel(openApi, property)
|
||||
models.push({
|
||||
export: model.export,
|
||||
type: model.type,
|
||||
base: model.base,
|
||||
template: model.template,
|
||||
link: model.link,
|
||||
isNullable: model.isNullable || property.nullable === true,
|
||||
imports: model.imports,
|
||||
enum: model.enum,
|
||||
enums: model.enums,
|
||||
properties: model.properties,
|
||||
...propertyValues,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return models
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Type } from "../../../client/interfaces/Type"
|
||||
|
||||
/**
|
||||
* If our model has a template type, then we want to generalize that!
|
||||
* In that case we should return "<T>" as our template type.
|
||||
* @param modelClass The parsed model class type.
|
||||
* @returns The model template type (<T> or empty).
|
||||
*/
|
||||
export const getModelTemplate = (modelClass: Type): string => {
|
||||
return modelClass.template ? "<T>" : ""
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { Model } from "../../../client/interfaces/Model"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import { getModel } from "./getModel"
|
||||
import { reservedWords } from "./getOperationParameterName"
|
||||
import { getType } from "./getType"
|
||||
import { OperationParameter } from "../../../client/interfaces/OperationParameter"
|
||||
import { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
import { Dictionary } from "../../../utils/types"
|
||||
import { OpenApiParameter } from "../interfaces/OpenApiParameter"
|
||||
import { listOperations } from "./listOperations"
|
||||
|
||||
export const getModels = (openApi: OpenApi): Model[] => {
|
||||
const models: Model[] = []
|
||||
if (openApi.components) {
|
||||
for (const definitionName in openApi.components.schemas) {
|
||||
if (openApi.components.schemas.hasOwnProperty(definitionName)) {
|
||||
const definition = openApi.components.schemas[definitionName]
|
||||
const definitionType = getType(definitionName)
|
||||
const model = getModel(
|
||||
openApi,
|
||||
definition,
|
||||
true,
|
||||
definitionType.base.replace(reservedWords, "_$1")
|
||||
)
|
||||
models.push(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle all query parameters in a single typed object
|
||||
* when x-codegen.queryParams is declared on the operation.
|
||||
*/
|
||||
const operations = listOperations(openApi)
|
||||
for (const operation of operations) {
|
||||
if (operation.codegen.queryParams) {
|
||||
const definition = getDefinitionFromParametersQuery(
|
||||
operation.parametersQuery
|
||||
)
|
||||
const model = getModel(
|
||||
openApi,
|
||||
definition,
|
||||
true,
|
||||
operation.codegen.queryParams
|
||||
)
|
||||
models.push(model)
|
||||
}
|
||||
}
|
||||
|
||||
return models
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines and converts query parameters into schema properties.
|
||||
* Given a typical parameter OAS:
|
||||
* ```
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* default: 10
|
||||
* required: true
|
||||
* description: Limit the number of results returned.
|
||||
* ```
|
||||
* Convert into a schema property:
|
||||
* ```
|
||||
* UnnamedSchema:
|
||||
* type: object
|
||||
* required:
|
||||
* - limit
|
||||
* properties:
|
||||
* limit:
|
||||
* description: Limit the number of results returned.
|
||||
* type: integer
|
||||
* default: 10
|
||||
* ```
|
||||
*/
|
||||
const getDefinitionFromParametersQuery = (
|
||||
parametersQuery: OperationParameter[]
|
||||
): OpenApiSchema => {
|
||||
/**
|
||||
* Identify query parameters that are required (non-optional).
|
||||
*/
|
||||
const required = parametersQuery
|
||||
.filter((parameter) => (parameter.spec as OpenApiParameter).required)
|
||||
.map((parameter) => (parameter.spec as OpenApiParameter).name)
|
||||
|
||||
const properties: Dictionary<OpenApiSchema> = {}
|
||||
for (const parameter of parametersQuery) {
|
||||
const spec = parameter.spec as OpenApiParameter
|
||||
/**
|
||||
* Augment a copy of schema with description and deprecated
|
||||
* and assign it as a named property on the schema.
|
||||
*/
|
||||
properties[spec.name] = Object.assign(
|
||||
{ ...spec.schema },
|
||||
{
|
||||
description: spec.description,
|
||||
deprecated: spec.deprecated,
|
||||
}
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Return an unnamed schema definition.
|
||||
*/
|
||||
return {
|
||||
type: "object",
|
||||
required,
|
||||
properties,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { Operation } from "../../../client/interfaces/Operation"
|
||||
import type { OperationParameters } from "../../../client/interfaces/OperationParameters"
|
||||
import type { OperationCodegen } from "../../../client/interfaces/OperationCodegen"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiOperation } from "../interfaces/OpenApiOperation"
|
||||
import type { OpenApiRequestBody } from "../interfaces/OpenApiRequestBody"
|
||||
import { getOperationErrors } from "./getOperationErrors"
|
||||
import { getOperationName } from "./getOperationName"
|
||||
import { getOperationParameters } from "./getOperationParameters"
|
||||
import { getOperationRequestBody } from "./getOperationRequestBody"
|
||||
import { getOperationResponseHeader } from "./getOperationResponseHeader"
|
||||
import { getOperationResponses } from "./getOperationResponses"
|
||||
import { getOperationResults } from "./getOperationResults"
|
||||
import { getRef } from "./getRef"
|
||||
import { getServiceName } from "./getServiceName"
|
||||
import { sortByRequired } from "./sortByRequired"
|
||||
|
||||
export const getOperation = (
|
||||
openApi: OpenApi,
|
||||
url: string,
|
||||
method: string,
|
||||
tag: string,
|
||||
op: OpenApiOperation,
|
||||
pathParams: OperationParameters
|
||||
): Operation => {
|
||||
const serviceName = getServiceName(tag)
|
||||
const operationName = getOperationName(url, method, op.operationId)
|
||||
const codegen: OperationCodegen = op["x-codegen"] || {}
|
||||
|
||||
// Create a new operation object for this method.
|
||||
const operation: Operation = {
|
||||
service: serviceName,
|
||||
name: codegen.method || operationName,
|
||||
operationId: op.operationId || null,
|
||||
summary: op.summary || null,
|
||||
description: op.description || null,
|
||||
deprecated: op.deprecated === true,
|
||||
method: method.toUpperCase(),
|
||||
path: url,
|
||||
parameters: [...pathParams.parameters],
|
||||
parametersPath: [...pathParams.parametersPath],
|
||||
parametersQuery: [...pathParams.parametersQuery],
|
||||
parametersForm: [...pathParams.parametersForm],
|
||||
parametersHeader: [...pathParams.parametersHeader],
|
||||
parametersCookie: [...pathParams.parametersCookie],
|
||||
parametersBody: pathParams.parametersBody,
|
||||
imports: [],
|
||||
errors: [],
|
||||
results: [],
|
||||
responseHeader: null,
|
||||
codegen,
|
||||
}
|
||||
|
||||
// Parse the operation parameters (path, query, body, etc).
|
||||
if (op.parameters) {
|
||||
const parameters = getOperationParameters(openApi, op.parameters)
|
||||
operation.imports.push(...parameters.imports)
|
||||
operation.parameters.push(...parameters.parameters)
|
||||
operation.parametersPath.push(...parameters.parametersPath)
|
||||
operation.parametersQuery.push(...parameters.parametersQuery)
|
||||
operation.parametersForm.push(...parameters.parametersForm)
|
||||
operation.parametersHeader.push(...parameters.parametersHeader)
|
||||
operation.parametersCookie.push(...parameters.parametersCookie)
|
||||
operation.parametersBody = parameters.parametersBody
|
||||
}
|
||||
|
||||
if (op.requestBody) {
|
||||
const requestBodyDef = getRef<OpenApiRequestBody>(openApi, op.requestBody)
|
||||
const requestBody = getOperationRequestBody(openApi, requestBodyDef)
|
||||
operation.imports.push(...requestBody.imports)
|
||||
operation.parameters.push(requestBody)
|
||||
operation.parametersBody = requestBody
|
||||
}
|
||||
|
||||
// Parse the operation responses.
|
||||
if (op.responses) {
|
||||
const operationResponses = getOperationResponses(openApi, op.responses)
|
||||
const operationResults = getOperationResults(operationResponses)
|
||||
operation.errors = getOperationErrors(operationResponses)
|
||||
operation.responseHeader = getOperationResponseHeader(operationResults)
|
||||
|
||||
operationResults.forEach((operationResult) => {
|
||||
operation.results.push(operationResult)
|
||||
operation.imports.push(...operationResult.imports)
|
||||
})
|
||||
}
|
||||
|
||||
if (codegen.queryParams) {
|
||||
operation.imports.push(codegen.queryParams)
|
||||
}
|
||||
|
||||
operation.parameters = operation.parameters.sort(sortByRequired)
|
||||
|
||||
return operation
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { OperationError } from "../../../client/interfaces/OperationError"
|
||||
import type { OperationResponse } from "../../../client/interfaces/OperationResponse"
|
||||
|
||||
export const getOperationErrors = (
|
||||
operationResponses: OperationResponse[]
|
||||
): OperationError[] => {
|
||||
return operationResponses
|
||||
.filter((operationResponse) => {
|
||||
return operationResponse.code >= 300 && operationResponse.description
|
||||
})
|
||||
.map((response) => ({
|
||||
code: response.code,
|
||||
description: response.description!,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import camelCase from "camelcase"
|
||||
|
||||
/**
|
||||
* Convert the input value to a correct operation (method) classname.
|
||||
* This will use the operation ID - if available - and otherwise fallback
|
||||
* on a generated name from the URL
|
||||
*/
|
||||
export const getOperationName = (
|
||||
url: string,
|
||||
method: string,
|
||||
operationId?: string
|
||||
): string => {
|
||||
if (operationId) {
|
||||
return camelCase(
|
||||
operationId
|
||||
.replace(/^[^a-zA-Z]+/g, "")
|
||||
.replace(/[^\w\-]+/g, "-")
|
||||
.trim()
|
||||
)
|
||||
}
|
||||
|
||||
const urlWithoutPlaceholders = url
|
||||
.replace(/[^/]*?{api-version}.*?\//g, "")
|
||||
.replace(/{(.*?)}/g, "")
|
||||
.replace(/\//g, "-")
|
||||
|
||||
return camelCase(`${method}-${urlWithoutPlaceholders}`)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { OperationParameter } from "../../../client/interfaces/OperationParameter"
|
||||
import { getPattern } from "../../../utils/getPattern"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiParameter } from "../interfaces/OpenApiParameter"
|
||||
import type { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
import { getModel } from "./getModel"
|
||||
import { getModelDefault } from "./getModelDefault"
|
||||
import { getOperationParameterName } from "./getOperationParameterName"
|
||||
import { getRef } from "./getRef"
|
||||
import { getType } from "./getType"
|
||||
|
||||
export const getOperationParameter = (
|
||||
openApi: OpenApi,
|
||||
parameter: OpenApiParameter
|
||||
): OperationParameter => {
|
||||
const operationParameter: OperationParameter = {
|
||||
spec: parameter,
|
||||
in: parameter.in,
|
||||
prop: parameter.name,
|
||||
export: "interface",
|
||||
name: getOperationParameterName(parameter.name),
|
||||
type: "any",
|
||||
base: "any",
|
||||
template: null,
|
||||
link: null,
|
||||
description: parameter.description || null,
|
||||
deprecated: parameter.deprecated === true,
|
||||
isDefinition: false,
|
||||
isReadOnly: false,
|
||||
isRequired: parameter.required === true,
|
||||
isNullable: parameter.nullable === true,
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
mediaType: null,
|
||||
}
|
||||
|
||||
if (parameter.$ref) {
|
||||
const definitionRef = getType(parameter.$ref)
|
||||
operationParameter.export = "reference"
|
||||
operationParameter.type = definitionRef.type
|
||||
operationParameter.base = definitionRef.base
|
||||
operationParameter.template = definitionRef.template
|
||||
operationParameter.imports.push(...definitionRef.imports)
|
||||
return operationParameter
|
||||
}
|
||||
|
||||
let schema = parameter.schema
|
||||
if (schema) {
|
||||
if (schema.$ref?.startsWith("#/components/parameters/")) {
|
||||
schema = getRef<OpenApiSchema>(openApi, schema)
|
||||
}
|
||||
if (schema.$ref) {
|
||||
const model = getType(schema.$ref)
|
||||
operationParameter.export = "reference"
|
||||
operationParameter.type = model.type
|
||||
operationParameter.base = model.base
|
||||
operationParameter.template = model.template
|
||||
operationParameter.imports.push(...model.imports)
|
||||
operationParameter.default = getModelDefault(schema)
|
||||
return operationParameter
|
||||
} else {
|
||||
const model = getModel(openApi, schema)
|
||||
operationParameter.export = model.export
|
||||
operationParameter.type = model.type
|
||||
operationParameter.base = model.base
|
||||
operationParameter.template = model.template
|
||||
operationParameter.link = model.link
|
||||
operationParameter.isReadOnly = model.isReadOnly
|
||||
operationParameter.isRequired =
|
||||
operationParameter.isRequired || model.isRequired
|
||||
operationParameter.isNullable =
|
||||
operationParameter.isNullable || model.isNullable
|
||||
operationParameter.format = model.format
|
||||
operationParameter.maximum = model.maximum
|
||||
operationParameter.exclusiveMaximum = model.exclusiveMaximum
|
||||
operationParameter.minimum = model.minimum
|
||||
operationParameter.exclusiveMinimum = model.exclusiveMinimum
|
||||
operationParameter.multipleOf = model.multipleOf
|
||||
operationParameter.maxLength = model.maxLength
|
||||
operationParameter.minLength = model.minLength
|
||||
operationParameter.maxItems = model.maxItems
|
||||
operationParameter.minItems = model.minItems
|
||||
operationParameter.uniqueItems = model.uniqueItems
|
||||
operationParameter.maxProperties = model.maxProperties
|
||||
operationParameter.minProperties = model.minProperties
|
||||
operationParameter.pattern = getPattern(model.pattern)
|
||||
operationParameter.default = model.default
|
||||
operationParameter.imports.push(...model.imports)
|
||||
operationParameter.enum.push(...model.enum)
|
||||
operationParameter.enums.push(...model.enums)
|
||||
operationParameter.properties.push(...model.properties)
|
||||
return operationParameter
|
||||
}
|
||||
}
|
||||
|
||||
return operationParameter
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import camelCase from "camelcase"
|
||||
|
||||
export const reservedWords =
|
||||
/^(arguments|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|eval|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)$/g
|
||||
|
||||
/**
|
||||
* Replaces any invalid characters from a parameter name.
|
||||
* For example: 'filter.someProperty' becomes 'filterSomeProperty'.
|
||||
*/
|
||||
export const getOperationParameterName = (value: string): string => {
|
||||
const clean = value
|
||||
.replace(/^[^a-zA-Z]+/g, "")
|
||||
.replace(/[^\w\-]+/g, "-")
|
||||
.trim()
|
||||
return camelCase(clean).replace(reservedWords, "_$1")
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { OperationParameters } from "../../../client/interfaces/OperationParameters"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiParameter } from "../interfaces/OpenApiParameter"
|
||||
import { getOperationParameter } from "./getOperationParameter"
|
||||
import { getRef } from "./getRef"
|
||||
|
||||
export const getOperationParameters = (
|
||||
openApi: OpenApi,
|
||||
parameters: OpenApiParameter[]
|
||||
): OperationParameters => {
|
||||
const operationParameters: OperationParameters = {
|
||||
imports: [],
|
||||
parameters: [],
|
||||
parametersPath: [],
|
||||
parametersQuery: [],
|
||||
parametersForm: [],
|
||||
parametersCookie: [],
|
||||
parametersHeader: [],
|
||||
parametersBody: null, // Not used in V3 -> @see requestBody
|
||||
}
|
||||
|
||||
// Iterate over the parameters
|
||||
parameters.forEach((parameterOrReference) => {
|
||||
const parameterDef = getRef<OpenApiParameter>(openApi, parameterOrReference)
|
||||
const parameter = getOperationParameter(openApi, parameterDef)
|
||||
|
||||
// We ignore the "api-version" param, since we do not want to add this
|
||||
// as the first / default parameter for each of the service calls.
|
||||
if (parameter.prop !== "api-version") {
|
||||
switch (parameterDef.in) {
|
||||
case "path":
|
||||
operationParameters.parametersPath.push(parameter)
|
||||
operationParameters.parameters.push(parameter)
|
||||
operationParameters.imports.push(...parameter.imports)
|
||||
break
|
||||
|
||||
case "query":
|
||||
operationParameters.parametersQuery.push(parameter)
|
||||
operationParameters.parameters.push(parameter)
|
||||
operationParameters.imports.push(...parameter.imports)
|
||||
break
|
||||
|
||||
case "formData":
|
||||
operationParameters.parametersForm.push(parameter)
|
||||
operationParameters.parameters.push(parameter)
|
||||
operationParameters.imports.push(...parameter.imports)
|
||||
break
|
||||
|
||||
case "cookie":
|
||||
operationParameters.parametersCookie.push(parameter)
|
||||
operationParameters.parameters.push(parameter)
|
||||
operationParameters.imports.push(...parameter.imports)
|
||||
break
|
||||
|
||||
case "header":
|
||||
operationParameters.parametersHeader.push(parameter)
|
||||
operationParameters.parameters.push(parameter)
|
||||
operationParameters.imports.push(...parameter.imports)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
return operationParameters
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { OperationParameter } from "../../../client/interfaces/OperationParameter"
|
||||
import { getPattern } from "../../../utils/getPattern"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiRequestBody } from "../interfaces/OpenApiRequestBody"
|
||||
import { getContent } from "./getContent"
|
||||
import { getModel } from "./getModel"
|
||||
import { getType } from "./getType"
|
||||
|
||||
export const getOperationRequestBody = (
|
||||
openApi: OpenApi,
|
||||
body: OpenApiRequestBody
|
||||
): OperationParameter => {
|
||||
const requestBody: OperationParameter = {
|
||||
spec: body,
|
||||
in: "body",
|
||||
export: "interface",
|
||||
prop: "requestBody",
|
||||
name: "requestBody",
|
||||
type: "any",
|
||||
base: "any",
|
||||
template: null,
|
||||
link: null,
|
||||
description: body.description || null,
|
||||
default: undefined,
|
||||
isDefinition: false,
|
||||
isReadOnly: false,
|
||||
isRequired: body.required === true,
|
||||
isNullable: body.nullable === true,
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
mediaType: null,
|
||||
}
|
||||
|
||||
if (body.content) {
|
||||
const content = getContent(openApi, body.content)
|
||||
if (content) {
|
||||
requestBody.mediaType = content.mediaType
|
||||
switch (requestBody.mediaType) {
|
||||
case "application/x-www-form-urlencoded":
|
||||
case "multipart/form-data":
|
||||
requestBody.in = "formData"
|
||||
requestBody.name = "formData"
|
||||
requestBody.prop = "formData"
|
||||
break
|
||||
}
|
||||
if (content.schema.$ref) {
|
||||
const model = getType(content.schema.$ref)
|
||||
requestBody.export = "reference"
|
||||
requestBody.type = model.type
|
||||
requestBody.base = model.base
|
||||
requestBody.template = model.template
|
||||
requestBody.imports.push(...model.imports)
|
||||
return requestBody
|
||||
} else {
|
||||
const model = getModel(openApi, content.schema)
|
||||
requestBody.export = model.export
|
||||
requestBody.type = model.type
|
||||
requestBody.base = model.base
|
||||
requestBody.template = model.template
|
||||
requestBody.link = model.link
|
||||
requestBody.isReadOnly = model.isReadOnly
|
||||
requestBody.isRequired = requestBody.isRequired || model.isRequired
|
||||
requestBody.isNullable = requestBody.isNullable || model.isNullable
|
||||
requestBody.format = model.format
|
||||
requestBody.maximum = model.maximum
|
||||
requestBody.exclusiveMaximum = model.exclusiveMaximum
|
||||
requestBody.minimum = model.minimum
|
||||
requestBody.exclusiveMinimum = model.exclusiveMinimum
|
||||
requestBody.multipleOf = model.multipleOf
|
||||
requestBody.maxLength = model.maxLength
|
||||
requestBody.minLength = model.minLength
|
||||
requestBody.maxItems = model.maxItems
|
||||
requestBody.minItems = model.minItems
|
||||
requestBody.uniqueItems = model.uniqueItems
|
||||
requestBody.maxProperties = model.maxProperties
|
||||
requestBody.minProperties = model.minProperties
|
||||
requestBody.pattern = getPattern(model.pattern)
|
||||
requestBody.imports.push(...model.imports)
|
||||
requestBody.enum.push(...model.enum)
|
||||
requestBody.enums.push(...model.enums)
|
||||
requestBody.properties.push(...model.properties)
|
||||
return requestBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestBody
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import type { OperationResponse } from "../../../client/interfaces/OperationResponse"
|
||||
import { getPattern } from "../../../utils/getPattern"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiResponse } from "../interfaces/OpenApiResponse"
|
||||
import type { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
import { getContent } from "./getContent"
|
||||
import { getModel } from "./getModel"
|
||||
import { getRef } from "./getRef"
|
||||
import { getType } from "./getType"
|
||||
|
||||
export const getOperationResponse = (
|
||||
openApi: OpenApi,
|
||||
response: OpenApiResponse,
|
||||
responseCode: number
|
||||
): OperationResponse => {
|
||||
const operationResponse: OperationResponse = {
|
||||
spec: response,
|
||||
in: "response",
|
||||
name: "",
|
||||
code: responseCode,
|
||||
description: response.description || null,
|
||||
export: "generic",
|
||||
type: "any",
|
||||
base: "any",
|
||||
template: null,
|
||||
link: null,
|
||||
isDefinition: false,
|
||||
isReadOnly: false,
|
||||
isRequired: false,
|
||||
isNullable: false,
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
}
|
||||
|
||||
if (response.content) {
|
||||
const content = getContent(openApi, response.content)
|
||||
if (content) {
|
||||
if (content.schema.$ref?.startsWith("#/components/responses/")) {
|
||||
content.schema = getRef<OpenApiSchema>(openApi, content.schema)
|
||||
}
|
||||
if (content.schema.$ref) {
|
||||
const model = getType(content.schema.$ref)
|
||||
operationResponse.export = "reference"
|
||||
operationResponse.type = model.type
|
||||
operationResponse.base = model.base
|
||||
operationResponse.template = model.template
|
||||
operationResponse.imports.push(...model.imports)
|
||||
return operationResponse
|
||||
} else {
|
||||
const model = getModel(openApi, content.schema)
|
||||
operationResponse.export = model.export
|
||||
operationResponse.type = model.type
|
||||
operationResponse.base = model.base
|
||||
operationResponse.template = model.template
|
||||
operationResponse.link = model.link
|
||||
operationResponse.isReadOnly = model.isReadOnly
|
||||
operationResponse.isRequired = model.isRequired
|
||||
operationResponse.isNullable = model.isNullable
|
||||
operationResponse.format = model.format
|
||||
operationResponse.maximum = model.maximum
|
||||
operationResponse.exclusiveMaximum = model.exclusiveMaximum
|
||||
operationResponse.minimum = model.minimum
|
||||
operationResponse.exclusiveMinimum = model.exclusiveMinimum
|
||||
operationResponse.multipleOf = model.multipleOf
|
||||
operationResponse.maxLength = model.maxLength
|
||||
operationResponse.minLength = model.minLength
|
||||
operationResponse.maxItems = model.maxItems
|
||||
operationResponse.minItems = model.minItems
|
||||
operationResponse.uniqueItems = model.uniqueItems
|
||||
operationResponse.maxProperties = model.maxProperties
|
||||
operationResponse.minProperties = model.minProperties
|
||||
operationResponse.pattern = getPattern(model.pattern)
|
||||
operationResponse.imports.push(...model.imports)
|
||||
operationResponse.enum.push(...model.enum)
|
||||
operationResponse.enums.push(...model.enums)
|
||||
operationResponse.properties.push(...model.properties)
|
||||
return operationResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We support basic properties from response headers, since both
|
||||
// fetch and XHR client just support string types.
|
||||
if (response.headers) {
|
||||
for (const name in response.headers) {
|
||||
if (response.headers.hasOwnProperty(name)) {
|
||||
operationResponse.in = "header"
|
||||
operationResponse.name = name
|
||||
operationResponse.type = "string"
|
||||
operationResponse.base = "string"
|
||||
return operationResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return operationResponse
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export const getOperationResponseCode = (
|
||||
value: string | "default"
|
||||
): number | null => {
|
||||
// You can specify a "default" response, this is treated as HTTP code 200
|
||||
if (value === "default") {
|
||||
return 200
|
||||
}
|
||||
|
||||
// Check if we can parse the code and return of successful.
|
||||
if (/[0-9]+/g.test(value)) {
|
||||
const code = parseInt(value)
|
||||
if (Number.isInteger(code)) {
|
||||
return Math.abs(code)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { OperationResponse } from "../../../client/interfaces/OperationResponse"
|
||||
|
||||
export const getOperationResponseHeader = (
|
||||
operationResponses: OperationResponse[]
|
||||
): string | null => {
|
||||
const header = operationResponses.find((operationResponses) => {
|
||||
return operationResponses.in === "header"
|
||||
})
|
||||
if (header) {
|
||||
return header.name
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { OperationResponse } from "../../../client/interfaces/OperationResponse"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiResponse } from "../interfaces/OpenApiResponse"
|
||||
import type { OpenApiResponses } from "../interfaces/OpenApiResponses"
|
||||
import { getOperationResponse } from "./getOperationResponse"
|
||||
import { getOperationResponseCode } from "./getOperationResponseCode"
|
||||
import { getRef } from "./getRef"
|
||||
|
||||
export const getOperationResponses = (
|
||||
openApi: OpenApi,
|
||||
responses: OpenApiResponses
|
||||
): OperationResponse[] => {
|
||||
const operationResponses: OperationResponse[] = []
|
||||
|
||||
// Iterate over each response code and get the
|
||||
// status code and response message (if any).
|
||||
for (const code in responses) {
|
||||
if (responses.hasOwnProperty(code)) {
|
||||
const responseOrReference = responses[code]
|
||||
const response = getRef<OpenApiResponse>(openApi, responseOrReference)
|
||||
const responseCode = getOperationResponseCode(code)
|
||||
|
||||
if (responseCode) {
|
||||
const operationResponse = getOperationResponse(
|
||||
openApi,
|
||||
response,
|
||||
responseCode
|
||||
)
|
||||
operationResponses.push(operationResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the responses to 2XX success codes come before 4XX and 5XX error codes.
|
||||
return operationResponses.sort((a, b): number => {
|
||||
return a.code < b.code ? -1 : a.code > b.code ? 1 : 0
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { Model } from "../../../client/interfaces/Model"
|
||||
import type { OperationResponse } from "../../../client/interfaces/OperationResponse"
|
||||
|
||||
const areEqual = (a: Model, b: Model): boolean => {
|
||||
const equal =
|
||||
a.type === b.type && a.base === b.base && a.template === b.template
|
||||
if (equal && a.link && b.link) {
|
||||
return areEqual(a.link, b.link)
|
||||
}
|
||||
return equal
|
||||
}
|
||||
|
||||
export const getOperationResults = (
|
||||
operationResponses: OperationResponse[]
|
||||
): OperationResponse[] => {
|
||||
const operationResults: OperationResponse[] = []
|
||||
|
||||
// Filter out success response codes, but skip "204 No Content"
|
||||
operationResponses.forEach((operationResponse) => {
|
||||
const { code } = operationResponse
|
||||
if (code && code !== 204 && code >= 200 && code < 300) {
|
||||
operationResults.push(operationResponse)
|
||||
}
|
||||
})
|
||||
|
||||
if (!operationResults.length) {
|
||||
operationResults.push({
|
||||
spec: { description: "" },
|
||||
in: "response",
|
||||
name: "",
|
||||
code: 200,
|
||||
description: "",
|
||||
export: "generic",
|
||||
type: "void",
|
||||
base: "void",
|
||||
template: null,
|
||||
link: null,
|
||||
isDefinition: false,
|
||||
isReadOnly: false,
|
||||
isRequired: false,
|
||||
isNullable: false,
|
||||
imports: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
})
|
||||
}
|
||||
|
||||
return operationResults.filter((operationResult, index, arr) => {
|
||||
return (
|
||||
arr.findIndex((item) => {
|
||||
return areEqual(item, operationResult)
|
||||
}) === index
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiReference } from "../interfaces/OpenApiReference"
|
||||
|
||||
const ESCAPED_REF_SLASH = /~1/g
|
||||
const ESCAPED_REF_TILDE = /~0/g
|
||||
|
||||
export const getRef = <T>(openApi: OpenApi, item: T & OpenApiReference): T => {
|
||||
if (item.$ref) {
|
||||
// Fetch the paths to the definitions, this converts:
|
||||
// "#/components/schemas/Form" to ["components", "schemas", "Form"]
|
||||
const paths = item.$ref
|
||||
.replace(/^#/g, "")
|
||||
.split("/")
|
||||
.filter((item) => item)
|
||||
|
||||
// Try to find the reference by walking down the path,
|
||||
// if we cannot find it, then we throw an error.
|
||||
let result: any = openApi
|
||||
paths.forEach((path) => {
|
||||
const decodedPath = decodeURIComponent(
|
||||
path.replace(ESCAPED_REF_SLASH, "/").replace(ESCAPED_REF_TILDE, "~")
|
||||
)
|
||||
if (result.hasOwnProperty(decodedPath)) {
|
||||
result = result[decodedPath]
|
||||
} else {
|
||||
throw new Error(`Could not find reference: "${item.$ref}"`)
|
||||
}
|
||||
})
|
||||
return result as T
|
||||
}
|
||||
return item as T
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { Model } from "../../../client/interfaces/Model"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import type { OpenApiSchema } from "../interfaces/OpenApiSchema"
|
||||
import type { getModel } from "./getModel"
|
||||
import { getRef } from "./getRef"
|
||||
|
||||
// Fix for circular dependency
|
||||
export type GetModelFn = typeof getModel
|
||||
|
||||
export const getRequiredPropertiesFromComposition = (
|
||||
openApi: OpenApi,
|
||||
required: string[],
|
||||
definitions: OpenApiSchema[],
|
||||
getModel: GetModelFn
|
||||
): Model[] => {
|
||||
return definitions
|
||||
.reduce((properties, definition) => {
|
||||
if (definition.$ref) {
|
||||
const schema = getRef<OpenApiSchema>(openApi, definition)
|
||||
return [...properties, ...getModel(openApi, schema).properties]
|
||||
}
|
||||
return [...properties, ...getModel(openApi, definition).properties]
|
||||
}, [] as Model[])
|
||||
.filter((property) => {
|
||||
return !property.isRequired && required.includes(property.name)
|
||||
})
|
||||
.map((property) => {
|
||||
return {
|
||||
...property,
|
||||
isRequired: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
|
||||
export const getServer = (openApi: OpenApi): string => {
|
||||
const server = openApi.servers?.[0]
|
||||
const variables = server?.variables || {}
|
||||
let url = server?.url || ""
|
||||
for (const variable in variables) {
|
||||
if (variables.hasOwnProperty(variable)) {
|
||||
url = url.replace(`{${variable}}`, variables[variable].default)
|
||||
}
|
||||
}
|
||||
return url.replace(/\/$/g, "")
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import camelCase from "camelcase"
|
||||
|
||||
/**
|
||||
* Convert the input value to a correct service name. This converts
|
||||
* the input string to PascalCase.
|
||||
*/
|
||||
export const getServiceName = (value: string): string => {
|
||||
const clean = value
|
||||
.replace(/^[^a-zA-Z]+/g, "")
|
||||
.replace(/[^\w\-]+/g, "-")
|
||||
.trim()
|
||||
return camelCase(clean, { pascalCase: true })
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Convert the service version to 'normal' version.
|
||||
* This basically removes any "v" prefix from the version string.
|
||||
* @param version
|
||||
*/
|
||||
export const getServiceVersion = (version = "1.0"): string => {
|
||||
return String(version).replace(/^v/gi, "")
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Service } from "../../../client/interfaces/Service"
|
||||
import type { OpenApi } from "../interfaces/OpenApi"
|
||||
import { listOperations } from "./listOperations"
|
||||
|
||||
/**
|
||||
* Get the OpenAPI services
|
||||
*/
|
||||
export const getServices = (openApi: OpenApi): Service[] => {
|
||||
const services = new Map<string, Service>()
|
||||
const operations = listOperations(openApi)
|
||||
for (const operation of operations) {
|
||||
// If we have already declared a service, then we should fetch that and
|
||||
// append the new method to it. Otherwise we should create a new service object.
|
||||
const service: Service = services.get(operation.service) || {
|
||||
name: operation.service,
|
||||
operations: [],
|
||||
imports: [],
|
||||
}
|
||||
|
||||
// Push the operation in the service
|
||||
service.operations.push(operation)
|
||||
service.imports.push(...operation.imports)
|
||||
services.set(operation.service, service)
|
||||
}
|
||||
return Array.from(services.values())
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import type { Type } from "../../../client/interfaces/Type"
|
||||
import { isDefined } from "../../../utils/isDefined"
|
||||
import { getMappedType } from "./getMappedType"
|
||||
import { stripNamespace } from "./stripNamespace"
|
||||
|
||||
const encode = (value: string): string => {
|
||||
return value.replace(/^[^a-zA-Z_$]+/g, "").replace(/[^\w$]+/g, "_")
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse any string value into a type object.
|
||||
* @param type String or String[] value like "integer", "Link[Model]" or ["string", "null"].
|
||||
* @param format String value like "binary" or "date".
|
||||
*/
|
||||
export const getType = (
|
||||
type: string | string[] = "any",
|
||||
format?: string
|
||||
): Type => {
|
||||
const result: Type = {
|
||||
type: "any",
|
||||
base: "any",
|
||||
template: null,
|
||||
imports: [],
|
||||
isNullable: false,
|
||||
}
|
||||
|
||||
// Special case for JSON Schema spec (december 2020, page 17),
|
||||
// that allows type to be an array of primitive types...
|
||||
if (Array.isArray(type)) {
|
||||
const joinedType = type
|
||||
.filter((value) => value !== "null")
|
||||
.map((value) => getMappedType(value, format))
|
||||
.filter(isDefined)
|
||||
.join(" | ")
|
||||
result.type = joinedType
|
||||
result.base = joinedType
|
||||
result.isNullable = type.includes("null")
|
||||
return result
|
||||
}
|
||||
|
||||
const mapped = getMappedType(type, format)
|
||||
if (mapped) {
|
||||
result.type = mapped
|
||||
result.base = mapped
|
||||
return result
|
||||
}
|
||||
|
||||
const typeWithoutNamespace = decodeURIComponent(stripNamespace(type))
|
||||
|
||||
if (/\[.*\]$/g.test(typeWithoutNamespace)) {
|
||||
const matches = typeWithoutNamespace.match(/(.*?)\[(.*)\]$/)
|
||||
if (matches?.length) {
|
||||
const match1 = getType(encode(matches[1]))
|
||||
const match2 = getType(encode(matches[2]))
|
||||
|
||||
if (match1.type === "any[]") {
|
||||
result.type = `${match2.type}[]`
|
||||
result.base = `${match2.type}`
|
||||
match1.imports = []
|
||||
} else if (match2.type) {
|
||||
result.type = `${match1.type}<${match2.type}>`
|
||||
result.base = match1.type
|
||||
result.template = match2.type
|
||||
} else {
|
||||
result.type = match1.type
|
||||
result.base = match1.type
|
||||
result.template = match1.type
|
||||
}
|
||||
|
||||
result.imports.push(...match1.imports)
|
||||
result.imports.push(...match2.imports)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
if (typeWithoutNamespace) {
|
||||
const type = encode(typeWithoutNamespace)
|
||||
result.type = type
|
||||
result.base = type
|
||||
result.imports.push(type)
|
||||
return result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { OpenApi } from "../interfaces/OpenApi"
|
||||
import { Operation } from "../../../client/interfaces/Operation"
|
||||
import { getOperationParameters } from "./getOperationParameters"
|
||||
import { unique } from "../../../utils/unique"
|
||||
import { getOperation } from "./getOperation"
|
||||
|
||||
export const listOperations = (openApi: OpenApi): Operation[] => {
|
||||
const operations: Operation[] = []
|
||||
for (const url in openApi.paths) {
|
||||
if (openApi.paths.hasOwnProperty(url)) {
|
||||
// Grab path and parse any global path parameters
|
||||
const path = openApi.paths[url]
|
||||
const pathParams = getOperationParameters(openApi, path.parameters || [])
|
||||
|
||||
// Parse all the methods for this path
|
||||
for (const method in path) {
|
||||
if (path.hasOwnProperty(method)) {
|
||||
switch (method) {
|
||||
case "get":
|
||||
case "put":
|
||||
case "post":
|
||||
case "delete":
|
||||
case "options":
|
||||
case "head":
|
||||
case "patch":
|
||||
// Each method contains an OpenAPI operation, we parse the operation
|
||||
const op = path[method]!
|
||||
const tags = op.tags?.length
|
||||
? op.tags.filter(unique)
|
||||
: ["Default"]
|
||||
tags.forEach((tag) => {
|
||||
const operation = getOperation(
|
||||
openApi,
|
||||
url,
|
||||
method,
|
||||
tag,
|
||||
op,
|
||||
pathParams
|
||||
)
|
||||
operations.push(operation)
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return operations
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { OperationParameter } from "../../../client/interfaces/OperationParameter"
|
||||
|
||||
export const sortByRequired = (
|
||||
a: OperationParameter,
|
||||
b: OperationParameter
|
||||
): number => {
|
||||
const aNeedsValue = a.isRequired && a.default === undefined
|
||||
const bNeedsValue = b.isRequired && b.default === undefined
|
||||
if (aNeedsValue && !bNeedsValue) return -1
|
||||
if (bNeedsValue && !aNeedsValue) return 1
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Strip (OpenAPI) namespaces fom values.
|
||||
* @param value
|
||||
*/
|
||||
export const stripNamespace = (value: string): string => {
|
||||
return value
|
||||
.trim()
|
||||
.replace(/^#\/components\/schemas\//, "")
|
||||
.replace(/^#\/components\/responses\//, "")
|
||||
.replace(/^#\/components\/parameters\//, "")
|
||||
.replace(/^#\/components\/examples\//, "")
|
||||
.replace(/^#\/components\/requestBodies\//, "")
|
||||
.replace(/^#\/components\/headers\//, "")
|
||||
.replace(/^#\/components\/securitySchemes\//, "")
|
||||
.replace(/^#\/components\/links\//, "")
|
||||
.replace(/^#\/components\/callbacks\//, "")
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{{>header}}
|
||||
|
||||
import type { BaseHttpRequest } from './core/BaseHttpRequest';
|
||||
import type { OpenAPIConfig } from './core/OpenAPI';
|
||||
import { {{{httpRequest}}} } from './core/{{{httpRequest}}}';
|
||||
|
||||
{{#if services}}
|
||||
{{#each services}}
|
||||
import { {{{name}}}{{{@root.postfix}}} } from './services/{{{name}}}{{{@root.postfix}}}';
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest;
|
||||
|
||||
export class {{{clientName}}} {
|
||||
|
||||
{{#each services}}
|
||||
public readonly {{{camelCase name}}}: {{{name}}}{{{@root.postfix}}};
|
||||
{{/each}}
|
||||
|
||||
public readonly request: BaseHttpRequest;
|
||||
|
||||
constructor(config?: Partial<OpenAPIConfig>, HttpRequest: HttpRequestConstructor = {{{httpRequest}}}) {
|
||||
this.request = new HttpRequest({
|
||||
BASE: config?.BASE ?? '{{{server}}}',
|
||||
VERSION: config?.VERSION ?? '{{{version}}}',
|
||||
WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
|
||||
CREDENTIALS: config?.CREDENTIALS ?? 'include',
|
||||
TOKEN: config?.TOKEN,
|
||||
USERNAME: config?.USERNAME,
|
||||
PASSWORD: config?.PASSWORD,
|
||||
HEADERS: config?.HEADERS,
|
||||
ENCODE_PATH: config?.ENCODE_PATH,
|
||||
});
|
||||
|
||||
{{#each services}}
|
||||
this.{{{camelCase name}}} = new {{{name}}}{{{@root.postfix}}}(this.request);
|
||||
{{/each}}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{{>header}}
|
||||
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
import type { ApiResult } from './ApiResult';
|
||||
|
||||
export class ApiError extends Error {
|
||||
public readonly url: string;
|
||||
public readonly status: number;
|
||||
public readonly statusText: string;
|
||||
public readonly body: any;
|
||||
public readonly request: ApiRequestOptions;
|
||||
|
||||
constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
|
||||
super(message);
|
||||
|
||||
this.name = 'ApiError';
|
||||
this.url = response.url;
|
||||
this.status = response.status;
|
||||
this.statusText = response.statusText;
|
||||
this.body = response.body;
|
||||
this.request = request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{{>header}}
|
||||
|
||||
export type ApiRequestOptions = {
|
||||
readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
|
||||
readonly url: string;
|
||||
readonly path?: Record<string, any>;
|
||||
readonly cookies?: Record<string, any>;
|
||||
readonly headers?: Record<string, any>;
|
||||
readonly query?: Record<string, any>;
|
||||
readonly formData?: Record<string, any>;
|
||||
readonly body?: any;
|
||||
readonly mediaType?: string;
|
||||
readonly responseHeader?: string;
|
||||
readonly errors?: Record<number, string>;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{{>header}}
|
||||
|
||||
export type ApiResult = {
|
||||
readonly url: string;
|
||||
readonly ok: boolean;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
readonly body: any;
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
{{>header}}
|
||||
|
||||
import type { ApiRequestOptions } from './ApiRequestOptions';
|
||||
import type { CancelablePromise } from './CancelablePromise';
|
||||
import type { OpenAPIConfig } from './OpenAPI';
|
||||
|
||||
export abstract class BaseHttpRequest {
|
||||
|
||||
constructor(public readonly config: OpenAPIConfig) {}
|
||||
|
||||
public abstract request<T>(options: ApiRequestOptions): CancelablePromise<T>;
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
{{>header}}
|
||||
|
||||
export class CancelError extends Error {
|
||||
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'CancelError';
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export interface OnCancel {
|
||||
readonly isResolved: boolean;
|
||||
readonly isRejected: boolean;
|
||||
readonly isCancelled: boolean;
|
||||
|
||||
(cancelHandler: () => void): void;
|
||||
}
|
||||
|
||||
export class CancelablePromise<T> implements Promise<T> {
|
||||
readonly [Symbol.toStringTag]!: string;
|
||||
|
||||
private _isResolved: boolean;
|
||||
private _isRejected: boolean;
|
||||
private _isCancelled: boolean;
|
||||
private readonly _cancelHandlers: (() => void)[];
|
||||
private readonly _promise: Promise<T>;
|
||||
private _resolve?: (value: T | PromiseLike<T>) => void;
|
||||
private _reject?: (reason?: any) => void;
|
||||
|
||||
constructor(
|
||||
executor: (
|
||||
resolve: (value: T | PromiseLike<T>) => void,
|
||||
reject: (reason?: any) => void,
|
||||
onCancel: OnCancel
|
||||
) => void
|
||||
) {
|
||||
this._isResolved = false;
|
||||
this._isRejected = false;
|
||||
this._isCancelled = false;
|
||||
this._cancelHandlers = [];
|
||||
this._promise = new Promise<T>((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
|
||||
const onResolve = (value: T | PromiseLike<T>): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return;
|
||||
}
|
||||
this._isResolved = true;
|
||||
this._resolve?.(value);
|
||||
};
|
||||
|
||||
const onReject = (reason?: any): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return;
|
||||
}
|
||||
this._isRejected = true;
|
||||
this._reject?.(reason);
|
||||
};
|
||||
|
||||
const onCancel = (cancelHandler: () => void): void => {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return;
|
||||
}
|
||||
this._cancelHandlers.push(cancelHandler);
|
||||
};
|
||||
|
||||
Object.defineProperty(onCancel, 'isResolved', {
|
||||
get: (): boolean => this._isResolved,
|
||||
});
|
||||
|
||||
Object.defineProperty(onCancel, 'isRejected', {
|
||||
get: (): boolean => this._isRejected,
|
||||
});
|
||||
|
||||
Object.defineProperty(onCancel, 'isCancelled', {
|
||||
get: (): boolean => this._isCancelled,
|
||||
});
|
||||
|
||||
return executor(onResolve, onReject, onCancel as OnCancel);
|
||||
});
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
||||
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
||||
): Promise<TResult1 | TResult2> {
|
||||
return this._promise.then(onFulfilled, onRejected);
|
||||
}
|
||||
|
||||
public catch<TResult = never>(
|
||||
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
|
||||
): Promise<T | TResult> {
|
||||
return this._promise.catch(onRejected);
|
||||
}
|
||||
|
||||
public finally(onFinally?: (() => void) | null): Promise<T> {
|
||||
return this._promise.finally(onFinally);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
if (this._isResolved || this._isRejected || this._isCancelled) {
|
||||
return;
|
||||
}
|
||||
this._isCancelled = true;
|
||||
if (this._cancelHandlers.length) {
|
||||
try {
|
||||
for (const cancelHandler of this._cancelHandlers) {
|
||||
cancelHandler();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Cancellation threw an error', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._cancelHandlers.length = 0;
|
||||
this._reject?.(new CancelError('Request aborted'));
|
||||
}
|
||||
|
||||
public get isCancelled(): boolean {
|
||||
return this._isCancelled;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user