haskell-graphql / graphql-api

Write type-safe GraphQL services in Haskell
BSD 3-Clause "New" or "Revised" License
406 stars 35 forks source link

(List Text) as argument #142

Open sunwukonga opened 6 years ago

sunwukonga commented 6 years ago

The following query works:

query {
  user {
    friends
  }
}

against a toApplication @RootQuery rootHandler request respond on the following:

type RootQuery = Object "Query" '[]
  '[ Field "user" User ]

type User = Object "User" '[]
  '[ Field "friends" (List Text) ]

rootHandler :: Handler IO RootQuery
rootHandler = pure $ pure myFriends

myFriends :: Handler IO (List Text)
myFriends = pure [pure "Jack", pure "Jim"]

However, I am having trouble doing this with an argument like:

type User = Object "User" '[]
  '[ Argument "friendNames" (List Text) :> Field "friends" (List Text) ]

myFriends :: [Text] -> Handler IO (List Text)
myFriends friends = pure $ map (pure :: Text -> IO Text)  friends

This results in an error:

Couldn't match type ‘[Text]’ with ‘List Text’
Expected type: Handler IO RootQuery
        Actual type: IO
                       (IO
                          ((Text -> IO Text)
                           :<> ((UserENUM -> IO UserENUM)
                                :<> ((DogStuff -> IO Text)
                                     :<> (IO [IO Text] :<> ([Text] -> IO [IO Text]))))))

The issue lies here ...............................................................................^^^^^ The types say that the argument needs to be List Text, but the handler function takes [Text] If I set the type signature of the handler `myFriends' to:

myFriends :: List Text -> Handler IO (List Text)

I get the following error:

  Couldn't match type ‘List (IO Text)’ with ‘[IO Text]’
      Expected type: Handler IO (List Text)
        Actual type: IO (List (IO Text))

Which is no surprise, given this from GraphQl/Resolver.hs:

type Handler m (API.List hg) = m [Handler m hg]

Ultimately, what I interpret this to mean is that type User should be defined as:

 type User = Object "User" '[]
  '[ Argument "friendNames" [Text] :> Field "friends" (List Text) ]

Which results in the error:

No instance for (GraphQL.API.GenericAnnotatedInputType
                         (D1
                            ('MetaData "[]" "GHC.Types" "ghc-prim" 'False)
                            (C1 ('MetaCons "[]" 'PrefixI 'False) U1
                             :+: C1
                                   ('MetaCons ":" ('InfixI 'LeftAssociative 9) 'False)
                                   (S1
                                      ('MetaSel
                                         'Nothing
                                         'GHC.Generics.NoSourceUnpackedness
                                         'GHC.Generics.NoSourceStrictness
                                         'GHC.Generics.DecidedLazy)
                                      (Rec0 Text)
                                    :*: S1
                                          ('MetaSel
                                             'Nothing
                                             'GHC.Generics.NoSourceUnpackedness
                                             'GHC.Generics.NoSourceStrictness
                                             'GHC.Generics.DecidedLazy)
                                          (Rec0 [Text])))))
        arising from a use of ‘GraphQL.API.$dmgetAnnotatedInputType’
In the expression: GraphQL.API.$dmgetAnnotatedInputType @[Text]
sunwukonga commented 6 years ago

I have solved this for myself, but I need feedback before I presume to push it as a PR.

I included a [t] as an instance of HasAnnotatedInputType in API.hs:

instance forall t. (HasAnnotatedInputType t) => HasAnnotatedInputType [t] where
    getAnnotatedInputType = TypeList . ListType <$> getAnnotatedInputType @t

the same definition as for List t ^^^. Also included a Defaultable instance for [a] in Resolver.hs:

 instance Defaultable [a]

Which allows the following:

type User = Object "User" '[]
  '[ Argument "friendNames" [Text] :> Field "friends" (List Text) ]

myFriends :: [Text] -> Handler IO (List Text)
myFriends friends = pure $ map pure friends

I feel like I should have been able to convince the compiler that List a and [a] are the same for HasAnnotatedInputType a, but I am having trouble even grokking how GraphQL.API.List leaves the realm of Types and becomes a usable value.

jml commented 6 years ago

@teh is probably best placed to help with this issue. Our type gymnastics are a bit much for me.

sunwukonga commented 6 years ago

It heartens me that I'm not the only one ;)

teh commented 6 years ago

@sunwukonga I had a go at this last weekend and I'm not so sure what I did any more either! I feel you are on the right track (we forgot to implement HasAnnotatedInputType for lists) but right now I'm not 100% sure yet that your solution is correct (I'm 90% sure though!). I'm sorry it's taking a while.

harendra-kumar commented 6 years ago

@teh any update on whether the solution suggested by @sunwukonga can be pushed as a fix? Even if you are not fully sure we can perhaps try it and see if users find any problem with it.

harendra-kumar commented 6 years ago

I tried this solution and so far it is working fine for me.

teh commented 6 years ago

@harendra-kumar if it's working for you and @sunwukonga we should include it. Do you want to open a PR?

EdmundsEcho commented 6 years ago

With the information provided in this post, I have been using the following patch to enable user input :: [input] that behaves accordingly in my handlers. I import the Patch where I would otherwise do so for GraphQL.API.

module Patch
  (
    module Patch
  , module GraphQL.API
  )
  where

import GraphQL.API
import Protolude hiding (Enum, TypeError)
import qualified GraphQL.Internal.Schema as Schema

instance Defaultable [a]

instance forall t. (HasAnnotatedInputType t)
  => HasAnnotatedInputType [t] where
  getAnnotatedInputType = Schema.TypeList . Schema.ListType <$> getAnnotatedInputType @t