私は 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 の一般的な認証実装の構造内では不可能です。HasServer
Cactus の答えは、existential を newtype でラップする必要があることを正しく示していますが、それ自体は単に Servant 制約に関するコンパイル エラー、おそらくインスタンスに関する問題につながるだけです。
ただし、この問題には一般的な答えがあります。それは単に、サーバントの などを独自の実装で複製AuthProtect
し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
最後に、サーバントを使用してこれをすべてまとめ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 型で、したがってハンドラーで型シノニムを使用できるようになります。 .
アプリにカスタム モナドを使用している場合は、authHandler の実行時に使用することで、このパターンを改善できenter
ます (そして、アプリ モナドが必要とするコンテキストを、渡されたコンテキストに追加しますserveWithContext
が、それはこの質問の範囲外です.. .)。