Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: wework/json-schema-to-openapi-schema
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: openapi-contrib/json-schema-to-openapi-schema
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Able to merge. These branches can be automatically merged.

Commits on Feb 9, 2019

  1. Adds draft 6 examples

    sixlive committed Feb 9, 2019

    Verified

    This commit was signed with the committer’s verified signature.
    sixlive TJ Miller
    Copy the full SHA
    db6fadb View commit details

Commits on Feb 18, 2019

  1. Verified

    This commit was signed with the committer’s verified signature.
    sixlive TJ Miller
    Copy the full SHA
    04be69f View commit details

Commits on Jul 11, 2019

  1. Update index.js

    sixlive authored Jul 11, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    441c117 View commit details

Commits on Oct 4, 2019

  1. Added changelog for v0.4.0

    Phil Sturgeon committed Oct 4, 2019
    Copy the full SHA
    f76bd2f View commit details

Commits on Oct 5, 2019

  1. Create FUNDING.yml

    Phil Sturgeon authored Oct 5, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b3d2c28 View commit details
  2. chore: added release action

    Phil Sturgeon committed Oct 5, 2019
    Copy the full SHA
    4cba8ad View commit details
  3. chore: tweak package

    Phil Sturgeon committed Oct 5, 2019
    Copy the full SHA
    878565f View commit details
  4. chore: remove package-lock.json

    Phil Sturgeon committed Oct 5, 2019
    Copy the full SHA
    32e7f14 View commit details
  5. chore: update contributor link

    Phil Sturgeon committed Oct 5, 2019
    Copy the full SHA
    27620d7 View commit details

Commits on Oct 6, 2019

  1. feat: implement dereferencing

    P0lip committed Oct 6, 2019

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    P0lip Jakub Rożek
    Copy the full SHA
    afdc901 View commit details

Commits on Oct 7, 2019

  1. Merge pull request #3 from P0lip/master

    feat: implement dereferencing
    Phil Sturgeon authored Oct 7, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ca13b7a View commit details

Commits on Nov 7, 2019

  1. Copy the full SHA
    71bef7b View commit details

Commits on Jan 20, 2020

  1. attribution and consistency

    Phil Sturgeon committed Jan 20, 2020
    Copy the full SHA
    61f4f07 View commit details
  2. improved test and publish

    Phil Sturgeon committed Jan 20, 2020
    Copy the full SHA
    010956b View commit details
  3. oops wrong date

    Phil Sturgeon committed Jan 20, 2020
    Copy the full SHA
    35f865a View commit details
  4. Merge pull request #5 from noc7c9/master

    Fix issue with falsy const values not triggering the rewrite
    Phil Sturgeon authored Jan 20, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ed38775 View commit details
  5. new workflows

    Phil Sturgeon committed Jan 20, 2020
    Copy the full SHA
    4146a27 View commit details
  6. thanks stoplight

    Phil Sturgeon committed Jan 20, 2020
    Copy the full SHA
    010c175 View commit details

Commits on Feb 24, 2020

  1. added scope to readme

    fixes #6
    Phil Sturgeon committed Feb 24, 2020
    Copy the full SHA
    623d88c View commit details

Commits on Mar 23, 2020

  1. BREAKING CHANGE: drop support for nodejs v8

    * 1.0.1
    
    * github action > circleci
    
    also treeware and semantic release
    
    * chore: updated mocha
    
    * chore: update nyc
    
    * chore: add semnatic release
    
    * allow locks
    
    * chore: never any nodejs v8 support
    
    Co-authored-by: Phil Sturgeon <me@philsturgeon.uk>
    philsturgeon and Phil Sturgeon authored Mar 23, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    83c4599 View commit details

Commits on Apr 15, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d2c23e7 View commit details

Commits on Apr 16, 2020

  1. chore: updated stoplight/yaml

    Phil Sturgeon committed Apr 16, 2020
    Copy the full SHA
    7a107e7 View commit details

Commits on Jul 21, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e467aa5 View commit details

Commits on Mar 2, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8d56737 View commit details

Commits on Mar 17, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    631342c View commit details

Commits on Jun 3, 2021

  1. fix: do not swallow errors from dereferencing (#22)

    BREAKING CHANGE: derefencing errors are thrown
    taras authored Jun 3, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f6cf737 View commit details

Commits on Jul 29, 2021

  1. feat: add command line to convert files (#24)

    Co-authored-by: Tremper, Diego (ESI) <diego.tremper@adp.com>
    diegotremper and Tremper, Diego (ESI) authored Jul 29, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b0df26e View commit details

Commits on Dec 29, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    90c99dc View commit details
  2. Copy the full SHA
    29b2a26 View commit details
  3. chore: test against latest lts only

    This library is becoming less important with OAS3.1 being actual JSON Schema and with the frequency of Node releases I cant a) keep changing this matrix, or b) justify wasting compute resources on checking them all all the time. LMK if there's a problem in a particular version and we'll revert if it becomes an issue.
    philsturgeon committed Dec 29, 2021
    Copy the full SHA
    608b026 View commit details
  4. Create LICENSE

    philsturgeon authored Dec 29, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    de8339f View commit details
  5. chore: release fix main

    philsturgeon committed Dec 29, 2021
    Copy the full SHA
    84cfb28 View commit details

Commits on Jul 29, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    dfd52c0 View commit details

Commits on Aug 2, 2022

  1. BREAKING: Native typescript support, circular references fix

    * Rewrote in typescript, created esm/cjs exports, brought in library, fixed circular references, added tests, changed test suite to vitest
    
    * working top level dereferencing
    
    * fix tests and filter if/then
    jonluca authored Aug 2, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    18393ac View commit details

Commits on Aug 3, 2022

  1. feat: windows test bug, add support for derefencing definitions (#30)

    * Clean up type imports, fix release scripts
    
    * removed dependency, only test on greater than node 14, added lint checks
    
    * test on windows
    
    * test on windows
    
    * test on windows
    
    * test on windows
    
    * support definitions conversions
    
    * fix typescript complaints in tests
    jonluca authored Aug 3, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3544cdb View commit details

Commits on Aug 5, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    315747a View commit details
  2. fix(cli): cli was broken due to bin not included in dist (#33)

    * Add documentation, clean up some types, small fixes
    
    * re-add binn
    jonluca authored Aug 5, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    eaab2be View commit details

Commits on Aug 8, 2022

  1. fix: add suffix to esm import (#35)

    * fix(esm): add suffix to import
    
    * fix(modules): Change module resolution to node16 to prevent esm import errors, change types library
    
    * fix(nested-definitions): add definitions availability for nested definitions
    jonluca authored Aug 8, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    499e9f5 View commit details

Commits on Aug 19, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    2e5dabf View commit details

Commits on Aug 20, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    538905f View commit details

Commits on Nov 7, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e5e74e7 View commit details

Commits on Mar 28, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ade6d2f View commit details

Commits on Nov 8, 2023

  1. fix: conversion for anyOf type: null to nullable: true (#44)

    Co-authored-by: Eva Arnika Varga <arnika@letsdothis.com>
    jonluca and arnikaeva authored Nov 8, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9318da1 View commit details

Commits on Nov 11, 2023

  1. BREAKING CHANGE: Move to yarn 4 and only output a cjs build, drop (of…

    …ficial) support for node 14, bump json-scheme-walker version and openapi types version (#45)
    
    * chore(versions): move to yarn 4, drop release for node 14, only output cjs
    
    BREAKING CHANGE: Move to yarn 4 and only output a cjs build, drop support for node 14
    
    * update build
    jonluca authored Nov 11, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5b05d98 View commit details

Commits on Dec 11, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6cc5fa8 View commit details

Commits on Jun 2, 2024

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    1056a81 View commit details
  2. Verified

    This commit was signed with the committer’s verified signature.
    jonluca JonLuca De Caro
    Copy the full SHA
    8449ab0 View commit details

Commits on Nov 19, 2024

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    4bed708 View commit details
  2. fix(lockfile): fix lockfile

    jonluca committed Nov 19, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    jonluca JonLuca De Caro
    Copy the full SHA
    358f8c6 View commit details
  3. Verified

    This commit was signed with the committer’s verified signature.
    jonluca JonLuca De Caro
    Copy the full SHA
    e48fcdf View commit details
Showing with 7,730 additions and 5,006 deletions.
  1. +0 −52 .circleci/config.yml
  2. +12 −0 .editorconfig
  3. +0 −26 .eslintrc.json
  4. +24 −0 .github/workflows/release.yml
  5. +28 −0 .github/workflows/test.yml
  6. +3 −1 .gitignore
  7. +1 −0 .prettierignore
  8. +934 −0 .yarn/releases/yarn-4.5.1.cjs
  9. +3 −0 .yarnrc.yml
  10. +18 −1 CHANGELOG.md
  11. +21 −0 LICENSE
  12. +64 −32 README.md
  13. +26 −0 bin/help-text.json
  14. +107 −0 bin/json-schema-to-openapi-schema.js
  15. +63 −0 eslint.config.mjs
  16. +0 −194 index.js
  17. +0 −3,477 package-lock.json
  18. +65 −20 package.json
  19. +43 −0 src/const.ts
  20. +340 −0 src/index.ts
  21. +20 −0 src/types.ts
  22. +63 −0 test/__snapshots__/circular_schema.test.ts.snap
  23. +52 −0 test/__snapshots__/dereference_schema.test.ts.snap
  24. +0 −21 test/array-items.test.js
  25. +17 −0 test/array-items.test.ts
  26. +36 −0 test/circular_schema.test.ts
  27. +0 −55 test/clone_schema.test.js
  28. +52 −0 test/clone_schema.test.ts
  29. +106 −110 test/{combination_keywords.test.js → combination_keywords.test.ts}
  30. +0 −24 test/complex_schemas.test.js
  31. +22 −0 test/complex_schemas.test.ts
  32. +0 −21 test/const.test.js
  33. +35 −0 test/const.test.ts
  34. +28 −0 test/default-null.test.ts
  35. +314 −0 test/dereference_schema.test.ts
  36. +14 −0 test/examples.test.ts
  37. +0 −25 test/exclusiveMinMax.test.js
  38. +22 −0 test/exclusiveMinMax.test.ts
  39. +5 −0 test/fixtures/definitions.yaml
  40. +0 −9 test/helpers.js
  41. +7 −0 test/helpers.ts
  42. +0 −24 test/if-then-else.test.js
  43. +94 −0 test/if-then-else.test.ts
  44. +0 −21 test/invalid_types.test.js
  45. +23 −0 test/invalid_types.test.ts
  46. +0 −29 test/items.test.js
  47. +26 −0 test/items.test.ts
  48. +0 −51 test/nullable.test.js
  49. +214 −0 test/nullable.test.ts
  50. +0 −35 test/pattern_properties.test.js
  51. +32 −0 test/pattern_properties.test.ts
  52. +43 −46 test/{properties.test.js → properties.test.ts}
  53. +35 −38 test/{readonly_writeonly.test.js → readonly_writeonly.test.ts}
  54. +32 −0 test/rewrite_as_extensions.test.ts
  55. +17 −17 test/schemas/address/json-schema.json
  56. +26 −26 test/schemas/address/openapi.json
  57. +105 −120 test/schemas/basic/json-schema.json
  58. +114 −126 test/schemas/basic/openapi.json
  59. +36 −36 test/schemas/calendar/json-schema.json
  60. +35 −35 test/schemas/calendar/openapi.json
  61. +45 −0 test/schemas/circular/json-schema.json
  62. +45 −0 test/schemas/circular/openapi-circular.json
  63. +45 −0 test/schemas/circular/openapi.json
  64. +56 −0 test/schemas/events/json-schema.json
  65. +58 −0 test/schemas/events/openapi.json
  66. +81 −81 test/schemas/example2/json-schema.json
  67. +79 −79 test/schemas/example2/openapi.json
  68. +115 −127 test/schemas/invalid/json-schema.json
  69. +0 −47 test/subschema.test.js
  70. +46 −0 test/subschema.test.ts
  71. +10 −0 test/tsconfig.json
  72. +56 −0 test/type-array-split.test.ts
  73. +28 −0 tsconfig.json
  74. +13 −0 vite.config.mjs
  75. +3,676 −0 yarn.lock
52 changes: 0 additions & 52 deletions .circleci/config.yml

This file was deleted.

12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
charset = utf-8

[*.{ts,js,yaml,scenario,md}]
indent_style = space
indent_size = 2

[*.{ts,js}]
trim_trailing_whitespace = true
insert_final_newline = true
26 changes: 0 additions & 26 deletions .eslintrc.json

This file was deleted.

24 changes: 24 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Release npm package

on:
push:
branches:
- main

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
cache: 'yarn'
- run: yarn install --immutable
- run: yarn build
- run: yarn test
- run: npx semantic-release --branches main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
28 changes: 28 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Tests

on: push

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- 20
- 22
- latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
- name: yarn install, build, and test
run: |
yarn install --immutable
yarn build
yarn lint
yarn test
env:
CI: true
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@ build.sh
.coveralls.yml
.node-version
.nyc_output
yarn.lock
resolved.yaml

# Logs
@@ -35,3 +34,6 @@ jspm_packages

# Optional REPL history
.node_repl_history
dist
.idea
.yarn/install-state.gz
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.yarn
934 changes: 934 additions & 0 deletions .yarn/releases/yarn-4.5.1.cjs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.5.1.cjs
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [1.0.0] - 2020-01-20

### Changed

- Moved over to the `@openapi-contrib` NPM organization.

## [0.4.0] - 2019-10-04

### Added

- Take the first JSON Schema `example` and put in OpenAPI Schema Object `example`

## [0.3.0] - 2018-12-18

### Added

- Create empty items, as it must always be present for type: array
- Rewrite exclusiveMinimum/exclusiveMaximum
- Rewrite if/then/else as oneOf + allOf
- Rewrite const as single element enum

## [0.2.0] - 2018-05-10

### Fixed

- Implemented [@cloudflare/json-schema-walker] to make sure all subschemas are
processed

[@cloudflare/json-schema-walker]: https://github.com/cloudflare/json-schema-tools#cloudflarejson-schema-walker

## [0.1.1] - 2018-04-09

### Added

- Convert `dependencies` to an allOf + oneOf OpenAPI-valid equivalent
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Phil Sturgeon <phil@apisyouwonthate.com>

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:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

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.
96 changes: 64 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
# JSON Schema to OpenAPI Schema

A little NodeJS package to convert JSON Schema to [OpenAPI Schema Objects](https://swagger.io/specification/#schemaObject).
A little NodeJS package to convert JSON Schema to a [OpenAPI Schema Object](http://spec.openapis.org/oas/v3.0.3.html#schema-object).

[![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)

## Features

* converts JSON Schema Draft 00 Wright (a.k.a draft v5) to OpenAPI 3.0 Schema Object
* switches `type: ['foo', 'null']` to `type: foo` and `nullable: true`
* supports deep structures with nested `allOf`s etc.
* switches `patternProperties` to `x-patternProperties`
* converts `dependencies` to an allOf + oneOf OpenAPI-valid equivalent
- converts JSON Schema Draft 04 to OpenAPI 3.0 Schema Object
- switches `type: ['foo', 'null']` to `type: foo` and `nullable: true`
- supports deep structures with nested `allOf`s etc.
- switches `patternProperties` to `x-patternProperties`
- converts `dependencies` to an allOf + oneOf OpenAPI-valid equivalent

## Installation

```
npm install --save json-schema-to-openapi-schema
```shell
npm install --save @openapi-contrib/json-schema-to-openapi-schema
```

Requires NodeJS v10 or greater.

## Usage

Here's a small example to get the idea:

```js
const toOpenApi = require('json-schema-to-openapi-schema');
const convert = require('@openapi-contrib/json-schema-to-openapi-schema');

const schema = {
'$schema': 'http://json-schema.org/draft-04/schema#',
type: ['string', 'null'],
format: 'date-time',
$schema: 'http://json-schema.org/draft-04/schema#',
type: ['string', 'null'],
format: 'date-time',
};

const convertedSchema = toOpenApi(schema);

console.log(convertedSchema);
(async () => {
const convertedSchema = await convert(schema);
console.log(convertedSchema);
})();
```

The example prints out
@@ -44,8 +49,6 @@ The example prints out
}
```

**NOTE**: `$ref`s are not dereferenced. Use a dereferencer such as [json-schema-ref-parser](https://www.npmjs.com/package/json-schema-ref-parser) prior to using this package.

### Options

The function accepts `options` object as the second argument.
@@ -54,28 +57,50 @@ The function accepts `options` object as the second argument.

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`.

#### `dereference` (boolean)

If set to `true`, all local and remote references (http/https and file) $refs will be dereferenced. Defaults to `false`.

#### `convertUnreferencedDefinitions` (boolean)

Defaults to true.

If a schema had a definitions property (which is valid in JSONSchema), and only some of those entries are referenced, we'll still try and convert the remaining definitions to OpenAPI. If you do not want this behavior, set this to `false`.

#### `dereferenceOptions` (object = $RefParser.Options)

Options to pass to the dereferencer (@apidevtools/json-schema-ref-parser). To prevent circular references, pass `{ dereference: { circular: 'ignore' } }`.

## Command Line

```sh
Usage:
json-schema-to-openapi-schema <command> [options] <file>

Commands:
convert Converts JSON Schema Draft 04 to OpenAPI 3.0 Schema Object

Options:
-h, --help Show help for any command
-v, --version Output the CLI version number
-d, --dereference If set all local and remote references (http/https and file) $refs will be dereferenced
```

## Why?

OpenAPI is often described as an extension of JSON Schema, but both specs have changed over time and grown independently. OpenAPI v2 was based on JSON Schema draft v4 with a long list of deviations, but OpenAPI v3 shrank that list, upping their support to draft v4 and making the list of discrepancies shorter. Despite OpenAPI v3 closing the gap, the issue of JSON Schema divergence has not been resolved fully.
OpenAPI is often described as an extension of JSON Schema, but both specs have changed over time and grown independently. OpenAPI v2 was based on JSON Schema draft v4 with a long list of deviations, but OpenAPI v3 shrank that list, upping their support to draft v4 and making the list of discrepancies shorter. This has been solved for OpenAPI v3.1, but for those using OpenAPI v3.0, you can use this tool to solve [the divergence](https://apisyouwonthate.com/blog/openapi-and-json-schema-divergence).

![Diagram showing data model (the objects, payload bodies, etc) and service model (endpoints, headers, metadata, etc)](https://cdn-images-1.medium.com/max/1600/0*hijIL-3Xa5EFZ783.png)

Carefully writing JSON Schema for your data model kiiiinda works, but it is possible to write JSON Schema that creates invalid OpenAPI, and vice versa. For more on this, read the article [_OpenAPI and JSON Schema Divergence_](https://blog.apisyouwonthate.com/openapi-and-json-schema-divergence-part-1-1daf6678d86e).

This tool sets out to allow folks to convert from JSON Schema (their one source of truth for everything) to OpenAPI (a thing for HTML docs and making SDKs).

## Versions

- **From:** [JSON Schema Draft v5 †](http://json-schema.org/specification-links.html#draft-5)
- **To:** [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md)
- **To:** [OpenAPI 3.0](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md)

_† Draft v5 is also known as Draft Wright 00, as the drafts are often named after the author, and this was the first one by A. Wright. Amongst other things, draft v5 aimed to rewrite the meta files, but the experiment failed, meaning we need to continue to use the draft v4 metafiles. Ugh._

## TODO

- [ ] Support later JSON Schema drafts via [cloudflare/json-schema-transformer] when it adds that functionality

## Converting Back

To convert the other way, check out [openapi-schema-to-json-schema], which this package was based on.
@@ -88,14 +113,21 @@ To run the test-suite:
npm test
```

## Credits
## Treeware

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.

## Thanks

- [mikunn] for creating [openapi-schema-to-json-schema] which this is based on
- [Phil Sturgeon] for flipping that conversion script about face
- [Stoplight][] for [donating time and effort](https://stoplight.io/blog/companies-supporting-open-source/) to this project, and many more.
- [mikunn][] for creating [openapi-schema-to-json-schema] which this is based on.
- [Phil Sturgeon][] for flipping that conversion script about face.
- [WeWork][] for giving this a home for a while.
- [All Contributors][link-contributors]

[mikunn]: https://github.com/mikunn
[Phil Sturgeon]: https://github.com/philsturgeon
[openapi-schema-to-json-schema]: https://github.com/mikunn/openapi-schema-to-json-schema
[link-contributors]: https://github.com/wework/json-schema-to-openapi-schema/graphs/contributors
[cloudflare/json-schema-transformer]: https://github.com/cloudflare/json-schema-tools/blob/master/workspaces/json-schema-transform/README.md
[wework]: https://github.com/wework
[stoplight]: https://stoplight.io/
[phil sturgeon]: https://github.com/philsturgeon
[openapi-schema-to-json-schema]: https://github.com/openapi-contrib/openapi-schema-to-json-schema
[link-contributors]: https://github.com/openapi-contrib/json-schema-to-openapi-schema/graphs/contributors
26 changes: 26 additions & 0 deletions bin/help-text.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"default": [
"Usage:",
" json-schema-to-openapi-schema <command> [options] <file>",
"",
"Commands:",
" convert Converts JSON Schema Draft 04 to OpenAPI 3.0 Schema Object",
"",
"Options:",
" -h, --help Show help for any command",
" -v, --version Output the CLI version number",
" -d, --dereference If set all local and remote references (http/https and file) $refs will be dereferenced",
""
],
"convert": [
"Converts JSON Schema Draft 04 to OpenAPI 3.0 Schema Object.",
"Returns a non-zero exit code if conversion fails.",
"",
"Usage:",
" json-schema-to-openapi-schema convert [options] <file>",
"",
"Options:",
" -d, --dereference If set all local and remote references (http/https and file) $refs will be dereferenced",
""
]
}
107 changes: 107 additions & 0 deletions bin/json-schema-to-openapi-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env node
'use strict';

const yargs = require('yargs');
const converter = require('../dist/index.js').default;
const helpText = require('./help-text.json');
const fs = require('fs');
const readFileAsync = require('util').promisify(fs.readFile);

(async function main() {
let args = parseArgs();
let command = args.command;
let file = args.file;
let options = args.options;

if (options.help) {
// Show help text and exit
console.log(getHelpText(command));
process.exit(0);
} else if (command === 'convert' && file) {
// Convert the JSON Schema file
await convert(file, options);
} else {
// Invalid args. Show help text and exit with non-zero
console.error('Error: Invalid arguments\n');
console.error(getHelpText(command));
process.exit(1);
}
})();

/**
* Parses the command-line arguments
*
* @returns {object} - The parsed arguments
*/
function parseArgs() {
// Configure the argument parser
yargs
.option('d', {
alias: 'dereference',
type: 'boolean',
default: false,
})
.option('h', {
alias: 'help',
type: 'boolean',
});

// Show the version number on "--version" or "-v"
yargs.version().alias('v', 'version');

// Disable the default "--help" behavior
yargs.help(false);

// Parse the command-line arguments
let args = yargs.argv;

// Normalize the parsed arguments
let parsed = {
command: args._[0],
file: args._[1],
options: {
dereference: args.dereference,
help: args.help,
},
};

return parsed;
}

/**
* Convert an JSON Schema to OpenAPI schema
*
* @param {string} file - The path of the file to convert
* @param {object} options - Conversion options
*/
async function convert(file, options) {
try {
const schema = await readFileAsync(file, 'utf8');
const converted = await converter(JSON.parse(schema), options);
console.log(JSON.stringify(converted));
} catch (error) {
errorHandler(error);
}
}

/**
* Returns the help text for the specified command
*
* @param {string} [commandName] - The command to show help text for
* @returns {string} - the help text
*/
function getHelpText(commandName) {
let lines = helpText[commandName] || helpText.default;
return lines.join('\n');
}

/**
* Writes error information to stderr and exits with a non-zero code
*
* @param {Error} err
*/
function errorHandler(err) {
let errorMessage = process.env.DEBUG ? err.stack : err.message;
console.error(errorMessage);
process.exit(1);
}
63 changes: 63 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import eslint from '@eslint/js';
import prettierPlugin from 'eslint-plugin-prettier';
import unusedImportsPlugin from 'eslint-plugin-unused-imports';

import prettierExtends from 'eslint-config-prettier';
import { fixupPluginRules } from '@eslint/compat';
import globals from 'globals';
import tseslint from 'typescript-eslint';

const globalToUse = {
...globals.browser,
...globals.serviceworker,
...globals.es2021,
...globals.worker,
...globals.node,
};

export default tseslint.config({
extends: [
{
ignores: ['dist/**', 'bin/**'],
},
prettierExtends,
eslint.configs.recommended,
...tseslint.configs.recommended,
],
plugins: {
prettierPlugin,
'unused-imports': fixupPluginRules(unusedImportsPlugin),
},
rules: {
indent: [
'error',
'tab',
{
SwitchCase: 1,
},
],
'linebreak-style': [
'error',
process.platform === 'win32' ? 'windows' : 'unix',
],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
},
],
'@typescript-eslint/no-explicit-any': 'off',
},
languageOptions: {
globals: globalToUse,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
});
194 changes: 0 additions & 194 deletions index.js

This file was deleted.

3,477 changes: 0 additions & 3,477 deletions package-lock.json

This file was deleted.

85 changes: 65 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,67 @@
{
"name": "json-schema-to-openapi-schema",
"version": "0.3.0",
"description": "Converts a JSON Schema to OpenAPI Schema Object",
"main": "index.js",
"scripts": {
"test": "nyc --reporter=html --reporter=text mocha",
"coverage": "nyc report --reporter=text-lcov | coveralls"
},
"repository": "github:wework/json-schema-to-openapi-schema",
"author": "Phil Sturgeon <phil.sturgeon@wework.com>",
"license": "MIT",
"devDependencies": {
"coveralls": "^3.0.0",
"mocha": "^5.0.0",
"nyc": "^11.6.0",
"should": "^13.2.0"
},
"dependencies": {
"@cloudflare/json-schema-walker": "^0.1.1"
}
"name": "@openapi-contrib/json-schema-to-openapi-schema",
"version": "0.0.0-development",
"description": "Converts a JSON Schema to OpenAPI Schema Object",
"files": [
"bin",
"dist",
"CHANGELOG.md",
"LICENSE",
"package.json"
],
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
},
"bin": "bin/json-schema-to-openapi-schema.js",
"types": "dist/index.d.ts",
"main": "dist/index.js",
"module": "dist/index.mjs",
"scripts": {
"prepublish": "yarn build",
"build": "rimraf dist && tsup src/index.ts --format esm,cjs --dts --clean",
"lint": "eslint . && prettier -c src",
"lint:fix": "eslint . --fix && prettier -c src -w",
"typecheck": "tsc --noEmit",
"test": "vitest",
"coverage": "vitest --coverage"
},
"repository": "github:openapi-contrib/json-schema-to-openapi-schema",
"author": "OpenAPI Contrib",
"license": "MIT",
"engines": {
"node": ">=18"
},
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.2",
"json-schema-walker": "^2.0.0",
"openapi-types": "^12.1.3",
"yargs": "^17.7.2"
},
"devDependencies": {
"@eslint/compat": "^1.2.3",
"@eslint/js": "^9.15.0",
"@types/json-schema": "^7.0.15",
"c8": "^10.1.2",
"eslint": "^9.15.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-unused-imports": "^4.1.4",
"globals": "^15.12.0",
"nock": "^13.5.6",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"tsup": "^8.3.5",
"typescript": "^5.6.3",
"typescript-eslint": "^8.15.0",
"vitest": "^2.1.5"
},
"prettier": {
"singleQuote": true,
"useTabs": true
},
"packageManager": "yarn@4.5.1"
}
43 changes: 43 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// TODO: having definitions inside an oas3 schema isn't exactly valid,
// maybe it is an idea to extract and split them into multiple oas3 schemas and reference to them.
// For now leaving as is.
export const allowedKeywords = [
'$ref',
'definitions',
// From Schema
'title',
'multipleOf',
'maximum',
'exclusiveMaximum',
'minimum',
'exclusiveMinimum',
'maxLength',
'minLength',
'pattern',
'maxItems',
'minItems',
'uniqueItems',
'maxProperties',
'minProperties',
'required',
'enum',
'type',
'not',
'allOf',
'oneOf',
'anyOf',
'items',
'properties',
'additionalProperties',
'description',
'format',
'default',
'nullable',
'discriminator',
'readOnly',
'writeOnly',
'example',
'externalDocs',
'deprecated',
'xml',
];
340 changes: 340 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,340 @@
import type {
JSONSchema4,
JSONSchema6Definition,
JSONSchema7Definition,
} from 'json-schema';
import type { Options, SchemaType, SchemaTypeKeys } from './types';
import { Walker } from 'json-schema-walker';
import { allowedKeywords } from './const';
import type { OpenAPIV3 } from 'openapi-types';
import type { JSONSchema } from '@apidevtools/json-schema-ref-parser';

class InvalidTypeError extends Error {
constructor(message: string) {
super(message);
this.name = 'InvalidTypeError';
this.message = message;
}
}

const oasExtensionPrefix = 'x-';

const handleDefinition = async <T extends JSONSchema4 = JSONSchema4>(
def: JSONSchema7Definition | JSONSchema6Definition | JSONSchema4,
schema: T,
) => {
if (typeof def !== 'object') {
return def;
}

const type = def.type;
if (type) {
// Walk just the definitions types
const walker = new Walker<T>();
await walker.loadSchema(
{
definitions: schema['definitions'] || [],
...def,
$schema: schema['$schema'],
} as any,
{
dereference: true,
cloneSchema: true,
dereferenceOptions: {
dereference: {
circular: 'ignore',
},
},
},
);
await walker.walk(convertSchema, walker.vocabularies.DRAFT_07);
if ('definitions' in walker.rootSchema) {
delete (<any>walker.rootSchema).definitions;
}
return walker.rootSchema;
}
if (Array.isArray(def)) {
// if it's an array, we might want to reconstruct the type;
const typeArr = def;
const hasNull = typeArr.includes('null');
if (hasNull) {
const actualTypes = typeArr.filter((l) => l !== 'null');
return {
type: actualTypes.length === 1 ? actualTypes[0] : actualTypes,
nullable: true,
// this is incorrect but thats ok, we are in the inbetween phase here
} as JSONSchema7Definition | JSONSchema6Definition | JSONSchema4;
}
}

return def;
};

const convert = async <T extends object = JSONSchema4>(
schema: T,
options?: Options,
): Promise<OpenAPIV3.Document> => {
const walker = new Walker<T>();
const convertDefs = options?.convertUnreferencedDefinitions ?? true;
await walker.loadSchema(schema, options);
await walker.walk(convertSchema, walker.vocabularies.DRAFT_07);
// if we want to convert unreferenced definitions, we need to do it iteratively here
const rootSchema = walker.rootSchema as unknown as JSONSchema;
if (convertDefs && rootSchema?.definitions) {
for (const defName in rootSchema.definitions) {
const def = rootSchema.definitions[defName];
rootSchema.definitions[defName] = await handleDefinition(def, schema);
}
}
return rootSchema as OpenAPIV3.Document;
};

function stripIllegalKeywords(schema: SchemaType) {
if (typeof schema !== 'object') {
return schema;
}
delete schema['$schema'];
delete schema['$id'];
if ('id' in schema) {
delete schema['id'];
}
return schema;
}

function convertSchema(schema: SchemaType | undefined) {
if (!schema) {
return schema;
}
schema = stripIllegalKeywords(schema);
schema = convertTypes(schema);
schema = rewriteConst(schema);
schema = convertDependencies(schema);
schema = convertNullable(schema);
schema = rewriteIfThenElse(schema);
schema = rewriteExclusiveMinMax(schema);
schema = convertExamples(schema);

if (typeof schema['patternProperties'] === 'object') {
schema = convertPatternProperties(schema);
}

if (schema.type === 'array' && typeof schema.items === 'undefined') {
schema.items = {};
}

// should be called last
schema = convertIllegalKeywordsAsExtensions(schema);
return schema;
}
const validTypes = new Set([
'null',
'boolean',
'object',
'array',
'number',
'string',
'integer',
]);
function validateType(type: any) {
if (typeof type === 'object' && !Array.isArray(type)) {
// Refs are allowed because they fix circular references
if (type.$ref) {
return;
}
// this is a de-referenced circular ref
if (type.properties) {
return;
}
}
const types = Array.isArray(type) ? type : [type];
types.forEach((type) => {
if (type && !validTypes.has(type))
throw new InvalidTypeError('Type "' + type + '" is not a valid type');
});
}

function convertDependencies(schema: SchemaType) {
const deps = schema.dependencies;
if (typeof deps !== 'object') {
return schema;
}

// Turns the dependencies keyword into an allOf of oneOf's
// "dependencies": {
// "post-office-box": ["street-address"]
// },
//
// becomes
//
// "allOf": [
// {
// "oneOf": [
// {"not": {"required": ["post-office-box"]}},
// {"required": ["post-office-box", "street-address"]}
// ]
// }
//

delete schema['dependencies'];
if (!Array.isArray(schema.allOf)) {
schema.allOf = [];
}

for (const key in deps) {
const foo: (JSONSchema4 & JSONSchema6Definition) & JSONSchema7Definition = {
oneOf: [
{
not: {
required: [key],
},
},
{
required: [key, deps[key]].flat() as string[],
},
],
};
schema.allOf.push(foo);
}
return schema;
}

function convertNullable(schema: SchemaType) {
for (const key of ['oneOf', 'anyOf'] as const) {
const schemas = schema[key] as JSONSchema4[];
if (!schemas) continue;

if (!Array.isArray(schemas)) {
return schema;
}

const hasNullable = schemas.some((item) => item.type === 'null');

if (!hasNullable) {
return schema;
}

const filtered = schemas.filter((l) => l.type !== 'null');
for (const schemaEntry of filtered) {
schemaEntry.nullable = true;
}

schema[key] = filtered;
}

return schema;
}

function convertTypes(schema: SchemaType) {
if (typeof schema !== 'object') {
return schema;
}
if (schema.type === undefined) {
return schema;
}

validateType(schema.type);

if (Array.isArray(schema.type)) {
if (schema.type.includes('null')) {
schema.nullable = true;
}
const typesWithoutNull = schema.type.filter((type) => type !== 'null');
if (typesWithoutNull.length === 0) {
delete schema.type;
} else if (typesWithoutNull.length === 1) {
schema.type = typesWithoutNull[0];
} else {
delete schema.type;
schema.anyOf = typesWithoutNull.map((type) => ({ type }));
}
} else if (schema.type === 'null') {
delete schema.type;
schema.nullable = true;
}

return schema;
}

// "patternProperties did not make it into OpenAPI v3.0"
// https://github.com/OAI/OpenAPI-Specification/issues/687
function convertPatternProperties(schema: SchemaType) {
schema['x-patternProperties'] = schema['patternProperties'];
delete schema['patternProperties'];
schema.additionalProperties ??= true;
return schema;
}

// keywords (or property names) that are not recognized within OAS3 are rewritten into extensions.
function convertIllegalKeywordsAsExtensions(schema: SchemaType) {
const keys = Object.keys(schema) as SchemaTypeKeys[];
keys
.filter(
(keyword) =>
!keyword.startsWith(oasExtensionPrefix) &&
!allowedKeywords.includes(keyword),
)
.forEach((keyword: SchemaTypeKeys) => {
const key = `${oasExtensionPrefix}${keyword}` as keyof SchemaType;
schema[key] = schema[keyword];
delete schema[keyword];
});
return schema;
}

function convertExamples(schema: SchemaType) {
if (schema['examples'] && Array.isArray(schema['examples'])) {
schema['example'] = schema['examples'][0];
delete schema['examples'];
}

return schema;
}

function rewriteConst(schema: SchemaType) {
if (Object.hasOwnProperty.call(schema, 'const')) {
schema.enum = [schema.const];
delete schema.const;
}
return schema;
}

function rewriteIfThenElse(schema: SchemaType) {
if (typeof schema !== 'object') {
return schema;
}
/* @handrews https://github.com/OAI/OpenAPI-Specification/pull/1766#issuecomment-442652805
if and the *Of keywords
There is a really easy solution for implementations, which is that
if: X, then: Y, else: Z
is equivalent to
oneOf: [allOf: [X, Y], allOf: [not: X, Z]]
*/
if ('if' in schema && schema.if && schema.then) {
schema.oneOf = [
{ allOf: [schema.if, schema.then].filter(Boolean) },
{ allOf: [{ not: schema.if }, schema.else].filter(Boolean) },
];
delete schema.if;
delete schema.then;
delete schema.else;
}
return schema;
}

function rewriteExclusiveMinMax(schema: SchemaType) {
if (typeof schema.exclusiveMaximum === 'number') {
schema.maximum = schema.exclusiveMaximum;
(schema as JSONSchema4).exclusiveMaximum = true;
}
if (typeof schema.exclusiveMinimum === 'number') {
schema.minimum = schema.exclusiveMinimum;
(schema as JSONSchema4).exclusiveMinimum = true;
}
return schema;
}

export default convert;
20 changes: 20 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { JSONSchema } from '@apidevtools/json-schema-ref-parser';
import type { ParserOptions } from '@apidevtools/json-schema-ref-parser';

export type addPrefixToObject = {
[K in keyof JSONSchema as `x-${K}`]: JSONSchema[K];
};

export interface Options {
cloneSchema?: boolean;
dereference?: boolean;
convertUnreferencedDefinitions?: boolean;
dereferenceOptions?: ParserOptions | undefined;
}
type ExtendedJSONSchema = addPrefixToObject & JSONSchema;
export type SchemaType = ExtendedJSONSchema & {
example?: JSONSchema['examples'][number];
'x-patternProperties'?: JSONSchema['patternProperties'];
nullable?: boolean;
};
export type SchemaTypeKeys = keyof SchemaType;
63 changes: 63 additions & 0 deletions test/__snapshots__/circular_schema.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`converting circular/openapi.json without circular references turned off 1`] = `
{
"definitions": {
"child": {
"properties": {
"name": {
"type": "string",
},
"parents": {
"items": {
"properties": {
"children": {
"items": [Circular],
"type": "array",
},
"name": {
"type": "string",
},
},
},
"type": "array",
},
},
},
"parent": {
"properties": {
"children": {
"items": {
"properties": {
"name": {
"type": "string",
},
"parents": {
"items": [Circular],
"type": "array",
},
},
},
"type": "array",
},
"name": {
"type": "string",
},
},
},
"person": {
"properties": {
"name": {
"type": "string",
},
"spouse": {
"type": [Circular],
},
},
},
"thing": {
"$ref": "#/definitions/thing",
},
},
}
`;
52 changes: 52 additions & 0 deletions test/__snapshots__/dereference_schema.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`throws an error when dereferecing fails 1`] = `
{
"additionalProperties": false,
"definitions": {
"configvariable": {
"additionalProperties": false,
"properties": {
"default": {
"type": "string",
},
"name": {
"pattern": "^[A-Z_]+[A-Z0-9_]*$",
"type": "string",
},
"required": {
"default": true,
"type": "boolean",
},
},
"required": [
"name",
],
"type": "object",
},
"envVarName": {
"pattern": "^[A-Z_]+[A-Z0-9_]*$",
"type": "string",
},
},
"properties": {
"componentId": {
"pattern": "^(.*)$",
"title": "The component id Schema",
"type": "string",
},
"configurationTemplate": {
"items": {
"$ref": "#/definitions/configvariable",
},
"title": "The Configurationtemplate Schema",
"type": "array",
},
},
"required": [
"componentId",
],
"title": "Component Manifest Schema",
"type": "object",
}
`;
21 changes: 0 additions & 21 deletions test/array-items.test.js

This file was deleted.

17 changes: 17 additions & 0 deletions test/array-items.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import convert from '../src';

it('array-items', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'array',
};

const result = await convert(schema);

const expected = {
type: 'array',
items: {},
};

expect(result).toEqual(expected);
});
36 changes: 36 additions & 0 deletions test/circular_schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import convert from '../src';
import { getSchema } from './helpers';
const test = 'circular';

it(`converts ${test}/openapi.json`, async ({ expect }) => {
const schema = getSchema(test + '/json-schema.json');
const result = await convert(schema, {
dereference: true,
dereferenceOptions: { dereference: { circular: 'ignore' } },
});
const expected = getSchema(test + '/openapi.json');
expect(result).toEqual(expected);
});

it(`converting ${test}/openapi.json in place`, async ({ expect }) => {
const schema = getSchema(test + '/json-schema.json');
const result = await convert(schema, {
cloneSchema: false,
dereference: true,
dereferenceOptions: { dereference: { circular: 'ignore' } },
});
const expected = getSchema(test + '/openapi.json');
expect(schema).toEqual(result);
expect(result).toEqual(expected);
});

it(`converting ${test}/openapi.json without circular references turned off `, async ({
expect,
}) => {
const schema = getSchema(test + '/json-schema.json');
const result = await convert(schema, {
cloneSchema: false,
dereference: true,
});
expect(result).toMatchSnapshot();
});
55 changes: 0 additions & 55 deletions test/clone_schema.test.js

This file was deleted.

52 changes: 52 additions & 0 deletions test/clone_schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import convert from '../src';

it('cloning schema by default', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: ['string', 'null'],
};

const result = await convert(schema);

const expected = {
type: 'string',
nullable: true,
};

expect(result).toEqual(expected);
expect(result).not.toEqual(schema);
});

it('cloning schema with cloneSchema option', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: ['string', 'null'],
};

const result = await convert(schema, { cloneSchema: true });

const expected = {
type: 'string',
nullable: true,
};

expect(result).toEqual(expected);
expect(result).not.toEqual(schema);
});

it('direct schema modification', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: ['string', 'null'],
};

const result = await convert(schema, { cloneSchema: false });

const expected = {
type: 'string',
nullable: true,
};

expect(result).toEqual(expected);
expect(result).toEqual(schema);
});
216 changes: 106 additions & 110 deletions test/combination_keywords.test.js → test/combination_keywords.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
'use strict';
import convert from '../src';

const convert = require('../');
const should = require('should');

it('iterates allOfs and converts types', () => {
it('iterates allOfs and converts types', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
allOf: [
@@ -13,22 +10,22 @@ it('iterates allOfs and converts types', () => {
properties: {
foo: {
type: 'integer',
format: 'int64'
}
}
format: 'int64',
},
},
},
{
allOf: [
{
type: 'number',
format: 'double'
}
]
}
]
format: 'double',
},
],
},
],
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
allOf: [
@@ -38,25 +35,25 @@ it('iterates allOfs and converts types', () => {
properties: {
foo: {
type: 'integer',
format: 'int64'
}
}
format: 'int64',
},
},
},
{
allOf: [
{
type: 'number',
format: 'double'
}
]
}
]
format: 'double',
},
],
},
],
};

should(result).deepEqual(expected, 'iterated allOfs');
expect(result).toEqual(expected);
});

it('iterates anyOfs and converts types', () => {
it('iterates anyOfs and converts types', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
anyOf: [
@@ -66,9 +63,9 @@ it('iterates anyOfs and converts types', () => {
properties: {
foo: {
type: 'integer',
format: 'int64'
}
}
format: 'int64',
},
},
},
{
anyOf: [
@@ -77,16 +74,16 @@ it('iterates anyOfs and converts types', () => {
properties: {
bar: {
type: 'number',
format: 'double'
}
}
}
]
}
]
format: 'double',
},
},
},
],
},
],
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
anyOf: [
@@ -96,9 +93,9 @@ it('iterates anyOfs and converts types', () => {
properties: {
foo: {
type: 'integer',
format: 'int64'
}
}
format: 'int64',
},
},
},
{
anyOf: [
@@ -107,19 +104,19 @@ it('iterates anyOfs and converts types', () => {
properties: {
bar: {
type: 'number',
format: 'double'
}
}
}
]
}
]
format: 'double',
},
},
},
],
},
],
};

should(result).deepEqual(expected, 'anyOfs iterated');
expect(result).toEqual(expected);
});

it('iterates oneOfs and converts types', () => {
it('iterates oneOfs and converts types', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
oneOf: [
@@ -128,26 +125,26 @@ it('iterates oneOfs and converts types', () => {
required: ['foo'],
properties: {
foo: {
type: ['string', 'null']
}
}
type: ['string', 'null'],
},
},
},
{
oneOf: [
{
type: 'object',
properties: {
bar: {
type: ['string', 'null']
}
}
}
]
}
]
type: ['string', 'null'],
},
},
},
],
},
],
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
oneOf: [
@@ -157,9 +154,9 @@ it('iterates oneOfs and converts types', () => {
properties: {
foo: {
type: 'string',
nullable: true
}
}
nullable: true,
},
},
},
{
oneOf: [
@@ -168,48 +165,47 @@ it('iterates oneOfs and converts types', () => {
properties: {
bar: {
type: 'string',
nullable: true
}
}
}
]
}
]
nullable: true,
},
},
},
],
},
],
};

should(result).deepEqual(expected, 'oneOfs iterated');
expect(result).toEqual(expected);
});

it('converts types in not', () => {
it('converts types in not', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
not: {
type: ['string', 'null'],
minLength: 8
}
}
minLength: 8,
},
},
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
type: 'object',
properties: {
not: {
type: 'string',
nullable: true,
minLength: 8
}
}
minLength: 8,
},
},
};

should(result).deepEqual(expected, 'not handled');
expect(result).toEqual(expected);
});


it('nested combination keywords', () => {
it('nested combination keywords', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
anyOf: [
@@ -219,38 +215,38 @@ it('nested combination keywords', () => {
type: 'object',
properties: {
foo: {
type: ['string', 'null']
}
}
type: ['string', 'null'],
},
},
},
{
type: 'object',
properties: {
bar: {
type: ['integer', 'null']
}
}
}
]
type: ['integer', 'null'],
},
},
},
],
},
{
type: 'object',
properties: {
foo: {
type: 'string',
}
}
},
},
},
{
not: {
type: 'string',
example: 'foobar'
}
}
]
example: 'foobar',
},
},
],
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
anyOf: [
@@ -261,37 +257,37 @@ it('nested combination keywords', () => {
properties: {
foo: {
type: 'string',
nullable: true
}
}
nullable: true,
},
},
},
{
type: 'object',
properties: {
bar: {
type: 'integer',
nullable: true
}
}
}
]
nullable: true,
},
},
},
],
},
{
type: 'object',
properties: {
foo: {
type: 'string',
}
}
},
},
},
{
not: {
type: 'string',
example: 'foobar'
}
}
]
example: 'foobar',
},
},
],
};

should(result).deepEqual(expected, 'nested combination keywords');
expect(result).toEqual(expected);
});
24 changes: 0 additions & 24 deletions test/complex_schemas.test.js

This file was deleted.

22 changes: 22 additions & 0 deletions test/complex_schemas.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import convert from '../src';
import { getSchema } from './helpers';

['basic', 'address', 'calendar', 'events'].forEach((test) => {
it(`converts ${test}/openapi.json`, async ({ expect }) => {
const schema = getSchema(test + '/json-schema.json');
const result = await convert(schema);

const expected = getSchema(test + '/openapi.json');

expect(result).toEqual(expected);
});

it(`converting ${test}/openapi.json in place`, async ({ expect }) => {
const schema = getSchema(test + '/json-schema.json');
const result = await convert(schema, { cloneSchema: false });
const expected = getSchema(test + '/openapi.json');

expect(schema).toEqual(result);
expect(result).toEqual(expected);
});
});
21 changes: 0 additions & 21 deletions test/const.test.js

This file was deleted.

35 changes: 35 additions & 0 deletions test/const.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import convert from '../src';

it('const', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'string',
const: 'hello',
};

const result = await convert(schema);

const expected = {
type: 'string',
enum: ['hello'],
};

expect(result).toEqual(expected);
});

it('falsy const', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'boolean',
const: false,
};

const result = await convert(schema);

const expected = {
type: 'boolean',
enum: [false],
};

expect(result).toEqual(expected);
});
28 changes: 28 additions & 0 deletions test/default-null.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import convert from '../src';

it('supports default values of null', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
nullableStringWithDefault: {
default: null,
oneOf: [{ type: 'string' }, { type: 'null' }],
},
},
};

const result = await convert(schema);

const expected = {
type: 'object',
properties: {
nullableStringWithDefault: {
default: null,
oneOf: [{ type: 'string', nullable: true }],
},
},
};

expect(result).toEqual(expected);
});
314 changes: 314 additions & 0 deletions test/dereference_schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
import convert from '../src';
import { join } from 'path';
import nock from 'nock';
import * as path from 'path';

it('not dereferencing schema by default', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
properties: {
foo: {
$ref: '#/definitions/foo',
},
},
definitions: {
foo: ['string', 'null'],
},
};

const result = await convert(JSON.parse(JSON.stringify(schema)));

const expected: any = { ...schema };
if ('$schema' in expected) {
delete expected.$schema;
}
expected.definitions = {
foo: {
type: 'string',
nullable: true,
},
};

expect(result).toEqual(expected);
});

it('dereferencing schema with deference option', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: {
$ref: '#/definitions/foo',
},
definitions: {
foo: ['string', 'null'],
},
};

const result = await convert(schema, { dereference: true });

const expected = {
type: 'string',
nullable: true,
definitions: {
foo: { type: 'string', nullable: true },
},
};

expect(result).toEqual(expected);
});

it('dereferencing schema with deference option at root', async ({ expect }) => {
const schema = {
definitions: {
AgilityServerWebServicesSDKServerServiceSVCJSONGetNavigationMenu2PostRequest:
{
type: 'object',
additionalProperties: false,
properties: {
navigationMenuIdentity: {
$ref: '#/definitions/NavigationMenuIdentity',
},
sessionId: {
type: 'string',
},
},
required: [],
title:
'AgilityServerWebServicesSDKServerServiceSVCJSONGetNavigationMenu2PostRequest',
},
NavigationMenuIdentity: {
type: 'object',
additionalProperties: false,
properties: {
Id: {
type: 'string',
},
LastModifiedDate: {
type: 'string',
},
Name: {
type: 'string',
},
__type: {
type: 'string',
},
},
required: [],
title: 'NavigationMenuIdentity',
},
},
$ref: '#/definitions/AgilityServerWebServicesSDKServerServiceSVCJSONGetNavigationMenu2PostRequest',
};

const result = await convert(schema, { dereference: true });

const expected = {
type: 'object',
additionalProperties: false,
properties: {
navigationMenuIdentity: {
type: 'object',
additionalProperties: false,
properties: {
Id: {
type: 'string',
},
LastModifiedDate: {
type: 'string',
},
Name: {
type: 'string',
},
__type: {
type: 'string',
},
},
required: [],
title: 'NavigationMenuIdentity',
},
sessionId: {
type: 'string',
},
},
required: [],
title:
'AgilityServerWebServicesSDKServerServiceSVCJSONGetNavigationMenu2PostRequest',
definitions: {
AgilityServerWebServicesSDKServerServiceSVCJSONGetNavigationMenu2PostRequest:
{
type: 'object',
additionalProperties: false,
properties: {
navigationMenuIdentity: {
type: 'object',
additionalProperties: false,
properties: {
Id: {
type: 'string',
},
LastModifiedDate: {
type: 'string',
},
Name: {
type: 'string',
},
__type: {
type: 'string',
},
},
required: [],
title: 'NavigationMenuIdentity',
},
sessionId: {
type: 'string',
},
},
required: [],
title:
'AgilityServerWebServicesSDKServerServiceSVCJSONGetNavigationMenu2PostRequest',
},
NavigationMenuIdentity: {
type: 'object',
additionalProperties: false,
properties: {
Id: {
type: 'string',
},
LastModifiedDate: {
type: 'string',
},
Name: {
type: 'string',
},
__type: {
type: 'string',
},
},
required: [],
title: 'NavigationMenuIdentity',
},
},
};

expect(result).toEqual(expected);
});

// skip until nock supports native fetch https://github.com/nock/nock/issues/2397
it.skip('dereferencing schema with remote http and https references', async ({
expect,
}) => {
nock('http://foo.bar/')
.get('/schema.yaml')
.replyWithFile(200, join(__dirname, 'fixtures/definitions.yaml'), {
'Content-Type': 'application/yaml',
});

nock('https://baz.foo/')
.get('/schema.yaml')
.replyWithFile(200, join(__dirname, 'fixtures/definitions.yaml'), {
'Content-Type': 'application/yaml',
});

const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
allOf: [
{ $ref: 'http://foo.bar/schema.yaml#/definitions/foo' },
{ $ref: 'https://baz.foo/schema.yaml#/definitions/bar' },
],
};

const result = await convert(schema, { dereference: true });

const expected = {
allOf: [{ type: 'string' }, { type: 'number' }],
};

expect(result).toEqual(expected);
});

it('dereferencing schema with file references', async ({ expect }) => {
const fileRef = join(__dirname, 'fixtures/definitions.yaml#/definitions/bar');
const unixStyle = path.resolve(fileRef).split(path.sep).join('/');
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
allOf: [
// points to current working directory, hence the `test` prefix
{ $ref: './test/fixtures/definitions.yaml#/definitions/foo' },
{ $ref: unixStyle },
],
};

const result = await convert(schema, { dereference: true });

const expected = {
allOf: [{ type: 'string' }, { type: 'number' }],
};

expect(result).toEqual(expected);
});

it('throws an error when dereferecing fails', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
properties: {
foo: {
$ref: './bad.json',
},
},
};

let error;
try {
await convert(schema, { dereference: true });
} catch (e) {
error = e;
}

expect(error).have.property('ioErrorCode', 'ENOENT');
});

it('throws an error when dereferecing fails', async ({ expect }) => {
const schema = {
definitions: {
envVarName: {
type: 'string',
pattern: '^[A-Z_]+[A-Z0-9_]*$',
},
configvariable: {
type: 'object',
properties: {
name: { $ref: '#/definitions/envVarName' },
default: { type: 'string' },
required: { type: 'boolean', default: true },
},
required: ['name'],
additionalProperties: false,
},
},
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'http://example.com/root.json',
type: 'object',
title: 'Component Manifest Schema',
required: ['componentId'],
additionalProperties: false,
properties: {
componentId: {
$id: '#/properties/componentId',
type: 'string',
title: 'The component id Schema',
pattern: '^(.*)$',
},
configurationTemplate: {
$id: '#/properties/configurationTemplate',
type: 'array',
title: 'The Configurationtemplate Schema',
items: {
$ref: '#/definitions/configvariable',
},
},
},
};

const result = await convert(schema);

expect(result).toMatchSnapshot();
});
14 changes: 14 additions & 0 deletions test/examples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import convert from '../src';

it('uses the first example from a schema', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-06/schema#',
examples: ['foo', 'bar'],
};

const result = await convert(schema);

expect(result).toEqual({
example: 'foo',
});
});
25 changes: 0 additions & 25 deletions test/exclusiveMinMax.test.js

This file was deleted.

22 changes: 22 additions & 0 deletions test/exclusiveMinMax.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import convert from '../src';

it('exclusiveMinMax', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'integer',
exclusiveMaximum: 10,
exclusiveMinimum: 0,
};

const result = await convert(schema);

const expected = {
type: 'integer',
maximum: 10,
exclusiveMaximum: true,
minimum: 0,
exclusiveMinimum: true,
};

expect(result).toEqual(expected);
});
5 changes: 5 additions & 0 deletions test/fixtures/definitions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
definitions:
foo:
type: 'string'
bar:
type: 'number'
9 changes: 0 additions & 9 deletions test/helpers.js

This file was deleted.

7 changes: 7 additions & 0 deletions test/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import fs from 'fs';
import { join } from 'path';

export const getSchema = (file: string) => {
const path = join(__dirname, 'schemas', file);
return JSON.parse(fs.readFileSync(path).toString());
};
24 changes: 0 additions & 24 deletions test/if-then-else.test.js

This file was deleted.

94 changes: 94 additions & 0 deletions test/if-then-else.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import convert from '../src';

it('if-then-else', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
if: { type: 'object' },
then: { properties: { id: { type: 'string' } } },
else: { format: 'uuid' },
};

const result = await convert(schema);

const expected = {
oneOf: [
{
allOf: [{ type: 'object' }, { properties: { id: { type: 'string' } } }],
},
{ allOf: [{ not: { type: 'object' } }, { format: 'uuid' }] },
],
};

expect(result).toEqual(expected);
});

it('if-then', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
properties: {
type: {
type: 'string',
enum: ['css', 'js', 'i18n', 'json'],
},
locale: {
type: 'string',
},
},
if: {
properties: {
type: {
const: 'i18n',
},
},
},
then: {
required: ['locale'],
},
};
const result = await convert(schema);

const expected = {
type: 'object',
properties: {
type: {
type: 'string',
enum: ['css', 'js', 'i18n', 'json'],
},
locale: {
type: 'string',
},
},
oneOf: [
{
allOf: [
{
properties: {
type: {
enum: ['i18n'],
},
},
},
{
required: ['locale'],
},
],
},
{
allOf: [
{
not: {
properties: {
type: {
enum: ['i18n'],
},
},
},
},
],
},
],
};

expect(result).toEqual(expected);
});
21 changes: 0 additions & 21 deletions test/invalid_types.test.js

This file was deleted.

23 changes: 23 additions & 0 deletions test/invalid_types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import convert from '../src';
import { getSchema } from './helpers';

it('dateTime is invalid type', async ({ expect }) => {
const schema = { type: 'dateTime' };
await expect(() => convert(schema)).rejects.toThrowError(
/is not a valid type/,
);
});

it('foo is invalid type', async ({ expect }) => {
const schema = { type: 'foo' };
await expect(() => convert(schema)).rejects.toThrowError(
/is not a valid type/,
);
});

it('invalid type inside complex schema', async ({ expect }) => {
const schema = getSchema('invalid/json-schema.json');
await expect(() => convert(schema)).rejects.toThrowError(
/is not a valid type/,
);
});
29 changes: 0 additions & 29 deletions test/items.test.js

This file was deleted.

26 changes: 26 additions & 0 deletions test/items.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import convert from '../src';

it('items', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'array',
items: {
type: 'string',
format: 'date-time',
example: '2017-01-01T12:34:56Z',
},
};

const result = await convert(schema);

const expected = {
type: 'array',
items: {
type: 'string',
format: 'date-time',
example: '2017-01-01T12:34:56Z',
},
};

expect(result).toEqual(expected);
});
51 changes: 0 additions & 51 deletions test/nullable.test.js

This file was deleted.

214 changes: 214 additions & 0 deletions test/nullable.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import convert from '../src';
import { describe } from 'vitest';
import type { JSONSchema4 } from 'json-schema';

describe('nullable', () => {
it('adds `nullable: true` for `type: [string, null]`', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: ['string', 'null'],
} satisfies JSONSchema4;

const result = await convert(schema);

expect(result).toEqual({
type: 'string',
nullable: true,
});
});

it.each(['oneOf', 'anyOf'] as const)(
'supports nullables inside sub-schemas %s',
async (key) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
[key]: [{ type: 'string' }, { type: 'null' }],
} satisfies JSONSchema4;

const result = await convert(schema);

expect(result).toEqual({
[key]: [{ type: 'string', nullable: true }],
});
},
);

it('supports nullables inside definitions', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-07/schema#',
definitions: {
Product: {
type: 'object',
properties: {
name: {
type: 'string',
},
price: {
type: 'number',
},
rating: {
type: ['null', 'number'],
},
},
required: ['name', 'price', 'rating'],
},
ProductList: {
type: 'object',
properties: {
name: {
type: 'string',
},
version: {
type: 'string',
},
products: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string',
},
price: {
type: 'number',
},
rating: {
type: ['null', 'number'],
},
},
required: ['name', 'price', 'rating'],
},
},
},
required: ['name', 'products', 'version'],
},
},
};

const result = await convert(schema);

expect(result).toEqual({
definitions: {
Product: {
type: 'object',
properties: {
name: {
type: 'string',
},
price: {
type: 'number',
},
rating: {
type: 'number',
nullable: true,
},
},
required: ['name', 'price', 'rating'],
},
ProductList: {
type: 'object',
properties: {
name: {
type: 'string',
},
version: {
type: 'string',
},
products: {
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string',
},
price: {
type: 'number',
},
rating: {
type: 'number',
nullable: true,
},
},
required: ['name', 'price', 'rating'],
},
},
},
required: ['name', 'products', 'version'],
},
},
});
});

it('does not add nullable for non null types', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'string',
} satisfies JSONSchema4;

const result = await convert(schema);

expect(result).toEqual({
type: 'string',
});
});

it.each(['oneOf', 'anyOf'] as const)(
'adds nullable for types with null',
async (key) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
title: 'NullExample',
description: 'Null Example',
[key]: [
{
type: 'object',
properties: {
foo: {
type: 'string',
},
},
},
{
type: 'object',
properties: {
bar: {
type: 'number',
},
},
},
{
type: 'null',
},
],
} satisfies JSONSchema4;

const result = await convert(schema);

expect(result).toEqual({
title: 'NullExample',
description: 'Null Example',
[key]: [
{
type: 'object',
properties: {
foo: {
type: 'string',
},
},
nullable: true,
},
{
type: 'object',
properties: {
bar: {
type: 'number',
},
},
nullable: true,
},
],
});
},
);
});
35 changes: 0 additions & 35 deletions test/pattern_properties.test.js

This file was deleted.

32 changes: 32 additions & 0 deletions test/pattern_properties.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import convert from '../src';

it('renames patternProperties to x-patternProperties', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
additionalProperties: {
type: 'string',
},
patternProperties: {
'^[a-z]*$': {
type: 'string',
},
},
};

const result = await convert(schema);

const expected = {
type: 'object',
additionalProperties: {
type: 'string',
},
'x-patternProperties': {
'^[a-z]*$': {
type: 'string',
},
},
};

expect(result).toEqual(expected);
});
89 changes: 43 additions & 46 deletions test/properties.test.js → test/properties.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
'use strict';
import convert from '../src';

const convert = require('../');
const should = require('should');

it('type array', () => {
it('type array', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: ['string', 'null']
type: ['string', 'null'],
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
type: 'string',
nullable: true
nullable: true,
};

should(result).deepEqual(expected);
expect(result).toEqual(expected);
});

it('properties', () => {
it('properties', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
@@ -29,12 +26,12 @@ it('properties', () => {
type: 'string',
},
bar: {
type: ['string', 'null']
}
}
type: ['string', 'null'],
},
},
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
type: 'object',
@@ -45,107 +42,107 @@ it('properties', () => {
},
bar: {
type: 'string',
nullable: true
}
}
nullable: true,
},
},
};

should(result).deepEqual(expected);
expect(result).toEqual(expected);
});

it('addionalProperties is false', () => {
it('addionalProperties is false', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
foo: {
type: 'string',
}
},
},
additionalProperties: false
additionalProperties: false,
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
type: 'object',
properties: {
foo: {
type: 'string',
}
},
},
additionalProperties: false
additionalProperties: false,
};

should(result).deepEqual(expected, 'properties converted');
expect(result).toEqual(expected);
});

it('addionalProperties is true', () => {
it('addionalProperties is true', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
foo: {
type: 'string',
}
},
},
additionalProperties: true
additionalProperties: true,
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
type: 'object',
properties: {
foo: {
type: 'string',
}
},
},
additionalProperties: true
additionalProperties: true,
};

should(result).deepEqual(expected);
expect(result).toEqual(expected);
});

it('addionalProperties is an object', () => {
it('addionalProperties is an object', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
foo: {
type: 'string',
}
},
},
additionalProperties: {
type: 'object',
properties: {
foo: {
type: 'string',
format: 'date-time'
}
}
}
format: 'date-time',
},
},
},
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
type: 'object',
properties: {
foo: {
type: 'string'
}
type: 'string',
},
},
additionalProperties: {
type: 'object',
properties: {
foo: {
type: 'string',
format: 'date-time'
}
}
}
format: 'date-time',
},
},
},
};

should(result).deepEqual(expected, 'properties and additionalProperties converted');
expect(result).toEqual(expected);
});
73 changes: 35 additions & 38 deletions test/readonly_writeonly.test.js → test/readonly_writeonly.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,47 @@
'use strict';
import convert from '../src';

const convert = require('../');
const should = require('should');

it('maintain readOnly and writeOnly props', () => {
it('maintain readOnly and writeOnly props', async ({ expect }) => {
const schema = {
type: 'object',
properties: {
prop1: {
type: 'string',
readOnly: true
readOnly: true,
},
prop2: {
type: 'string',
writeOnly: true
}
}
writeOnly: true,
},
},
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
type: 'object',
properties: {
prop1: {
type: 'string',
readOnly: true
readOnly: true,
},
prop2: {
type: 'string',
writeOnly: true
}
}
writeOnly: true,
},
},
};

should(result).deepEqual(expected);
expect(result).toEqual(expected);
});

it('deep schema', () => {
it('deep schema', async ({ expect }) => {
const schema = {
type: 'object',
required: ['prop1', 'prop2'],
properties: {
prop1: {
type: 'string',
readOnly: true
readOnly: true,
},
prop2: {
allOf: [
@@ -54,33 +51,33 @@ it('deep schema', () => {
properties: {
prop3: {
type: 'object',
readOnly: true
}
}
readOnly: true,
},
},
},
{
type: 'object',
properties: {
prop4: {
type: 'object',
readOnly: true
}
}
readOnly: true,
},
},
},
]
}
}
],
},
},
};

const result = convert(schema);
const result = await convert(schema);

const expected = {
type: 'object',
required: ['prop1', 'prop2'],
properties: {
prop1: {
type: 'string',
readOnly: true
readOnly: true,
},
prop2: {
allOf: [
@@ -90,23 +87,23 @@ it('deep schema', () => {
properties: {
prop3: {
type: 'object',
readOnly: true
}
}
readOnly: true,
},
},
},
{
type: 'object',
properties: {
prop4: {
type: 'object',
readOnly: true
}
}
readOnly: true,
},
},
},
]
}
}
],
},
},
};

should(result).deepEqual(expected);
expect(result).toEqual(expected);
});
32 changes: 32 additions & 0 deletions test/rewrite_as_extensions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import convert from '../src';

it('renames illegal (unknown) keywords as extensions and skips those that already are', async ({
expect,
}) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
subject: {
type: 'string',
customProperty: true,
'x-alreadyAnExtension': true,
},
},
};

const result = await convert(schema);

const expected = {
type: 'object',
properties: {
subject: {
type: 'string',
'x-customProperty': true,
'x-alreadyAnExtension': true,
},
},
};

expect(result).toEqual(expected);
});
34 changes: 17 additions & 17 deletions test/schemas/address/json-schema.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "An Address following the convention of http://microformats.org/wiki/hcard",
"type": "object",
"properties": {
"post-office-box": { "type": "string" },
"extended-address": { "type": "string" },
"street-address": { "type": "string" },
"locality":{ "type": "string" },
"region": { "type": "string" },
"postal-code": { "type": "string" },
"country-name": { "type": "string"}
},
"required": ["locality", "region", "country-name"],
"dependencies": {
"post-office-box": ["street-address"],
"extended-address": ["street-address"]
}
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "An Address following the convention of http://microformats.org/wiki/hcard",
"type": "object",
"properties": {
"post-office-box": { "type": "string" },
"extended-address": { "type": "string" },
"street-address": { "type": "string" },
"locality": { "type": "string" },
"region": { "type": "string" },
"postal-code": { "type": "string" },
"country-name": { "type": "string" }
},
"required": ["locality", "region", "country-name"],
"dependencies": {
"post-office-box": ["street-address"],
"extended-address": ["street-address"]
}
}
52 changes: 26 additions & 26 deletions test/schemas/address/openapi.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
{
"description": "An Address following the convention of http://microformats.org/wiki/hcard",
"type": "object",
"properties": {
"post-office-box": { "type": "string" },
"extended-address": { "type": "string" },
"street-address": { "type": "string" },
"locality":{ "type": "string" },
"region": { "type": "string" },
"postal-code": { "type": "string" },
"country-name": { "type": "string"}
},
"required": ["locality", "region", "country-name"],
"allOf": [
{
"oneOf": [
{"not": {"required": ["post-office-box"]}},
{"required": ["post-office-box", "street-address"]}
]
},
{
"oneOf": [
{"not": {"required": ["extended-address"]}},
{"required": ["extended-address", "street-address"]}
]
}
]
"description": "An Address following the convention of http://microformats.org/wiki/hcard",
"type": "object",
"properties": {
"post-office-box": { "type": "string" },
"extended-address": { "type": "string" },
"street-address": { "type": "string" },
"locality": { "type": "string" },
"region": { "type": "string" },
"postal-code": { "type": "string" },
"country-name": { "type": "string" }
},
"required": ["locality", "region", "country-name"],
"allOf": [
{
"oneOf": [
{ "not": { "required": ["post-office-box"] } },
{ "required": ["post-office-box", "street-address"] }
]
},
{
"oneOf": [
{ "not": { "required": ["extended-address"] } },
{ "required": ["extended-address", "street-address"] }
]
}
]
}
225 changes: 105 additions & 120 deletions test/schemas/basic/json-schema.json
Original file line number Diff line number Diff line change
@@ -1,134 +1,119 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"allOf": [
{
{
"anyOf": [
{
"type": "object",
"properties": {
"cats": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [
1
]
}
}
}
},
{
"type": "object",
"properties": {
"dogs": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [
1
]
}
}
}
},
{
"type": "object",
"properties": {
"bring_cats": {
"type": "array",
"items": {
"allOf": [
{
"type": "object",
"properties": {
"email": {
"type": "string",
{
"type": "object",
"properties": {
"cats": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [1]
}
}
}
},
{
"type": "object",
"properties": {
"dogs": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [1]
}
}
}
},
{
"type": "object",
"properties": {
"bring_cats": {
"type": "array",
"items": {
"allOf": [
{
"type": "object",
"properties": {
"email": {
"type": "string",
"example": "cats@email.com"
},
"sms": {
"type": [
"string",
"null"
],
},
"sms": {
"type": ["string", "null"],
"example": "+12345678"
},
"properties": {
"type": "object",
"additionalProperties": {
"type": "string"
},
},
"properties": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"name": "Wookie"
}
}
}
},
{
"required": [
"email"
]
}
]
}
}
}
}
]
},
{
"type": "object",
"properties": {
"playground": {
"type": "object",
"required": [
"feeling",
"child"
],
"properties": {
"feeling": {
"type": "string",
}
}
},
{
"required": ["email"]
}
]
}
}
}
}
]
},
{
"type": "object",
"properties": {
"playground": {
"type": "object",
"required": ["feeling", "child"],
"properties": {
"feeling": {
"type": "string",
"example": "Good feeling"
},
"child": {
"type": "object",
"required": [
"name",
"age"
],
"properties": {
"name": {
"type": "string",
},
"child": {
"type": "object",
"required": ["name", "age"],
"properties": {
"name": {
"type": "string",
"example": "Steven"
},
"age": {
"type": "integer",
},
"age": {
"type": "integer",
"example": 5
}
}
},
"toy": {
"type": "object",
"properties": {
"breaks_easily": {
"type": "boolean",
"default": false
},
"color": {
"type": "string",
"description": "Color of the toy"
},
"type": {
"type": "string",
}
}
},
"toy": {
"type": "object",
"properties": {
"breaks_easily": {
"type": "boolean",
"default": false
},
"color": {
"type": "string",
"description": "Color of the toy"
},
"type": {
"type": "string",
"enum": ["bucket", "shovel"],
"description": "Toy type"
}
}
}
}
}
}
}
]
"description": "Toy type"
}
}
}
}
}
}
}
]
}
240 changes: 114 additions & 126 deletions test/schemas/basic/openapi.json
Original file line number Diff line number Diff line change
@@ -1,131 +1,119 @@
{
"allOf": [
{
{
"anyOf": [
{
"type": "object",
"properties": {
"cats": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [
1
]
}
}
}
},
{
"type": "object",
"properties": {
"dogs": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [
1
]
}
}
}
},
{
"type": "object",
"properties": {
"bring_cats": {
"type": "array",
"items": {
"allOf": [
{
"type": "object",
"properties": {
"email": {
"type": "string",
"example": "cats@email.com"
},
"sms": {
"type": "string",
"nullable": true,
"example": "+12345678"
},
"properties": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"name": "Wookie"
}
}
}
},
{
"required": [
"email"
]
}
]
}
}
}
}
]
},
{
"type": "object",
"properties": {
"playground": {
"type": "object",
"required": [
"feeling",
"child"
],
"properties": {
"feeling": {
"type": "string",
"example": "Good feeling"
},
"child": {
"type": "object",
"required": [
"name",
"age"
],
"properties": {
"name": {
"type": "string",
"example": "Steven"
},
"age": {
"type": "integer",
"example": 5
}
}
},
"toy": {
"type": "object",
"properties": {
"breaks_easily": {
"type": "boolean",
"default": false
},
"color": {
"type": "string",
"description": "Color of the toy"
},
"type": {
"type": "string",
{
"type": "object",
"properties": {
"cats": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [1]
}
}
}
},
{
"type": "object",
"properties": {
"dogs": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [1]
}
}
}
},
{
"type": "object",
"properties": {
"bring_cats": {
"type": "array",
"items": {
"allOf": [
{
"type": "object",
"properties": {
"email": {
"type": "string",
"example": "cats@email.com"
},
"sms": {
"type": "string",
"nullable": true,
"example": "+12345678"
},
"properties": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"example": {
"name": "Wookie"
}
}
}
},
{
"required": ["email"]
}
]
}
}
}
}
]
},
{
"type": "object",
"properties": {
"playground": {
"type": "object",
"required": ["feeling", "child"],
"properties": {
"feeling": {
"type": "string",
"example": "Good feeling"
},
"child": {
"type": "object",
"required": ["name", "age"],
"properties": {
"name": {
"type": "string",
"example": "Steven"
},
"age": {
"type": "integer",
"example": 5
}
}
},
"toy": {
"type": "object",
"properties": {
"breaks_easily": {
"type": "boolean",
"default": false
},
"color": {
"type": "string",
"description": "Color of the toy"
},
"type": {
"type": "string",
"enum": ["bucket", "shovel"],
"description": "Toy type"
}
}
}
}
}
}
}
]
"description": "Toy type"
}
}
}
}
}
}
}
]
}
72 changes: 36 additions & 36 deletions test/schemas/calendar/json-schema.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "A representation of an event",
"type": "object",
"required": [ "dtstart", "summary" ],
"properties": {
"dtstart": {
"format": "date-time",
"type": "string",
"description": "Event starting time"
},
"dtend": {
"format": "date-time",
"type": "string",
"description": "Event ending time"
},
"summary": { "type": "string" },
"location": { "type": "string" },
"url": { "type": "string", "format": "uri" },
"duration": {
"format": "time",
"type": "string",
"description": "Event duration"
},
"rdate": {
"format": "date-time",
"type": "string",
"description": "Recurrence date"
},
"rrule": {
"type": "string",
"description": "Recurrence rule"
},
"category": { "type": "string" },
"description": { "type": "string" },
"geo": { "$ref": "http://json-schema.org/geo" }
}
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "A representation of an event",
"type": "object",
"required": ["dtstart", "summary"],
"properties": {
"dtstart": {
"format": "date-time",
"type": "string",
"description": "Event starting time"
},
"dtend": {
"format": "date-time",
"type": "string",
"description": "Event ending time"
},
"summary": { "type": "string" },
"location": { "type": "string" },
"url": { "type": "string", "format": "uri" },
"duration": {
"format": "time",
"type": "string",
"description": "Event duration"
},
"rdate": {
"format": "date-time",
"type": "string",
"description": "Recurrence date"
},
"rrule": {
"type": "string",
"description": "Recurrence rule"
},
"category": { "type": "string" },
"description": { "type": "string" },
"geo": { "$ref": "http://json-schema.org/geo" }
}
}
70 changes: 35 additions & 35 deletions test/schemas/calendar/openapi.json
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
{
"description": "A representation of an event",
"type": "object",
"required": [ "dtstart", "summary" ],
"properties": {
"dtstart": {
"format": "date-time",
"type": "string",
"description": "Event starting time"
},
"dtend": {
"format": "date-time",
"type": "string",
"description": "Event ending time"
},
"summary": { "type": "string" },
"location": { "type": "string" },
"url": { "type": "string", "format": "uri" },
"duration": {
"format": "time",
"type": "string",
"description": "Event duration"
},
"rdate": {
"format": "date-time",
"type": "string",
"description": "Recurrence date"
},
"rrule": {
"type": "string",
"description": "Recurrence rule"
},
"category": { "type": "string" },
"description": { "type": "string" },
"geo": { "$ref": "http://json-schema.org/geo" }
}
"description": "A representation of an event",
"type": "object",
"required": ["dtstart", "summary"],
"properties": {
"dtstart": {
"format": "date-time",
"type": "string",
"description": "Event starting time"
},
"dtend": {
"format": "date-time",
"type": "string",
"description": "Event ending time"
},
"summary": { "type": "string" },
"location": { "type": "string" },
"url": { "type": "string", "format": "uri" },
"duration": {
"format": "time",
"type": "string",
"description": "Event duration"
},
"rdate": {
"format": "date-time",
"type": "string",
"description": "Recurrence date"
},
"rrule": {
"type": "string",
"description": "Recurrence rule"
},
"category": { "type": "string" },
"description": { "type": "string" },
"geo": { "$ref": "http://json-schema.org/geo" }
}
}
45 changes: 45 additions & 0 deletions test/schemas/circular/json-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"definitions": {
"thing": {
"$ref": "#/definitions/thing"
},
"person": {
"properties": {
"name": {
"type": "string"
},
"spouse": {
"type": {
"$ref": "#/definitions/person"
}
}
}
},
"parent": {
"properties": {
"name": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/child"
}
}
}
},
"child": {
"properties": {
"name": {
"type": "string"
},
"parents": {
"type": "array",
"items": {
"$ref": "#/definitions/parent"
}
}
}
}
}
}
45 changes: 45 additions & 0 deletions test/schemas/circular/openapi-circular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"definitions": {
"thing": {
"$ref": "#/definitions/thing"
},
"person": {
"properties": {
"name": {
"type": "string"
},
"spouse": {
"type": {
"$ref": "#/definitions/person"
}
}
}
},
"parent": {
"properties": {
"name": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/child"
}
}
}
},
"child": {
"properties": {
"name": {
"type": "string"
},
"parents": {
"type": "array",
"items": {
"$ref": "#/definitions/parent"
}
}
}
}
}
}
45 changes: 45 additions & 0 deletions test/schemas/circular/openapi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"definitions": {
"thing": {
"$ref": "#/definitions/thing"
},
"person": {
"properties": {
"name": {
"type": "string"
},
"spouse": {
"type": {
"$ref": "#/definitions/person"
}
}
}
},
"parent": {
"properties": {
"name": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/child"
}
}
}
},
"child": {
"properties": {
"name": {
"type": "string"
},
"parents": {
"type": "array",
"items": {
"$ref": "#/definitions/parent"
}
}
}
}
}
}
56 changes: 56 additions & 0 deletions test/schemas/events/json-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"id": "http://some.site.somewhere/event-schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Descriminate object by objectType",
"type": "object",
"properties": {
"event": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/EventA" },
{ "$ref": "#/definitions/EventB" }
],
"required": ["objectType"],
"discriminator": {
"propertyName": "objectType",
"mapping": {
"ev-a": "#/definitions/schemas/EventA",
"ev-b": "#/definitions/schemas/EventB"
}
}
},
"health": {
"type": "object",
"properties": {
"unavailable": {
"type": "boolean",
"produced-by": "health-checker"
}
}
}
},
"definitions": {
"EventA": {
"type": "object",
"properties": {
"objectType": {
"type": "string"
},
"infoA": {
"type": "string"
}
}
},
"EventB": {
"type": "object",
"properties": {
"objectType": {
"type": "string"
},
"infoB": {
"type": "number"
}
}
}
}
}
58 changes: 58 additions & 0 deletions test/schemas/events/openapi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"description": "Descriminate object by objectType",
"type": "object",
"properties": {
"event": {
"type": "object",
"oneOf": [
{
"$ref": "#/definitions/EventA"
},
{
"$ref": "#/definitions/EventB"
}
],
"required": ["objectType"],
"discriminator": {
"propertyName": "objectType",
"mapping": {
"ev-a": "#/definitions/schemas/EventA",
"ev-b": "#/definitions/schemas/EventB"
}
}
},
"health": {
"type": "object",
"properties": {
"unavailable": {
"type": "boolean",
"x-produced-by": "health-checker"
}
}
}
},
"definitions": {
"EventA": {
"type": "object",
"properties": {
"objectType": {
"type": "string"
},
"infoA": {
"type": "string"
}
}
},
"EventB": {
"type": "object",
"properties": {
"objectType": {
"type": "string"
},
"infoB": {
"type": "number"
}
}
}
}
}
162 changes: 81 additions & 81 deletions test/schemas/example2/json-schema.json
Original file line number Diff line number Diff line change
@@ -1,83 +1,83 @@
{
"id": "http://some.site.somewhere/entry-schema#",
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "schema for an fstab entry",
"type": "object",
"required": [ "storage" ],
"properties": {
"storage": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/diskDevice" },
{ "$ref": "#/definitions/diskUUID" },
{ "$ref": "#/definitions/nfs" },
{ "$ref": "#/definitions/tmpfs" }
]
},
"fstype": {
"enum": [ "ext3", "ext4", "btrfs" ]
},
"options": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"uniqueItems": true
},
"readonly": { "type": "boolean" }
},
"definitions": {
"diskDevice": {
"properties": {
"type": { "enum": [ "disk" ] },
"device": {
"type": "string",
"pattern": "^/dev/[^/]+(/[^/]+)*$"
}
},
"required": [ "type", "device" ],
"additionalProperties": false
},
"diskUUID": {
"properties": {
"type": { "enum": [ "disk" ] },
"label": {
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
}
},
"required": [ "type", "label" ],
"additionalProperties": false
},
"nfs": {
"properties": {
"type": { "enum": [ "nfs" ] },
"remotePath": {
"type": "string",
"pattern": "^(/[^/]+)+$"
},
"server": {
"type": "string",
"oneOf": [
{ "format": "hostname" },
{ "format": "ipv4" },
{ "format": "ipv6" }
]
}
},
"required": [ "type", "server", "remotePath" ],
"additionalProperties": false
},
"tmpfs": {
"properties": {
"type": { "enum": [ "tmpfs" ] },
"sizeInMB": {
"type": "integer",
"minimum": 16,
"maximum": 512
}
},
"required": [ "type", "sizeInMB" ],
"additionalProperties": false
}
}
"id": "http://some.site.somewhere/entry-schema#",
"$schema": "http://json-schema.org/draft-06/schema#",
"description": "schema for an fstab entry",
"type": "object",
"required": ["storage"],
"properties": {
"storage": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/diskDevice" },
{ "$ref": "#/definitions/diskUUID" },
{ "$ref": "#/definitions/nfs" },
{ "$ref": "#/definitions/tmpfs" }
]
},
"fstype": {
"enum": ["ext3", "ext4", "btrfs"]
},
"options": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"uniqueItems": true
},
"readonly": { "type": "boolean" }
},
"definitions": {
"diskDevice": {
"properties": {
"type": { "enum": ["disk"] },
"device": {
"type": "string",
"pattern": "^/dev/[^/]+(/[^/]+)*$"
}
},
"required": ["type", "device"],
"additionalProperties": false
},
"diskUUID": {
"properties": {
"type": { "enum": ["disk"] },
"label": {
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
}
},
"required": ["type", "label"],
"additionalProperties": false
},
"nfs": {
"properties": {
"type": { "enum": ["nfs"] },
"remotePath": {
"type": "string",
"pattern": "^(/[^/]+)+$"
},
"server": {
"type": "string",
"oneOf": [
{ "format": "hostname" },
{ "format": "ipv4" },
{ "format": "ipv6" }
]
}
},
"required": ["type", "server", "remotePath"],
"additionalProperties": false
},
"tmpfs": {
"properties": {
"type": { "enum": ["tmpfs"] },
"sizeInMB": {
"type": "integer",
"minimum": 16,
"maximum": 512
}
},
"required": ["type", "sizeInMB"],
"additionalProperties": false
}
}
}
158 changes: 79 additions & 79 deletions test/schemas/example2/openapi.json
Original file line number Diff line number Diff line change
@@ -1,81 +1,81 @@
{
"description": "schema for an fstab entry",
"type": "object",
"required": [ "storage" ],
"properties": {
"storage": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/diskDevice" },
{ "$ref": "#/definitions/diskUUID" },
{ "$ref": "#/definitions/nfs" },
{ "$ref": "#/definitions/tmpfs" }
]
},
"fstype": {
"enum": [ "ext3", "ext4", "btrfs" ]
},
"options": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"uniqueItems": true
},
"readonly": { "type": "boolean" }
},
"definitions": {
"diskDevice": {
"properties": {
"type": { "enum": [ "disk" ] },
"device": {
"type": "string",
"pattern": "^/dev/[^/]+(/[^/]+)*$"
}
},
"required": [ "type", "device" ],
"additionalProperties": false
},
"diskUUID": {
"properties": {
"type": { "enum": [ "disk" ] },
"label": {
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
}
},
"required": [ "type", "label" ],
"additionalProperties": false
},
"nfs": {
"properties": {
"type": { "enum": [ "nfs" ] },
"remotePath": {
"type": "string",
"pattern": "^(/[^/]+)+$"
},
"server": {
"type": "string",
"oneOf": [
{ "format": "hostname" },
{ "format": "ipv4" },
{ "format": "ipv6" }
]
}
},
"required": [ "type", "server", "remotePath" ],
"additionalProperties": false
},
"tmpfs": {
"properties": {
"type": { "enum": [ "tmpfs" ] },
"sizeInMB": {
"type": "integer",
"minimum": 16,
"maximum": 512
}
},
"required": [ "type", "sizeInMB" ],
"additionalProperties": false
}
}
"description": "schema for an fstab entry",
"type": "object",
"required": ["storage"],
"properties": {
"storage": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/diskDevice" },
{ "$ref": "#/definitions/diskUUID" },
{ "$ref": "#/definitions/nfs" },
{ "$ref": "#/definitions/tmpfs" }
]
},
"fstype": {
"enum": ["ext3", "ext4", "btrfs"]
},
"options": {
"type": "array",
"minItems": 1,
"items": { "type": "string" },
"uniqueItems": true
},
"readonly": { "type": "boolean" }
},
"definitions": {
"diskDevice": {
"properties": {
"type": { "enum": ["disk"] },
"device": {
"type": "string",
"pattern": "^/dev/[^/]+(/[^/]+)*$"
}
},
"required": ["type", "device"],
"additionalProperties": false
},
"diskUUID": {
"properties": {
"type": { "enum": ["disk"] },
"label": {
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
}
},
"required": ["type", "label"],
"additionalProperties": false
},
"nfs": {
"properties": {
"type": { "enum": ["nfs"] },
"remotePath": {
"type": "string",
"pattern": "^(/[^/]+)+$"
},
"server": {
"type": "string",
"oneOf": [
{ "format": "hostname" },
{ "format": "ipv4" },
{ "format": "ipv6" }
]
}
},
"required": ["type", "server", "remotePath"],
"additionalProperties": false
},
"tmpfs": {
"properties": {
"type": { "enum": ["tmpfs"] },
"sizeInMB": {
"type": "integer",
"minimum": 16,
"maximum": 512
}
},
"required": ["type", "sizeInMB"],
"additionalProperties": false
}
}
}
242 changes: 115 additions & 127 deletions test/schemas/invalid/json-schema.json
Original file line number Diff line number Diff line change
@@ -1,131 +1,119 @@
{
"allOf": [
{
"anyOf": [
{
"type": "object",
"properties": {
"cats": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [
1
]
}
}
}
},
{
"type": "object",
"properties": {
"dogs": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [
1
]
}
}
}
},
{
"type": "object",
"properties": {
"bring_cats": {
"type": "array",
"items": {
"allOf": [
{
"type": "object",
"properties": {
"email": {
"type": "string",
"example": "cats@email.com"
},
"sms": {
"type": "string",
"nullable": true,
"example": "+12345678"
},
"properties": {
"type": "object",
"additionalProperties": {
"type": "invalidtype"
},
"example": {
"name": "Wookie"
}
}
}
},
{
"required": [
"email"
]
}
]
}
}
}
}
]
},
{
"type": "object",
"properties": {
"playground": {
"type": "object",
"required": [
"feeling",
"child"
],
"properties": {
"feeling": {
"type": "string",
"example": "Good feeling"
},
"child": {
"type": "object",
"required": [
"name",
"age"
],
"properties": {
"name": {
"type": "string",
"example": "Steven"
},
"age": {
"type": "integer",
"example": 5
}
}
},
"toy": {
"type": "object",
"properties": {
"breaks_easily": {
"type": "boolean",
"default": false
},
"color": {
"type": "string",
"description": "Color of the toy"
},
"type": {
"type": "string",
{
"anyOf": [
{
"type": "object",
"properties": {
"cats": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [1]
}
}
}
},
{
"type": "object",
"properties": {
"dogs": {
"type": "array",
"items": {
"type": "integer",
"format": "int64",
"example": [1]
}
}
}
},
{
"type": "object",
"properties": {
"bring_cats": {
"type": "array",
"items": {
"allOf": [
{
"type": "object",
"properties": {
"email": {
"type": "string",
"example": "cats@email.com"
},
"sms": {
"type": "string",
"nullable": true,
"example": "+12345678"
},
"properties": {
"type": "object",
"additionalProperties": {
"type": "invalidtype"
},
"example": {
"name": "Wookie"
}
}
}
},
{
"required": ["email"]
}
]
}
}
}
}
]
},
{
"type": "object",
"properties": {
"playground": {
"type": "object",
"required": ["feeling", "child"],
"properties": {
"feeling": {
"type": "string",
"example": "Good feeling"
},
"child": {
"type": "object",
"required": ["name", "age"],
"properties": {
"name": {
"type": "string",
"example": "Steven"
},
"age": {
"type": "integer",
"example": 5
}
}
},
"toy": {
"type": "object",
"properties": {
"breaks_easily": {
"type": "boolean",
"default": false
},
"color": {
"type": "string",
"description": "Color of the toy"
},
"type": {
"type": "string",
"enum": ["bucket", "shovel"],
"description": "Toy type"
}
}
}
}
}
}
}
]
"description": "Toy type"
}
}
}
}
}
}
}
]
}
47 changes: 0 additions & 47 deletions test/subschema.test.js

This file was deleted.

46 changes: 46 additions & 0 deletions test/subschema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import convert from '../src';

it('strips $id from all subschemas not just root`', async ({ expect }) => {
const schema = {
$id: 'https://foo/bla',
id: 'https://foo/bla',
$schema: 'http://json-schema.org/draft-06/schema#',
type: 'object',
properties: {
foo: {
$id: '/properties/foo',
type: 'array',
items: {
$id: '/properties/foo/items',
type: 'object',
properties: {
id: {
$id: '/properties/foo/items/properties/id',
type: 'string',
},
},
},
},
},
};

const result = await convert(schema);

const expected = {
type: 'object',
properties: {
foo: {
type: 'array',
items: {
type: 'object',
properties: {
id: {
type: 'string',
},
},
},
},
},
};
expect(result).toEqual(expected);
});
10 changes: 10 additions & 0 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"lib": ["ESNext", "dom"],
"types": ["vitest/globals"],
"rootDir": "../"
},
"include": ["."]
}
56 changes: 56 additions & 0 deletions test/type-array-split.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import convert from '../src';

it('splits type arrays correctly', async ({ expect }) => {
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
type: 'object',
properties: {
emptyArray: {
type: [],
},
arrayWithNull: {
type: ['null'],
},
arrayWithSingleType: {
type: ['string'],
},
arrayWithNullAndSingleType: {
type: ['null', 'string'],
},
arrayWithNullAndMultipleTypes: {
type: ['null', 'string', 'number'],
},
arrayWithMultipleTypes: {
type: ['string', 'number'],
},
},
};

const result = await convert(schema);

const expected = {
type: 'object',
properties: {
emptyArray: {},
arrayWithNull: {
nullable: true,
},
arrayWithSingleType: {
type: 'string',
},
arrayWithNullAndSingleType: {
nullable: true,
type: 'string',
},
arrayWithNullAndMultipleTypes: {
nullable: true,
anyOf: [{ type: 'string' }, { type: 'number' }],
},
arrayWithMultipleTypes: {
anyOf: [{ type: 'string' }, { type: 'number' }],
},
},
};

expect(result).toEqual(expected);
});
28 changes: 28 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"outDir": "dist",
"target": "es2020",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"baseUrl": "src",
"declaration": true,
"esModuleInterop": true,
"inlineSourceMap": false,
"lib": ["esnext"],
"listEmittedFiles": false,
"listFiles": false,
"moduleResolution": "Node",
"noFallthroughCasesInSwitch": true,
"pretty": true,
"resolveJsonModule": true,
"rootDir": "src",
"skipLibCheck": true,
"noImplicitAny": false,
"strict": true,
"noUnusedParameters": true,
"sourceMap": true
},
"compileOnSave": false,
"exclude": ["node_modules", "dist", "coverage", "bin"],
"include": ["src"]
}
13 changes: 13 additions & 0 deletions vite.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
watch: false,
isolate: false,
reporters: 'verbose',
},
esbuild: {
target: 'node22',
},
});
3,676 changes: 3,676 additions & 0 deletions yarn.lock

Large diffs are not rendered by default.