haskell-servant / servant-quickcheck

40 stars 21 forks source link

No instance for (HasGenRequest API) when using servant-checked-exceptions #67

Closed tneisinger closed 4 years ago

tneisinger commented 4 years ago

A reproduction of this problem can be found here: https://github.com/tneisinger/sce-issue

I'm trying to use servant-quickcheck and servant-checked-exceptions together on a project, but I'm getting the following error:

• No instance for (HasGenRequest Lib.API)
        arising from a use of ‘serverSatisfies’
    • In the expression:
        serverSatisfies api burl args (not500 <%> mempty)
      In the second argument of ‘($)’, namely
        ‘\ burl -> serverSatisfies api burl args (not500 <%> mempty)’
      In the second argument of ‘($)’, namely
        ‘withServantServerAndContext api ctx (return server)
           $ \ burl -> serverSatisfies api burl args (not500 <%> mempty)’
   |
28 |       serverSatisfies api burl args (not500
   | 

With an API defined like this:

type API = Throws NoUsersError :> "users" :> Get '[JSON] [User]

And a test defined like this:

main :: IO ()
main = hspec spec

spec :: Spec
spec = describe "" $
  it "API demonstrates some best practices" $
    withServantServerAndContext api ctx (return server) $ \burl ->
      serverSatisfies api burl args (not500
                                 <%> mempty)

args :: Args
args = defaultArgs { maxSuccess = 500 }

ctx :: Context '[]
ctx = EmptyContext

I wasn't sure if I should submit this issue here or on the servant-checked-exceptions repo, but I decided here might be the better place. Hopefully it isn't inappropriate to ask this here.

Has anyone else dealt with this issue before? Any advice?

I think the advanced type-magic involved here puts this problem a bit out of my depth, so any help is greatly appreciated. Thanks!

parsonsmatt commented 4 years ago

I think we just need an instance of HasGenRequest for Throws err :> rest. It's not certain where this instance should live, but you can implement it in your project for a start.

Since Throws adds an annotation on what the endpoint might possibly return, I think the implementation can be a pass through, like:

instance (HasGenRequest rest) => HasGenRequest (Throws err :> rest) where
  genRequest _ = genRequest @rest

I'm not super familiar with the library though, so i may be wrong here.

tneisinger commented 4 years ago

Awesome, that's what I needed! One small tweak and this seems to be working:

instance (HasGenRequest rest) => HasGenRequest (Throws err :> rest) where
  genRequest _ = genRequest (Proxy :: Proxy rest)

Thanks!