UNPKG

@omnigraph/raml

Version:

This package generates `GraphQLSchema` instance from **RAML API Document** (`.raml`) file located at a URL or FileSystem by resolving the JSON Schema dependencies. It uses `@omnigraph/json-schema` by generating the necessary configuration.

225 lines (224 loc) • 10.4 kB
import { getAbsolutePath, getCwd } from 'json-machete'; import toJsonSchema from 'to-json-schema'; import { loadApi } from '@ardatan/raml-1-parser'; import { process } from '@graphql-mesh/cross-helpers'; import { getInterpolatedHeadersFactory } from '@graphql-mesh/string-interpolation'; import { sanitizeNameForGraphQL } from '@graphql-mesh/utils'; import { asArray } from '@graphql-tools/utils'; import { fetch as crossUndiciFetch } from '@whatwg-node/fetch'; import { getFieldNameFromPath } from './utils.js'; function resolveTraitsByIs(base) { const allTraits = []; for (const traitRef of base.is()) { const traitNode = traitRef.trait(); if (traitNode) { allTraits.push(traitNode); allTraits.push(...resolveTraitsByIs(traitNode)); } } return allTraits; } /** * Generates the options for JSON Schema Loader * from RAML Loader options by extracting the JSON Schema references * from RAML API Document */ export async function getJSONSchemaOptionsFromRAMLOptions({ source, cwd: ramlFileCwd = process.cwd(), operations: extraOperations, endpoint: forcedBaseUrl, fetch = crossUndiciFetch, schemaHeaders = {}, selectQueryOrMutationField = [], }) { const fieldTypeMap = {}; for (const { fieldName, type } of selectQueryOrMutationField) { fieldTypeMap[fieldName] = type; } const operations = extraOperations || []; const ramlAbsolutePath = getAbsolutePath(source, ramlFileCwd); const schemaHeadersFactory = getInterpolatedHeadersFactory(schemaHeaders); const ramlAPI = (await loadApi(ramlAbsolutePath, [], { httpResolver: { getResourceAsync: async (url) => { const fetchResponse = await fetch(url, { headers: schemaHeadersFactory({ env: process.env }), }); const content = await fetchResponse.text(); if (fetchResponse.status !== 200) { return { errorMessage: content, }; } return { content, }; }, getResource: () => { throw new Error(`Sync fetching not available for URLs`); }, }, })); let endpoint = forcedBaseUrl; if (!endpoint) { endpoint = ramlAPI.baseUri().value(); for (const endpointParamNode of ramlAPI.baseUriParameters()) { const paramName = endpointParamNode.name(); endpoint = endpoint.split(`{${paramName}}`).join(`{context.${paramName}}`); } } const pathTypeMap = new Map(); const typePathMap = new Map(); for (const typeNode of ramlAPI.types()) { const typeNodeJson = typeNode.toJSON(); for (const typeName in typeNodeJson) { const { schemaPath } = typeNodeJson[typeName]; if (schemaPath) { pathTypeMap.set(schemaPath, typeName); typePathMap.set(typeName, schemaPath); } } } const cwd = getCwd(ramlAbsolutePath); const apiQueryParameters = []; const apiBodyNodes = []; const apiResponses = []; for (const traitNode of ramlAPI.traits()) { apiQueryParameters.push(...traitNode.queryParameters()); apiBodyNodes.push(...traitNode.body()); apiResponses.push(...traitNode.responses()); const nestedTraits = resolveTraitsByIs(traitNode); for (const nestedTrait of nestedTraits) { apiQueryParameters.push(...nestedTrait.queryParameters()); apiBodyNodes.push(...nestedTrait.body()); apiResponses.push(...nestedTrait.responses()); } } for (const resourceNode of ramlAPI.allResources()) { const resourceQueryParameters = [...apiQueryParameters]; const resourceBodyNodes = [...apiBodyNodes]; const resourceResponses = [...apiResponses]; const resourceTraits = resolveTraitsByIs(resourceNode); for (const traitNode of resourceTraits) { apiQueryParameters.push(...traitNode.queryParameters()); apiBodyNodes.push(...traitNode.body()); apiResponses.push(...traitNode.responses()); } for (const methodNode of resourceNode.methods()) { const queryParameters = [...resourceQueryParameters]; const bodyNodes = [...resourceBodyNodes]; const responses = [...resourceResponses]; const traits = resolveTraitsByIs(methodNode); for (const traitNode of traits) { queryParameters.push(...traitNode.queryParameters()); bodyNodes.push(...traitNode.body()); responses.push(...traitNode.responses()); } queryParameters.push(...methodNode.queryParameters()); bodyNodes.push(...methodNode.body()); responses.push(...methodNode.responses()); let requestSchema; let requestTypeName; const responseByStatusCode = {}; const method = methodNode.method().toUpperCase(); let fieldName = methodNode.displayName()?.replace('GET_', ''); const description = methodNode.description()?.value() || resourceNode.description()?.value(); const originalFullRelativeUrl = resourceNode.completeRelativeUri(); let fullRelativeUrl = originalFullRelativeUrl; const argTypeMap = {}; const queryParamArgMap = {}; for (const uriParameterNode of resourceNode.uriParameters()) { const paramName = uriParameterNode.name(); const argName = sanitizeNameForGraphQL(paramName); fullRelativeUrl = fullRelativeUrl.replace(`{${paramName}}`, `{args.${argName}}`); const uriParameterNodeJson = uriParameterNode.toJSON(); if (uriParameterNodeJson.displayName) { uriParameterNodeJson.title = uriParameterNodeJson.displayName; } argTypeMap[argName] = uriParameterNodeJson; } for (const queryParameterNode of queryParameters) { const parameterName = queryParameterNode.name(); const argName = sanitizeNameForGraphQL(parameterName); const queryParameterNodeJson = queryParameterNode.toJSON(); if (queryParameterNodeJson.displayName) { queryParameterNodeJson.title = queryParameterNodeJson.displayName; } queryParamArgMap[parameterName] = argName; argTypeMap[argName] = queryParameterNodeJson; } for (const bodyNode of bodyNodes) { if (bodyNode.name().includes('application/json')) { const bodyJson = bodyNode.toJSON(); if (bodyJson.schemaPath) { const schemaPath = bodyJson.schemaPath; requestSchema = schemaPath; requestTypeName = pathTypeMap.get(schemaPath); } else if (bodyJson.type) { const typeName = asArray(bodyJson.type)[0]; requestTypeName = typeName; const schemaPath = typePathMap.get(typeName); requestSchema = schemaPath; } } } for (const responseNode of responses) { const statusCode = responseNode.code().value(); const responseNodeDescription = responseNode.description()?.value(); for (const bodyNode of responseNode.body()) { if (bodyNode.name().includes('application/json')) { const bodyJson = bodyNode.toJSON(); if (bodyJson.schemaPath) { const schemaPath = bodyJson.schemaPath; const typeName = pathTypeMap.get(schemaPath); if (schemaPath) { responseByStatusCode[statusCode] = { responseSchema: schemaPath, responseTypeName: typeName, }; } } else if (bodyJson.type) { const typeName = asArray(bodyJson.type)[0]; const schemaPath = typePathMap.get(typeName); if (schemaPath) { responseByStatusCode[statusCode] = { responseSchema: schemaPath, responseTypeName: typeName, }; } } if (!responseByStatusCode[statusCode] && bodyJson.example) { const responseSchema = toJsonSchema(bodyJson.example, { required: false, }); responseSchema.description = responseNodeDescription; responseByStatusCode[statusCode] = { responseSchema, }; } } } } fieldName = fieldName || getFieldNameFromPath(originalFullRelativeUrl, method, responseByStatusCode['200']?.responseTypeName); if (fieldName) { const graphQLFieldName = sanitizeNameForGraphQL(fieldName); const operationType = fieldTypeMap[graphQLFieldName] ?? method === 'GET' ? 'query' : 'mutation'; operations.push({ type: operationType, field: graphQLFieldName, description, path: fullRelativeUrl, method, requestSchema, requestTypeName, responseByStatusCode, queryParamArgMap, argTypeMap, }); } } } return { operations, endpoint, cwd, fetch, }; }