diff --git a/.gitignore b/.gitignore index e329f36..6bf2f12 100644 --- a/.gitignore +++ b/.gitignore @@ -51,7 +51,8 @@ typings/ # Output of 'npm pack' *.tgz -# Yarn Integrity file +# Yarn stuff +yarn.lock .yarn-integrity # dotenv environment variables file diff --git a/README.md b/README.md index 4f8b41a..65f91a9 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,10 @@ const options = { // Defaults to `false` for backwards compatibility, but in future versions // the effect of `true` is likely going to be the default and only way. It is // highly recommended that new implementations set this value to `true`. - nullableArrayItems: true + nullableArrayItems: true, + // Indicates how to define the `ID` scalar as part of a JSON Schema. Valid options + // are `string`, `number`, or `both`. Defaults to `string` + idTypeMapping: 'string' } // schema is your GraphQL schema. @@ -43,7 +46,7 @@ const jsonSchema = fromIntrospectionQuery(introspection, options); ```graphql type Todo { - id: String! + id: ID! name: String! completed: Boolean color: Color @@ -55,7 +58,7 @@ const jsonSchema = fromIntrospectionQuery(introspection, options); } type SimpleTodo { - id: String! + id: ID! name: String! } @@ -77,7 +80,7 @@ const jsonSchema = fromIntrospectionQuery(introspection, options); type Query { "A Query with 1 required argument and 1 optional argument" todo( - id: String!, + id: ID!, "A default value of false" isCompleted: Boolean=false ): Todo @@ -97,7 +100,7 @@ const jsonSchema = fromIntrospectionQuery(introspection, options); "A Mutation with 2 required arguments" update_todo( - id: String!, + id: ID!, data: TodoInputType! ): Todo! @@ -129,7 +132,7 @@ const options = { nullableArrayItems: true } arguments: { type: 'object', properties: { - id: { '$ref': '#/definitions/String' }, + id: { '$ref': '#/definitions/ID' }, isCompleted: { description: 'A default value of false', '$ref': '#/definitions/Boolean', @@ -192,7 +195,7 @@ const options = { nullableArrayItems: true } arguments: { type: 'object', properties: { - id: { '$ref': '#/definitions/String' }, + id: { '$ref': '#/definitions/ID' }, data: { '$ref': '#/definitions/TodoInputType' } }, required: [ 'id', 'data' ] @@ -235,7 +238,7 @@ const options = { nullableArrayItems: true } id: { type: 'object', properties: { - return: { '$ref': '#/definitions/String' }, + return: { '$ref': '#/definitions/ID' }, arguments: { type: 'object', properties: {}, required: [] } }, required: [] @@ -285,13 +288,18 @@ const options = { nullableArrayItems: true } }, required: [ 'id', 'name', 'colors' ] }, + ID: { + description: 'The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.', + type: 'string', + title: 'ID' + }, SimpleTodo: { type: 'object', properties: { id: { type: 'object', properties: { - return: { '$ref': '#/definitions/String' }, + return: { '$ref': '#/definitions/ID' }, arguments: { type: 'object', properties: {}, required: [] } }, required: [] diff --git a/__tests__/spec.ts b/__tests__/spec.ts index 4b4b76c..3b02beb 100644 --- a/__tests__/spec.ts +++ b/__tests__/spec.ts @@ -1,10 +1,13 @@ import ajv from 'ajv' import { JSONSchema6 } from 'json-schema' import { fromIntrospectionQuery } from '../lib/fromIntrospectionQuery' +import type { IDTypeMapping as IDTypeMappingType } from '../lib/types' import { getTodoSchemaIntrospection, todoSchemaAsJsonSchema, todoSchemaAsJsonSchemaWithoutNullableArrayItems, + todoSchemaAsJsonSchemaWithIdTypeNumber, + todoSchemaAsJsonSchemaWithIdTypeStringOrNumber, } from '../test-utils' describe('GraphQL to JSON Schema', () => { @@ -30,4 +33,30 @@ describe('GraphQL to JSON Schema', () => { validator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')) expect(validator.validateSchema(result)).toBe(true) }) + + test('from IntrospectionQuery object with idTypeMapping = "number"', () => { + const options = { + nullableArrayItems: true, + idTypeMapping: 'number' as IDTypeMappingType, + } + const result = fromIntrospectionQuery(introspection, options) + expect(result).toEqual(todoSchemaAsJsonSchemaWithIdTypeNumber) + const validator = new ajv() + validator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')) + expect(validator.validateSchema(result)).toBe(true) + }) + + test('from IntrospectionQuery object with idTypeMapping = "both"', () => { + const options = { + nullableArrayItems: true, + idTypeMapping: 'both' as IDTypeMappingType, + } + const result = fromIntrospectionQuery(introspection, options) + expect(result).toEqual( + todoSchemaAsJsonSchemaWithIdTypeStringOrNumber + ) + const validator = new ajv() + validator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')) + expect(validator.validateSchema(result)).toBe(true) + }) }) diff --git a/doc-exampleGenerator.ts b/doc-exampleGenerator.ts index 3fb51fc..2f968f5 100644 --- a/doc-exampleGenerator.ts +++ b/doc-exampleGenerator.ts @@ -14,7 +14,7 @@ const nativeScalarsToFilter = ['String', 'Int', 'Boolean'] const readmeSDL: string = ` type Todo { - id: String! + id: ID! name: String! completed: Boolean color: Color @@ -26,7 +26,7 @@ const readmeSDL: string = ` } type SimpleTodo { - id: String! + id: ID! name: String! } @@ -48,7 +48,7 @@ const readmeSDL: string = ` type Query { "A Query with 1 required argument and 1 optional argument" todo( - id: String!, + id: ID!, "A default value of false" isCompleted: Boolean=false ): Todo @@ -68,7 +68,7 @@ const readmeSDL: string = ` "A Mutation with 2 required arguments" update_todo( - id: String!, + id: ID!, data: TodoInputType! ): Todo! diff --git a/lib/fromIntrospectionQuery.ts b/lib/fromIntrospectionQuery.ts index d223cca..f0cdc20 100644 --- a/lib/fromIntrospectionQuery.ts +++ b/lib/fromIntrospectionQuery.ts @@ -2,7 +2,13 @@ import { IntrospectionQuery, IntrospectionType } from 'graphql' import { JSONSchema6 } from 'json-schema' import { includes, partition, reduce } from 'lodash' import { introspectionTypeReducer, JSONSchema6Acc } from './reducer' -import { filterDefinitionsTypes, isIntrospectionObjectType } from './typeGuards' +import { + ID_TYPE_MAPPING_OPTION_DEFAULT, + filterDefinitionsTypes, + isIntrospectionObjectType, +} from './typeGuards' + +import type { IDTypeMapping as IDTypeMappingType } from './types' // FIXME: finish this type export interface GraphQLJSONSchema6 extends JSONSchema6 { @@ -16,6 +22,7 @@ export interface GraphQLJSONSchema6 extends JSONSchema6 { export interface FromIntrospectionQueryOptions { ignoreInternals?: boolean nullableArrayItems?: boolean + idTypeMapping?: IDTypeMappingType } export const fromIntrospectionQuery = ( @@ -26,6 +33,7 @@ export const fromIntrospectionQuery = ( // Defaults ignoreInternals: true, nullableArrayItems: false, + idTypeMapping: ID_TYPE_MAPPING_OPTION_DEFAULT, // User-specified ...(opts || {}), } diff --git a/lib/reducer.ts b/lib/reducer.ts index 738663b..ca84a41 100644 --- a/lib/reducer.ts +++ b/lib/reducer.ts @@ -20,7 +20,12 @@ import { isIntrospectionScalarType, isIntrospectionDefaultScalarType, } from './typeGuards' -import { graphqlToJSONType, typesMapping } from './typesMapping' +import { graphqlToJSONType, scalarToJsonType } from './typesMapping' + +import type { + GraphQLTypeNames, + IDTypeMapping as IDTypeMappingType, +} from './types' export type JSONSchema6Acc = { [k: string]: JSONSchema6 @@ -28,6 +33,7 @@ export type JSONSchema6Acc = { type ReducerOptions = { nullableArrayItems?: boolean + idTypeMapping?: IDTypeMappingType } type GetRequiredFieldsType = ReadonlyArray< @@ -199,7 +205,7 @@ export const introspectionTypeReducer: ( } } else if (isIntrospectionDefaultScalarType(curr)) { acc[curr.name] = { - type: (typesMapping as any)[curr.name], + type: scalarToJsonType(curr.name as GraphQLTypeNames, options), title: curr.name, } } else if (isIntrospectionScalarType(curr)) { diff --git a/lib/typeGuards.ts b/lib/typeGuards.ts index 7600a1e..dc37b4d 100644 --- a/lib/typeGuards.ts +++ b/lib/typeGuards.ts @@ -19,14 +19,24 @@ import { } from 'graphql' import { filter, has, startsWith, includes } from 'lodash' -export const SUPPORTED_KINDS = [ +export { ID_TYPE_MAPPING_OPTION_DEFAULT } from './typesMapping' + +export const SUPPORTED_SCALARS = Object.freeze([ + 'Boolean', + 'String', + 'Int', + 'Float', + 'ID', +]) + +export const SUPPORTED_KINDS = Object.freeze([ TypeKind.SCALAR, TypeKind.OBJECT, TypeKind.INPUT_OBJECT, TypeKind.INTERFACE, TypeKind.ENUM, TypeKind.UNION, -] +]) /////////////////// /// Type guards /// @@ -81,8 +91,7 @@ export const isIntrospectionUnionType = ( export const isIntrospectionDefaultScalarType = ( type: IntrospectionSchema['types'][0] ): type is IntrospectionScalarType => - type.kind === TypeKind.SCALAR && - includes(['Boolean', 'String', 'Int', 'Float'], type.name) + type.kind === TypeKind.SCALAR && includes(SUPPORTED_SCALARS, type.name) // Ignore all GraphQL native Scalars, directives, etc... export interface FilterDefinitionsTypesOptions { diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000..712f7c7 --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,2 @@ +export type GraphQLTypeNames = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID' +export type IDTypeMapping = 'string' | 'number' | 'both' diff --git a/lib/typesMapping.ts b/lib/typesMapping.ts index 8e91070..5dd28c0 100644 --- a/lib/typesMapping.ts +++ b/lib/typesMapping.ts @@ -14,16 +14,36 @@ import { isNonNullIntrospectionType, } from './typeGuards' -export type GraphQLTypeNames = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID' +import { GraphQLTypeNames, IDTypeMapping as IDTypeMappingType } from './types' -export const typesMapping: { [k in GraphQLTypeNames]: JSONSchema6TypeName } = { +export const ID_TYPE_MAPPING_OPTION_DEFAULT = 'string' as IDTypeMappingType + +const ID_TYPES: { + [k in IDTypeMappingType]: JSONSchema6TypeName | JSONSchema6TypeName[] +} = { + string: 'string', + number: 'number', + both: ['string', 'number'], +} + +const SCALAR_TO_JSON: { + [k in GraphQLTypeNames]: JSONSchema6TypeName | JSONSchema6TypeName[] +} = { Boolean: 'boolean', String: 'string', Int: 'number', Float: 'number', - ID: 'string', + ID: ID_TYPES[ID_TYPE_MAPPING_OPTION_DEFAULT], } +export const scalarToJsonType = ( + scalarName: GraphQLTypeNames, + options: GraphqlToJSONTypeOptions = {} +): JSONSchema6TypeName | JSONSchema6TypeName[] => + Object.assign({}, SCALAR_TO_JSON, { + ID: ID_TYPES[options.idTypeMapping || ID_TYPE_MAPPING_OPTION_DEFAULT], + })[scalarName] + // Convert a GraphQL Type to a valid JSON Schema type export type GraphqlToJSONTypeArg = | IntrospectionTypeRef @@ -34,6 +54,7 @@ export type GraphqlToJSONTypeOptions = { nullableArrayItems?: boolean isArray?: boolean isNonNull?: boolean + idTypeMapping?: IDTypeMappingType } export const graphqlToJSONType = ( @@ -59,7 +80,7 @@ export const graphqlToJSONType = ( if (includes(SUPPORTED_KINDS, k.kind)) { jsonType.$ref = `#/definitions/${name}` } else { - jsonType.type = (typesMapping as any)[name] + jsonType.type = scalarToJsonType(name as GraphQLTypeNames, options) } // Only if the option allows for it, represent an array with nullable items diff --git a/test-utils.ts b/test-utils.ts index e346dec..4dfab83 100644 --- a/test-utils.ts +++ b/test-utils.ts @@ -49,7 +49,7 @@ export const getTodoSchemaIntrospection = (): GetTodoSchemaIntrospectionResult = "A simpler ToDo Object" type SimpleTodo { - id: String! + id: ID! name: String! } @@ -133,484 +133,539 @@ export const getTodoSchemaIntrospection = (): GetTodoSchemaIntrospectionResult = } } -export const todoSchemaAsJsonSchema: JSONSchema6 = { - $schema: 'http://json-schema.org/draft-06/schema#', - properties: { - Query: { - type: 'object', - properties: { - todo: { - type: 'object', - properties: { - arguments: { - type: 'object', - properties: { - id: { - $ref: '#/definitions/String', - description: 'todo identifier', - }, - isCompleted: { $ref: '#/definitions/Boolean', default: false }, - requiredNonNullStrings: { - type: 'array', - items: { $ref: '#/definitions/String' }, - }, - optionalNonNullStrings: { - type: 'array', - items: { +export const todoSchemaAsJsonSchema: JSONSchema6 = generateBaseSchema() + +export const todoSchemaAsJsonSchemaWithoutNullableArrayItems: JSONSchema6 = cloneDeepWith( + generateBaseSchema(), + (value, key, object, stack) => { + // Convert the new way back to the old way + if ( + key === 'items' && + isEqual(Object.keys(value), ['anyOf']) && + value.anyOf.length === 2 && + value.anyOf.find((e: any) => isEqual(e, { type: 'null' })) + ) { + return value.anyOf.find((e: any) => !isEqual(e, { type: 'null' })) + } + } +) + +export const todoSchemaAsJsonSchemaWithIdTypeNumber: JSONSchema6 = cloneDeepWith( + generateBaseSchema(), + (value, key, object, stack) => { + if (key === 'type' && object?.title === 'ID') { + return 'number' + } + } +) + +export const todoSchemaAsJsonSchemaWithIdTypeStringOrNumber: JSONSchema6 = cloneDeepWith( + generateBaseSchema(), + (value, key, object, stack) => { + if (key === 'type' && object?.title === 'ID') { + return ['string', 'number'] + } + } +) + +function generateBaseSchema() { + return { + $schema: 'http://json-schema.org/draft-06/schema#', + properties: { + Query: { + type: 'object', + properties: { + todo: { + type: 'object', + properties: { + arguments: { + type: 'object', + properties: { + id: { $ref: '#/definitions/String', + description: 'todo identifier', }, - }, - requiredNullableStrings: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/String' }, { type: 'null' }], + isCompleted: { + $ref: '#/definitions/Boolean', + default: false, }, - }, - optionalNullableStringsWithDefault: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/String' }, { type: 'null' }], + requiredNonNullStrings: { + type: 'array', + items: { $ref: '#/definitions/String' }, + }, + optionalNonNullStrings: { + type: 'array', + items: { + $ref: '#/definitions/String', + }, + }, + requiredNullableStrings: { + type: 'array', + items: { + anyOf: [ + { $ref: '#/definitions/String' }, + { type: 'null' }, + ], + }, + }, + optionalNullableStringsWithDefault: { + type: 'array', + items: { + anyOf: [ + { $ref: '#/definitions/String' }, + { type: 'null' }, + ], + }, + default: ['foo'], }, - default: ['foo'], - }, - color: { $ref: '#/definitions/Color' }, - requiredColor: { $ref: '#/definitions/Color' }, - requiredColorWithDefault: { - $ref: '#/definitions/Color', - default: 'RED', - }, + color: { $ref: '#/definitions/Color' }, + requiredColor: { $ref: '#/definitions/Color' }, + requiredColorWithDefault: { + $ref: '#/definitions/Color', + default: 'RED', + }, - colors: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/Color' }, { type: 'null' }], + colors: { + type: 'array', + items: { + anyOf: [ + { $ref: '#/definitions/Color' }, + { type: 'null' }, + ], + }, }, - }, - requiredColors: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/Color' }, { type: 'null' }], + requiredColors: { + type: 'array', + items: { + anyOf: [ + { $ref: '#/definitions/Color' }, + { type: 'null' }, + ], + }, }, - }, - requiredColorsNonNullable: { - type: 'array', - items: { $ref: '#/definitions/Color' }, - }, - requiredColorsWithDefault: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/Color' }, { type: 'null' }], + requiredColorsNonNullable: { + type: 'array', + items: { $ref: '#/definitions/Color' }, + }, + requiredColorsWithDefault: { + type: 'array', + items: { + anyOf: [ + { $ref: '#/definitions/Color' }, + { type: 'null' }, + ], + }, + default: ['GREEN', 'RED'], + }, + requiredColorsNonNullableWithDefault: { + type: 'array', + items: { $ref: '#/definitions/Color' }, + default: ['GREEN', 'RED'], }, - default: ['GREEN', 'RED'], - }, - requiredColorsNonNullableWithDefault: { - type: 'array', - items: { $ref: '#/definitions/Color' }, - default: ['GREEN', 'RED'], }, + required: [ + 'id', + 'requiredNonNullStrings', + 'requiredNullableStrings', + 'requiredColor', + 'requiredColorWithDefault', + 'requiredColors', + 'requiredColorsNonNullable', + 'requiredColorsWithDefault', + 'requiredColorsNonNullableWithDefault', + ], + }, + return: { + $ref: '#/definitions/Todo', }, - required: [ - 'id', - 'requiredNonNullStrings', - 'requiredNullableStrings', - 'requiredColor', - 'requiredColorWithDefault', - 'requiredColors', - 'requiredColorsNonNullable', - 'requiredColorsWithDefault', - 'requiredColorsNonNullableWithDefault', - ], - }, - return: { - $ref: '#/definitions/Todo', }, + required: [], }, - required: [], - }, - todos: { - type: 'object', - properties: { - arguments: { - type: 'object', - properties: {}, - required: [], - }, - return: { - type: 'array', - items: { $ref: '#/definitions/Todo' }, + todos: { + type: 'object', + properties: { + arguments: { + type: 'object', + properties: {}, + required: [], + }, + return: { + type: 'array', + items: { $ref: '#/definitions/Todo' }, + }, }, + required: [], }, - required: [], - }, - todoUnions: { - type: 'object', - properties: { - arguments: { - type: 'object', - properties: {}, - required: [], - }, - return: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/TodoUnion' }, { type: 'null' }], + todoUnions: { + type: 'object', + properties: { + arguments: { + type: 'object', + properties: {}, + required: [], + }, + return: { + type: 'array', + items: { + anyOf: [ + { $ref: '#/definitions/TodoUnion' }, + { type: 'null' }, + ], + }, }, }, + required: [], }, - required: [], - }, - node: { - type: 'object', - properties: { - arguments: { - type: 'object', - properties: { - id: { - description: 'Node identifier', - $ref: '#/definitions/String', + node: { + type: 'object', + properties: { + arguments: { + type: 'object', + properties: { + id: { + description: 'Node identifier', + $ref: '#/definitions/String', + }, }, + required: ['id'], + }, + return: { + $ref: '#/definitions/Node', }, - required: ['id'], - }, - return: { - $ref: '#/definitions/Node', }, + required: [], }, - required: [], }, + // Inappropriate for individual queries to be required, despite possibly having + // NON_NULL return types + required: [], }, - // Inappropriate for individual queries to be required, despite possibly having - // NON_NULL return types - required: [], - }, - Mutation: { - type: 'object', - properties: { - update_todo: { - type: 'object', - properties: { - arguments: { - type: 'object', - properties: { - id: { $ref: '#/definitions/String' }, - todo: { $ref: '#/definitions/TodoInputType' }, + Mutation: { + type: 'object', + properties: { + update_todo: { + type: 'object', + properties: { + arguments: { + type: 'object', + properties: { + id: { $ref: '#/definitions/String' }, + todo: { $ref: '#/definitions/TodoInputType' }, + }, + required: ['id', 'todo'], + }, + return: { + $ref: '#/definitions/Todo', }, - required: ['id', 'todo'], - }, - return: { - $ref: '#/definitions/Todo', }, + required: [], }, - required: [], - }, - create_todo: { - type: 'object', - properties: { - arguments: { - type: 'object', - properties: { - todo: { $ref: '#/definitions/TodoInputType' }, + create_todo: { + type: 'object', + properties: { + arguments: { + type: 'object', + properties: { + todo: { $ref: '#/definitions/TodoInputType' }, + }, + required: ['todo'], + }, + return: { + $ref: '#/definitions/Todo', }, - required: ['todo'], - }, - return: { - $ref: '#/definitions/Todo', }, + required: [], }, - required: [], - }, - create_todo_union: { - type: 'object', - properties: { - arguments: { - type: 'object', - properties: { - id: { $ref: '#/definitions/String' }, + create_todo_union: { + type: 'object', + properties: { + arguments: { + type: 'object', + properties: { + id: { $ref: '#/definitions/String' }, + }, + required: ['id'], + }, + return: { + $ref: '#/definitions/TodoUnion', }, - required: ['id'], - }, - return: { - $ref: '#/definitions/TodoUnion', }, + required: [], }, - required: [], }, + // Inappropriate for individual mutations to be required, despite possibly having + // NON_NULL return types + required: [], }, - // Inappropriate for individual mutations to be required, despite possibly having - // NON_NULL return types - required: [], - }, - }, - definitions: { - Boolean: { - type: 'boolean', - title: 'Boolean', - description: 'The `Boolean` scalar type represents `true` or `false`.', - }, - String: { - type: 'string', - title: 'String', - description: - 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', }, - Todo: { - type: 'object', - description: 'A ToDo Object', - properties: { - id: { - description: 'A unique identifier', - type: 'object', - properties: { - return: { $ref: '#/definitions/String' }, - arguments: { type: 'object', properties: {}, required: [] }, - }, - required: [], - }, - name: { - type: 'object', - properties: { - return: { $ref: '#/definitions/String' }, - arguments: { type: 'object', properties: {}, required: [] }, + definitions: { + ID: { + type: 'string', + title: 'ID', + description: + 'The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.', + }, + Boolean: { + type: 'boolean', + title: 'Boolean', + description: 'The `Boolean` scalar type represents `true` or `false`.', + }, + String: { + type: 'string', + title: 'String', + description: + 'The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.', + }, + Todo: { + type: 'object', + description: 'A ToDo Object', + properties: { + id: { + description: 'A unique identifier', + type: 'object', + properties: { + return: { $ref: '#/definitions/String' }, + arguments: { type: 'object', properties: {}, required: [] }, + }, + required: [], }, - required: [], - }, - completed: { - type: 'object', - properties: { - return: { $ref: '#/definitions/Boolean' }, - arguments: { type: 'object', properties: {}, required: [] }, + name: { + type: 'object', + properties: { + return: { $ref: '#/definitions/String' }, + arguments: { type: 'object', properties: {}, required: [] }, + }, + required: [], }, - required: [], - }, - color: { - type: 'object', - properties: { - return: { $ref: '#/definitions/Color' }, - arguments: { type: 'object', properties: {}, required: [] }, + completed: { + type: 'object', + properties: { + return: { $ref: '#/definitions/Boolean' }, + arguments: { type: 'object', properties: {}, required: [] }, + }, + required: [], }, - required: [], - }, - requiredColors: { - description: - 'A required list containing colors that cannot contain nulls', - type: 'object', - properties: { - return: { - type: 'array', - items: { $ref: '#/definitions/Color' }, + color: { + type: 'object', + properties: { + return: { $ref: '#/definitions/Color' }, + arguments: { type: 'object', properties: {}, required: [] }, }, - arguments: { type: 'object', properties: {}, required: [] }, + required: [], }, - required: [], - }, - optionalColors: { - description: - 'A non-required list containing colors that cannot contain nulls', - type: 'object', - properties: { - return: { - type: 'array', - items: { $ref: '#/definitions/Color' }, + requiredColors: { + description: + 'A required list containing colors that cannot contain nulls', + type: 'object', + properties: { + return: { + type: 'array', + items: { $ref: '#/definitions/Color' }, + }, + arguments: { type: 'object', properties: {}, required: [] }, }, - arguments: { type: 'object', properties: {}, required: [] }, + required: [], }, - required: [], - }, - fieldWithOptionalArgument: { - type: 'object', - properties: { - return: { - type: 'array', - items: { $ref: '#/definitions/String' }, + optionalColors: { + description: + 'A non-required list containing colors that cannot contain nulls', + type: 'object', + properties: { + return: { + type: 'array', + items: { $ref: '#/definitions/Color' }, + }, + arguments: { type: 'object', properties: {}, required: [] }, }, - arguments: { - type: 'object', - properties: { - optionalFilter: { - type: 'array', - items: { $ref: '#/definitions/String' }, + required: [], + }, + fieldWithOptionalArgument: { + type: 'object', + properties: { + return: { + type: 'array', + items: { $ref: '#/definitions/String' }, + }, + arguments: { + type: 'object', + properties: { + optionalFilter: { + type: 'array', + items: { $ref: '#/definitions/String' }, + }, }, + required: [], }, - required: [], }, + required: [], }, - required: [], - }, - fieldWithRequiredArgument: { - type: 'object', - properties: { - return: { - type: 'array', - items: { $ref: '#/definitions/String' }, - }, - arguments: { - type: 'object', - properties: { - requiredFilter: { - type: 'array', - items: { $ref: '#/definitions/String' }, + fieldWithRequiredArgument: { + type: 'object', + properties: { + return: { + type: 'array', + items: { $ref: '#/definitions/String' }, + }, + arguments: { + type: 'object', + properties: { + requiredFilter: { + type: 'array', + items: { $ref: '#/definitions/String' }, + }, }, + required: ['requiredFilter'], }, - required: ['requiredFilter'], }, + required: [], }, - required: [], - }, - nullableFieldThatReturnsListOfNonNullStrings: { - type: 'object', - properties: { - return: { - type: 'array', - items: { $ref: '#/definitions/String' }, - }, - arguments: { - type: 'object', - properties: { - nonRequiredArgumentOfNullableStrings: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/String' }, { type: 'null' }], + nullableFieldThatReturnsListOfNonNullStrings: { + type: 'object', + properties: { + return: { + type: 'array', + items: { $ref: '#/definitions/String' }, + }, + arguments: { + type: 'object', + properties: { + nonRequiredArgumentOfNullableStrings: { + type: 'array', + items: { + anyOf: [ + { $ref: '#/definitions/String' }, + { type: 'null' }, + ], + }, }, - }, - nonRequiredArgumentOfNonNullableStrings: { - type: 'array', - items: { $ref: '#/definitions/String' }, - }, - requiredArgumentOfNullableStrings: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/String' }, { type: 'null' }], + nonRequiredArgumentOfNonNullableStrings: { + type: 'array', + items: { $ref: '#/definitions/String' }, + }, + requiredArgumentOfNullableStrings: { + type: 'array', + items: { + anyOf: [ + { $ref: '#/definitions/String' }, + { type: 'null' }, + ], + }, + }, + requiredArgumentOfNonNullableStrings: { + type: 'array', + items: { $ref: '#/definitions/String' }, }, }, - requiredArgumentOfNonNullableStrings: { - type: 'array', - items: { $ref: '#/definitions/String' }, - }, + required: [ + 'requiredArgumentOfNullableStrings', + 'requiredArgumentOfNonNullableStrings', + ], }, - required: [ - 'requiredArgumentOfNullableStrings', - 'requiredArgumentOfNonNullableStrings', - ], }, + required: [], }, - required: [], - }, - nullableFieldThatReturnsListOfNullableStrings: { - type: 'object', - properties: { - return: { - type: 'array', - items: { - anyOf: [{ $ref: '#/definitions/String' }, { type: 'null' }], + nullableFieldThatReturnsListOfNullableStrings: { + type: 'object', + properties: { + return: { + type: 'array', + items: { + anyOf: [{ $ref: '#/definitions/String' }, { type: 'null' }], + }, }, + arguments: { type: 'object', properties: {}, required: [] }, }, - arguments: { type: 'object', properties: {}, required: [] }, + required: [], }, - required: [], }, + required: ['id', 'name', 'requiredColors'], }, - required: ['id', 'name', 'requiredColors'], - }, - SimpleTodo: { - type: 'object', - description: 'A simpler ToDo Object', - properties: { - id: { - type: 'object', - properties: { - return: { $ref: '#/definitions/String' }, - arguments: { type: 'object', properties: {}, required: [] }, + SimpleTodo: { + type: 'object', + description: 'A simpler ToDo Object', + properties: { + id: { + type: 'object', + properties: { + return: { $ref: '#/definitions/ID' }, + arguments: { type: 'object', properties: {}, required: [] }, + }, + required: [], }, - required: [], - }, - name: { - type: 'object', - properties: { - return: { $ref: '#/definitions/String' }, - arguments: { type: 'object', properties: {}, required: [] }, + name: { + type: 'object', + properties: { + return: { $ref: '#/definitions/String' }, + arguments: { type: 'object', properties: {}, required: [] }, + }, + required: [], }, - required: [], }, + required: ['id', 'name'], }, - required: ['id', 'name'], - }, - Color: { - // Yes, ENUM types should be the JSON built-in "string" type - type: 'string', - anyOf: [ - { - enum: ['RED'], - title: 'Red color', - description: 'Red color', - }, - { - enum: ['GREEN'], - title: 'Green color', - description: 'Green color', + Color: { + // Yes, ENUM types should be the JSON built-in "string" type + type: 'string', + anyOf: [ + { + enum: ['RED'], + title: 'Red color', + description: 'Red color', + }, + { + enum: ['GREEN'], + title: 'Green color', + description: 'Green color', + }, + ], + }, + TodoInputType: { + type: 'object', + description: + 'A type that describes ToDoInputType. Its description might not\nfit within the bounds of 80 width and so you want MULTILINE', + properties: { + name: { $ref: '#/definitions/String' }, + completed: { $ref: '#/definitions/Boolean' }, + color: { default: 'RED', $ref: '#/definitions/Color' }, + contactInfo: { + $ref: '#/definitions/ContactInfoInputType', + default: { email: 'spam@example.dev' }, + }, }, - ], - }, - TodoInputType: { - type: 'object', - description: - 'A type that describes ToDoInputType. Its description might not\nfit within the bounds of 80 width and so you want MULTILINE', - properties: { - name: { $ref: '#/definitions/String' }, - completed: { $ref: '#/definitions/Boolean' }, - color: { default: 'RED', $ref: '#/definitions/Color' }, - contactInfo: { - $ref: '#/definitions/ContactInfoInputType', - default: { email: 'spam@example.dev' }, + required: ['name'], + }, + ContactInfoInputType: { + type: 'object', + description: 'Description of ContactInfoInputType.', + properties: { + email: { $ref: '#/definitions/String' }, }, + required: [], }, - required: ['name'], - }, - ContactInfoInputType: { - type: 'object', - description: 'Description of ContactInfoInputType.', - properties: { - email: { $ref: '#/definitions/String' }, + TodoUnion: { + description: 'A Union of Todo and SimpleTodo', + oneOf: [ + { $ref: '#/definitions/Todo' }, + { $ref: '#/definitions/SimpleTodo' }, + ], }, - required: [], - }, - TodoUnion: { - description: 'A Union of Todo and SimpleTodo', - oneOf: [ - { $ref: '#/definitions/Todo' }, - { $ref: '#/definitions/SimpleTodo' }, - ], - }, - Node: { - type: 'object', - description: 'Anything with an ID can be a node', - properties: { - id: { - type: 'object', - description: 'A unique identifier', - properties: { - return: { $ref: '#/definitions/String' }, - arguments: { type: 'object', properties: {}, required: [] }, + Node: { + type: 'object', + description: 'Anything with an ID can be a node', + properties: { + id: { + type: 'object', + description: 'A unique identifier', + properties: { + return: { $ref: '#/definitions/String' }, + arguments: { type: 'object', properties: {}, required: [] }, + }, + required: [], }, - required: [], }, + required: ['id'], }, - required: ['id'], }, - }, + } as JSONSchema6 } - -export const todoSchemaAsJsonSchemaWithoutNullableArrayItems: JSONSchema6 = cloneDeepWith( - todoSchemaAsJsonSchema, - (value, key, object, stack) => { - // Convert the new way back to the old way - if ( - key === 'items' && - isEqual(Object.keys(value), ['anyOf']) && - value.anyOf.length === 2 && - value.anyOf.find((e: any) => isEqual(e, { type: 'null' })) - ) { - return value.anyOf.find((e: any) => !isEqual(e, { type: 'null' })) - } - } -)