/** * ------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. * See License in the project root for license information. * ------------------------------------------------------------------------------------------- */ import { trace } from "@opentelemetry/api"; import { getObservabilityOptionsFromRequest } from "../observabilityOptions.js"; import { httpStatusCode, methodStatusCode } from "./options/ChaosHandlerData.js"; import { ChaosStrategy } from "./options/chaosStrategy.js"; /** * * Class * Middleware * Class representing RedirectHandler */ export class ChaosHandler { /** * * To create an instance of ChaosHandler * @param [options] - The chaos handler options instance * @param manualMap - The Map passed by user containing url-statusCode info */ constructor(options, manualMap) { /** * A member holding options to customize the handler behavior */ this.options = { chaosStrategy: ChaosStrategy.RANDOM, statusMessage: "A random status message", chaosPercentage: 10, }; const chaosOptions = Object.assign(this.options, options); if (chaosOptions.chaosPercentage > 100 || chaosOptions.chaosPercentage < 0) { throw new Error("Chaos Percentage must be set to a value between 0 and 100."); } this.options = chaosOptions; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.manualMap = manualMap !== null && manualMap !== void 0 ? manualMap : new Map(); } /** * Fetches a random status code for the RANDOM mode from the predefined array * @param requestMethod - the API method for the request * @returns a random status code from a given set of status codes */ generateRandomStatusCode(requestMethod) { const statusCodeArray = methodStatusCode[requestMethod]; return statusCodeArray[Math.floor(Math.random() * statusCodeArray.length)]; } /** * Strips out the host url and returns the relative url only * @param chaosHandlerOptions - The ChaosHandlerOptions object * @param urlMethod - the complete URL * @returns the string as relative URL */ getRelativeURL(chaosHandlerOptions, urlMethod) { const baseUrl = chaosHandlerOptions.baseUrl; if (baseUrl === undefined) { return urlMethod; } return urlMethod.replace(baseUrl, "").trim(); } /** * Gets a status code from the options or a randomly generated status code * @param chaosHandlerOptions - The ChaosHandlerOptions object * @param requestURL - the URL for the request * @param requestMethod - the API method for the request * @returns generated statusCode */ getStatusCode(chaosHandlerOptions, requestURL, requestMethod) { if (chaosHandlerOptions.chaosStrategy === ChaosStrategy.MANUAL) { if (chaosHandlerOptions.statusCode !== undefined) { return chaosHandlerOptions.statusCode; } else { // manual mode with no status code, can be a global level or request level without statusCode const relativeURL = this.getRelativeURL(chaosHandlerOptions, requestURL); const definedResponses = this.manualMap.get(relativeURL); if (definedResponses !== undefined) { // checking Manual Map for exact match const mapCode = definedResponses.get(requestMethod); if (mapCode !== undefined) { return mapCode; } // else statusCode would be undefined } else { // checking for regex match if exact match doesn't work this.manualMap.forEach((value, key) => { var _a; const regexURL = new RegExp(key + "$"); if (regexURL.test(relativeURL)) { const responseCode = (_a = this.manualMap.get(key)) === null || _a === void 0 ? void 0 : _a.get(requestMethod); if (responseCode !== undefined) { return responseCode; } } }); } } } // for manual mode status or if the url was not mapped to a code return a random status return this.generateRandomStatusCode(requestMethod); } /** * Generates a respondy for the chaoe response * @param chaosHandlerOptions - The ChaosHandlerOptions object * @param statusCode - the status code for the response * @returns the response body */ createResponseBody(chaosHandlerOptions, statusCode) { if (chaosHandlerOptions.responseBody) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return chaosHandlerOptions.responseBody; } let body; if (statusCode >= 400) { const codeMessage = httpStatusCode[statusCode]; const errMessage = chaosHandlerOptions.statusMessage; body = { error: { code: codeMessage, message: errMessage, }, }; } else { body = {}; } // eslint-disable-next-line @typescript-eslint/no-unsafe-return return body; } /** * Composes a new chaotic response code with the configured parameters * @param url The url of the request * @param fetchRequestInit The fetch request init object * @returns a response object with the configured parameters */ createChaosResponse(url, fetchRequestInit) { var _a; if (fetchRequestInit.method === undefined) { throw new Error("Request method must be defined."); } const requestMethod = fetchRequestInit.method; const statusCode = this.getStatusCode(this.options, url, requestMethod); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const responseBody = this.createResponseBody(this.options, statusCode); const stringBody = typeof responseBody === "string" ? responseBody : JSON.stringify(responseBody); return { url, body: stringBody, status: statusCode, statusText: this.options.statusMessage, headers: (_a = this.options.headers) !== null && _a !== void 0 ? _a : {}, }; } execute(url, requestInit, requestOptions) { const obsOptions = getObservabilityOptionsFromRequest(requestOptions); if (obsOptions) { return trace.getTracer(obsOptions.getTracerInstrumentationName()).startActiveSpan("chaosHandler - execute", (span) => { try { span.setAttribute("com.microsoft.kiota.handler.chaos.enable", true); return this.runChaos(url, requestInit, requestOptions); } finally { span.end(); } }); } return this.runChaos(url, requestInit, requestOptions); } runChaos(url, requestInit, requestOptions, span) { if (Math.floor(Math.random() * 100) < this.options.chaosPercentage) { span === null || span === void 0 ? void 0 : span.addEvent(ChaosHandler.chaosHandlerTriggeredEventKey); return Promise.resolve(this.createChaosResponse(url, requestInit)); } else { if (!this.next) { throw new Error("Please set the next middleware to continue the request"); } return this.next.execute(url, requestInit, requestOptions); } } } ChaosHandler.chaosHandlerTriggeredEventKey = "com.microsoft.kiota.chaos_handler_triggered"; //# sourceMappingURL=chaosHandler.js.map