创建使用extractCredentials指令的自定义指令-值映射不是调用extractCredentials的成员

问题描述

我想创建一个将包装一些Routes的自定义指令,然后该内部指令将允许内部路由访问UserContext对象(如果存在)。

authenticated { uc =>
  post {
       // ... can use uc object here
  }
}

我收到以下代码的编译时错误

case class UserContext(username: String)

def authenticated: Directive1[Option[UserContext]] =
    for {
      credentials <- extractCredentials
      result <- {
        credentials match {
          case Some(c) if c.scheme.equalsIgnoreCase("Bearer") => UserContext(c.token()) // authenticate(c.token)
          case _ => None  //rejectUnauthenticated(AuthenticationFailedRejection.CredentialsMissing)
        }
      }
    } yield result

错误是:

 value map is not a member of UserRoutes.this.UserContext
[error]         credentials match {
[error]                     ^
[error] one error found
[error] (Compile / compileIncremental) Compilation Failed

我正在将VS Code与bloop一起使用,并且鼠标悬停错误显示

值映射不是具有java.io.Serializable的产品的成员

extractCredentials返回一个Option [HttpCredentials],所以我不确定为什么不能匹配它或映射它。

解决方法

您在这里做什么:

  case Some(c) if c.scheme.equalsIgnoreCase("Bearer") => UserContext(c.token()) // authenticate(c.token)
  case _ => None  //rejectUnauthenticated(AuthenticationFailedRejection.CredentialsMissing)

返回UserContextNone。一些编译器必须推断这两者之间的通用类型。每个case classcase object实现Product with Serializable,因此它将是两个case实例的LUB,它们没有实现其他公共共享超类型。

因此,将推断result右侧的内容是Product with Serializable而不是Option[UserContext]for的理解是,Option带有map,可以在其上调用.map { result => result },我们认为类型不匹配。

相信您想在这里

  case Some(c) if c.scheme.equalsIgnoreCase("Bearer") => Some(UserContext(c.token()))
  case _ => None

以防万一,提醒:Option与Java中的@nullable不同,因此a: A不会自动提升为Some(a): Option[A],而None是与null不同。

编辑:

另一件事是,您拥有flatMap并在2种不同的类型(指令和选项)上进行映射,因此您不能仅将两者结合在一起进行理解。我认为您想做类似的事情:

extractCredentials.map { credentials =>
  credentials.collect {
    case c if c.scheme.equalsIgnoreCase("Bearer") => UserContext(c.token())
  }
}
,

除了 Serializable产品的错误外,您还试图创建一个指令。如果使用Some(UserContext)修复代码,则下一个错误是:

Error:(113,14) type mismatch;
 required: akka.http.scaladsl.server.Directive
      

编译器说,当您对指令执行flatMap时,您需要返回一个新的指令而不是一个Option。

此时,您可以从函数 (Tuple1(Option [用户] =>路由)=>路由 )创建指令。考虑到路由是 RequestContext => Future [RouteResult] ,例如:

def authenticated: Directive1[Option[UserContext]] = {
  extractCredentials.flatMap { credentials =>
      Directive { inner =>
        ctx => {
          val result = credentials match {
            case Some(c) if c.scheme.equalsIgnoreCase("Bearer") => Some(UserContext(c.token())) // authenticate(c.token)
            case _ => None //rejectUnauthenticated(AuthenticationFailedRejection.CredentialsMissing)
          }
          inner(Tuple1(result))(ctx)
        }
      }
    }
  }

有了这个,您就有了一个新指令 已认证 ,该指令使用extractCredentials。