{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
module Database.InfluxDB.JSON
(
parseResultsWith
, parseResultsWithDecoder
, Decoder(..)
, strictDecoder
, lenientDecoder
, getField
, getTag
, A.parseJSON
, parseUTCTime
, parsePOSIXTime
, parseRFC3339
, parseQueryField
, parseResultsObject
, parseSeriesObject
, parseSeriesBody
, parseErrorObject
) where
import Control.Applicative
import Control.Exception
import Control.Monad
import qualified Control.Monad.Fail as Fail
import Data.Maybe
import Data.Aeson
import Data.HashMap.Strict (HashMap)
import Data.Text (Text)
import Data.Time.Clock
import Data.Time.Clock.POSIX
import Data.Time.Format
import Data.Vector (Vector)
import qualified Data.Aeson.Types as A
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Scientific as Sci
import qualified Data.Text as T
import qualified Data.Vector as V
import Database.InfluxDB.Types
parseResultsWith
:: (Maybe Text -> HashMap Text Text -> Vector Text -> Array -> A.Parser a)
-> Value
-> A.Parser (Vector a)
parseResultsWith = parseResultsWithDecoder lenientDecoder
parseResultsWithDecoder
:: Decoder a
-> (Maybe Text -> HashMap Text Text -> Vector Text -> Array -> A.Parser a)
-> Value
-> A.Parser (Vector a)
parseResultsWithDecoder Decoder {..} row val0 = success
where
success = do
results <- parseResultsObject val0
(join -> series) <- V.forM results $ \val ->
parseSeriesObject val <|> parseErrorObject val
values <- V.forM series $ \val -> do
(name, tags, columns, values) <- parseSeriesBody val
decodeFold $ V.forM values $ A.withArray "values" $ \fields -> do
assert (V.length columns == V.length fields) $ return ()
decodeEach $ row name tags columns fields
return $! join values
data Decoder a = forall b. Decoder
{ decodeEach :: A.Parser a -> A.Parser b
, decodeFold :: A.Parser (Vector b) -> A.Parser (Vector a)
}
strictDecoder :: Decoder a
strictDecoder = Decoder
{ decodeEach = id
, decodeFold = id
}
lenientDecoder :: Decoder a
lenientDecoder = Decoder
{ decodeEach = optional
, decodeFold = \p -> do
bs <- p
return $! V.map fromJust $ V.filter isJust bs
}
getField
:: Fail.MonadFail m
=> Text
-> Vector Text
-> Vector Value
-> m Value
getField column columns fields =
case V.elemIndex column columns of
Nothing -> Fail.fail $ "getField: no such column " ++ show column
Just idx -> case V.indexM fields idx of
Nothing -> Fail.fail $ "getField: index out of bound for " ++ show column
Just field -> return field
getTag
:: Fail.MonadFail m
=> Text
-> HashMap Text Value
-> m Value
getTag tag tags = case HashMap.lookup tag tags of
Nothing -> Fail.fail $ "getTag: no such tag " ++ show tag
Just val -> return val
parseResultsObject :: Value -> A.Parser (Vector A.Value)
parseResultsObject = A.withObject "results" $ \obj -> obj .: "results"
parseSeriesObject :: Value -> A.Parser (Vector A.Value)
parseSeriesObject = A.withObject "series" $ \obj ->
fromMaybe V.empty <$> obj .:? "series"
parseSeriesBody
:: Value
-> A.Parser (Maybe Text, HashMap Text Text, Vector Text, Array)
parseSeriesBody = A.withObject "series" $ \obj -> do
!name <- obj .:? "name"
!columns <- obj .: "columns"
!values <- obj .:? "values" .!= V.empty
!tags <- obj .:? "tags" .!= HashMap.empty
return (name, tags, columns, values)
parseErrorObject :: A.Value -> A.Parser a
parseErrorObject = A.withObject "error" $ \obj -> do
message <- obj .: "error"
fail $ T.unpack message
parseUTCTime :: Precision ty -> A.Value -> A.Parser UTCTime
parseUTCTime prec val = case prec of
RFC3339 -> parseRFC3339 val
_ -> posixSecondsToUTCTime <$!> parsePOSIXTime prec val
parsePOSIXTime :: Precision ty -> A.Value -> A.Parser POSIXTime
parsePOSIXTime prec val = case prec of
RFC3339 -> utcTimeToPOSIXSeconds <$!> parseRFC3339 val
_ -> A.withScientific err
(\s -> case timestampToUTC s of
Nothing -> A.typeMismatch err val
Just !utc -> return utc)
val
where
err = "POSIX timestamp in " ++ T.unpack (precisionName prec)
timestampToUTC s = do
n <- Sci.toBoundedInteger s
return $! fromIntegral (n :: Int) * precisionScale prec
parseRFC3339 :: ParseTime time => A.Value -> A.Parser time
parseRFC3339 val = A.withText err
(maybe (A.typeMismatch err val) (return $!)
. parseTimeM True defaultTimeLocale fmt
. T.unpack)
val
where
fmt, err :: String
fmt = "%FT%X%QZ"
err = "RFC3339-formatted timestamp"
parseQueryField :: A.Value -> A.Parser QueryField
parseQueryField val = case val of
A.Number sci ->
return $! either FieldFloat FieldInt $ Sci.floatingOrInteger sci
A.String txt ->
return $! FieldString txt
A.Bool b ->
return $! FieldBool b
A.Null ->
return FieldNull
_ -> fail $ "parseQueryField: expected a flat data structure, but got "
++ show val
{-# DEPRECATED parseQueryField
"This function parses numbers in a misleading way. Use 'parseJSON' instead."
#-}