UNPKG

@omnigraph/openapi

Version:
1,003 lines • 52 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.futureLinks = void 0; exports.getJSONSchemaOptionsFromOpenAPIOptions = getJSONSchemaOptionsFromOpenAPIOptions; exports.getJSONSchemaOptionsFromMultipleOpenAPIOptions = getJSONSchemaOptionsFromMultipleOpenAPIOptions; const graphql_1 = require("graphql"); const json_machete_1 = require("json-machete"); const cross_helpers_1 = require("@graphql-mesh/cross-helpers"); const fusion_composition_1 = require("@graphql-mesh/fusion-composition"); const string_interpolation_1 = require("@graphql-mesh/string-interpolation"); const utils_1 = require("@graphql-mesh/utils"); const utils_2 = require("@graphql-tools/utils"); const utils_js_1 = require("./utils.js"); const defaultHateoasConfig = { linkNameIdentifier: 'rel', linkPathIdentifier: 'href', linkObjectIdentifier: '_links', linkObjectExtensionIdentifier: 'x-links', }; // Global fallback for backward compatibility - exported for testing exports.futureLinks = new Set(); async function getJSONSchemaOptionsFromOpenAPIOptions(name, { source, fallbackFormat, cwd, fetch: fetchFn, endpoint, schemaHeaders, operationHeaders, queryParams = {}, selectQueryOrMutationField = [], logger = new utils_1.DefaultLogger('getJSONSchemaOptionsFromOpenAPIOptions'), jsonApi, HATEOAS, }, hateoasContext) { // Use provided context or create/use global fallback const contextFutureLinks = hateoasContext?.futureLinks || exports.futureLinks; const loadedSchemas = hateoasContext?.loadedSchemas || new Map(); const hateOasConfig = HATEOAS === true ? defaultHateoasConfig : HATEOAS === false ? false : { ...defaultHateoasConfig, ...HATEOAS, }; if (typeof source === 'string') { source = string_interpolation_1.stringInterpolator.parse(source, { env: cross_helpers_1.process.env, }); } const fieldTypeMap = {}; for (const { fieldName, type } of selectQueryOrMutationField) { fieldTypeMap[fieldName] = type; } const schemaHeadersFactory = (0, string_interpolation_1.getInterpolatedHeadersFactory)(schemaHeaders); logger?.debug(`Fetching OpenAPI Document from ${source}`); let oasOrSwagger; const readFileOrUrlForJsonMachete = (path, opts) => (0, utils_1.readFileOrUrl)(path, { cwd: opts.cwd, fetch: fetchFn, headers: schemaHeadersFactory({ env: cross_helpers_1.process.env }), importFn: utils_1.defaultImportFn, logger, fallbackFormat, }); if (typeof source === 'string') { oasOrSwagger = (await (0, json_machete_1.dereferenceObject)({ $ref: source, }, { cwd, readFileOrUrl: readFileOrUrlForJsonMachete, debugLogFn: logger.debug.bind(logger), })); } else { oasOrSwagger = await (0, json_machete_1.dereferenceObject)(source, { cwd, readFileOrUrl: readFileOrUrlForJsonMachete, debugLogFn: logger.debug.bind(logger), }); } (0, json_machete_1.handleUntitledDefinitions)(oasOrSwagger); function lookByTitle(map, key) { if (map != null) { for (const keyInMap in map) { const val = map[keyInMap]; if (val.title === key) { return val; } } } } function lookFromMap(map, key) { if (map != null) { const val = map[key]; if (val == null) { return lookByTitle(map, key); } return val; } } function lookFromMaps(oas, key) { return (lookFromMap(oas.components?.schemas, key) || lookFromMap(oas.definitions, key)); } function isObjectRecord(value) { return value != null && typeof value === 'object' && !Array.isArray(value); } function ensureDiscriminatorMapping(schema) { const discriminatorMapping = schema.discriminatorMapping; if (isObjectRecord(discriminatorMapping)) { return discriminatorMapping; } const newDiscriminatorMapping = {}; schema.discriminatorMapping = newDiscriminatorMapping; return newDiscriminatorMapping; } function getRefPathFromMappingValue(value) { if (value.startsWith('#')) { return { refPath: value.slice(1), isExternalFileRef: false, }; } if (value.startsWith('..')) { const hashIndex = value.indexOf('#'); if (hashIndex === -1) { const refPath = value.slice(2); const firstSegment = refPath.split('/').find(Boolean); if (!refPath.startsWith('/') || firstSegment?.includes('.')) { return null; } return { refPath, isExternalFileRef: false, }; } return { refPath: value.slice(hashIndex + 1), isExternalFileRef: true, }; } return null; } function lookInUnionBranchesByResolvedRef(schema, refPath) { for (const combinator of ['oneOf', 'anyOf', 'allOf']) { const list = schema[combinator]; if (Array.isArray(list)) { for (const subSchema of list) { if (isObjectRecord(subSchema) && subSchema.$resolvedRef === refPath) { return subSchema; } } } } } function tryApplyDiscriminatorMapping(schema) { const discriminator = schema.discriminator; if (!isObjectRecord(discriminator)) { return; } const mapping = discriminator.mapping; if (!isObjectRecord(mapping)) { return; } for (const [key, value] of Object.entries(mapping)) { if (typeof value !== 'string') { continue; } const ref = getRefPathFromMappingValue(value); if (ref != null) { const resolvedSchema = ref.isExternalFileRef ? lookInUnionBranchesByResolvedRef(schema, ref.refPath) || (0, json_machete_1.resolvePath)(ref.refPath, oasOrSwagger) : (0, json_machete_1.resolvePath)(ref.refPath, oasOrSwagger) || lookInUnionBranchesByResolvedRef(schema, ref.refPath); if (!resolvedSchema) { logger.warn(`Invalid discriminator mapping: ${value}`); continue; } ensureDiscriminatorMapping(schema)[key] = resolvedSchema; } else if (value.includes('/')) { logger.warn(`Unsupported discriminator mapping: ${value}`); } else { const schemaObj = lookFromMaps(oasOrSwagger, value); if (!schemaObj) { logger.warn(`Invalid discriminator mapping: ${value}`); continue; } ensureDiscriminatorMapping(schema)[key] = schemaObj; } } } const visitedSchemas = new WeakSet(); function visitSchemaOrSchemas(schemaOrSchemas) { if (Array.isArray(schemaOrSchemas)) { for (const schema of schemaOrSchemas) { visitSchema(schema); } } else { visitSchema(schemaOrSchemas); } } function visitSchema(schema) { if (!isObjectRecord(schema)) { return; } if (visitedSchemas.has(schema)) { return; } visitedSchemas.add(schema); tryApplyDiscriminatorMapping(schema); for (const combinator of ['oneOf', 'anyOf', 'allOf']) { const list = schema[combinator]; if (Array.isArray(list)) { for (const sub of list) { visitSchema(sub); } } } for (const schemaKey of [ 'additionalItems', 'additionalProperties', 'contains', 'else', 'if', 'items', 'not', 'then', ]) { visitSchemaOrSchemas(schema[schemaKey]); } for (const propsKey of ['definitions', 'properties', 'patternProperties']) { const props = schema[propsKey]; if (isObjectRecord(props)) { for (const sub of Object.values(props)) { visitSchema(sub); } } } // Freshly-resolved mapping targets may themselves contain inline discriminators const resolvedMapping = schema.discriminatorMapping; if (isObjectRecord(resolvedMapping)) { for (const sub of Object.values(resolvedMapping)) { visitSchema(sub); } } } function visitContent(content) { if (!isObjectRecord(content)) { return; } for (const media of Object.values(content)) { if (isObjectRecord(media)) { visitSchema(media.schema); } } } function visitParameters(parameters) { if (!Array.isArray(parameters)) { return; } for (const parameter of parameters) { if (!isObjectRecord(parameter)) { continue; } // OpenAPI v3 / Swagger v2 body parameter visitSchema(parameter.schema); // OpenAPI v3 parameter content variant visitContent(parameter.content); } } function visitRequestBody(requestBody) { if (!isObjectRecord(requestBody)) { return; } visitContent(requestBody.content); } function visitResponses(responses) { if (!isObjectRecord(responses)) { return; } for (const response of Object.values(responses)) { if (!isObjectRecord(response)) { continue; } // OpenAPI v3 visitContent(response.content); // Swagger v2 visitSchema(response.schema); const headers = response.headers; if (isObjectRecord(headers)) { for (const header of Object.values(headers)) { if (isObjectRecord(header)) { visitSchema(header.schema); } } } } } const operationMethodNames = new Set([ 'get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace', ]); const visitedPathItems = new WeakSet(); function visitOperation(operation) { if (!isObjectRecord(operation)) { return; } visitParameters(operation.parameters); visitRequestBody(operation.requestBody); visitResponses(operation.responses); visitCallbacks(operation.callbacks); } function visitPathItem(pathItem) { if (!isObjectRecord(pathItem)) { return; } if (visitedPathItems.has(pathItem)) { return; } visitedPathItems.add(pathItem); visitParameters(pathItem.parameters); for (const [key, operation] of Object.entries(pathItem)) { if (!operationMethodNames.has(key.toLowerCase())) { continue; } visitOperation(operation); } } function visitCallbacks(callbacks) { if (!isObjectRecord(callbacks)) { return; } for (const callback of Object.values(callbacks)) { if (!isObjectRecord(callback)) { continue; } for (const pathItem of Object.values(callback)) { visitPathItem(pathItem); } } } function visitOpenAPIDocument(oas) { const v3Components = oas.components; if (isObjectRecord(v3Components)) { const schemas = v3Components.schemas; if (isObjectRecord(schemas)) { for (const schema of Object.values(schemas)) { visitSchema(schema); } } const parameters = v3Components.parameters; if (isObjectRecord(parameters)) { visitParameters(Object.values(parameters)); } const requestBodies = v3Components.requestBodies; if (isObjectRecord(requestBodies)) { for (const requestBody of Object.values(requestBodies)) { visitRequestBody(requestBody); } } const componentResponses = v3Components.responses; if (isObjectRecord(componentResponses)) { visitResponses(componentResponses); } const headers = v3Components.headers; if (isObjectRecord(headers)) { for (const header of Object.values(headers)) { if (isObjectRecord(header)) { visitSchema(header.schema); } } } visitCallbacks(v3Components.callbacks); } const v2Definitions = oas.definitions; if (isObjectRecord(v2Definitions)) { for (const schema of Object.values(v2Definitions)) { visitSchema(schema); } } const v2Parameters = oas.parameters; if (isObjectRecord(v2Parameters)) { visitParameters(Object.values(v2Parameters)); } const v2Responses = oas.responses; if (isObjectRecord(v2Responses)) { visitResponses(v2Responses); } const paths = oas.paths; if (isObjectRecord(paths)) { for (const pathItem of Object.values(paths)) { visitPathItem(pathItem); } } } visitOpenAPIDocument(oasOrSwagger); const operations = []; let baseOperationArgTypeMap; if (!endpoint) { if ('servers' in oasOrSwagger) { const serverObj = oasOrSwagger.servers[0]; endpoint = serverObj.url.split('{').join('{args.'); if (serverObj.variables) { for (const variableName in serverObj.variables) { const variable = serverObj.variables[variableName]; if (!variable.type) { variable.type = 'string'; } baseOperationArgTypeMap = baseOperationArgTypeMap || {}; baseOperationArgTypeMap[variableName] = variable; if (variable.default) { endpoint = endpoint.replace(`{args.${variableName}}`, `{args.${variableName}:${variable.default}}`); } } } } if ('schemes' in oasOrSwagger && oasOrSwagger.schemes.length > 0 && oasOrSwagger.host) { endpoint = oasOrSwagger.schemes[0] + '://' + oasOrSwagger.host; if ('basePath' in oasOrSwagger) { endpoint += oasOrSwagger.basePath; } } } const methodObjFieldMap = new WeakMap(); // Try to resolve any pending future links with currently loaded schemas for (const [schemaName, schemaData] of loadedSchemas) { for (const futureLink of Array.from(contextFutureLinks)) { if (futureLink(schemaName, schemaData.oasDoc, schemaData.methodObjFieldMap)) { contextFutureLinks.delete(futureLink); } } } // Try to resolve future links with current schema for (const futureLink of Array.from(contextFutureLinks)) { if (futureLink(name, oasOrSwagger, methodObjFieldMap)) { contextFutureLinks.delete(futureLink); } } for (const relativePath in oasOrSwagger.paths) { const pathObj = oasOrSwagger.paths[relativePath]; const pathParameters = pathObj.parameters; for (const method in pathObj) { if (method === 'parameters' || method === 'summary' || method === 'description' || method === 'servers' || method === '$resolvedRef' || method.startsWith('x-')) { continue; } const methodObj = pathObj[method]; const operationConfig = { method: method.toUpperCase(), path: relativePath, type: method.toUpperCase() === 'GET' ? 'query' : 'mutation', field: methodObj.operationId && (0, utils_1.sanitizeNameForGraphQL)(methodObj.operationId), description: methodObj.description || methodObj.summary, schemaHeaders, operationHeaders, responseByStatusCode: {}, deprecated: methodObj.deprecated, ...(baseOperationArgTypeMap ? { argTypeMap: { ...baseOperationArgTypeMap, }, } : {}), jsonApiFields: jsonApi, }; operations.push(operationConfig); methodObjFieldMap.set(methodObj, operationConfig); let allParams; if (methodObj.parameters && Array.isArray(methodObj.parameters)) { allParams = [...(pathParameters || []), ...methodObj.parameters]; } else { allParams = { ...(pathParameters || {}), ...(methodObj.parameters || {}), }; } for (const paramObjIndex in allParams) { const paramObj = allParams[paramObjIndex]; const argName = (0, utils_1.sanitizeNameForGraphQL)(paramObj.name); const operationArgTypeMap = (operationConfig.argTypeMap = operationConfig.argTypeMap || {}); switch (paramObj.in) { case 'query': operationConfig.queryParamArgMap = operationConfig.queryParamArgMap || {}; operationConfig.queryParamArgMap[paramObj.name] = argName; if (paramObj.name in queryParams) { paramObj.required = false; if (!paramObj.schema?.default) { paramObj.schema = paramObj.schema || { type: 'string', }; paramObj.required = false; paramObj.schema.nullable = true; const valueFromQueryParams = queryParams[paramObj.name]; if (valueFromQueryParams === 'string' && !valueFromQueryParams.includes('{')) { paramObj.schema.default = queryParams[paramObj.name]; } } } if ('explode' in paramObj) { operationConfig.queryStringOptionsByParam = operationConfig.queryStringOptionsByParam || {}; operationConfig.queryStringOptionsByParam[paramObj.name] = operationConfig.queryStringOptionsByParam[paramObj.name] || {}; if (paramObj.explode) { operationConfig.queryStringOptionsByParam[paramObj.name].arrayFormat = 'repeat'; operationConfig.queryStringOptionsByParam[paramObj.name].destructObject = true; } else { switch (paramObj.style) { case 'form': case 'simple': // simple is not intended for a query param but seems to be used in some APIs operationConfig.queryStringOptionsByParam[paramObj.name].arrayFormat = 'comma'; break; default: if (paramObj.style === undefined && paramObj.schema.type === 'array') { // when params is array and style is not defined, we assume it is form style. // it is how swagger editor behaves operationConfig.queryStringOptionsByParam[paramObj.name].arrayFormat = 'comma'; break; } logger.warn(`Other styles including ${paramObj.style} of query parameters are not supported yet.`); } } } break; case 'path': { // If it is in the path, let JSON Schema handler put it operationConfig.path = operationConfig.path.replace(`{${paramObj.name}}`, `{args.${argName}}`); break; } case 'header': { operationConfig.headers = operationConfig.headers || {}; if (typeof operationHeaders === 'object' && operationHeaders[paramObj.name]) { paramObj.required = false; const valueFromGlobal = operationHeaders[paramObj.name]; if (!valueFromGlobal.includes('{')) { if (paramObj.schema) { paramObj.schema.default = valueFromGlobal; } } else { if (paramObj.schema?.default) { delete paramObj.schema.default; } } } if (typeof operationHeaders === 'function') { paramObj.required = false; if (paramObj.schema?.default) { delete paramObj.schema.default; } } let defaultValueSuffix = ''; if (paramObj.schema?.default) { defaultValueSuffix = `:${paramObj.schema.default}`; } operationConfig.headers[paramObj.name] = `{args.${argName}${defaultValueSuffix}}`; break; } case 'cookie': { operationConfig.headers = operationConfig.headers || {}; operationConfig.headers.cookie = operationConfig.headers.cookie || ''; const cookieParams = operationConfig.headers.cookie.split(' ').filter(c => !!c); cookieParams.push(`${paramObj.name}={args.${argName}};`); operationConfig.headers.cookie = `${cookieParams.join(' ')}`; break; } case 'body': if (paramObj.schema && Object.keys(paramObj.schema).length > 0) { operationConfig.requestSchema = paramObj.schema; } if (paramObj.example) { operationConfig.requestSample = paramObj.example; } if (paramObj.examples) { operationConfig.requestSample = Object.values(paramObj.examples)[0]; } break; } operationArgTypeMap[argName] = paramObj.schema || paramObj.content?.['application/json']?.schema || paramObj; if (!operationArgTypeMap[argName].title) { operationArgTypeMap[argName].name = paramObj.name; } if (!operationArgTypeMap[argName].description) { operationArgTypeMap[argName].description = paramObj.description; } if (paramObj.required) { operationArgTypeMap[argName].nullable = false; } if (!('type' in paramObj) && !paramObj.schema && !paramObj.content && !paramObj.example && !paramObj.examples) { operationArgTypeMap[argName].type = 'string'; } } if ('requestBody' in methodObj) { const requestBodyObj = methodObj.requestBody; if ('content' in requestBodyObj) { // use json if available, otherwise fall back to the first type const contentKeys = Object.keys(requestBodyObj.content); const contentKey = contentKeys.find(contentKey => typeof contentKey === 'string' && contentKey.includes('json')) || contentKeys[0]; const contentSchema = requestBodyObj.content[contentKey]?.schema; if (contentSchema && Object.keys(contentSchema).length > 0) { operationConfig.requestSchema = contentSchema; } const examplesObj = requestBodyObj.content[contentKey]?.examples; if (examplesObj) { const firstItem = Object.values(examplesObj)[0]; if (typeof firstItem === 'object' && 'value' in firstItem) { operationConfig.requestSample = firstItem.value; } else { operationConfig.requestSample = firstItem; } } if (!operationConfig.headers?.['Content-Type'] && typeof contentKey === 'string') { operationConfig.headers = operationConfig.headers || {}; operationConfig.headers['Content-Type'] = contentKey; } } } const responseByStatusCode = operationConfig.responseByStatusCode; // Handling multiple response types for (const responseKey in methodObj.responses) { if (responseKey === '$resolvedRef') { continue; } const responseObj = methodObj.responses[responseKey]; let schemaObj; if ('consumes' in methodObj) { operationConfig.headers = operationConfig.headers || {}; operationConfig.headers['Content-Type'] = methodObj.consumes.join(', '); } if ('produces' in methodObj) { operationConfig.headers = operationConfig.headers || {}; operationConfig.headers.Accept = methodObj.produces.join(', '); } /** * The OAS rule is that 204 responses should not specify a content key. * But in the real world, it happens that some specifications present 204 responses with an * empty content key. * * @see https://swagger.io/docs/specification/v3_0/describing-responses/#empty-response-body */ if ('content' in responseObj && Object.keys(responseObj.content).length !== 0) { const responseObjForStatusCode = { oneOf: [], }; let allMimeTypes = []; if (typeof operationHeaders === 'object') { const acceptFromOperationHeader = operationHeaders.accept || operationHeaders.Accept; if (acceptFromOperationHeader) { allMimeTypes = [acceptFromOperationHeader]; } } if (allMimeTypes.length === 0) { allMimeTypes = Object.keys(responseObj.content); } const jsonLikeMimeTypes = allMimeTypes.filter(c => c !== '*/*' && c.toString().includes('json')); const mimeTypes = jsonLikeMimeTypes.length > 0 ? jsonLikeMimeTypes : allMimeTypes; // If we have a better accept header, overwrite User's choice if ((!operationConfig.headers?.accept && !operationConfig.headers?.Accept) || mimeTypes.length === 1) { operationConfig.headers = operationConfig.headers || {}; if (operationConfig.headers.Accept) { delete operationConfig.headers.Accept; } operationConfig.headers.accept = jsonLikeMimeTypes.length > 0 ? jsonLikeMimeTypes.join(',') : allMimeTypes[0].toString(); } for (const contentKey in responseObj.content) { if (!mimeTypes.some(mimeType => mimeType.includes(contentKey))) { continue; } schemaObj = responseObj.content[contentKey].schema; if (schemaObj && Object.keys(schemaObj).length > 0) { responseObjForStatusCode.oneOf.push(schemaObj); } else if (contentKey.toString().startsWith('text')) { responseObjForStatusCode.oneOf.push({ type: 'string' }); } else { const examplesObj = responseObj.content[contentKey].examples; if (examplesObj) { let examples = Object.values(examplesObj); if (contentKey.includes('json')) { examples = examples.map(example => { if (typeof example === 'string') { return JSON.parse(example); } return example; }); } responseObjForStatusCode.oneOf.push({ examples, }); } let example = responseObj.content[contentKey].example; if (example) { if (typeof example === 'string' && contentKey.includes('json')) { example = JSON.parse(example); } responseObjForStatusCode.oneOf.push({ examples: [example], }); } } } if (responseObjForStatusCode.oneOf.length === 1) { responseByStatusCode[responseKey] = responseByStatusCode[responseKey] || {}; responseByStatusCode[responseKey].responseSchema = responseObjForStatusCode.oneOf[0]; } else if (responseObjForStatusCode.oneOf.length > 1) { responseByStatusCode[responseKey] = responseByStatusCode[responseKey] || {}; responseByStatusCode[responseKey].responseSchema = responseObjForStatusCode; } } else if ('schema' in responseObj) { schemaObj = responseObj.schema; if (schemaObj && Object.keys(schemaObj).length > 0) { responseByStatusCode[responseKey] = responseByStatusCode[responseKey] || {}; responseByStatusCode[responseKey].responseSchema = schemaObj; } } else if ('examples' in responseObj) { const examples = Object.values(responseObj.examples); responseByStatusCode[responseKey] = responseByStatusCode[responseKey] || {}; let example = examples[0]; if (typeof example === 'string') { try { // Parse if possible example = JSON.parse(example); } catch (e) { // Do nothing } } responseByStatusCode[responseKey].responseSample = example; } else if (responseKey.toString() === '204') { responseByStatusCode[responseKey] = responseByStatusCode[responseKey] || {}; responseByStatusCode[responseKey].responseSchema = { type: 'null', description: responseObj.description, }; } if (hateOasConfig && schemaObj?.properties?.[hateOasConfig.linkObjectIdentifier]?.properties) { const links = (responseByStatusCode[responseKey].links ||= {}); await Promise.all(Object.keys(schemaObj.properties[hateOasConfig.linkObjectIdentifier].properties).map(async (linkName) => { const xLinkObj = schemaObj.properties?.[hateOasConfig.linkObjectIdentifier]?.[hateOasConfig.linkObjectExtensionIdentifier]?.find(link => link[hateOasConfig.linkNameIdentifier] === linkName); if (xLinkObj) { const xLinkHref = xLinkObj[hateOasConfig.linkPathIdentifier]; // Remove query parameters and path parameters for comparison const cleanXLinkHref = xLinkHref.split('?')[0].replace(/{[^}]+}/g, '{}'); const deferred = (0, utils_2.createDeferred)(); function findActualOperationAndPath(possibleName, possibleOasDoc, possibleMethodObjFieldMap) { let actualOperation; let actualPath; for (const path in possibleOasDoc.paths) { const cleanPath = path.split('?')[0].replace(/{[^}]+}/g, '{}'); if (cleanPath === cleanXLinkHref) { actualPath = path; // Find the operation by looking for GET method or first available method const pathObj = possibleOasDoc.paths[path]; actualOperation = pathObj.get || pathObj[Object.keys(pathObj)[0]]; break; } } if (actualOperation) { const args = {}; const paramsInLink = (0, string_interpolation_1.getInterpolationKeys)(xLinkHref); const paramsInTarget = (0, string_interpolation_1.getInterpolationKeys)(actualPath); for (const paramIndex in paramsInTarget) { args[paramsInTarget[paramIndex]] = `{root['${paramsInLink[paramIndex]}']}`; } if (possibleName === name) { links[linkName] = { get fieldName() { const linkOperationConfig = possibleMethodObjFieldMap.get(actualOperation); return linkOperationConfig.field; }, args, }; } else { const succesfulRes = actualOperation.responses[200]; if (succesfulRes && 'content' in succesfulRes) { const contentKeys = Object.keys(succesfulRes.content); const contentKey = contentKeys.find(contentKey => typeof contentKey === 'string' && contentKey.match('json')) || contentKeys[0]; const content = succesfulRes.content[contentKey]; const contentSchema = content.schema; let objectSchema; if ('$ref' in contentSchema) { throw new Error('Reference in response is not supported'); } else { if (contentSchema.type === 'array') { const items = (0, utils_2.asArray)(contentSchema.items); for (const item of items) { if ('$ref' in item) { throw new Error('Array of references is not supported'); } if (item.title) { objectSchema = item; break; } } } else if (contentSchema.title) { objectSchema = contentSchema; } } if (objectSchema) { const properties = {}; for (const paramName of paramsInTarget) { const propInTarget = objectSchema.properties[paramName]; if (propInTarget) { properties[paramName] = propInTarget; } } schemaObj.properties[linkName] = { ...objectSchema, properties, }; fusion_composition_1.futureAdditions.push({ targetTypeName: schemaObj.title, targetFieldName: linkName, sourceName: possibleName, sourceTypeName: 'Query', requiredSelectionSet: `{ ${paramsInLink.join(' ')} }`, get sourceFieldName() { const linkOperationConfig = possibleMethodObjFieldMap.get(actualOperation); return linkOperationConfig.field; }, sourceArgs: args, }); } } } contextFutureLinks.delete(findActualOperationAndPath); deferred.resolve(); return true; } return false; } // Only set timeout if we're not in batch mode (no context provided) if (!hateoasContext) { setTimeout(() => { logger.warn(`Could not find operation for link ${linkName} in ${name} for ${xLinkHref}`); contextFutureLinks.delete(findActualOperationAndPath); deferred.resolve(); }, 5000); } if (!findActualOperationAndPath(name, oasOrSwagger, methodObjFieldMap)) { contextFutureLinks.add(findActualOperationAndPath); } // In batch mode, resolve immediately since we'll handle resolution later if (hateoasContext) { deferred.resolve(); } return deferred.promise; } })); } if ('links' in responseObj) { const dereferencedLinkObj = await (0, json_machete_1.dereferenceObject)({ links: responseObj.links, }, { cwd, root: oasOrSwagger, readFileOrUrl: readFileOrUrlForJsonMachete, }); responseByStatusCode[responseKey].links = responseByStatusCode[responseKey].links || {}; for (const linkName in dereferencedLinkObj.links) { const linkObj = responseObj.links[linkName]; let args; if (linkObj.parameters) { args = {}; for (const parameterName in linkObj.parameters) { const parameterExp = linkObj.parameters[parameterName]; const sanitizedParamName = (0, utils_1.sanitizeNameForGraphQL)(parameterName); if (typeof parameterExp === 'string') { args[sanitizedParamName] = parameterExp.startsWith('$') ? `{root.${parameterExp}}` : parameterExp.split('$').join('root.$'); } else { args[sanitizedParamName] = parameterExp; } } } const sanitizedLinkName = (0, utils_1.sanitizeNameForGraphQL)(linkName); if ('operationRef' in linkObj) { const [externalPath, ref] = linkObj.operationRef.split('#'); if (externalPath) { logger.debug(`Skipping external operation reference ${linkObj.operationRef}\n Use additionalTypeDefs and additionalResolvers instead.`); } else { const actualOperation = (0, json_machete_1.resolvePath)(ref, oasOrSwagger); responseByStatusCode[responseKey].links[sanitizedLinkName] = { get fieldName() { const linkOperationConfig = methodObjFieldMap.get(actualOperation); return linkOperationConfig.field; }, args, description: linkObj.description, }; } } else if ('operationId' in linkObj) { responseByStatusCode[responseKey].links[sanitizedLinkName] = { fieldName: (0, utils_1.sanitizeNameForGraphQL)(linkObj.operationId), args, description: linkObj.description, }; } } } if (!operationConfig.field) { methodObj.operationId = (0, utils_js_1.getFieldNameFromPath)(relativePath, method, schemaObj?.$resolvedRef); // Operation ID might not be avaiable so let's generate field name from path and response type schema operationConfig.field = (0, utils_1.sanitizeNameForGraphQL)(methodObj.operationId); } // Give a better name to the request input object if (typeof operationConfig.requestSchema === 'object' && !operationConfig.requestSchema.title) { operationConfig.requestSchema.title = operationConfig.field + '_request'; } } if ('callbacks' in methodObj) { for (const callbackKey in methodObj.callbacks) { const callbackObj = methodObj.callbacks[callbackKey]; for (const callbackUrlRefKey in callbackObj) { if (callbackUrlRefKey.startsWith('$')) { continue; } const pubsubTopicSuffix = callbackUrlRefKey .split('$request.query') .join('args') .split('$request.body#/') .join('args.') .split('$response.body#/') .join('args.'); const callbackOperationConfig = { type: graphql_1.OperationTypeNode.SUBSCRIPTION, field: '', pubsubTopic: '', }; const callbackUrlObj = callbackObj[callbackUrlRefKey]; for (const method in callbackUrlObj) { const callbackOperation = callbackUrlObj[method]; callbackOperationConfig.pubsubTopic = `webhook:${method}:${pubsubTopicSuffix}`; callbackOperationConfig.field = callbackOperation.operationId; callbackOperationConfig.description = callbackOperation.description || callbackOperation.summary; const requestBodyContents = callbackOperation.requestBody?.content; if (requestBodyContents) { callbackOperationConfig.responseSchema = requestBodyContents[Object.keys(requestBodyContents)[0]].schema; } const responses = callbackOperation.responses; if (responses) { const response = responses[Object.keys(responses)[0]]; if (response) { const responseContents = response.content; if (responseContents) { callbackOperationConfig.requestSchema = responseContents[Object.keys(responseContents)[0]].schema; } } } } callbackOperationConfig.field = callbackOperationConfig.field || (0, utils_1.sanitizeNameForGraphQL)(callbackKey); operations.push(callbackOperationConfig); } } } if (fieldTypeMap[operationConfig.field]) { operationConfig.type = fieldTypeMap[operationConfig.field]; } } } // Register this schema in the context for future cross-references if (hateoasContext) { loadedSchemas.set(name, { oasDoc: oasOrSwagger, methodObjFieldMap, }); } return { operat