Deadpan-DDP-0.9.4.0: src/Data/EJson/EJson.hs
{-|
Description : Internal definitions for EJson functionality
Currently EJson functionality is built on top of the
`Data.Aeson.Value` type.
Functions are written to convert back and forth between
`Data.EJson.EJsonValue` and `Data.Aeson.Value`.
The conversion functions from EJsonValue to Value are in a seperate
module: "Data.EJson.EJson2Value".
This has some negative impact on performance, but aids simplicity.
-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Data.EJson.EJson (
EJsonValue(..)
-- Conversion functions
, value2EJson
-- Smart Constructors
, ejobject
, ejarray
, ejstring
, ejnumber
, ejbool
, ejdate
, ejbinary
, ejuser
, ejnull
-- Prisms
, _EJObject
, _EJObjectKey
, _EJObjectKeyString
, _EJArray
, _EJAraryIndex
, _EJString
, _EJNumber
, _EJBool
, _EJDate
, _EJBinary
, _EJUser
, _EJNull
) where
import Control.Monad
import Data.Aeson
import Data.Scientific
import Data.Time.Clock.POSIX
import Data.Time.Clock
import Data.Text.Internal
import Data.Text.Encoding
import Data.ByteString hiding (putStr, map)
import Data.ByteString.Base64
import Data.Maybe
import Data.String
import Control.Lens
import qualified Data.Vector
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
data EJsonValue =
EJObject !(HM.HashMap Text EJsonValue)
| EJArray !(Data.Vector.Vector EJsonValue)
| EJString !Text
| EJNumber !Scientific
| EJBool !Bool
| EJDate !UTCTime
| EJBinary !ByteString
| EJUser !Text !EJsonValue
| EJNull
deriving (Eq)
-- Instances follow!
-- Prismo time!
-- http://adventuretime.wikia.com/wiki/Prismo
makePrisms ''EJsonValue
-- Possibly access the value indicated by a key into a possible EJsonValue EJObject.
--
_EJObjectKey :: Text -> Traversal' EJsonValue (Maybe EJsonValue)
_EJObjectKey k = _EJObject . at k
-- | A helpful prism that looks up values of type EJ{"foo" : "bar", ...}
-- with a Text key "foo" and returns Just "bar", or Nothing.
-- Used frequently for checking message types and ids.
--
_EJObjectKeyString :: Applicative f
=> Text
-> (Text -> f Text)
-> EJsonValue
-> f EJsonValue
_EJObjectKeyString k = _EJObject . at k . _Just . _EJString
_EJAraryIndex :: Applicative f
=> Int
-> (EJsonValue -> f EJsonValue)
-> EJsonValue
-> f EJsonValue
_EJAraryIndex i = _EJArray . ix i
instance IsString EJsonValue
where
fromString = EJString . T.pack
instance Monoid EJsonValue
where
mempty = EJNull
EJObject o1 `mappend` EJObject o2 = EJObject $ mappend o1 o2
EJArray a1 `mappend` EJArray a2 = EJArray $ mappend a1 a2
_ `mappend` _ = error "TODO: Haven't considered what to do here yet..."
-- TODO: Decide what to do about these error cases
instance Num EJsonValue
where
fromInteger = EJNumber . fromIntegral
(EJNumber a) + (EJNumber b) = EJNumber (a + b)
_ + _ = error "don't add non-numbers"
(EJNumber a) * (EJNumber b) = EJNumber (a * b)
_ * _ = error "don't multiply non-numbers"
abs (EJNumber a) = EJNumber (abs a)
abs _ = error "don't abolute non-numbers"
signum (EJNumber a) = EJNumber (signum a)
signum _ = error "don't signum non-numbers"
negate (EJNumber a) = EJNumber (negate a)
negate _ = error "don't negate non-numbers"
-- Smart Constructors
{-# Inline ejobject #-}
ejobject :: [(Text, EJsonValue)] -> EJsonValue
ejobject = EJObject . HM.fromList
{-# Inline ejarray #-}
ejarray :: [EJsonValue] -> EJsonValue
ejarray = EJArray . Data.Vector.fromList
{-# Inline ejstring #-}
ejstring :: Text -> EJsonValue
ejstring = EJString
{-# Inline ejnumber #-}
ejnumber :: Scientific -> EJsonValue
ejnumber = EJNumber
{-# Inline ejbool #-}
ejbool :: Bool -> EJsonValue
ejbool = EJBool
{-# Inline ejdate #-}
ejdate :: Scientific -> EJsonValue
ejdate = EJDate . posixSecondsToUTCTime . realToFrac
{-# Inline ejbinary #-}
ejbinary :: ByteString -> EJsonValue
ejbinary = EJBinary
{-# Inline ejuser #-}
ejuser :: Text -> EJsonValue -> EJsonValue
ejuser = EJUser
{-# Inline ejnull #-}
ejnull :: EJsonValue
ejnull = EJNull
-- Conversion
value2EJson :: Value -> EJsonValue
value2EJson (Object o) = escapeObject o
value2EJson (Array a) = EJArray $ Data.Vector.map value2EJson a
value2EJson (String s) = EJString s
value2EJson (Number n) = EJNumber n
value2EJson (Bool b) = EJBool b
value2EJson Null = EJNull
-- Helpers
simpleKey :: Text -> Object -> Maybe Value
simpleKey = HM.lookup
parseDate :: Value -> Maybe EJsonValue
parseDate (Number n) = Just $ EJDate $ posixSecondsToUTCTime $ realToFrac n
parseDate _ = Nothing
parseBinary :: Value -> Maybe EJsonValue
parseBinary (String s) = Just (EJBinary (decodeLenient (encodeUtf8 s)))
parseBinary _ = Nothing
parseUser :: Value -> Value -> Maybe EJsonValue
parseUser (String k) v = Just $ EJUser k (value2EJson v)
parseUser _ _ = Nothing
parseEscaped :: Value -> Maybe EJsonValue
parseEscaped (Object o) = Just $ simpleObj o
parseEscaped _ = Nothing
getDate :: Int -> Object -> Maybe EJsonValue
getDate 1 o = parseDate =<< simpleKey "$date" o
getDate _ _ = Nothing
getBinary :: Int -> Object -> Maybe EJsonValue
getBinary 1 o = parseBinary =<< simpleKey "$binary" o
getBinary _ _ = Nothing
getUser :: Int -> Object -> Maybe EJsonValue
getUser 2 o = do t <- simpleKey "$type" o
v <- simpleKey "$value" o
parseUser t v
getUser _ _ = Nothing
getEscaped :: Int -> Object -> Maybe EJsonValue
getEscaped 1 o = parseEscaped =<< simpleKey "$escape" o
getEscaped _ _ = Nothing
simpleObj :: HM.HashMap Text Value -> EJsonValue
simpleObj o = EJObject $ HM.map value2EJson o
escapeObject :: Object -> EJsonValue
escapeObject o = fromMaybe (simpleObj o) $ msum
$ map (`uncurry` (HM.size o, o))
[getDate, getBinary, getUser, getEscaped]