Декодировать необязательный параметр запроса с помощью QueryParamDecoder в 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/1 или b/c и т. д.), а дробь должна быть меньше 1 (1/2 , 5/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 (это тоже, когда он не может преобразовать строку в Int, используя .toInt), но что, если я введу что-то вроде ?part=/1, он должен поймать ArrayIndexOutOfBoundsException, потому что я запрашиваю первые два значения в массиве или, скажем, IllegalArgumentException, когда дробь вообще недействительна. Как я могу добиться этого, поймав все за один проход? Спасибо!


person fan    schedule 26.05.2021    source источник


Ответы (1)


что ж, самым простым подходом будет использование .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'")
    }
  }
person Luis Miguel Mejía Suárez    schedule 26.05.2021
comment
Работает как шарм! Большое спасибо! Пытался решить это 2 дня подряд. Я просматривал документацию по emap и не мог понять, когда использовать decode, а когда emap. Было бы здорово, если бы вы могли пролить свет на это. :) - person fan; 26.05.2021
comment
@fan decode – это метод, который вызывает фреймворк, и это единственный метод, который вам нужно реализовать, если вы пишете Decorder с нуля. - emap (и друзья) являются помощниками для создания новых Decoders из других Decoders, поэтому, как вы можете видеть, я предположил, что запись уже может быть декодирована как String, а затем попытался преобразовать этот String в Part - person Luis Miguel Mejía Suárez; 26.05.2021