chore: Remove generated client types folder (#7633)

* chore: Remove generated client types folder

* chore: Remove typescript-codegen package and client commands for oas
This commit is contained in:
Stevche Radevski
2024-06-06 16:14:35 +02:00
committed by GitHub
parent 0507dbe027
commit 78768574af
249 changed files with 90 additions and 9128 deletions

View File

@@ -85,70 +85,6 @@ 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.
```bash
yarn medusa-oas client --src-file ./store.oas.json`
```
#### `--name <name>`
Namespace for the generated client. Usually `admin` or `store`.
```bash
yarn medusa-oas client --name admin`
```
#### `--out-dir <path>`
Specify in which directory should the files be outputted. Accepts relative and absolute path.
If the directory doesn't exist, it will be created. Defaults to `./`.
```bash
yarn medusa-oas client --out-dir ./client`
```
#### `--type <type>`
Client component types to generate. Accepts `all`, `types`, `client`, `hooks`.
Defaults to `all`.
```bash
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`.
```bash
yarn medusa-oas client --types-packages @medusajs/client-types`
```
#### `--client-packages <name>`
Replace relative import statements by client package name. Mandatory when using `--type hooks`.
```bash
yarn medusa-oas client --client-packages @medusajs/medusa-js`
```
#### `--clean`
Delete destination directory content before generating client.
```bash
yarn medusa-oas client --clean
```
---
### Command - `docs`
Will sanitize OAS for use with Redocly's API documentation viewer.
@@ -188,7 +124,7 @@ yarn medusa-oas docs --src-file ./store.oas.json --dry-run
#### `--clean`
Delete destination directory content before generating client.
Delete destination directory content before generating the docs.
```bash
yarn medusa-oas docs --src-file ./store.oas.json --clean

View File

@@ -37,7 +37,6 @@
},
"dependencies": {
"@medusajs/medusa": "^1.20.4",
"@medusajs/openapi-typescript-codegen": "^0.2.1",
"@medusajs/utils": "^1.11.8",
"@readme/json-schema-ref-parser": "^1.2.0",
"@readme/openapi-parser": "^2.4.0",

View File

@@ -1,176 +0,0 @@
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))
command.showHelpAfterError(true)
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,
})
}

View File

@@ -2,7 +2,6 @@
import { Command } from "commander"
import { getCommand as oasGetCommand } from "./command-oas"
import { getCommand as clientGetCommand } from "./command-client"
import { getCommand as docsGetCommand } from "./command-docs"
const run = async () => {
@@ -13,11 +12,6 @@ const run = async () => {
*/
program.addCommand(oasGetCommand())
/**
* Alias to command-client.ts
*/
program.addCommand(clientGetCommand())
/**
* Alias to command-docs.ts
*/

View File

@@ -1,7 +0,0 @@
/dist
node_modules
.DS_store
.env*
.env
*.sql
/bin

View File

@@ -1,33 +0,0 @@
# @medusajs/openapi-typescript-codegen
## 0.2.1
### Patch Changes
- [#3675](https://github.com/medusajs/medusa/pull/3675) [`0b3c6fde3`](https://github.com/medusajs/medusa/commit/0b3c6fde30c9bac052a8e1aa641ef925b6937c0e) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - feat(codegen:test): coverage x-expanded-relation + x-codegen.queryParams
## 0.2.0
### Minor Changes
- [#3477](https://github.com/medusajs/medusa/pull/3477) [`826d4bedf`](https://github.com/medusajs/medusa/commit/826d4bedfe1b6459163711d5173eb8eadfdea26e) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - feat(codegen,types): SetRelation on expanded types
- [#3442](https://github.com/medusajs/medusa/pull/3442) [`7b57695e0`](https://github.com/medusajs/medusa/commit/7b57695e00433e1d54f8cdc912ef7e5f28fc1071) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - feat(codegen): x-expanded-relations
### Patch Changes
- [#3272](https://github.com/medusajs/medusa/pull/3272) [`1c40346e9`](https://github.com/medusajs/medusa/commit/1c40346e9e56718de4a6e2d5c2d52abd388343e3) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - feat(codegen): openapi-typescript-codegen fork
## 0.2.0-rc.0
### Minor Changes
- [#3477](https://github.com/medusajs/medusa/pull/3477) [`826d4bedf`](https://github.com/medusajs/medusa/commit/826d4bedfe1b6459163711d5173eb8eadfdea26e) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - feat(codegen,types): SetRelation on expanded types
- [#3442](https://github.com/medusajs/medusa/pull/3442) [`7b57695e0`](https://github.com/medusajs/medusa/commit/7b57695e00433e1d54f8cdc912ef7e5f28fc1071) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - feat(codegen): x-expanded-relations
### Patch Changes
- [#3272](https://github.com/medusajs/medusa/pull/3272) [`1c40346e9`](https://github.com/medusajs/medusa/commit/1c40346e9e56718de4a6e2d5c2d52abd388343e3) Thanks [@patrick-medusajs](https://github.com/patrick-medusajs)! - feat(codegen): openapi-typescript-codegen fork
## 0.1.0

View File

@@ -1,39 +0,0 @@
# 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.

View File

@@ -1,18 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "12"
}
}
],
[
"@babel/preset-typescript",
{
"onlyRemoveTypeImports": true
}
]
]
}

View File

@@ -1,14 +0,0 @@
module.exports = {
globals: {
"ts-jest": {
tsconfig: "tsconfig.json",
isolatedModules: false,
},
},
transform: {
"^.+\\.[jt]s?$": "ts-jest",
},
testEnvironment: `node`,
moduleFileExtensions: [`js`, `ts`],
testTimeout: 30000,
}

View File

@@ -1,61 +0,0 @@
{
"name": "@medusajs/openapi-typescript-codegen",
"version": "0.2.1",
"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 src --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": "^25.5.4",
"node-fetch": "2.6.7",
"qs": "6.10.3",
"rollup": "3.9.1",
"rollup-plugin-terser": "7.0.2",
"ts-jest": "^25.5.1",
"tslib": "2.3.1",
"typescript": "4.9.5"
}
}

View File

@@ -1,83 +0,0 @@
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(),
}

View File

@@ -1,6 +0,0 @@
export enum HttpClient {
FETCH = "fetch",
XHR = "xhr",
NODE = "node",
AXIOS = "axios",
}

View File

@@ -1,5 +0,0 @@
export enum Indent {
SPACE_4 = "4",
SPACE_2 = "2",
TAB = "tab",
}

View File

@@ -1,9 +0,0 @@
import type { Model } from "./Model"
import type { Service } from "./Service"
export interface Client {
version: string
server: string
models: Model[]
services: Service[]
}

View File

@@ -1,6 +0,0 @@
export interface Enum {
name: string
value: string
type: string
description: string | null
}

View File

@@ -1,36 +0,0 @@
import type { Enum } from "./Enum"
import type { Schema } from "./Schema"
export type NestedRelation = {
field: string
nestedRelations: NestedRelation[]
base?: string
isArray?: boolean
hasDepth?: boolean
}
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[]
nestedRelations?: NestedRelation[]
}

View File

@@ -1,8 +0,0 @@
import type { Model } from "./Model"
export interface ModelComposition {
type: "one-of" | "any-of" | "all-of"
imports: string[]
enums: Model[]
properties: Model[]
}

View File

@@ -1,19 +0,0 @@
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
}

View File

@@ -1,4 +0,0 @@
export interface OperationCodegen {
method?: string
queryParams?: string
}

View File

@@ -1,4 +0,0 @@
export interface OperationError {
code: number
description: string
}

View File

@@ -1,10 +0,0 @@
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
}

View File

@@ -1,12 +0,0 @@
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
}

View File

@@ -1,8 +0,0 @@
import type { Model } from "./Model"
import { OpenApiResponse } from "../../openApi/v3/interfaces/OpenApiResponse"
export interface OperationResponse extends Model {
spec: OpenApiResponse
in: "response" | "header"
code: number
}

View File

@@ -1,34 +0,0 @@
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
}

View File

@@ -1,7 +0,0 @@
import type { Operation } from "./Operation"
export interface Service {
name: string
operations: Operation[]
imports: string[]
}

View File

@@ -1,7 +0,0 @@
export interface Type {
type: string
base: string
template: string | null
imports: string[]
isNullable: boolean
}

View File

@@ -1,116 +0,0 @@
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,
}

View File

@@ -1,20 +0,0 @@
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 }
}

View File

@@ -1,9 +0,0 @@
export interface WithExtendedRelationsExtension {
"x-expanded-relations"?: {
field: string
relations?: string[]
totals?: string[]
implicit?: string[]
eager?: string[]
}
}

View File

@@ -1,4 +0,0 @@
export interface WithEnumExtension {
"x-enum-varnames"?: string[]
"x-enum-descriptions"?: string[]
}

View File

@@ -1,21 +0,0 @@
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
}

View File

@@ -1,9 +0,0 @@
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
}

View File

@@ -1,25 +0,0 @@
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>
}

View File

@@ -1,8 +0,0 @@
/**
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#contactObject
*/
export interface OpenApiContact {
name?: string
url?: string
email?: string
}

View File

@@ -1,9 +0,0 @@
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>
}

View File

@@ -1,13 +0,0 @@
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
}

View File

@@ -1,11 +0,0 @@
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
}

View File

@@ -1,7 +0,0 @@
/**
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#externalDocumentationObject
*/
export interface OpenApiExternalDocs {
description?: string
url: string
}

View File

@@ -1,20 +0,0 @@
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>
}

View File

@@ -1,14 +0,0 @@
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
}

View File

@@ -1,7 +0,0 @@
/**
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#licenseObject
*/
export interface OpenApiLicense {
name: string
url?: string
}

View File

@@ -1,15 +0,0 @@
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
}

View File

@@ -1,15 +0,0 @@
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>
}

View File

@@ -1,11 +0,0 @@
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>
}

View File

@@ -1,11 +0,0 @@
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
}

View File

@@ -1,27 +0,0 @@
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>
}

View File

@@ -1,23 +0,0 @@
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>
}

View File

@@ -1,21 +0,0 @@
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[]
}

View File

@@ -1,8 +0,0 @@
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
}

View File

@@ -1,6 +0,0 @@
/**
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject
*/
export interface OpenApiReference {
$ref?: string
}

View File

@@ -1,13 +0,0 @@
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
}

View File

@@ -1,15 +0,0 @@
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>
}

View File

@@ -1,11 +0,0 @@
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
}

View File

@@ -1,62 +0,0 @@
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"
import { WithExtendedRelationsExtension } from "./Extensions/WithDefaultRelationsExtension"
/**
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schemaObject
*/
export interface OpenApiSchema
extends OpenApiReference,
WithEnumExtension,
WithExtendedRelationsExtension {
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
}

View File

@@ -1,6 +0,0 @@
/**
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#securityRequirementObject
*/
export interface OpenApiSecurityRequirement {
[name: string]: string
}

View File

@@ -1,16 +0,0 @@
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
}

View File

@@ -1,11 +0,0 @@
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>
}

View File

@@ -1,10 +0,0 @@
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
}

View File

@@ -1,10 +0,0 @@
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
}

View File

@@ -1,10 +0,0 @@
/**
* 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
}

View File

@@ -1,52 +0,0 @@
import { getModel } from "../getModel"
import { OpenApi } from "../../interfaces/OpenApi"
import { Model } from "../../../../client/interfaces/Model"
import { OpenApiSchema } from "../../interfaces/OpenApiSchema"
describe("getModel", () => {
let openApi: OpenApi
beforeEach(async () => {
openApi = {
openapi: "3.0.0",
info: {
title: "Test",
version: "1.0.0",
},
paths: {},
components: {},
}
})
it("should set model spec with definition", () => {
const modelName = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
properties: {
id: {
type: "string",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
expect(model.spec).toEqual(definition)
})
it("should set property spec with definition", () => {
const modelName = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
properties: {
order: {
type: "object",
properties: {
id: {
type: "string",
},
},
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
expect(model.properties[0].spec).toEqual(definition.properties!.order)
})
})

View File

@@ -1,169 +0,0 @@
import { getModels } from "../getModels"
import { OpenApi } from "../../interfaces/OpenApi"
describe("getModels", () => {
let openApi: OpenApi
beforeEach(async () => {
openApi = {
openapi: "3.0.0",
info: {
title: "Test",
version: "1.0.0",
},
paths: {},
components: {},
}
})
it("should return an empty array if no models are found", () => {
const models = getModels(openApi)
expect(models).toEqual([])
})
it("should return an array of models", () => {
openApi.components = {
schemas: {
OrderRes: {
type: "object",
properties: {
id: {
type: "string",
},
},
},
},
}
const models = getModels(openApi)
expect(models).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "OrderRes",
properties: expect.arrayContaining([
expect.objectContaining({
name: "id",
type: "string",
}),
]),
}),
])
)
})
it("should return an array of models with expanded relations", () => {
openApi.components = {
schemas: {
OrderRes: {
type: "object",
"x-expanded-relations": {
field: "order",
relations: ["region", "region.country"],
},
properties: {
order: {
$ref: "#/components/schemas/Order",
},
},
},
Order: {
type: "object",
properties: {
region: {
$ref: "#/components/schemas/Region",
},
},
},
Region: {
type: "object",
properties: {
country: {
$ref: "#/components/schemas/Country",
},
},
},
Country: {
type: "object",
},
},
}
const models = getModels(openApi)
expect(models).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "OrderRes",
properties: expect.arrayContaining([
expect.objectContaining({
name: "order",
base: "Order",
nestedRelations: expect.arrayContaining([
expect.objectContaining({
base: "Order",
field: "order",
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "region",
base: "Region",
nestedRelations: expect.arrayContaining([
expect.objectContaining({
base: "Country",
field: "country",
nestedRelations: [],
}),
]),
}),
]),
}),
]),
}),
]),
}),
])
)
})
it("should convert query parameters into a schema when x-codegen.queryParams is declared", () => {
openApi.paths = {
"/": {
get: {
operationId: "GetOrder",
"x-codegen": {
queryParams: "GetOrderQueryParams",
},
parameters: [
{
description: "Limit the number of results",
in: "query",
name: "limit",
schema: {
type: "integer",
},
required: true,
deprecated: true,
},
],
responses: {
"200": {
description: "OK",
},
},
},
},
}
const models = getModels(openApi)
expect(models).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "GetOrderQueryParams",
properties: expect.arrayContaining([
expect.objectContaining({
description: "Limit the number of results",
name: "limit",
type: "number",
isRequired: true,
deprecated: true,
}),
]),
}),
])
)
})
})

View File

@@ -1,436 +0,0 @@
import { Model } from "../../../../client/interfaces/Model"
import { handleExpandedRelations } from "../getModelsExpandedRelations"
import { getModel } from "../getModel"
import { OpenApi } from "../../interfaces/OpenApi"
import { OpenApiSchema } from "../../interfaces/OpenApiSchema"
import { getType } from "../getType"
function getModelsTest(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)
models.push(model)
}
}
}
return models
}
describe("getModelsExpandedRelations", () => {
let openApi: OpenApi
beforeEach(async () => {
openApi = {
openapi: "3.0.0",
info: {
title: "Test",
version: "1.0.0",
},
paths: {},
components: {
schemas: {
Order: {
type: "object",
properties: {
region: {
$ref: "#/components/schemas/Region",
},
total: {
type: "number",
},
},
},
Region: {
type: "object",
properties: {
country: {
$ref: "#/components/schemas/Country",
},
},
},
Country: {
type: "object",
},
Customer: {
type: "object",
properties: {
orders: {
type: "array",
items: {
$ref: "#/components/schemas/Order",
},
},
},
},
},
},
}
})
describe("basic use cases", () => {
it("should find nested relation - model", () => {
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "order",
relations: ["region"],
},
properties: {
order: {
$ref: "#/components/schemas/Order",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
handleExpandedRelations(model, models)
expect(model.properties[0].nestedRelations).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "order",
base: "Order",
isArray: false,
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "region",
base: "Region",
isArray: false,
nestedRelations: [],
}),
]),
}),
])
)
})
it("should find nested relation - shallow", () => {
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "order",
relations: ["total"],
},
properties: {
order: {
$ref: "#/components/schemas/Order",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
handleExpandedRelations(model, models)
expect(model.properties[0].nestedRelations).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "order",
base: "Order",
isArray: false,
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "total",
nestedRelations: [],
}),
]),
}),
])
)
})
it("should find nested relation - array", () => {
const modelName: string = "CustomerRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "customer",
relations: ["orders"],
},
properties: {
customer: {
$ref: "#/components/schemas/Customer",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
handleExpandedRelations(model, models)
expect(model.properties[0].nestedRelations).toEqual(
expect.arrayContaining([
expect.objectContaining({
field: "customer",
base: "Customer",
isArray: false,
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "orders",
base: "Order",
isArray: true,
nestedRelations: [],
}),
]),
}),
])
)
})
})
describe("misc usage", () => {
it.each([["allOf"], ["anyOf"], ["oneOf"]])(
"should findPropInCombination - %s",
(combination) => {
openApi.components!.schemas!.ExpandedOrder = {
[combination]: [{ $ref: "#/components/schemas/Order" }],
}
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "order",
relations: ["region"],
},
properties: {
order: {
$ref: "#/components/schemas/ExpandedOrder",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
handleExpandedRelations(model, models)
expect(model.properties[0].nestedRelations).toEqual(
expect.arrayContaining([
expect.objectContaining({
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "region",
}),
]),
}),
])
)
}
)
it.each([["relations"], ["totals"], ["implicit"], ["eager"]])(
"should find nested relation with relation type - %s",
(relationType) => {
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "order",
[relationType]: ["region"],
},
properties: {
order: {
$ref: "#/components/schemas/Order",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
handleExpandedRelations(model, models)
expect(model.properties[0].nestedRelations).toEqual(
expect.arrayContaining([
expect.objectContaining({
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "region",
}),
]),
}),
])
)
}
)
it("should set field hasDepth - true", () => {
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "order",
relations: ["region.country"],
},
properties: {
order: {
$ref: "#/components/schemas/Order",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
handleExpandedRelations(model, models)
expect(model.properties[0].nestedRelations).toEqual(
expect.arrayContaining([
expect.objectContaining({
hasDepth: true,
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "region",
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "country",
}),
]),
}),
]),
}),
])
)
})
it("should set relation hasDepth - true", () => {
const modelName: string = "CustomerRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "customer",
relations: ["orders.region.country"],
},
properties: {
customer: {
$ref: "#/components/schemas/Customer",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
handleExpandedRelations(model, models)
expect(model.properties[0].nestedRelations).toEqual(
expect.arrayContaining([
expect.objectContaining({
hasDepth: true,
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "orders",
hasDepth: true,
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "region",
nestedRelations: expect.arrayContaining([
expect.objectContaining({
field: "country",
nestedRelations: [],
}),
]),
}),
]),
}),
]),
}),
])
)
})
it("should add models with relation to root model imports, only once", () => {
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "order",
relations: ["region", "region.country"],
},
properties: {
order: {
$ref: "#/components/schemas/Order",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
handleExpandedRelations(model, models)
expect(model.imports).toEqual(expect.arrayContaining(["Order", "Region"]))
})
})
describe("errors", () => {
it("should throw if field is not found", () => {
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "nope",
},
properties: {
order: {
$ref: "#/components/schemas/Order",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
expect(() => handleExpandedRelations(model, models)).toThrow(
"x-expanded-relations - field not found"
)
})
it("should throw if relation is not found", () => {
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "order",
relations: ["nope"],
},
properties: {
order: {
$ref: "#/components/schemas/Order",
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
expect(() => handleExpandedRelations(model, models)).toThrow(
"x-expanded-relations - relation not found"
)
})
it.each([["allOf"], ["anyOf"], ["oneOf"]])(
"should throw if field exports as a combination - %s",
(combination) => {
const modelName: string = "OrderRes"
const definition: OpenApiSchema = {
type: "object",
"x-expanded-relations": {
field: "order",
relations: ["region"],
},
properties: {
order: {
[combination]: [{ $ref: "#/components/schemas/Order" }],
},
},
}
const model: Model = getModel(openApi, definition, true, modelName)
const models: Model[] = [...getModelsTest(openApi), model]
expect(() => handleExpandedRelations(model, models)).toThrow(
"x-expanded-relations - unsupported - field referencing multiple models"
)
}
)
})
})

View File

@@ -1,71 +0,0 @@
import { OpenApi } from "../../interfaces/OpenApi"
import { OpenApiOperation } from "../../interfaces/OpenApiOperation"
import { getOperation } from "../getOperation"
import { getOperationParameters } from "../getOperationParameters"
describe("getOperation", () => {
let openApi: OpenApi
beforeEach(async () => {
openApi = {
openapi: "3.0.0",
info: {
title: "Test",
version: "1.0.0",
},
paths: {},
components: {},
}
})
it("should parse x-codegen", () => {
const op: OpenApiOperation = {
"x-codegen": {
method: "list",
},
responses: {
"200": {
description: "OK",
},
},
}
const pathParams = getOperationParameters(openApi, [])
const operation = getOperation(
openApi,
"/orders",
"get",
"Orders",
op,
pathParams
)
expect(operation).toEqual(
expect.objectContaining({
codegen: { method: "list" },
})
)
})
it("should add x-codegen.queryParams to imports", () => {
const op: OpenApiOperation = {
"x-codegen": {
queryParams: "OrdersQueryParams",
},
responses: {
"200": {
description: "OK",
},
},
}
const pathParams = getOperationParameters(openApi, [])
const operation = getOperation(
openApi,
"/orders",
"get",
"Orders",
op,
pathParams
)
expect(operation.imports).toEqual(
expect.arrayContaining(["OrdersQueryParams"])
)
})
})

View File

@@ -1,27 +0,0 @@
import { OpenApi } from "../../interfaces/OpenApi"
import { getOperationParameter } from "../getOperationParameter"
import { OpenApiParameter } from "../../interfaces/OpenApiParameter"
describe("getOperation", () => {
let openApi: OpenApi
beforeEach(async () => {
openApi = {
openapi: "3.0.0",
info: {
title: "Test",
version: "1.0.0",
},
paths: {},
components: {},
}
})
it("should set spec with definition", () => {
const parameter: OpenApiParameter = {
name: "id",
in: "path",
}
const operationParameter = getOperationParameter(openApi, parameter)
expect(operationParameter.spec).toEqual(parameter)
})
})

View File

@@ -1,37 +0,0 @@
import { OpenApi } from "../../interfaces/OpenApi"
import { getOperationRequestBody } from "../getOperationRequestBody"
import { OpenApiRequestBody } from "../../interfaces/OpenApiRequestBody"
describe("getOperation", () => {
let openApi: OpenApi
beforeEach(async () => {
openApi = {
openapi: "3.0.0",
info: {
title: "Test",
version: "1.0.0",
},
paths: {},
components: {},
}
})
it("should set spec with definition", () => {
const body: OpenApiRequestBody = {
content: {
"application/json": {
schema: {
type: "object",
properties: {
id: {
type: "string",
},
},
},
},
},
}
const operationRequestBody = getOperationRequestBody(openApi, body)
expect(operationRequestBody.spec).toEqual(body)
})
})

View File

@@ -1,26 +0,0 @@
import { OpenApi } from "../../interfaces/OpenApi"
import { getOperationResponse } from "../getOperationResponse"
import { OpenApiResponse } from "../../interfaces/OpenApiResponse"
describe("getOperation", () => {
let openApi: OpenApi
beforeEach(async () => {
openApi = {
openapi: "3.0.0",
info: {
title: "Test",
version: "1.0.0",
},
paths: {},
components: {},
}
})
it("should set spec with definition", () => {
const response: OpenApiResponse = {
description: "OK",
}
const operationResponse = getOperationResponse(openApi, response, 200)
expect(operationResponse.spec).toEqual(response)
})
})

View File

@@ -1,9 +0,0 @@
export const escapeName = (value: string): string => {
if (value || value === "") {
const validName = /^[a-zA-Z_$][\w$]+$/g.test(value)
if (!validName) {
return `'${value}'`
}
}
return value
}

View File

@@ -1,24 +0,0 @@
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,
}))
}

View File

@@ -1,51 +0,0 @@
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
}

View File

@@ -1,34 +0,0 @@
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 []
}

View File

@@ -1,35 +0,0 @@
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)
}

View File

@@ -1,224 +0,0 @@
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
}

View File

@@ -1,92 +0,0 @@
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
}

View File

@@ -1,42 +0,0 @@
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
}

View File

@@ -1,112 +0,0 @@
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: property,
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
}

View File

@@ -1,11 +0,0 @@
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>" : ""
}

View File

@@ -1,117 +0,0 @@
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"
import { handleExpandedRelations } from "./getModelsExpandedRelations"
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)
}
}
}
for (const model of models) {
handleExpandedRelations(model, models)
}
/**
* 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,
}
}

View File

@@ -1,156 +0,0 @@
import { Model, NestedRelation } from "../../../client/interfaces/Model"
export const handleExpandedRelations = (model: Model, allModels: Model[]) => {
const xExpandedRelation = model.spec["x-expanded-relations"]
if (!xExpandedRelation) {
return
}
const field = xExpandedRelation.field
const relations = xExpandedRelation.relations ?? []
const totals = xExpandedRelation.totals ?? []
const implicit = xExpandedRelation.implicit ?? []
const eager = xExpandedRelation.eager ?? []
const nestedRelation: NestedRelation = {
field,
nestedRelations: [],
}
for (const relation of [...relations, ...totals, ...implicit, ...eager]) {
const splitRelation = relation.split(".")
walkSplitRelations(nestedRelation, splitRelation, 0)
}
const prop = getPropertyByName(nestedRelation.field, model)
if (!prop) {
throw new Error(`x-expanded-relations - field not found
Schema: ${model.name}
NestedRelation: ${JSON.stringify(nestedRelation, null, 2)}
Model: ${JSON.stringify(model.spec, null, 2)}`)
}
walkNestedRelations(allModels, model, model, nestedRelation)
model.imports = [...new Set(model.imports)]
/**
* To reduce complexity in the exportInterface template, nestedRelations is
* set on the property that is the root of the nested relations instead of
* setting it on the model.
*/
prop.nestedRelations = [nestedRelation]
}
const walkSplitRelations = (
parentNestedRelation: NestedRelation,
splitRelation: string[],
depthIndex: number
) => {
const field = splitRelation[depthIndex]
let nestedRelation: NestedRelation | undefined =
parentNestedRelation.nestedRelations.find(
(nestedRelation) => nestedRelation.field === field
)
if (!nestedRelation) {
nestedRelation = {
field,
nestedRelations: [],
}
parentNestedRelation.nestedRelations.push(nestedRelation)
}
depthIndex++
if (depthIndex < splitRelation.length) {
walkSplitRelations(nestedRelation, splitRelation, depthIndex)
}
}
const walkNestedRelations = (
allModels: Model[],
rootModel: Model,
model: Model,
nestedRelation: NestedRelation,
parentNestedRelation?: NestedRelation
): void => {
const prop = ["all-of", "any-of", "one-of"].includes(model.export)
? findPropInCombination(nestedRelation.field, model, allModels)
: getPropertyByName(nestedRelation.field, model)
if (!prop) {
throw new Error(`x-expanded-relations - relation not found
Schema: ${rootModel.name}
NestedRelation: ${JSON.stringify(nestedRelation, null, 2)}
Model: ${JSON.stringify(model.spec, null, 2)}`)
}
if (["all-of", "any-of", "one-of"].includes(prop.export)) {
/**
* Root property for nested relations can not use combination strategies.
* To use combination, they must be defined in referenced models instead.
*/
throw new Error(`x-expanded-relations - unsupported - field referencing multiple models
Schema: ${rootModel.name}
NestedRelation: ${JSON.stringify(nestedRelation, null, 2)}
Model: ${JSON.stringify(model.spec, null, 2)}`)
}
if (!["reference", "array"].includes(prop.export)) {
return
}
nestedRelation.base = prop.type
nestedRelation.isArray = prop.export === "array"
if (!nestedRelation.nestedRelations.length) {
return
}
const childModel = getModelByName(prop.type, allModels)
if (!childModel) {
throw new Error(`x-expanded-relations - field referencing unknown model
Schema: ${rootModel.name}
NestedRelation: ${JSON.stringify(nestedRelation, null, 2)}
Model: ${JSON.stringify(model.spec, null, 2)}`)
}
rootModel.imports.push(prop.type)
if (parentNestedRelation) {
parentNestedRelation.hasDepth = true
}
for (const childNestedRelation of nestedRelation.nestedRelations) {
walkNestedRelations(
allModels,
rootModel,
childModel,
childNestedRelation,
nestedRelation
)
}
}
const findPropInCombination = (
fieldName: string,
model: Model,
allModels: Model[]
) => {
for (const property of model.properties) {
switch (property.export) {
case "interface":
return getPropertyByName(fieldName, model)
case "reference":
const tmpModel = getModelByName(property.type, allModels)
if (tmpModel) {
return getPropertyByName(fieldName, tmpModel)
}
break
}
}
}
function getModelByName(name: string, models: Model[]): Model | void {
for (const model of models) {
if (model.name === name) {
return model
}
}
}
function getPropertyByName(name: string, model: Model): Model | void {
for (const property of model.properties) {
if (property.name === name) {
return property
}
}
}

View File

@@ -1,95 +0,0 @@
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
}

View File

@@ -1,15 +0,0 @@
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!,
}))
}

View File

@@ -1,28 +0,0 @@
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}`)
}

View File

@@ -1,99 +0,0 @@
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
}

View File

@@ -1,16 +0,0 @@
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")
}

View File

@@ -1,64 +0,0 @@
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
}

View File

@@ -1,90 +0,0 @@
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
}

View File

@@ -1,99 +0,0 @@
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
}

View File

@@ -1,18 +0,0 @@
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
}

View File

@@ -1,13 +0,0 @@
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
}

View File

@@ -1,38 +0,0 @@
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
})
}

View File

@@ -1,56 +0,0 @@
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
)
})
}

View File

@@ -1,32 +0,0 @@
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
}

View File

@@ -1,33 +0,0 @@
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,
}
})
}

View File

@@ -1,13 +0,0 @@
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, "")
}

View File

@@ -1,13 +0,0 @@
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 })
}

View File

@@ -1,8 +0,0 @@
/**
* 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, "")
}

View File

@@ -1,26 +0,0 @@
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())
}

View File

@@ -1,85 +0,0 @@
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
}

View File

@@ -1,49 +0,0 @@
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
}

Some files were not shown because too many files have changed in this diff Show More