我在使用 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
实例的一些问题。
然而,这个问题有一个通用的答案,就是用你自己的实现复制 ServantAuthProtect
等AuthHandler
,并为它编写你自己的 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
,但这超出了这个问题的范围。 .)。