Skip to content

Commit fc1b6b7

Browse files
authored
fix: compound types (#13)
* fix: compound types * fix: remove unnecessary snapshot
1 parent 7849446 commit fc1b6b7

File tree

4 files changed

+46
-6
lines changed

4 files changed

+46
-6
lines changed

src/writer.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,18 @@ export const write = async (source, opts = {}) => {
270270
? 'T.Intersect' // allOff
271271
: 'T.Union' // oneOf
272272

273-
const list = anyOf || allOf || oneOf
273+
let list = anyOf || allOf || oneOf
274+
275+
if ('properties' in options) {
276+
const { properties, required = [] } = options
277+
delete options.properties
278+
delete options.required
279+
280+
for (const key in properties) {
281+
list = list.concat({ type: 'object', properties, required })
282+
break
283+
}
284+
}
274285

275286
w.write(`${compoundType}(`)
276287
// TODO: use ref

test/index.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,22 @@ test('parse some openapi examples', async (t) => {
393393

394394
test('nullable test', async (t) => {
395395
await writeFile('./tmp/test-nullable.yaml.js', await write('./test/test-nullable.yaml'))
396-
t.assert.snapshot(await readFile('./tmp/petstore.yaml.js', 'utf8'))
397396
const { components } = await import('../tmp/test-nullable.yaml.js')
398397
assert.deepEqual(components.schemas.Test, Type.Union([Type.Null(), Type.Object({
399398
testStr: Type.Optional(Type.Union([Type.Null(), Type.String({ minLength: 2, maxLength: 2 })])),
400399
testArr: Type.Union([Type.Null(), Type.Array(Type.Number())]),
401400
})]))
402401
})
402+
403+
test('allOf test', async (t) => {
404+
await writeFile('./tmp/test-allOf.yaml.js', await write('./test/test-allOf.yaml'))
405+
const { components } = await import('../tmp/test-allOf.yaml.js')
406+
assert.deepEqual(components.schemas.AB, Type.Intersect([
407+
Type.Object({
408+
a: Type.Optional(Type.String())
409+
}),
410+
Type.Object({
411+
b: Type.Optional(Type.String())
412+
})
413+
]))
414+
})

test/index.js.snapshot

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ exports[`basic test > esm 1`] = `
66
"/* 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"
77
`;
88

9-
exports[`nullable test 1`] = `
10-
"/* 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"
11-
`;
12-
139
exports[`petstore.json 1`] = `
1410
"/* 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"
1511
`;

test/test-allOf.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
openapi: 3.0.3
2+
info:
3+
description: Title
4+
version: 1.0.0
5+
servers:
6+
- url: https
7+
8+
components:
9+
schemas:
10+
A:
11+
type: object
12+
properties:
13+
a:
14+
type: string
15+
AB:
16+
type: object
17+
properties:
18+
b:
19+
type: string
20+
allOf:
21+
- $ref: '#/components/schemas/A'

0 commit comments

Comments
 (0)