<commit_before>/*
 * Copyright 2013-2020 http4s.org
 *
 * SPDX-License-Identifier: Apache-2.0
 */

package org.http4s.circe.middleware

import cats.effect._
import cats.data._
import io.circe._
import io.circe.syntax._
import org.http4s._
import org.http4s.headers.Connection
import org.http4s.circe._
import org.typelevel.ci.CIString

object JsonDebugErrorHandler {
  private[this] val messageFailureLogger =
    org.log4s.getLogger("org.http4s.circe.middleware.jsondebugerrorhandler.message-failures")
  private[this] val serviceErrorLogger =
    org.log4s.getLogger("org.http4s.circe.middleware.jsondebugerrorhandler.service-errors")

  // Can be parametric on my other PR is merged.
  def apply[F[_]: Sync, G[_]](
      service: Kleisli[F, Request[G], Response[G]],
      redactWhen: CIString => Boolean = Headers.SensitiveHeaders.contains
  ): Kleisli[F, Request[G], Response[G]] =
    Kleisli { req =>
      import cats.syntax.applicative._
      import cats.syntax.applicativeError._
      implicit def entEnc[M[_], N[_]] = JsonErrorHandlerResponse.entEnc[M, N](redactWhen)

      service
        .run(req)
        .handleErrorWith {
          case mf: MessageFailure =>
            messageFailureLogger.debug(mf)(
              s"""Message failure handling request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
                .getOrElse("<unknown>")}""")
            val firstResp = mf.toHttpResponse[G](req.httpVersion)
            Response[G](
              status = firstResp.status,
              httpVersion = firstResp.httpVersion,
              headers = firstResp.headers.redactSensitive(redactWhen)
            ).withEntity(JsonErrorHandlerResponse[G](req, mf)).pure[F]
          case t =>
            serviceErrorLogger.error(t)(
              s"""Error servicing request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
                .getOrElse("<unknown>")}"""
            )
            Response[G](
              Status.InternalServerError,
              req.httpVersion,
              Headers(
                Connection(CIString("close")) ::
                  Nil
              ))
              .withEntity(JsonErrorHandlerResponse[G](req, t))
              .pure[F]
        }
    }

  private final case class JsonErrorHandlerResponse[F[_]](
      req: Request[F],
      caught: Throwable
  )
  private object JsonErrorHandlerResponse {
    def entEnc[F[_], G[_]](
        redactWhen: CIString => Boolean
    ): EntityEncoder[F, JsonErrorHandlerResponse[G]] =
      jsonEncoderOf(
        encoder(redactWhen)
      )
    def encoder[F[_]](
        redactWhen: CIString => Boolean
    ): Encoder[JsonErrorHandlerResponse[F]] =
      (a: JsonErrorHandlerResponse[F]) => Json.obj(
        "request" -> encodeRequest(a.req, redactWhen),
        "throwable" -> encodeThrowable(a.caught)
      )
  }

  private def encodeRequest[F[_]](req: Request[F], redactWhen: CIString => Boolean): Json =
    Json
      .obj(
        "method" -> req.method.name.asJson,<commit_msg>add scalafmt<commit_after>/*
 * Copyright 2013-2020 http4s.org
 *
 * SPDX-License-Identifier: Apache-2.0
 */

package org.http4s.circe.middleware

import cats.effect._
import cats.data._
import io.circe._
import io.circe.syntax._
import org.http4s._
import org.http4s.headers.Connection
import org.http4s.circe._
import org.typelevel.ci.CIString

object JsonDebugErrorHandler {
  private[this] val messageFailureLogger =
    org.log4s.getLogger("org.http4s.circe.middleware.jsondebugerrorhandler.message-failures")
  private[this] val serviceErrorLogger =
    org.log4s.getLogger("org.http4s.circe.middleware.jsondebugerrorhandler.service-errors")

  // Can be parametric on my other PR is merged.
  def apply[F[_]: Sync, G[_]](
      service: Kleisli[F, Request[G], Response[G]],
      redactWhen: CIString => Boolean = Headers.SensitiveHeaders.contains
  ): Kleisli[F, Request[G], Response[G]] =
    Kleisli { req =>
      import cats.syntax.applicative._
      import cats.syntax.applicativeError._
      implicit def entEnc[M[_], N[_]] = JsonErrorHandlerResponse.entEnc[M, N](redactWhen)

      service
        .run(req)
        .handleErrorWith {
          case mf: MessageFailure =>
            messageFailureLogger.debug(mf)(
              s"""Message failure handling request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
                .getOrElse("<unknown>")}""")
            val firstResp = mf.toHttpResponse[G](req.httpVersion)
            Response[G](
              status = firstResp.status,
              httpVersion = firstResp.httpVersion,
              headers = firstResp.headers.redactSensitive(redactWhen)
            ).withEntity(JsonErrorHandlerResponse[G](req, mf)).pure[F]
          case t =>
            serviceErrorLogger.error(t)(
              s"""Error servicing request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
                .getOrElse("<unknown>")}"""
            )
            Response[G](
              Status.InternalServerError,
              req.httpVersion,
              Headers(
                Connection(CIString("close")) ::
                  Nil
              ))
              .withEntity(JsonErrorHandlerResponse[G](req, t))
              .pure[F]
        }
    }

  private final case class JsonErrorHandlerResponse[F[_]](
      req: Request[F],
      caught: Throwable
  )
  private object JsonErrorHandlerResponse {
    def entEnc[F[_], G[_]](
        redactWhen: CIString => Boolean
    ): EntityEncoder[F, JsonErrorHandlerResponse[G]] =
      jsonEncoderOf(
        encoder(redactWhen)
      )
    def encoder[F[_]](
        redactWhen: CIString => Boolean
    ): Encoder[JsonErrorHandlerResponse[F]] =
      (a: JsonErrorHandlerResponse[F]) =>
        Json.obj(
          "request" -> encodeRequest(a.req, redactWhen),
          "throwable" -> encodeThrowable(a.caught)
        )
  }

  private def encodeRequest[F[_]](req: Request[F], redactWhen: CIString => Boolean): Json =
    Json
      .obj(
        "method" -> req.method.name.asJson,
{'fraction_non_alphanumeric': 0.12335827098919368, 'fraction_numerical': 0.006317539484621779, 'mean_word_length': 2.534665099882491, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3753413', 'n_tokens_mistral': 1844, 'n_tokens_neox': 1779, 'n_words': 332}