{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveTraversable, TypeFamilies, OverloadedStrings #-}
module Data.Greskell.PMap
(
PMap,
lookup,
lookupM,
lookupAs,
lookupAs',
lookupAsM,
lookupList,
lookupListAs,
lookupListAs',
pMapInsert,
pMapDelete,
pMapLookup,
pMapToList,
pMapFromList,
Single,
Multi,
PMapKey(..),
PMapLookupException(..),
pMapDecribeError,
pMapToThrow,
pMapToFail
) where
import Prelude hiding (lookup)
import Control.Exception (Exception)
import Control.Monad.Catch (MonadThrow(..), MonadCatch(..))
import Control.Monad.Fail (MonadFail)
import Data.Aeson.Types (Parser)
import qualified Data.Foldable as F
import Data.Functor.Identity (Identity)
import Data.Greskell.AsIterator (AsIterator(..))
import Data.Greskell.GMap (GMapEntry)
import Data.Greskell.GraphSON (GValue, GraphSONTyped(..), FromGraphSON(..), parseEither)
import qualified Data.HashMap.Strict as HM
import Data.List.NonEmpty (NonEmpty((:|)))
import Data.Maybe (listToMaybe)
import Data.Monoid (Monoid(..))
import Data.Semigroup (Semigroup((<>)))
import qualified Data.Semigroup as S
import Data.Traversable (Traversable(traverse))
import Data.Text (Text, unpack)
import Data.Greskell.NonEmptyLike (NonEmptyLike)
import qualified Data.Greskell.NonEmptyLike as NEL
newtype PMap c v = PMap (HM.HashMap Text (c v))
deriving (Show,Eq,Functor,Foldable,Traversable)
instance GraphSONTyped (PMap c v) where
gsonTypeFor _ = "g:Map"
instance FromGraphSON (c v) => FromGraphSON (PMap c v) where
parseGraphSON gv = fmap PMap $ parseGraphSON gv
instance NonEmptyLike c => Semigroup (PMap c v) where
(PMap a) <> (PMap b) = PMap (HM.unionWith NEL.append a b)
instance NonEmptyLike c => Monoid (PMap c v) where
mempty = PMap $ HM.empty
mappend = (<>)
instance AsIterator (PMap c v) where
type IteratorItem (PMap c v) = GMapEntry Text (c v)
pMapInsert :: NonEmptyLike c => Text -> v -> PMap c v -> PMap c v
pMapInsert k v (PMap hm) = PMap $ HM.insertWith NEL.append k (NEL.singleton v) hm
pMapDelete :: Text -> PMap c v -> PMap c v
pMapDelete k (PMap hm) = PMap $ HM.delete k hm
pMapLookup :: NonEmptyLike c => Text -> PMap c v -> [v]
pMapLookup k (PMap hm) = maybe [] (F.toList . NEL.toNonEmpty) $ HM.lookup k hm
pMapToList :: F.Foldable c => PMap c v -> [(Text, v)]
pMapToList (PMap hm) = expandValues =<< HM.toList hm
where
expandValues (k, cv) = map ((,) k) $ F.toList cv
pMapFromList :: NonEmptyLike c => [(Text, v)] -> PMap c v
pMapFromList = F.foldr f mempty
where
f (k, v) pm = pMapInsert k v pm
lookup :: (PMapKey k, NonEmptyLike c) => k -> PMap c v -> Maybe v
lookup k pm = listToMaybe $ lookupList k pm
lookupM :: (PMapKey k, NonEmptyLike c, MonadThrow m) => k -> PMap c v -> m v
lookupM k pm = maybe (throwM $ PMapNoSuchKey $ keyText k) return $ lookup k pm
lookupAs :: (PMapKey k, NonEmptyLike c, PMapValue k ~ a, FromGraphSON a)
=> k -> PMap c GValue -> Either PMapLookupException a
lookupAs k pm =
case lookup k pm of
Nothing -> Left $ PMapNoSuchKey kt
Just gv -> either (Left . PMapParseError kt) Right $ parseEither gv
where
kt = keyText k
lookupAs' :: (PMapKey k, NonEmptyLike c, PMapValue k ~ (Maybe a), FromGraphSON a)
=> k -> PMap c GValue -> Either PMapLookupException (Maybe a)
lookupAs' k pm = either fromError Right $ lookupAs k pm
where
fromError (PMapNoSuchKey _) = Right Nothing
fromError e = Left e
lookupAsM :: (PMapKey k, NonEmptyLike c, PMapValue k ~ a, FromGraphSON a, MonadThrow m)
=> k -> PMap c GValue -> m a
lookupAsM k pm = pMapToThrow $ lookupAs k pm
lookupList :: (PMapKey k, NonEmptyLike c) => k -> PMap c v -> [v]
lookupList k pm = pMapLookup (keyText k) pm
lookupListAs :: (PMapKey k, NonEmptyLike c, PMapValue k ~ a, FromGraphSON a)
=> k -> PMap c GValue -> Either PMapLookupException (NonEmpty a)
lookupListAs k pm =
case lookupList k pm of
[] -> Left $ PMapNoSuchKey kt
(x : rest) -> either (Left . PMapParseError kt) Right $ traverse parseEither (x :| rest)
where
kt = keyText k
lookupListAs' :: (PMapKey k, NonEmptyLike c, PMapValue k ~ (Maybe a), FromGraphSON a)
=> k -> PMap c GValue -> Either PMapLookupException [Maybe a]
lookupListAs' k pm = either fromError (Right . F.toList) $ lookupListAs k pm
where
fromError (PMapNoSuchKey _) = Right []
fromError e = Left e
type Single = S.First
newtype Multi a = Multi (NonEmpty a)
deriving (Show,Eq,Ord,Functor,Semigroup,Foldable,Traversable,NonEmptyLike,FromGraphSON)
class PMapKey k where
type PMapValue k :: *
keyText :: k -> Text
instance PMapKey Text where
type PMapValue Text = GValue
keyText = id
data PMapLookupException =
PMapNoSuchKey Text
| PMapParseError Text String
deriving (Show,Eq,Ord)
instance Exception PMapLookupException
pMapDecribeError :: PMapLookupException -> String
pMapDecribeError (PMapNoSuchKey k) = "Property '" ++ unpack k ++ "' does not exist."
pMapDecribeError (PMapParseError k pe) = "Parse error of property '" ++ unpack k ++ "': " ++ pe
pMapToThrow :: MonadThrow m => Either PMapLookupException a -> m a
pMapToThrow (Left e) = throwM e
pMapToThrow (Right a) = return a
pMapToFail :: MonadFail m => Either PMapLookupException a -> m a
pMapToFail (Left e) = fail $ pMapDecribeError e
pMapToFail (Right a) = return a