@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.
303 lines (294 loc) • 13.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
const jsonSchema = require('@omnigraph/json-schema');
const utils = require('@graphql-mesh/utils');
const jsonMachete = require('json-machete');
const raml1Parser = require('@ardatan/raml-1-parser');
const fetch = require('@whatwg-node/fetch');
const toJsonSchema = _interopDefault(require('to-json-schema'));
const utils$1 = require('@graphql-tools/utils');
const changeCase = require('change-case');
const stringInterpolation = require('@graphql-mesh/string-interpolation');
const crossHelpers = require('@graphql-mesh/cross-helpers');
function getFieldNameFromPath(path, method, typeName) {
// Replace identifiers with "by"
path = path.split('{').join('by_').split('}').join('');
const [actualPartsStr, allQueryPartsStr] = path.split('?');
const actualParts = actualPartsStr.split('/').filter(Boolean);
let fieldNameWithoutMethod = actualParts.join('_');
// If path doesn't give any field name without identifiers, we can use the return type with HTTP Method name
if ((!fieldNameWithoutMethod || fieldNameWithoutMethod.startsWith('by')) && typeName) {
// lowercase looks better in the schema
const prefix = changeCase.camelCase(typeName);
if (fieldNameWithoutMethod) {
fieldNameWithoutMethod = prefix + '_' + fieldNameWithoutMethod;
}
else {
fieldNameWithoutMethod = prefix;
}
}
if (allQueryPartsStr) {
const queryParts = allQueryPartsStr.split('&');
for (const queryPart of queryParts) {
const [queryName] = queryPart.split('=');
fieldNameWithoutMethod += '_' + 'by' + '_' + queryName;
}
}
// get_ doesn't look good in field names
const methodPrefix = method.toLowerCase();
if (methodPrefix === 'get') {
return fieldNameWithoutMethod;
}
return methodPrefix + '_' + fieldNameWithoutMethod;
}
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
*/
async function getJSONSchemaOptionsFromRAMLOptions({ ramlFilePath, cwd: ramlFileCwd = crossHelpers.process.cwd(), operations: extraOperations, baseUrl: forcedBaseUrl, fetch: fetch$1 = fetch.fetch, schemaHeaders = {}, selectQueryOrMutationField = [], }) {
var _a, _b, _c, _d, _e, _f;
const fieldTypeMap = {};
for (const { fieldName, type } of selectQueryOrMutationField) {
fieldTypeMap[fieldName] = type;
}
const operations = extraOperations || [];
const ramlAbsolutePath = jsonMachete.getAbsolutePath(ramlFilePath, ramlFileCwd);
const schemaHeadersFactory = stringInterpolation.getInterpolatedHeadersFactory(schemaHeaders);
const ramlAPI = (await raml1Parser.loadApi(ramlAbsolutePath, [], {
httpResolver: {
getResourceAsync: async (url) => {
const fetchResponse = await fetch$1(url, {
headers: schemaHeadersFactory({ env: crossHelpers.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 baseUrl = forcedBaseUrl;
if (!baseUrl) {
baseUrl = ramlAPI.baseUri().value();
for (const baseUrlParamNode of ramlAPI.baseUriParameters()) {
const paramName = baseUrlParamNode.name();
baseUrl = baseUrl.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 = jsonMachete.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 = (_a = methodNode.displayName()) === null || _a === void 0 ? void 0 : _a.replace('GET_', '');
const description = ((_b = methodNode.description()) === null || _b === void 0 ? void 0 : _b.value()) || ((_c = resourceNode.description()) === null || _c === void 0 ? void 0 : _c.value());
const originalFullRelativeUrl = resourceNode.completeRelativeUri();
let fullRelativeUrl = originalFullRelativeUrl;
const argTypeMap = {};
for (const uriParameterNode of resourceNode.uriParameters()) {
const paramName = uriParameterNode.name();
const argName = utils.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 = utils.sanitizeNameForGraphQL(parameterName);
const queryParameterNodeJson = queryParameterNode.toJSON();
if (queryParameterNodeJson.displayName) {
queryParameterNodeJson.title = queryParameterNodeJson.displayName;
}
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 = utils$1.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 = (_d = responseNode.description()) === null || _d === void 0 ? void 0 : _d.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 = utils$1.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, (_e = responseByStatusCode['200']) === null || _e === void 0 ? void 0 : _e.responseTypeName);
if (fieldName) {
const graphQLFieldName = utils.sanitizeNameForGraphQL(fieldName);
const operationType = ((_f = fieldTypeMap[graphQLFieldName]) !== null && _f !== void 0 ? _f : method === 'GET') ? 'query' : 'mutation';
operations.push({
type: operationType,
field: graphQLFieldName,
description,
path: fullRelativeUrl,
method,
requestSchema,
requestTypeName,
responseByStatusCode,
argTypeMap,
});
}
}
}
return {
operations,
baseUrl,
cwd,
fetch: fetch$1,
};
}
/**
* Creates a local GraphQLSchema instance from a RAML API Document.
* Everytime this function is called, the RAML file and its dependencies will be resolved on runtime.
* If you want to avoid this, use `createBundle` function to create a bundle once and save it to a storage
* then load it with `loadGraphQLSchemaFromBundle`.
*/
async function loadGraphQLSchemaFromRAML(name, options) {
const extraJSONSchemaOptions = await getJSONSchemaOptionsFromRAMLOptions(options);
return jsonSchema.loadGraphQLSchemaFromJSONSchemas(name, {
...options,
...extraJSONSchemaOptions,
});
}
/**
* Creates a bundle by downloading and resolving the internal references once
* to load the schema locally later
*/
async function createBundle(name, ramlLoaderOptions) {
const { operations, baseUrl, cwd, fetch } = await getJSONSchemaOptionsFromRAMLOptions(ramlLoaderOptions);
return jsonSchema.createBundle(name, {
...ramlLoaderOptions,
operationHeaders: typeof ramlLoaderOptions.operationHeaders === 'object' ? ramlLoaderOptions.operationHeaders : {},
baseUrl,
operations,
cwd,
fetch,
});
}
Object.defineProperty(exports, 'getGraphQLSchemaFromBundle', {
enumerable: true,
get: function () {
return jsonSchema.getGraphQLSchemaFromBundle;
}
});
exports.createBundle = createBundle;
exports.default = loadGraphQLSchemaFromRAML;
exports.getJSONSchemaOptionsFromRAMLOptions = getJSONSchemaOptionsFromRAMLOptions;