All Downloads are FREE. Search and download functionalities are using the official Maven repository.

exception.DecodeException.scala Maven / Gradle / Ivy

// Copyright (c) 2018-2021 by Rob Norris
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package skunk.exception

import cats.syntax.all._
import skunk.{ Encoder, Decoder }
import skunk.util.Origin
// import skunk.net.Protocol
import skunk.util.Text
import skunk.util.Text.{ plain, empty, cyan, green }
import skunk.data.TypedRowDescription
import skunk.data.TypedRowDescription.Field

// todo: this with a ctor we can call for quick query, which has no portal
class DecodeException[F[_], A, B](
  data:      List[Option[String]],
  error:     Decoder.Error,
  sql:       String,
  sqlOrigin: Option[Origin],
  arguments: A,
  argumentsOrigin: Option[Origin],
  encoder:   Encoder[A],
  rowDescription: TypedRowDescription,
  cause:     Option[Throwable] = None
) extends SkunkException(
  sql             = Some(sql),
  message         = "Decoding error.",
  detail          = Some("This query's decoder was unable to decode a row of data."),
  arguments       = encoder.types.zip(encoder.encode(arguments)),
  argumentsOrigin = argumentsOrigin,
  sqlOrigin       = sqlOrigin
) {

  // def this(
  //   portal: Protocol.QueryPortal[F, A, B],
  //   data:   List[Option[String]],
  //   error:  Decoder.Error,
  //   rowDescription: TypedRowDescription
  // ) = this(
  //   data,
  //   error,
  //   portal.preparedQuery.query.sql,
  //   Some(portal.preparedQuery.query.origin),
  //   portal.arguments,
  //   Some(portal.argumentsOrigin),
  //   portal.preparedQuery.query.encoder,
  //   rowDescription
  // )

  val MaxValue: Int = 15

  // Truncate column values at MaxValue char
  private val dataʹ = data.map(_.map { s =>
    if (s.length > MaxValue) s.take(MaxValue) + "⋯" else s
  })

  def describe(col: ((Field, Int), Option[String])): List[Text] = {
    val ((t, n), op) = col
    List(
      green(t.name),
      Text(t.tpe.name),
      plain("->"),
      green(op.getOrElse("NULL")),
      if (n === error.offset) cyan(s"├── ${error.message}${cause.foldMap(_ => " (see below)")}") else empty
      // TODO - make a fence for errors that span, like
      //        ├───── foo bar
      //        │
    )
  }

  final protected def row: String =
    s"""|The row in question returned the following values (truncated to $MaxValue chars).
        |
        |  ${Text.grid(rowDescription.fields.zipWithIndex.zip(dataʹ).map(describe)).intercalate(plain("\n|  ")).render}
        |
        |""".stripMargin

  final protected def exception: String =
    cause.foldMap { e =>
      s"""|The decoder threw the following exception:
          |
          |  ${e.getClass.getName}: ${e.getMessage}
          |    ${e.getStackTrace.mkString("\n    ")}
          |
          |""".stripMargin
    }

  override def sections: List[String] =
    super.sections :+ row :+ exception

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy