Skip to content
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

Fix error on anonymous query #137

Merged
merged 6 commits into from
Jan 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions docs/source/tutorial/tutorial.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
-- This file has been generated from package.yaml by hpack version 0.15.0.
-- This file has been generated from package.yaml by hpack version 0.20.0.
--
-- see: https://github.com/sol/hpack
--
-- hash: b3da6c729f0fa19c9ad82cb7e45f616850463bcc1654b9cd4797e34f6685ebd8

name: tutorial
version: 0.0.1
Expand All @@ -18,11 +20,11 @@ library
other-modules:
Paths_tutorial
build-depends:
base >= 4.9 && < 5
, protolude
aeson
, base >=4.9 && <5
, graphql-api
, markdown-unlit >=0.4
, protolude
, random
, markdown-unlit >= 0.4
, aeson
default-language: Haskell2010
ghc-options: -Wall -pgmL markdown-unlit
30 changes: 18 additions & 12 deletions graphql-wai/graphql-wai.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
-- This file has been generated from package.yaml by hpack version 0.15.0.
-- This file has been generated from package.yaml by hpack version 0.20.0.
--
-- see: https://github.com/sol/hpack
--
-- hash: 12d030d800c1c036c89a9464dd8de8b05f9f6dc28e0faae9d2b105b2b120460e

name: graphql-wai
version: 0.1.0
Expand All @@ -22,15 +24,17 @@ library
default-extensions: NoImplicitPrelude OverloadedStrings RecordWildCards TypeApplications
ghc-options: -Wall -fno-warn-redundant-constraints -Werror
build-depends:
base >= 4.9 && < 5
, protolude
aeson
, base >=4.9 && <5
, exceptions
, wai
, http-types
, graphql-api
, aeson
, http-types
, protolude
, wai
exposed-modules:
GraphQL.Wai
other-modules:
Paths_graphql_wai
default-language: Haskell2010

test-suite wai-tests
Expand All @@ -41,13 +45,15 @@ test-suite wai-tests
default-extensions: NoImplicitPrelude OverloadedStrings RecordWildCards TypeApplications
ghc-options: -Wall -fno-warn-redundant-constraints -Werror
build-depends:
base >= 4.9 && < 5
, protolude
aeson
, base >=4.9 && <5
, exceptions
, wai
, http-types
, graphql-api
, aeson
, wai-extra
, graphql-wai
, http-types
, protolude
, wai
, wai-extra
other-modules:
Paths_graphql_wai
default-language: Haskell2010
2 changes: 1 addition & 1 deletion src/GraphQL/Internal/Execution.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import GraphQL.Internal.Validation
-- * Return {operation}.
getOperation :: QueryDocument value -> Maybe Name -> Either ExecutionError (Operation value)
getOperation (LoneAnonymousOperation op) Nothing = pure op
getOperation (MultipleOperations ops) (Just name) = note (NoSuchOperation name) (Map.lookup name ops)
getOperation (MultipleOperations ops) (Just name) = note (NoSuchOperation name) (Map.lookup (pure name) ops)
getOperation (MultipleOperations ops) Nothing =
case toList ops of
[op] -> pure op
Expand Down
75 changes: 68 additions & 7 deletions src/GraphQL/Internal/Name.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module GraphQL.Internal.Name
( Name(unName)
( Name(unName, Name)
, NameError(..)
, makeName
, nameFromSymbol
, nameParser
-- * Named things
, HasName(..)
-- * Unsafe functions
Expand All @@ -17,13 +18,58 @@ module GraphQL.Internal.Name

import Protolude

import qualified Data.Aeson as Aeson
import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)
import GraphQL.Internal.Syntax.AST
( Name(..)
, NameError(..)
, unsafeMakeName
, makeName
)
import Data.Char (isDigit)
import Data.Text as T (Text)
import qualified Data.Attoparsec.Text as A
import Test.QuickCheck (Arbitrary(..), elements, listOf)
import Data.String (IsString(..))

import GraphQL.Internal.Syntax.Tokens (tok)

-- * Name

-- | A name in GraphQL.
--
-- https://facebook.github.io/graphql/#sec-Names
newtype Name = Name { unName :: T.Text } deriving (Eq, Ord, Show)


-- | Create a 'Name', panicking if the given text is invalid.
--
-- Prefer 'makeName' to this in all cases.
--
-- >>> unsafeMakeName "foo"
-- Name {unName = "foo"}
unsafeMakeName :: HasCallStack => Text -> Name
unsafeMakeName name =
case makeName name of
Left e -> panic (show e)
Right n -> n

-- | Create a 'Name'.
--
-- Names must match the regex @[_A-Za-z][_0-9A-Za-z]*@. If the given text does
-- not match, return Nothing.
--
-- >>> makeName "foo"
-- Right (Name {unName = "foo"})
-- >>> makeName "9-bar"
-- Left (NameError "9-bar")
makeName :: Text -> Either NameError Name
makeName name = first (const (NameError name)) (A.parseOnly nameParser name)

-- | Parser for 'Name'.
nameParser :: A.Parser Name
nameParser = Name <$> tok ((<>) <$> A.takeWhile1 isA_z
<*> A.takeWhile ((||) <$> isDigit <*> isA_z))
where
-- `isAlpha` handles many more Unicode Chars
isA_z = A.inClass $ '_' : ['A'..'Z'] <> ['a'..'z']

-- | An invalid name.
newtype NameError = NameError Text deriving (Eq, Show)

-- | Convert a type-level 'Symbol' into a GraphQL 'Name'.
nameFromSymbol :: forall (n :: Symbol). KnownSymbol n => Either NameError Name
Expand All @@ -41,3 +87,18 @@ nameFromSymbol = makeName (toS (symbolVal @n Proxy))
class HasName a where
-- | Get the name of the object.
getName :: a -> Name

instance IsString Name where
fromString = unsafeMakeName . toS

instance Aeson.ToJSON Name where
toJSON = Aeson.toJSON . unName

instance Arbitrary Name where
arbitrary = do
initial <- elements alpha
rest <- listOf (elements (alpha <> numeric))
pure (Name (toS (initial:rest)))
where
alpha = ['A'..'Z'] <> ['a'..'z'] <> ['_']
numeric = ['0'..'9']
83 changes: 8 additions & 75 deletions src/GraphQL/Internal/Syntax/AST.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@
{-# LANGUAGE ScopedTypeVariables #-}

module GraphQL.Internal.Syntax.AST
( Name(unName)
, nameParser
, NameError(..)
, unsafeMakeName
, makeName
, QueryDocument(..)
( QueryDocument(..)
, SchemaDocument(..)
, Definition(..)
, OperationDefinition(..)
, Node(..)
, getNodeName
, VariableDefinition(..)
, Variable(..)
, SelectionSet
Expand Down Expand Up @@ -54,72 +48,11 @@ module GraphQL.Internal.Syntax.AST

import Protolude

import qualified Data.Aeson as Aeson
import qualified Data.Attoparsec.Text as A
import Data.Char (isDigit)
import Data.String (IsString(..))
import Test.QuickCheck (Arbitrary(..), elements, listOf, oneof)
--import Data.String (IsString(..))
import Test.QuickCheck (Arbitrary(..), listOf, oneof)

import GraphQL.Internal.Arbitrary (arbitraryText)
import GraphQL.Internal.Syntax.Tokens (tok)

-- * Name

-- | A name in GraphQL.
--
-- https://facebook.github.io/graphql/#sec-Names
newtype Name = Name { unName :: Text } deriving (Eq, Ord, Show)

-- | Create a 'Name', panicking if the given text is invalid.
--
-- Prefer 'makeName' to this in all cases.
--
-- >>> unsafeMakeName "foo"
-- Name {unName = "foo"}
unsafeMakeName :: HasCallStack => Text -> Name
unsafeMakeName name =
case makeName name of
Left e -> panic (show e)
Right n -> n

-- | Create a 'Name'.
--
-- Names must match the regex @[_A-Za-z][_0-9A-Za-z]*@. If the given text does
-- not match, return Nothing.
--
-- >>> makeName "foo"
-- Right (Name {unName = "foo"})
-- >>> makeName "9-bar"
-- Left (NameError "9-bar")
makeName :: Text -> Either NameError Name
makeName name = first (const (NameError name)) (A.parseOnly nameParser name)

-- | An invalid name.
newtype NameError = NameError Text deriving (Eq, Show)


instance IsString Name where
fromString = unsafeMakeName . toS

instance Aeson.ToJSON Name where
toJSON = Aeson.toJSON . unName

instance Arbitrary Name where
arbitrary = do
initial <- elements alpha
rest <- listOf (elements (alpha <> numeric))
pure (Name (toS (initial:rest)))
where
alpha = ['A'..'Z'] <> ['a'..'z'] <> ['_']
numeric = ['0'..'9']

-- | Parser for 'Name'.
nameParser :: A.Parser Name
nameParser = Name <$> tok ((<>) <$> A.takeWhile1 isA_z
<*> A.takeWhile ((||) <$> isDigit <*> isA_z))
where
-- `isAlpha` handles many more Unicode Chars
isA_z = A.inClass $ '_' : ['A'..'Z'] <> ['a'..'z']
import GraphQL.Internal.Name (Name)

-- * Documents

Expand All @@ -143,12 +76,12 @@ data OperationDefinition
| AnonymousQuery SelectionSet
deriving (Eq,Show)

data Node = Node Name [VariableDefinition] [Directive] SelectionSet
data Node = Node (Maybe Name) [VariableDefinition] [Directive] SelectionSet
deriving (Eq,Show)

-- TODO: Just make Node implement HasName.
getNodeName :: Node -> Name
getNodeName (Node name _ _ _) = name
--
getNodeName :: Node -> Maybe Name
getNodeName (Node maybeName _ _ _) = maybeName

data VariableDefinition = VariableDefinition Variable Type (Maybe DefaultValue)
deriving (Eq,Show)
Expand Down
Loading