0

I'm working on a nest.js microservices project. The controllers and services are defined and the back-end runs without errors. I'm trying to create a resource(subscriptionPlan in this example) using grpCurl, like this:

 grpcurl -d '{
  "name": "Test GRPC",
  "code": "12312",
  "description": "test",
  "price": 10,
  "invoicePeriod": 10,
  "invoiceDuration":"DAY"
}' -plaintext  -proto rpc/rpc.proto 127.0.0.1:5000 rpc.SubscriptionPlanService/Create

when I run this command I get an error message: Failed to process proto source files.: could not parse given files: %!v(PANIC=Error method: runtime error: invalid memory address or nil pointer dereference).

I feel there isn't a problem in the back-end/services code , it's something with the project set-up maybe I'm missing a library on command-line tools. I checked x-code dev tools are installed and all the module dependencies are also installed.

here is my : subscription_plan.service.ts

/* eslint-disable complexity */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { injectable } from 'inversify';
import _ from 'lodash';

import { createEverLogger } from '../../helpers/Log';
import { ErrorGenerator } from '../../shared/errors.generator';
import {
  BadRequestError,
  ConflictError,
  NotFoundError,
  ParseError,
} from '../../shared/errors.messages';
import { DatabaseService } from '../database/database.service';
import { servicesContainer } from '../inversify.config';
import { IService } from '../IService';
import { SubscriptionPlans } from './model/subscription_plan.model';
import {
  DeleteSubscriptionPlanResponse,
  ISubscriptionPlanService,
  SubscriptionInputPayload,
  SubscriptionPlan,
  SubscriptionPlanFilter,
  SubscriptionPlanResponse,
  SubscriptionPlanUpdatePayload,
  UpdateSubscriptionPlanResponse,
} from './types/subscription_plan.types';
import { subscriptionPlanCreateSchema } from './validators/subscription_plan.create.yup';
import { subscriptionPlanFilterSchema } from './validators/subscription_plan.filter.yup';
import { subscriptionPlanUpdateSchema } from './validators/subscription_plan.update.yup';

/**
 * Subscription Plans Service
 * CRUD operation for Subscription Plan
 * @export
 * @class SubscriptionPlanService
 * @implements {ISubscriptionPlanService}
 * @implements {IService}
 */

@injectable()
export class SubscriptionPlanService
  implements ISubscriptionPlanService, IService {
  private logger = createEverLogger({ name: 'SubscriptionPlanService' });
  private dbService = servicesContainer.get<DatabaseService>(DatabaseService);
  /**
   * Create the subscription plan
   *
   * Returns the newly created subscription plan object with id
   *
   * @param {SubscriptionInputPayload} payload
   * @returns {Promise<SubscriptionPlan>}
   * @memberof SubscriptionPlanService
   */
  async create(payload: SubscriptionInputPayload): Promise<SubscriptionPlan> {
    let result: SubscriptionPlan;
    try {
      // Validate the payload
      await subscriptionPlanCreateSchema.validate(payload, {
        abortEarly: false,
      });
      const slug = payload.name.toLowerCase().replace(' ', '-');

      // Check for existing slug
      const isExist = await this.dbService.findOne<
        SubscriptionPlan,
        SubscriptionPlanFilter
      >({ slug });
      if (!_.isNil(isExist)) {
        throw ConflictError(ErrorGenerator.Duplicate('Subscription Plan'));
      }
      // Make db call
      result = await this.dbService.create<SubscriptionPlan, SubscriptionPlans>(
        new SubscriptionPlans({ ...payload, slug }),
      );
      this.logger.debug('Subscription Plan added Successfully', result);
    } catch (e) {
      this.logger.error(e);
      ParseError(e, ErrorGenerator.Duplicate('Subscription Plan'));
    }
    if (!_.isEmpty(result?.id)) {
      return result;
    }
    throw BadRequestError(ErrorGenerator.UnableSave('Subscription Plan'));
  }
  /**
   * Get the subscription plan by id only
   * will return single object
   * @param {SubscriptionPlanFilter} where
   * @returns {Promise<SubscriptionPlan>}
   * @memberof SubscriptionPlanService
   */
  async findOne(where: SubscriptionPlanFilter): Promise<SubscriptionPlan> {
    let edge: SubscriptionPlan;
    try {
      // Validate Input
      await subscriptionPlanFilterSchema.validate(where, {
        abortEarly: false,
      });
      // Get the subscription plan id
      // TODO: Implement other filters
      const id = where?.id;
      if (!_.isNil(id)) {
        // make db call
        edge = await this.dbService.findOne<
          SubscriptionPlan,
          SubscriptionPlanFilter
        >(new SubscriptionPlans({ id }));
      }
    } catch (e) {
      this.logger.error(e);
      ParseError(e, ErrorGenerator.NotFound('Subscription Plan'));
    }
    if (!_.isEmpty(edge)) {
      this.logger.debug('Subscription Plan loaded Successfully', edge);

      return edge;
    }
    throw NotFoundError(ErrorGenerator.NotFound('Subscription Plan'));
  }
  /**
   * Get all the subscriptions plans
   * with pagination
   * @param {SubscriptionPlanFilter} [where]
   * @returns {Promise<SubscriptionPlanResponse>}
   * @memberof SubscriptionPlanService
   */
  async findAll(
    where?: SubscriptionPlanFilter,
  ): Promise<SubscriptionPlanResponse> {
    // Validate the Input

    let edges: SubscriptionPlan[];
    let count: number; // Rows counts
    let recordLimit = 10; // Pagination Limit
    let recordSkip = 0; // Pagination: SKIP

    // TODO
    // Transform from Object to Array
    // { id: SortDirection.ASC } to [ "id", "ASC"]
    // for (const [key, value] of Object.entries(sortBy)) {
    //   sortOrder.push([key, value]);
    // }
    try {
      await subscriptionPlanFilterSchema.validate(where, {
        abortEarly: false,
      });
      if (where) {
        // TODO: Implement other filters
        const { id, limit, skip } = where;
        // isNil check for for null or undefined
        if (!_.isNil(id) && !_.isNil(limit) && !_.isNil(skip)) {
          // Set Limit and Skip for `page_info`
          recordLimit = limit;
          recordSkip = skip;
          // Load the SubscriptionPlan with ID and Pagination
          [edges, count] = await this.dbService.findAll<
            SubscriptionPlan,
            Partial<SubscriptionPlanFilter>
          >(new SubscriptionPlans({ id }), recordLimit, recordSkip);
        } else if (!_.isNil(limit) && !_.isNil(skip)) {
          // Set Limit and Skip for `page_info`
          recordLimit = limit;
          recordSkip = skip;
          // Load All SubscriptionPlan with default pagination
          [edges, count] = await this.dbService.findAll<
            SubscriptionPlan,
            Partial<SubscriptionPlanFilter>
          >(new SubscriptionPlans(), recordLimit, recordSkip);
        } else if (!_.isNil(id)) {
          // Load All SubscriptionPlan with id with default pagination
          [edges, count] = await this.dbService.findAll<
            SubscriptionPlan,
            Partial<SubscriptionPlanFilter>
          >(new SubscriptionPlans({ id }), recordLimit, recordSkip);
        }
      } else {
        // Load All SubscriptionPlan with default pagination
        [edges, count] = await this.dbService.findAll<
          SubscriptionPlan,
          Partial<SubscriptionPlanFilter>
        >(new SubscriptionPlans(), recordLimit, recordSkip);
      }
    } catch (error) {
      this.logger.error(error);
      // Empty
      ParseError(error, ErrorGenerator.NotFound('Subscription Plan'));
    }
    // Validate edges are not empty
    if (!_.isEmpty(edges)) {
      this.logger.debug('Subscription Plan loaded Successfully', edges);

      return {
        edges,
        page_info: {
          total: count,
          limit: recordLimit,
          skip: recordSkip,
          has_more: count > recordLimit + recordSkip ? true : false,
        },
      };
    }
    throw NotFoundError(ErrorGenerator.NotFound('Subscription Plan'));
  }
  count(where?: SubscriptionPlanFilter): Promise<number> {
    throw new Error('Method not implemented.');
  }
  /**
   * Update the subscription plan
   * by id only
   * @param {SubscriptionPlanUpdatePayload} payload
   * @param {SubscriptionPlanFilter} where
   * @returns {Promise<UpdateSubscriptionPlanResponse>}
   * @memberof SubscriptionPlanService
   */
  async update(
    payload: SubscriptionPlanUpdatePayload,
    where: SubscriptionPlanFilter,
  ): Promise<UpdateSubscriptionPlanResponse> {
    let modified: number;
    let edges: SubscriptionPlan[];

    try {
      // Validate the input
      await subscriptionPlanUpdateSchema.validate(
        { ...payload, ...where },
        { abortEarly: false },
      );
      // Check where is defined
      if (where) {
        const { id } = where;
        // Get Subscription plan id
        if (!_.isNil(id)) {
          // Generate the slug
          const slug = payload.name.toLowerCase().replace(' ', '-');
          // Check for existing slug
          const isExist = await this.dbService.findOne<
            SubscriptionPlan,
            SubscriptionPlanFilter
          >({ slug });
          // Validate the ID is not same
          // Return document can have the same ID as of update
          if (!_.isNil(isExist) && isExist?.id != id) {
            throw ConflictError(ErrorGenerator.Duplicate('Subscription Plan'));
          }
          // Make db call
          [edges, modified] = await this.dbService.update<
            SubscriptionPlan,
            Partial<SubscriptionPlan>,
            SubscriptionPlanFilter
          >(
            new SubscriptionPlans({ ...payload, slug }),
            new SubscriptionPlans({ id }),
          );
          this.logger.debug('Subscription Plan Update Successfully', edges);
        }
      }
    } catch (e) {
      this.logger.error(e);
      ParseError(e, ErrorGenerator.Duplicate('Subscription Plan'));
    }
    if (modified > 0) {
      // Return the update data with count
      return { modified, edges };
    }
    throw NotFoundError(ErrorGenerator.NotFound('Subscription Plan'));
  }
  /**
   * Delete the subscription plan
   * by id only
   * @param {SubscriptionPlanFilter} where
   * @returns {Promise<DeleteSubscriptionPlanResponse>}
   * @memberof SubscriptionPlanService
   */
  async delete(
    where: SubscriptionPlanFilter,
  ): Promise<DeleteSubscriptionPlanResponse> {
    let modified: number;
    let edges: SubscriptionPlan[];

    try {
      this.logger.info(where, 'Delete request');
      // Validate the payload
      await subscriptionPlanFilterSchema.validate(where, { abortEarly: false });
      // Check where is defined
      if (where) {
        // Get the subscription plan id
        const { id } = where;
        if (!_.isNil(id)) {
          // Make db call
          [edges, modified] = await this.dbService.delete<
            SubscriptionPlan,
            SubscriptionPlanFilter
          >(new SubscriptionPlans({ id }));
          this.logger.debug('Subscription Plan deleted Successfully', edges);
        }
      }
    } catch (e) {
      this.logger.error(e);
      ParseError(e, ErrorGenerator.UnableToDelete('Subscription Plan'));
    }
    if (modified > 0) {
      return { modified, edges };
    }
    throw NotFoundError(ErrorGenerator.NotFound('Subscription Plan'));
  }
}

Here is my service_plan.controller.ts

/* eslint-disable @typescript-eslint/no-unused-vars */
import { Controller } from '@nestjs/common';
import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { HttpError } from 'http-json-errors';
import { inject, LazyServiceIdentifer } from 'inversify';

import { rpc, subscription_plan } from '../codegen/rpc';
import { SubscriptionPlanService } from '../services/SubscriptionPlan/subscription_plan.service';

import SubscriptionPlanResponse = subscription_plan.SubscriptionPlanResponse;
import SubscriptionPlanFilter = subscription_plan.SubscriptionPlanFilter;
import SubscriptionPlan = subscription_plan.SubscriptionPlan;
import DeleteSubscriptionPlanResponse = subscription_plan.DeleteSubscriptionPlanResponse;
import UpdateSubscriptionPlanResponse = subscription_plan.UpdateSubscriptionPlanResponse;
import UpdateSubscriptionPlanRequest = subscription_plan.UpdateSubscriptionPlanRequest;
import SubscriptionPlanInput = subscription_plan.SubscriptionPlanInput;
import IEmpty = rpc.IEmpty;

@Controller()
export class SubscriptionPlanController {
  constructor(
    @inject(new LazyServiceIdentifer(() => SubscriptionPlanService))
    private readonly subscriptionPlanService: SubscriptionPlanService,
  ) {}
  /**
   * Get all subscription plans
   * Test command : grpcurl -plaintext  -proto rpc/rpc.proto 127.0.0.1:5000 rpc.SubscriptionPlanService/FindAll
   * @param {IEmpty} req
   * @returns {Promise<SubscriptionPlans>}
   * @memberof SubscriptionPlanController
   */
  @GrpcMethod('SubscriptionPlanService', 'FindAll')
  async findAll(req: IEmpty): Promise<SubscriptionPlanResponse> {
    try {
      const obj = await this.subscriptionPlanService.findAll();
      return SubscriptionPlanResponse.create(
        (obj as unknown) as SubscriptionPlanResponse,
      );
    } catch (error) {
      const errorInfo = error as HttpError;
      throw new RpcException({
        code: errorInfo.statusCode,
        message: JSON.stringify(error),
      });
    }
  }

  /**
   * Get One subscription plan
   * Test command : grpcurl -d '{"id":"513-A"}' -plaintext  -proto rpc/rpc.proto 127.0.0.1:5000 rpc.SubscriptionPlanService/FindOne
   * @param {SubscriptionPlanFilter} where
   * @returns {Promise<SubscriptionPlan>}
   * @memberof SubscriptionPlanController
   */
  @GrpcMethod('SubscriptionPlanService', 'FindOne')
  async findOne(where: SubscriptionPlanFilter): Promise<SubscriptionPlan> {
    try {
      const id = where?.id;
      const obj = await this.subscriptionPlanService.findOne({ id });
      return SubscriptionPlan.create((obj as unknown) as SubscriptionPlan);
    } catch (error) {
      const errorInfo = error as HttpError;
      throw new RpcException({
        code: errorInfo.statusCode,
        message: JSON.stringify(error),
      });
    }
  }

  /**
   * Create subscription plan
   * Test command : grpcurl -d '{
      "name": "Test GRPC",
      "code": "12312",
      "description": "test",
      "price": 10,
      "invoicePeriod": 10,
      "invoiceDuration":"DAY"
    }' -plaintext  -proto rpc/rpc.proto 127.0.0.1:5000 rpc.SubscriptionPlanService/Create
  *
   * @param {SubscriptionPlanInput} payload
   * @returns {Promise<SubscriptionPlan>}
   * @memberof SubscriptionPlanController
   */
  @GrpcMethod('SubscriptionPlanService', 'Create')
  async create(payload: SubscriptionPlanInput): Promise<SubscriptionPlan> {
    try {
      const obj = await this.subscriptionPlanService.create({
        name: payload?.name,
        price: payload?.price,
        invoice_duration: payload?.invoice_duration as any,
        invoice_period: payload?.invoice_period,
        trail_period: payload?.trail_period,
        trail_duration: payload?.trail_duration as any,
        description: payload?.description,
        code: payload?.code,
      });
      return SubscriptionPlan.create((obj as unknown) as SubscriptionPlan);
    } catch (error) {
      const errorInfo = error as HttpError;
      throw new RpcException({
        code: errorInfo.statusCode,
        message: JSON.stringify(error) || error,
      });
    }
  }

  /**
   * Update subscription plan
   * Test command :
   * grpcurl -d '{"payload":{"name":"Update Text"},"where":{"id":"97-A"}}'
   * -plaintext  -proto rpc/rpc.proto 127.0.0.1:5000 rpc.SubscriptionPlanService/Update
   * @param {UpdateSubscriptionPlanRequest} data
   * @returns {Promise<UpdateSubscriptionPlanResponse>}
   * @memberof SubscriptionPlanController
   */
  @GrpcMethod('SubscriptionPlanService', 'Update')
  async update(
    data: UpdateSubscriptionPlanRequest,
  ): Promise<UpdateSubscriptionPlanResponse> {
    try {
      const { payload, where } = data;
      const obj = await this.subscriptionPlanService.update(
        payload as any,
        where,
      );
      return UpdateSubscriptionPlanResponse.create(
        (obj as unknown) as UpdateSubscriptionPlanResponse,
      );
    } catch (error) {
      const errorInfo = error as HttpError;
      throw new RpcException({
        code: errorInfo.statusCode,
        message: JSON.stringify(error),
      });
    }
  }

  /**
   * Delete subscription plan
   * Test command : grpcurl -d '{"id":"513-A"}' -plaintext  -proto rpc/rpc.proto 127.0.0.1:5000 rpc.SubscriptionPlanService/Delete
   * @param {SubscriptionPlanFilter} where
   * @returns {Promise<DeleteSubscriptionPlanResponse>}
   * @memberof SubscriptionPlanController
   */
  @GrpcMethod('SubscriptionPlanService', 'Delete')
  async delete(
    where: SubscriptionPlanFilter,
  ): Promise<DeleteSubscriptionPlanResponse> {
    try {
      const id = where?.id;
      const obj = await this.subscriptionPlanService.delete({ id });
      return DeleteSubscriptionPlanResponse.create(
        (obj as unknown) as DeleteSubscriptionPlanResponse,
      );
    } catch (error) {
      const errorInfo = error as HttpError;
      throw new RpcException({
        code: errorInfo.statusCode,
        message: JSON.stringify(error),
      });
    }
  }
}

I have also added the subscription module to app modules.

my app.module.ts

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
import { GraphQLModule } from '@nestjs/graphql';
import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json';
import _ from 'lodash';

import { ConfigModule } from './config/config.module';
// import { servicesContainer } from './services/inversify.config';
import { ServicesModule } from './services/service.module';
// import { SubscriptionPlanService } from './services/SubscriptionPlan/subscription_plan.service';
import { HttpExceptionFilter } from './shared/exception-filter/http-exception.filter';
import { TimeoutInterceptor } from './shared/interceptor/timeout.interceptor';
import schemaDirectives from './shared/schema-directive/index';
import { SubscriptionPlanModule } from './subscription_plans/subscription_plan.module';
import { UserModule } from './users/user.module';

@Module({
  imports: [
    ConfigModule,
    ServicesModule,
    SubscriptionPlanModule,
    UserModule,
    GraphQLModule.forRootAsync({
      useFactory: () => ({
        schemaDirectives,
        include: [],
        typePaths: ['./**/**/*.graphql'],
        installSubscriptionHandlers: true,
        context: ({ req }) => ({ req }),
        introspection: true,
        // debug: configService.get<string>('app.nodeEnv') === 'development',
        // engine: {
        //   schemaTag: configService.get<string>('app.nodeEnv'),
        //   apiKey: configService.get<string>('app.apolloEngineApiKey'),
        // },
        resolverValidationOptions: {
          requireResolversForResolveType: false,
        },
        resolvers: {
          JSON: GraphQLJSON,
          JSONObject: GraphQLJSONObject,
        },
        formatError: (error) => {
          try {
            error.message = JSON.parse(error.message);
          } catch (e) {
            // Empty
          }
          return {
            ...error,
            message: error.message,
            code: _.get(error, 'extensions.exception.title', 'UNKNOWN'),
            locations: error.locations,
            path: error.path,
          };
        },
        formatResponse: (response) => response,
      }),
      inject: [ConfigService],
    }),
  ],

  controllers: [],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: TimeoutInterceptor,
    },
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {
  constructor() {
    // Debug the Insert operation
    // const s = servicesContainer.get<SubscriptionPlanService>(
    //   SubscriptionPlanService,
    // );
    // void s.create({
    //   name: 'Test',
    //   invoice_duration: 'DAY',
    //   invoice_period: 30,
    //   price: 10,
    //   code: '12312',
    //   description: 'test',
    //   trail_duration: 'DAY',
    //   trail_period: 12,
    // });
    // void s.findAll();
    // void s.delete({ id: '257-A' });
    // void s.findOne({ id: '257-A' });
    // void s.update({ name: 'Test Update name1' }, { id: '353-A' });
  }
}

I feel like sharing all these code were un-necessary but if anyone needs any kind of information do let me know in the comments.

Even if you a clue to what might be raising the issue mentioned above would be of great help.

1 Answers1

0

I got the same error message while everything in the server seemed to work as expected. It turned out I was calling grpcurl in the wrong directory.

Based on the argument -proto rpc/rpc.proto, you should check that you are in the parent directory of rpc and try calling grpcurl again.

The gRPCurl project already has a suggestion to change the error message to a better one.

UPDATE: The above bad error message issue seems to be fixed as mentioned in it:

Failed to process proto source files.: could not parse given files: open <your given proto path and file argument here>: no such file or directory

Jarno Lamberg
  • 1,530
  • 12
  • 12