{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts, StandaloneDeriving, UndecidableInstances #-}
module Database.Persist.Class.PersistEntity
( PersistEntity (..)
, Update (..)
, BackendSpecificUpdate
, SelectOpt (..)
, Filter (..)
, BackendSpecificFilter
, Entity (..)
, entityValues
, keyValueEntityToJSON, keyValueEntityFromJSON
, entityIdToJSON, entityIdFromJSON
, toPersistValueJSON, fromPersistValueJSON
, toPersistValueEnum, fromPersistValueEnum
) where
import Database.Persist.Types.Base
import Database.Persist.Class.PersistField
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.Aeson (ToJSON (..), FromJSON (..), fromJSON, object, (.:), (.=), Value (Object))
import qualified Data.Aeson.Parser as AP
import Data.Aeson.Types (Parser,Result(Error,Success))
#if MIN_VERSION_aeson(1,0,0)
import Data.Aeson.Text (encodeToTextBuilder)
#else
import Data.Aeson.Encode (encodeToTextBuilder)
#endif
import Data.Attoparsec.ByteString (parseOnly)
import Control.Applicative as A ((<$>), (<*>))
import Data.Monoid (mappend)
import qualified Data.HashMap.Strict as HM
import Data.Typeable (Typeable)
import Data.Maybe (isJust)
import GHC.Generics
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
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 :: Either typ [typ]
, filterFilter :: PersistFilter
}
| FilterAnd [Filter record]
| FilterOr [Filter record]
| BackendFilter
(BackendSpecificFilter (PersistEntityBackend record) record)
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, ToJSON (Key record))
=> Entity record -> Value
keyValueEntityToJSON (Entity key value) = object
[ "key" .= key
, "value" .= value
]
keyValueEntityFromJSON :: (PersistEntity record, FromJSON record, FromJSON (Key record))
=> Value -> Parser (Entity record)
keyValueEntityFromJSON (Object o) = Entity
A.<$> o .: "key"
A.<*> o .: "value"
keyValueEntityFromJSON _ = fail "keyValueEntityFromJSON: not an object"
entityIdToJSON :: (PersistEntity record, ToJSON record, ToJSON (Key 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, FromJSON (Key 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")