greskell-core-1.0.0.1: src/Data/Greskell/GraphSON/GValue.hs
{-# LANGUAGE DeriveGeneric #-}
-- |
-- Module: Data.Greskell.GraphSON.GValue
-- Description: Aeson Value with GraphSON wrappers
-- Maintainer: Toshio Ito <debug.ito@gmail.com>
--
-- __This module is for advanced use. Most users should just use "Data.Greskell.GraphSON".__
--
-- This module defines 'GValue' and exposes its deconstructors.
--
-- @since 0.1.2.0
module Data.Greskell.GraphSON.GValue
( -- * GValue type
GValue (..)
, GValueBody (..)
-- ** constructors
, nonTypedGValue
, typedGValue'
-- ** deconstructors
-- $caveat_decon
, unwrapAll
, unwrapOne
, gValueBody
, gValueType
) where
import Control.Applicative ((<$>), (<*>))
import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), Value (..))
import Data.Aeson.KeyMap (KeyMap)
import Data.Aeson.Types (Parser)
import Data.Foldable (foldl')
import Data.Hashable (Hashable (..))
import Data.HashMap.Strict (HashMap)
import Data.Scientific (Scientific)
import Data.Text (Text)
import Data.Vector (Vector)
import GHC.Generics (Generic)
import Data.Greskell.GraphSON.Core (GraphSON (..), nonTypedGraphSON, typedGraphSON')
-- | An Aeson 'Value' wrapped in 'GraphSON' wrapper type. Basically
-- this type is the Haskell representaiton of a GraphSON-encoded
-- document.
--
-- This type is used to parse GraphSON documents. See also
-- 'Data.Greskell.GraphSON.FromGraphSON' class.
--
-- @since 0.1.2.0
newtype GValue
= GValue { unGValue :: GraphSON GValueBody }
deriving (Eq, Generic, Show)
instance Hashable GValue
-- | 'GValue' without the top-level 'GraphSON' wrapper.
--
-- @since 1.0.0.0
data GValueBody
= GObject !(KeyMap GValue)
| GArray !(Vector GValue)
| GString !Text
| GNumber !Scientific
| GBool !Bool
| GNull
deriving (Eq, Generic, Show)
instance Hashable GValueBody where
-- See Data.Aeson.Types.Internal
hashWithSalt s (GObject o) = s `hashWithSalt` (0::Int) `hashWithSalt` o
hashWithSalt s (GArray a) = foldl' hashWithSalt (s `hashWithSalt` (1::Int)) a
hashWithSalt s (GString str) = s `hashWithSalt` (2::Int) `hashWithSalt` str
hashWithSalt s (GNumber n) = s `hashWithSalt` (3::Int) `hashWithSalt` n
hashWithSalt s (GBool b) = s `hashWithSalt` (4::Int) `hashWithSalt` b
hashWithSalt s GNull = s `hashWithSalt` (5::Int)
-- | Parse 'GraphSON' wrappers recursively in 'Value', making it into
-- 'GValue'.
instance FromJSON GValue where
parseJSON input = do
gv <- parseJSON input
recursed_value <- recurse $ gsonValue gv
return $ GValue $ gv { gsonValue = recursed_value }
where
recurse :: Value -> Parser GValueBody
recurse (Object o) = GObject <$> traverse parseJSON o
recurse (Array a) = GArray <$> traverse parseJSON a
recurse (String s) = return $ GString s
recurse (Number n) = return $ GNumber n
recurse (Bool b) = return $ GBool b
recurse Null = return GNull
-- | Reconstruct 'Value' from 'GValue'. It preserves all GraphSON
-- wrappers.
instance ToJSON GValue where
toJSON (GValue gson_body) = toJSON $ fmap toJSON gson_body
instance ToJSON GValueBody where
toJSON (GObject o) = toJSON o
toJSON (GArray a) = toJSON a
toJSON (GString s) = String s
toJSON (GNumber n) = Number n
toJSON (GBool b) = Bool b
toJSON GNull = Null
-- | Create a 'GValue' without \"@type\" field.
--
-- @since 0.1.2.0
nonTypedGValue :: GValueBody -> GValue
nonTypedGValue = GValue . nonTypedGraphSON
-- | Create a 'GValue' with the given \"@type\" field.
--
-- @since 0.1.2.0
typedGValue' :: Text -- ^ \"@type\" field.
-> GValueBody -> GValue
typedGValue' t b = GValue $ typedGraphSON' t b
-- $caveat_decon
--
-- __In most cases, you should not use these deconstructors.__ That is
-- because internal structure of 'GValue' may vary depending on the
-- Gremlin server instance and its serializer. You should instead use
-- parsers based on 'Data.Greskell.GraphSON.FromGraphSON' class, such
-- as 'Data.Greskell.GraphSON.parseEither'.
--
-- If you are implementing parsers for GraphSON objects described in
-- Gremlin IO Reference
-- (<http://tinkerpop.apache.org/docs/current/dev/io/>), you may use
-- these descructors.
--
-- | Remove all 'GraphSON' wrappers recursively from 'GValue'.
--
-- @since 0.1.2.0
unwrapAll :: GValue -> Value
unwrapAll = unwrapBase unwrapAll
-- | Remove the top-level 'GraphSON' wrapper, but leave other wrappers
-- as-is. The remaining wrappers are reconstructed by 'toJSON' to make
-- them into 'Value'.
--
-- @since 0.1.2.0
unwrapOne :: GValue -> Value
unwrapOne = unwrapBase toJSON
unwrapBase :: (GValue -> Value) -> GValue -> Value
unwrapBase mapChild (GValue gson_body) = unwrapBody $ gsonValue gson_body
where
unwrapBody GNull = Null
unwrapBody (GBool b) = Bool b
unwrapBody (GNumber n) = Number n
unwrapBody (GString s) = String s
unwrapBody (GArray a) = Array $ fmap mapChild a
unwrapBody (GObject o) = Object $ fmap mapChild o
-- | Get the 'GValueBody' from 'GValue'.
--
-- @since 0.1.2.0
gValueBody :: GValue -> GValueBody
gValueBody = gsonValue . unGValue
-- | Get the 'gsonType' field from 'GValue'.
--
-- @since 0.1.2.0
gValueType :: GValue -> Maybe Text
gValueType = gsonType . unGValue