-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathResolver.hs
519 lines (463 loc) · 22.4 KB
/
Resolver.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-} -- nicer type errors in some cases
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-} -- for TypeError
{-# OPTIONS_HADDOCK not-home #-}
-- | Description: Implement handlers for GraphQL schemas
module GraphQL.Internal.Resolver
( ResolverError(..)
, HasResolver(..)
, (:<>)(..)
, Result(..)
, unionValue
) where
-- TODO (probably incomplete, the spec is large)
-- - input objects - I'm not super clear from the spec on how
-- they differ from normal objects.
-- - "extend type X" is used in examples in the spec but it's not
-- explained anywhere?
-- - Directives (https://facebook.github.io/graphql/#sec-Type-System.Directives)
-- - Enforce non-empty lists (might only be doable via value-level validation)
import Protolude hiding (Enum, TypeError, throwE)
import qualified Data.Text as Text
import qualified Data.List.NonEmpty as NonEmpty
import GHC.TypeLits (KnownSymbol, TypeError, ErrorMessage(..), Symbol, symbolVal)
import GHC.Types (Type)
import qualified GHC.Exts (Any)
import Unsafe.Coerce (unsafeCoerce)
import GraphQL.Internal.API
( HasAnnotatedType(..)
, HasAnnotatedInputType(..)
, (:>)
)
import qualified GraphQL.Internal.API as API
import qualified GraphQL.Value as GValue
import GraphQL.Value
( Value
, pattern ValueEnum
, FromValue(..)
, ToValue(..)
)
import GraphQL.Internal.Name (Name, HasName(..), unName)
import qualified GraphQL.Internal.OrderedMap as OrderedMap
import GraphQL.Internal.Schema (ObjectTypeDefinition(..))
import GraphQL.Internal.Output (GraphQLError(..))
import GraphQL.Internal.Validation
( SelectionSetByType
, SelectionSet(..)
, Field
, ValidationErrors
, getSubSelectionSet
, getSelectionSetForType
, lookupArgument
)
data ResolverError
-- | There was a problem in the schema. Server-side problem.
= SchemaError API.SchemaError
-- | Couldn't find the requested field in the object. A client-side problem.
| FieldNotFoundError Name
-- | No value provided for name, and no default specified. Client-side problem.
| ValueMissing Name
-- | Could not translate value into Haskell. Probably a client-side problem.
| InvalidValue Name Text
-- | Found validation errors when we tried to merge fields.
| ValidationError ValidationErrors
-- | Tried to get subselection of leaf field.
| SubSelectionOnLeaf (SelectionSetByType Value)
-- | Tried to treat an object as a leaf.
| MissingSelectionSet
deriving (Show, Eq)
instance GraphQLError ResolverError where
formatError (SchemaError e) =
"Schema error: " <> formatError e
formatError (FieldNotFoundError field) =
"Field not supported by the API: " <> show field
formatError (ValueMissing name) =
"No value provided for " <> show name <> ", and no default specified."
formatError (InvalidValue name text) =
"Could not coerce " <> show name <> " to valid value: " <> text
formatError (ValidationError errs) =
"Validation errors: " <> Text.intercalate ", " (map formatError (NonEmpty.toList errs))
formatError (SubSelectionOnLeaf ss) =
"Tried to get values within leaf field: " <> show ss
formatError MissingSelectionSet =
"Tried to treat object as if it were leaf field."
-- | Object field separation operator.
--
-- Use this to provide handlers for fields of an object.
--
-- Say you had the following GraphQL type with \"foo\" and \"bar\" fields,
-- e.g.
--
-- @
-- type MyObject {
-- foo: Int!
-- bar: String!
-- }
-- @
--
-- You could provide handlers for it like this:
--
-- >>> :m +System.Environment
-- >>> let fooHandler = pure 42
-- >>> let barHandler = System.Environment.getProgName
-- >>> let myObjectHandler = pure $ fooHandler :<> barHandler :<> ()
data a :<> b = a :<> b
infixr 8 :<>
-- Result collects errors and values at the same time unless a handler
-- tells us to bail out in which case we stop the processing
-- immediately.
data Result a = Result [ResolverError] a deriving (Show, Functor, Eq)
-- Aggregating results keeps all errors and creates a ValueList
-- containing the individual values.
aggregateResults :: [Result Value] -> Result Value
aggregateResults r = toValue <$> sequenceA r
throwE :: Applicative f => ResolverError -> f (Result Value)
throwE err = pure (Result [err] GValue.ValueNull)
instance Applicative Result where
pure v = Result [] v
(Result e1 f) <*> (Result e2 x) = Result (e1 <> e2) (f x)
ok :: Value -> Result Value
ok = pure
class HasResolver m a where
type Handler m a
resolve :: Handler m a -> Maybe (SelectionSetByType Value) -> m (Result Value)
-- | Called when the schema expects an input argument @name@ of type @a@ but
-- @name@ has not been provided.
valueMissing :: API.Defaultable a => Name -> Either ResolverError a
valueMissing name = maybe (Left (ValueMissing name)) Right (API.defaultFor name)
instance forall m. (Applicative m) => HasResolver m Int32 where
type Handler m Int32 = m Int32
resolve handler Nothing = map (ok . toValue) handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Double where
type Handler m Double = m Double
resolve handler Nothing = map (ok . toValue) handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Text where
type Handler m Text = m Text
resolve handler Nothing = map (ok . toValue) handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Bool where
type Handler m Bool = m Bool
resolve handler Nothing = map (ok . toValue) handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m hg. (Monad m, Applicative m, HasResolver m hg) => HasResolver m (API.List hg) where
type Handler m (API.List hg) = m [Handler m hg]
resolve handler selectionSet = do
h <- handler
let a = traverse (flip (resolve @m @hg) selectionSet) h
map aggregateResults a
instance forall m ksN enum. (Applicative m, API.GraphQLEnum enum) => HasResolver m (API.Enum ksN enum) where
type Handler m (API.Enum ksN enum) = m enum
resolve handler Nothing = map (ok . GValue.ValueEnum . API.enumToValue) handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m hg. (HasResolver m hg, Monad m) => HasResolver m (Maybe hg) where
type Handler m (Maybe hg) = m (Maybe (Handler m hg))
resolve handler selectionSet = do
result <- handler
case result of
Just x -> resolve @m @hg (x :: Handler m hg) selectionSet
Nothing -> (pure . ok) GValue.ValueNull
-- TODO: A parametrized `Result` is really not a good way to handle the
-- "result" for resolveField, but not sure what to use either. Tom liked the
-- tuple we had before more because it didn't imply any other structure or
-- meaning. Maybe we can just create a new datatype. jml thinks we should
-- extract some helpful generic monad, ala `Validator`.
-- <https://github.com/jml/graphql-api/issues/98>
type ResolveFieldResult = Result (Maybe GValue.Value)
-- Extract field name from an argument type. TODO: ideally we'd run
-- this directly on the "a :> b" argument structure, but that requires
-- passing in the plain argument structure type into resolveField or
-- resolving "name" in the buildFieldResolver. Both options duplicate
-- code somwehere else.
type family FieldName (a :: Type) = (r :: Symbol) where
FieldName (JustHandler (API.Field name t)) = name
FieldName (PlainArgument a f) = FieldName f
FieldName (EnumArgument a f) = FieldName f
FieldName x = TypeError ('Text "Unexpected branch in FieldName type family. Please file a bug!" ':<>: 'ShowType x)
resolveField :: forall dispatchType (m :: Type -> Type).
( BuildFieldResolver m dispatchType
, Monad m
, KnownSymbol (FieldName dispatchType)
)
=> FieldHandler m dispatchType
-> m ResolveFieldResult
-> ObjectTypeDefinition
-> Field Value
-> m ResolveFieldResult
resolveField handler nextHandler defn field =
-- check name before
case API.nameFromSymbol @(FieldName dispatchType) of
Left err -> pure (Result [SchemaError err] (Just GValue.ValueNull))
Right name'
| getName field == name' ->
case buildFieldResolver @m @dispatchType handler field of
Left err -> pure (Result [err] (Just GValue.ValueNull))
Right resolver -> do
Result errs value <- resolver
pure (Result errs (Just value))
| getName field == "__typename" ->
pure $ Result [] (Just $ GValue.ValueString $ GValue.String $ unName $ getName defn)
| otherwise -> nextHandler
-- We're using our usual trick of rewriting a type in a closed type
-- family to emulate a closed typeclass. The following are the
-- universe of "allowed" class instances for field types:
data JustHandler a
data EnumArgument a b
data PlainArgument a b
-- injective helps with errors sometimes
type family FieldResolverDispatchType (a :: Type) = (r :: Type) | r -> a where
FieldResolverDispatchType (API.Field ksA t) = JustHandler (API.Field ksA t)
FieldResolverDispatchType (API.Argument ksB (API.Enum name t) :> f) = EnumArgument (API.Argument ksB (API.Enum name t)) (FieldResolverDispatchType f)
FieldResolverDispatchType (API.Argument ksC t :> f) = PlainArgument (API.Argument ksC t) (FieldResolverDispatchType f)
-- | Derive the handler type from the Field/Argument type in a closed
-- type family: We don't want anyone else to extend this ever.
type family FieldHandler (m :: Type -> Type) (a :: Type) = (r :: Type) where
FieldHandler m (JustHandler (API.Field ksD t)) = Handler m t
FieldHandler m (PlainArgument (API.Argument ksE t) f) = t -> FieldHandler m f
FieldHandler m (EnumArgument (API.Argument ksF (API.Enum name t)) f) = t -> FieldHandler m f
class BuildFieldResolver m fieldResolverType where
buildFieldResolver :: FieldHandler m fieldResolverType -> Field Value -> Either ResolverError (m (Result Value))
instance forall ksG t m.
( KnownSymbol ksG, HasResolver m t, HasAnnotatedType t, Monad m
) => BuildFieldResolver m (JustHandler (API.Field ksG t)) where
buildFieldResolver handler field = do
pure (resolve @m @t handler (getSubSelectionSet field))
instance forall ksH t f m.
( KnownSymbol ksH
, BuildFieldResolver m f
, FromValue t
, API.Defaultable t
, HasAnnotatedInputType t
, Monad m
) => BuildFieldResolver m (PlainArgument (API.Argument ksH t) f) where
buildFieldResolver handler field = do
argument <- first SchemaError (API.getArgumentDefinition @(API.Argument ksH t))
let argName = getName argument
value <- case lookupArgument field argName of
Nothing -> valueMissing @t argName
Just v -> first (InvalidValue argName) (fromValue @t v)
buildFieldResolver @m @f (handler value) field
instance forall ksK t f m name.
( KnownSymbol ksK
, BuildFieldResolver m f
, KnownSymbol name
, API.Defaultable t
, API.GraphQLEnum t
, Monad m
) => BuildFieldResolver m (EnumArgument (API.Argument ksK (API.Enum name t)) f) where
buildFieldResolver handler field = do
argName <- first SchemaError (API.nameFromSymbol @ksK)
value <- case lookupArgument field argName of
Nothing -> valueMissing @t argName
Just (ValueEnum enum) -> first (InvalidValue argName) (API.enumFromValue @t enum)
Just value -> Left (InvalidValue argName (show value <> " not an enum: " <> show (API.enumValues @t)))
buildFieldResolver @m @f (handler value) field
-- Note that we enumerate all ks variables with capital letters so we
-- can figure out error messages like the following that don't come
-- with line numbers:
--
-- • No instance for (GHC.TypeLits.KnownSymbol ks0)
-- arising from a use of ‘interpretAnonymousQuery’
-- We only allow Field and Argument :> Field combinations:
type family RunFieldsType (m :: Type -> Type) (a :: [Type]) = (r :: Type) where
RunFieldsType m '[API.Field ksI t] = API.Field ksI t
RunFieldsType m '[a :> b] = a :> b
RunFieldsType m ((API.Field ksJ t) ': rest) = API.Field ksJ t :<> RunFieldsType m rest
RunFieldsType m ((a :> b) ': rest) = (a :> b) :<> RunFieldsType m rest
RunFieldsType m a = TypeError (
'Text "All field entries in an Object must be Field or Argument :> Field. Got: " ':<>: 'ShowType a)
-- Match the three possible cases for Fields (see also RunFieldsType)
type family RunFieldsHandler (m :: Type -> Type) (a :: Type) = (r :: Type) where
RunFieldsHandler m (f :<> fs) = FieldHandler m (FieldResolverDispatchType f) :<> RunFieldsHandler m fs
RunFieldsHandler m (API.Field ksL t) = FieldHandler m (FieldResolverDispatchType (API.Field ksL t))
RunFieldsHandler m (a :> b) = FieldHandler m (FieldResolverDispatchType (a :> b))
RunFieldsHandler m a = TypeError (
'Text "Unexpected RunFieldsHandler types: " ':<>: 'ShowType a)
class RunFields m a where
-- | Run a single 'Selection' over all possible fields (as specified by the
-- type @a@), returning exactly one 'GValue.ObjectField' when a field
-- matches, or an error otherwise.
--
-- Individual implementations are responsible for calling 'runFields' if
-- they haven't matched the field and there are still candidate fields
-- within the handler.
runFields :: RunFieldsHandler m a -> ObjectTypeDefinition -> Field Value -> m ResolveFieldResult
instance forall f fs m dispatchType.
( BuildFieldResolver m dispatchType
, dispatchType ~ FieldResolverDispatchType f
, RunFields m fs
, KnownSymbol (FieldName dispatchType)
, Monad m
) => RunFields m (f :<> fs) where
runFields (handler :<> nextHandlers) defn field =
resolveField @dispatchType @m handler nextHandler defn field
where
nextHandler = runFields @m @fs nextHandlers defn field
instance forall ksM t m dispatchType.
( BuildFieldResolver m dispatchType
, KnownSymbol ksM
, dispatchType ~ FieldResolverDispatchType (API.Field ksM t)
, Monad m
) => RunFields m (API.Field ksM t) where
runFields handler defn field =
resolveField @dispatchType @m handler nextHandler defn field
where
nextHandler = pure (Result [FieldNotFoundError (getName field)] Nothing)
instance forall m a b dispatchType.
( BuildFieldResolver m dispatchType
, dispatchType ~ FieldResolverDispatchType (a :> b)
, KnownSymbol (FieldName dispatchType)
, Monad m
) => RunFields m (a :> b) where
runFields handler defn field =
resolveField @dispatchType @m handler nextHandler defn field
where
nextHandler = pure (Result [FieldNotFoundError (getName field)] Nothing)
instance forall typeName interfaces fields m.
( RunFields m (RunFieldsType m fields)
, API.HasObjectDefinition (API.Object typeName interfaces fields)
, Monad m
) => HasResolver m (API.Object typeName interfaces fields) where
type Handler m (API.Object typeName interfaces fields) = m (RunFieldsHandler m (RunFieldsType m fields))
resolve _ Nothing = throwE MissingSelectionSet
resolve mHandler (Just selectionSet) =
case getSelectionSet of
Left err -> throwE err
Right (ss, defn) -> do
-- Run the handler so the field resolvers have access to the object.
-- This (and other places, including field resolvers) is where user
-- code can do things like look up something in a database.
handler <- mHandler
r <- traverse (runFields @m @(RunFieldsType m fields) handler defn) ss
let (Result errs obj) = GValue.objectFromOrderedMap . OrderedMap.catMaybes <$> sequenceA r
pure (Result errs (GValue.ValueObject obj))
where
getSelectionSet = do
defn <- first SchemaError $ API.getDefinition @(API.Object typeName interfaces fields)
-- Fields of a selection set may be behind "type conditions", due to
-- inline fragments or the use of fragment spreads. These type
-- conditions are represented in the schema by the name of a type
-- (e.g. "Dog"). To determine which type conditions (and thus which
-- fields) are relevant for this 1selection set, we need to look up the
-- actual types they refer to, as interfaces (say) match objects
-- differently than unions.
--
-- See <https://facebook.github.io/graphql/#sec-Field-Collection> for
-- more details.
(SelectionSet ss') <- first ValidationError $ getSelectionSetForType defn selectionSet
pure (ss', defn)
-- TODO(tom): we're getting to a point where it might make sense to
-- split resolver into submodules (GraphQL.Resolver.Union etc.)
-- | For unions we need a way to have type-safe, open sum types based
-- on the possible 'API.Object's of a union. The following closed type
-- family selects one Object from the union and returns the matching
-- 'HasResolver' 'Handler' type. If the object @o@ is not a member of
-- 'API.Union' then the user code won't compile.
--
-- This type family is an implementation detail but its TypeError
-- messages are visible at compile time.
type family TypeIndex (m :: Type -> Type) (object :: Type) (union :: Type) = (result :: Type) where
TypeIndex m (API.Object name interfaces fields) (API.Union uName (API.Object name interfaces fields:_)) =
Handler m (API.Object name interfaces fields)
TypeIndex m (API.Object name interfaces fields) (API.Union uName (API.Object name' i' f':objects)) =
TypeIndex m (API.Object name interfaces fields) (API.Union uName objects)
-- Slightly nicer type errors:
TypeIndex _ (API.Object name interfaces fields) (API.Union uName '[]) =
TypeError ('Text "Type not found in union definition: " ':<>: 'ShowType (API.Object name interfaces fields))
TypeIndex _ (API.Object name interfaces fields) x =
TypeError ('Text "3rd type must be a union but it is: " ':<>: 'ShowType x)
TypeIndex _ o _ =
TypeError ('Text "Invalid TypeIndex. Must be Object but got: " ':<>: 'ShowType o)
-- | The 'Handler' type of a 'API.Union' must be the same for all
-- possible Objects, but each Object has a different type. We
-- unsafeCoerce the return type into an Any, tagging it with the union
-- and the underlying monad for type safety, but we elide the Object
-- type itself. This way we can represent all 'Handler' types of the
-- Union with a single type and still stay type-safe.
type role DynamicUnionValue representational representational
data DynamicUnionValue (union :: Type) (m :: Type -> Type) = DynamicUnionValue { _label :: Text, _value :: GHC.Exts.Any }
class RunUnion m union objects where
runUnion :: DynamicUnionValue union m -> SelectionSetByType Value -> m (Result Value)
instance forall m union objects name interfaces fields.
( Monad m
, KnownSymbol name
, TypeIndex m (API.Object name interfaces fields) union ~ Handler m (API.Object name interfaces fields)
, RunFields m (RunFieldsType m fields)
, API.HasObjectDefinition (API.Object name interfaces fields)
, RunUnion m union objects
) => RunUnion m union (API.Object name interfaces fields:objects) where
runUnion duv selectionSet =
case extractUnionValue @(API.Object name interfaces fields) @union @m duv of
Just handler -> resolve @m @(API.Object name interfaces fields) handler (Just selectionSet)
Nothing -> runUnion @m @union @objects duv selectionSet
-- AFAICT it should not be possible to ever hit the empty case because
-- the compiler doesn't allow constructing a unionValue that's not in
-- the Union. If the following code ever gets executed it's almost
-- certainly a bug in the union code.
--
-- We still need to implement this instance for the compiler because
-- it exhaustively checks all cases when deconstructs the Union.
instance forall m union. RunUnion m union '[] where
runUnion (DynamicUnionValue label _) selection =
panic ("Unexpected branch in runUnion, got " <> show selection <> " for label " <> label <> ". Please file a bug.")
instance forall m unionName objects.
( Monad m
, KnownSymbol unionName
, RunUnion m (API.Union unionName objects) objects
) => HasResolver m (API.Union unionName objects) where
type Handler m (API.Union unionName objects) = m (DynamicUnionValue (API.Union unionName objects) m)
resolve _ Nothing = throwE MissingSelectionSet
resolve mHandler (Just selectionSet) = do
duv <- mHandler
runUnion @m @(API.Union unionName objects) @objects duv selectionSet
symbolText :: forall ks. KnownSymbol ks => Text
symbolText = toS (symbolVal @ks Proxy)
-- | Translate a 'Handler' into a DynamicUnionValue type required by
-- 'Union' handlers. This is dynamic, but nevertheless type-safe
-- because we can only tag with types that are part of the union.
--
-- Use e.g. like "unionValue @Cat" if you have an object like this:
--
-- >>> type Cat = API.Object "Cat" '[] '[API.Field "name" Text]
--
-- and then use `unionValue @Cat (pure (pure "Felix"))`. See
-- `examples/UnionExample.hs` for more code.
unionValue ::
forall (object :: Type) (union :: Type) m (name :: Symbol) interfaces fields.
(Monad m, API.Object name interfaces fields ~ object, KnownSymbol name)
=> TypeIndex m object union -> m (DynamicUnionValue union m)
unionValue x =
-- TODO(tom) - we might want to move to Typeable `cast` for uValue
-- instead of doing our own unsafeCoerce because it comes with
-- additional safety guarantees: Typerep is unforgeable, while we
-- can still into a bad place by matching on name only. We can't
-- actually segfault this because right now we walk the list of
-- objects in a union left-to-right so in case of duplicate names we
-- only every see one type. That doesn't seen like a great thing to
-- rely on though!
-- Note that unsafeCoerce is safe because we index the type from the
-- union with an 'API.Object' whose name we're storing in label. On
-- the way out we check that the name is the same, and we know the
-- type universe is the same because we annotated DynamicUnionValue
-- with the type universe.
pure (DynamicUnionValue (symbolText @name) (unsafeCoerce x))
extractUnionValue ::
forall (object :: Type) (union :: Type) m (name :: Symbol) interfaces fields.
(API.Object name interfaces fields ~ object, KnownSymbol name)
=> DynamicUnionValue union m -> Maybe (TypeIndex m object union)
extractUnionValue (DynamicUnionValue uName uValue) =
if uName == symbolText @name
then Just (unsafeCoerce uValue)
else Nothing