module Database.InfluxDB.JSON
(
parseResultsWith
, parseResultsWithDecoder
, Decoder(..)
, strictDecoder
, lenientDecoder
, getField
, getTag
, parseUTCTime
, parsePOSIXTime
, parseRFC3339
, parseQueryField
, parseResultsObject
, parseSeriesObject
, parseSeriesBody
, parseErrorObject
) where
import Control.Applicative
import Control.Exception
import Control.Monad
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
:: Monad m
=> Text
-> Vector Text
-> Vector Value
-> m Value
getField column columns fields =
case V.elemIndex column columns of
Nothing -> fail $ "getField: no such column " ++ show column
Just idx -> case V.indexM fields idx of
Nothing -> fail $ "getField: index out of bound for " ++ show column
Just field -> return field
getTag
:: Monad m
=> Text
-> HashMap Text Value
-> m Value
getTag tag tags = case HashMap.lookup tag tags of
Nothing -> 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