在 Scala 中使用 QueryParamDecoder 解码可选查询参数

Decode an optional query parameter using QueryParamDecoder in Scala

我想解码我的 Scala 代码中的可选查询参数。我正在使用 http4s。该参数的形式为 ?part=35/43。最终目标是将这个分数存储为 Type Part = (Int, Int),这样我们就可以将 (35, 43) 作为元组在代码中进一步使用。我创建了一个对象,例如: https://http4s.org/v0.18/dsl/#optional-query-parameters

  object OptionalPartitionQueryParamMatcher
      extends OptionalValidatingQueryParamDecoderMatcher[Part]("part")

现在 OptionalValidatingQueryParamDecoderMatcher 需要范围内的隐式 QueryParamDecoder[Part]

为此我创建了一个隐式 val,它需要检查我们是否真的有一个有效的分数,也就是说,两个字符都应该是数字(而不是 a/1b/c 等)且分数应小于 1(1/25/8 等):

  implicit val ev: QueryParamDecoder[Part] =
    new QueryParamDecoder[Part] {
      def decode(
          partition: QueryParameterValue
        ): ValidatedNel[ParseFailure, Part] = {

        val partAndPartitions = partition.value.split("/")

        Validated
          .catchOnly[NumberFormatException] {
            val part = partAndPartitions(0).toInt
            val partitions = partAndPartitions(1).toInt

            if (
              partAndPartitions.length != 2 || part > partitions || part <= 0 || partitions <= 0
            ) {
              throw new IllegalArgumentException
            }
            (part, partitions)
          }
          .leftMap(e => ParseFailure("Invalid query parameter part", e.getMessage))
          .toValidatedNel
      }
    }

上面代码的问题是,它只捕获 NumberFormatException(当它不能使用 .toInt 将字符串转换为 Int 时也是如此)但是如果我输入类似 [=23 的东西怎么办? =],然后它应该捕获 ArrayIndexOutOfBoundsException 因为我正在查询数组中的前两个值,或者当小数根本无效时假设 IllegalArgumentException 。我怎样才能做到这一点,一次性捕获所有内容?谢谢!

嗯,最简单的方法是使用 .catchOnly[Throwable] (甚至直接 QueryParamDecoder .fromUnsafeCast,它会捕获任何错误。

不过,我个人更愿意这样做:
(我现在无法编译代码,如果有错别字,请见谅)

implicit final val PartQueryParamDecoder: QueryParamDecoder[Part] =
  QueryParamDecoder[String].emap { str =>
    def failure(details: String): Either[ParseFailure, Part] =
      Left(ParseFailure(
        sanitized = "Invalid query parameter part",
        details = s"'${str}' is not properly formttated: ${details}"
      ))

    str.split('/').toList match {
      case aRaw :: bRaw :: Nil =>
        (aRaw.toIntOption, bRaw.toIntOption) match {
          case (Some(a), Some(b)) =>
            Right(Part(a, b))

          case _ =>
            failure(details = "Some of the fraction parts are not numbers")
        }

      case _ =>
        failure(details = "It doesn't correspond to a fraction 'a/b'")
    }
  }