0

奇妙なエラーが発生しました。修正方法が見つかりません。私はサーバントを使用しており、一般的な認証ライブラリを構築しようとしています (たとえば、デフォルトではバックエンドはありません)。

コードは次のとおりです。

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 の一般的な認証実装の構造内では不可能です。HasServerCactus の答えは、existential を newtype でラップする必要があることを正しく示していますが、それ自体は単に Servant 制約に関するコンパイル エラー、おそらくインスタンスに関する問題につながるだけです。

ただし、この問題には一般的な答えがあります。それは単に、サーバントの などを独自の実装で複製AuthProtectAuthHandler、独自のバージョンの 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が、それはこの質問の範囲外です.. .)。

于 2016-10-27T14:56:57.030 に答える