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

Creating a custom directive that uses extractCredentials directive - value map is not a member calling extractCredentials

我想创建一个自定义指令来包装一些路由,然后这将使内部路由能够访问 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

我正在使用带有 bloop 的 VS Code,鼠标悬停错误显示:

value map is not a member of Product with java.io.Serializable

extractCredentials returns 一个 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 理解期望有 Optionmap,它可以调用 .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 和 map 在 2 种不同的类型(指令和选项)上,所以你不能将它们组合成一个来理解。我假设你想做类似的事情:

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

除了 Product with Serializable 的错误之外,您正在尝试创建指令。如果您使用 Some(UserContext) 修复代码,下一个错误是:

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

编译器说当你对一个指令执行平面映射时,你需要return一个新的指令而不是一个选项。

此时你可以从一个函数创建指令 (Tuple1(Option[User) => Route) => Route .考虑到 Route 是 RequestContext => Future[RouteResult] like:

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 的新指令 authenticated