Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/writer.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,18 @@ export const write = async (source, opts = {}) => {
? 'T.Intersect' // allOff
: 'T.Union' // oneOf

const list = anyOf || allOf || oneOf
let list = anyOf || allOf || oneOf

if ('properties' in options) {
const { properties, required = [] } = options
delete options.properties
delete options.required

for (const key in properties) {
list = list.concat({ type: 'object', properties, required })
break
}
}

w.write(`${compoundType}(`)
// TODO: use ref
Expand Down
14 changes: 13 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,10 +393,22 @@ test('parse some openapi examples', async (t) => {

test('nullable test', async (t) => {
await writeFile('./tmp/test-nullable.yaml.js', await write('./test/test-nullable.yaml'))
t.assert.snapshot(await readFile('./tmp/petstore.yaml.js', 'utf8'))
const { components } = await import('../tmp/test-nullable.yaml.js')
assert.deepEqual(components.schemas.Test, Type.Union([Type.Null(), Type.Object({
testStr: Type.Optional(Type.Union([Type.Null(), Type.String({ minLength: 2, maxLength: 2 })])),
testArr: Type.Union([Type.Null(), Type.Array(Type.Number())]),
})]))
})

test('allOf test', async (t) => {
await writeFile('./tmp/test-allOf.yaml.js', await write('./test/test-allOf.yaml'))
const { components } = await import('../tmp/test-allOf.yaml.js')
assert.deepEqual(components.schemas.AB, Type.Intersect([
Type.Object({
a: Type.Optional(Type.String())
}),
Type.Object({
b: Type.Optional(Type.String())
})
]))
})
4 changes: 0 additions & 4 deletions test/index.js.snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ exports[`basic test > esm 1`] = `
"/* eslint eslint-comments/no-unlimited-disable: off */\\n/* eslint-disable */\\n// This document was generated automatically by openapi-box\\n\\n/**\\n * @typedef {import('@sinclair/typebox').TSchema} TSchema\\n */\\n\\n/**\\n * @template {TSchema} T\\n * @typedef {import('@sinclair/typebox').Static<T>} Static\\n */\\n\\n/**\\n * @typedef {import('@sinclair/typebox').SchemaOptions} SchemaOptions\\n */\\n\\n/**\\n * @typedef {{\\n * [Path in keyof typeof schema]: {\\n * [Method in keyof typeof schema[Path]]: {\\n * [Prop in keyof typeof schema[Path][Method]]: typeof schema[Path][Method][Prop] extends TSchema ?\\n * Static<typeof schema[Path][Method][Prop]> :\\n * undefined\\n * }\\n * }\\n * }} SchemaType\\n */\\n\\n/**\\n * @typedef {{\\n * [ComponentType in keyof typeof _components]: {\\n * [ComponentName in keyof typeof _components[ComponentType]]: typeof _components[ComponentType][ComponentName] extends TSchema ?\\n * Static<typeof _components[ComponentType][ComponentName]> :\\n * undefined\\n * }\\n * }} ComponentType\\n */\\n\\nimport { Type as T, TypeRegistry, Kind, CloneType } from '@sinclair/typebox'\\nimport { Value } from '@sinclair/typebox/value'\\n\\n/**\\n * @typedef {{\\n * [Kind]: 'Binary'\\n * static: string | File | Blob | Uint8Array\\n * anyOf: [{\\n * type: 'object',\\n * additionalProperties: true\\n * }, {\\n * type: 'string',\\n * format: 'binary'\\n * }]\\n * } & TSchema} TBinary\\n */\\n\\n/**\\n * @returns {TBinary}\\n */\\nconst Binary = () => {\\n /**\\n * @param {TBinary} schema\\n * @param {unknown} value\\n * @returns {boolean}\\n */\\n function BinaryCheck(schema, value) {\\n const type = Object.prototype.toString.call(value)\\n return (\\n type === '[object Blob]' ||\\n type === '[object File]' ||\\n type === '[object String]' ||\\n type === '[object Uint8Array]'\\n )\\n }\\n\\n if (!TypeRegistry.Has('Binary')) TypeRegistry.Set('Binary', BinaryCheck)\\n\\n return /** @type {TBinary} */ ({\\n anyOf: [\\n {\\n type: 'object',\\n additionalProperties: true\\n },\\n {\\n type: 'string',\\n format: 'binary'\\n }\\n ],\\n [Kind]: 'Binary'\\n })\\n}\\n\\nconst ComponentsSchemasDef0 = T.Object({\\n lat: T.Number(),\\n long: T.Number()\\n})\\nconst ComponentsSchemasDef1 = T.Array(\\n T.Object({\\n title: T.String(),\\n address: T.String(),\\n coordinates: CloneType(ComponentsSchemasDef0)\\n })\\n)\\n\\nconst schema = {\\n '/hello': {\\n GET: {\\n args: T.Void(),\\n data: T.Any({ 'x-status-code': '200' }),\\n error: T.Union([T.Any({ 'x-status-code': 'default' })])\\n }\\n },\\n '/hello-typed': {\\n GET: {\\n args: T.Void(),\\n data: T.Object(\\n {\\n hello: T.Boolean()\\n },\\n {\\n 'x-status-code': '200',\\n 'x-content-type': 'application/json'\\n }\\n ),\\n error: T.Union([\\n T.Object(\\n {\\n error: T.String()\\n },\\n {\\n 'x-status-code': '404',\\n 'x-content-type': 'application/json'\\n }\\n )\\n ])\\n }\\n },\\n '/multiple-content': {\\n GET: {\\n args: T.Void(),\\n data: T.Object(\\n {\\n name: T.String()\\n },\\n {\\n 'x-status-code': '200',\\n 'x-content-type': 'application/json'\\n }\\n ),\\n error: T.Union([\\n T.Object(\\n {\\n error: T.String()\\n },\\n {\\n 'x-status-code': '404',\\n 'x-content-type': 'application/json'\\n }\\n )\\n ])\\n }\\n },\\n '/some-route/{id}': {\\n POST: {\\n args: T.Object({\\n headers: T.Object({\\n auth: T.String({ 'x-in': 'header' })\\n }),\\n params: T.Object({\\n id: T.String({ 'x-in': 'path' })\\n }),\\n query: T.Object({\\n filter: T.String({ 'x-in': 'query' }),\\n address: T.Array(T.String(), { 'x-in': 'query' }),\\n deep: T.Object(\\n {\\n deepTitle: T.Optional(T.String())\\n },\\n {\\n 'x-in': 'query'\\n }\\n )\\n }),\\n body: T.Object(\\n {\\n human: T.Object({\\n name: T.String(),\\n age: T.Optional(T.Number()),\\n gender: T.Union([T.Literal('batman'), T.Literal('joker')])\\n }),\\n address: CloneType(ComponentsSchemasDef1),\\n recursive: T.Object(\\n {},\\n {\\n additionalProperties: true\\n }\\n )\\n },\\n {\\n 'x-content-type': 'application/json'\\n }\\n )\\n }),\\n data: T.Object(\\n {\\n params: T.Object({\\n id: T.Optional(T.String())\\n }),\\n query: T.Object({\\n filter: T.String(),\\n address: T.Array(T.String()),\\n deep: T.Object({\\n deepTitle: T.String()\\n })\\n }),\\n body: T.Object({\\n human: T.Object({\\n name: T.String(),\\n age: T.Optional(T.Number()),\\n gender: T.Union([T.Literal('batman'), T.Literal('joker')])\\n }),\\n address: CloneType(ComponentsSchemasDef1),\\n recursive: T.Object(\\n {},\\n {\\n additionalProperties: true\\n }\\n )\\n })\\n },\\n {\\n 'x-status-code': '201',\\n 'x-content-type': 'application/json'\\n }\\n ),\\n error: T.Union([T.Any({ 'x-status-code': 'default' })])\\n }\\n }\\n}\\n\\nconst _components = {\\n schemas: {\\n 'def-0': CloneType(ComponentsSchemasDef0),\\n 'def-1': CloneType(ComponentsSchemasDef1)\\n }\\n}\\n\\nexport { schema, _components as components }\\n"
`;

exports[`nullable test 1`] = `
"/* eslint eslint-comments/no-unlimited-disable: off */\\n/* eslint-disable */\\n// This document was generated automatically by openapi-box\\n\\n/**\\n * @typedef {import('@sinclair/typebox').TSchema} TSchema\\n */\\n\\n/**\\n * @template {TSchema} T\\n * @typedef {import('@sinclair/typebox').Static<T>} Static\\n */\\n\\n/**\\n * @typedef {import('@sinclair/typebox').SchemaOptions} SchemaOptions\\n */\\n\\n/**\\n * @typedef {{\\n * [Path in keyof typeof schema]: {\\n * [Method in keyof typeof schema[Path]]: {\\n * [Prop in keyof typeof schema[Path][Method]]: typeof schema[Path][Method][Prop] extends TSchema ?\\n * Static<typeof schema[Path][Method][Prop]> :\\n * undefined\\n * }\\n * }\\n * }} SchemaType\\n */\\n\\n/**\\n * @typedef {{\\n * [ComponentType in keyof typeof _components]: {\\n * [ComponentName in keyof typeof _components[ComponentType]]: typeof _components[ComponentType][ComponentName] extends TSchema ?\\n * Static<typeof _components[ComponentType][ComponentName]> :\\n * undefined\\n * }\\n * }} ComponentType\\n */\\n\\nimport { Type as T, TypeRegistry, Kind, CloneType } from '@sinclair/typebox'\\nimport { Value } from '@sinclair/typebox/value'\\n\\n/**\\n * @typedef {{\\n * [Kind]: 'Binary'\\n * static: string | File | Blob | Uint8Array\\n * anyOf: [{\\n * type: 'object',\\n * additionalProperties: true\\n * }, {\\n * type: 'string',\\n * format: 'binary'\\n * }]\\n * } & TSchema} TBinary\\n */\\n\\n/**\\n * @returns {TBinary}\\n */\\nconst Binary = () => {\\n /**\\n * @param {TBinary} schema\\n * @param {unknown} value\\n * @returns {boolean}\\n */\\n function BinaryCheck(schema, value) {\\n const type = Object.prototype.toString.call(value)\\n return (\\n type === '[object Blob]' ||\\n type === '[object File]' ||\\n type === '[object String]' ||\\n type === '[object Uint8Array]'\\n )\\n }\\n\\n if (!TypeRegistry.Has('Binary')) TypeRegistry.Set('Binary', BinaryCheck)\\n\\n return /** @type {TBinary} */ ({\\n anyOf: [\\n {\\n type: 'object',\\n additionalProperties: true\\n },\\n {\\n type: 'string',\\n format: 'binary'\\n }\\n ],\\n [Kind]: 'Binary'\\n })\\n}\\n\\nconst ComponentsSchemasError = T.Object({\\n code: T.Integer({ format: 'int32' }),\\n message: T.String()\\n})\\nconst ComponentsSchemasPet = T.Object({\\n id: T.Integer({ format: 'int64' }),\\n name: T.String(),\\n tag: T.Optional(T.String())\\n})\\nconst ComponentsSchemasPets = T.Array(CloneType(ComponentsSchemasPet))\\n\\nconst schema = {\\n '/pets': {\\n GET: {\\n args: T.Optional(\\n T.Object({\\n query: T.Optional(\\n T.Object({\\n limit: T.Optional(T.Integer({ format: 'int32', 'x-in': 'query' }))\\n })\\n )\\n })\\n ),\\n data: CloneType(ComponentsSchemasPets, {\\n 'x-status-code': '200',\\n 'x-content-type': 'application/json'\\n }),\\n error: T.Union([\\n CloneType(ComponentsSchemasError, {\\n 'x-status-code': 'default',\\n 'x-content-type': 'application/json'\\n })\\n ])\\n },\\n POST: {\\n args: T.Void(),\\n data: T.Any({ 'x-status-code': '201' }),\\n error: T.Union([\\n CloneType(ComponentsSchemasError, {\\n 'x-status-code': 'default',\\n 'x-content-type': 'application/json'\\n })\\n ])\\n }\\n },\\n '/pets/{petId}': {\\n GET: {\\n args: T.Object({\\n params: T.Object({\\n petId: T.String({ 'x-in': 'path' })\\n })\\n }),\\n data: CloneType(ComponentsSchemasPet, {\\n 'x-status-code': '200',\\n 'x-content-type': 'application/json'\\n }),\\n error: T.Union([\\n CloneType(ComponentsSchemasError, {\\n 'x-status-code': 'default',\\n 'x-content-type': 'application/json'\\n })\\n ])\\n }\\n }\\n}\\n\\nconst _components = {\\n parameters: {\\n skipParam: T.Integer({ format: 'int32', 'x-in': 'query' }),\\n limitParam: T.Integer({ format: 'int32', 'x-in': 'query' })\\n },\\n responses: {\\n NotFound: T.Any({}),\\n IllegalInput: T.Any({}),\\n GeneralError: CloneType(ComponentsSchemasError, {\\n 'x-content-type': 'application/json'\\n })\\n },\\n requestBodies: {\\n Pet: CloneType(ComponentsSchemasPet, {\\n 'x-content-type': 'application/json'\\n })\\n },\\n schemas: {\\n Error: CloneType(ComponentsSchemasError),\\n Pet: CloneType(ComponentsSchemasPet),\\n Pets: CloneType(ComponentsSchemasPets)\\n }\\n}\\n\\nexport { schema, _components as components }\\n"
`;

exports[`petstore.json 1`] = `
"/* eslint eslint-comments/no-unlimited-disable: off */\\n/* eslint-disable */\\n// This document was generated automatically by openapi-box\\n\\n/**\\n * @typedef {import('@sinclair/typebox').TSchema} TSchema\\n */\\n\\n/**\\n * @template {TSchema} T\\n * @typedef {import('@sinclair/typebox').Static<T>} Static\\n */\\n\\n/**\\n * @typedef {import('@sinclair/typebox').SchemaOptions} SchemaOptions\\n */\\n\\n/**\\n * @typedef {{\\n * [Path in keyof typeof schema]: {\\n * [Method in keyof typeof schema[Path]]: {\\n * [Prop in keyof typeof schema[Path][Method]]: typeof schema[Path][Method][Prop] extends TSchema ?\\n * Static<typeof schema[Path][Method][Prop]> :\\n * undefined\\n * }\\n * }\\n * }} SchemaType\\n */\\n\\n/**\\n * @typedef {{\\n * [ComponentType in keyof typeof _components]: {\\n * [ComponentName in keyof typeof _components[ComponentType]]: typeof _components[ComponentType][ComponentName] extends TSchema ?\\n * Static<typeof _components[ComponentType][ComponentName]> :\\n * undefined\\n * }\\n * }} ComponentType\\n */\\n\\nimport { Type as T, TypeRegistry, Kind, CloneType } from '@sinclair/typebox'\\nimport { Value } from '@sinclair/typebox/value'\\n\\n/**\\n * @typedef {{\\n * [Kind]: 'Binary'\\n * static: string | File | Blob | Uint8Array\\n * anyOf: [{\\n * type: 'object',\\n * additionalProperties: true\\n * }, {\\n * type: 'string',\\n * format: 'binary'\\n * }]\\n * } & TSchema} TBinary\\n */\\n\\n/**\\n * @returns {TBinary}\\n */\\nconst Binary = () => {\\n /**\\n * @param {TBinary} schema\\n * @param {unknown} value\\n * @returns {boolean}\\n */\\n function BinaryCheck(schema, value) {\\n const type = Object.prototype.toString.call(value)\\n return (\\n type === '[object Blob]' ||\\n type === '[object File]' ||\\n type === '[object String]' ||\\n type === '[object Uint8Array]'\\n )\\n }\\n\\n if (!TypeRegistry.Has('Binary')) TypeRegistry.Set('Binary', BinaryCheck)\\n\\n return /** @type {TBinary} */ ({\\n anyOf: [\\n {\\n type: 'object',\\n additionalProperties: true\\n },\\n {\\n type: 'string',\\n format: 'binary'\\n }\\n ],\\n [Kind]: 'Binary'\\n })\\n}\\n\\nconst ComponentsSchemasError = T.Object({\\n code: T.Integer({ format: 'int32' }),\\n message: T.String()\\n})\\nconst ComponentsSchemasPet = T.Object({\\n id: T.Integer({ format: 'int64' }),\\n name: T.String(),\\n tag: T.Optional(T.String())\\n})\\nconst ComponentsSchemasPets = T.Array(CloneType(ComponentsSchemasPet))\\n\\nconst schema = {\\n '/pets': {\\n GET: {\\n args: T.Optional(\\n T.Object({\\n query: T.Optional(\\n T.Object({\\n limit: T.Optional(T.Integer({ format: 'int32', 'x-in': 'query' }))\\n })\\n )\\n })\\n ),\\n data: CloneType(ComponentsSchemasPets, {\\n 'x-status-code': '200',\\n 'x-content-type': 'application/json'\\n }),\\n error: T.Union([\\n CloneType(ComponentsSchemasError, {\\n 'x-status-code': 'default',\\n 'x-content-type': 'application/json'\\n })\\n ])\\n },\\n POST: {\\n args: T.Void(),\\n data: T.Any({ 'x-status-code': '201' }),\\n error: T.Union([\\n CloneType(ComponentsSchemasError, {\\n 'x-status-code': 'default',\\n 'x-content-type': 'application/json'\\n })\\n ])\\n }\\n },\\n '/pets/{petId}': {\\n GET: {\\n args: T.Object({\\n params: T.Object({\\n petId: T.String({ 'x-in': 'path' })\\n })\\n }),\\n data: CloneType(ComponentsSchemasPet, {\\n 'x-status-code': '200',\\n 'x-content-type': 'application/json'\\n }),\\n error: T.Union([\\n CloneType(ComponentsSchemasError, {\\n 'x-status-code': 'default',\\n 'x-content-type': 'application/json'\\n })\\n ])\\n }\\n }\\n}\\n\\nconst _components = {\\n parameters: {\\n skipParam: T.Integer({ format: 'int32', 'x-in': 'query' }),\\n limitParam: T.Integer({ format: 'int32', 'x-in': 'query' })\\n },\\n responses: {\\n NotFound: T.Any({}),\\n IllegalInput: T.Any({}),\\n GeneralError: CloneType(ComponentsSchemasError, {\\n 'x-content-type': 'application/json'\\n })\\n },\\n requestBodies: {\\n Pet: CloneType(ComponentsSchemasPet, {\\n 'x-content-type': 'application/json'\\n })\\n },\\n schemas: {\\n Error: CloneType(ComponentsSchemasError),\\n Pet: CloneType(ComponentsSchemasPet),\\n Pets: CloneType(ComponentsSchemasPets)\\n }\\n}\\n\\nexport { schema, _components as components }\\n"
`;
Expand Down
21 changes: 21 additions & 0 deletions test/test-allOf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
openapi: 3.0.3
info:
description: Title
version: 1.0.0
servers:
- url: https

components:
schemas:
A:
type: object
properties:
a:
type: string
AB:
type: object
properties:
b:
type: string
allOf:
- $ref: '#/components/schemas/A'
Loading