{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# OPTIONS_HADDOCK not-home #-}
module GraphQL.Internal.Value.FromValue
( FromValue(..)
, prop_roundtripValue
, wrongType
) where
import Protolude hiding (TypeError)
import qualified Data.List.NonEmpty as NonEmpty
import GHC.Generics ((:*:)(..))
import GHC.TypeLits (KnownSymbol, TypeError, ErrorMessage(..))
import GHC.Types (Type)
import GraphQL.Internal.Name (nameFromSymbol)
import qualified GraphQL.Internal.OrderedMap as OM
import GraphQL.Internal.Value
import GraphQL.Internal.Value.ToValue (ToValue(..))
class FromValue a where
fromValue :: Value' ConstScalar -> Either Text a
default fromValue :: (Generic a, GenericFromValue (Rep a)) => Value' ConstScalar -> Either Text a
fromValue (ValueObject v) = to <$> genericFromValue v
fromValue v = wrongType "genericFromValue only works with objects." v
instance FromValue Int32 where
fromValue (ValueInt v) = pure v
fromValue v = wrongType "Int" v
instance FromValue Double where
fromValue (ValueFloat v) = pure v
fromValue v = wrongType "Double" v
instance FromValue Bool where
fromValue (ValueBoolean v) = pure v
fromValue v = wrongType "Bool" v
instance FromValue Text where
fromValue (ValueString (String v)) = pure v
fromValue v = wrongType "String" v
instance forall v. FromValue v => FromValue [v] where
fromValue (ValueList' (List' values)) = traverse (fromValue @v) values
fromValue v = wrongType "List" v
instance forall v. FromValue v => FromValue (NonEmpty v) where
fromValue (ValueList' (List' values)) =
case NonEmpty.nonEmpty values of
Nothing -> Left "Cannot construct NonEmpty from empty list"
Just values' -> traverse (fromValue @v) values'
fromValue v = wrongType "List" v
instance forall v. FromValue v => FromValue (Maybe v) where
fromValue ValueNull = pure Nothing
fromValue x = Just <$> fromValue @v x
prop_roundtripValue :: forall a. (Eq a, ToValue a, FromValue a) => a -> Bool
prop_roundtripValue x = fromValue (toValue x) == Right x
wrongType :: (MonadError Text m, Show a) => Text -> a -> m b
wrongType expected value = throwError ("Wrong type, should be: `" <> expected <> "` but is: `" <> show value <> "`")
class GenericFromValue (f :: Type -> Type) where
genericFromValue :: Object' ConstScalar -> Either Text (f p)
instance forall dataName consName records s l p.
( KnownSymbol dataName
, KnownSymbol consName
, GenericFromValue records
) => GenericFromValue (D1 ('MetaData dataName s l 'False)
(C1 ('MetaCons consName p 'True) records
)) where
genericFromValue o = M1 . M1 <$> genericFromValue @records o
instance forall l r.
( GenericFromValue l
, GenericFromValue r
) => GenericFromValue (l :*: r) where
genericFromValue object = liftA2 (:*:) (genericFromValue @l object) (genericFromValue @r object)
getValue :: forall wrappedType fieldName u s l p. (FromValue wrappedType, KnownSymbol fieldName)
=> Object' ConstScalar -> Either Text ((S1 ('MetaSel ('Just fieldName) u s l) (Rec0 wrappedType)) p)
getValue (Object' fieldMap) = do
fieldName <- case nameFromSymbol @fieldName of
Left err -> throwError ("invalid field name" <> show err)
Right name' -> pure name'
case OM.lookup fieldName fieldMap of
Nothing -> throwError ("Key not found: " <> show fieldName)
Just v -> M1 . K1 <$> fromValue @wrappedType v
instance forall wrappedType fieldName u s l.
( KnownSymbol fieldName
, FromValue wrappedType
) => GenericFromValue (S1 ('MetaSel ('Just fieldName) u s l) (Rec0 wrappedType)) where
genericFromValue = getValue @wrappedType @fieldName
instance forall l r m.
( TypeError ('Text "Generic fromValue only works for records with exactly one data constructor.")
) => GenericFromValue (D1 m (l :+: r)) where
genericFromValue = panic "genericFromValue cannot be called for records with more than one data constructor. Code that tries will not be compiled."