From e789bfe94f9cca3756ad0d59bddf81f997735394 Mon Sep 17 00:00:00 2001 From: Shubhendu Madhukar Date: Mon, 21 Nov 2022 10:30:47 +0530 Subject: [PATCH] implement basic error response and metadata. #188 --- docs/mocking-gRPC.md | 26 +++++++++++++++ .../blogPackage/BlogService/createBlog.mock | 10 +++++- src/parser/GrpcParser.ts | 33 +++++++++++++++---- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/docs/mocking-gRPC.md b/docs/mocking-gRPC.md index b8cc299..69a6f91 100644 --- a/docs/mocking-gRPC.md +++ b/docs/mocking-gRPC.md @@ -107,6 +107,32 @@ You can also add delays in your grpc mock services, by adding a delay key with t You don't need to modify your proto file to accommodate the additional key, since Camouflage will delete the "delay" key from the response before sending it to the client. +## Sending GRPC Error responses + +Camouflage provides an experimental support to send error responses starting v0.11.0 onwards, for unary and client side streaming calls. To send an error response, append a json error object with `code` and optional `message` to your mock content. + +```json +{ + "error": { + "code": 16, + "message": "User is unauthenticted." + } +} +``` + +## Sending GRPC Error responses + +Camouflage provides an experimental support to send metadata/trailers with responses starting v0.11.0 onwards, for unary and client side streaming calls. To send metadata, append a json metadata object with relevant keys and values to your mock content. + +```json +{ + "metadata": { + "key1": "value1", + "key2": "value2" + } +} +``` + !!!caution Since the Camouflage gRPC server needs to register the new services, everytime you add a new protofile, you'd need to restart the Camouflage server. Good news is, you can do so easily by making a get request to /restart endpoint. Though the downtime is minimal (less than a second, we do not recommend restarting the server during a performance test. Note that restart is required only if you add a new protofile. If you have added a new mock file or updated an existing one, a restart is not required. diff --git a/grpc/mocks/blogPackage/BlogService/createBlog.mock b/grpc/mocks/blogPackage/BlogService/createBlog.mock index 301f307..2746c0c 100644 --- a/grpc/mocks/blogPackage/BlogService/createBlog.mock +++ b/grpc/mocks/blogPackage/BlogService/createBlog.mock @@ -1,4 +1,12 @@ { "id": {{num_between lower=500 upper=600}}, - "title": "something" + "title": "something", + "error": { + "code": 16, + "message": "User is unauthenticated." + }, + "metadata": { + "key1": "value1", + "key2": "value2" + } } diff --git a/src/parser/GrpcParser.ts b/src/parser/GrpcParser.ts index b18932e..d903c5a 100644 --- a/src/parser/GrpcParser.ts +++ b/src/parser/GrpcParser.ts @@ -6,6 +6,7 @@ import { getHandlebars } from '../handlebar' import { getLoaderInstance } from "../ConfigLoader"; import { CamouflageConfig } from "../ConfigLoader/LoaderInterface"; const Handlebars = getHandlebars() +import * as grpc from "@grpc/grpc-js"; /** * Parser class for GRPC Protocol mocks to define handlers for: * - Unary calls @@ -42,16 +43,26 @@ export default class GrpcParser { const response = JSON.parse(fileContent); const delay: number = response.delay || 0; delete response.delay; + const error = response.error || null + delete response.error; + const metadata = response.metadata || null; + delete response.metadata; + const trailers = new grpc.Metadata(); + if (metadata) { + for (const key in metadata) { + trailers.add(key, metadata[key]) + } + } setTimeout(() => { - callback(null, response); + callback(error, response, trailers); }, delay); } else { logger.error(`No suitable mock file was found for ${mockFilePath}`); - callback(null, { error: `No suitable mock file was found for ${mockFilePath}` }); + callback({ code: 5, message: `No suitable mock file was found for ${mockFilePath}` }, {}); } } catch (error) { logger.error(error); - callback(null, { error: error }); + callback({ code: 10, message: error }, {}); } }; /** @@ -137,16 +148,26 @@ export default class GrpcParser { const response = JSON.parse(fileContent); const delay: number = response.delay || 0; delete response.delay; + const error = response.error || null; + delete response.error + const metadata = response.metadata || null; + delete response.metadata; + const trailers = new grpc.Metadata(); + if (metadata) { + for (const key in metadata) { + trailers.add(key, metadata[key]) + } + } setTimeout(() => { - callback(null, response); + callback(error, response, trailers); }, delay); } else { logger.error(`No suitable mock file was found for ${mockFilePath}`); - callback(null, { error: `No suitable mock file was found for ${mockFilePath}` }); + callback({ code: 5, error: `No suitable mock file was found for ${mockFilePath}` }, {}); } } catch (error) { logger.error(error); - callback(null, { error: error }); + callback({ code: 10, message: error }, {}); } }); };