Skip to content

gRPC exceptions and filter #1953

@tychota

Description

@tychota

After discussion the goal is to implement gRPC exceptions and filter, and document it, to improve gRPC integration.

See #1953 (comment)

-- OLD ISSUE --

I'm submitting a...

#1015 report a similar problem but the result did not helped me fixing my issue

See what i tried section.


[ ] Regression 
[ ] Bug report
[ ] Feature request
[x] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

I have an application that expose a public HTTP api.
The "gateway" communicate with microservices through GRPC.

@Injectable()
export class AuthService {
  constructor(private readonly commandBus: CommandBus, private readonly queryBus: QueryBus) {}

  // ...

  async assertEmailDoesNotExist(email: string) {
    const emailAlreadyExist = await this.queryBus.execute(new DoesEmailExistQuery(email));
    if (emailAlreadyExist) {
      // >>>> Here <<<<
      // I would like to display an "ALREADY_EXIST"
      // https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
      const exception = new RpcException({ message: 'Email already exist', status: 6 });
      throw exception;
    }
  }
}

but I get

{
  "level":50,
   /// ...
  "context":"ExceptionsHandler",
  "msg":"2 UNKNOWN: Email already exist",
  "v":1
}

in my log (custom pino logger).

Expected behavior

{
  "level":50,
   /// ...
  "context":"ExceptionsHandler",
  "msg":"6 ALREADY_EXISTS: Email already exist",
  "v":1
}

What i tried

  • setting the code of the exception (like ex.code = xxx)
  • passing an object RpcException({ message: 'Email already exist', status: 6 })
  • doing a rpc filter (as suggested in issue)
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, RpcExceptionFilter } from '@nestjs/common';
import { RpcException, BaseRpcExceptionFilter } from '@nestjs/microservices';
import { Observable, throwError } from 'rxjs';

export class HttpExceptionFilter extends BaseRpcExceptionFilter implements RpcExceptionFilter {
  catch(exception: RpcException, host: ArgumentsHost): Observable<unknown> {
    if (exception instanceof RpcException) {
      return throwError({status: 6})
    }
    return super.catch(exception, host);
  }
}

Ultimatly, without doc, I lack knowledge how to solve my problem. I did try but I guess not in the right direction

Minimal reproduction of the problem with instructions

Not really minimal (but close two, one api gateway, two microservices) but you can found the codebase here: https://github.com/tychota/shitake

To run it:

  • yarn
  • create a db "events" and a db "accounts" in postgres with user "shitake"/"password"
  • (in one terminal) yarn webpack -f webpack.config.js
  • (in an other) node dist/server.js
  • go to swagger: http://localhost:3001/api/#/auth/post_auth_register
  • register twice with same email

What is the motivation / use case for changing the behavior?

In http, it is super nice, you can just throw a ConflictException and boom, you have a 429.

In GRPC world it is not as nice :/

You have to play with message string (hardcoded stuff) and ultimatly not every status code are equal (https://github.com/grpc/grpc/blob/master/doc/statuscodes.md) (like a 401 in HTTp may be expected but a 500 isn't). Here you have just the equivalence of 500, with all what is implied (logging of a 500).

Environment


Nest version:
    "@nestjs/common": "^6.0.5",
    "@nestjs/cqrs": "^6.0.0",
 
For Tooling issues:
- Node version: v8.12.0
- Platform:  Linux, Gentoo

Closing

Let me know if it is clear enough.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions