Skip to content

Commit 3c0af04

Browse files
authored
Merge pull request #134 from sunwukonga/todo/Node_implements_HasName
Make Node instance of HasName. Implement getName.
2 parents 2d81b80 + 04ae5a5 commit 3c0af04

File tree

4 files changed

+117
-123
lines changed

4 files changed

+117
-123
lines changed

src/GraphQL/Internal/Name.hs

+67-7
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 Data.Text as T (Text)
25+
import qualified Data.Attoparsec.Text as A
26+
import Test.QuickCheck (Arbitrary(..), elements, listOf)
27+
import Data.String (IsString(..))
28+
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 :: T.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 NameError.
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

+5-73
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)
60-
import Data.String (IsString(..))
51+
--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

+24-23
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)