diff --git a/package.json b/package.json index f130a91..6d05a60 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "homepage": "https://github.com/quizizz/service-communication-wrapper#readme", "dependencies": { - "axios": "^0.27.2" + "axios": "^0.27.2", + "uuid": "3.0.1" } } diff --git a/src/http/index.js b/src/http/index.js index 1bcf01f..77480e6 100644 --- a/src/http/index.js +++ b/src/http/index.js @@ -1,6 +1,8 @@ const QError = require('../helpers/error'); const path = require('path'); const Axios = require('axios'); +const uuid = require('uuid/v4'); +const { hrtime } = require('node:process'); class HttpCommunication { name; @@ -26,17 +28,33 @@ class HttpCommunication { this.contextStorage = contextStorage; } + static getRequestContext(req, customContextValue) { + const start = hrtime.bigint(); + return { + traceId: (req.headers && req.headers['x-q-traceid']) ? req.headers['x-q-traceid'] : uuid(), + userId: (req.user && req.user.id) + ? String(req.user.id) + : req.headers['x-q-userid'] + ? req.headers['x-q-userid'] + : null, + ab: (req.headers && (req.headers['x-q-ab-route'] || req.headers['X-Q-AB-ROUTE'])) ? req.headers['x-q-ab-route'] || req.headers['X-Q-AB-ROUTE'] : null, + reqStartTime: start, + ...customContextValue, + }; + } + handleError(params, response) { const { method, route, request } = params; if (response.status >= 400) { if (response.data) { - const { error } = response.data; - throw new QError(error, `${this.name}_internal_service.RESPONSE_ERROR`, { + const { error, errorType = 'server.UKW' } = response.data; + throw new QError(error, errorType, { service: this.name, data: response.data, request, method, route, + type: errorType, } ); } @@ -60,7 +78,18 @@ class HttpCommunication { return finalURL.toString(); } - async makeReqeust(params) { + populateHeadersFromContext(ctx) { + const customHeaders = { + 'X-Q-TRACEID': (ctx && ctx.traceId) ? ctx.traceId : uuid(), + }; + if (ctx) { + if (ctx.userId) customHeaders['X-Q-USERID'] = ctx.userId; + if (ctx.ab) customHeaders['X-Q-AB-ROUTE'] = ctx.ab; + } + return customHeaders; + } + + async makeRequest(params) { const { route, method, request } = params; const requestURL = this.createRequestURL(route, request.query); let response; @@ -69,29 +98,26 @@ class HttpCommunication { if (requestContext) { this.axiosConfig.headers = { ...this.axiosConfig.headers, - // select which headers we want to pass and pass them + ...this.populateHeadersFromContext(requestContext), } } - if (method === 'get') { - response = await Axios.get( - requestURL, - this.axiosConfig, - ); - } else { - response = await Axios[method]( - requestURL, - request.body || {}, - this.axiosConfig, - ); + const req = { + method, + url: requestURL, + ...this.axiosConfig, } + if (request.body) { + req['data'] = request.body; + } + response = await Axios(req); this.handleError(params, response); return response.data; } async post(route, request) { - const data = await this.makeReqeust({ + const data = await this.makeRequest({ method: 'post', route, request, @@ -100,7 +126,7 @@ class HttpCommunication { } async put(route, request) { - const data = await this.makeReqeust({ + const data = await this.makeRequest({ method: 'put', route, request, @@ -108,8 +134,26 @@ class HttpCommunication { return data; } + async patch(route, request) { + const data = await this.makeRequest({ + method: 'patch', + route, + request, + }); + return data; + } + + async delete(route, request) { + const data = await this.makeRequest({ + method: 'delete', + route, + request, + }); + return data; + } + async get(route, request = {}) { - const data = await this.makeReqeust({ + const data = await this.makeRequest({ method: 'get', route, request, diff --git a/types/index.d.ts b/types/index.d.ts index 781f0a2..f49d16a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -9,10 +9,18 @@ export class HttpCommunication { */ constructor({ name, axiosConfig, contextStorage } : { name: string, axiosConfig?: AxiosRequestConfig, contextStorage?: any }); + /** + * Function to generate the context object + * @param req {Express Request} + * @param customContextValue any custom values that you want to store in the context + * Return extracted values from the req headers and any custom values pass to generate the context object + */ + static getRequestContext(req: any, customContextValue?: Record): { traceId: string, userId: string, ab: string }; + /** * Http Get Request - * @param route - * @param request + * @param route + * @param request * @typeParam T - Response type provided by the callee * @returns Returns response of the get request */ @@ -20,17 +28,35 @@ export class HttpCommunication { /** * Http Put Request - * @param route - * @param request + * @param route + * @param request * @typeParam T - Response type provided by the callee * @returns Returns response of the put request */ put(route: string, request: { query: Record, body: Record }): Promise; + /** + * Http Delete Request + * @param route + * @param request + * @typeParam T - Response type provided by the callee + * @returns Returns response of the put request + */ + delete(route: string, request: { query: Record, body: Record }): Promise; + + /** + * Http Patch Request + * @param route + * @param request + * @typeParam T - Response type provided by the callee + * @returns Returns response of the put request + */ + patch(route: string, request: { query: Record, body: Record }): Promise; + /** * Http Post Request - * @param route - * @param request + * @param route + * @param request * @typeParam T - Response type provided by the callee * @returns Returns response of the post request */