@graphql-mesh/thrift
Version:
481 lines (477 loc) • 24.1 kB
JavaScript
'use strict';
const thriftParser = require('@creditkarma/thrift-parser');
const utils = require('@graphql-mesh/utils');
const graphql = require('graphql');
const graphqlScalars = require('graphql-scalars');
const thriftClient = require('@creditkarma/thrift-client');
const thriftServerCore = require('@creditkarma/thrift-server-core');
const pascalCase = require('pascal-case');
const store = require('@graphql-mesh/store');
const utils$1 = require('@graphql-tools/utils');
const stringInterpolation = require('@graphql-mesh/string-interpolation');
const crossHelpers = require('@graphql-mesh/cross-helpers');
class ThriftHandler {
constructor({ config, baseDir, store: store$1, fetchFn, importFn, logger }) {
this.config = config;
this.baseDir = baseDir;
this.idl = store$1.proxy('idl.json', store.PredefinedProxyOptions.JsonWithoutValidation);
this.fetchFn = fetchFn;
this.importFn = importFn;
this.logger = logger;
}
async getMeshSource() {
var _a, _b;
const { schemaHeaders, serviceName, operationHeaders } = this.config;
const thriftAST = await this.idl.getWithSet(async () => {
const rawThrift = await utils.readFileOrUrl(this.config.idl, {
allowUnknownExtensions: true,
cwd: this.baseDir,
headers: schemaHeaders,
fetch: this.fetchFn,
logger: this.logger,
importFn: this.importFn,
});
const parseResult = thriftParser.parse(rawThrift, { organize: false });
if (parseResult.type === thriftParser.SyntaxType.ThriftErrors) {
if (parseResult.errors.length === 1) {
throw parseResult.errors[0];
}
throw new utils$1.AggregateError(parseResult.errors);
}
return parseResult;
});
const enumTypeMap = new Map();
const outputTypeMap = new Map();
const inputTypeMap = new Map();
const rootFields = {};
const annotations = {};
const methodAnnotations = {};
const methodNames = [];
const methodParameters = {};
const topTypeMap = {};
class MeshThriftClient extends thriftServerCore.ThriftClient {
constructor() {
super(...arguments);
this._serviceName = serviceName;
this._annotations = annotations;
this._methodAnnotations = methodAnnotations;
this._methodNames = methodNames;
this._methodParameters = methodParameters;
}
writeType(typeVal, value, output) {
switch (typeVal.type) {
case thriftServerCore.TType.BOOL:
output.writeBool(value);
break;
case thriftServerCore.TType.BYTE:
output.writeByte(value);
break;
case thriftServerCore.TType.DOUBLE:
output.writeDouble(value);
break;
case thriftServerCore.TType.I16:
output.writeI16(value);
break;
case thriftServerCore.TType.I32:
output.writeI32(value);
break;
case thriftServerCore.TType.I64:
output.writeI64(value.toString());
break;
case thriftServerCore.TType.STRING:
output.writeString(value);
break;
case thriftServerCore.TType.STRUCT: {
output.writeStructBegin(typeVal.name);
const typeMap = typeVal.fields;
for (const argName in value) {
const argType = typeMap[argName];
const argVal = value[argName];
if (argType) {
output.writeFieldBegin(argName, argType.type, argType.id);
this.writeType(argType, argVal, output);
output.writeFieldEnd();
}
}
output.writeFieldStop();
output.writeStructEnd();
break;
}
case thriftServerCore.TType.ENUM:
// TODO: A
break;
case thriftServerCore.TType.MAP: {
const keys = Object.keys(value);
output.writeMapBegin(typeVal.keyType.type, typeVal.valType.type, keys.length);
for (const key of keys) {
this.writeType(typeVal.keyType, key, output);
const val = value[key];
this.writeType(typeVal.valType, val, output);
}
output.writeMapEnd();
break;
}
case thriftServerCore.TType.LIST:
output.writeListBegin(typeVal.elementType.type, value.length);
for (const element of value) {
this.writeType(typeVal.elementType, element, output);
}
output.writeListEnd();
break;
case thriftServerCore.TType.SET:
output.writeSetBegin(typeVal.elementType.type, value.length);
for (const element of value) {
this.writeType(typeVal.elementType, element, output);
}
output.writeSetEnd();
break;
}
}
readType(type, input) {
switch (type) {
case thriftServerCore.TType.BOOL:
return input.readBool();
case thriftServerCore.TType.BYTE:
return input.readByte();
case thriftServerCore.TType.DOUBLE:
return input.readDouble();
case thriftServerCore.TType.I16:
return input.readI16();
case thriftServerCore.TType.I32:
return input.readI32();
case thriftServerCore.TType.I64:
return BigInt(input.readI64().toString());
case thriftServerCore.TType.STRING:
return input.readString();
case thriftServerCore.TType.STRUCT: {
const result = {};
input.readStructBegin();
while (true) {
const field = input.readFieldBegin();
const fieldType = field.fieldType;
const fieldName = field.fieldName || 'success';
if (fieldType === thriftServerCore.TType.STOP) {
break;
}
result[fieldName] = this.readType(fieldType, input);
input.readFieldEnd();
}
input.readStructEnd();
return result;
}
case thriftServerCore.TType.ENUM:
// TODO: A
break;
case thriftServerCore.TType.MAP: {
const result = {};
const map = input.readMapBegin();
for (let i = 0; i < map.size; i++) {
const key = this.readType(map.keyType, input);
const value = this.readType(map.valueType, input);
result[key] = value;
}
input.readMapEnd();
return result;
}
case thriftServerCore.TType.LIST: {
const result = [];
const list = input.readListBegin();
for (let i = 0; i < list.size; i++) {
const element = this.readType(list.elementType, input);
result.push(element);
}
input.readListEnd();
return result;
}
case thriftServerCore.TType.SET: {
const result = [];
const list = input.readSetBegin();
for (let i = 0; i < list.size; i++) {
const element = this.readType(list.elementType, input);
result.push(element);
}
input.readSetEnd();
return result;
}
}
}
async doRequest(methodName, args, fields, context) {
const Transport = this.transport;
const Protocol = this.protocol;
const writer = new Transport();
const output = new Protocol(writer);
const id = this.incrementRequestId();
output.writeMessageBegin(methodName, thriftServerCore.MessageType.CALL, id);
this.writeType({
name: pascalCase.pascalCase(methodName) + '__Args',
type: thriftServerCore.TType.STRUCT,
fields,
id,
}, args, output);
output.writeMessageEnd();
const data = await this.connection.send(writer.flush(), context);
const reader = this.transport.receiver(data);
const input = new Protocol(reader);
const { fieldName, messageType } = input.readMessageBegin();
if (fieldName === methodName) {
if (messageType === thriftServerCore.MessageType.EXCEPTION) {
const err = thriftServerCore.TApplicationExceptionCodec.decode(input);
input.readMessageEnd();
return Promise.reject(err);
}
else {
const result = this.readType(thriftServerCore.TType.STRUCT, input);
input.readMessageEnd();
if (result.success != null) {
return result.success;
}
else {
throw new thriftServerCore.TApplicationException(thriftServerCore.TApplicationExceptionType.UNKNOWN, methodName + ' failed: unknown result');
}
}
}
else {
throw new thriftServerCore.TApplicationException(thriftServerCore.TApplicationExceptionType.WRONG_METHOD_NAME, 'Received a response to an unknown RPC function: ' + fieldName);
}
}
}
MeshThriftClient.serviceName = serviceName;
MeshThriftClient.annotations = annotations;
MeshThriftClient.methodAnnotations = methodAnnotations;
MeshThriftClient.methodNames = methodNames;
const thriftHttpClient = thriftClient.createHttpClient(MeshThriftClient, {
...this.config,
requestOptions: {
headers: operationHeaders,
},
});
function processComments(comments) {
return comments.map(comment => comment.value).join('\n');
}
function getGraphQLFunctionType(functionType, id = Math.random()) {
let inputType;
let outputType;
let typeVal;
switch (functionType.type) {
case thriftParser.SyntaxType.BinaryKeyword:
case thriftParser.SyntaxType.StringKeyword:
inputType = graphql.GraphQLString;
outputType = graphql.GraphQLString;
break;
case thriftParser.SyntaxType.DoubleKeyword:
inputType = graphql.GraphQLFloat;
outputType = graphql.GraphQLFloat;
typeVal = typeVal || { type: thriftServerCore.TType.DOUBLE };
break;
case thriftParser.SyntaxType.VoidKeyword:
typeVal = typeVal || { type: thriftServerCore.TType.VOID };
inputType = graphqlScalars.GraphQLVoid;
outputType = graphqlScalars.GraphQLVoid;
break;
case thriftParser.SyntaxType.BoolKeyword:
typeVal = typeVal || { type: thriftServerCore.TType.BOOL };
inputType = graphql.GraphQLBoolean;
outputType = graphql.GraphQLBoolean;
break;
case thriftParser.SyntaxType.I8Keyword:
inputType = graphql.GraphQLInt;
outputType = graphql.GraphQLInt;
typeVal = typeVal || { type: thriftServerCore.TType.I08 };
break;
case thriftParser.SyntaxType.I16Keyword:
inputType = graphql.GraphQLInt;
outputType = graphql.GraphQLInt;
typeVal = typeVal || { type: thriftServerCore.TType.I16 };
break;
case thriftParser.SyntaxType.I32Keyword:
inputType = graphql.GraphQLInt;
outputType = graphql.GraphQLInt;
typeVal = typeVal || { type: thriftServerCore.TType.I32 };
break;
case thriftParser.SyntaxType.ByteKeyword:
inputType = graphqlScalars.GraphQLByte;
outputType = graphqlScalars.GraphQLByte;
typeVal = typeVal || { type: thriftServerCore.TType.BYTE };
break;
case thriftParser.SyntaxType.I64Keyword:
inputType = graphqlScalars.GraphQLBigInt;
outputType = graphqlScalars.GraphQLBigInt;
typeVal = typeVal || { type: thriftServerCore.TType.I64 };
break;
case thriftParser.SyntaxType.ListType: {
const ofTypeList = getGraphQLFunctionType(functionType.valueType, id);
inputType = new graphql.GraphQLList(ofTypeList.inputType);
outputType = new graphql.GraphQLList(ofTypeList.outputType);
typeVal = typeVal || { type: thriftServerCore.TType.LIST, elementType: ofTypeList.typeVal };
break;
}
case thriftParser.SyntaxType.SetType: {
const ofSetType = getGraphQLFunctionType(functionType.valueType, id);
inputType = new graphql.GraphQLList(ofSetType.inputType);
outputType = new graphql.GraphQLList(ofSetType.outputType);
typeVal = typeVal || { type: thriftServerCore.TType.SET, elementType: ofSetType.typeVal };
break;
}
case thriftParser.SyntaxType.MapType: {
inputType = graphqlScalars.GraphQLJSON;
outputType = graphqlScalars.GraphQLJSON;
const ofTypeKey = getGraphQLFunctionType(functionType.keyType, id);
const ofTypeValue = getGraphQLFunctionType(functionType.valueType, id);
typeVal = typeVal || { type: thriftServerCore.TType.MAP, keyType: ofTypeKey.typeVal, valType: ofTypeValue.typeVal };
break;
}
case thriftParser.SyntaxType.Identifier: {
const typeName = functionType.value;
if (enumTypeMap.has(typeName)) {
const enumType = enumTypeMap.get(typeName);
inputType = enumType;
outputType = enumType;
}
if (inputTypeMap.has(typeName)) {
inputType = inputTypeMap.get(typeName);
}
if (outputTypeMap.has(typeName)) {
outputType = outputTypeMap.get(typeName);
}
typeVal = topTypeMap[typeName];
break;
}
default:
throw new Error(`Unknown function type: ${crossHelpers.util.inspect(functionType)}!`);
}
return {
inputType: inputType,
outputType: outputType,
typeVal: {
...typeVal,
id,
},
};
}
const { args: commonArgs, contextVariables } = stringInterpolation.parseInterpolationStrings(Object.values(operationHeaders || {}));
const headersFactory = stringInterpolation.getInterpolatedHeadersFactory(operationHeaders);
for (const statement of thriftAST.body) {
switch (statement.type) {
case thriftParser.SyntaxType.EnumDefinition:
enumTypeMap.set(statement.name.value, new graphql.GraphQLEnumType({
name: statement.name.value,
description: processComments(statement.comments),
values: statement.members.reduce((prev, curr) => ({
...prev,
[curr.name.value]: {
description: processComments(curr.comments),
value: curr.name.value,
},
}), {}),
}));
break;
case thriftParser.SyntaxType.StructDefinition: {
const structName = statement.name.value;
const description = processComments(statement.comments);
const objectFields = {};
const inputObjectFields = {};
const structTypeVal = {
id: Math.random(),
name: structName,
type: thriftServerCore.TType.STRUCT,
fields: {},
};
topTypeMap[structName] = structTypeVal;
const structFieldTypeMap = structTypeVal.fields;
for (const field of statement.fields) {
const fieldName = field.name.value;
let fieldOutputType;
let fieldInputType;
const description = processComments(field.comments);
const processedFieldTypes = getGraphQLFunctionType(field.fieldType, (_a = field.fieldID) === null || _a === void 0 ? void 0 : _a.value);
fieldOutputType = processedFieldTypes.outputType;
fieldInputType = processedFieldTypes.inputType;
if (field.requiredness === 'required') {
fieldOutputType = new graphql.GraphQLNonNull(fieldOutputType);
fieldInputType = new graphql.GraphQLNonNull(fieldInputType);
}
objectFields[fieldName] = {
type: fieldOutputType,
description,
};
inputObjectFields[fieldName] = {
type: fieldInputType,
description,
};
structFieldTypeMap[fieldName] = processedFieldTypes.typeVal;
}
outputTypeMap.set(structName, new graphql.GraphQLObjectType({
name: structName,
description,
fields: objectFields,
}));
inputTypeMap.set(structName, new graphql.GraphQLInputObjectType({
name: structName + 'Input',
description,
fields: inputObjectFields,
}));
break;
}
case thriftParser.SyntaxType.ServiceDefinition:
for (const fnIndex in statement.functions) {
const fn = statement.functions[fnIndex];
const fnName = fn.name.value;
const description = processComments(fn.comments);
const { outputType: returnType } = getGraphQLFunctionType(fn.returnType, Number(fnIndex) + 1);
const args = {};
for (const argName in commonArgs) {
const typeNameOrType = commonArgs[argName].type;
args[argName] = {
type: typeof typeNameOrType === 'string' ? inputTypeMap.get(typeNameOrType) : typeNameOrType || graphql.GraphQLID,
};
}
const fieldTypeMap = {};
for (const field of fn.fields) {
const fieldName = field.name.value;
const fieldDescription = processComments(field.comments);
let { inputType: fieldType, typeVal } = getGraphQLFunctionType(field.fieldType, (_b = field.fieldID) === null || _b === void 0 ? void 0 : _b.value);
if (field.requiredness === 'required') {
fieldType = new graphql.GraphQLNonNull(fieldType);
}
args[fieldName] = {
type: fieldType,
description: fieldDescription,
};
fieldTypeMap[fieldName] = typeVal;
}
rootFields[fnName] = {
type: returnType,
description,
args,
resolve: async (root, args, context, info) => thriftHttpClient.doRequest(fnName, args, fieldTypeMap, {
headers: headersFactory({ root, args, context, info, env: crossHelpers.process.env }),
}),
};
methodNames.push(fnName);
methodAnnotations[fnName] = { annotations: {}, fieldAnnotations: {} };
methodParameters[fnName] = fn.fields.length + 1;
}
break;
case thriftParser.SyntaxType.TypedefDefinition: {
const { inputType, outputType } = getGraphQLFunctionType(statement.definitionType, Math.random());
const typeName = statement.name.value;
inputTypeMap.set(typeName, inputType);
outputTypeMap.set(typeName, outputType);
break;
}
}
}
const queryObjectType = new graphql.GraphQLObjectType({
name: 'Query',
fields: rootFields,
});
const schema = new graphql.GraphQLSchema({
query: queryObjectType,
});
return {
schema,
contextVariables,
};
}
}
module.exports = ThriftHandler;