servant-elm icon indicating copy to clipboard operation
servant-elm copied to clipboard

Possibility to Generate For `AuthProtect` endpoints?

Open erewok opened this issue 9 years ago • 7 comments

Hello,

I have been using this library in a personal project and it's pretty damn cool. Thank you for writing it and for releasing it.

I did have a question, though, about AuthProtect endpoints. I have been using the servant-auth-cookie library to generate sessions and enforce authentication for certain endpoints, but I have not been able to use servant-elm to generate Api calls for these endpoints.

Maybe an example would be illustrative. With an endpoint like this:

type AdminApi = 
  "admin" :> AuthProtect "cookie-auth" :> Get '[HTML] Html
  :<|> "admin" :> "post" :> ReqBody '[JSON] Post.BlogPost :> AuthProtect "cookie-auth" :> Post '[JSON] Post.BlogPost

If I try to generate Elm for this using servant-elm, I get a compiler error like this:

client/GenerateElm.hs:21:9: error:
    • No instance for (Servant.Foreign.Internal.HasForeign
                         servant-elm-0.1.0.2:Servant.Elm.Foreign.LangElm
                         elm-export-0.3.0.0:Elm.Type.ElmTypeExpr
                         (Servant.API.Experimental.Auth.AuthProtect "cookie-auth"
                          Servant.API.Sub.:> Servant.API.Verbs.Post
                                               '[Servant.API.ContentTypes.JSON]
                                               Post.BlogPost))
        arising from a use of ‘generateElmForAPI’
    • In the second argument of ‘(:)’, namely
        ‘generateElmForAPI adminProxyApi’
      In the second argument of ‘(:)’, namely
        ‘defElmImports : generateElmForAPI adminProxyApi’
      In the second argument of ‘(:)’, namely
        ‘"import Exts.Date exposing (..)"
         : defElmImports : generateElmForAPI adminProxyApi’
make: *** [Api.elm] Error 1

Am I doing something wrong or must there be an instance defined to be able to handle AuthProtected endpoints?

Thanks for any suggestions.

erewok avatar Oct 14 '16 05:10 erewok

Hi,

Thanks - glad you like the library!

We're waiting for servant-foreign to add support for the AuthProtect combinator, then we can think about how to support auth in servant-elm.

However, if your Elm requests don't need to do anything special (I'm guessing the browser automatically adds your auth cookie to the requests?), in the meantime you can just add an instance for AuthProtect that does nothing:

instance (KnownSymbol sym, HasForeign lang ftype sublayout)
    => HasForeign lang ftype (AuthProtect sym :> sublayout) where
    type Foreign ftype (AuthProtect sym :> sublayout) = Foreign ftype sublayout

    foreignFor lang ftype Proxy req =
      foreignFor lang ftype (Proxy :: Proxy sublayout) req

mattjbray avatar Oct 16 '16 18:10 mattjbray

Nice! I'll give it a shot and report back. Thanks for the suggestion!

erewok avatar Oct 16 '16 19:10 erewok

Well, I got stuck on an ambiguous type variable problem and was unfortunately at a loss as to how to begin debugging it, but it's alright if this doesn't work because I can just generate the Elm by hand and return to this problem once the servant-foreign library supports the AuthProtect combinator.

I'll post my attempt and the compiler error anyway in case anyone is working on something similar and stumbles across this discussion:

{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeOperators         #-}

import           Data.List
import           Data.Proxy
import           GHC.TypeLits    (KnownSymbol)
import           Servant.Elm
import           Servant.Foreign

import           Api

instance (KnownSymbol sym, HasForeign lang ftype sublayout)
    => HasForeign lang ftype (AuthProtect sym :> sublayout) where
    type Foreign ftype (AuthProtect sym :> sublayout) = Foreign ftype sublayout

    foreignFor lang ftype Proxy req =
      foreignFor lang ftype (Proxy :: Proxy sublayout) req

Results in:

    • Couldn't match type ‘Foreign ftype api0’
                     with ‘Foreign ftype sublayout’
      Expected type: Foreign ftype (AuthProtect sym :> sublayout)
        Actual type: Foreign ftype api0
      NB: ‘Foreign’ is a type function, and may not be injective
      The type variable ‘api0’ is ambiguous
    • In the expression:
        foreignFor lang ftype (Proxy :: Proxy sublayout) req
      In an equation for ‘foreignFor’:
          foreignFor lang ftype Proxy req
            = foreignFor lang ftype (Proxy :: Proxy sublayout) req
      In the instance declaration for
        ‘HasForeign lang ftype (AuthProtect sym :> sublayout)’
    • Relevant bindings include
        req :: Req ftype (bound at client/GenerateElm.hs:19:33)
        ftype :: Proxy ftype (bound at client/GenerateElm.hs:19:21)
        foreignFor :: Proxy lang
                      -> Proxy ftype
                      -> Proxy (AuthProtect sym :> sublayout)
                      -> Req ftype
                      -> Foreign ftype (AuthProtect sym :> sublayout)
          (bound at client/GenerateElm.hs:19:5)

erewok avatar Oct 16 '16 19:10 erewok

I just happened to be working on this and using {-# LANGUAGE ScopedTypeVariables #-} solved the ambiguous type variable problem.

garetht avatar Oct 17 '16 00:10 garetht

@garetht's suggestion worked for me.

erewok avatar Oct 17 '16 01:10 erewok

Great! Sorry, it would have been helpful for me to put the language pragmas and imports in my snippet...

mattjbray avatar Oct 17 '16 12:10 mattjbray

Oh, no worries. I should understand the language pragmas better than I do if I'm going to be using them.

erewok avatar Oct 17 '16 16:10 erewok