{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}
module Database.Persist.Class.PersistEntity
( PersistEntity (..)
, Update (..)
, BackendSpecificUpdate
, SelectOpt (..)
, Filter (..)
, FilterValue (..)
, BackendSpecificFilter
, Entity (..)
, recordName
, entityValues
, keyValueEntityToJSON, keyValueEntityFromJSON
, entityIdToJSON, entityIdFromJSON
, toPersistValueJSON, fromPersistValueJSON
, toPersistValueEnum, fromPersistValueEnum
) where
import Data.Aeson (ToJSON (..), FromJSON (..), fromJSON, object, (.:), (.=), Value (Object))
import qualified Data.Aeson.Parser as AP
import Data.Aeson.Types (Parser,Result(Error,Success))
import Data.Aeson.Text (encodeToTextBuilder)
import Data.Attoparsec.ByteString (parseOnly)
import qualified Data.HashMap.Strict as HM
import Data.Maybe (isJust)
import Data.Monoid (mappend)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Builder as TB
import Data.Typeable (Typeable)
import GHC.Generics
import Database.Persist.Class.PersistField
import Database.Persist.Types.Base
class ( PersistField (Key record), ToJSON (Key record), FromJSON (Key record)
, Show (Key record), Read (Key record), Eq (Key record), Ord (Key record))
=> PersistEntity record where
type PersistEntityBackend record
data Key record
keyToValues :: Key record -> [PersistValue]
keyFromValues :: [PersistValue] -> Either Text (Key record)
persistIdField :: EntityField record (Key record)
entityDef :: Monad m => m record -> EntityDef
data EntityField record :: * -> *
persistFieldDef :: EntityField record typ -> FieldDef
toPersistFields :: record -> [SomePersistField]
fromPersistValues :: [PersistValue] -> Either Text record
data Unique record
persistUniqueKeys :: record -> [Unique record]
persistUniqueToFieldNames :: Unique record -> [(HaskellName, DBName)]
persistUniqueToValues :: Unique record -> [PersistValue]
fieldLens :: EntityField record field
-> (forall f. Functor f => (field -> f field) -> Entity record -> f (Entity record))
type family BackendSpecificUpdate backend record
recordName
:: (PersistEntity record)
=> record -> Text
recordName = unHaskellName . entityHaskell . entityDef . Just
data Update record = forall typ. PersistField typ => Update
{ updateField :: EntityField record typ
, updateValue :: typ
, updateUpdate :: PersistUpdate
}
| BackendUpdate
(BackendSpecificUpdate (PersistEntityBackend record) record)
data SelectOpt record = forall typ. Asc (EntityField record typ)
| forall typ. Desc (EntityField record typ)
| OffsetBy Int
| LimitTo Int
type family BackendSpecificFilter backend record
data Filter record = forall typ. PersistField typ => Filter
{ filterField :: EntityField record typ
, filterValue :: FilterValue typ
, filterFilter :: PersistFilter
}
| FilterAnd [Filter record]
| FilterOr [Filter record]
| BackendFilter
(BackendSpecificFilter (PersistEntityBackend record) record)
data FilterValue typ where
FilterValue :: typ -> FilterValue typ
FilterValues :: [typ] -> FilterValue typ
UnsafeValue :: forall a typ. PersistField a => a -> FilterValue typ
data Entity record =
Entity { entityKey :: Key record
, entityVal :: record }
deriving Typeable
deriving instance (Generic (Key record), Generic record) => Generic (Entity record)
deriving instance (Eq (Key record), Eq record) => Eq (Entity record)
deriving instance (Ord (Key record), Ord record) => Ord (Entity record)
deriving instance (Show (Key record), Show record) => Show (Entity record)
deriving instance (Read (Key record), Read record) => Read (Entity record)
entityValues :: PersistEntity record => Entity record -> [PersistValue]
entityValues (Entity k record) =
if isJust (entityPrimary ent)
then
map toPersistValue (toPersistFields record)
else
keyToValues k ++ map toPersistValue (toPersistFields record)
where
ent = entityDef $ Just record
keyValueEntityToJSON :: (PersistEntity record, ToJSON record)
=> Entity record -> Value
keyValueEntityToJSON (Entity key value) = object
[ "key" .= key
, "value" .= value
]
keyValueEntityFromJSON :: (PersistEntity record, FromJSON record)
=> Value -> Parser (Entity record)
keyValueEntityFromJSON (Object o) = Entity
<$> o .: "key"
<*> o .: "value"
keyValueEntityFromJSON _ = fail "keyValueEntityFromJSON: not an object"
entityIdToJSON :: (PersistEntity record, ToJSON record) => Entity record -> Value
entityIdToJSON (Entity key value) = case toJSON value of
Object o -> Object $ HM.insert "id" (toJSON key) o
x -> x
entityIdFromJSON :: (PersistEntity record, FromJSON record) => Value -> Parser (Entity record)
entityIdFromJSON value@(Object o) = Entity <$> o .: "id" <*> parseJSON value
entityIdFromJSON _ = fail "entityIdFromJSON: not an object"
instance (PersistEntity record, PersistField record, PersistField (Key record))
=> PersistField (Entity record) where
toPersistValue (Entity key value) = case toPersistValue value of
(PersistMap alist) -> PersistMap ((idField, toPersistValue key) : alist)
_ -> error $ T.unpack $ errMsg "expected PersistMap"
fromPersistValue (PersistMap alist) = case after of
[] -> Left $ errMsg $ "did not find " `Data.Monoid.mappend` idField `mappend` " field"
("_id", kv):afterRest ->
fromPersistValue (PersistMap (before ++ afterRest)) >>= \record ->
keyFromValues [kv] >>= \k ->
Right (Entity k record)
_ -> Left $ errMsg $ "impossible id field: " `mappend` T.pack (show alist)
where
(before, after) = break ((== idField) . fst) alist
fromPersistValue x = Left $
errMsg "Expected PersistMap, received: " `mappend` T.pack (show x)
errMsg :: Text -> Text
errMsg = mappend "PersistField entity fromPersistValue: "
idField :: Text
idField = "_id"
toPersistValueJSON :: ToJSON a => a -> PersistValue
toPersistValueJSON = PersistText . LT.toStrict . TB.toLazyText . encodeToTextBuilder . toJSON
fromPersistValueJSON :: FromJSON a => PersistValue -> Either Text a
fromPersistValueJSON z = case z of
PersistByteString bs -> mapLeft (T.append "Could not parse the JSON (was a PersistByteString): ")
$ parseGo bs
PersistText t -> mapLeft (T.append "Could not parse the JSON (was PersistText): ")
$ parseGo (TE.encodeUtf8 t)
a -> Left $ T.append "Expected PersistByteString, received: " (T.pack (show a))
where parseGo bs = mapLeft T.pack $ case parseOnly AP.value bs of
Left err -> Left err
Right v -> case fromJSON v of
Error err -> Left err
Success a -> Right a
mapLeft _ (Right a) = Right a
mapLeft f (Left b) = Left (f b)
toPersistValueEnum :: Enum a => a -> PersistValue
toPersistValueEnum = toPersistValue . fromEnum
fromPersistValueEnum :: (Enum a, Bounded a) => PersistValue -> Either Text a
fromPersistValueEnum v = fromPersistValue v >>= go
where go i = let res = toEnum i in
if i >= fromEnum (asTypeOf minBound res) && i <= fromEnum (asTypeOf maxBound res)
then Right res
else Left ("The number " `mappend` T.pack (show i) `mappend` " was out of the "
`mappend` "allowed bounds for an enum type")