Skip to content

Commit 05fc62b

Browse files
committed
Make Node instance of HasName. Implement getName
Following todo formerly in src/GraphQL/Internal/Syntax/AST.hs: TODO: Just make Node implement HasName. Declared Node as instance of HasName and wrote implementation of getname for it. Because of a cyclic dependency between Name and AST, moved the Name specific code from GraphQL.Internal.Syntax.AST module into the GraphQL.Internal.Name module. Updated imports and exposures in the AST and Name modules described above. Simple import and qualified name changes to: GraphQL/Internal/Syntax/Encoder GraphQL/Internal/Syntax/Parser
1 parent 6f76aa1 commit 05fc62b

File tree

4 files changed

+116
-122
lines changed

4 files changed

+116
-122
lines changed

src/GraphQL/Internal/Name.hs

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
{-# LANGUAGE RankNTypes #-}
66
{-# LANGUAGE ScopedTypeVariables #-}
77
module GraphQL.Internal.Name
8-
( Name(unName)
8+
( Name(unName, Name)
99
, NameError(..)
1010
, makeName
1111
, nameFromSymbol
12+
, nameParser
1213
-- * Named things
1314
, HasName(..)
1415
-- * Unsafe functions
@@ -17,13 +18,57 @@ module GraphQL.Internal.Name
1718

1819
import Protolude
1920

21+
import qualified Data.Aeson as Aeson
2022
import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)
21-
import GraphQL.Internal.Syntax.AST
22-
( Name(..)
23-
, NameError(..)
24-
, unsafeMakeName
25-
, makeName
26-
)
23+
import Data.Char (isDigit)
24+
import qualified Data.Attoparsec.Text as A
25+
import Test.QuickCheck (Arbitrary(..), elements, listOf)
26+
import Data.String (IsString(..))
27+
28+
import GraphQL.Internal.Arbitrary (arbitraryText)
29+
import GraphQL.Internal.Syntax.Tokens (tok)
30+
31+
-- * Name
32+
33+
-- | A name in GraphQL.
34+
--
35+
-- https://facebook.github.io/graphql/#sec-Names
36+
newtype Name = Name { unName :: Text } deriving (Eq, Ord, Show)
37+
38+
-- | Create a 'Name', panicking if the given text is invalid.
39+
--
40+
-- Prefer 'makeName' to this in all cases.
41+
--
42+
-- >>> unsafeMakeName "foo"
43+
-- Name {unName = "foo"}
44+
unsafeMakeName :: HasCallStack => Text -> Name
45+
unsafeMakeName name =
46+
case makeName name of
47+
Left e -> panic (show e)
48+
Right n -> n
49+
50+
-- | Create a 'Name'.
51+
--
52+
-- Names must match the regex @[_A-Za-z][_0-9A-Za-z]*@. If the given text does
53+
-- not match, return Nothing.
54+
--
55+
-- >>> makeName "foo"
56+
-- Right (Name {unName = "foo"})
57+
-- >>> makeName "9-bar"
58+
-- Left (NameError "9-bar")
59+
makeName :: Text -> Either NameError Name
60+
makeName name = first (const (NameError name)) (A.parseOnly nameParser name)
61+
62+
-- | Parser for 'Name'.
63+
nameParser :: A.Parser Name
64+
nameParser = Name <$> tok ((<>) <$> A.takeWhile1 isA_z
65+
<*> A.takeWhile ((||) <$> isDigit <*> isA_z))
66+
where
67+
-- `isAlpha` handles many more Unicode Chars
68+
isA_z = A.inClass $ '_' : ['A'..'Z'] <> ['a'..'z']
69+
70+
-- | An invalid name.
71+
newtype NameError = NameError Text deriving (Eq, Show)
2772

2873
-- | Convert a type-level 'Symbol' into a GraphQL 'Name'.
2974
nameFromSymbol :: forall (n :: Symbol). KnownSymbol n => Either NameError Name
@@ -41,3 +86,18 @@ nameFromSymbol = makeName (toS (symbolVal @n Proxy))
4186
class HasName a where
4287
-- | Get the name of the object.
4388
getName :: a -> Name
89+
90+
instance IsString Name where
91+
fromString = unsafeMakeName . toS
92+
93+
instance Aeson.ToJSON Name where
94+
toJSON = Aeson.toJSON . unName
95+
96+
instance Arbitrary Name where
97+
arbitrary = do
98+
initial <- elements alpha
99+
rest <- listOf (elements (alpha <> numeric))
100+
pure (Name (toS (initial:rest)))
101+
where
102+
alpha = ['A'..'Z'] <> ['a'..'z'] <> ['_']
103+
numeric = ['0'..'9']

src/GraphQL/Internal/Syntax/AST.hs

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,11 @@
44
{-# LANGUAGE ScopedTypeVariables #-}
55

66
module GraphQL.Internal.Syntax.AST
7-
( Name(unName)
8-
, nameParser
9-
, NameError(..)
10-
, unsafeMakeName
11-
, makeName
12-
, QueryDocument(..)
7+
( QueryDocument(..)
138
, SchemaDocument(..)
149
, Definition(..)
1510
, OperationDefinition(..)
1611
, Node(..)
17-
, getNodeName
1812
, VariableDefinition(..)
1913
, Variable(..)
2014
, SelectionSet
@@ -54,72 +48,11 @@ module GraphQL.Internal.Syntax.AST
5448

5549
import Protolude
5650

57-
import qualified Data.Aeson as Aeson
58-
import qualified Data.Attoparsec.Text as A
59-
import Data.Char (isDigit)
6051
import Data.String (IsString(..))
6152
import Test.QuickCheck (Arbitrary(..), elements, listOf, oneof)
6253

6354
import GraphQL.Internal.Arbitrary (arbitraryText)
64-
import GraphQL.Internal.Syntax.Tokens (tok)
65-
66-
-- * Name
67-
68-
-- | A name in GraphQL.
69-
--
70-
-- https://facebook.github.io/graphql/#sec-Names
71-
newtype Name = Name { unName :: Text } deriving (Eq, Ord, Show)
72-
73-
-- | Create a 'Name', panicking if the given text is invalid.
74-
--
75-
-- Prefer 'makeName' to this in all cases.
76-
--
77-
-- >>> unsafeMakeName "foo"
78-
-- Name {unName = "foo"}
79-
unsafeMakeName :: HasCallStack => Text -> Name
80-
unsafeMakeName name =
81-
case makeName name of
82-
Left e -> panic (show e)
83-
Right n -> n
84-
85-
-- | Create a 'Name'.
86-
--
87-
-- Names must match the regex @[_A-Za-z][_0-9A-Za-z]*@. If the given text does
88-
-- not match, return Nothing.
89-
--
90-
-- >>> makeName "foo"
91-
-- Right (Name {unName = "foo"})
92-
-- >>> makeName "9-bar"
93-
-- Left (NameError "9-bar")
94-
makeName :: Text -> Either NameError Name
95-
makeName name = first (const (NameError name)) (A.parseOnly nameParser name)
96-
97-
-- | An invalid name.
98-
newtype NameError = NameError Text deriving (Eq, Show)
99-
100-
101-
instance IsString Name where
102-
fromString = unsafeMakeName . toS
103-
104-
instance Aeson.ToJSON Name where
105-
toJSON = Aeson.toJSON . unName
106-
107-
instance Arbitrary Name where
108-
arbitrary = do
109-
initial <- elements alpha
110-
rest <- listOf (elements (alpha <> numeric))
111-
pure (Name (toS (initial:rest)))
112-
where
113-
alpha = ['A'..'Z'] <> ['a'..'z'] <> ['_']
114-
numeric = ['0'..'9']
115-
116-
-- | Parser for 'Name'.
117-
nameParser :: A.Parser Name
118-
nameParser = Name <$> tok ((<>) <$> A.takeWhile1 isA_z
119-
<*> A.takeWhile ((||) <$> isDigit <*> isA_z))
120-
where
121-
-- `isAlpha` handles many more Unicode Chars
122-
isA_z = A.inClass $ '_' : ['A'..'Z'] <> ['a'..'z']
55+
import GraphQL.Internal.Name (HasName(getName), Name(unName, Name), unsafeMakeName)
12356

12457
-- * Documents
12558

@@ -146,9 +79,8 @@ data OperationDefinition
14679
data Node = Node Name [VariableDefinition] [Directive] SelectionSet
14780
deriving (Eq,Show)
14881

149-
-- TODO: Just make Node implement HasName.
150-
getNodeName :: Node -> Name
151-
getNodeName (Node name _ _ _) = name
82+
instance HasName Node where
83+
getName (Node name _ _ _) = name
15284

15385
data VariableDefinition = VariableDefinition Variable Type (Maybe DefaultValue)
15486
deriving (Eq,Show)

src/GraphQL/Internal/Syntax/Encoder.hs

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import qualified Data.Aeson as Aeson
1010
import Data.Text (Text, cons, intercalate, pack, snoc)
1111

1212
import qualified GraphQL.Internal.Syntax.AST as AST
13+
import GraphQL.Internal.Name (unName)
1314

1415
-- * Document
1516

@@ -30,7 +31,7 @@ operationDefinition (AST.AnonymousQuery ss) = selectionSet ss
3031

3132
node :: AST.Node -> Text
3233
node (AST.Node name vds ds ss) =
33-
AST.unName name
34+
unName name
3435
<> optempty variableDefinitions vds
3536
<> optempty directives ds
3637
<> selectionSet ss
@@ -46,7 +47,7 @@ defaultValue :: AST.DefaultValue -> Text
4647
defaultValue val = "=" <> value val
4748

4849
variable :: AST.Variable -> Text
49-
variable (AST.Variable name) = "$" <> AST.unName name
50+
variable (AST.Variable name) = "$" <> unName name
5051

5152
selectionSet :: AST.SelectionSet -> Text
5253
selectionSet = bracesCommas selection
@@ -58,8 +59,8 @@ selection (AST.SelectionFragmentSpread x) = fragmentSpread x
5859

5960
field :: AST.Field -> Text
6061
field (AST.Field alias name args ds ss) =
61-
optempty (`snoc` ':') (maybe mempty AST.unName alias)
62-
<> AST.unName name
62+
optempty (`snoc` ':') (maybe mempty unName alias)
63+
<> unName name
6364
<> optempty arguments args
6465
<> optempty directives ds
6566
<> optempty selectionSet ss
@@ -68,17 +69,17 @@ arguments :: [AST.Argument] -> Text
6869
arguments = parensCommas argument
6970

7071
argument :: AST.Argument -> Text
71-
argument (AST.Argument name v) = AST.unName name <> ":" <> value v
72+
argument (AST.Argument name v) = unName name <> ":" <> value v
7273

7374
-- * Fragments
7475

7576
fragmentSpread :: AST.FragmentSpread -> Text
7677
fragmentSpread (AST.FragmentSpread name ds) =
77-
"..." <> AST.unName name <> optempty directives ds
78+
"..." <> unName name <> optempty directives ds
7879

7980
inlineFragment :: AST.InlineFragment -> Text
8081
inlineFragment (AST.InlineFragment (Just (AST.NamedType tc)) ds ss) =
81-
"... on " <> AST.unName tc
82+
"... on " <> unName tc
8283
<> optempty directives ds
8384
<> optempty selectionSet ss
8485
inlineFragment (AST.InlineFragment Nothing ds ss) =
@@ -87,7 +88,7 @@ inlineFragment (AST.InlineFragment Nothing ds ss) =
8788

8889
fragmentDefinition :: AST.FragmentDefinition -> Text
8990
fragmentDefinition (AST.FragmentDefinition name (AST.NamedType tc) ds ss) =
90-
"fragment " <> AST.unName name <> " on " <> AST.unName tc
91+
"fragment " <> unName name <> " on " <> unName tc
9192
<> optempty directives ds
9293
<> selectionSet ss
9394

@@ -101,7 +102,7 @@ value (AST.ValueInt x) = pack $ show x
101102
value (AST.ValueFloat x) = pack $ show x
102103
value (AST.ValueBoolean x) = booleanValue x
103104
value (AST.ValueString x) = stringValue x
104-
value (AST.ValueEnum x) = AST.unName x
105+
value (AST.ValueEnum x) = unName x
105106
value (AST.ValueList x) = listValue x
106107
value (AST.ValueObject x) = objectValue x
107108
value AST.ValueNull = "null"
@@ -121,31 +122,31 @@ objectValue :: AST.ObjectValue -> Text
121122
objectValue (AST.ObjectValue ofs) = bracesCommas objectField ofs
122123

123124
objectField :: AST.ObjectField -> Text
124-
objectField (AST.ObjectField name v) = AST.unName name <> ":" <> value v
125+
objectField (AST.ObjectField name v) = unName name <> ":" <> value v
125126

126127
-- * Directives
127128

128129
directives :: [AST.Directive] -> Text
129130
directives = spaces directive
130131

131132
directive :: AST.Directive -> Text
132-
directive (AST.Directive name args) = "@" <> AST.unName name <> optempty arguments args
133+
directive (AST.Directive name args) = "@" <> unName name <> optempty arguments args
133134

134135
-- * Type Reference
135136

136137
type_ :: AST.Type -> Text
137-
type_ (AST.TypeNamed (AST.NamedType x)) = AST.unName x
138+
type_ (AST.TypeNamed (AST.NamedType x)) = unName x
138139
type_ (AST.TypeList x) = listType x
139140
type_ (AST.TypeNonNull x) = nonNullType x
140141

141142
namedType :: AST.NamedType -> Text
142-
namedType (AST.NamedType name) = AST.unName name
143+
namedType (AST.NamedType name) = unName name
143144

144145
listType :: AST.ListType -> Text
145146
listType (AST.ListType ty) = brackets (type_ ty)
146147

147148
nonNullType :: AST.NonNullType -> Text
148-
nonNullType (AST.NonNullTypeNamed (AST.NamedType x)) = AST.unName x <> "!"
149+
nonNullType (AST.NonNullTypeNamed (AST.NamedType x)) = unName x <> "!"
149150
nonNullType (AST.NonNullTypeList x) = listType x <> "!"
150151

151152
typeDefinition :: AST.TypeDefinition -> Text
@@ -159,7 +160,7 @@ typeDefinition (AST.TypeDefinitionTypeExtension x) = typeExtensionDefinition x
159160

160161
objectTypeDefinition :: AST.ObjectTypeDefinition -> Text
161162
objectTypeDefinition (AST.ObjectTypeDefinition name ifaces fds) =
162-
"type " <> AST.unName name
163+
"type " <> unName name
163164
<> optempty (spaced . interfaces) ifaces
164165
<> optempty fieldDefinitions fds
165166

@@ -171,7 +172,7 @@ fieldDefinitions = bracesCommas fieldDefinition
171172

172173
fieldDefinition :: AST.FieldDefinition -> Text
173174
fieldDefinition (AST.FieldDefinition name args ty) =
174-
AST.unName name <> optempty argumentsDefinition args
175+
unName name <> optempty argumentsDefinition args
175176
<> ":"
176177
<> type_ ty
177178

@@ -180,36 +181,36 @@ argumentsDefinition = parensCommas inputValueDefinition
180181

181182
interfaceTypeDefinition :: AST.InterfaceTypeDefinition -> Text
182183
interfaceTypeDefinition (AST.InterfaceTypeDefinition name fds) =
183-
"interface " <> AST.unName name <> fieldDefinitions fds
184+
"interface " <> unName name <> fieldDefinitions fds
184185

185186
unionTypeDefinition :: AST.UnionTypeDefinition -> Text
186187
unionTypeDefinition (AST.UnionTypeDefinition name ums) =
187-
"union " <> AST.unName name <> "=" <> unionMembers ums
188+
"union " <> unName name <> "=" <> unionMembers ums
188189

189190
unionMembers :: [AST.NamedType] -> Text
190191
unionMembers = intercalate "|" . fmap namedType
191192

192193
scalarTypeDefinition :: AST.ScalarTypeDefinition -> Text
193-
scalarTypeDefinition (AST.ScalarTypeDefinition name) = "scalar " <> AST.unName name
194+
scalarTypeDefinition (AST.ScalarTypeDefinition name) = "scalar " <> unName name
194195

195196
enumTypeDefinition :: AST.EnumTypeDefinition -> Text
196197
enumTypeDefinition (AST.EnumTypeDefinition name evds) =
197-
"enum " <> AST.unName name
198+
"enum " <> unName name
198199
<> bracesCommas enumValueDefinition evds
199200

200201
enumValueDefinition :: AST.EnumValueDefinition -> Text
201-
enumValueDefinition (AST.EnumValueDefinition name) = AST.unName name
202+
enumValueDefinition (AST.EnumValueDefinition name) = unName name
202203

203204
inputObjectTypeDefinition :: AST.InputObjectTypeDefinition -> Text
204205
inputObjectTypeDefinition (AST.InputObjectTypeDefinition name ivds) =
205-
"input " <> AST.unName name <> inputValueDefinitions ivds
206+
"input " <> unName name <> inputValueDefinitions ivds
206207

207208
inputValueDefinitions :: [AST.InputValueDefinition] -> Text
208209
inputValueDefinitions = bracesCommas inputValueDefinition
209210

210211
inputValueDefinition :: AST.InputValueDefinition -> Text
211212
inputValueDefinition (AST.InputValueDefinition name ty dv) =
212-
AST.unName name <> ":" <> type_ ty <> maybe mempty defaultValue dv
213+
unName name <> ":" <> type_ ty <> maybe mempty defaultValue dv
213214

214215
typeExtensionDefinition :: AST.TypeExtensionDefinition -> Text
215216
typeExtensionDefinition (AST.TypeExtensionDefinition otd) =

0 commit comments

Comments
 (0)