tianbar-1.2.0.0: src/System/Tianbar/Plugin/DBus/JSON.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module System.Tianbar.Plugin.DBus.JSON () where
import Control.Applicative
import Data.Aeson hiding (Array)
import qualified Data.Aeson.Types as A
import qualified Data.HashMap.Strict as HM
import Data.Int
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Scientific as S
import qualified Data.Text as T
import qualified Data.Vector as V
import Data.Word
import DBus
import System.Tianbar.Plugin.DBus.Utils
fromVariantJust :: IsVariant v => Variant -> v
fromVariantJust = fromJust . fromVariant
instance ToJSON Variant where
toJSON v = case variantType v of
TypeBoolean -> toJSON (fromVariantJust v :: Bool)
TypeWord8 -> toJSON (fromVariantJust v :: Word8)
TypeWord16 -> toJSON (fromVariantJust v :: Word16)
TypeWord32 -> toJSON (fromVariantJust v :: Word32)
TypeWord64 -> toJSON (fromVariantJust v :: Word64)
TypeInt16 -> toJSON (fromVariantJust v :: Int16)
TypeInt32 -> toJSON (fromVariantJust v :: Int32)
TypeInt64 -> toJSON (fromVariantJust v :: Int64)
TypeDouble -> doubleToJSON (fromVariantJust v :: Double)
TypeString -> toJSON (fromVariantJust v :: T.Text)
TypeSignature -> toJSON (fromVariantJust v :: Signature)
TypeObjectPath -> toJSON (fromVariantJust v :: ObjectPath)
TypeUnixFd -> toJSON (fromVariantJust v :: Word32)
TypeVariant -> formatStringMarker "__variant" (fromVariantJust :: Variant -> Variant) v
TypeArray _ -> toJSON (arrayItems $ fromVariantJust v)
TypeDictionary _ _ -> toJSON $ M.fromList $ map variantStringKey $
dictionaryItems $ fromVariantJust v
TypeStructure _ -> toJSON $ structureItems $ fromVariantJust v
-- Floating point zero is instantiated as "0". To distinguish from integer
-- zero, encode it as "0.0"
doubleToJSON :: Double -> A.Value
doubleToJSON 0 = A.Number (S.scientific 0 (-1))
doubleToJSON v = toJSON v
instance IsValue v => IsVariant (HM.HashMap T.Text v) where
toVariant = toVariant . M.fromList . HM.toList
fromVariant = fmap (HM.fromList . M.toList) . fromVariant
instance FromJSON Variant where
parseJSON v = snd <$> parseSimpleVairant v
<|> parseArrayVariant v
<|> parseDictVariant v
parseSimpleVairant :: Value -> A.Parser (Type, Variant)
parseSimpleVairant (Bool b) = pure $ (TypeBoolean, toVariant b)
parseSimpleVairant (Number n) | S.base10Exponent n < 0 = pure $ (TypeDouble, toVariant doubleValue)
| otherwise = pure $ (TypeInt64, toVariant (fromIntegral integerValue :: Int64))
where doubleValue = S.toRealFloat n :: Double
integerValue = S.coefficient n * (10 ^ S.base10Exponent n)
parseSimpleVairant (String s) = pure $ (TypeString, toVariant s)
parseSimpleVairant v = (,) TypeObjectPath <$> parseObjectPathJSON v
<|> (,) TypeVariant <$> parseNestedVariant v
parseObjectPathJSON :: Value -> A.Parser Variant
parseObjectPathJSON v = toVariant <$> (parseJSON v :: A.Parser ObjectPath)
parseNestedVariant :: Value -> A.Parser Variant
parseNestedVariant v = (toVariant :: Variant -> Variant) <$> (parseStringMarker "__variant" Just v)
parseArrayVariant :: Value -> A.Parser Variant
parseArrayVariant v@(A.Array a) = mapM parseSimpleVairant (V.toList a) >>= makeArray
where makeArray items = case commonType items of
Just typ -> pure $ unwrapVariantsType typ (map snd items)
Nothing -> A.typeMismatch "items in array" v
parseArrayVariant val = A.typeMismatch "parseArrayVariant" val
commonType :: [(Type, Variant)] -> Maybe Type
commonType [] = Just TypeVariant -- all empty arrays are the same
commonType ((typ, _):rest) | all (typ ==) (map fst rest) = Just typ
| otherwise = Nothing
parseDictVariant :: Value -> A.Parser Variant
parseDictVariant v@(Object o) = do
let (keys, values) = unzip $ HM.toList o
values' <- mapM parseSimpleVairant values
let dict = M.fromList $ zip keys values'
makeDict dict
where makeDict :: M.Map T.Text (Type, Variant) -> A.Parser Variant
makeDict items = case commonType (M.elems items) of
Just typ -> pure $ unwrapVariantsType typ (M.map snd items)
Nothing -> A.typeMismatch "items in dictionary" v
parseDictVariant val = A.typeMismatch "parseDictVariant" val
variantString :: Variant -> String
variantString v = s
where Just s = case variantType v of
TypeString -> fromVariant v
TypeVariant -> Just $ variantString v'
where v' = fromVariantJust v
_ -> Just $ show v
variantStringKey :: (Variant, Variant) -> (String, Variant)
variantStringKey (k, v) = (variantString k, v)
parseStringMarker :: FromJSON v => T.Text -> (v -> Maybe a) -> Value -> A.Parser a
parseStringMarker marker parseFunc = withObject "Object expected" $ \o ->
(parseFunc <$> o .: marker) >>= maybeParse
where maybeParse Nothing = empty
maybeParse (Just v) = pure v
formatStringMarker :: ToJSON v => T.Text -> (a -> v) -> a -> Value
formatStringMarker marker formatFunc val = object [marker .= formatFunc val]
instance ToJSON ObjectPath where
toJSON = formatStringMarker "__object_path" formatObjectPath
instance FromJSON ObjectPath where
parseJSON = parseStringMarker "__object_path" parseObjectPath
instance ToJSON InterfaceName where
toJSON = toJSON . formatInterfaceName
instance ToJSON MemberName where
toJSON = toJSON . formatMemberName
instance ToJSON Signature where
toJSON = toJSON . formatSignature
instance ToJSON Signal where
toJSON s = object [ "path" .= toJSON (signalPath s)
, "iface" .= toJSON (signalInterface s)
, "member" .= toJSON (signalMember s)
, "body" .= toJSON (signalBody s)
]
instance ToJSON ErrorName where
toJSON = toJSON . formatErrorName
instance ToJSON Serial where
toJSON = toJSON . serialValue
instance ToJSON BusName where
toJSON = toJSON . formatBusName
instance ToJSON MethodError where
toJSON e = object [ "name" .= toJSON (methodErrorName e)
, "serial" .= toJSON (methodErrorSerial e)
, "sender" .= toJSON (methodErrorSender e)
, "destination" .= toJSON (methodErrorDestination e)
, "body" .= toJSON (methodErrorBody e)
, "message" .= toJSON (methodErrorMessage e)
]
instance ToJSON MethodReturn where
toJSON r = object [ "serial" .= toJSON (methodReturnSerial r)
, "sender" .= toJSON (methodReturnSender r)
, "destination" .= toJSON (methodReturnDestination r)
, "body" .= toJSON (methodReturnBody r)
]