-
Notifications
You must be signed in to change notification settings - Fork 32
Create a Request object and an interpretRequest path #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 3 commits
c6241d3
af093b4
73f83e5
6572204
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,8 @@ library: | |
- scientific | ||
- QuickCheck | ||
- text | ||
- vector | ||
- unordered-containers | ||
|
||
tests: | ||
graphql-api-tests: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,8 +51,11 @@ module GraphQL.Value | |
import Protolude | ||
|
||
import qualified Data.Aeson as Aeson | ||
import Data.Aeson (ToJSON(..), (.=), pairs) | ||
import Data.Aeson (FromJSON(..), ToJSON(..), (.=), pairs) | ||
import qualified Data.HashMap.Lazy as HashMap | ||
import qualified Data.Map as Map | ||
import Data.Scientific (toRealFloat) | ||
import qualified Data.Vector as Vector | ||
import Test.QuickCheck (Arbitrary(..), Gen, oneof, listOf, sized) | ||
|
||
import GraphQL.Internal.Arbitrary (arbitraryText) | ||
|
@@ -86,6 +89,11 @@ instance Traversable Value' where | |
traverse f (ValueList' xs) = ValueList' <$> traverse f xs | ||
traverse f (ValueObject' xs) = ValueObject' <$> traverse f xs | ||
|
||
instance FromJSON scalar => FromJSON (Value' scalar) where | ||
parseJSON (Aeson.Object x) = ValueObject' <$> parseJSON (Aeson.Object x) | ||
parseJSON (Aeson.Array x) = ValueList' <$> parseJSON (Aeson.Array x) | ||
parseJSON x = ValueScalar' <$> parseJSON x | ||
|
||
instance ToJSON scalar => ToJSON (Value' scalar) where | ||
toJSON (ValueScalar' x) = toJSON x | ||
toJSON (ValueList' x) = toJSON x | ||
|
@@ -151,6 +159,11 @@ toObject _ = empty | |
-- * Scalars | ||
|
||
-- | A non-variable value which contains no other values. | ||
-- | ||
-- Note that the 'FromJSON' instance always decodes JSON strings to | ||
-- GraphQL strings (never enums) and JSON numbers to GraphQL floats | ||
-- (never ints); doing a better job of resolving this requires query | ||
-- context. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little uncomfortable about this. The "Zen of Python" advises "in the face of ambiguity, refuse the temptation to guess", and I think that's good advice. Some options I could think of:
|
||
data ConstScalar | ||
= ConstInt Int32 | ||
| ConstFloat Double | ||
|
@@ -160,6 +173,13 @@ data ConstScalar | |
| ConstNull | ||
deriving (Eq, Ord, Show) | ||
|
||
instance FromJSON ConstScalar where | ||
parseJSON (Aeson.String x) = parseJSON (Aeson.String x) >>= return . ConstString | ||
parseJSON (Aeson.Number x) = return $ ConstFloat $ toRealFloat x | ||
parseJSON (Aeson.Bool x) = return $ ConstBoolean x | ||
parseJSON Aeson.Null = return ConstNull | ||
parseJSON _ = mempty | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not |
||
|
||
instance ToJSON ConstScalar where | ||
toJSON (ConstInt x) = toJSON x | ||
toJSON (ConstFloat x) = toJSON x | ||
|
@@ -213,14 +233,12 @@ astToScalar _ = empty | |
|
||
-- * Strings | ||
|
||
newtype String = String Text deriving (Eq, Ord, Show) | ||
newtype String = String Text deriving (Eq, Ord, Show, Aeson.FromJSON, | ||
Aeson.ToJSON) | ||
|
||
instance Arbitrary String where | ||
arbitrary = String <$> arbitraryText | ||
|
||
instance ToJSON String where | ||
toJSON (String x) = toJSON x | ||
|
||
-- * Lists | ||
|
||
newtype List' scalar = List' [Value' scalar] deriving (Eq, Ord, Show, Functor) | ||
|
@@ -245,6 +263,9 @@ instance Arbitrary scalar => Arbitrary (List' scalar) where | |
-- invalid lists. | ||
arbitrary = List' <$> listOf arbitrary | ||
|
||
instance FromJSON scalar => FromJSON (List' scalar) where | ||
parseJSON = Aeson.withArray "List" $ \v -> | ||
mapM parseJSON v >>= return . List' . Vector.toList | ||
|
||
instance ToJSON scalar => ToJSON (List' scalar) where | ||
toJSON (List' x) = toJSON x | ||
|
@@ -302,6 +323,16 @@ objectFromList xs = Object' <$> OrderedMap.orderedMap xs | |
unionObjects :: [Object' scalar] -> Maybe (Object' scalar) | ||
unionObjects objects = Object' <$> OrderedMap.unions [obj | Object' obj <- objects] | ||
|
||
instance FromJSON scalar => FromJSON (Object' scalar) where | ||
parseJSON = Aeson.withObject "Object" $ \v -> do | ||
-- Order of keys is lost before we get here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you remind me why this is relevant? |
||
let kvps = HashMap.toList v | ||
names <- mapM parseJSON (Aeson.String <$> fst <$> kvps) | ||
values <- mapM parseJSON (snd <$> kvps) | ||
case objectFromList $ zip names values of | ||
Nothing -> mempty | ||
Just obj -> return obj | ||
|
||
instance ToJSON scalar => ToJSON (Object' scalar) where | ||
-- Direct encoding to preserve order of keys / values | ||
toJSON (Object' xs) = toJSON (Map.fromList [(unName k, v) | (k, v) <- OrderedMap.toList xs]) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
mempty
the right thing here? Should we maybefail
instead? I'm not even sure whymempty
is a valid value forName
- maybe @jml remembers :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In both this and the other case, you're running inside the Aeson
Parser
monad. Protolude is a little more aggressive about movingfail
out of the baseMonad
class, so it looks like I need toimport Control.Monad.Fail
and then it works.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@teh This is a wart in Aeson IMO. Because
mempty
wasn't prefixed withreturn
(orpure
), it'smempty :: Aeson.Parser Name
, notmempty :: Name
(which wouldn't compile).If you look at the source for
Parser a
, it has an implementation ofMonoid
such thatmempty = fail "mempty"
. I think this kind of sucks.empty
(i.e. failedAlternative
, not monoid identity), would be much more correct.But
fail
is best, because it's informative.