@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,155 lines (1,141 loc) • 87.2 kB
JavaScript
import { DefaultLogger, readFileOrUrl, defaultImportFn, sanitizeNameForGraphQL, getHeadersObj } from '@graphql-mesh/utils';
import toJsonSchema from 'to-json-schema';
import { dereferenceObject, healJSONSchema, resolvePath, visitJSONSchema, referenceJSONSchema } from 'json-machete';
import { getInterpolatedHeadersFactory, stringInterpolator, parseInterpolationStrings } from '@graphql-mesh/string-interpolation';
import { process as process$1, util } from '@graphql-mesh/cross-helpers';
import { isListType, isNonNullType, isInputObjectType, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLError, getNamedType, isScalarType, isUnionType, GraphQLScalarType, Kind, GraphQLFloat, GraphQLBoolean, specifiedDirectives } from 'graphql';
import { GraphQLJSON, isSomeInputTypeComposer, ListComposer, SchemaComposer, ScalarTypeComposer, UnionTypeComposer, ObjectTypeComposer } from 'graphql-compose';
import { asArray, memoize1, memoize2 } from '@graphql-tools/utils';
import urlJoin from 'url-join';
import { stringify, parse } from 'qs';
import lodashSet from 'lodash.set';
import { GraphQLJSON as GraphQLJSON$1, RegularExpression, GraphQLURL, GraphQLIPv6, GraphQLIPv4, GraphQLEmailAddress, GraphQLTime, GraphQLDateTime, GraphQLBigInt } from 'graphql-scalars';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { pascalCase } from 'pascal-case';
import { fetch } from '@whatwg-node/fetch';
function isPubSubOperationConfig(operationConfig) {
return 'pubsubTopic' in operationConfig;
}
function getOperationMetadata(operationConfig) {
let httpMethod;
let operationType;
let rootTypeName;
if (isPubSubOperationConfig(operationConfig)) {
httpMethod = null;
operationType = 'subscription';
rootTypeName = 'Subscription';
}
else {
httpMethod = operationConfig.method;
// Fix compability with Mesh handler
operationType = operationConfig.type.toLowerCase();
if (!httpMethod) {
if (operationType === 'mutation') {
httpMethod = 'POST';
}
else {
httpMethod = 'GET';
}
}
if (!rootTypeName) {
if (httpMethod === 'GET') {
rootTypeName = 'Query';
}
}
rootTypeName = operationType === 'query' ? 'Query' : 'Mutation';
}
return {
httpMethod,
operationType,
rootTypeName,
fieldName: operationConfig.field,
};
}
function cleanObject(obj) {
if (typeof obj === 'object' && obj != null) {
const newObj = Array.isArray(obj) ? [] : {};
for (const key in obj) {
const newObjForKey = cleanObject(obj[key]);
if (newObjForKey != null) {
newObj[key] = newObjForKey;
}
}
return newObj;
}
return obj;
}
function isFileUpload(obj) {
return typeof obj.createReadStream === 'function';
}
const anySchema = {
title: 'Any',
anyOf: [
{
type: 'object',
additionalProperties: true,
},
{
type: 'string',
},
{
type: 'number',
},
{
type: 'boolean',
},
],
};
async function handleOperationResponseConfig(operationResponseConfig, { schemaHeaders, cwd, fetchFn, logger = new DefaultLogger('handleOperationResponseConfig'), }) {
if (operationResponseConfig.responseSchema) {
const schema = typeof operationResponseConfig.responseSchema === 'string'
? {
$ref: operationResponseConfig.responseSchema,
title: operationResponseConfig.responseTypeName,
}
: operationResponseConfig.responseSchema;
if (operationResponseConfig.responseSample) {
schema.examples = schema.examples || [operationResponseConfig.responseSample];
}
return schema;
}
else if (operationResponseConfig.responseSample) {
const sample = typeof operationResponseConfig.responseSample === 'object'
? operationResponseConfig.responseSample
: await readFileOrUrl(operationResponseConfig.responseSample, {
cwd,
fetch: fetchFn,
logger,
importFn: defaultImportFn,
headers: schemaHeaders,
}).catch((e) => {
throw new Error(`responseSample - ${e.message}`);
});
const generatedSchema = toJsonSchema(sample, {
required: false,
objects: {
additionalProperties: false,
},
strings: {
detectFormat: true,
},
arrays: {
mode: 'first',
},
});
generatedSchema.title = operationResponseConfig.responseTypeName;
generatedSchema.examples = [sample];
return generatedSchema;
}
else {
const generatedSchema = operationResponseConfig.responseTypeName
? {
...anySchema,
title: operationResponseConfig.responseTypeName,
}
: anySchema;
return generatedSchema;
}
}
async function getReferencedJSONSchemaFromOperations({ operations, cwd, schemaHeaders, ignoreErrorResponses, logger = new DefaultLogger('getReferencedJSONSchemaFromOperations'), fetchFn, }) {
const finalJsonSchema = {
type: 'object',
title: '_schema',
properties: {},
required: ['query'],
};
for (const operationConfig of operations) {
const { operationType, rootTypeName, fieldName } = getOperationMetadata(operationConfig);
const rootTypeDefinition = (finalJsonSchema.properties[operationType] = finalJsonSchema.properties[operationType] || {
type: 'object',
title: rootTypeName,
properties: {},
});
rootTypeDefinition.properties = rootTypeDefinition.properties || {};
if ('responseByStatusCode' in operationConfig) {
rootTypeDefinition.properties[fieldName] = rootTypeDefinition.properties[fieldName] || {};
const statusCodeOneOfIndexMap = {};
const responseSchemas = [];
for (const statusCode in operationConfig.responseByStatusCode) {
if (ignoreErrorResponses && !statusCode.startsWith('2')) {
continue;
}
const responseOperationConfig = operationConfig.responseByStatusCode[statusCode];
const responseOperationSchema = await handleOperationResponseConfig(responseOperationConfig, {
cwd,
schemaHeaders,
fetchFn,
logger,
});
statusCodeOneOfIndexMap[statusCode] = responseSchemas.length;
responseOperationSchema.title = responseOperationSchema.title || `${fieldName}_${statusCode}_response`;
responseSchemas.push(responseOperationSchema);
}
if (responseSchemas.length === 1) {
rootTypeDefinition.properties[fieldName] = responseSchemas[0];
}
else if (responseSchemas.length === 0) {
rootTypeDefinition.properties[fieldName] = anySchema;
}
else {
rootTypeDefinition.properties[fieldName] = {
$comment: `statusCodeOneOfIndexMap:${JSON.stringify(statusCodeOneOfIndexMap)}`,
title: fieldName + '_response',
oneOf: responseSchemas,
};
}
}
else {
rootTypeDefinition.properties[fieldName] = await handleOperationResponseConfig(operationConfig, {
cwd,
schemaHeaders,
fetchFn,
logger,
});
}
const rootTypeInputPropertyName = operationType + 'Input';
const rootInputTypeName = rootTypeName + 'Input';
const rootTypeInputTypeDefinition = (finalJsonSchema.properties[rootTypeInputPropertyName] = finalJsonSchema
.properties[rootTypeInputPropertyName] || {
type: 'object',
title: rootInputTypeName,
properties: {},
});
if ('binary' in operationConfig) {
const generatedSchema = {
title: operationConfig.requestTypeName || 'File',
type: 'file',
};
rootTypeInputTypeDefinition.properties[fieldName] = generatedSchema;
}
else if ('requestSchema' in operationConfig && operationConfig.requestSchema) {
rootTypeInputTypeDefinition.properties[fieldName] =
typeof operationConfig.requestSchema === 'string'
? {
$ref: operationConfig.requestSchema,
title: operationConfig.requestTypeName,
}
: operationConfig.requestSchema;
if (operationConfig.requestSample) {
rootTypeInputTypeDefinition.properties[fieldName].examples = rootTypeInputTypeDefinition.properties[fieldName]
.examples || [operationConfig.requestSample];
}
}
else if ('requestSample' in operationConfig) {
const sample = typeof operationConfig.requestSample === 'object'
? operationConfig.requestSample
: await readFileOrUrl(operationConfig.requestSample, {
cwd,
headers: schemaHeaders,
fetch: fetchFn,
logger,
importFn: defaultImportFn,
}).catch((e) => {
throw new Error(`requestSample:${operationConfig.requestSample} cannot be read - ${e.message}`);
});
const generatedSchema = toJsonSchema(sample, {
required: false,
objects: {
additionalProperties: false,
},
strings: {
detectFormat: true,
},
arrays: {
mode: 'first',
},
});
generatedSchema.title = operationConfig.requestTypeName;
generatedSchema.examples = [sample];
rootTypeInputTypeDefinition.properties[fieldName] = generatedSchema;
}
}
return finalJsonSchema;
}
async function getDereferencedJSONSchemaFromOperations({ operations, cwd = process$1.cwd(), logger, fetchFn, schemaHeaders, ignoreErrorResponses, noDeduplication = false, }) {
const referencedJSONSchema = await getReferencedJSONSchemaFromOperations({
operations,
cwd,
schemaHeaders,
ignoreErrorResponses,
fetchFn,
});
logger.debug(`Dereferencing JSON Schema to resolve all $refs`);
const schemaHeadersFactory = getInterpolatedHeadersFactory(schemaHeaders);
const fullyDeferencedSchema = await dereferenceObject(referencedJSONSchema, {
cwd,
fetchFn,
logger,
headers: schemaHeadersFactory({ env: process$1.env }),
});
logger.debug(`Healing JSON Schema`);
const healedSchema = await healJSONSchema(fullyDeferencedSchema, { noDeduplication });
return healedSchema;
}
function resolveDataByUnionInputType(data, type, schemaComposer) {
var _a;
if (data) {
if (isListType(type)) {
return asArray(data).map(elem => resolveDataByUnionInputType(elem, type.ofType, schemaComposer));
}
if (isNonNullType(type)) {
return resolveDataByUnionInputType(data, type.ofType, schemaComposer);
}
if (isInputObjectType(type)) {
const fieldMap = type.getFields();
const isOneOf = schemaComposer.getAnyTC(type).getDirectiveByName('oneOf');
data = asArray(data)[0];
for (const propertyName in data) {
const fieldName = sanitizeNameForGraphQL(propertyName);
const field = fieldMap[fieldName];
if (field) {
if (isOneOf) {
const resolvedData = resolveDataByUnionInputType(data[fieldName], field.type, schemaComposer);
return resolvedData;
}
const fieldData = data[fieldName];
data[fieldName] = undefined;
const realFieldName = ((_a = field.extensions) === null || _a === void 0 ? void 0 : _a.propertyName) || fieldName;
data[realFieldName] = resolveDataByUnionInputType(fieldData, field.type, schemaComposer);
}
}
}
}
return data;
}
const defaultQsOptions = {
indices: false,
};
const isListTypeOrNonNullListType = memoize1(function isListTypeOrNonNullListType(type) {
if (isNonNullType(type)) {
return isListType(type.ofType);
}
return isListType(type);
});
function createError(message, extensions) {
return new GraphQLError(message, undefined, undefined, undefined, undefined, undefined, extensions);
}
function linkResolver(linkObjArgs, actualResolver, root, args, context, info) {
for (const argKey in linkObjArgs) {
const argInterpolation = linkObjArgs[argKey];
const actualValue = stringInterpolator.parse(argInterpolation, {
root,
args,
context,
info,
env: process$1.env,
});
lodashSet(args, argKey, actualValue);
lodashSet(args, `input.${argKey}`, actualValue);
}
return actualResolver(root, args, context, info);
}
const responseMetadataType = new GraphQLObjectType({
name: 'ResponseMetadata',
fields: {
url: { type: GraphQLString },
method: { type: GraphQLString },
status: { type: GraphQLInt },
statusTest: { type: GraphQLString },
headers: { type: GraphQLJSON },
body: { type: GraphQLJSON },
},
});
async function addExecutionLogicToComposer(schemaComposer, { fetch: globalFetch, logger, operations, operationHeaders, baseUrl, pubsub: globalPubsub, queryParams, queryStringOptions = {}, }) {
logger.debug(`Attaching execution logic to the schema`);
const qsOptions = { ...defaultQsOptions, ...queryStringOptions };
for (const operationConfig of operations) {
const { httpMethod, rootTypeName, fieldName } = getOperationMetadata(operationConfig);
const operationLogger = logger.child(`${rootTypeName}.${fieldName}`);
const interpolationStrings = [
...Object.values(operationHeaders || {}),
...Object.values(queryParams || {}),
baseUrl,
];
const rootTypeComposer = schemaComposer[rootTypeName];
const field = rootTypeComposer.getField(fieldName);
if (isPubSubOperationConfig(operationConfig)) {
field.description = operationConfig.description || `PubSub Topic: ${operationConfig.pubsubTopic}`;
field.subscribe = (root, args, context, info) => {
const pubsub = (context === null || context === void 0 ? void 0 : context.pubsub) || globalPubsub;
if (!pubsub) {
return new GraphQLError(`You should have PubSub defined in either the config or the context!`);
}
const interpolationData = { root, args, context, info, env: process$1.env };
const pubsubTopic = stringInterpolator.parse(operationConfig.pubsubTopic, interpolationData);
operationLogger.debug(`=> Subscribing to pubSubTopic: ${pubsubTopic}`);
return pubsub.asyncIterator(pubsubTopic);
};
field.resolve = root => {
operationLogger.debug('Received ', root, ' from ', operationConfig.pubsubTopic);
return root;
};
interpolationStrings.push(operationConfig.pubsubTopic);
}
else if (operationConfig.path) {
if (process$1.env.DEBUG) {
field.description = `
***Original Description***: ${operationConfig.description || '(none)'}
***Method***: ${operationConfig.method}
***Base URL***: ${baseUrl}
***Path***: ${operationConfig.path}
`;
}
else {
field.description = operationConfig.description;
}
field.resolve = async (root, args, context) => {
var _a, _b, _c;
operationLogger.debug(`=> Resolving`);
const interpolationData = { root, args, context, env: process$1.env };
const interpolatedBaseUrl = stringInterpolator.parse(baseUrl, interpolationData);
const interpolatedPath = stringInterpolator.parse(operationConfig.path, interpolationData);
let fullPath = urlJoin(interpolatedBaseUrl, interpolatedPath);
const nonInterpolatedHeaders = {
...operationHeaders,
...operationConfig === null || operationConfig === void 0 ? void 0 : operationConfig.headers,
};
const headers = {};
for (const headerName in nonInterpolatedHeaders) {
const nonInterpolatedValue = nonInterpolatedHeaders[headerName];
const interpolatedValue = stringInterpolator.parse(nonInterpolatedValue, interpolationData);
if (interpolatedValue) {
headers[headerName] = interpolatedValue;
}
}
const requestInit = {
method: httpMethod,
headers,
};
if (queryParams) {
const interpolatedQueryParams = {};
for (const queryParamName in queryParams) {
interpolatedQueryParams[queryParamName] = stringInterpolator.parse(queryParams[queryParamName], interpolationData);
}
const queryParamsString = stringify(interpolatedQueryParams, qsOptions);
fullPath += fullPath.includes('?') ? '&' : '?';
fullPath += queryParamsString;
}
// Handle binary data
if ('binary' in operationConfig) {
const binaryUpload = await args.input;
if (isFileUpload(binaryUpload)) {
const readable = binaryUpload.createReadStream();
const chunks = [];
for await (const chunk of readable) {
for (const byte of chunk) {
chunks.push(byte);
}
}
requestInit.body = new Uint8Array(chunks);
const [, contentType] = Object.entries(headers).find(([key]) => key.toLowerCase() === 'content-type') || [];
if (!contentType) {
headers['content-type'] = binaryUpload.mimetype;
}
}
requestInit.body = binaryUpload;
}
else {
if (operationConfig.requestBaseBody != null) {
args.input = args.input || {};
for (const key in operationConfig.requestBaseBody) {
const configValue = operationConfig.requestBaseBody[key];
if (typeof configValue === 'string') {
const value = stringInterpolator.parse(configValue, interpolationData);
lodashSet(args.input, key, value);
}
else {
args.input[key] = configValue;
}
}
}
// Resolve union input
const input = (args.input = resolveDataByUnionInputType(cleanObject(args.input), (_c = (_b = (_a = field.args) === null || _a === void 0 ? void 0 : _a.input) === null || _b === void 0 ? void 0 : _b.type) === null || _c === void 0 ? void 0 : _c.getType(), schemaComposer));
if (input != null) {
switch (httpMethod) {
case 'GET':
case 'HEAD':
case 'CONNECT':
case 'OPTIONS':
case 'TRACE': {
fullPath += fullPath.includes('?') ? '&' : '?';
fullPath += stringify(input, qsOptions);
break;
}
case 'POST':
case 'PUT':
case 'PATCH':
case 'DELETE': {
const [, contentType] = Object.entries(headers).find(([key]) => key.toLowerCase() === 'content-type') || [];
if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/x-www-form-urlencoded')) {
requestInit.body = stringify(input, qsOptions);
}
else {
requestInit.body = JSON.stringify(input);
}
break;
}
default:
return createError(`Unknown HTTP Method: ${httpMethod}`, {
url: fullPath,
method: httpMethod,
});
}
}
}
// Delete unused queryparams
const [actualPath, queryString] = fullPath.split('?');
if (queryString) {
const queryParams = parse(queryString);
const cleanedQueryParams = cleanObject(queryParams);
fullPath = actualPath + '?' + stringify(cleanedQueryParams, qsOptions);
}
operationLogger.debug(`=> Fetching `, fullPath, `=>`, requestInit);
const fetch = (context === null || context === void 0 ? void 0 : context.fetch) || globalFetch;
if (!fetch) {
return createError(`You should have fetch defined in either the config or the context!`, {
url: fullPath,
method: httpMethod,
});
}
const response = await fetch(fullPath, requestInit);
// If return type is a file
if (field.type.getTypeName() === 'File') {
return response.blob();
}
const responseText = await response.text();
operationLogger.debug(`=> Received`, {
headers: response.headers,
text: responseText,
});
let responseJson;
try {
responseJson = JSON.parse(responseText);
}
catch (error) {
const returnNamedGraphQLType = getNamedType(field.type.getType());
// The result might be defined as scalar
if (isScalarType(returnNamedGraphQLType)) {
operationLogger.debug(` => Return type is not a JSON so returning ${responseText}`);
return responseText;
}
else if (response.status === 204) {
responseJson = {};
}
else {
return createError(`Unexpected response`, {
url: fullPath,
method: httpMethod,
responseText,
error,
});
}
}
if (!response.status.toString().startsWith('2')) {
const returnNamedGraphQLType = getNamedType(field.type.getType());
if (!isUnionType(returnNamedGraphQLType)) {
return createError(`HTTP Error: ${response.status}`, {
url: fullPath,
method: httpMethod,
...(response.statusText ? { status: response.statusText } : {}),
responseJson,
});
}
}
operationLogger.debug(`Returning `, responseJson);
// Sometimes API returns an array but the return type is not an array
const isListReturnType = isListTypeOrNonNullListType(field.type.getType());
const isArrayResponse = Array.isArray(responseJson);
if (isListReturnType && !isArrayResponse) {
operationLogger.debug(`Response is not array but return type is list. Normalizing the response`);
responseJson = [responseJson];
}
if (!isListReturnType && isArrayResponse) {
operationLogger.debug(`Response is array but return type is not list. Normalizing the response`);
responseJson = responseJson[0];
}
const addResponseMetadata = (obj) => {
return {
...obj,
$url: fullPath,
$method: httpMethod,
$request: {
query: {
...obj,
...args,
...args.input,
},
path: {
...obj,
...args,
},
header: requestInit.headers,
},
$response: {
url: fullPath,
method: httpMethod,
status: response.status,
statusText: response.statusText,
headers: getHeadersObj(response.headers),
body: obj,
},
};
};
operationLogger.debug(`Adding response metadata to the response object`);
return Array.isArray(responseJson)
? responseJson.map(obj => addResponseMetadata(obj))
: addResponseMetadata(responseJson);
};
interpolationStrings.push(...Object.values(operationConfig.headers || {}));
interpolationStrings.push(operationConfig.path);
if ('links' in operationConfig) {
for (const linkName in operationConfig.links) {
const linkObj = operationConfig.links[linkName];
const typeTC = schemaComposer.getOTC(field.type.getTypeName());
typeTC.addFields({
[linkName]: () => {
const targetField = schemaComposer.Query.getField(linkObj.fieldName);
return {
...targetField,
args: {},
description: linkObj.description || targetField.description,
resolve: (root, args, context, info) => linkResolver(linkObj.args, targetField.resolve, root, args, context, info),
};
},
});
}
}
if ('exposeResponseMetadata' in operationConfig && operationConfig.exposeResponseMetadata) {
const typeTC = schemaComposer.getOTC(field.type.getTypeName());
typeTC.addFields({
_response: {
type: responseMetadataType,
resolve: root => root.$response,
},
});
}
if ('responseByStatusCode' in operationConfig) {
const unionOrSingleTC = schemaComposer.getAnyTC(getNamedType(field.type.getType()));
const types = 'getTypes' in unionOrSingleTC ? unionOrSingleTC.getTypes() : [unionOrSingleTC];
const statusCodeOneOfIndexMap = unionOrSingleTC.getExtension('statusCodeOneOfIndexMap') || {};
for (const statusCode in operationConfig.responseByStatusCode) {
const responseConfig = operationConfig.responseByStatusCode[statusCode];
if (responseConfig.links || responseConfig.exposeResponseMetadata) {
const typeTCThunked = types[statusCodeOneOfIndexMap[statusCode] || 0];
const typeTC = schemaComposer.getAnyTC(typeTCThunked.getTypeName());
if ('addFieldArgs' in typeTC) {
if (responseConfig.exposeResponseMetadata) {
typeTC.addFields({
_response: {
type: responseMetadataType,
resolve: root => root.$response,
},
});
}
for (const linkName in responseConfig.links || []) {
typeTC.addFields({
[linkName]: () => {
const linkObj = responseConfig.links[linkName];
const targetField = schemaComposer.Query.getField(linkObj.fieldName);
return {
...targetField,
args: {},
description: linkObj.description || targetField.description,
resolve: (root, args, context, info) => linkResolver(linkObj.args, targetField.resolve, root, args, context, info),
};
},
});
}
}
}
}
}
}
const { args: globalArgs } = parseInterpolationStrings(interpolationStrings, operationConfig.argTypeMap);
rootTypeComposer.addFieldArgs(fieldName, globalArgs);
}
logger.debug(`Building the executable schema.`);
return schemaComposer;
}
function getValidTypeName({ schemaComposer, isInput, subSchema, }) {
const sanitizedName = sanitizeNameForGraphQL(isInput ? subSchema.title + '_Input' : subSchema.title);
if (schemaComposer.has(sanitizedName)) {
let i = 2;
while (schemaComposer.has(sanitizedName + i)) {
i++;
}
return sanitizedName + i;
}
return sanitizedName;
}
function getStringScalarWithMinMaxLength({ schemaComposer, subSchema, }) {
const name = getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
});
function coerceString(value) {
if (value != null) {
const vStr = value.toString();
if (typeof subSchema.minLength !== 'undefined' && vStr.length < subSchema.minLength) {
throw new Error(`${name} cannot be less than ${subSchema.minLength} but given ${vStr}`);
}
if (typeof subSchema.maxLength !== 'undefined' && vStr.length > subSchema.maxLength) {
throw new Error(`${name} cannot be more than ${subSchema.maxLength} but given ${vStr}`);
}
return vStr;
}
}
return schemaComposer.createScalarTC({
name,
description: subSchema.description,
serialize: coerceString,
parseValue: coerceString,
parseLiteral: ast => {
if ('value' in ast) {
return coerceString(ast.value);
}
return null;
},
extensions: {
codegenScalarType: 'string',
},
});
}
const JSONSchemaStringFormats = [
'date',
'hostname',
'regex',
'json-pointer',
'relative-json-pointer',
'uri-reference',
'uri-template',
];
function getJSONSchemaStringFormatScalarMap(ajv) {
const map = new Map();
for (const format of JSONSchemaStringFormats) {
const schema = {
type: 'string',
format,
};
let validate;
try {
validate = ajv.compile(schema);
}
catch (e) {
validate = (value) => ajv.validate(schema, value);
}
const coerceString = (value) => {
if (validate(value)) {
return value;
}
throw new Error(`Expected ${format} but got: ${value}`);
};
const scalar = new GraphQLScalarType({
name: pascalCase(format),
description: `Represents ${format} values`,
serialize: coerceString,
parseValue: coerceString,
parseLiteral: ast => {
if (ast.kind === Kind.STRING) {
return coerceString(ast.value);
}
throw new Error(`Expected string in ${format} format but got: ${ast.value}`);
},
extensions: {
codegenScalarType: 'string',
},
});
map.set(format, scalar);
}
return map;
}
function getTypeResolverFromOutputTCs(ajv, outputTypeComposers, statusCodeOneOfIndexMap) {
const statusCodeTypeMap = new Map();
for (const statusCode in statusCodeOneOfIndexMap) {
statusCodeTypeMap.set(statusCode.toString(), outputTypeComposers[statusCodeOneOfIndexMap[statusCode]]);
}
return function resolveType(data, context, info) {
if (data.__typename) {
return data.__typename;
}
else if (data.resourceType) {
return data.resourceType;
}
if (data.$response && statusCodeOneOfIndexMap) {
const responseData = data.$response;
const type = statusCodeTypeMap.get(responseData.status.toString()) || statusCodeTypeMap.get('default');
if (type) {
if ('getFields' in type) {
return type.getTypeName();
}
else {
return type.getResolveType()(data, context, info, type.getType());
}
}
}
const validationErrors = {};
for (const outputTypeComposer of outputTypeComposers) {
const validateFn = outputTypeComposer.getExtension('validateWithJSONSchema');
if (validateFn) {
const isValid = validateFn(data);
const typeName = outputTypeComposer.getTypeName();
if (isValid) {
if ('getFields' in outputTypeComposer) {
return outputTypeComposer.getTypeName();
}
else {
return outputTypeComposer.getResolveType()(data, context, info, outputTypeComposer.getType());
}
}
validationErrors[typeName] = ajv.errors || validateFn.errors;
}
}
if (data.$response) {
const responseData = data.$response;
const error = new GraphQLError(`HTTP Error: ${responseData.status}`, undefined, undefined, undefined, undefined, undefined, {
...responseData,
responseJson: data,
});
console.error(error);
return error;
}
const error = new GraphQLError(`Received data doesn't met the union`, null, null, null, null, null, {
validationErrors,
});
console.error(error);
return error;
};
}
const ONE_OF_DEFINITION = /* GraphQL */ `
directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
`;
function getUnionTypeComposers({ schemaComposer, ajv, typeComposersList, subSchemaAndTypeComposers, }) {
if (typeComposersList.length === 1) {
return typeComposersList[0];
}
const unionInputFields = {};
const outputTypeComposers = [];
typeComposersList.forEach(typeComposers => {
const { input, output } = typeComposers;
if (isSomeInputTypeComposer(output)) {
const containerTypeName = `${output.getTypeName()}_container`;
outputTypeComposers.push(schemaComposer.getOrCreateOTC(containerTypeName, otc => otc.addFields({
[output.getTypeName()]: {
type: output,
resolve: root => root,
},
})));
}
else {
outputTypeComposers.push(output);
}
unionInputFields[input.getTypeName()] = {
type: input,
};
});
subSchemaAndTypeComposers.input.addFields(unionInputFields);
if (!schemaComposer.hasDirective('oneOf')) {
schemaComposer.addTypeDefs(ONE_OF_DEFINITION);
}
const resolveType = getTypeResolverFromOutputTCs(ajv, outputTypeComposers, subSchemaAndTypeComposers.output.getExtension('statusCodeOneOfIndexMap'));
subSchemaAndTypeComposers.output.setResolveType(resolveType);
for (const outputTypeComposer of outputTypeComposers) {
if ('getFields' in outputTypeComposer) {
subSchemaAndTypeComposers.output.addType(outputTypeComposer);
}
else {
for (const possibleType of outputTypeComposer.getTypes()) {
subSchemaAndTypeComposers.output.addType(possibleType);
}
}
}
return {
input: subSchemaAndTypeComposers.input,
output: subSchemaAndTypeComposers.output,
};
}
function getGenericJSONScalar({ isInput, subSchema, schemaComposer, validateWithJSONSchema, }) {
function coerceGenericJSONScalar(value) {
if (!validateWithJSONSchema(value)) {
throw new Error(`${util.inspect(value)} is not valid!`);
}
return value;
}
const name = getValidTypeName({
schemaComposer,
isInput,
subSchema,
});
return schemaComposer.createScalarTC({
name,
description: subSchema.description,
serialize: coerceGenericJSONScalar,
parseValue: coerceGenericJSONScalar,
parseLiteral(...args) {
const value = GraphQLJSON.parseLiteral(...args);
return coerceGenericJSONScalar(value);
},
extensions: {
codegenScalarType: 'any',
examples: subSchema.examples,
default: subSchema.default,
},
});
}
const ajvMemoizedCompile = memoize2(function ajvCompile(ajv, jsonSchema) {
const schema = typeof jsonSchema === 'object'
? {
...jsonSchema,
$schema: undefined,
}
: jsonSchema;
try {
return ajv.compile(schema);
}
catch (_a) {
// eslint-disable-next-line no-inner-declarations
function validateFn(value) {
return ajv.validate(schema, value);
}
Object.defineProperty(validateFn, 'errors', {
get() {
return ajv.errors;
},
});
return validateFn;
}
});
function getValidateFnForSchemaPath(ajv, path, schema) {
const subSchema = resolvePath(path, schema);
const fn = function validateFn(data) {
const ajvValidateFn = ajvMemoizedCompile(ajv, subSchema);
return ajvValidateFn(data);
};
Object.defineProperty(fn, 'errors', {
get() {
return ajvMemoizedCompile(ajv, subSchema).errors;
},
});
return fn;
}
/* eslint-disable no-case-declarations */
const isListTC = memoize1(function isListTC(type) {
return type instanceof ListComposer;
});
const GraphQLVoid = new GraphQLScalarType({
name: 'Void',
description: 'Represents empty values',
serialize: () => '',
extensions: {
codegenScalarType: 'void',
},
});
const GraphQLFile = new GraphQLScalarType({
name: 'File',
extensions: {
codegenScalarType: 'File',
},
});
function getComposerFromJSONSchema(schema, logger) {
const schemaComposer = new SchemaComposer();
const ajv = new Ajv({
strict: false,
});
addFormats(ajv);
const formatScalarMap = getJSONSchemaStringFormatScalarMap(ajv);
const rootInputTypeNameComposerMap = {
QueryInput: () => schemaComposer.Query,
MutationInput: () => schemaComposer.Mutation,
SubscriptionInput: () => schemaComposer.Subscription,
};
return visitJSONSchema(schema, {
enter(subSchema, { path, visitedSubschemaResultMap }) {
var _a;
logger === null || logger === void 0 ? void 0 : logger.debug(`Entering ${path} for GraphQL Schema`);
if (typeof subSchema === 'boolean') {
const typeComposer = schemaComposer.getAnyTC(GraphQLJSON$1);
return subSchema
? {
input: typeComposer,
output: typeComposer,
}
: undefined;
}
const validateWithJSONSchema = getValidateFnForSchemaPath(ajv, path, schema);
if (!subSchema) {
throw new Error(`Something is wrong with ${path}`);
}
if (subSchema.pattern) {
const scalarType = new RegularExpression(getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
}), new RegExp(subSchema.pattern), {
description: subSchema.description,
});
const typeComposer = schemaComposer.getAnyTC(scalarType);
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
};
}
if (subSchema.const) {
const tsTypeName = JSON.stringify(subSchema.const);
const scalarTypeName = getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
});
const scalarType = new RegularExpression(scalarTypeName, new RegExp(subSchema.const), {
description: subSchema.description || `A field whose value is ${tsTypeName}`,
errorMessage: (_r, v) => `Expected ${tsTypeName} but got ${JSON.stringify(v)}`,
});
scalarType.extensions = {
codegenScalarType: tsTypeName,
};
const typeComposer = schemaComposer.createScalarTC(scalarType);
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
};
}
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 = '_';
}
values[enumKey] = {
// Falsy values are ignored by GraphQL
// eslint-disable-next-line no-unneeded-ternary
value: value ? value : value === null || value === void 0 ? void 0 : value.toString(),
};
}
const typeComposer = schemaComposer.createEnumTC({
name: getValidTypeName({
schemaComposer,
isInput: false,
subSchema,
}),
values,
description: subSchema.description,
extensions: {
examples: subSchema.examples,
default: subSchema.default,
},
});
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
};
}
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 if (validTypes.length === 0) {
const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
};
}
else {
const typeComposer = getGenericJSONScalar({
isInput: true,
subSchema,
schemaComposer,
validateWithJSONSchema,
});
return {
input: typeComposer,
output: typeComposer,
nullable: subSchema.nullable,
};
}
}
switch (subSchema.type) {
case 'file': {
const typeComposer = schemaComposer.getAnyTC(GraphQLFile);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
};
}
case 'boolean': {
const typeComposer = schemaComposer.getAnyTC(GraphQLBoolean);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
};
}
case 'null': {
const typeComposer = schemaComposer.getAnyTC(GraphQLVoid);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
};
}
case 'integer': {
if (subSchema.format === 'int64') {
const typeComposer = schemaComposer.getAnyTC(GraphQLBigInt);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
};
}
const typeComposer = schemaComposer.getAnyTC(GraphQLInt);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
};
}
case 'number': {
const typeComposer = schemaComposer.getAnyTC(GraphQLFloat);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
};
}
case 'string': {
if (subSchema.minLength || subSchema.maxLength) {
const scalarType = getStringScalarWithMinMaxLength({
schemaComposer,
subSchema,
});
const typeComposer = schemaComposer.getAnyTC(scalarType);
return {
input: typeComposer,
output: typeComposer,
description: subSchema.description,
nullable: subSchema.nullable,
};
}
switch (subSchema.format) {
case 'date-time': {
const typeComposer = schemaComposer.getAnyTC(GraphQLDateTime);
return {
input: typeComposer,