@@ -27,6 +27,8 @@ module GraphQL.Internal.API
27
27
, HasAnnotatedInputType
28
28
, HasObjectDefinition (.. )
29
29
, getArgumentDefinition
30
+ , SchemaError (.. )
31
+ , nameFromSymbol
30
32
-- | Exported for testing.
31
33
, getFieldDefinition
32
34
, getInterfaceDefinition
@@ -35,13 +37,16 @@ module GraphQL.Internal.API
35
37
36
38
import Protolude hiding (Enum , TypeError )
37
39
40
+ import qualified Data.List.NonEmpty as NonEmpty
38
41
import GHC.Generics ((:*:) (.. ))
39
42
import GHC.TypeLits (Symbol , KnownSymbol , TypeError , ErrorMessage (.. ))
40
43
import GHC.Types (Type )
41
44
42
45
import qualified GraphQL.Internal.Schema as Schema
43
- import GraphQL.Internal.Name (Name , NameError , nameFromSymbol )
46
+ import qualified GraphQL.Internal.Name as Name
47
+ import GraphQL.Internal.Name (Name , NameError )
44
48
import GraphQL.Internal.API.Enum (GraphQLEnum (.. ))
49
+ import GraphQL.Internal.Output (GraphQLError (.. ))
45
50
46
51
-- $setup
47
52
-- >>> :set -XDataKinds -XTypeOperators
@@ -83,6 +88,21 @@ data Field (name :: Symbol) (fieldType :: Type)
83
88
data Argument (name :: Symbol ) (argType :: Type )
84
89
85
90
91
+ -- | The type-level schema was somehow invalid.
92
+ data SchemaError
93
+ = NameError NameError
94
+ | EmptyFieldList
95
+ | EmptyUnion
96
+ deriving (Eq , Show )
97
+
98
+ instance GraphQLError SchemaError where
99
+ formatError (NameError err) = formatError err
100
+ formatError EmptyFieldList = " Empty field list in type definition"
101
+ formatError EmptyUnion = " Empty object list in union"
102
+
103
+ nameFromSymbol :: forall (n :: Symbol ). KnownSymbol n => Either SchemaError Name
104
+ nameFromSymbol = first NameError (Name. nameFromSymbol @ n )
105
+
86
106
-- | Specify a default value for a type in a GraphQL schema.
87
107
--
88
108
-- GraphQL schema can have default values in certain places. For example,
@@ -115,41 +135,52 @@ instance Defaultable (Maybe a) where
115
135
cons :: a -> [a ] -> [a ]
116
136
cons = (:)
117
137
138
+ singleton :: a -> NonEmpty a
139
+ singleton x = x :| []
140
+
118
141
-- Transform into a Schema definition
119
142
class HasObjectDefinition a where
120
143
-- Todo rename to getObjectTypeDefinition
121
- getDefinition :: Either NameError Schema. ObjectTypeDefinition
144
+ getDefinition :: Either SchemaError Schema. ObjectTypeDefinition
122
145
123
146
class HasFieldDefinition a where
124
- getFieldDefinition :: Either NameError Schema. FieldDefinition
147
+ getFieldDefinition :: Either SchemaError Schema. FieldDefinition
125
148
126
149
127
150
-- Fields
128
151
class HasFieldDefinitions a where
129
- getFieldDefinitions :: Either NameError [ Schema. FieldDefinition]
152
+ getFieldDefinitions :: Either SchemaError ( NonEmpty Schema. FieldDefinition)
130
153
131
154
instance forall a as . (HasFieldDefinition a , HasFieldDefinitions as ) => HasFieldDefinitions (a : as ) where
132
- getFieldDefinitions = cons <$> getFieldDefinition @ a <*> getFieldDefinitions @ as
155
+ getFieldDefinitions =
156
+ case getFieldDefinitions @ as of
157
+ Left EmptyFieldList -> singleton <$> getFieldDefinition @ a
158
+ Left err -> Left err
159
+ Right fields -> NonEmpty. cons <$> getFieldDefinition @ a <*> pure fields
133
160
134
161
instance HasFieldDefinitions '[] where
135
- getFieldDefinitions = pure []
162
+ getFieldDefinitions = Left EmptyFieldList
136
163
137
164
138
165
-- object types from union type lists, e.g. for
139
166
-- Union "Horse" '[Leg, Head, Tail]
140
167
-- ^ ^^^^^^^^^^^^^^^^^ this part
141
168
class HasUnionTypeObjectTypeDefinitions a where
142
- getUnionTypeObjectTypeDefinitions :: Either NameError [ Schema. ObjectTypeDefinition]
169
+ getUnionTypeObjectTypeDefinitions :: Either SchemaError ( NonEmpty Schema. ObjectTypeDefinition)
143
170
144
171
instance forall a as . (HasObjectDefinition a , HasUnionTypeObjectTypeDefinitions as ) => HasUnionTypeObjectTypeDefinitions (a : as ) where
145
- getUnionTypeObjectTypeDefinitions = cons <$> getDefinition @ a <*> getUnionTypeObjectTypeDefinitions @ as
172
+ getUnionTypeObjectTypeDefinitions =
173
+ case getUnionTypeObjectTypeDefinitions @ as of
174
+ Left EmptyUnion -> singleton <$> getDefinition @ a
175
+ Left err -> Left err
176
+ Right objects -> NonEmpty. cons <$> getDefinition @ a <*> pure objects
146
177
147
178
instance HasUnionTypeObjectTypeDefinitions '[] where
148
- getUnionTypeObjectTypeDefinitions = pure []
179
+ getUnionTypeObjectTypeDefinitions = Left EmptyUnion
149
180
150
181
-- Interfaces
151
182
class HasInterfaceDefinitions a where
152
- getInterfaceDefinitions :: Either NameError Schema. Interfaces
183
+ getInterfaceDefinitions :: Either SchemaError Schema. Interfaces
153
184
154
185
instance forall a as . (HasInterfaceDefinition a , HasInterfaceDefinitions as ) => HasInterfaceDefinitions (a : as ) where
155
186
getInterfaceDefinitions = cons <$> getInterfaceDefinition @ a <*> getInterfaceDefinitions @ as
@@ -158,12 +189,12 @@ instance HasInterfaceDefinitions '[] where
158
189
getInterfaceDefinitions = pure []
159
190
160
191
class HasInterfaceDefinition a where
161
- getInterfaceDefinition :: Either NameError Schema. InterfaceTypeDefinition
192
+ getInterfaceDefinition :: Either SchemaError Schema. InterfaceTypeDefinition
162
193
163
194
instance forall ks fields . (KnownSymbol ks , HasFieldDefinitions fields ) => HasInterfaceDefinition (Interface ks fields ) where
164
195
getInterfaceDefinition =
165
196
let name = nameFromSymbol @ ks
166
- fields = Schema. NonEmptyList <$> getFieldDefinitions @ fields
197
+ fields = getFieldDefinitions @ fields
167
198
in Schema. InterfaceTypeDefinition <$> name <*> fields
168
199
169
200
-- Give users some help if they don't terminate Arguments with a Field:
@@ -183,7 +214,7 @@ instance forall t ks. (KnownSymbol ks, HasAnnotatedType t) => HasFieldDefinition
183
214
in Schema. FieldDefinition <$> name <*> pure [] <*> getAnnotatedType @ t
184
215
185
216
class HasArgumentDefinition a where
186
- getArgumentDefinition :: Either NameError Schema. ArgumentDefinition
217
+ getArgumentDefinition :: Either SchemaError Schema. ArgumentDefinition
187
218
188
219
instance forall ks t . (KnownSymbol ks , HasAnnotatedInputType t ) => HasArgumentDefinition (Argument ks t ) where
189
220
getArgumentDefinition = Schema. ArgumentDefinition <$> argName <*> argType <*> defaultValue
@@ -205,7 +236,7 @@ instance forall ks is fields.
205
236
getDefinition =
206
237
let name = nameFromSymbol @ ks
207
238
interfaces = getInterfaceDefinitions @ is
208
- fields = Schema. NonEmptyList <$> getFieldDefinitions @ fields
239
+ fields = getFieldDefinitions @ fields
209
240
in Schema. ObjectTypeDefinition <$> name <*> interfaces <*> fields
210
241
211
242
-- Builtin output types (annotated types)
@@ -215,7 +246,7 @@ class HasAnnotatedType a where
215
246
-- forget this. Maybe we can flip the internal encoding to be
216
247
-- non-null by default and needing explicit null-encoding (via
217
248
-- Maybe).
218
- getAnnotatedType :: Either NameError (Schema. AnnotatedType Schema. GType )
249
+ getAnnotatedType :: Either SchemaError (Schema. AnnotatedType Schema. GType )
219
250
220
251
-- | Turn a non-null type into the optional version of its own type.
221
252
dropNonNull :: Schema. AnnotatedType t -> Schema. AnnotatedType t
@@ -228,7 +259,7 @@ instance forall a. HasAnnotatedType a => HasAnnotatedType (Maybe a) where
228
259
-- see TODO in HasAnnotatedType class
229
260
getAnnotatedType = dropNonNull <$> getAnnotatedType @ a
230
261
231
- builtinType :: Schema. Builtin -> Either NameError (Schema. AnnotatedType Schema. GType )
262
+ builtinType :: Schema. Builtin -> Either SchemaError (Schema. AnnotatedType Schema. GType )
232
263
builtinType = pure . Schema. TypeNonNull . Schema. NonNullTypeNamed . Schema. BuiltinType
233
264
234
265
-- TODO(jml): Given that AnnotatedType is parametrised, we can probably reduce
@@ -263,13 +294,13 @@ instance forall ks enum. (KnownSymbol ks, GraphQLEnum enum) => HasAnnotatedType
263
294
getAnnotatedType = do
264
295
let name = nameFromSymbol @ ks
265
296
let enums = sequenceA (enumValues @ enum ) :: Either NameError [Schema. Name ]
266
- let et = Schema. EnumTypeDefinition <$> name <*> map (map Schema. EnumValueDefinition ) enums
297
+ let et = Schema. EnumTypeDefinition <$> name <*> map (map Schema. EnumValueDefinition ) (first NameError enums)
267
298
Schema. TypeNonNull . Schema. NonNullTypeNamed . Schema. DefinedType . Schema. TypeDefinitionEnum <$> et
268
299
269
300
instance forall ks as . (KnownSymbol ks , HasUnionTypeObjectTypeDefinitions as ) => HasAnnotatedType (Union ks as ) where
270
301
getAnnotatedType =
271
302
let name = nameFromSymbol @ ks
272
- types = Schema. NonEmptyList <$> getUnionTypeObjectTypeDefinitions @ as
303
+ types = getUnionTypeObjectTypeDefinitions @ as
273
304
in (Schema. TypeNamed . Schema. DefinedType . Schema. TypeDefinitionUnion ) <$> (Schema. UnionTypeDefinition <$> name <*> types)
274
305
275
306
-- Help users with better type errors
@@ -281,14 +312,14 @@ instance TypeError ('Text "Cannot encode Integer because it has arbitrary size b
281
312
-- Builtin input types
282
313
class HasAnnotatedInputType a where
283
314
-- See TODO comment in "HasAnnotatedType" class for nullability.
284
- getAnnotatedInputType :: Either NameError (Schema. AnnotatedType Schema. InputType )
285
- default getAnnotatedInputType :: (Generic a , GenericAnnotatedInputType (Rep a )) => Either NameError (Schema. AnnotatedType Schema. InputType )
315
+ getAnnotatedInputType :: Either SchemaError (Schema. AnnotatedType Schema. InputType )
316
+ default getAnnotatedInputType :: (Generic a , GenericAnnotatedInputType (Rep a )) => Either SchemaError (Schema. AnnotatedType Schema. InputType )
286
317
getAnnotatedInputType = genericGetAnnotatedInputType @ (Rep a )
287
318
288
319
instance forall a . HasAnnotatedInputType a => HasAnnotatedInputType (Maybe a ) where
289
320
getAnnotatedInputType = dropNonNull <$> getAnnotatedInputType @ a
290
321
291
- builtinInputType :: Schema. Builtin -> Either NameError (Schema. AnnotatedType Schema. InputType )
322
+ builtinInputType :: Schema. Builtin -> Either SchemaError (Schema. AnnotatedType Schema. InputType )
292
323
builtinInputType = pure . Schema. TypeNonNull . Schema. NonNullTypeNamed . Schema. BuiltinInputType
293
324
294
325
instance HasAnnotatedInputType Int where
@@ -316,16 +347,16 @@ instance forall ks enum. (KnownSymbol ks, GraphQLEnum enum) => HasAnnotatedInput
316
347
getAnnotatedInputType = do
317
348
let name = nameFromSymbol @ ks
318
349
enums = sequenceA (enumValues @ enum ) :: Either NameError [Schema. Name ]
319
- let et = Schema. EnumTypeDefinition <$> name <*> map (map Schema. EnumValueDefinition ) enums
350
+ let et = Schema. EnumTypeDefinition <$> name <*> map (map Schema. EnumValueDefinition ) (first NameError enums)
320
351
Schema. TypeNonNull . Schema. NonNullTypeNamed . Schema. DefinedInputType . Schema. InputTypeDefinitionEnum <$> et
321
352
322
353
323
354
-- Generic getAnnotatedInputType function
324
355
class GenericAnnotatedInputType (f :: Type -> Type ) where
325
- genericGetAnnotatedInputType :: Either NameError (Schema. AnnotatedType Schema. InputType )
356
+ genericGetAnnotatedInputType :: Either SchemaError (Schema. AnnotatedType Schema. InputType )
326
357
327
358
class GenericInputObjectFieldDefinitions (f :: Type -> Type ) where
328
- genericGetInputObjectFieldDefinitions :: Either NameError [ Schema. InputObjectFieldDefinition]
359
+ genericGetInputObjectFieldDefinitions :: Either SchemaError ( NonEmpty Schema. InputObjectFieldDefinition)
329
360
330
361
instance forall dataName consName records s l p .
331
362
( KnownSymbol dataName
@@ -341,7 +372,6 @@ instance forall dataName consName records s l p.
341
372
. Schema. DefinedInputType
342
373
. Schema. InputTypeDefinitionObject
343
374
. Schema. InputObjectTypeDefinition name
344
- . Schema. NonEmptyList
345
375
) (genericGetInputObjectFieldDefinitions @ records )
346
376
347
377
instance forall wrappedType fieldName rest u s l .
@@ -354,7 +384,7 @@ instance forall wrappedType fieldName rest u s l.
354
384
annotatedInputType <- getAnnotatedInputType @ wrappedType
355
385
let l = Schema. InputObjectFieldDefinition name annotatedInputType Nothing
356
386
r <- genericGetInputObjectFieldDefinitions @ rest
357
- pure (l : r)
387
+ pure (NonEmpty. cons l r)
358
388
359
389
instance forall wrappedType fieldName u s l .
360
390
( KnownSymbol fieldName
@@ -364,4 +394,4 @@ instance forall wrappedType fieldName u s l.
364
394
name <- nameFromSymbol @ fieldName
365
395
annotatedInputType <- getAnnotatedInputType @ wrappedType
366
396
let l = Schema. InputObjectFieldDefinition name annotatedInputType Nothing
367
- pure [l]
397
+ pure (l :| [] )
0 commit comments