UNPKG

@omnigraph/json-schema

Version:

This package generates GraphQL Schema from JSON Schema and sample JSON request and responses. You can define your root field endpoints like below in your GraphQL Config for example;

1,009 lines (1,006 loc) • 70.3 kB
/* eslint-disable no-case-declarations */ import { getNamedType, GraphQLBoolean, GraphQLFloat, GraphQLInt, GraphQLString, isNonNullType, } from 'graphql'; import { EnumTypeComposer, InputTypeComposer, InterfaceTypeComposer, isSomeInputTypeComposer, ListComposer, ObjectTypeComposer, ScalarTypeComposer, SchemaComposer, UnionTypeComposer, } from 'graphql-compose'; import { GraphQLBigInt, GraphQLByte, GraphQLDateTime, GraphQLEmailAddress, GraphQLIPv4, GraphQLIPv6, GraphQLJSON, GraphQLNegativeFloat, GraphQLNegativeInt, GraphQLNonEmptyString, GraphQLNonNegativeFloat, GraphQLNonNegativeInt, GraphQLNonPositiveFloat, GraphQLNonPositiveInt, GraphQLPositiveFloat, GraphQLPositiveInt, GraphQLTime, GraphQLTimestamp, GraphQLURL, GraphQLUUID, } from 'graphql-scalars'; import { visitJSONSchema } from 'json-machete'; import { sanitizeNameForGraphQL } from '@graphql-mesh/utils'; import { DictionaryDirective, DiscriminatorDirective, EnumDirective, ExampleDirective, FlattenDirective, LengthDirective, OneOfDirective, RegExpDirective, ResolveRootDirective, ResolveRootFieldDirective, TypeScriptDirective, } from './directives.js'; import { getJSONSchemaStringFormatScalarMap } from './getJSONSchemaStringFormatScalarMap.js'; import { getUnionTypeComposers } from './getUnionTypeComposers.js'; import { getValidTypeName } from './getValidTypeName.js'; import { GraphQLFile, GraphQLVoid } from './scalars.js'; const formatScalarMapWithoutAjv = { byte: GraphQLByte, binary: GraphQLFile, 'date-time': GraphQLDateTime, time: GraphQLTime, email: GraphQLEmailAddress, ipv4: GraphQLIPv4, ipv6: GraphQLIPv6, uri: GraphQLURL, uuid: GraphQLUUID, 'unix-time': GraphQLTimestamp, int64: GraphQLBigInt, int32: GraphQLInt, decimal: GraphQLFloat, float: GraphQLFloat, }; const deepMergedInputTypeComposerFields = new WeakMap(); function deepMergeInputTypeComposerFields(existingInputTypeComposer, newInputTypeComposer) { let mergedInputTypes = deepMergedInputTypeComposerFields.get(existingInputTypeComposer); if (!mergedInputTypes) { mergedInputTypes = new WeakSet(); deepMergedInputTypeComposerFields.set(existingInputTypeComposer, mergedInputTypes); } if (mergedInputTypes.has(newInputTypeComposer)) { return; } mergedInputTypes.add(newInputTypeComposer); const existingInputTypeComposerFields = existingInputTypeComposer.getFields(); const newInputTypeComposerFields = newInputTypeComposer.getFields(); for (const [newFieldKey, newFieldValue] of Object.entries(newInputTypeComposerFields)) { const existingFieldValue = existingInputTypeComposerFields[newFieldKey]; if (!existingFieldValue) { existingInputTypeComposerFields[newFieldKey] = newFieldValue; } else { const existingFieldUnwrappedTC = typeof existingFieldValue.type?.getUnwrappedTC === 'function' ? existingFieldValue.type?.getUnwrappedTC() : undefined; const newFieldUnwrappedTC = typeof newFieldValue.type.getUnwrappedTC === 'function' ? newFieldValue.type.getUnwrappedTC() : undefined; if (existingFieldUnwrappedTC instanceof InputTypeComposer && newFieldUnwrappedTC instanceof InputTypeComposer) { deepMergeInputTypeComposerFields(existingFieldUnwrappedTC, newFieldUnwrappedTC); } else { existingInputTypeComposerFields[newFieldKey] = newFieldValue; } } } } function deepMergeObjectTypeComposerFields(existingObjectTypeComposer, newObjectTypeComposer) { const existingObjectTypeComposerFields = existingObjectTypeComposer.getFields(); const newObjectTypeComposerFields = newObjectTypeComposer.getFields(); for (const [newFieldKey, newFieldValue] of Object.entries(newObjectTypeComposerFields)) { const existingFieldValue = existingObjectTypeComposerFields[newFieldKey]; if (!existingFieldValue) { existingObjectTypeComposerFields[newFieldKey] = newFieldValue; } else { const existingFieldUnwrappedTC = typeof existingFieldValue.type?.getUnwrappedTC === 'function' ? existingFieldValue.type?.getUnwrappedTC() : undefined; const newFieldUnwrappedTC = typeof newFieldValue.type.getUnwrappedTC === 'function' ? newFieldValue.type.getUnwrappedTC() : undefined; if (existingFieldUnwrappedTC instanceof ObjectTypeComposer && newFieldUnwrappedTC instanceof ObjectTypeComposer) { deepMergeObjectTypeComposerFields(existingFieldUnwrappedTC, newFieldUnwrappedTC); } else { if (newFieldUnwrappedTC && existingFieldUnwrappedTC && !isUnspecificType(newFieldUnwrappedTC) && isUnspecificType(existingFieldUnwrappedTC)) { continue; } existingObjectTypeComposerFields[newFieldKey] = newFieldValue; } } } } export function getComposerFromJSONSchema({ subgraphName, schema, logger, getScalarForFormat, }) { const schemaComposer = new SchemaComposer(); const formatScalarMap = getJSONSchemaStringFormatScalarMap(); const getDefaultScalarForFormat = (format) => formatScalarMapWithoutAjv[format] || formatScalarMap.get(format); const rootInputTypeNameComposerMap = { QueryInput: () => schemaComposer.Query, MutationInput: () => schemaComposer.Mutation, SubscriptionInput: () => schemaComposer.Subscription, }; return visitJSONSchema(schema, { enter(subSchema, { path, visitedSubschemaResultMap }) { if (typeof subSchema === 'boolean' || subSchema.title === 'Any') { const typeComposer = schemaComposer.getAnyTC(GraphQLJSON); return subSchema ? { input: typeComposer, output: typeComposer, } : undefined; } if (!subSchema) { throw new Error(`Something is wrong with ${path}`); } if (subSchema.type === 'array') { if (subSchema.items != null && typeof subSchema.items === 'object' && Object.keys(subSchema.items).length > 0) { return { // These are filled after enter get input() { const typeComposers = visitedSubschemaResultMap.get(subSchema.items); return typeComposers.input.getTypePlural(); }, get output() { const typeComposers = visitedSubschemaResultMap.get(subSchema.items); return typeComposers.output.getTypePlural(); }, ...subSchema, }; } if (subSchema.contains) { // Scalars cannot be in union type const typeComposer = schemaComposer.getAnyTC(GraphQLJSON).getTypePlural(); return { input: typeComposer, output: typeComposer, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } // If it doesn't have any clue { // const typeComposer = getGenericJSONScalar({ // schemaComposer, // isInput: false, // subSchema, // validateWithJSONSchema, // }).getTypePlural(); const typeComposer = schemaComposer.getAnyTC(GraphQLJSON).getTypePlural(); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } } if (subSchema.pattern) { let typeScriptType; switch (subSchema.type) { case 'number': typeScriptType = 'number'; break; case 'integer': if (subSchema.format === 'int64') { typeScriptType = 'bigint'; } else { typeScriptType = 'number'; } break; default: typeScriptType = 'string'; break; } schemaComposer.addDirective(RegExpDirective); schemaComposer.addDirective(TypeScriptDirective); const typeComposer = schemaComposer.createScalarTC({ name: getValidTypeName({ schemaComposer, isInput: false, subSchema, }), directives: [ { name: 'regexp', args: { subgraph: subgraphName, pattern: subSchema.pattern, }, }, { name: 'typescript', args: { subgraph: subgraphName, type: typeScriptType, }, }, ], }); return { input: typeComposer, output: typeComposer, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, deprecated: subSchema.deprecated, }; } if (subSchema.const) { const scalarTypeName = getValidTypeName({ schemaComposer, isInput: false, subSchema, }); schemaComposer.addDirective(EnumDirective); schemaComposer.addDirective(TypeScriptDirective); schemaComposer.addDirective(ExampleDirective); let enumValueName = sanitizeNameForGraphQL(subSchema.const.toString()); if (enumValueName === 'true' || enumValueName === 'false') { enumValueName = enumValueName.toUpperCase(); } const typeComposer = schemaComposer.createEnumTC({ name: scalarTypeName, values: { [enumValueName]: { directives: [ { name: 'enum', args: { subgraph: subgraphName, value: JSON.stringify(subSchema.const), }, }, ], }, }, directives: [ { name: 'typescript', args: { subgraph: subgraphName, type: JSON.stringify(subSchema.const), }, }, { name: 'example', args: { subgraph: subgraphName, value: subSchema.const, }, }, ], extensions: { default: subSchema.const, }, }); return { input: typeComposer, output: typeComposer, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, deprecated: subSchema.deprecated, }; } if (subSchema.enum && subSchema.type !== 'boolean') { const values = {}; for (const value of subSchema.enum) { let enumKey = sanitizeNameForGraphQL(value.toString()); if (enumKey === 'false' || enumKey === 'true' || enumKey === 'null') { enumKey = enumKey.toUpperCase(); } if (typeof enumKey === 'string' && enumKey.length === 0) { enumKey = '_'; } schemaComposer.addDirective(EnumDirective); // Falsy values are ignored by GraphQL // eslint-disable-next-line no-unneeded-ternary const enumValue = value || value === 0 ? value : value?.toString(); const directives = []; if (enumValue !== enumKey) { directives.push({ name: 'enum', args: { subgraph: subgraphName, value: JSON.stringify(enumValue), }, }); } values[enumKey] = { directives, value: enumValue, }; } const directives = []; if (subSchema.examples?.length) { schemaComposer.addDirective(ExampleDirective); for (const example of subSchema.examples) { directives.push({ name: 'example', args: { subgraph: subgraphName, value: example, }, }); } } const typeComposer = schemaComposer.createEnumTC({ name: getValidTypeName({ schemaComposer, isInput: false, subSchema, }), values, description: subSchema.description, directives, extensions: { default: subSchema.default, }, }); return { input: typeComposer, output: typeComposer, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } if (Array.isArray(subSchema.type)) { const validTypes = subSchema.type.filter((typeName) => typeName !== 'null'); if (validTypes.length === 1) { subSchema.type = validTypes[0]; // continue with the single type } else { const typeComposer = schemaComposer.getAnyTC(GraphQLJSON); return { input: typeComposer, output: typeComposer, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } } if (subSchema.format) { const formatScalar = getScalarForFormat?.(subSchema.format) || getDefaultScalarForFormat(subSchema.format); if (formatScalar) { const typeComposer = schemaComposer.getAnyTC(formatScalar); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } } if (subSchema.minimum === 0) { const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNonNegativeInt : GraphQLNonNegativeFloat); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } else if (subSchema.minimum > 0) { const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLPositiveInt : GraphQLPositiveFloat); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } if (subSchema.maximum === 0) { const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNonPositiveInt : GraphQLNonPositiveFloat); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } else if (subSchema.maximum < 0) { const typeComposer = schemaComposer.getAnyTC(subSchema.type === 'integer' ? GraphQLNegativeInt : GraphQLNegativeFloat); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } if (subSchema.maximum > Number.MAX_SAFE_INTEGER || subSchema.minimum < Number.MIN_SAFE_INTEGER) { const typeComposer = schemaComposer.getAnyTC(GraphQLBigInt); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } switch (subSchema.type) { case 'boolean': { const typeComposer = schemaComposer.getAnyTC(GraphQLBoolean); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } case 'null': { const typeComposer = schemaComposer.getAnyTC(GraphQLVoid); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } case 'integer': { const typeComposer = schemaComposer.getAnyTC(GraphQLInt); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } case 'number': { const typeComposer = schemaComposer.getAnyTC(GraphQLFloat); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } case 'string': { if (subSchema.minLength === 1 && subSchema.maxLength == null) { const tc = schemaComposer.getAnyTC(GraphQLNonEmptyString); return { input: tc, output: tc, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } if (subSchema.minLength || subSchema.maxLength) { schemaComposer.addDirective(LengthDirective); const typeComposer = schemaComposer.createScalarTC({ name: getValidTypeName({ schemaComposer, isInput: false, subSchema, }), description: subSchema.description, directives: [ { name: 'length', args: { subgraph: subgraphName, min: subSchema.minLength, max: subSchema.maxLength, }, }, ], }); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } const typeComposer = schemaComposer.getAnyTC(GraphQLString); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } case 'object': { switch (subSchema.title) { case '_schema': return { output: schemaComposer, ...subSchema, }; case 'Query': return { output: schemaComposer.Query, ...subSchema, }; case 'Mutation': return { output: schemaComposer.Mutation, ...subSchema, }; case 'Subscription': { if (path === '/properties/subscription') { return { output: schemaComposer.Subscription, ...subSchema, }; } subSchema.title = 'Subscription_'; break; } } } } if (subSchema.oneOf && !subSchema.properties) { schemaComposer.addDirective(OneOfDirective); const input = schemaComposer.createInputTC({ name: getValidTypeName({ schemaComposer, isInput: true, subSchema, }), fields: {}, directives: [ { name: 'oneOf', args: { subgraph: subgraphName, }, }, ], }); const extensions = {}; if (subSchema.$comment?.startsWith('statusCodeOneOfIndexMap:')) { const statusCodeOneOfIndexMapStr = subSchema.$comment.replace('statusCodeOneOfIndexMap:', ''); const statusCodeOneOfIndexMap = JSON.parse(statusCodeOneOfIndexMapStr); if (statusCodeOneOfIndexMap) { extensions.statusCodeOneOfIndexMap = statusCodeOneOfIndexMap; } } const output = schemaComposer.createUnionTC({ name: getValidTypeName({ schemaComposer, isInput: false, subSchema, }), description: subSchema.description, types: [], extensions, }); return { input, output, ...subSchema, }; } if (subSchema.properties || subSchema.allOf || subSchema.anyOf || subSchema.additionalProperties) { if (subSchema.title === 'Any') { const typeComposer = schemaComposer.getAnyTC(GraphQLJSON); return { input: typeComposer, output: typeComposer, description: subSchema.description, nullable: subSchema.nullable, readOnly: subSchema.readOnly, writeOnly: subSchema.writeOnly, default: subSchema.default, deprecated: subSchema.deprecated, }; } const config = { name: getValidTypeName({ schemaComposer, isInput: false, subSchema, }), description: subSchema.description, fields: {}, directives: [], extensions: { default: subSchema.default, }, }; if (subSchema.examples?.length) { schemaComposer.addDirective(ExampleDirective); for (const example of subSchema.examples) { config.directives.push({ name: 'example', args: { subgraph: subgraphName, value: example, }, }); } } if (subSchema.discriminator?.propertyName) { schemaComposer.addDirective(DiscriminatorDirective); } const directives = []; if (subSchema.examples?.length) { schemaComposer.addDirective(ExampleDirective); for (const example of subSchema.examples) { directives.push({ name: 'example', args: { subgraph: subgraphName, value: example, }, }); } } return { input: schemaComposer.createInputTC({ name: getValidTypeName({ schemaComposer, isInput: true, subSchema, }), description: subSchema.description, fields: {}, directives, extensions: { default: subSchema.default, }, }), output: subSchema.discriminator ? schemaComposer.createInterfaceTC(config) : schemaComposer.createObjectTC(config), ...subSchema, ...(subSchema.properties ? { properties: { ...subSchema.properties } } : {}), ...(subSchema.allOf ? { allOf: [...subSchema.allOf] } : {}), ...(subSchema.additionalProperties ? { additionalProperties: subSchema.additionalProperties === true ? true : { ...subSchema.additionalProperties }, } : {}), ...(subSchema.discriminatorMapping ? { discriminatorMapping: { ...subSchema.discriminatorMapping } } : {}), }; } return subSchema; }, leave(subSchemaAndTypeComposers, { path }) { // const validateWithJSONSchema = getValidateFnForSchemaPath(ajv, path, schema); const subSchemaOnly = { ...subSchemaAndTypeComposers, input: undefined, output: undefined, }; if (subSchemaOnly.discriminator?.propertyName) { schemaComposer.addDirective(DiscriminatorDirective); const discriminatorArgs = { subgraph: subgraphName, field: subSchemaOnly.discriminator.propertyName, }; if (subSchemaOnly.discriminator.mapping) { const mappingByName = {}; for (const discriminatorValue in subSchemaOnly.discriminatorMapping) { const discType = subSchemaOnly.discriminatorMapping[discriminatorValue]; mappingByName[discriminatorValue] = discType.output.getTypeName(); } discriminatorArgs.mapping = mappingByName; } subSchemaAndTypeComposers.output.setDirectiveByName('discriminator', discriminatorArgs); } if (subSchemaAndTypeComposers.oneOf && !subSchemaAndTypeComposers.properties) { const isPlural = subSchemaAndTypeComposers.oneOf.some(({ output }) => output instanceof ListComposer); if (isPlural) { const { input, output, flatten } = getUnionTypeComposers({ subgraphName, schemaComposer, typeComposersList: subSchemaAndTypeComposers.oneOf.map(({ input, output }) => ({ input: input.ofType || input, output: output.ofType || output, })), subSchemaAndTypeComposers, logger, }); return { input: input.getTypePlural(), output: output instanceof ListComposer ? output : output.getTypePlural(), nullable: subSchemaAndTypeComposers.nullable, default: subSchemaAndTypeComposers.default, readOnly: subSchemaAndTypeComposers.readOnly, writeOnly: subSchemaAndTypeComposers.writeOnly, flatten, deprecated: subSchemaAndTypeComposers.deprecated, }; } return getUnionTypeComposers({ subgraphName, schemaComposer, typeComposersList: subSchemaAndTypeComposers.oneOf, subSchemaAndTypeComposers, logger, }); } const fieldMap = {}; const inputFieldMap = {}; let isList = false; if (subSchemaAndTypeComposers.allOf) { let ableToUseGraphQLInputObjectType = true; for (const maybeTypeComposers of subSchemaAndTypeComposers.allOf) { let { input: inputTypeComposer, output: outputTypeComposer } = maybeTypeComposers; if (inputTypeComposer instanceof ListComposer) { isList = true; inputTypeComposer = inputTypeComposer.ofType; } if (outputTypeComposer instanceof ListComposer) { isList = true; outputTypeComposer = outputTypeComposer.ofType; } if (inputTypeComposer instanceof ScalarTypeComposer || inputTypeComposer instanceof EnumTypeComposer) { ableToUseGraphQLInputObjectType = false; } else { const inputTypeElemFieldMap = inputTypeComposer.getFields(); for (const fieldName in inputTypeElemFieldMap) { const newInputField = inputTypeElemFieldMap[fieldName]; const existingInputField = inputFieldMap[fieldName]; if (!existingInputField) { inputFieldMap[fieldName] = newInputField; } else { /* If the new field collides with an existing field: - If both the existing and the new field have an input type composer, combine their subfields - Otherwise, replace the existing field with the new one */ const existingInputFieldUnwrappedTC = typeof existingInputField.type?.getUnwrappedTC === 'function' ? existingInputField.type.getUnwrappedTC() : undefined; const newInputFieldUnwrappedTC = typeof newInputField.type?.getUnwrappedTC === 'function' ? newInputField.type.getUnwrappedTC() : undefined; if (existingInputFieldUnwrappedTC instanceof InputTypeComposer && newInputFieldUnwrappedTC instanceof InputTypeComposer) { deepMergeInputTypeComposerFields(existingInputFieldUnwrappedTC, newInputFieldUnwrappedTC); } else { inputFieldMap[fieldName] = newInputField; } } } } if (isSomeInputTypeComposer(outputTypeComposer)) { schemaComposer.addDirective(ResolveRootDirective); fieldMap[outputTypeComposer.getTypeName()] = { type: outputTypeComposer, directives: [ { name: 'resolveRoot', args: { subgraph: subgraphName, }, }, ], }; } else if (outputTypeComposer instanceof UnionTypeComposer) { const outputTCElems = outputTypeComposer.getTypes(); for (const outputTCElem of outputTCElems) { const outputTypeElemFieldMap = outputTCElem.getFields(); for (const fieldName in outputTypeElemFieldMap) { const field = outputTypeElemFieldMap[fieldName]; fieldMap[fieldName] = field; } } } else { if (outputTypeComposer instanceof InterfaceTypeComposer) { subSchemaAndTypeComposers.output.addInterface(outputTypeComposer); schemaComposer.addSchemaMustHaveType(subSchemaAndTypeComposers.output); } const typeElemFieldMap = outputTypeComposer.getFields(); for (const fieldName in typeElemFieldMap) { const newField = typeElemFieldMap[fieldName]; const existingField = fieldMap[fieldName]; if (!existingField) { fieldMap[fieldName] = newField; } else { /* If the new field collides with an existing field: - If both the existing and the new field have an object type composer, combine their subfields - Otherwise, replace the existing field with the new one */ const existingFieldUnwrappedTC = typeof existingField.type?.getUnwrappedTC === 'function' ? existingField.type.getUnwrappedTC() : undefined; const newFieldUnwrappedTC = typeof newField.type?.getUnwrappedTC === 'function' ? newField.type.getUnwrappedTC() : undefined; if (existingFieldUnwrappedTC instanceof ObjectTypeComposer && newFieldUnwrappedTC instanceof ObjectTypeComposer) { deepMergeObjectTypeComposerFields(existingFieldUnwrappedTC, newFieldUnwrappedTC); } else { if (newFieldUnwrappedTC && existingFieldUnwrappedTC && !isUnspecificType(newFieldUnwrappedTC) && isUnspecificType(existingFieldUnwrappedTC)) { continue; } fieldMap[fieldName] = newField; } } } } } if (subSchemaAndTypeComposers.examples?.length) { schemaComposer.addDirective(ExampleDirective); const directives = subSchemaAndTypeComposers.output.getDirectives() || []; for (const example of subSchemaAndTypeComposers.examples) { directives.push({ name: 'example', args: { subgraph: subgraphName, value: example, }, }); } subSchemaAndTypeComposers.output.setDirectives(directives); } subSchemaAndTypeComposers.output.addFields(fieldMap); subSchemaAndTypeComposers.output.setExtensions({ // validateWithJSONSchema, default: subSchemaAndTypeComposers.default, }); if (ableToUseGraphQLInputObjectType) { subSchemaAndTypeComposers.input.addFields(inputFieldMap); if (subSchemaAndTypeComposers.examples?.length) { schemaComposer.addDirective(ExampleDirective); const directives = subSchemaAndTypeComposers.input.getDirectives() || []; for (const example of subSchemaAndTypeComposers.examples) { directives.push({ name: 'example', args: { subgraph: subgraphName, value: example, }, }); } subSchemaAndTypeComposers.input.setDirectives(directives); } subSchemaAndTypeComposers.input.setExtensions({ default: subSchemaAndTypeComposers.default, }); } else { subSchemaAndTypeComposers.input = schemaComposer.getAnyTC(GraphQLJSON); } } if (subSchemaAndTypeComposers.anyOf) { // It should not have `required` because it is `anyOf` not `allOf` let ableToUseGraphQLInputObjectType = true; for (const typeComposers of subSchemaAndTypeComposers.anyOf) { let { input: inputTypeComposer, output: outputTypeComposer } = typeComposers; if (inputTypeComposer instanceof ListComposer || outputTypeComposer instanceof ListComposer) { isList = true; inputTypeComposer = inputTypeComposer.ofType; outputTypeComposer = outputTypeComposer.ofType; } if (inputTypeComposer instanceof ScalarTypeComposer || inputTypeComposer instanceof EnumTypeComposer) { ableToUseGraphQLInputObjectType = false; } else { const inputTypeElemFieldMap = inputTypeComposer.getFields(); for (const fieldName in inputTypeElemFieldMap) { // In case of conflict set it to JSON // TODO: But instead we can convert that field into a oneOf of all possible types if (inputFieldMap[fieldName]) { let existingType = inputFieldMap[fieldName].type; if (typeof existingType === 'function') { existingType = existingType(); } let newType = inputTypeElemFieldMap[fieldName].type; if (typeof newType === 'function') { newType = newType(); } const newTypeName = newType.getTypeName().replace('!', ''); const existingTypeName = existingType.getTypeName().replace('!', ''); if (existingTypeName !== newTypeName) { if (newTypeName !== 'JSON') { inputFieldMap[fieldName] = { type: schemaComposer.getAnyTC(GraphQLJSON), }; } if (existingTypeName === 'JSON') { const field = inputTypeElemFieldMap[fieldName]; inputFieldMap[fieldName] = isNonNullType(field.type.getType()) ? { ...field, type: () => field.type.ofType, } : field; } } } else { const field = inputTypeElemFieldMap[fieldName]; inputFieldMap[fieldName] = isNonNullType(field.type.getType()) ? { ...field, type: () => field.type.ofType, } : field; } } } if (outputTypeComposer instanceof ScalarTypeComposer) { const typeName = outputTypeComposer.getTypeName(); // In case of conflict set it to JSON // TODO: But instead we can convert that field into a union of all possible types if (fieldMap[typeName]) { const existingTypeName = fieldMap[typeName]?.type?.getTypeName(); if (existingTypeName === 'JSON') { schemaComposer.addDirective(ResolveRootDirective); fieldMap[typeName] = { type: outputTypeComposer, directives: [ { name: 'resolveRoot', args: { subgraph: subgraphName, }, },