UNPKG

@cosmology/ast

Version:
662 lines (596 loc) 18.7 kB
import { GenericParseContext } from '../../../../encoding'; import { ProtoService, ProtoServiceMethod } from '@cosmology/types'; import { arrowFunctionExpression, classDeclaration, classMethod, classProperty, commentBlock, commentLine, identifier, tsMethodSignature } from '../../../../utils'; import { camel } from '@cosmology/utils'; import { returnReponseType, optionalBool, processRpcComment } from '../utils/rpc'; import { headersInit, initRequest, getInitReqProperties } from './utils'; import * as t from '@babel/types' const getFetchReqArgsService = ( name: string, packageImport: string ) => { // this hack around shouldn't exist, contact sdk team to modify the path for broadcastTx if (name === 'broadcastTx') { name = 'txs' } const fetchArgs = []; // first argument of fetchReq const argTemplateLiteral = t.templateLiteral( [ t.templateElement( { raw: '/' + packageImport.replace(/\./g, "/") + '/' + name, cooked: '/' + packageImport.replace(/\./g, "/") + '/' + name }, true, ) ], // quasis [], // empty expressions ) // adds proto path to fetchReq fetchArgs.push(argTemplateLiteral); // initReqProperties (contains information for initReq parameter in fetchReq) arguments: const initReqProperties = getInitReqProperties() const fetchArgsInitReqObj = t.objectExpression( initReqProperties ) // adds initReq parameter to fetchReq fetchArgs.push(fetchArgsInitReqObj) return fetchArgs } const grpcGatewayPOSTServiceMethodDefinition = ( name: string, svc: ProtoServiceMethod, packageImport: string, leadingComments?: t.CommentBlock[] ) => { const requestType = svc.requestType; const responseType = svc.responseType; const fieldNames = Object.keys(svc.fields ?? {}) const hasParams = fieldNames.length > 0; const optional = optionalBool(hasParams, fieldNames); // first parameter in method // ex: static Send(request: MsgSend) // paramRequest is an object representing everything in brackets here const paramRequest = identifier( 'request', t.tsTypeAnnotation( t.tsTypeReference( t.identifier(requestType), ) ), optional ); // fetchArgs will be used in method body's return statement expression. // Contains arguments to fm.fetchReq // this one is different from the Msg, especially the package name const fetchArgs = getFetchReqArgsService(name, packageImport) // method's body const body = t.blockStatement( [ t.returnStatement( t.callExpression( t.memberExpression( t.identifier('fm'), t.identifier('fetchReq'), ), fetchArgs, ) ) ] ) return classMethod( 'method', t.identifier(name), [paramRequest, initRequest], // params body, returnReponseType(responseType), leadingComments, false, true, // static ) } const staticExpressionsNoParams = t.callExpression( t.memberExpression( t.identifier('fm'), t.identifier('renderURLSearchParams'), false ), [ t.objectExpression( [ t.spreadElement( t.identifier('request') ) ] ), t.arrayExpression( [] ) ] ) // {...initReq, method: "GET"} const staticSecondFetchReqArg = t.objectExpression( [ t.spreadElement( t.identifier('initRequest') ), t.objectProperty( t.identifier('method'), t.stringLiteral('GET'), false, false ) ] ) const getQuasisNoParams = ( path: string ) => { let quasis: any[] = []; // path? // ex: /cosmos/bank/v1beta1/supply? quasis.push( t.templateElement( { raw: path + '?', cooked: path + '?' }, false ) ) // add empty tail element quasis.push( t.templateElement( { raw: '', cooked: '' }, true ) ) return quasis } // get quasis (string expressions) when there is an Params element const getQuasisParams = ( path: string, indicesLeft: number[], indicesRight: number[] ) => { let quasis: any[] = []; // add left path element to quasis (path before Params element) const firstPath = path.slice(0, indicesLeft[0]); quasis.push( t.templateElement( { raw: firstPath, cooked: firstPath, }, false ) ) // check if path end with param or quasis, get that quasis if any let lastPath = ''; if (indicesRight[indicesRight.length - 1] != path.length - 1) { lastPath = path.slice(indicesRight[indicesRight.length - 1] + 1, path.length); // console.log(lastPath); } // get paths in between params for (let i = 0; i < indicesLeft.length - 1; i++) { const tempPath = path.slice(indicesRight[i] + 1, indicesLeft[i + 1]); quasis.push( t.templateElement( { raw: tempPath, cooked: tempPath, }, false ) ) } // add remaining path (if exists) or only '?' sign quasis.push( t.templateElement( { raw: lastPath != '' ? lastPath + '?' : '?', cooked: lastPath != '' ? lastPath + '?' : '?' }, false ) ) // add empty tail element quasis.push( t.templateElement( { raw: '', cooked: '' }, true ) ) return quasis } const getExpressionsNoParams = () => { return [staticExpressionsNoParams] } // Get expressions for a path with Params. // Returning array must be of length 2. // example expressions: ${req["denom"]} AND ${fm.renderURLSearchParams(req, ["denom"])} const getExpressionsParams = ( paramsName: string[] ) => { let expressions: any[] = []; let arrParams = []; for (let i = 0; i < paramsName.length; i++) { // ${req["denom"]} expressions.push( t.memberExpression( t.identifier('request'), t.stringLiteral(paramsName[i]), true, ) ) arrParams.push( t.stringLiteral(paramsName[i]) ) } // ${fm.renderURLSearchParams(req, ["denom"])} expressions.push( t.callExpression( t.memberExpression( t.identifier('fm'), t.identifier('renderURLSearchParams'), false ), [ t.objectExpression( [ t.spreadElement( t.identifier('request') ) ] ), t.arrayExpression( arrParams ) ] ) ) return expressions } // Get fm.fetchReq arguments if there is no Params element // In this case, len of quasis must be 2 and len of expressions must be 1. const getFetchReqArgsNoParams = ( path: string ) => { let args: any[] = []; const quasis = getQuasisNoParams(path); const expressions = getExpressionsNoParams(); args.push( t.templateLiteral( quasis, expressions, ) ) // {...initReq, method: "GET"} args.push(staticSecondFetchReqArg); return args; } // Get fm.fetchReq arguments if there is an Params element const getFetchReqArgsParams = ( path: string, indicesLeft: number[], indicesRight: number[], ) => { let args: any[] = []; // first argument // ex: `/cosmos/staking/v1beta1/delegators/${req["delegator_addr"]}/validators/${req["validator_addr"]}? => quasis // ${fm.renderURLSearchParams(req, ["delegator_addr", "validator_addr"])}` => expressions let paramsName = []; for (let i = 0; i < indicesLeft.length; i++) { paramsName.push(path.slice(indicesLeft[i] + 1, indicesRight[i])); } const quasis = getQuasisParams(path, indicesLeft, indicesRight); const expressions = getExpressionsParams(paramsName); args.push( t.templateLiteral( quasis, expressions, ) ) // {...initReq, method: "GET"} args.push(staticSecondFetchReqArg); return args } // fetchArgs will be used in method body's return statement expression. // Contains arguments to fm.fetchReq const getFetchReqArgs = ( context: GenericParseContext, svc: ProtoServiceMethod, ) => { // getPath ex: // rpc Grants(QueryGrantsRequest) returns (QueryGrantsResponse) { // option (google.api.http).get = "/cosmos/authz/v1beta1/grants"; // } let getPath: string | undefined; try { getPath = svc.options['(google.api.http).get'] } catch { } if ((typeof getPath!) === 'undefined') { getPath = context.ref.proto.package + '.' + svc.name } let args: any[]; // check if getPath contains params // ex: "/cosmos/bank/v1beta1/balances/{address}" // NOTE: {address} here is param // contains params if (getPath!.indexOf('{') > -1) { // get all indices of '{' and '}' from path. const indicesLeft = [...getPath!.matchAll(/{/g)].map(match => match.index); const indicesRight = [...getPath!.matchAll(/}/g)].map(match => match.index); args = getFetchReqArgsParams(getPath!, indicesLeft, indicesRight); } else { // contains no params args = getFetchReqArgsNoParams(getPath!); } return args } // function to define a method of grpc-gateway fetch request const grpcGatewayMethodDefinition = ( context: GenericParseContext, name: string, svc: ProtoServiceMethod, leadingComments?: t.CommentBlock[] ) => { const requestType = svc.requestType; const responseType = svc.responseType; // first parameter in method // ex: static Send(request: MsgSend) // paramRequest is an object representing everything in brackets here const paramRequest = identifier( 'request', t.tsTypeAnnotation( t.tsTypeReference( t.identifier(requestType), ) ), false ); // fm.fetchReq(fetchArgs are here) const fetchArgs = getFetchReqArgs(context, svc); // class method body (only return statement) const body = t.blockStatement( [ t.returnStatement( t.callExpression( t.memberExpression( t.identifier('fm'), t.identifier('fetchReq'), false ), fetchArgs ) ) ] ) return classMethod( 'method', t.identifier(name), [paramRequest, initRequest], // params body, returnReponseType(responseType), leadingComments, false, true, // static ) } export const createGRPCGatewayQueryClass = ( context: GenericParseContext, service: ProtoService ) => { // adds import context.addUtil('fm'); const camelRpcMethods = context.pluginValue('rpcClients.camelCase'); const keys = Object.keys(service.methods ?? {}); //two different ways to generate methods for Query and Service let methods; //case Query if (service.name === "Query") { methods = keys .map(key => { const method = service.methods[key]; const name = camelRpcMethods ? camel(key) : key; const leadingComments = method.comment ? [commentBlock(processRpcComment(method))] : []; return grpcGatewayMethodDefinition( context, name, method, leadingComments ) }) } else { //case Service methods = keys .map(key => { const isGet = key.substring(0, 3) === "Get"; const method = service.methods[key]; const name = camelRpcMethods ? camel(key) : key; const leadingComments = method.comment ? [commentBlock(processRpcComment(method))] : []; if (!isGet) { //POST METHOD return grpcGatewayPOSTServiceMethodDefinition( name, method, context.ref.proto.package, leadingComments ) } else { return grpcGatewayMethodDefinition( context, name, method, leadingComments ) } }) } return t.exportNamedDeclaration( t.classDeclaration( t.identifier(service.name), null, t.classBody( [ ...methods, ] ), [] ), ) } // function to define a method of grpc-gateway style const grpcGatewayQuerierMethodDefinition = ( serviceName: string, context: GenericParseContext, name: string, svc: ProtoServiceMethod, leadingComments?: t.CommentBlock[] ) => { const requestType = svc.requestType; const responseType = svc.responseType; // first parameter in method // ex: static Send(request: MsgSend) // paramRequest is an object representing everything in brackets here const paramRequest = identifier( 'req', t.tsTypeAnnotation( t.tsTypeReference( t.identifier(requestType), ) ), false ); // class method body (only return statement) const body = t.blockStatement( [ t.returnStatement( t.callExpression( t.memberExpression( t.identifier(serviceName), t.identifier(name), false ), [ t.identifier('req'), t.objectExpression( [ t.objectProperty( t.identifier('headers'), t.identifier('headers'), false, true ), t.objectProperty( t.identifier('pathPrefix'), t.memberExpression( t.thisExpression(), t.identifier('url') ) ) ] ) ] ) ) ] ) return classMethod( 'method', t.identifier(name), [paramRequest, headersInit], // params body, returnReponseType(responseType), leadingComments, false, false, // static false, true // async ) } export const createGRPCGatewayWrapperClass = ( context: GenericParseContext, service: ProtoService ) => { const serviceName = service.name; let className: string; if (serviceName === 'Query') { // QueryClientImp for wrapper class className = 'QueryClientImpl' } else { className = 'ServiceClientImpl' } const camelRpcMethods = context.pluginValue('rpcClients.camelCase'); const keys = Object.keys(service.methods ?? {}); const methods = keys .map(key => { const method = service.methods[key]; const name = camelRpcMethods ? camel(key) : key; const leadingComments = method.comment ? [commentBlock(processRpcComment(method))] : []; return grpcGatewayQuerierMethodDefinition( serviceName, context, name, method, leadingComments ) }) return t.exportNamedDeclaration( t.classDeclaration( t.identifier(className), null, t.classBody( [ classProperty( t.identifier('url'), null, t.tsTypeAnnotation( t.tsStringKeyword() ), [], false, false, true, "private" ), t.classMethod( 'constructor', t.identifier('constructor'), [ identifier( 'url', t.tsTypeAnnotation( t.tsStringKeyword() ) ) ], t.blockStatement( [ t.expressionStatement( t.assignmentExpression( '=', t.memberExpression( t.thisExpression(), t.identifier('url') ), t.identifier('url') ) ) ] ) ), ...methods, ] ), [] ), ) }