├── .eslintrc.js ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src ├── bin.ts ├── consts.ts ├── index.ts ├── lib │ ├── convert.ts │ ├── converters │ │ ├── parameter.ts │ │ └── schema.ts │ ├── errors │ │ ├── invalid-input-error.ts │ │ └── invalid-type-error.ts │ └── utils │ │ └── isObject.ts └── openapi-schema-types.ts ├── test ├── clone_schema.test.ts ├── combination_keywords.test.ts ├── combiners.test.ts ├── complex_schemas.test.ts ├── converters.test.ts ├── definition_keyworks.test.ts ├── helpers.ts ├── invalid_types.test.ts ├── items.test.ts ├── nullable.test.ts ├── numeric_types.test.ts ├── parameter.test.ts ├── pattern_properties.test.ts ├── properties.test.ts ├── readonly_writeonly.test.ts ├── schemas │ ├── schema-1-expected.json │ ├── schema-1.json │ └── schema-2-invalid-type.json ├── string_types.test.ts ├── transform.test.ts ├── tsconfig.json └── unsupported_properties.test.ts ├── tsconfig.json ├── vite.config.ts └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | node: true, 7 | }, 8 | extends: ["prettier", "eslint:recommended", "plugin:@typescript-eslint/recommended"], 9 | parser: "@typescript-eslint/parser", 10 | globals: { 11 | Atomics: "readonly", 12 | SharedArrayBuffer: "readonly", 13 | }, 14 | parserOptions: { 15 | ecmaVersion: 2018, 16 | }, 17 | plugins: ["prettier", "unused-imports", "@typescript-eslint"], 18 | rules: { 19 | "linebreak-style": ["error", "unix"], 20 | quotes: ["error", "double"], 21 | semi: ["error", "always"], 22 | "@typescript-eslint/no-explicit-any": "off", 23 | "@typescript-eslint/ban-ts-comment": "off", 24 | "@typescript-eslint/consistent-type-definitions": ["error", "interface"], 25 | "@typescript-eslint/consistent-type-imports": [ 26 | "error", 27 | { 28 | prefer: "type-imports", 29 | }, 30 | ], 31 | }, 32 | ignorePatterns: ["dist/**"], 33 | }; 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release npm package 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: 18 17 | - run: yarn install --frozen-lockfile 18 | - run: yarn build 19 | - run: yarn test 20 | - run: npx semantic-release --branches main 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: 11 | - 18 12 | - latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: yarn install, build, and test 20 | run: | 21 | yarn --frozen-lockfile 22 | yarn build 23 | yarn lint 24 | yarn test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | build.sh 3 | .coveralls.yml 4 | .node-version 5 | .nyc_output 6 | yarn.lock 7 | resolved.yaml 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage 21 | # (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | dist 40 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 OpenAPI Contrib 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI Schema to JSON Schema 2 | 3 | A little NodeJS package to convert OpenAPI Schema Object or Parameter Object to JSON Schema. 4 | 5 | Currently converts from [OpenAPI 3.0](https://spec.openapis.org/oas/v3.0.3.html) to [JSON Schema Draft 4](http://json-schema.org/specification-links.html#draft-4). 6 | 7 | [![Treeware](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=Treeware&query=%24.total&url=https%3A%2F%2Fpublic.offset.earth%2Fusers%2Ftreeware%2Ftrees)](https://treeware.earth) 8 | 9 | ## Why? 10 | 11 | OpenAPI is a specification for describing RESTful APIs. OpenAPI v3.0 allows us to describe the structures of request and response payloads in a detailed manner. This would, theoretically, mean that we should be able to automatically validate request and response payloads. However, as of writing there aren't many validators around. 12 | 13 | The good news is that there are many validators for JSON Schema for different languages. The bad news is that OpenAPI v3.0 is [not entirely compatible with JSON Schema](https://stoplight.io/blog/openapi-json-schema/). The Schema Object of OpenAPI v3.0 is an extended subset of JSON Schema Specification Wright Draft 00 with some differences. This will be resolved in OpenAPI v3.1, but until then... this tool will fill that gap. 14 | 15 | There is also a [CLI tool](https://github.com/mikunn/openapi2schema) for creating a JSON of schemas from the whole API specification. 16 | 17 | If you need to do the conversion in reverse, checkout [json-schema-to-openapi-schema](https://github.com/openapi-contrib/json-schema-to-openapi-schema). 18 | 19 | ## Features 20 | 21 | - converts OpenAPI v3.0 Schema Object to JSON Schema Draft 4 22 | - converts OpenAPI v3.0 Parameter Object to JSON Schema Draft 4 23 | - deletes `nullable` and adds `"null"` to `type` array if `nullable` is `true` 24 | - supports deep structures with nested `allOf`s etc. 25 | - removes [OpenAPI specific properties](https://spec.openapis.org/oas/v3.0.3.html#fixed-fields-20) such as `discriminator`, `deprecated` etc. unless specified otherwise 26 | - optionally supports `patternProperties` with `x-patternProperties` in the Schema Object 27 | 28 | **NOTE**: `$ref`s are not handled in any way, so please use a resolver such as [json-schema-ref-parser](https://github.com/APIDevTools/json-schema-ref-parser) or [swagger-cli bundle](https://www.npmjs.com/package/swagger-cli) prior to using this package. 29 | 30 | ## Installation 31 | 32 | ``` 33 | npm install --save @openapi-contrib/openapi-schema-to-json-schema 34 | ``` 35 | 36 | ### CLI 37 | 38 | ```bash 39 | npx "@openapi-contrib/openapi-schema-to-json-schema" --input openapi.json --output json-schema.json 40 | ``` 41 | 42 | ## Converting OpenAPI schema 43 | 44 | Here's a small example to get the idea: 45 | 46 | ```js 47 | var { openapiSchemaToJsonSchema: toJsonSchema } = require("@openapi-contrib/openapi-schema-to-json-schema"); 48 | 49 | var schema = { 50 | type: "string", 51 | format: "date-time", 52 | nullable: true, 53 | }; 54 | 55 | var convertedSchema = toJsonSchema(schema); 56 | 57 | console.log(convertedSchema); 58 | ``` 59 | 60 | The example prints out 61 | 62 | ```js 63 | { 64 | type: ['string', 'null'], 65 | format: 'date-time', 66 | '$schema': 'http://json-schema.org/draft-04/schema#' 67 | } 68 | ``` 69 | 70 | Provide the function the schema object with possible options. 71 | 72 | ### Options 73 | 74 | The function accepts `options` object as the second argument. 75 | 76 | #### `cloneSchema` (boolean) 77 | 78 | If set to `false`, converts the provided schema in place. If `true`, clones the schema by converting it to JSON and back. The overhead of the cloning is usually negligible. Defaults to `true`. 79 | 80 | #### `dateToDateTime` (boolean) 81 | 82 | This is `false` by default and leaves `date` format as is. If set to `true`, sets `format: 'date'` to `format: 'date-time'`. 83 | 84 | For example 85 | 86 | ```js 87 | var schema = { 88 | type: "string", 89 | format: "date", 90 | }; 91 | 92 | var convertedSchema = toJsonSchema(schema, { dateToDateTime: true }); 93 | 94 | console.log(convertedSchema); 95 | ``` 96 | 97 | prints 98 | 99 | ```js 100 | { 101 | type: 'string', 102 | format: 'date-time', 103 | '$schema': 'http://json-schema.org/draft-04/schema#' 104 | } 105 | ``` 106 | 107 | #### `keepNotSupported` (array) 108 | 109 | By default, the following fields are removed from the result schema: `nullable`, `discriminator`, `readOnly`, `writeOnly`, `xml`, `externalDocs`, `example` and `deprecated` as they are not supported by JSON Schema Draft 4. Provide an array of the ones you want to keep (as strings) and they won't be removed. 110 | 111 | #### `removeReadOnly` (boolean) 112 | 113 | If set to `true`, will remove properties set as `readOnly`. If the property is set as `required`, it will be removed from the `required` array as well. The property will be removed even if `readOnly` is set to be kept with `keepNotSupported`. 114 | 115 | #### `removeWriteOnly` (boolean) 116 | 117 | Similar to `removeReadOnly`, but for `writeOnly` properties. 118 | 119 | #### `supportPatternProperties` (boolean) 120 | 121 | If set to `true` and `x-patternProperties` property is present, change `x-patternProperties` to `patternProperties` and call `patternPropertiesHandler`. If `patternPropertiesHandler` is not defined, call the default handler. See `patternPropertiesHandler` for more information. 122 | 123 | #### `patternPropertiesHandler` (function) 124 | 125 | Provide a function to handle pattern properties and set `supportPatternProperties` to take effect. The function takes the schema where `x-patternProperties` is defined on the root level. At this point `x-patternProperties` is changed to `patternProperties`. It must return the modified schema. 126 | 127 | If the handler is not provided, the default handler is used. If `additionalProperties` is set and is an object, the default handler sets it to false if the `additionalProperties` object has deep equality with a pattern object inside `patternProperties`. This is because we might want to define `additionalProperties` in OpenAPI spec file, but want to validate against a pattern. The pattern would turn out to be useless if `additionalProperties` of the same structure were allowed. Create you own handler to override this functionality. 128 | 129 | See `test/pattern_properties.test.js` for examples how this works. 130 | 131 | #### `definitionKeywords` (array) 132 | 133 | By default, definitions are not converted. If your documents follow the convention of having a definitions object at the root of a (sub)schema, you can set definitionKeywords to `['definitions']`. 134 | 135 | ```js 136 | var schema = { 137 | definitions: { 138 | sharedDefinition: { 139 | type: "object", 140 | properties: { 141 | foo: { 142 | type: "string", 143 | nullable: true, 144 | }, 145 | }, 146 | }, 147 | }, 148 | }; 149 | var convertedSchema = toJsonSchema(schema, { 150 | definitionKeywords: ["definitions"], 151 | }); 152 | console.log(convertedSchema); 153 | ``` 154 | 155 | prints 156 | 157 | ```js 158 | { 159 | $schema: 'http://json-schema.org/draft-04/schema#', 160 | definitions: { 161 | sharedDefinition: { 162 | type: 'object', 163 | properties: { 164 | foo: { 165 | type: ['string', 'null'] 166 | } 167 | } 168 | } 169 | } 170 | } 171 | ``` 172 | 173 | ## Converting OpenAPI parameters 174 | 175 | OpenAPI parameters can be converted: 176 | 177 | ```js 178 | var { fromParameter } = require("@openapi-contrib/openapi-schema-to-json-schema"); 179 | 180 | var param = { 181 | name: "parameter name", 182 | in: "query", 183 | schema: { 184 | type: "string", 185 | format: "date", 186 | }, 187 | }; 188 | 189 | var convertedSchema = fromParameter(param); 190 | 191 | console.log(convertedSchema); 192 | ``` 193 | 194 | The result is as follows: 195 | 196 | ```js 197 | { 198 | type: 'string', 199 | format: 'date', 200 | '$schema': 'http://json-schema.org/draft-04/schema#' 201 | } 202 | ``` 203 | 204 | When a parameter has several schemas (one per MIME type) a map is returned instead. 205 | 206 | ```js 207 | { 208 | name: 'parameter name', 209 | in: 'query', 210 | content: { 211 | 'application/javascript': { 212 | schema: { 213 | type: 'string' 214 | } 215 | }, 216 | 'text/css': { 217 | schema: { 218 | type: 'string' 219 | } 220 | } 221 | } 222 | } 223 | ``` 224 | 225 | would be converted to: 226 | 227 | ```js 228 | { 229 | 'application/javascript': { 230 | type: 'string', 231 | '$schema': 'http://json-schema.org/draft-04/schema#' 232 | }, 233 | 'text/css': { 234 | type: 'string', 235 | '$schema': 'http://json-schema.org/draft-04/schema#' 236 | } 237 | } 238 | ``` 239 | 240 | ## Treeware 241 | 242 | This package is [Treeware](https://treeware.earth). If you use it in production, then we ask that you [**buy the world a tree**](https://plant.treeware.earth/{venfor}/{package}) to thank us for our work. By contributing to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats. 243 | 244 | ## Thanks 245 | 246 | - [Stoplight][] for donating time and effort to this project, and many more. 247 | - [mikunn][] for originally creating this package. 248 | - [All Contributors][link-contributors] 249 | 250 | [stoplight]: https://stoplight.io/ 251 | [mikunn]: https://github.com/mikunn 252 | [link-contributors]: https://github.com/openapi-contrib/openapi-schema-to-json-schema/graphs/contributors 253 | 254 | ## Copyright 255 | 256 | Copyright 2023 the [OpenAPI Contrib organization](https://github.com/openapi-contrib). Code released under the [MIT License](https://github.com/openapi-contrib/openapi-schema-to-json-schema/blob/main/LICENSE). 257 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openapi-contrib/openapi-schema-to-json-schema", 3 | "version": "0.0.0-development", 4 | "description": "Converts OpenAPI Schema Object to JSON Schema", 5 | "types": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src", 9 | "package.json", 10 | "tsconfig.json" 11 | ], 12 | "main": "dist/index.js", 13 | "scripts": { 14 | "build": "rimraf dist && tsc -p tsconfig.json", 15 | "test": "vitest", 16 | "coverage": "vitest --coverage", 17 | "lint": "eslint . && prettier -c src", 18 | "typecheck": "tsc --noEmit", 19 | "lint:fix": "eslint . --fix && prettier -c src -w" 20 | }, 21 | "repository": "https://github.com/openapi-contrib/openapi-schema-to-json-schema", 22 | "author": "OpenAPI Contrib", 23 | "license": "MIT", 24 | "bin": "dist/bin.js", 25 | "dependencies": { 26 | "@types/json-schema": "^7.0.12", 27 | "@types/lodash": "^4.14.195", 28 | "@types/node": "^20.4.1", 29 | "fast-deep-equal": "^3.1.3", 30 | "lodash": "^4.17.21", 31 | "openapi-typescript": "^5.4.1", 32 | "yargs": "^17.7.2" 33 | }, 34 | "devDependencies": { 35 | "@types/yargs": "^17.0.24", 36 | "@typescript-eslint/eslint-plugin": "^6.0.0", 37 | "@typescript-eslint/parser": "^6.0.0", 38 | "c8": "^8.0.0", 39 | "eslint": "^8.44.0", 40 | "eslint-config-prettier": "^8.8.0", 41 | "eslint-plugin-prettier": "^5.0.0", 42 | "eslint-plugin-unused-imports": "^3.0.0", 43 | "prettier": "^3.0.0", 44 | "rimraf": "^5.0.1", 45 | "semantic-release": "^21.0.7", 46 | "typescript": "^5.1.6", 47 | "vitest": "^0.33.0" 48 | }, 49 | "prettier": { 50 | "printWidth": 120, 51 | "useTabs": false, 52 | "arrowParens": "always", 53 | "trailingComma": "all" 54 | }, 55 | "engines": { 56 | "node": ">=14.0.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/bin.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import yargs from "yargs"; 4 | import { hideBin } from "yargs/helpers"; 5 | import { openapiSchemaToJsonSchema } from "./index.js"; 6 | import fs from "fs/promises"; 7 | import * as process from "process"; 8 | import type { AcceptibleInputSchema } from "./openapi-schema-types"; 9 | const args = yargs(hideBin(process.argv)) 10 | .options({ 11 | input: { type: "string", alias: "f", demandOption: true }, 12 | output: { type: "string", alias: "o" }, 13 | }) 14 | .parseSync(); 15 | 16 | const { input, output } = args; 17 | 18 | const getFileContents = async () => { 19 | try { 20 | const fileContents = await fs.readFile(input, "utf-8"); 21 | return JSON.parse(fileContents) as AcceptibleInputSchema; 22 | } catch (e: any) { 23 | console.error(`Error: ${e.message}`); 24 | process.exit(1); 25 | } 26 | }; 27 | 28 | (async () => { 29 | try { 30 | const fileContents = await getFileContents(); 31 | const convertedSchema = await openapiSchemaToJsonSchema(fileContents); 32 | const outputFile = output || input.replace(/\.json$/, "-converted.json"); 33 | await fs.writeFile(outputFile, JSON.stringify(convertedSchema, null, 2)); 34 | } catch (e: any) { 35 | console.error(`Error: ${e.message}`); 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | export const NOT_SUPPORTED = [ 2 | "nullable", 3 | "discriminator", 4 | "readOnly", 5 | "writeOnly", 6 | "xml", 7 | "externalDocs", 8 | "example", 9 | "deprecated", 10 | ] as const; 11 | export const STRUCTS = ["allOf", "anyOf", "oneOf", "not", "items", "additionalProperties"] as const; 12 | // Valid JSON schema v4 formats 13 | export const VALID_OPENAPI_FORMATS = ["date-time", "email", "hostname", "ipv4", "ipv6", "uri", "uri-reference"]; 14 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import deepEqual from "fast-deep-equal"; 2 | import { fromSchema, fromParameter } from "./lib/convert"; 3 | import type { Options, OptionsInternal } from "./openapi-schema-types"; 4 | import { NOT_SUPPORTED, STRUCTS } from "./consts"; 5 | import type { JSONSchema4 } from "json-schema"; 6 | import type { ParameterObject, ResponseObject } from "openapi-typescript/src/types"; 7 | import cloneDeep from "lodash/cloneDeep"; 8 | import type { AcceptibleInputSchema } from "./openapi-schema-types"; 9 | 10 | const patternPropertiesHandler = (schema) => { 11 | let pattern; 12 | const patternsObj = schema.patternProperties; 13 | const additProps = schema.additionalProperties; 14 | 15 | if (typeof additProps !== "object") { 16 | return schema; 17 | } 18 | 19 | for (pattern in patternsObj) { 20 | if (deepEqual(patternsObj[pattern], additProps)) { 21 | schema.additionalProperties = false; 22 | break; 23 | } 24 | } 25 | 26 | return schema; 27 | }; 28 | 29 | const resolveOptions = (_options?: Options): OptionsInternal => { 30 | const options = cloneDeep(_options || {}) as OptionsInternal; 31 | 32 | options.dateToDateTime = Boolean(options.dateToDateTime); 33 | options.cloneSchema ??= true; 34 | options.supportPatternProperties = Boolean(options.supportPatternProperties); 35 | options.keepNotSupported ??= []; 36 | options.definitionKeywords ??= []; 37 | options.strictMode ??= true; 38 | 39 | if (typeof options.patternPropertiesHandler !== "function") { 40 | options.patternPropertiesHandler = patternPropertiesHandler; 41 | } 42 | 43 | options._removeProps = []; 44 | 45 | if (options.removeReadOnly) { 46 | options._removeProps.push("readOnly"); 47 | } 48 | 49 | if (options.removeWriteOnly) { 50 | options._removeProps.push("writeOnly"); 51 | } 52 | 53 | options._structs = STRUCTS; 54 | options._notSupported = NOT_SUPPORTED.filter((l) => { 55 | return !options.keepNotSupported?.includes(l); 56 | }); 57 | 58 | return options; 59 | }; 60 | 61 | const openapiSchemaToJsonSchema = ( 62 | schema: T, 63 | options?: Options, 64 | ): JSONSchema4 => { 65 | const optionsInternal = resolveOptions(options); 66 | return fromSchema(schema, optionsInternal); 67 | }; 68 | 69 | const openapiParameterToJsonSchema = (parameter: ParameterObject | ResponseObject, options?: Options): JSONSchema4 => { 70 | const optionsInternal = resolveOptions(options); 71 | return fromParameter(parameter, optionsInternal); 72 | }; 73 | 74 | export { 75 | openapiParameterToJsonSchema as fromParameter, 76 | openapiSchemaToJsonSchema as fromSchema, 77 | openapiSchemaToJsonSchema, 78 | }; 79 | export default openapiSchemaToJsonSchema; 80 | -------------------------------------------------------------------------------- /src/lib/convert.ts: -------------------------------------------------------------------------------- 1 | import convertFromSchema from "./converters/schema"; 2 | import convertFromParameter from "./converters/parameter"; 3 | 4 | export const fromSchema = convertFromSchema; 5 | export const fromParameter = convertFromParameter; 6 | 7 | export default { 8 | fromSchema: convertFromSchema, 9 | fromParameter: convertFromParameter, 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/converters/parameter.ts: -------------------------------------------------------------------------------- 1 | import convertFromSchema from "./schema"; 2 | import InvalidInputError from "../errors/invalid-input-error"; 3 | import type { OptionsInternal } from "../../openapi-schema-types"; 4 | import type { ParameterObject } from "openapi-typescript/src/types"; 5 | import type { ResponseObject } from "openapi-typescript/src/types"; 6 | import type { JSONSchema4 } from "json-schema"; 7 | 8 | const convertParameterSchema = ({ description }: ParameterObject | ResponseObject, schema, options): JSONSchema4 => { 9 | const jsonSchema = convertFromSchema(schema || {}, options); 10 | 11 | if (description) { 12 | jsonSchema.description = description; 13 | } 14 | 15 | return jsonSchema; 16 | }; 17 | 18 | const convertFromContents = (parameter: ResponseObject, options: OptionsInternal): JSONSchema4 => { 19 | const schemas = {}; 20 | 21 | for (const mime in parameter.content) { 22 | schemas[mime] = convertParameterSchema(parameter, parameter.content[mime].schema, options); 23 | } 24 | 25 | return schemas; 26 | }; 27 | 28 | const isResponseObject = (parameter: ParameterObject | ResponseObject): parameter is ResponseObject => { 29 | return Boolean(parameter) && "content" in parameter && Boolean(parameter.content); 30 | }; 31 | 32 | // Convert from OpenAPI 3.0 `ParameterObject` to JSON schema v4 33 | const convertFromParameter = (parameter: ParameterObject | ResponseObject, options: OptionsInternal): JSONSchema4 => { 34 | if ("schema" in parameter && parameter.schema) { 35 | return convertParameterSchema(parameter, parameter.schema, options); 36 | } 37 | if (isResponseObject(parameter)) { 38 | return convertFromContents(parameter, options); 39 | } 40 | if (options.strictMode) { 41 | throw new InvalidInputError("OpenAPI parameter must have either a 'schema' or a 'content' property"); 42 | } 43 | return convertParameterSchema(parameter, {}, options); 44 | }; 45 | 46 | export default convertFromParameter; 47 | -------------------------------------------------------------------------------- /src/lib/converters/schema.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../utils/isObject"; 2 | import InvalidTypeError from "../errors/invalid-type-error"; 3 | import type { OptionsInternal } from "../../openapi-schema-types"; 4 | import type { JSONSchema4, JSONSchema4TypeName } from "json-schema"; 5 | import { VALID_OPENAPI_FORMATS } from "../../consts"; 6 | import type { SchemaObject } from "openapi-typescript/src/types"; 7 | import type { PatternPropertiesHandler } from "../../openapi-schema-types"; 8 | import type { OpenAPI3 } from "openapi-typescript"; 9 | import type { ReferenceObject } from "openapi-typescript/src/types"; 10 | import type { AcceptibleInputSchema } from "../../openapi-schema-types"; 11 | import cloneDeep from "lodash/cloneDeep"; 12 | import get from "lodash/get"; 13 | import set from "lodash/set"; 14 | // Convert from OpenAPI 3.0 `SchemaObject` to JSON schema v4 15 | function convertFromSchema( 16 | schema: T, 17 | options: OptionsInternal, 18 | ): JSONSchema4 { 19 | const newSchema = convertSchema(schema, options); 20 | (newSchema).$schema = "http://json-schema.org/draft-04/schema#"; 21 | return newSchema; 22 | } 23 | 24 | function convertSchema(schema: OpenAPI3 | SchemaObject | ReferenceObject, options: OptionsInternal): JSONSchema4 { 25 | if (options.cloneSchema) { 26 | schema = cloneDeep(schema); 27 | } 28 | 29 | const structs = options._structs; 30 | const notSupported = options._notSupported; 31 | const strictMode = options.strictMode; 32 | const definitionKeywords = options.definitionKeywords || []; 33 | const beforeTransform = options.beforeTransform; 34 | const afterTransform = options.afterTransform; 35 | 36 | if (beforeTransform) { 37 | schema = beforeTransform(schema, options); 38 | } 39 | 40 | for (const struct of structs) { 41 | if (Array.isArray(schema[struct])) { 42 | let cloned = false; 43 | 44 | for (let j = 0; j < schema[struct].length; j++) { 45 | if (!isObject(schema[struct][j])) { 46 | if (options.cloneSchema && !cloned) { 47 | cloned = true; 48 | schema[struct] = schema[struct].slice(); 49 | } 50 | 51 | schema[struct].splice(j, 1); 52 | j--; 53 | continue; 54 | } 55 | 56 | schema[struct][j] = convertSchema(schema[struct][j], options); 57 | } 58 | } else if (schema[struct] === null) { 59 | delete schema[struct]; 60 | } else if (typeof schema[struct] === "object") { 61 | schema[struct] = convertSchema(schema[struct], options); 62 | } 63 | } 64 | let convertedSchema = schema as SchemaObject; 65 | 66 | for (const def of definitionKeywords) { 67 | const innerDef = get(schema, def); 68 | if (typeof innerDef === "object") { 69 | const convertedInnerDef = convertProperties(innerDef, options); 70 | set(schema, def, convertedInnerDef); 71 | } 72 | } 73 | 74 | if ("properties" in convertedSchema) { 75 | convertedSchema.properties = convertProperties(convertedSchema.properties, options); 76 | 77 | if (Array.isArray(convertedSchema.required)) { 78 | convertedSchema.required = convertedSchema.required.filter( 79 | (key) => "properties" in convertedSchema && convertedSchema.properties?.[key] !== undefined, 80 | ); 81 | if (convertedSchema.required.length === 0) { 82 | delete convertedSchema.required; 83 | } 84 | } 85 | if (Object.keys(convertedSchema.properties).length === 0) { 86 | delete convertedSchema.properties; 87 | } 88 | } 89 | 90 | if (strictMode && "type" in convertedSchema) { 91 | validateType(convertedSchema.type); 92 | } 93 | 94 | convertTypes(convertedSchema); 95 | convertFormat(convertedSchema, options); 96 | 97 | if ("x-patternProperties" in convertedSchema && options.supportPatternProperties) { 98 | convertedSchema = convertPatternProperties(convertedSchema, options.patternPropertiesHandler); 99 | } 100 | 101 | for (const item of notSupported) { 102 | delete convertedSchema[item]; 103 | } 104 | 105 | if (afterTransform) { 106 | return afterTransform(convertedSchema, options); 107 | } 108 | 109 | return convertedSchema as JSONSchema4; 110 | } 111 | 112 | const validTypes = ["integer", "number", "string", "boolean", "object", "array", "null"] as const; 113 | function validateType(type: unknown) { 114 | if (type && !(validTypes as ReadonlyArray).includes(type)) { 115 | throw new InvalidTypeError(`Type ${JSON.stringify(type)} is not a valid type`); 116 | } 117 | } 118 | 119 | function convertProperties( 120 | properties: Record | undefined, 121 | options: OptionsInternal, 122 | ) { 123 | let key; 124 | const props = {}; 125 | let removeProp; 126 | 127 | if (!isObject(properties) || !properties) { 128 | return props; 129 | } 130 | 131 | for (key in properties) { 132 | const property = properties[key]; 133 | 134 | if (!isObject(property)) { 135 | continue; 136 | } 137 | 138 | removeProp = options._removeProps.some((prop) => property[prop] === true); 139 | 140 | if (removeProp) { 141 | continue; 142 | } 143 | 144 | props[key] = convertSchema(property, options); 145 | } 146 | 147 | return props; 148 | } 149 | 150 | function convertTypes(schema: SchemaObject) { 151 | if ("type" in schema) { 152 | const type = schema.type as JSONSchema4TypeName; 153 | const schemaEnum = schema.enum as (string | null)[]; 154 | if (type !== undefined && schema.nullable === true) { 155 | (schema).type = [type, "null"]; 156 | if (Array.isArray(schemaEnum) && !schemaEnum.includes(null)) { 157 | // @ts-ignore 158 | schema.enum = schemaEnum.concat([null]); 159 | } 160 | } 161 | } 162 | 163 | return schema; 164 | } 165 | 166 | const formatConverters = { 167 | int32: convertFormatInt32, 168 | int64: convertFormatInt64, 169 | float: convertFormatFloat, 170 | double: convertFormatDouble, 171 | byte: convertFormatByte, 172 | } as const; 173 | 174 | function convertFormat(schema: SchemaObject | JSONSchema4, { dateToDateTime }: OptionsInternal) { 175 | const format = schema.format; 176 | const settings = { 177 | MIN_INT_32: 0 - 2 ** 31, 178 | MAX_INT_32: 2 ** 31 - 1, 179 | MIN_INT_64: 0 - 2 ** 63, 180 | MAX_INT_64: 2 ** 63 - 1, 181 | MIN_FLOAT: 0 - 2 ** 128, 182 | MAX_FLOAT: 2 ** 128 - 1, 183 | MIN_DOUBLE: 0 - Number.MAX_VALUE, 184 | MAX_DOUBLE: Number.MAX_VALUE, 185 | 186 | // Matches base64 (RFC 4648) 187 | // Matches `standard` base64 not `base64url`. The specification does not 188 | // exclude it but current ongoing OpenAPI plans will distinguish btoh. 189 | BYTE_PATTERN: "^[\\w\\d+\\/=]*$", 190 | }; 191 | 192 | if (format === undefined || VALID_OPENAPI_FORMATS.includes(format)) { 193 | return schema; 194 | } 195 | 196 | if (format === "date" && dateToDateTime === true) { 197 | return convertFormatDate(schema); 198 | } 199 | 200 | const converter = formatConverters[format]; 201 | 202 | if (!converter) { 203 | return schema; 204 | } 205 | 206 | return converter(schema, settings); 207 | } 208 | 209 | function convertFormatInt32(schema: JSONSchema4, { MIN_INT_32, MAX_INT_32 }) { 210 | if ((!schema.minimum && schema.minimum !== 0) || schema.minimum < MIN_INT_32) { 211 | schema.minimum = MIN_INT_32; 212 | } 213 | if ((!schema.maximum && schema.maximum !== 0) || schema.maximum > MAX_INT_32) { 214 | schema.maximum = MAX_INT_32; 215 | } 216 | return schema; 217 | } 218 | 219 | function convertFormatInt64( 220 | schema: JSONSchema4, 221 | { MIN_INT_64, MAX_INT_64 }: { MIN_INT_64: number; MAX_INT_64: number }, 222 | ) { 223 | if ((!schema.minimum && schema.minimum !== 0) || schema.minimum < MIN_INT_64) { 224 | schema.minimum = MIN_INT_64; 225 | } 226 | if ((!schema.maximum && schema.maximum !== 0) || schema.maximum > MAX_INT_64) { 227 | schema.maximum = MAX_INT_64; 228 | } 229 | return schema; 230 | } 231 | 232 | function convertFormatFloat(schema: JSONSchema4, { MIN_FLOAT, MAX_FLOAT }: { MIN_FLOAT: number; MAX_FLOAT: number }) { 233 | if ((!schema.minimum && schema.minimum !== 0) || schema.minimum < MIN_FLOAT) { 234 | schema.minimum = MIN_FLOAT; 235 | } 236 | if ((!schema.maximum && schema.maximum !== 0) || schema.maximum > MAX_FLOAT) { 237 | schema.maximum = MAX_FLOAT; 238 | } 239 | return schema; 240 | } 241 | 242 | function convertFormatDouble( 243 | schema: JSONSchema4, 244 | { MIN_DOUBLE, MAX_DOUBLE }: { MIN_DOUBLE: number; MAX_DOUBLE: number }, 245 | ) { 246 | if ((!schema.minimum && schema.minimum !== 0) || schema.minimum < MIN_DOUBLE) { 247 | schema.minimum = MIN_DOUBLE; 248 | } 249 | if ((!schema.maximum && schema.maximum !== 0) || schema.maximum > MAX_DOUBLE) { 250 | schema.maximum = MAX_DOUBLE; 251 | } 252 | return schema; 253 | } 254 | 255 | function convertFormatDate(schema: SchemaObject | JSONSchema4) { 256 | schema.format = "date-time"; 257 | return schema; 258 | } 259 | 260 | function convertFormatByte(schema: JSONSchema4, { BYTE_PATTERN }: { BYTE_PATTERN: string }) { 261 | schema.pattern = BYTE_PATTERN; 262 | return schema; 263 | } 264 | 265 | function convertPatternProperties(schema: SchemaObject, handler: PatternPropertiesHandler) { 266 | if (isObject(schema["x-patternProperties"])) { 267 | (schema).patternProperties = schema["x-patternProperties"]; 268 | } 269 | 270 | delete schema["x-patternProperties"]; 271 | 272 | return handler(schema); 273 | } 274 | 275 | export default convertFromSchema; 276 | -------------------------------------------------------------------------------- /src/lib/errors/invalid-input-error.ts: -------------------------------------------------------------------------------- 1 | class InvalidInputError extends Error { 2 | message: string; 3 | name: string; 4 | constructor(message: string) { 5 | super(message); 6 | this.name = "InvalidInputError"; 7 | this.message = message; 8 | } 9 | } 10 | export default InvalidInputError; 11 | -------------------------------------------------------------------------------- /src/lib/errors/invalid-type-error.ts: -------------------------------------------------------------------------------- 1 | class InvalidTypeError extends Error { 2 | message: string; 3 | name: string; 4 | constructor(message: string) { 5 | super(message); 6 | this.name = "InvalidTypeError"; 7 | this.message = message; 8 | } 9 | } 10 | export default InvalidTypeError; 11 | -------------------------------------------------------------------------------- /src/lib/utils/isObject.ts: -------------------------------------------------------------------------------- 1 | export const isObject = (maybeObj: unknown) => maybeObj !== null && typeof maybeObj === "object"; 2 | -------------------------------------------------------------------------------- /src/openapi-schema-types.ts: -------------------------------------------------------------------------------- 1 | import type { NOT_SUPPORTED, STRUCTS } from "./consts"; 2 | import type { OpenAPI3 } from "openapi-typescript"; 3 | import type { SchemaObject } from "openapi-typescript/src/types"; 4 | import type { ReferenceObject } from "openapi-typescript/src/types"; 5 | import type { JSONSchema4 } from "json-schema"; 6 | export type { OpenAPI3 }; 7 | // We don't know what the shape of the object looks like when it's passed in, but we know its some mix of these two 8 | export type PatternPropertiesHandler = (schema: SchemaObject) => SchemaObject; 9 | export type AcceptibleInputSchema = SchemaObject | OpenAPI3 | Record; 10 | 11 | export interface Options { 12 | dateToDateTime?: boolean; 13 | cloneSchema?: boolean; 14 | supportPatternProperties?: boolean; 15 | keepNotSupported?: (typeof NOT_SUPPORTED)[number][]; 16 | strictMode?: boolean; 17 | removeReadOnly?: boolean; 18 | removeWriteOnly?: boolean; 19 | patternPropertiesHandler?: PatternPropertiesHandler; 20 | definitionKeywords?: string[]; 21 | beforeTransform?: (schema: SchemaObject | ReferenceObject | OpenAPI3, options: Options) => SchemaObject; 22 | afterTransform?: (schema: SchemaObject | ReferenceObject | OpenAPI3, options: Options) => JSONSchema4; 23 | } 24 | 25 | export interface OptionsInternal extends Options { 26 | _removeProps: string[]; 27 | _structs: typeof STRUCTS; 28 | _notSupported: (typeof NOT_SUPPORTED)[number][]; 29 | patternPropertiesHandler: PatternPropertiesHandler; 30 | } 31 | -------------------------------------------------------------------------------- /test/clone_schema.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("cloning schema by default", async ({ expect }) => { 4 | const schema = { 5 | type: "string", 6 | nullable: true, 7 | properties: { 8 | foo: true, 9 | bar: { 10 | allOf: [ 11 | null, 12 | { 13 | type: "string", 14 | }, 15 | null, 16 | ], 17 | }, 18 | }, 19 | }; 20 | 21 | const cloned = JSON.parse(JSON.stringify(schema)); 22 | 23 | const result = convert(cloned); 24 | 25 | const expected = { 26 | $schema: "http://json-schema.org/draft-04/schema#", 27 | type: ["string", "null"], 28 | properties: { 29 | bar: { 30 | allOf: [ 31 | { 32 | type: "string", 33 | }, 34 | ], 35 | }, 36 | }, 37 | }; 38 | 39 | expect(result).toEqual(expected); 40 | expect(cloned).toEqual(schema); 41 | }); 42 | 43 | it("cloning schema with cloneSchema option", async ({ expect }) => { 44 | const schema = { 45 | type: "string", 46 | nullable: true, 47 | }; 48 | 49 | const cloned = JSON.parse(JSON.stringify(schema)); 50 | 51 | const result = convert(schema, { cloneSchema: true }); 52 | 53 | const expected = { 54 | $schema: "http://json-schema.org/draft-04/schema#", 55 | type: ["string", "null"], 56 | }; 57 | 58 | expect(result).toEqual(expected); 59 | expect(cloned).toEqual(schema); 60 | }); 61 | 62 | it("handles circular references", async ({ expect }) => { 63 | const a: Record = {}; 64 | 65 | a.a = a; 66 | 67 | const schema = { 68 | type: "string", 69 | nullable: true, 70 | a, 71 | }; 72 | 73 | const result = convert(schema, { cloneSchema: true }); 74 | expect(result).not.toEqual(schema); 75 | }); 76 | 77 | it("direct schema modification", async ({ expect }) => { 78 | const schema = { 79 | type: "string", 80 | nullable: true, 81 | }; 82 | 83 | const result = convert(schema, { cloneSchema: false }); 84 | 85 | const expected = { 86 | $schema: "http://json-schema.org/draft-04/schema#", 87 | type: ["string", "null"], 88 | }; 89 | 90 | expect(result).toEqual(expected); 91 | expect(result).toEqual(schema); 92 | }); 93 | -------------------------------------------------------------------------------- /test/combination_keywords.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("iterates allOfs", async ({ expect }) => { 4 | const schema = { 5 | allOf: [ 6 | { 7 | type: "object", 8 | required: ["foo"], 9 | properties: { 10 | foo: { 11 | type: "integer", 12 | }, 13 | }, 14 | }, 15 | { 16 | allOf: [ 17 | { 18 | type: "number", 19 | }, 20 | ], 21 | }, 22 | ], 23 | }; 24 | 25 | const result = convert(schema); 26 | 27 | const expected = { 28 | $schema: "http://json-schema.org/draft-04/schema#", 29 | allOf: [ 30 | { 31 | type: "object", 32 | required: ["foo"], 33 | properties: { 34 | foo: { 35 | type: "integer", 36 | }, 37 | }, 38 | }, 39 | { 40 | allOf: [ 41 | { 42 | type: "number", 43 | }, 44 | ], 45 | }, 46 | ], 47 | }; 48 | 49 | expect(result).toEqual(expected); 50 | }); 51 | 52 | it("iterates anyOfs", async ({ expect }) => { 53 | const schema = { 54 | anyOf: [ 55 | { 56 | type: "object", 57 | required: ["foo"], 58 | properties: { 59 | foo: { 60 | type: "integer", 61 | }, 62 | }, 63 | }, 64 | { 65 | anyOf: [ 66 | { 67 | type: "object", 68 | properties: { 69 | bar: { 70 | type: "number", 71 | }, 72 | }, 73 | }, 74 | ], 75 | }, 76 | ], 77 | }; 78 | 79 | const result = convert(schema); 80 | 81 | const expected = { 82 | $schema: "http://json-schema.org/draft-04/schema#", 83 | anyOf: [ 84 | { 85 | type: "object", 86 | required: ["foo"], 87 | properties: { 88 | foo: { 89 | type: "integer", 90 | }, 91 | }, 92 | }, 93 | { 94 | anyOf: [ 95 | { 96 | type: "object", 97 | properties: { 98 | bar: { 99 | type: "number", 100 | }, 101 | }, 102 | }, 103 | ], 104 | }, 105 | ], 106 | }; 107 | 108 | expect(result).toEqual(expected); 109 | }); 110 | 111 | it("iterates oneOfs", async ({ expect }) => { 112 | const schema = { 113 | oneOf: [ 114 | { 115 | type: "object", 116 | required: ["foo"], 117 | properties: { 118 | foo: { 119 | type: "integer", 120 | }, 121 | }, 122 | }, 123 | { 124 | oneOf: [ 125 | { 126 | type: "object", 127 | properties: { 128 | bar: { 129 | type: "number", 130 | }, 131 | }, 132 | }, 133 | ], 134 | }, 135 | ], 136 | }; 137 | 138 | const result = convert(schema); 139 | 140 | const expected = { 141 | $schema: "http://json-schema.org/draft-04/schema#", 142 | oneOf: [ 143 | { 144 | type: "object", 145 | required: ["foo"], 146 | properties: { 147 | foo: { 148 | type: "integer", 149 | }, 150 | }, 151 | }, 152 | { 153 | oneOf: [ 154 | { 155 | type: "object", 156 | properties: { 157 | bar: { 158 | type: "number", 159 | }, 160 | }, 161 | }, 162 | ], 163 | }, 164 | ], 165 | }; 166 | 167 | expect(result).toEqual(expected); 168 | }); 169 | 170 | it("converts types in not", async ({ expect }) => { 171 | const schema = { 172 | type: "object", 173 | properties: { 174 | not: { 175 | type: "string", 176 | minLength: 8, 177 | }, 178 | }, 179 | }; 180 | 181 | const result = convert(schema); 182 | 183 | const expected = { 184 | $schema: "http://json-schema.org/draft-04/schema#", 185 | type: "object", 186 | properties: { 187 | not: { 188 | type: "string", 189 | minLength: 8, 190 | }, 191 | }, 192 | }; 193 | 194 | expect(result).toEqual(expected); 195 | }); 196 | 197 | it("converts types in not", async ({ expect }) => { 198 | const schema = { 199 | not: { 200 | type: "string", 201 | minLength: 8, 202 | }, 203 | }; 204 | 205 | const result = convert(schema); 206 | 207 | const expected = { 208 | $schema: "http://json-schema.org/draft-04/schema#", 209 | not: { 210 | type: "string", 211 | minLength: 8, 212 | }, 213 | }; 214 | 215 | expect(result).toEqual(expected); 216 | }); 217 | 218 | it("nested combination keywords", async ({ expect }) => { 219 | const schema = { 220 | anyOf: [ 221 | { 222 | allOf: [ 223 | { 224 | type: "object", 225 | properties: { 226 | foo: { 227 | type: "string", 228 | nullable: true, 229 | }, 230 | }, 231 | }, 232 | { 233 | type: "object", 234 | properties: { 235 | bar: { 236 | type: "integer", 237 | nullable: true, 238 | }, 239 | }, 240 | }, 241 | ], 242 | }, 243 | { 244 | type: "object", 245 | properties: { 246 | foo: { 247 | type: "string", 248 | }, 249 | }, 250 | }, 251 | { 252 | not: { 253 | type: "string", 254 | example: "foobar", 255 | }, 256 | }, 257 | ], 258 | }; 259 | 260 | const result = convert(schema); 261 | 262 | const expected = { 263 | $schema: "http://json-schema.org/draft-04/schema#", 264 | anyOf: [ 265 | { 266 | allOf: [ 267 | { 268 | type: "object", 269 | properties: { 270 | foo: { 271 | type: ["string", "null"], 272 | }, 273 | }, 274 | }, 275 | { 276 | type: "object", 277 | properties: { 278 | bar: { 279 | type: ["integer", "null"], 280 | }, 281 | }, 282 | }, 283 | ], 284 | }, 285 | { 286 | type: "object", 287 | properties: { 288 | foo: { 289 | type: "string", 290 | }, 291 | }, 292 | }, 293 | { 294 | not: { 295 | type: "string", 296 | }, 297 | }, 298 | ], 299 | }; 300 | 301 | expect(result).toEqual(expected); 302 | }); 303 | -------------------------------------------------------------------------------- /test/combiners.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("allOf is null", async ({ expect }) => { 4 | const schema = { 5 | allOf: null, 6 | }; 7 | 8 | const result = convert(schema); 9 | 10 | const expected = { 11 | $schema: "http://json-schema.org/draft-04/schema#", 12 | }; 13 | 14 | expect(result).toEqual(expected); 15 | }); 16 | 17 | it("anyOf is null", async ({ expect }) => { 18 | const schema = { 19 | anyOf: null, 20 | }; 21 | 22 | const result = convert(schema); 23 | 24 | const expected = { 25 | $schema: "http://json-schema.org/draft-04/schema#", 26 | }; 27 | 28 | expect(result).toEqual(expected); 29 | }); 30 | 31 | it("oneOf is null", async ({ expect }) => { 32 | const schema = { 33 | oneOf: null, 34 | }; 35 | 36 | const result = convert(schema); 37 | 38 | const expected = { 39 | $schema: "http://json-schema.org/draft-04/schema#", 40 | }; 41 | 42 | expect(result).toEqual(expected); 43 | }); 44 | -------------------------------------------------------------------------------- /test/complex_schemas.test.ts: -------------------------------------------------------------------------------- 1 | import { getSchema } from "./helpers"; 2 | import convert from "../src"; 3 | 4 | it("complex schema", async ({ expect }) => { 5 | const schema = getSchema("schema-1.json"); 6 | const result = convert(schema); 7 | const expected = getSchema("schema-1-expected.json"); 8 | 9 | expect(result).toEqual(expected); 10 | }); 11 | 12 | it("converting complex schema in place", async ({ expect }) => { 13 | const schema = getSchema("schema-1.json"); 14 | const result = convert(schema, { cloneSchema: false }); 15 | const expected = getSchema("schema-1-expected.json"); 16 | 17 | expect(schema).toEqual(result); 18 | expect(result).toEqual(expected); 19 | }); 20 | -------------------------------------------------------------------------------- /test/converters.test.ts: -------------------------------------------------------------------------------- 1 | import { fromSchema } from "../src"; 2 | 3 | it("using exports.fromSchema", async ({ expect }) => { 4 | const schema = { 5 | type: "string", 6 | nullable: true, 7 | }; 8 | 9 | const result = fromSchema(schema); 10 | 11 | const expected = { 12 | $schema: "http://json-schema.org/draft-04/schema#", 13 | type: ["string", "null"], 14 | }; 15 | 16 | expect(result).toEqual(expected); 17 | }); 18 | -------------------------------------------------------------------------------- /test/definition_keyworks.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | describe("handles conversion in keywords specified in additionalKeywords", function () { 4 | const schema = { 5 | definitions: { 6 | sharedDefinition: { 7 | type: "object", 8 | properties: { 9 | foo: { 10 | type: "string", 11 | nullable: true, 12 | }, 13 | }, 14 | }, 15 | }, 16 | }; 17 | 18 | it("handles conversion in keywords specified in additionalKeywords", function ({ expect }) { 19 | const result = convert(schema, { 20 | definitionKeywords: ["definitions"], 21 | }); 22 | 23 | const expected = { 24 | $schema: "http://json-schema.org/draft-04/schema#", 25 | definitions: { 26 | sharedDefinition: { 27 | type: "object", 28 | properties: { 29 | foo: { 30 | type: ["string", "null"], 31 | }, 32 | }, 33 | }, 34 | }, 35 | }; 36 | 37 | expect(result).toEqual(expected); 38 | }); 39 | 40 | it("does not convert when no definition keywords are included", function ({ expect }) { 41 | const result = convert(schema); 42 | 43 | const expected = { 44 | $schema: "http://json-schema.org/draft-04/schema#", 45 | definitions: { 46 | sharedDefinition: { 47 | properties: { 48 | foo: { 49 | nullable: true, 50 | type: "string", 51 | }, 52 | }, 53 | type: "object", 54 | }, 55 | }, 56 | }; 57 | 58 | expect(result).toEqual(expected); 59 | }); 60 | 61 | it("handles nested definition keywords", function ({ expect }) { 62 | const nestedSchema = { 63 | schema: { 64 | definitions: { 65 | sharedDefinition: { 66 | type: "object", 67 | properties: { 68 | foo: { 69 | type: "string", 70 | nullable: true, 71 | }, 72 | }, 73 | }, 74 | }, 75 | }, 76 | }; 77 | const result = convert(nestedSchema, { definitionKeywords: ["schema.definitions"] }); 78 | 79 | const expected = { 80 | $schema: "http://json-schema.org/draft-04/schema#", 81 | schema: { 82 | definitions: { 83 | sharedDefinition: { 84 | type: "object", 85 | properties: { 86 | foo: { 87 | type: ["string", "null"], 88 | }, 89 | }, 90 | }, 91 | }, 92 | }, 93 | }; 94 | 95 | expect(result).toEqual(expected); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/helpers.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { join } from "path"; 3 | 4 | export default { 5 | getSchema, 6 | }; 7 | 8 | export function getSchema(file) { 9 | const path = join(__dirname, "schemas", file); 10 | return JSON.parse(fs.readFileSync(path, "utf8")); 11 | } 12 | -------------------------------------------------------------------------------- /test/invalid_types.test.ts: -------------------------------------------------------------------------------- 1 | import { getSchema } from "./helpers"; 2 | import convert from "../src"; 3 | 4 | it("invalid types", async ({ expect }) => { 5 | expect(() => { 6 | convert({ 7 | type: "dateTime", 8 | }); 9 | }).toThrow("is not a valid type"); 10 | 11 | expect(() => { 12 | convert({ 13 | type: "foo", 14 | }); 15 | }).toThrow("is not a valid type"); 16 | 17 | expect(() => { 18 | convert({ 19 | type: ["string", null], 20 | }); 21 | }).toThrow("is not a valid type"); 22 | 23 | expect(() => { 24 | convert(getSchema("schema-2-invalid-type.json")); 25 | }).toThrow("is not a valid type"); 26 | }); 27 | 28 | it("valid types", async ({ expect }) => { 29 | const types = ["integer", "number", "string", "boolean", "object", "array", "null"]; 30 | 31 | types.forEach((type) => { 32 | const result = convert({ 33 | type, 34 | }); 35 | 36 | const expected = { 37 | $schema: "http://json-schema.org/draft-04/schema#", 38 | type, 39 | }; 40 | 41 | expect(result).toEqual(expected); 42 | }); 43 | 44 | it("invalid type allowed when strictMode = false", async ({ expect }) => { 45 | const schema = { 46 | type: "nonsense", 47 | }; 48 | 49 | const result = convert(schema, { strictMode: false }); 50 | 51 | const expected = { 52 | $schema: "http://json-schema.org/draft-04/schema#", 53 | type: "nonsense", 54 | }; 55 | 56 | expect(result).toEqual(expected); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/items.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("items", async ({ expect }) => { 4 | const schema = { 5 | type: "array", 6 | items: { 7 | type: "string", 8 | example: "2017-01-01T12:34:56Z", 9 | }, 10 | }; 11 | 12 | const result = convert(schema); 13 | 14 | const expected = { 15 | $schema: "http://json-schema.org/draft-04/schema#", 16 | type: "array", 17 | items: { 18 | type: "string", 19 | }, 20 | }; 21 | 22 | expect(result).toEqual(expected); 23 | }); 24 | 25 | it("handles items with invalid values", async ({ expect }) => { 26 | const schema = { 27 | type: "array", 28 | items: [ 29 | { 30 | type: "string", 31 | }, 32 | 2, 33 | null, 34 | { 35 | type: "number", 36 | }, 37 | "foo", 38 | { 39 | type: "array", 40 | }, 41 | ], 42 | }; 43 | 44 | const result = convert(schema); 45 | 46 | const expected = { 47 | $schema: "http://json-schema.org/draft-04/schema#", 48 | type: "array", 49 | items: [ 50 | { 51 | type: "string", 52 | }, 53 | { 54 | type: "number", 55 | }, 56 | { 57 | type: "array", 58 | }, 59 | ], 60 | }; 61 | 62 | expect(result).toEqual(expected); 63 | }); 64 | -------------------------------------------------------------------------------- /test/nullable.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("handles nullable without enum", async ({ expect }) => { 4 | const schema = { 5 | type: "string", 6 | nullable: true, 7 | }; 8 | 9 | const result = convert(schema); 10 | 11 | const expected = { 12 | $schema: "http://json-schema.org/draft-04/schema#", 13 | type: ["string", "null"], 14 | }; 15 | 16 | expect(result).toEqual(expected); 17 | 18 | { 19 | const schema = { 20 | type: "string", 21 | nullable: false, 22 | }; 23 | 24 | const result = convert(schema); 25 | 26 | const expected = { 27 | $schema: "http://json-schema.org/draft-04/schema#", 28 | type: "string", 29 | }; 30 | 31 | expect(result).toEqual(expected); 32 | } 33 | }); 34 | 35 | it("handles nullable with enum", async ({ expect }) => { 36 | const result = convert({ 37 | type: "string", 38 | enum: ["a", "b"], 39 | nullable: true, 40 | }); 41 | 42 | const expected = { 43 | $schema: "http://json-schema.org/draft-04/schema#", 44 | type: ["string", "null"], 45 | enum: ["a", "b", null], 46 | }; 47 | 48 | expect(result).toEqual(expected); 49 | 50 | const resultTwo = convert({ 51 | type: "string", 52 | enum: ["a", "b", null], 53 | nullable: true, 54 | }); 55 | 56 | const expectedTwo = { 57 | $schema: "http://json-schema.org/draft-04/schema#", 58 | type: ["string", "null"], 59 | enum: ["a", "b", null], 60 | }; 61 | 62 | expect(resultTwo).toEqual(expectedTwo); 63 | const resultThree = convert({ 64 | type: "string", 65 | enum: ["a", "b"], 66 | nullable: false, 67 | }); 68 | 69 | const expectedThree = { 70 | $schema: "http://json-schema.org/draft-04/schema#", 71 | type: "string", 72 | enum: ["a", "b"], 73 | }; 74 | expect(resultThree).toEqual(expectedThree); 75 | }); 76 | -------------------------------------------------------------------------------- /test/numeric_types.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("handles int32 format", async ({ expect }) => { 4 | const schema = { 5 | type: "integer", 6 | format: "int32", 7 | }; 8 | 9 | const result = convert(schema); 10 | 11 | const expected = { 12 | $schema: "http://json-schema.org/draft-04/schema#", 13 | type: "integer", 14 | format: "int32", 15 | minimum: 0 - Math.pow(2, 31), 16 | maximum: Math.pow(2, 31) - 1, 17 | }; 18 | 19 | expect(result).toEqual(expected); 20 | }); 21 | 22 | test("handles int32 format with specified minimum", async function ({ expect }) { 23 | const schema = { 24 | type: "integer", 25 | format: "int32", 26 | minimum: 500, 27 | }; 28 | 29 | const result = convert(schema); 30 | 31 | const expected = { 32 | $schema: "http://json-schema.org/draft-04/schema#", 33 | type: "integer", 34 | format: "int32", 35 | minimum: 500, 36 | maximum: Math.pow(2, 31) - 1, 37 | }; 38 | 39 | expect(result).toEqual(expected); 40 | }); 41 | 42 | test("handles int32 format with specified minimum that's too small", async function ({ expect }) { 43 | const schema = { 44 | type: "integer", 45 | format: "int32", 46 | minimum: -Math.pow(2, 32), 47 | }; 48 | 49 | const result = convert(schema); 50 | 51 | const expected = { 52 | $schema: "http://json-schema.org/draft-04/schema#", 53 | type: "integer", 54 | format: "int32", 55 | minimum: 0 - Math.pow(2, 31), 56 | maximum: Math.pow(2, 31) - 1, 57 | }; 58 | 59 | expect(result).toEqual(expected); 60 | }); 61 | 62 | test("handles int32 format with specified maximum", async function ({ expect }) { 63 | const schema = { 64 | type: "integer", 65 | format: "int32", 66 | maximum: 500, 67 | }; 68 | 69 | const result = convert(schema); 70 | 71 | const expected = { 72 | $schema: "http://json-schema.org/draft-04/schema#", 73 | type: "integer", 74 | format: "int32", 75 | minimum: 0 - Math.pow(2, 31), 76 | maximum: 500, 77 | }; 78 | 79 | expect(result).toEqual(expected); 80 | }); 81 | 82 | test("handles int32 format with specified minimum that's too big", async function ({ expect }) { 83 | const schema = { 84 | type: "integer", 85 | format: "int32", 86 | maximum: Math.pow(2, 32), 87 | }; 88 | 89 | const result = convert(schema); 90 | 91 | const expected = { 92 | $schema: "http://json-schema.org/draft-04/schema#", 93 | type: "integer", 94 | format: "int32", 95 | minimum: 0 - Math.pow(2, 31), 96 | maximum: Math.pow(2, 31) - 1, 97 | }; 98 | 99 | expect(result).toEqual(expected); 100 | }); 101 | 102 | it("handles int64 format", async ({ expect }) => { 103 | const schema = { 104 | type: "integer", 105 | format: "int64", 106 | }; 107 | 108 | const result = convert(schema); 109 | 110 | const expected = { 111 | $schema: "http://json-schema.org/draft-04/schema#", 112 | type: "integer", 113 | format: "int64", 114 | minimum: 0 - Math.pow(2, 63), 115 | maximum: Math.pow(2, 63) - 1, 116 | }; 117 | 118 | expect(result).toEqual(expected); 119 | }); 120 | 121 | test("handles int64 format with specified minimum", async function ({ expect }) { 122 | const schema = { 123 | type: "integer", 124 | format: "int64", 125 | minimum: 500, 126 | }; 127 | 128 | const result = convert(schema); 129 | 130 | const expected = { 131 | $schema: "http://json-schema.org/draft-04/schema#", 132 | type: "integer", 133 | format: "int64", 134 | minimum: 500, 135 | maximum: Math.pow(2, 63) - 1, 136 | }; 137 | 138 | expect(result).toEqual(expected); 139 | }); 140 | 141 | test("handles int64 format with specified minimum that's too small", async function ({ expect }) { 142 | const schema = { 143 | type: "integer", 144 | format: "int64", 145 | minimum: -Math.pow(2, 64), 146 | }; 147 | 148 | const result = convert(schema); 149 | 150 | const expected = { 151 | $schema: "http://json-schema.org/draft-04/schema#", 152 | type: "integer", 153 | format: "int64", 154 | minimum: 0 - Math.pow(2, 63), 155 | maximum: Math.pow(2, 63) - 1, 156 | }; 157 | 158 | expect(result).toEqual(expected); 159 | }); 160 | 161 | test("handles int64 format with specified maximum", async function ({ expect }) { 162 | const schema = { 163 | type: "integer", 164 | format: "int64", 165 | maximum: 500, 166 | }; 167 | 168 | const result = convert(schema); 169 | 170 | const expected = { 171 | $schema: "http://json-schema.org/draft-04/schema#", 172 | type: "integer", 173 | format: "int64", 174 | minimum: 0 - Math.pow(2, 63), 175 | maximum: 500, 176 | }; 177 | 178 | expect(result).toEqual(expected); 179 | }); 180 | 181 | test("handles int64 format with specified minimum that's too big", async function ({ expect }) { 182 | const schema = { 183 | type: "integer", 184 | format: "int64", 185 | maximum: Math.pow(2, 64), 186 | }; 187 | 188 | const result = convert(schema); 189 | 190 | const expected = { 191 | $schema: "http://json-schema.org/draft-04/schema#", 192 | type: "integer", 193 | format: "int64", 194 | minimum: 0 - Math.pow(2, 63), 195 | maximum: Math.pow(2, 63) - 1, 196 | }; 197 | 198 | expect(result).toEqual(expected); 199 | }); 200 | 201 | it("handles float format", async ({ expect }) => { 202 | const schema = { 203 | type: "number", 204 | format: "float", 205 | }; 206 | 207 | const result = convert(schema); 208 | 209 | const expected = { 210 | $schema: "http://json-schema.org/draft-04/schema#", 211 | type: "number", 212 | format: "float", 213 | minimum: 0 - Math.pow(2, 128), 214 | maximum: Math.pow(2, 128) - 1, 215 | }; 216 | 217 | expect(result).toEqual(expected); 218 | }); 219 | 220 | test("handles float format with specified minimum", async function ({ expect }) { 221 | const schema = { 222 | type: "number", 223 | format: "float", 224 | minimum: 500, 225 | }; 226 | 227 | const result = convert(schema); 228 | 229 | const expected = { 230 | $schema: "http://json-schema.org/draft-04/schema#", 231 | type: "number", 232 | format: "float", 233 | minimum: 500, 234 | maximum: Math.pow(2, 128) - 1, 235 | }; 236 | 237 | expect(result).toEqual(expected); 238 | }); 239 | 240 | test("handles float format with specified minimum that's too small", async function ({ expect }) { 241 | const schema = { 242 | type: "number", 243 | format: "float", 244 | minimum: -Math.pow(2, 129), 245 | }; 246 | 247 | const result = convert(schema); 248 | 249 | const expected = { 250 | $schema: "http://json-schema.org/draft-04/schema#", 251 | type: "number", 252 | format: "float", 253 | minimum: 0 - Math.pow(2, 128), 254 | maximum: Math.pow(2, 128) - 1, 255 | }; 256 | 257 | expect(result).toEqual(expected); 258 | }); 259 | 260 | test("handles float format with specified maximum", async function ({ expect }) { 261 | const schema = { 262 | type: "number", 263 | format: "float", 264 | maximum: 500, 265 | }; 266 | 267 | const result = convert(schema); 268 | 269 | const expected = { 270 | $schema: "http://json-schema.org/draft-04/schema#", 271 | type: "number", 272 | format: "float", 273 | minimum: 0 - Math.pow(2, 128), 274 | maximum: 500, 275 | }; 276 | 277 | expect(result).toEqual(expected); 278 | }); 279 | 280 | test("handles float format with specified minimum that's too big", async function ({ expect }) { 281 | const schema = { 282 | type: "number", 283 | format: "float", 284 | maximum: Math.pow(2, 129), 285 | }; 286 | 287 | const result = convert(schema); 288 | 289 | const expected = { 290 | $schema: "http://json-schema.org/draft-04/schema#", 291 | type: "number", 292 | format: "float", 293 | minimum: 0 - Math.pow(2, 128), 294 | maximum: Math.pow(2, 128) - 1, 295 | }; 296 | 297 | expect(result).toEqual(expected); 298 | }); 299 | 300 | it("handles double format", async ({ expect }) => { 301 | const schema = { 302 | type: "number", 303 | format: "double", 304 | }; 305 | 306 | const result = convert(schema); 307 | 308 | const expected = { 309 | $schema: "http://json-schema.org/draft-04/schema#", 310 | type: "number", 311 | format: "double", 312 | minimum: 0 - Number.MAX_VALUE, 313 | maximum: Number.MAX_VALUE, 314 | }; 315 | 316 | expect(result).toEqual(expected); 317 | }); 318 | 319 | test("handles double format with specified minimum", async function ({ expect }) { 320 | const schema = { 321 | type: "number", 322 | format: "double", 323 | minimum: 50.5, 324 | }; 325 | 326 | const result = convert(schema); 327 | 328 | const expected = { 329 | $schema: "http://json-schema.org/draft-04/schema#", 330 | type: "number", 331 | format: "double", 332 | minimum: 50.5, 333 | maximum: Number.MAX_VALUE - 1, 334 | }; 335 | 336 | expect(result).toEqual(expected); 337 | }); 338 | 339 | test("handles double format with specified maximum", async function ({ expect }) { 340 | const schema = { 341 | type: "number", 342 | format: "double", 343 | maximum: 50.5, 344 | }; 345 | 346 | const result = convert(schema); 347 | 348 | const expected = { 349 | $schema: "http://json-schema.org/draft-04/schema#", 350 | type: "number", 351 | format: "double", 352 | minimum: 0 - Number.MAX_VALUE, 353 | maximum: 50.5, 354 | }; 355 | 356 | expect(result).toEqual(expected); 357 | }); 358 | -------------------------------------------------------------------------------- /test/parameter.test.ts: -------------------------------------------------------------------------------- 1 | import * as convert from "../src"; 2 | 3 | it("converting a minimal OpenAPI 3.0 parameter", async ({ expect }) => { 4 | const schema = { 5 | name: "parameter name", 6 | in: "cookie", 7 | schema: { 8 | type: "string", 9 | nullable: true, 10 | }, 11 | }; 12 | 13 | const result = convert.fromParameter(schema); 14 | 15 | const expected = { 16 | $schema: "http://json-schema.org/draft-04/schema#", 17 | type: ["string", "null"], 18 | }; 19 | 20 | expect(result).toEqual(expected); 21 | }); 22 | 23 | it("converting an extensive OpenAPI 3.0 parameter", async ({ expect }) => { 24 | const schema = { 25 | name: "parameter name", 26 | in: "cookie", 27 | schema: { 28 | type: "string", 29 | nullable: true, 30 | }, 31 | required: true, 32 | allowEmptyValue: true, 33 | deprecated: true, 34 | allowReserved: true, 35 | style: "matrix", 36 | explode: true, 37 | example: "parameter example", 38 | }; 39 | 40 | const result = convert.fromParameter(schema); 41 | 42 | const expected = { 43 | $schema: "http://json-schema.org/draft-04/schema#", 44 | type: ["string", "null"], 45 | }; 46 | 47 | expect(result).toEqual(expected); 48 | }); 49 | 50 | it("converting a OpenAPI 3.0 parameter with MIME schemas", async ({ expect }) => { 51 | const schema = { 52 | name: "parameter name", 53 | in: "cookie", 54 | content: { 55 | "application/javascript": { 56 | schema: { 57 | type: "string", 58 | nullable: true, 59 | }, 60 | }, 61 | "text/css": { 62 | schema: { 63 | type: "string", 64 | nullable: true, 65 | }, 66 | }, 67 | }, 68 | }; 69 | 70 | const result = convert.fromParameter(schema); 71 | 72 | const expected = { 73 | "application/javascript": { 74 | $schema: "http://json-schema.org/draft-04/schema#", 75 | type: ["string", "null"], 76 | }, 77 | "text/css": { 78 | $schema: "http://json-schema.org/draft-04/schema#", 79 | type: ["string", "null"], 80 | }, 81 | }; 82 | 83 | expect(result).toEqual(expected); 84 | }); 85 | 86 | it("converting a OpenAPI 3.0 parameter with MIMEs without a schema", async ({ expect }) => { 87 | const schema = { 88 | name: "parameter name", 89 | in: "cookie", 90 | content: { 91 | "application/javascript": { 92 | schema: { 93 | type: "string", 94 | nullable: true, 95 | }, 96 | }, 97 | "text/css": {}, 98 | }, 99 | }; 100 | 101 | const result = convert.fromParameter(schema); 102 | 103 | const expected = { 104 | "application/javascript": { 105 | $schema: "http://json-schema.org/draft-04/schema#", 106 | type: ["string", "null"], 107 | }, 108 | "text/css": { 109 | $schema: "http://json-schema.org/draft-04/schema#", 110 | }, 111 | }; 112 | 113 | expect(result).toEqual(expected); 114 | }); 115 | 116 | it("using a OpenAPI 3.0 parameter description", async ({ expect }) => { 117 | const schema = { 118 | name: "parameter name", 119 | in: "cookie", 120 | description: "parameter description", 121 | schema: { 122 | description: "schema description", 123 | }, 124 | }; 125 | 126 | const result = convert.fromParameter(schema); 127 | 128 | const expected = { 129 | $schema: "http://json-schema.org/draft-04/schema#", 130 | description: "parameter description", 131 | }; 132 | 133 | expect(result).toEqual(expected); 134 | }); 135 | 136 | it("throwing on OpenAPI 3.0 parameters without schemas", async ({ expect }) => { 137 | const schema = { 138 | name: "parameter name", 139 | in: "cookie", 140 | }; 141 | 142 | expect(() => { 143 | convert.fromParameter(schema); 144 | }).toThrow("parameter must have either a"); 145 | }); 146 | 147 | it("doesnt throw for parameters without schemas with stricMode disabled", async ({ expect }) => { 148 | const schema = { 149 | name: "parameter name", 150 | in: "cookie", 151 | }; 152 | 153 | const result = convert.fromParameter(schema, { strictMode: false }); 154 | 155 | const expected = { 156 | $schema: "http://json-schema.org/draft-04/schema#", 157 | }; 158 | 159 | expect(result).toEqual(expected); 160 | }); 161 | -------------------------------------------------------------------------------- /test/pattern_properties.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("handling additional properties of the same type: string", async ({ expect }) => { 4 | const schema = { 5 | type: "object", 6 | additionalProperties: { 7 | type: "string", 8 | }, 9 | "x-patternProperties": { 10 | "^[a-z]*$": { 11 | type: "string", 12 | }, 13 | }, 14 | }; 15 | 16 | const result = convert(schema, { supportPatternProperties: true }); 17 | 18 | const expected = { 19 | $schema: "http://json-schema.org/draft-04/schema#", 20 | type: "object", 21 | additionalProperties: false, 22 | patternProperties: { 23 | "^[a-z]*$": { 24 | type: "string", 25 | }, 26 | }, 27 | }; 28 | 29 | expect(result).toEqual(expected); 30 | }); 31 | 32 | it("handling additional properties of the same type: number", async ({ expect }) => { 33 | const schema = { 34 | type: "object", 35 | additionalProperties: { 36 | type: "number", 37 | }, 38 | "x-patternProperties": { 39 | "^[a-z]*$": { 40 | type: "number", 41 | }, 42 | }, 43 | }; 44 | 45 | const result = convert(schema, { supportPatternProperties: true }); 46 | 47 | const expected = { 48 | $schema: "http://json-schema.org/draft-04/schema#", 49 | type: "object", 50 | additionalProperties: false, 51 | patternProperties: { 52 | "^[a-z]*$": { 53 | type: "number", 54 | }, 55 | }, 56 | }; 57 | 58 | expect(result).toEqual(expected); 59 | }); 60 | 61 | it("handling additional properties with one of patternProperty types", async ({ expect }) => { 62 | const schema = { 63 | type: "object", 64 | additionalProperties: { 65 | type: "number", 66 | }, 67 | "x-patternProperties": { 68 | "^[a-z]*$": { 69 | type: "string", 70 | }, 71 | "^[A-Z]*$": { 72 | type: "number", 73 | }, 74 | }, 75 | }; 76 | 77 | const result = convert(schema, { supportPatternProperties: true }); 78 | 79 | const expected = { 80 | $schema: "http://json-schema.org/draft-04/schema#", 81 | type: "object", 82 | additionalProperties: false, 83 | patternProperties: { 84 | "^[a-z]*$": { 85 | type: "string", 86 | }, 87 | "^[A-Z]*$": { 88 | type: "number", 89 | }, 90 | }, 91 | }; 92 | 93 | expect(result).toEqual(expected); 94 | }); 95 | 96 | it("handling additionalProperties with matching objects", async ({ expect }) => { 97 | const schema = { 98 | type: "object", 99 | additionalProperties: { 100 | type: "object", 101 | properties: { 102 | test: { 103 | type: "string", 104 | }, 105 | }, 106 | }, 107 | "x-patternProperties": { 108 | "^[a-z]*$": { 109 | type: "string", 110 | }, 111 | "^[A-Z]*$": { 112 | type: "object", 113 | properties: { 114 | test: { 115 | type: "string", 116 | }, 117 | }, 118 | }, 119 | }, 120 | }; 121 | 122 | const result = convert(schema, { supportPatternProperties: true }); 123 | 124 | const expected = { 125 | $schema: "http://json-schema.org/draft-04/schema#", 126 | type: "object", 127 | additionalProperties: false, 128 | patternProperties: { 129 | "^[a-z]*$": { 130 | type: "string", 131 | }, 132 | "^[A-Z]*$": { 133 | type: "object", 134 | properties: { 135 | test: { 136 | type: "string", 137 | }, 138 | }, 139 | }, 140 | }, 141 | }; 142 | 143 | expect(result).toEqual(expected); 144 | }); 145 | 146 | it("handling null x-patternProperties", async ({ expect }) => { 147 | const schema = { 148 | type: "object", 149 | additionalProperties: { 150 | type: "object", 151 | properties: { 152 | test: { 153 | type: "string", 154 | }, 155 | }, 156 | }, 157 | "x-patternProperties": null, 158 | }; 159 | 160 | const result = convert(schema, { supportPatternProperties: true }); 161 | 162 | const expected = { 163 | $schema: "http://json-schema.org/draft-04/schema#", 164 | type: "object", 165 | additionalProperties: { 166 | type: "object", 167 | properties: { 168 | test: { 169 | type: "string", 170 | }, 171 | }, 172 | }, 173 | }; 174 | 175 | expect(result["x-patternProperties"]).toEqual(void 0); 176 | expect(result).toEqual(expected); 177 | }); 178 | 179 | it("handling additionalProperties with non-matching objects", async ({ expect }) => { 180 | const schema = { 181 | type: "object", 182 | additionalProperties: { 183 | type: "object", 184 | properties: { 185 | test: { 186 | type: "string", 187 | }, 188 | }, 189 | }, 190 | "x-patternProperties": { 191 | "^[a-z]*$": { 192 | type: "string", 193 | }, 194 | "^[A-Z]*$": { 195 | type: "object", 196 | properties: { 197 | test: { 198 | type: "integer", 199 | }, 200 | }, 201 | }, 202 | }, 203 | }; 204 | 205 | const result = convert(schema, { supportPatternProperties: true }); 206 | 207 | const expected = { 208 | $schema: "http://json-schema.org/draft-04/schema#", 209 | type: "object", 210 | additionalProperties: { 211 | type: "object", 212 | properties: { 213 | test: { 214 | type: "string", 215 | }, 216 | }, 217 | }, 218 | patternProperties: { 219 | "^[a-z]*$": { 220 | type: "string", 221 | }, 222 | "^[A-Z]*$": { 223 | type: "object", 224 | properties: { 225 | test: { 226 | type: "integer", 227 | }, 228 | }, 229 | }, 230 | }, 231 | }; 232 | 233 | expect(result).toEqual(expected); 234 | }); 235 | 236 | it("handling additionalProperties with matching array", async ({ expect }) => { 237 | const schema = { 238 | type: "object", 239 | additionalProperties: { 240 | type: "array", 241 | items: { 242 | type: "string", 243 | }, 244 | }, 245 | "x-patternProperties": { 246 | "^[a-z]*$": { 247 | type: "string", 248 | }, 249 | "^[A-Z]*$": { 250 | type: "array", 251 | items: { 252 | type: "string", 253 | }, 254 | }, 255 | }, 256 | }; 257 | 258 | const result = convert(schema, { supportPatternProperties: true }); 259 | 260 | const expected = { 261 | $schema: "http://json-schema.org/draft-04/schema#", 262 | type: "object", 263 | additionalProperties: false, 264 | patternProperties: { 265 | "^[a-z]*$": { 266 | type: "string", 267 | }, 268 | "^[A-Z]*$": { 269 | type: "array", 270 | items: { 271 | type: "string", 272 | }, 273 | }, 274 | }, 275 | }; 276 | 277 | expect(result).toEqual(expected); 278 | }); 279 | 280 | it("handling additionalProperties with composition types", async ({ expect }) => { 281 | const schema = { 282 | type: "object", 283 | additionalProperties: { 284 | oneOf: [ 285 | { 286 | type: "string", 287 | }, 288 | { 289 | type: "integer", 290 | }, 291 | ], 292 | }, 293 | "x-patternProperties": { 294 | "^[a-z]*$": { 295 | oneOf: [ 296 | { 297 | type: "string", 298 | }, 299 | { 300 | type: "integer", 301 | }, 302 | ], 303 | }, 304 | }, 305 | }; 306 | 307 | const result = convert(schema, { supportPatternProperties: true }); 308 | 309 | const expected = { 310 | $schema: "http://json-schema.org/draft-04/schema#", 311 | type: "object", 312 | additionalProperties: false, 313 | patternProperties: { 314 | "^[a-z]*$": { 315 | oneOf: [ 316 | { 317 | type: "string", 318 | }, 319 | { 320 | type: "integer", 321 | }, 322 | ], 323 | }, 324 | }, 325 | }; 326 | 327 | expect(result).toEqual(expected); 328 | }); 329 | 330 | it("not supporting patternProperties", async ({ expect }) => { 331 | const schema = { 332 | type: "object", 333 | additionalProperties: { 334 | type: "string", 335 | }, 336 | "x-patternProperties": { 337 | "^[a-z]*$": { 338 | type: "string", 339 | }, 340 | }, 341 | }; 342 | 343 | const result = convert(schema, { supportPatternProperties: false }); 344 | 345 | const expected = { 346 | $schema: "http://json-schema.org/draft-04/schema#", 347 | type: "object", 348 | additionalProperties: { 349 | type: "string", 350 | }, 351 | "x-patternProperties": { 352 | "^[a-z]*$": { 353 | type: "string", 354 | }, 355 | }, 356 | }; 357 | 358 | expect(result).toEqual(expected); 359 | }); 360 | 361 | it("not supporting patternProperties by default", async ({ expect }) => { 362 | const schema = { 363 | type: "object", 364 | additionalProperties: { 365 | type: "string", 366 | }, 367 | "x-patternProperties": { 368 | "^[a-z]*$": { 369 | type: "string", 370 | }, 371 | }, 372 | }; 373 | 374 | const result = convert(schema); 375 | 376 | const expected = { 377 | $schema: "http://json-schema.org/draft-04/schema#", 378 | type: "object", 379 | additionalProperties: { 380 | type: "string", 381 | }, 382 | "x-patternProperties": { 383 | "^[a-z]*$": { 384 | type: "string", 385 | }, 386 | }, 387 | }; 388 | 389 | expect(result).toEqual(expected); 390 | }); 391 | 392 | it("setting custom patternProperties handler", async ({ expect }) => { 393 | const schema = { 394 | type: "object", 395 | additionalProperties: { 396 | type: "string", 397 | }, 398 | "x-patternProperties": { 399 | "^[a-z]*$": { 400 | type: "string", 401 | }, 402 | }, 403 | }; 404 | 405 | const result = convert(schema, { 406 | supportPatternProperties: true, 407 | patternPropertiesHandler: function (schema) { 408 | schema.patternProperties = false; 409 | return schema; 410 | }, 411 | }); 412 | 413 | const expected = { 414 | $schema: "http://json-schema.org/draft-04/schema#", 415 | type: "object", 416 | additionalProperties: { 417 | type: "string", 418 | }, 419 | patternProperties: false, 420 | }; 421 | 422 | expect(result).toEqual(expected); 423 | }); 424 | 425 | it("additionalProperties not modified if set to true", async ({ expect }) => { 426 | const schema = { 427 | type: "object", 428 | additionalProperties: true, 429 | "x-patternProperties": { 430 | "^[a-z]*$": { 431 | type: "string", 432 | }, 433 | }, 434 | }; 435 | 436 | const result = convert(schema, { supportPatternProperties: true }); 437 | 438 | const expected = { 439 | $schema: "http://json-schema.org/draft-04/schema#", 440 | type: "object", 441 | additionalProperties: true, 442 | patternProperties: { 443 | "^[a-z]*$": { 444 | type: "string", 445 | }, 446 | }, 447 | }; 448 | 449 | expect(result).toEqual(expected); 450 | }); 451 | -------------------------------------------------------------------------------- /test/properties.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("properties", async ({ expect }) => { 4 | const schema = { 5 | type: "object", 6 | required: ["bar"], 7 | properties: { 8 | foo: { 9 | type: "string", 10 | example: "2017-01-01T12:34:56Z", 11 | }, 12 | bar: { 13 | type: "string", 14 | nullable: true, 15 | }, 16 | }, 17 | }; 18 | 19 | const result = convert(schema); 20 | 21 | const expected = { 22 | $schema: "http://json-schema.org/draft-04/schema#", 23 | type: "object", 24 | required: ["bar"], 25 | properties: { 26 | foo: { 27 | type: "string", 28 | }, 29 | bar: { 30 | type: ["string", "null"], 31 | }, 32 | }, 33 | }; 34 | 35 | expect(result).toEqual(expected); 36 | }); 37 | 38 | it("properties value is null", async ({ expect }) => { 39 | const schema = { 40 | type: "object", 41 | properties: null, 42 | }; 43 | 44 | const result = convert(schema); 45 | 46 | const expected = { 47 | $schema: "http://json-schema.org/draft-04/schema#", 48 | type: "object", 49 | }; 50 | 51 | expect(result).toEqual(expected); 52 | }); 53 | 54 | it("strips malformed properties children", async ({ expect }) => { 55 | const schema = { 56 | type: "object", 57 | required: ["bar"], 58 | properties: { 59 | foo: { 60 | type: "string", 61 | example: "2017-01-01T12:34:56Z", 62 | }, 63 | foobar: 2, 64 | bar: { 65 | type: "string", 66 | nullable: true, 67 | }, 68 | baz: null, 69 | }, 70 | }; 71 | 72 | const result = convert(schema); 73 | 74 | const expected = { 75 | $schema: "http://json-schema.org/draft-04/schema#", 76 | type: "object", 77 | required: ["bar"], 78 | properties: { 79 | foo: { 80 | type: "string", 81 | }, 82 | bar: { 83 | type: ["string", "null"], 84 | }, 85 | }, 86 | }; 87 | 88 | expect(result).toEqual(expected); 89 | }); 90 | 91 | it("additionalProperties is false", async ({ expect }) => { 92 | const schema = { 93 | type: "object", 94 | properties: { 95 | foo: { 96 | type: "string", 97 | example: "2017-01-01T12:34:56Z", 98 | }, 99 | }, 100 | additionalProperties: false, 101 | }; 102 | 103 | const result = convert(schema); 104 | 105 | const expected = { 106 | $schema: "http://json-schema.org/draft-04/schema#", 107 | type: "object", 108 | properties: { 109 | foo: { 110 | type: "string", 111 | }, 112 | }, 113 | additionalProperties: false, 114 | }; 115 | 116 | expect(result).toEqual(expected); 117 | }); 118 | 119 | it("additionalProperties is true", async ({ expect }) => { 120 | const schema = { 121 | type: "object", 122 | properties: { 123 | foo: { 124 | type: "string", 125 | example: "2017-01-01T12:34:56Z", 126 | }, 127 | }, 128 | additionalProperties: true, 129 | }; 130 | 131 | const result = convert(schema); 132 | 133 | const expected = { 134 | $schema: "http://json-schema.org/draft-04/schema#", 135 | type: "object", 136 | properties: { 137 | foo: { 138 | type: "string", 139 | }, 140 | }, 141 | additionalProperties: true, 142 | }; 143 | 144 | expect(result).toEqual(expected); 145 | }); 146 | 147 | it("additionalProperties is an object", async ({ expect }) => { 148 | const schema = { 149 | type: "object", 150 | properties: { 151 | foo: { 152 | type: "string", 153 | example: "2017-01-01T12:34:56Z", 154 | }, 155 | }, 156 | additionalProperties: { 157 | type: "object", 158 | properties: { 159 | foo: { 160 | type: "string", 161 | }, 162 | }, 163 | }, 164 | }; 165 | 166 | const result = convert(schema); 167 | 168 | const expected = { 169 | $schema: "http://json-schema.org/draft-04/schema#", 170 | type: "object", 171 | properties: { 172 | foo: { 173 | type: "string", 174 | }, 175 | }, 176 | additionalProperties: { 177 | type: "object", 178 | properties: { 179 | foo: { 180 | type: "string", 181 | }, 182 | }, 183 | }, 184 | }; 185 | 186 | expect(result).toEqual(expected); 187 | }); 188 | -------------------------------------------------------------------------------- /test/readonly_writeonly.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("removing readOnly prop", async ({ expect }) => { 4 | const schema = { 5 | type: "object", 6 | properties: { 7 | prop1: { 8 | type: "string", 9 | readOnly: true, 10 | }, 11 | prop2: { 12 | type: "string", 13 | }, 14 | }, 15 | }; 16 | 17 | const result = convert(schema, { removeReadOnly: true }); 18 | 19 | const expected = { 20 | $schema: "http://json-schema.org/draft-04/schema#", 21 | type: "object", 22 | properties: { 23 | prop2: { 24 | type: "string", 25 | }, 26 | }, 27 | }; 28 | 29 | expect(result).toEqual(expected); 30 | }); 31 | 32 | it("removing readOnly prop even if keeping", async ({ expect }) => { 33 | const schema = { 34 | type: "object", 35 | properties: { 36 | prop1: { 37 | type: "string", 38 | readOnly: true, 39 | }, 40 | prop2: { 41 | type: "string", 42 | }, 43 | }, 44 | }; 45 | 46 | const result = convert(schema, { 47 | removeReadOnly: true, 48 | keepNotSupported: ["readOnly"], 49 | }); 50 | 51 | const expected = { 52 | $schema: "http://json-schema.org/draft-04/schema#", 53 | type: "object", 54 | properties: { 55 | prop2: { 56 | type: "string", 57 | }, 58 | }, 59 | }; 60 | 61 | expect(result).toEqual(expected); 62 | }); 63 | 64 | it("removing writeOnly prop & required", async ({ expect }) => { 65 | const schema = { 66 | type: "object", 67 | required: ["prop1", "prop2", "prop3", "prop4"], 68 | properties: { 69 | prop1: { 70 | type: "string", 71 | writeOnly: true, 72 | }, 73 | prop2: { 74 | type: "string", 75 | writeOnly: true, 76 | }, 77 | prop3: { 78 | type: "string", 79 | writeOnly: true, 80 | }, 81 | prop4: { 82 | type: "string", 83 | }, 84 | }, 85 | }; 86 | 87 | const result = convert(schema, { removeWriteOnly: true }); 88 | 89 | const expected = { 90 | $schema: "http://json-schema.org/draft-04/schema#", 91 | type: "object", 92 | required: ["prop4"], 93 | properties: { 94 | prop4: { 95 | type: "string", 96 | }, 97 | }, 98 | }; 99 | 100 | expect(result).toEqual(expected); 101 | }); 102 | 103 | it("removing readOnly from required", async ({ expect }) => { 104 | const schema = { 105 | type: "object", 106 | required: ["prop1", "prop2", "prop3", "prop4"], 107 | properties: { 108 | prop1: { 109 | type: "string", 110 | }, 111 | prop2: { 112 | type: "string", 113 | readOnly: true, 114 | }, 115 | prop3: { 116 | type: "string", 117 | readOnly: true, 118 | }, 119 | prop4: { 120 | type: "string", 121 | }, 122 | }, 123 | }; 124 | 125 | const result = convert(schema, { removeReadOnly: true }); 126 | 127 | const expected = { 128 | $schema: "http://json-schema.org/draft-04/schema#", 129 | type: "object", 130 | required: ["prop1", "prop4"], 131 | properties: { 132 | prop1: { 133 | type: "string", 134 | }, 135 | prop4: { 136 | type: "string", 137 | }, 138 | }, 139 | }; 140 | 141 | expect(result).toEqual(expected); 142 | }); 143 | 144 | it("deleting required if empty", async ({ expect }) => { 145 | const schema = { 146 | type: "object", 147 | required: ["prop1"], 148 | properties: { 149 | prop1: { 150 | type: "string", 151 | readOnly: true, 152 | }, 153 | prop2: { 154 | type: "string", 155 | }, 156 | }, 157 | }; 158 | 159 | const result = convert(schema, { removeReadOnly: true }); 160 | 161 | const expected = { 162 | $schema: "http://json-schema.org/draft-04/schema#", 163 | type: "object", 164 | properties: { 165 | prop2: { 166 | type: "string", 167 | }, 168 | }, 169 | }; 170 | 171 | expect(result).toEqual(expected); 172 | }); 173 | 174 | it("deleting properties if empty", async ({ expect }) => { 175 | const schema = { 176 | type: "object", 177 | required: ["prop1"], 178 | properties: { 179 | prop1: { 180 | type: "string", 181 | readOnly: true, 182 | }, 183 | }, 184 | }; 185 | 186 | const result = convert(schema, { removeReadOnly: true }); 187 | 188 | const expected = { 189 | $schema: "http://json-schema.org/draft-04/schema#", 190 | type: "object", 191 | }; 192 | 193 | expect(result).toEqual(expected); 194 | }); 195 | 196 | it("not removing readOnly props by default", async ({ expect }) => { 197 | const schema = { 198 | type: "object", 199 | required: ["prop1", "prop2"], 200 | properties: { 201 | prop1: { 202 | type: "string", 203 | readOnly: true, 204 | }, 205 | prop2: { 206 | type: "string", 207 | }, 208 | }, 209 | }; 210 | 211 | const result = convert(schema); 212 | 213 | const expected = { 214 | $schema: "http://json-schema.org/draft-04/schema#", 215 | type: "object", 216 | required: ["prop1", "prop2"], 217 | properties: { 218 | prop1: { 219 | type: "string", 220 | }, 221 | prop2: { 222 | type: "string", 223 | }, 224 | }, 225 | }; 226 | 227 | expect(result).toEqual(expected); 228 | }); 229 | 230 | it("not removing writeOnly props by default", async ({ expect }) => { 231 | const schema = { 232 | type: "object", 233 | required: ["prop1", "prop2"], 234 | properties: { 235 | prop1: { 236 | type: "string", 237 | writeOnly: true, 238 | }, 239 | prop2: { 240 | type: "string", 241 | }, 242 | }, 243 | }; 244 | 245 | const result = convert(schema); 246 | 247 | const expected = { 248 | $schema: "http://json-schema.org/draft-04/schema#", 249 | type: "object", 250 | required: ["prop1", "prop2"], 251 | properties: { 252 | prop1: { 253 | type: "string", 254 | }, 255 | prop2: { 256 | type: "string", 257 | }, 258 | }, 259 | }; 260 | 261 | expect(result).toEqual(expected); 262 | }); 263 | 264 | it("deep schema", async ({ expect }) => { 265 | const schema = { 266 | type: "object", 267 | required: ["prop1", "prop2"], 268 | properties: { 269 | prop1: { 270 | type: "string", 271 | readOnly: true, 272 | }, 273 | prop2: { 274 | allOf: [ 275 | { 276 | type: "object", 277 | required: ["prop3"], 278 | properties: { 279 | prop3: { 280 | type: "object", 281 | readOnly: true, 282 | }, 283 | }, 284 | }, 285 | { 286 | type: "object", 287 | properties: { 288 | prop4: { 289 | type: "object", 290 | readOnly: true, 291 | }, 292 | }, 293 | }, 294 | ], 295 | }, 296 | }, 297 | }; 298 | 299 | const result = convert(schema, { removeReadOnly: true }); 300 | 301 | const expected = { 302 | $schema: "http://json-schema.org/draft-04/schema#", 303 | type: "object", 304 | required: ["prop2"], 305 | properties: { 306 | prop2: { 307 | allOf: [ 308 | { 309 | type: "object", 310 | }, 311 | { 312 | type: "object", 313 | }, 314 | ], 315 | }, 316 | }, 317 | }; 318 | 319 | expect(result).toEqual(expected); 320 | }); 321 | -------------------------------------------------------------------------------- /test/schemas/schema-1-expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "allOf": [ 3 | { 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "properties": { 8 | "cats": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer" 12 | } 13 | } 14 | } 15 | }, 16 | { 17 | "type": "object", 18 | "properties": { 19 | "dogs": { 20 | "type": "array", 21 | "items": { 22 | "type": "integer" 23 | } 24 | } 25 | } 26 | }, 27 | { 28 | "type": "object", 29 | "properties": { 30 | "bring_cats": { 31 | "type": "array", 32 | "items": { 33 | "allOf": [ 34 | { 35 | "type": "object", 36 | "properties": { 37 | "email": { 38 | "type": "string" 39 | }, 40 | "sms": { 41 | "type": ["string", "null"] 42 | }, 43 | "properties": { 44 | "type": "object", 45 | "additionalProperties": { 46 | "type": "string" 47 | } 48 | } 49 | } 50 | }, 51 | { 52 | "required": ["email"] 53 | } 54 | ] 55 | } 56 | } 57 | } 58 | } 59 | ] 60 | }, 61 | { 62 | "type": "object", 63 | "properties": { 64 | "playground": { 65 | "type": "object", 66 | "required": ["feeling", "child"], 67 | "properties": { 68 | "feeling": { 69 | "type": "string" 70 | }, 71 | "child": { 72 | "type": "object", 73 | "required": ["name", "age"], 74 | "properties": { 75 | "name": { 76 | "type": "string" 77 | }, 78 | "age": { 79 | "type": "integer" 80 | } 81 | } 82 | }, 83 | "toy": { 84 | "type": "object", 85 | "properties": { 86 | "breaks_easily": { 87 | "type": "boolean", 88 | "default": false 89 | }, 90 | "color": { 91 | "type": "string", 92 | "description": "Color of the toy" 93 | }, 94 | "type": { 95 | "type": "string", 96 | "enum": ["bucket", "shovel"], 97 | "description": "Toy type" 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | ], 106 | "$schema": "http://json-schema.org/draft-04/schema#" 107 | } 108 | -------------------------------------------------------------------------------- /test/schemas/schema-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "allOf": [ 3 | { 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "properties": { 8 | "cats": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer", 12 | "example": [1] 13 | } 14 | } 15 | } 16 | }, 17 | { 18 | "type": "object", 19 | "properties": { 20 | "dogs": { 21 | "type": "array", 22 | "items": { 23 | "type": "integer", 24 | "example": [1] 25 | } 26 | } 27 | } 28 | }, 29 | { 30 | "type": "object", 31 | "properties": { 32 | "bring_cats": { 33 | "type": "array", 34 | "items": { 35 | "allOf": [ 36 | { 37 | "type": "object", 38 | "properties": { 39 | "email": { 40 | "type": "string", 41 | "example": "cats@email.com" 42 | }, 43 | "sms": { 44 | "type": "string", 45 | "nullable": true, 46 | "example": "+12345678" 47 | }, 48 | "properties": { 49 | "type": "object", 50 | "additionalProperties": { 51 | "type": "string" 52 | }, 53 | "example": { 54 | "name": "Wookie" 55 | } 56 | } 57 | } 58 | }, 59 | { 60 | "required": ["email"] 61 | } 62 | ] 63 | } 64 | } 65 | } 66 | } 67 | ] 68 | }, 69 | { 70 | "type": "object", 71 | "properties": { 72 | "playground": { 73 | "type": "object", 74 | "required": ["feeling", "child"], 75 | "properties": { 76 | "feeling": { 77 | "type": "string", 78 | "example": "Good feeling" 79 | }, 80 | "child": { 81 | "type": "object", 82 | "required": ["name", "age"], 83 | "properties": { 84 | "name": { 85 | "type": "string", 86 | "example": "Steven" 87 | }, 88 | "age": { 89 | "type": "integer", 90 | "example": 5 91 | } 92 | } 93 | }, 94 | "toy": { 95 | "type": "object", 96 | "properties": { 97 | "breaks_easily": { 98 | "type": "boolean", 99 | "default": false 100 | }, 101 | "color": { 102 | "type": "string", 103 | "description": "Color of the toy" 104 | }, 105 | "type": { 106 | "type": "string", 107 | "enum": ["bucket", "shovel"], 108 | "description": "Toy type" 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /test/schemas/schema-2-invalid-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "allOf": [ 3 | { 4 | "anyOf": [ 5 | { 6 | "type": "object", 7 | "properties": { 8 | "cats": { 9 | "type": "array", 10 | "items": { 11 | "type": "integer", 12 | "example": [1] 13 | } 14 | } 15 | } 16 | }, 17 | { 18 | "type": "object", 19 | "properties": { 20 | "dogs": { 21 | "type": "array", 22 | "items": { 23 | "type": "integer", 24 | "example": [1] 25 | } 26 | } 27 | } 28 | }, 29 | { 30 | "type": "object", 31 | "properties": { 32 | "bring_cats": { 33 | "type": "array", 34 | "items": { 35 | "allOf": [ 36 | { 37 | "type": "object", 38 | "properties": { 39 | "email": { 40 | "type": "string", 41 | "example": "cats@email.com" 42 | }, 43 | "sms": { 44 | "type": "string", 45 | "nullable": true, 46 | "example": "+12345678" 47 | }, 48 | "properties": { 49 | "type": "object", 50 | "additionalProperties": { 51 | "type": "invalidtype" 52 | }, 53 | "example": { 54 | "name": "Wookie" 55 | } 56 | } 57 | } 58 | }, 59 | { 60 | "required": ["email"] 61 | } 62 | ] 63 | } 64 | } 65 | } 66 | } 67 | ] 68 | }, 69 | { 70 | "type": "object", 71 | "properties": { 72 | "playground": { 73 | "type": "object", 74 | "required": ["feeling", "child"], 75 | "properties": { 76 | "feeling": { 77 | "type": "string", 78 | "example": "Good feeling" 79 | }, 80 | "child": { 81 | "type": "object", 82 | "required": ["name", "age"], 83 | "properties": { 84 | "name": { 85 | "type": "string", 86 | "example": "Steven" 87 | }, 88 | "age": { 89 | "type": "integer", 90 | "example": 5 91 | } 92 | } 93 | }, 94 | "toy": { 95 | "type": "object", 96 | "properties": { 97 | "breaks_easily": { 98 | "type": "boolean", 99 | "default": false 100 | }, 101 | "color": { 102 | "type": "string", 103 | "description": "Color of the toy" 104 | }, 105 | "type": { 106 | "type": "string", 107 | "enum": ["bucket", "shovel"], 108 | "description": "Toy type" 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /test/string_types.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("plain string is untouched", async ({ expect }) => { 4 | const schema = { 5 | type: "string", 6 | }; 7 | 8 | const result = convert(schema); 9 | 10 | const expected = { 11 | $schema: "http://json-schema.org/draft-04/schema#", 12 | type: "string", 13 | }; 14 | 15 | expect(result).toEqual(expected); 16 | }); 17 | 18 | it("handles date", async ({ expect }) => { 19 | const schema = { 20 | type: "string", 21 | format: "date", 22 | }; 23 | 24 | const result = convert(schema); 25 | 26 | const expected = { 27 | $schema: "http://json-schema.org/draft-04/schema#", 28 | type: "string", 29 | format: "date", 30 | }; 31 | 32 | expect(result).toEqual(expected); 33 | 34 | { 35 | const schema = { 36 | type: "string", 37 | format: "date", 38 | }; 39 | 40 | const result = convert(schema, { dateToDateTime: true }); 41 | 42 | const expected = { 43 | $schema: "http://json-schema.org/draft-04/schema#", 44 | type: "string", 45 | format: "date-time", 46 | }; 47 | 48 | expect(result).toEqual(expected); 49 | } 50 | }); 51 | 52 | it("handles byte format", async ({ expect }) => { 53 | const schema = { 54 | type: "string", 55 | format: "byte", 56 | }; 57 | 58 | const result = convert(schema); 59 | 60 | const expected = { 61 | $schema: "http://json-schema.org/draft-04/schema#", 62 | type: "string", 63 | format: "byte", 64 | pattern: "^[\\w\\d+\\/=]*$", 65 | }; 66 | 67 | expect(result).toEqual(expected); 68 | }); 69 | 70 | it("retaining custom formats", async ({ expect }) => { 71 | const schema = { 72 | type: "string", 73 | format: "custom_email", 74 | }; 75 | 76 | const result = convert(schema); 77 | 78 | const expected = { 79 | $schema: "http://json-schema.org/draft-04/schema#", 80 | type: "string", 81 | format: "custom_email", 82 | }; 83 | 84 | expect(result).toEqual(expected); 85 | }); 86 | 87 | it("retain password format", async ({ expect }) => { 88 | const schema = { 89 | type: "string", 90 | format: "password", 91 | }; 92 | 93 | const result = convert(schema); 94 | 95 | const expected = { 96 | $schema: "http://json-schema.org/draft-04/schema#", 97 | type: "string", 98 | format: "password", 99 | }; 100 | 101 | expect(result).toEqual(expected); 102 | }); 103 | 104 | it("retain binary format", async ({ expect }) => { 105 | const schema = { 106 | type: "string", 107 | format: "binary", 108 | }; 109 | 110 | const result = convert(schema); 111 | 112 | const expected = { 113 | $schema: "http://json-schema.org/draft-04/schema#", 114 | type: "string", 115 | format: "binary", 116 | }; 117 | 118 | expect(result).toEqual(expected); 119 | }); 120 | -------------------------------------------------------------------------------- /test/transform.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("handles conversion in keywords specified in additionalKeywords", function ({ expect }) { 4 | const schema = { 5 | type: "boolean", 6 | }; 7 | 8 | const result = convert(schema, { 9 | beforeTransform: function (schema) { 10 | schema.type = "string"; 11 | return schema; 12 | }, 13 | afterTransform: function (schema) { 14 | schema.examples = ["foo", "bar"]; 15 | return schema; 16 | }, 17 | }); 18 | 19 | const expected = { 20 | $schema: "http://json-schema.org/draft-04/schema#", 21 | type: "string", 22 | examples: ["foo", "bar"], 23 | }; 24 | 25 | expect(result).toEqual(expected); 26 | }); 27 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "lib": ["ESNext", "dom"], 6 | "types": ["vitest/globals"] 7 | }, 8 | "include": ["."] 9 | } 10 | -------------------------------------------------------------------------------- /test/unsupported_properties.test.ts: -------------------------------------------------------------------------------- 1 | import convert from "../src"; 2 | 3 | it("remove discriminator by default", async ({ expect }) => { 4 | const schema = { 5 | oneOf: [ 6 | { 7 | type: "object", 8 | required: ["foo"], 9 | properties: { 10 | foo: { 11 | type: "string", 12 | }, 13 | }, 14 | }, 15 | { 16 | type: "object", 17 | required: ["foo"], 18 | properties: { 19 | foo: { 20 | type: "string", 21 | }, 22 | }, 23 | }, 24 | ], 25 | discriminator: { 26 | propertyName: "foo", 27 | }, 28 | }; 29 | 30 | const result = convert(schema); 31 | 32 | const expected = { 33 | $schema: "http://json-schema.org/draft-04/schema#", 34 | oneOf: [ 35 | { 36 | type: "object", 37 | required: ["foo"], 38 | properties: { 39 | foo: { 40 | type: "string", 41 | }, 42 | }, 43 | }, 44 | { 45 | type: "object", 46 | required: ["foo"], 47 | properties: { 48 | foo: { 49 | type: "string", 50 | }, 51 | }, 52 | }, 53 | ], 54 | }; 55 | 56 | expect(result).toEqual(expected); 57 | }); 58 | 59 | it("remove readOnly by default", async ({ expect }) => { 60 | const schema = { 61 | type: "object", 62 | properties: { 63 | readOnly: { 64 | type: "string", 65 | readOnly: true, 66 | }, 67 | }, 68 | }; 69 | 70 | const result = convert(schema); 71 | 72 | const expected = { 73 | $schema: "http://json-schema.org/draft-04/schema#", 74 | type: "object", 75 | properties: { 76 | readOnly: { 77 | type: "string", 78 | }, 79 | }, 80 | }; 81 | expect(result).toEqual(expected); 82 | }); 83 | 84 | it("remove writeOnly by default", async ({ expect }) => { 85 | const schema = { 86 | type: "object", 87 | properties: { 88 | test: { 89 | type: "string", 90 | writeOnly: true, 91 | }, 92 | }, 93 | }; 94 | 95 | const result = convert(schema); 96 | 97 | const expected = { 98 | $schema: "http://json-schema.org/draft-04/schema#", 99 | type: "object", 100 | properties: { 101 | test: { 102 | type: "string", 103 | }, 104 | }, 105 | }; 106 | 107 | expect(result).toEqual(expected); 108 | }); 109 | 110 | it("remove xml by default", async ({ expect }) => { 111 | const schema = { 112 | type: "object", 113 | properties: { 114 | foo: { 115 | type: "string", 116 | xml: { 117 | attribute: true, 118 | }, 119 | }, 120 | }, 121 | }; 122 | 123 | const result = convert(schema); 124 | 125 | const expected = { 126 | $schema: "http://json-schema.org/draft-04/schema#", 127 | type: "object", 128 | properties: { 129 | foo: { 130 | type: "string", 131 | }, 132 | }, 133 | }; 134 | 135 | expect(result).toEqual(expected); 136 | }); 137 | 138 | it("remove externalDocs by default", async ({ expect }) => { 139 | const schema = { 140 | type: "object", 141 | properties: { 142 | foo: { 143 | type: "string", 144 | }, 145 | }, 146 | externalDocs: { 147 | url: "http://foo.bar", 148 | }, 149 | }; 150 | 151 | const result = convert(schema); 152 | 153 | const expected = { 154 | $schema: "http://json-schema.org/draft-04/schema#", 155 | type: "object", 156 | properties: { 157 | foo: { 158 | type: "string", 159 | }, 160 | }, 161 | }; 162 | 163 | expect(result).toEqual(expected); 164 | }); 165 | 166 | it("remove example by default", async ({ expect }) => { 167 | const schema = { 168 | type: "string", 169 | example: "foo", 170 | }; 171 | 172 | const result = convert(schema); 173 | 174 | const expected = { 175 | $schema: "http://json-schema.org/draft-04/schema#", 176 | type: "string", 177 | }; 178 | 179 | expect(result).toEqual(expected); 180 | }); 181 | 182 | it("remove deprecated by default", async ({ expect }) => { 183 | const schema = { 184 | type: "string", 185 | deprecated: true, 186 | }; 187 | 188 | const result = convert(schema); 189 | 190 | const expected = { 191 | $schema: "http://json-schema.org/draft-04/schema#", 192 | type: "string", 193 | }; 194 | 195 | expect(result).toEqual(expected); 196 | }); 197 | 198 | it("retaining fields", async ({ expect }) => { 199 | const schema = { 200 | type: "object", 201 | properties: { 202 | readOnly: { 203 | type: "string", 204 | readOnly: true, 205 | example: "foo", 206 | }, 207 | anotherProp: { 208 | type: "object", 209 | properties: { 210 | writeOnly: { 211 | type: "string", 212 | writeOnly: true, 213 | }, 214 | }, 215 | }, 216 | }, 217 | discriminator: "bar", 218 | }; 219 | 220 | const result = convert(schema, { keepNotSupported: ["readOnly", "discriminator"] }); 221 | 222 | const expected = { 223 | $schema: "http://json-schema.org/draft-04/schema#", 224 | type: "object", 225 | properties: { 226 | readOnly: { 227 | type: "string", 228 | readOnly: true, 229 | }, 230 | anotherProp: { 231 | type: "object", 232 | properties: { 233 | writeOnly: { 234 | type: "string", 235 | }, 236 | }, 237 | }, 238 | }, 239 | discriminator: "bar", 240 | }; 241 | 242 | expect(result).toEqual(expected); 243 | }); 244 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "outDir": "dist", 5 | "target": "es2020", 6 | "allowJs": true, 7 | "allowSyntheticDefaultImports": true, 8 | "baseUrl": "src", 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "inlineSourceMap": false, 12 | "lib": ["ES2020"], 13 | "listEmittedFiles": false, 14 | "listFiles": false, 15 | "moduleResolution": "node", 16 | "noFallthroughCasesInSwitch": true, 17 | "pretty": true, 18 | "resolveJsonModule": true, 19 | "rootDir": "src", 20 | "strict": true, 21 | "noUnusedParameters": true, 22 | "noImplicitAny": false, 23 | "sourceMap": true 24 | }, 25 | "compileOnSave": false, 26 | "exclude": ["node_modules", "dist", "coverage", "bin"], 27 | "include": ["src"] 28 | } 29 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | watch: false, 7 | threads: false, 8 | isolate: false, 9 | reporters: "verbose", 10 | }, 11 | esbuild: { 12 | target: "node10", 13 | }, 14 | }); 15 | --------------------------------------------------------------------------------