@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;
259 lines (258 loc) • 12 kB
JavaScript
import { AnySchema } from 'json-machete';
import toJsonSchema from 'to-json-schema';
import { getInterpolationKeys } from '@graphql-mesh/string-interpolation';
import { defaultImportFn, DefaultLogger, readFileOrUrl, sanitizeNameForGraphQL, } from '@graphql-mesh/utils';
import { getOperationMetadata } from './utils.js';
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 {
        return AnySchema;
    }
}
export async function getReferencedJSONSchemaFromOperations({ operations, cwd, schemaHeaders, ignoreErrorResponses, logger = new DefaultLogger('getReferencedJSONSchemaFromOperations'), fetchFn, endpoint, operationHeaders, queryParams, }) {
    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: {},
            readOnly: true,
        });
        rootTypeDefinition.properties = rootTypeDefinition.properties || {};
        const interpolationStrings = [
            ...Object.values(operationHeaders || {}),
            ...Object.values(queryParams || {}).map(val => val.toString()),
            endpoint,
        ];
        if ('pubsubTopic' in operationConfig) {
            interpolationStrings.push(operationConfig.pubsubTopic);
        }
        if ('headers' in operationConfig) {
            interpolationStrings.push(...Object.values(operationConfig.headers || {}));
        }
        if ('path' in operationConfig) {
            interpolationStrings.push(operationConfig.path);
        }
        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,
            });
        }
        if (operationConfig.deprecated) {
            rootTypeDefinition.properties[fieldName].deprecated = true;
        }
        const rootTypeInputPropertyName = operationType + 'Input';
        const rootInputTypeName = rootTypeName + 'Input';
        const rootTypeInputTypeDefinition = (finalJsonSchema.properties[rootTypeInputPropertyName] =
            finalJsonSchema.properties[rootTypeInputPropertyName] || {
                type: 'object',
                title: rootInputTypeName,
                properties: {},
                writeOnly: true,
            });
        if ('queryParamsSample' in operationConfig &&
            operationConfig.queryParamsSample &&
            typeof operationConfig.queryParamsSample === 'object') {
            for (const queryParamName in operationConfig.queryParamsSample) {
                const example = operationConfig.queryParamsSample[queryParamName];
                const generatedSchema = toJsonSchema(example, {
                    required: false,
                    objects: {
                        additionalProperties: false,
                    },
                    strings: {
                        detectFormat: true,
                    },
                    arrays: {
                        mode: 'first',
                    },
                });
                generatedSchema.examples = [example];
                const argName = sanitizeNameForGraphQL(queryParamName);
                operationConfig.queryParamArgMap = operationConfig.queryParamArgMap || {};
                operationConfig.queryParamArgMap[queryParamName] = argName;
                operationConfig.argTypeMap = operationConfig.argTypeMap || {};
                operationConfig.argTypeMap[argName] = generatedSchema;
            }
        }
        const interpolationKeys = getInterpolationKeys(...interpolationStrings);
        if ('queryParamArgMap' in operationConfig) {
            interpolationKeys.push(...Object.values(operationConfig.queryParamArgMap).map(key => `args.${key}`));
        }
        for (const interpolationKey of interpolationKeys) {
            const [initialObjectName, varName] = interpolationKey.split('.');
            if (initialObjectName === 'args') {
                rootTypeInputTypeDefinition.properties[fieldName] = rootTypeInputTypeDefinition.properties[fieldName] || {
                    title: `${rootTypeInputPropertyName}_${fieldName}`,
                    type: 'object',
                    properties: {},
                };
                if (operationConfig.argTypeMap != null && varName in operationConfig.argTypeMap) {
                    const argTypeDef = operationConfig.argTypeMap[varName];
                    if (typeof argTypeDef === 'object') {
                        rootTypeInputTypeDefinition.properties[fieldName].properties[varName] = argTypeDef;
                    }
                    else {
                        rootTypeInputTypeDefinition.properties[fieldName].properties[varName] = {
                            $ref: argTypeDef,
                        };
                    }
                }
                else if (!rootTypeInputTypeDefinition.properties[fieldName].properties[varName]) {
                    rootTypeInputTypeDefinition.properties[fieldName].properties[varName] = {
                        type: 'string',
                    };
                }
            }
        }
        if ('binary' in operationConfig) {
            const generatedSchema = {
                type: 'string',
                format: 'binary',
            };
            rootTypeInputTypeDefinition.properties[fieldName] = rootTypeInputTypeDefinition.properties[fieldName] || {
                title: `${rootTypeInputPropertyName}_${fieldName}`,
                type: 'object',
                properties: {},
            };
            rootTypeInputTypeDefinition.properties[fieldName].properties.input = generatedSchema;
        }
        else if ('requestSchema' in operationConfig && operationConfig.requestSchema) {
            rootTypeInputTypeDefinition.properties[fieldName] = rootTypeInputTypeDefinition.properties[fieldName] || {
                title: `${rootTypeInputPropertyName}_${fieldName}`,
                type: 'object',
                properties: {},
            };
            rootTypeInputTypeDefinition.properties[fieldName].properties.input =
                typeof operationConfig.requestSchema === 'string'
                    ? {
                        $ref: operationConfig.requestSchema,
                        title: operationConfig.requestTypeName,
                    }
                    : operationConfig.requestSchema;
            if (operationConfig.requestSample) {
                rootTypeInputTypeDefinition.properties[fieldName].properties.input.examples =
                    rootTypeInputTypeDefinition.properties[fieldName].properties.input.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(`${operationConfig.field}.requestSample: ${operationConfig.requestSample}; ${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] = rootTypeInputTypeDefinition.properties[fieldName] || {
                title: `${rootTypeInputPropertyName}_${fieldName}`,
                type: 'object',
                properties: {},
            };
            rootTypeInputTypeDefinition.properties[fieldName].properties.input = generatedSchema;
        }
    }
    return finalJsonSchema;
}