Closed tmbull closed 7 years ago
Hello,
no need to apologize, there is indeed design flaw in cookied
function. I didn't think of using it in tandem with query parameters and therefore it's hard to use it this way.
As a temporary workaround you can use cookies
with lambdas like that:
handler q1 q2 ... qN session = ...
handler' q1 q2 ... qN = cookied (handler q1 q2) -- here session variable can be omitted
-- or when session argument goes first
handler session q1 q2 ... qN = ...
handler' session q1 q2 ... qN = cookied (\s -> handler s q1 q2 ... qN) session
Looks ugly, but should work. I'm thinking how to simplify it.
The problem is that cookies
should transform function of type
t1 -> t2 -> ... -> tN -> Session -> r
to
t1 -> t2 -> ... -> tN -> WithMetadata Session -> Handler (Cookied r)
, i.e. wrap the last argument and lift result to Handler
monad.
Dealing with "the last argument" when we don't know exact number of arguments is quite tricky.
Even if we rewrite the code, so that the session is the first argument, we will have to drag Handler
monad through applications of each argument, which results in more complicated and verbose code.
As a side note, there is one more problem: cookied
takes pure function (in a sesnse that it's result is not in Handler
monad). This means you cannot throw errors inside it. Unlike the first problem, it's easier to fix.
I have few ideas, I would like try before resorting to template haskell, so it might take the time :)
Done, it is possible without TH. Here is an example of new cookied
function:
type TestApi
= "test-0-0"
:> AuthProtect "cookie-auth"
:> Get '[JSON] (Cookied String)
:<|> "test-1-0"
:> AuthProtect "cookie-auth"
:> QueryParam "q1" String
:> Get '[JSON] (Cookied String)
:<|> "test-1-1"
:> QueryParam "q1" String
:> AuthProtect "cookie-auth"
:> Get '[JSON] (Cookied String)
:<|> "test-2-0"
:> AuthProtect "cookie-auth"
:> QueryParam "q1" String
:> QueryParam "q2" Int
:> Get '[JSON] (Cookied String)
:<|> "test-2-1"
:> QueryParam "q1" String
:> AuthProtect "cookie-auth"
:> QueryParam "q2" Int
:> Get '[JSON] (Cookied String)
:<|> "test-2-2"
:> QueryParam "q1" String
:> QueryParam "q2" Int
:> AuthProtect "cookie-auth"
:> Get '[JSON] (Cookied String)
:<|> "test-3-0"
:> AuthProtect "cookie-auth"
:> QueryParam "q1" String
:> QueryParam "q2" Int
:> QueryParam "q3" Bool
:> Get '[JSON] (Cookied String)
:<|> "test-3-1"
:> QueryParam "q1" String
:> AuthProtect "cookie-auth"
:> QueryParam "q2" Int
:> QueryParam "q3" Bool
:> Get '[JSON] (Cookied String)
:<|> "test-3-2"
:> QueryParam "q1" String
:> QueryParam "q2" Int
:> AuthProtect "cookie-auth"
:> QueryParam "q3" Bool
:> Get '[JSON] (Cookied String)
:<|> "test-3-3"
:> QueryParam "q1" String
:> QueryParam "q2" Int
:> QueryParam "q3" Bool
:> AuthProtect "cookie-auth"
:> Get '[JSON] (Cookied String)
test0 :: Session -> Handler String
test0 session = return . concat $ (["test-0-0::", show session] :: [String])
test10 :: Session -> Maybe String -> Handler String
test10 session q1 = return . concat $ (["test-1-0::", show q1, "::", show session] :: [String])
test11 :: Maybe String -> Session -> Handler String
test11 q1 session = return . concat $ (["test-1-1::", show q1, "::", show session] :: [String])
test20 :: Session -> Maybe String -> Maybe Int -> Handler String
test20 session q1 q2 = return . concat $ (["test-2-0::", show q1, "::", show q2, "::", show session] :: [String])
test21 :: Maybe String -> Session -> Maybe Int -> Handler String
test21 q1 session q2 = return . concat $ (["test-2-1::", show q1, "::", show q2, "::", show session] :: [String])
test22 :: Maybe String -> Maybe Int -> Session -> Handler String
test22 q1 q2 session = return . concat $ (["test-2-2::", show q1, "::", show q2, "::", show session] :: [String])
test30 :: Session -> Maybe String -> Maybe Int -> Maybe Bool -> Handler String
test30 session q1 q2 q3 = return . concat $ (["test-3-0::", show q1, "::", show q2, "::", show q3, "::", show session] :: [String])
test31 :: Maybe String -> Session -> Maybe Int -> Maybe Bool -> Handler String
test31 q1 session q2 q3 = return . concat $ (["test-3-1::", show q1, "::", show q2, "::", show q3, "::", show session] :: [String])
test32 :: Maybe String -> Maybe Int -> Session -> Maybe Bool -> Handler String
test32 q1 q2 session q3 = return . concat $ (["test-3-2::", show q1, "::", show q2, "::", show q3, "::", show session] :: [String])
test33 :: Maybe String -> Maybe Int -> Maybe Bool -> Session -> Handler String
test33 q1 q2 q3 session = return . concat $ (["test-3-3::", show q1, "::", show q2, "::", show q3, "::", show session] :: [String])
serveTest
= cookied' test0
:<|> cookied' test10
:<|> cookied' test11
:<|> cookied' test20
:<|> cookied' test21
:<|> cookied' test22
:<|> cookied' test30
:<|> cookied' test31
:<|> cookied' test32
:<|> cookied' test33
where
cookied' :: CookiedWrapper Session
cookied' = cookied settings rs sks (Proxy :: Proxy Session)
So, to use it you should do the following:
1) check that your handlers return value in Handler
monad (in the example it's simply return
).
2) enable FlexibleContexts
extension
The price of such wrapper is obscure compiler error messages when something goes wrong. In case of ambiguity, make sure you provided signature for every function you pass to cookied
.
Awesome! Thanks for the quick update. I'll give it a try and let you know how it goes.
@zohl wanted to let you know that I tried the new cookied
in our project and it works great, so I'll go ahead and close this. Thanks again for the quick response.
Alright, thank you for letting me know!
Hi, first of all, I apologize if this is a general Haskell question but I'm currently trying to use
servant-auth-cookie
to add authentication to an API with several endpoints. The issue is that most of my endpoints have several query parameters and/or captures. I struggled with this for a while, and eventually ended up rolling my own version for handlers of arbitrary arity:It seems like ought to be an easier solution. Am I doing this wrong? Thanks!