{"version":3,"sources":["../../../src/adapters/azure/http-trigger-v4.adapter.ts"],"sourcesContent":["//#region Imports\n\nimport { URL } from 'node:url';\nimport type {\n  Context,\n  Cookie,\n  HttpRequest,\n  HttpResponseSimple,\n} from '@azure/functions';\nimport type { BothValueHeaders } from '../../@types';\nimport type {\n  AdapterContract,\n  AdapterRequest,\n  GetResponseAdapterProps,\n  OnErrorProps,\n} from '../../contracts';\nimport {\n  getDefaultIfUndefined,\n  getEventBodyAsBuffer,\n  getFlattenedHeadersMap,\n  getPathWithQueryStringParams,\n} from '../../core';\n\n//#endregion\n\n/**\n * The options to customize the {@link HttpTriggerV4Adapter}\n *\n * @breadcrumb Adapters / Azure / HttpTriggerV4Adapter\n * @public\n */\nexport interface HttpTriggerV4AdapterOptions {\n  /**\n   * Strip base path for custom domains\n   *\n   * @defaultValue ''\n   */\n  stripBasePath?: string;\n}\n\n/**\n * The adapter to handle requests from Http Trigger on Azure Function V4.\n *\n * @example\n * ```typescript\n * const stripBasePath = '/any/custom/base/path'; // default ''\n * const adapter = new HttpTriggerV4Adapter({ stripBasePath });\n * ```\n *\n * @see {@link https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node | Reference}\n *\n * @breadcrumb Adapters / Azure / HttpTriggerV4Adapter\n * @public\n */\nexport class HttpTriggerV4Adapter\n  implements AdapterContract<HttpRequest, Context, HttpResponseSimple>\n{\n  //#region Constructor\n\n  /**\n   * Default constructor\n   *\n   * @param options - The options to customize the {@link HttpTriggerV4Adapter}\n   */\n  constructor(protected readonly options?: HttpTriggerV4AdapterOptions) {}\n\n  //#endregion\n\n  //#region Public Methods\n\n  /**\n   * {@inheritDoc}\n   */\n  public getAdapterName(): string {\n    return HttpTriggerV4Adapter.name;\n  }\n\n  /**\n   * {@inheritDoc}\n   */\n  public canHandle(event: unknown, context: unknown): boolean {\n    const maybeEvent = event as Partial<HttpRequest> | undefined;\n    const maybeContext = context as Partial<Context> | undefined;\n\n    return !!(\n      maybeEvent &&\n      maybeEvent.method &&\n      maybeEvent.headers &&\n      maybeEvent.url &&\n      maybeEvent.query &&\n      maybeContext &&\n      maybeContext.traceContext &&\n      maybeContext.bindingDefinitions &&\n      maybeContext.log &&\n      !!maybeContext.log.info &&\n      maybeContext.bindingData\n    );\n  }\n\n  /**\n   * {@inheritDoc}\n   */\n  public getRequest(event: HttpRequest): AdapterRequest {\n    const path = this.getPathFromEvent(event);\n\n    const method = event.method!;\n    const headers = getFlattenedHeadersMap(event.headers, ',', true);\n\n    let body: Buffer | undefined;\n\n    if (event.body) {\n      const [bufferBody, contentLength] = getEventBodyAsBuffer(\n        event.rawBody,\n        false,\n      );\n\n      body = bufferBody;\n      headers['content-length'] = String(contentLength);\n    }\n\n    const remoteAddress = headers['x-forwarded-for'];\n\n    return {\n      method,\n      path,\n      headers,\n      remoteAddress,\n      body,\n    };\n  }\n\n  /**\n   * {@inheritDoc}\n   */\n  public getResponse({\n    body,\n    statusCode,\n    headers: originalHeaders,\n  }: GetResponseAdapterProps<HttpRequest>): HttpResponseSimple {\n    const headers = getFlattenedHeadersMap(originalHeaders, ',', true);\n    const cookies = this.getAzureCookiesFromHeaders(originalHeaders);\n\n    if (headers['set-cookie']) delete headers['set-cookie'];\n\n    return {\n      body,\n      statusCode,\n      headers,\n      // I tried to understand this property with\n      // https://docs.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/content-negotiation\n      // but I don't know if it's worth implementing this guy as an option\n      // I found out when this guy is set to true and the framework sets content-type, azure returns 500\n      // So I'll leave it as is and hope no one has any problems.\n      enableContentNegotiation: false,\n      cookies,\n    };\n  }\n\n  /**\n   * {@inheritDoc}\n   */\n  public onErrorWhileForwarding({\n    error,\n    respondWithErrors,\n    event,\n    delegatedResolver,\n    log,\n  }: OnErrorProps<HttpRequest, HttpResponseSimple>): void {\n    const body = respondWithErrors ? error.stack : '';\n    const errorResponse = this.getResponse({\n      event,\n      statusCode: 500,\n      body: body || '',\n      headers: {},\n      isBase64Encoded: false,\n      log,\n    });\n\n    delegatedResolver.succeed(errorResponse);\n  }\n\n  //#endregion\n\n  //#region Protected Methods\n\n  /**\n   * Get path from event with query strings\n   *\n   * @param event - The event sent by serverless\n   */\n  protected getPathFromEvent(event: HttpRequest): string {\n    const stripBasePath = getDefaultIfUndefined(\n      this.options?.stripBasePath,\n      '',\n    );\n\n    const url = new URL(event.url);\n    const originalPath = url.pathname;\n\n    const replaceRegex = new RegExp(`^${stripBasePath}`);\n    const path = originalPath.replace(replaceRegex, '');\n\n    const queryParams = event.query;\n\n    return getPathWithQueryStringParams(path, queryParams);\n  }\n\n  /**\n   * Get the Azure Cookie list parsed from set-cookie header.\n   *\n   * @param headers - The headers object\n   */\n  protected getAzureCookiesFromHeaders(headers: BothValueHeaders): Cookie[] {\n    const setCookie = headers['set-cookie'];\n\n    const headerCookies = Array.isArray(setCookie)\n      ? setCookie\n      : setCookie\n        ? [setCookie]\n        : [];\n\n    return headerCookies.map(cookie => this.parseCookie(cookie));\n  }\n\n  /**\n   * Parse the string cookie to the Azure Cookie Object.\n   * This code was written by {@link https://github.com/zachabney | @zachabney}\n   * on {@link https://github.com/zachabney/azure-aws-serverless-express/blob/241d2d5c4d5906e4817662cad6426ec2cbbf9ca7/src/index.js#L4-L49 | this library}.\n   *\n   * @param cookie - The cookie\n   */\n  protected parseCookie(cookie: string): Cookie {\n    return cookie.split(';').reduce(\n      (azureCookieObject, cookieProperty, index) => {\n        const [key, value] = cookieProperty.split('=');\n\n        const sanitizedKey = key.toLowerCase().trim();\n        const sanitizedValue = value && value.trim();\n\n        if (index === 0) {\n          azureCookieObject.name = key;\n          azureCookieObject.value = sanitizedValue;\n\n          return azureCookieObject;\n        }\n\n        switch (sanitizedKey) {\n          case 'domain':\n            azureCookieObject.domain = sanitizedValue;\n            break;\n          case 'path':\n            azureCookieObject.path = sanitizedValue;\n            break;\n          case 'expires':\n            azureCookieObject.expires = new Date(sanitizedValue);\n            break;\n          case 'secure':\n            azureCookieObject.secure = true;\n            break;\n          case 'httponly':\n            azureCookieObject.httpOnly = true;\n            break;\n          case 'samesite':\n            azureCookieObject.sameSite = sanitizedValue as Cookie['sameSite'];\n            break;\n          case 'max-age':\n            azureCookieObject.maxAge = Number(sanitizedValue);\n            break;\n        }\n\n        return azureCookieObject;\n      },\n      { name: '', value: '' } as Cookie,\n    );\n  }\n\n  //#endregion\n}\n"],"mappings":"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","names":["URL","HttpTriggerV4Adapter","_HttpTriggerV4Adapter","options","__name","event","context","maybeEvent","maybeContext","path","method","headers","getFlattenedHeadersMap","body","bufferBody","contentLength","getEventBodyAsBuffer","remoteAddress","statusCode","originalHeaders","cookies","error","respondWithErrors","delegatedResolver","log","errorResponse","stripBasePath","getDefaultIfUndefined","originalPath","URL","replaceRegex","queryParams","getPathWithQueryStringParams","setCookie","cookie","azureCookieObject","cookieProperty","index","key","value","sanitizedKey","sanitizedValue"]}