将 `Option[A]` 转换为 Http4s 中的 Ok() 或 NotFound() API

Converting an `Option[A]` to an Ok() or NotFound() inside an Http4s API

我有一个 API 看起来像这样:

object Comics {
  ...

  def impl[F[_]: Applicative]: Comics[F] = new Comics[F] {
    def getAuthor(slug: Authors.Slug): F[Option[Authors.Author]] =
      ...

以及如下所示的路由:

object Routes {
  def comicsRoutes[F[_]: Sync](comics: Comics[F]): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._
    HttpRoutes.of[F] {
      case GET -> Root / "comics" / authorSlug =>
        comics
          .getAuthor(Authors.Slug(authorSlug))
          .flatMap {
            case Some(author) => Ok(author)
            case None         => NotFound()
          }

所以当有 None 时,它会转换为 404。由于有多个路由,.flatMap { ... } 会重复。

问题:如何将它移动到一个单独的 .orNotFound 特定于我的项目的辅助函数中?


我的尝试:

为了让事情对我来说简单(并避免在 F 最初进行参数化),我尝试在 comicsRoutes:

中定义它
  def comicsRoutes[F[_]: Sync](comics: Comics[F]): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    def orNotFound[A](opt: Option[A]): ???[A] =
      opt match {
        case Some(value) => Ok(value)
        case None        => NotFound()
      }

    HttpRoutes.of[F] {
      case GET -> Root / "comics" / authorSlug =>
        comics
          .getAuthor(Authors.Slug(authorSlug))
          .flatMap(orNotFound)

但是这里 ??? 是什么?它似乎不是 ResponseStatus。此外,.flatMap { ... } 是在 import dsl._ 下创建的,但我想将它移到更远的地方。什么是好地方?它是进入路由文件,还是我把它放在 separate ExtendedSomething extension file 中? (我预计 ???Something 可能相关,但我对缺少的类型有些困惑。)

(同样重要的是,我如何找出这里的 ????我希望 ??? 在类型级别可能会给我一个“打字洞”,并且 VSCode的悬停功能提供了非常零星的文档价值。)

Http4sDsl[F] 为您的操作返回的类型是 F[Response[F]]

它必须用 F 包裹,因为您在 F 上使用 .flatMapResponse 使用 F 进行参数化,因为它将生成使用 F.

返回给调用方的结果

要找出答案,您可以使用 IntelliJ,然后通过 IDE 生成注释(Alt+Enter,然后“将类型注释添加到值定义”)。您还可以:

  • preview implicits 检查从 Statuses trait 导入的 Ok 对象是否提供了具有 http4sOkSyntax 隐式转换的扩展方法(Ctrl+Alt+Shift+加号,你可以按它多次扩展隐式,然后 Ctrl+Alt+Shift+Minut 再次隐藏它们)
  • 通过按两次 Shift 打开查找 window 找到 http4sOkSyntax,然后再按两次以包括非项目符号,
  • 从那里使用 Ctrl+B 导航,通过 OkOpsEntityResponseGenerator class,这为您提供了 您使用的功能(在 apply 中)返回 F[Resposne[F]].

因此,如果您想移动 around/extract 它们,请注意实例化 DSL 和扩展方法需要哪些隐式。

(快捷方式在 Mac OS 之间有所不同 - 有时使用 Cmd 而不是 Ctrl - 和非 Mac OS 系统所以如果你只是在文档中检查它们有问题)。

感谢 Mateusz,我了解到 ??? 应该是 F[Response[F]]

为了使这个辅助函数充分发挥作用,又发生了两个与类型相关的问题:

  1. 因为 value: A 是多态的,Http4s 需要一个隐式的 EntityEncoder[F, A] 来序列化一个任意值。 (这不是原始 { case ... } 匹配的问题,因为类型是具体的而不是多态的。

  2. 出于某种原因,添加此隐式注释是不够的。做 .flatMap(orNotFound) 类型推断失败。 .flatMap(orNotFound[Authors.Slug]) 解决了这个问题。

(感谢 keynmol 指出另外两个。)

完成所有三个更改后,结果为:

  def comicsRoutes[F[_]: Sync](comics: Comics[F]): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    def orNotFound[A](opt: Option[A])(implicit ee: EntityEncoder[F, A]): F[Response[F]] =
      opt match {
        case Some(value) => Ok(value)
        case None        => NotFound()
      }

    HttpRoutes.of[F] {
      case GET -> Root / "comics" / authorSlug =>
        comics
          .getAuthor(Authors.Slug(authorSlug))
          .flatMap(orNotFound[Authors.Author])
      ...