0

我遇到了一个奇怪的错误,我找不到修复它的方法。我正在使用servant,并且正在尝试构建一个通用的身份验证库(例如,默认情况下没有后端)。

代码如下:

type TokenProtect auth = AuthProtect "auth-token"
type instance AuthServerData (TokenProtect auth) = Id auth

Id是另一种类型的家庭。错误消息如下。

    • Family instance purports to bind type variable ‘auth’
        but the real LHS (expanding synonyms) is:
          AuthServerData (AuthProtect "auth-token") = ...
    • In the type instance declaration for ‘AuthServerData’

你知道我该如何修复这个代码吗?

4

2 回答 2

0

我在使用 Servant 时遇到了这个问题,我认为我的用例与原始提问者的相似。基本上,我希望 AuthProtect 允许我将由类提供的某些类型同义词约束的类型通过线程传递给我的处理程序,例如

class IsDatabase db where 
   type DatabaseAuthResult db :: *
instance IsDatabase MyDBType
   type DatabaseAuthResult MyDBType = DBUser

因此需要类似于原始海报的代码:

type TokenProtect db = AuthProtect "auth-token"
type instance AuthServerData (TokenProtect db) = DatabaseAuthResult db

据我所知,这在 Servant 的一般 auth implementation的结构中根本不可能。仙人掌的回答正确地说你必须将存在包装在一个新类型中,但这本身只会导致与仆人约束有关的编译错误,可能是HasServer实例的一些问题。

然而,这个问题有一个通用的答案,就是用你自己的实现复制 ServantAuthProtectAuthHandler,并为它编写你自己的 HasServer 版本。

-- import for all the internal servant stuff like addAuthCheck
import Servant.Server.Internal.RoutingApplication

data DBAuthProtect (tag :: k) db deriving (Typeable)
newtype DBAuthHandler r db result = DBAuthHandler {unDBAuthHandler :: r -> Handler result}

instance ( HasServer api context
         , HasContextEntry context (DBAuthHandler Request db (AuthServerData (DBAuthProtect tag db))))
  => HasServer (DBAuthProtect tag db :> api) context where
  type ServerT (DBAuthProtect tag db :> api) m = AuthServerData (DBAuthProtect tag db) -> ServerT api m
  route Proxy context subserver = 
    route (Proxy :: Proxy api) context (subserver `addAuthCheck` withRequest authCheck)
      where 
       authHandler :: Request -> Handler (AuthServerData (DBAuthProtect tag db))
       authHandler = unDBAuthHandler (getContextEntry context)
       authCheck :: Request -> DelayedIO (AuthServerData (DBAuthProtect tag db))
       authCheck = (>>= either delayedFailFatal return) . liftIO . runExceptT . authHandler

然后,您可以将其与 类似地使用AuthProtect,因此类似于

type TokenProtect db = DBAuthProtect "auth-token" db
type instance AuthServerData (TokenProtect db) = DatabaseAuthResult db
type ProtectedAPI db = "private" :> TokenProtect db :> Get [...]
dbAuthHandler :: (IsDatabase db) => db -> DBAuthHandler Request db (DatabaseAuthResult db)
dbAuthHandler db = DBAuthHandler $ \ req -> do 
  -- req :: Request
  -- ... do some work here and return a type (DatabaseAuthResult db), so for MyDBType you would return DBUser - you have both the db itself and the request to work with

最后,您使用 Servant 将所有这些放在一起,serveWithContext并在上下文中提供部分应用的处理程序

mkContext :: db -> Context '[DBAuthHandler Request db (AuthServerData db)]
mkContext db = dbAuthHandler db :. EmptyContext

main :: IO ()
main = do 
  db <- getMyDBSomehow -- a concrete type, say MyDBType
  let myApi = (Proxy :: Proxy (ProtectedAPI MyDBType))
  serveWithContext myApi (mkContext db) handlers      

基本上它的工作方式是通过各种位和部分线程化类型变量,因此您最终得到一个由 db 类型参数化的 api(类似于处理程序),允许您在 api 类型中使用类型同义词,因此在您的处理程序中.

如果您为您的应用程序使用自定义 monad,您可以通过enter在运行 authHandler 时使用来改进此模式(并将您的应用程序 monad 需要的任何上下文添加到您传递给您的上下文中serveWithContext,但这超出了这个问题的范围。 .)。

于 2016-10-27T14:56:57.030 回答
0

你需要TokenProtect变成一个newtype包装器:

newtype TokenProtect auth = TokenProtect (AuthProtect "auth-token")
type instance AuthServerData (TokenProtect auth) = Id auth

原因是类型同义词就是:同义词;所以你的代码相当于写

type instance AuthServerData (AuthProtect "auth-token") = Id auth

这当然是指未绑定的类型变量auth

于 2016-07-21T15:34:51.597 回答