packages feed

hermes-json-0.6.0.0: src/Data/Hermes/Decoder/Value.hs

{-# OPTIONS_HADDOCK show-extensions #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiWayIf #-}

module Data.Hermes.Decoder.Value
  ( atKey
  , atKeyOptional
  , atKeyStrict
  , atPointer
  , bool
  , char
  , double
  , int
  , uint
  , getType
  , list
  , nullable
  , object
  , objectAsKeyValues
  , objectAsMap
  , parseScientific
  , scientific
  , string
  , text
  , listOfDouble
  , listOfInt
  , isNull
  , vector
  , withBool
  , withDouble
  , withInt
  , withObjectAsMap
  , withRawByteString
  , withRawText
  , withScientific
  , withString
  , withText
  , withType
  , withVector
  ) where

import           Control.Monad ((>=>))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Unsafe as Unsafe
import qualified Data.DList as DList
import           Data.Map (Map)
import qualified Data.Map.Strict as M
import qualified Data.Scientific as Sci
import           Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Foreign as T
import qualified Data.Vector.Generic as G
import qualified Data.Vector.Generic.Mutable as GM
import qualified Foreign.C.String as F
import qualified Foreign.ForeignPtr as F
import qualified Foreign.Marshal.Alloc as F
import qualified Foreign.Marshal.Array as F
import qualified Foreign.Marshal.Utils as F
import qualified Foreign.Ptr as F
import qualified Foreign.Storable as F

import           Data.Hermes.Decoder.Internal
import           Data.Hermes.Decoder.Internal.Scientific
import           Data.Hermes.Decoder.Path
import           Data.Hermes.SIMDJSON

-- | Decode a value at the particular JSON pointer following RFC 6901.
-- Be careful where you use this because it rewinds the document on each
-- successive call.
--
-- > decodeEither (atPointer "/statuses/99" decodeObject) input
atPointer :: Text -> Decoder a -> Decoder a
atPointer jptr (Decoder f) = Decoder $ \_ -> do
  doc <- asks hDocument
  withRunInIO $ \run ->
    F.withForeignPtr doc $ \docPtr ->
      Unsafe.unsafeUseAsCStringLen (T.encodeUtf8 jptr) $ \(cstr, len) ->
        allocaValue $ \vPtr -> run . withPointer jptr $ do
          err <- liftIO $ atPointerImpl cstr len (Document docPtr) vPtr
          handleErrorCode "" err
          f vPtr
{-# INLINE atPointer #-}

-- | Enter an `Object` to begin parsing its fields.
object :: FieldsDecoder a -> Decoder a
object f = Decoder $ \(Value valPtr) ->
  withRunInIO $ \run ->
    allocaValue $ \vPtr -> do
      err <- getObjectFromValueImpl (Value valPtr)
      run $ do
        handleErrorCode (typePrefix "object") err
        runDecoder (runFieldsDecoder f . Object $ F.castPtr valPtr) vPtr
{-# INLINE object #-}

-- | Helper to work with an Int parsed from a Value.
withInt :: (Int -> Decoder a) -> Decoder a
withInt f = Decoder $ \val -> getInt val >>= \i -> runDecoder (f i) val
{-# INLINE withInt #-}

-- | Helper to work with a Double parsed from a Value.
withDouble :: (Double -> Decoder a) -> Decoder a
withDouble f = Decoder $ \val -> getDouble val >>= \d -> runDecoder (f d) val
{-# INLINE withDouble #-}

-- | Helper to work with a Bool parsed from a Value.
withBool :: (Bool -> Decoder a) -> Decoder a
withBool f = Decoder $ \val -> getBool val >>= \b -> runDecoder (f b) val
{-# INLINE withBool #-}

-- | Helper to work with the raw ByteString of the JSON token parsed from the given Value.
withRawByteString :: (BS.ByteString -> Decoder a) -> Decoder a
withRawByteString f = Decoder $ \val -> getRawByteString val >>= \b -> runDecoder (f b) val
{-# INLINE withRawByteString #-}

-- | Helper to work with the raw ByteString of the JSON token parsed from the given Value.
withRawText :: (Text -> Decoder a) -> Decoder a
withRawText f = Decoder $ \val -> getRawText val >>= \b -> runDecoder (f b) val
{-# INLINE withRawText #-}

-- | Helper to work with a String parsed from a Value.
withString :: (String -> Decoder a) -> Decoder a
withString f = Decoder $ \val -> getString val >>= \s -> runDecoder (f s) val
{-# INLINE withString #-}

-- | Helper to work with a Text parsed from a Value.
withText :: (Text -> Decoder a) -> Decoder a
withText f = Decoder $ \val -> getText val >>= \t -> runDecoder (f t) val
{-# INLINE withText #-}

-- | Returns True if the Value is null.
isNull :: Decoder Bool
isNull = Decoder $ \valPtr ->
  withRunInIO $ \run ->
    F.alloca $ \ptr -> do
      err <- isNullImpl valPtr ptr
      run $ do
        handleErrorCode "" err
        fmap F.toBool . liftIO $ F.peek ptr
{-# INLINE isNull #-}

-- | Is more efficient by looping in C++ instead of Haskell.
listOfInt :: Decoder [Int]
listOfInt =
  withArrayLen $ \(arrPtr, len) ->
    withRunInIO $ \run ->
      F.allocaArray len $ \out -> do
        err <- intArrayImpl arrPtr out
        run $ handleErrorCode "Error decoding array of ints." err
        F.peekArray len out
{-# RULES "list int/listOfInt" list int = listOfInt #-}

-- | Is more efficient by looping in C++ instead of Haskell.
listOfDouble :: Decoder [Double]
listOfDouble =
  withArrayLen $ \(arrPtr, len) ->
    withRunInIO $ \run ->
      F.allocaArray len $ \out -> do
        err <- doubleArrayImpl arrPtr out
        run $ handleErrorCode "Error decoding array of doubles." err
        F.peekArray len out
{-# RULES "list double/listOfDouble" list double = listOfDouble #-}

-- | Find an object field by key, where an exception is thrown
-- if the key is missing.
atKey :: Text -> Decoder a -> FieldsDecoder a
atKey key parser =
  FieldsDecoder $ \obj -> Decoder $ \val -> withUnorderedField val parser obj key
{-# INLINE atKey #-}

-- | Find an object field by key, where Nothing is returned
-- if the key is missing.
atKeyOptional :: Text -> Decoder a -> FieldsDecoder (Maybe a)
atKeyOptional key parser =
  FieldsDecoder $ \obj -> Decoder $ \val -> withUnorderedOptionalField val parser obj key
{-# INLINE atKeyOptional #-}

-- | Uses find_field, which means if you access a field out-of-order
-- this will throw an exception. It also cannot support optional fields.
atKeyStrict :: Text -> Decoder a -> FieldsDecoder a
atKeyStrict key parser =
  FieldsDecoder $ \obj -> Decoder $ \val -> withField val parser obj key
{-# INLINE atKeyStrict #-}

-- | Parse a homogenous JSON array into a Haskell list.
list :: Decoder a -> Decoder [a]
list f = withArrayIter $ iterateOverArray f
{-# INLINE[2] list #-}

-- | Parse a homogenous JSON array into a generic `Vector`.
vector :: G.Vector v a => Decoder a -> Decoder (v a)
vector f = withArrayLenIter $ iterateOverArrayLen f
{-# INLINE vector #-}

withVector :: G.Vector v a => Decoder a -> (v a -> Decoder a) -> Decoder a
withVector inner f = Decoder $ \val -> runDecoder (vector inner) val >>= \v -> runDecoder (f v) val
{-# INLINE withVector #-}

-- | Parse an object into a homogenous list of key-value tuples.
objectAsKeyValues
  :: (Text -> Decoder k)
  -- ^ Parses a Text key in the Decoder monad. JSON keys are always text.
  -> Decoder v
  -- ^ Decoder for the field value.
  -> Decoder [(k, v)]
objectAsKeyValues kf vf = withObjectIter $ iterateOverFields kf vf
{-# INLINE objectAsKeyValues #-}

-- | Parse an object into a strict `Map`.
objectAsMap
  :: Ord k
  => (Text -> Decoder k)
  -- ^ Parses a Text key in the Decoder monad. JSON keys are always text.
  -> Decoder v
  -- ^ Decoder for the field value.
  -> Decoder (Map k v)
objectAsMap kf vf = withObjectIter $ iterateOverFieldsMap kf vf
{-# INLINE objectAsMap #-}

withObjectAsMap
  :: Ord k
  => (Text -> Decoder k)
  -- ^ Parses a Text key in the Decoder monad. JSON keys are always text.
  -> Decoder v
  -- ^ Decoder for the field value.
  -> (Map k v -> Decoder a)
  -> Decoder a
withObjectAsMap kf vf f = Decoder $ \val -> runDecoder (objectAsMap kf vf) val >>= \m -> runDecoder (f m) val
{-# INLINE withObjectAsMap #-}

-- | Transforms a parser to return Nothing when the value is null.
nullable :: Decoder a -> Decoder (Maybe a)
nullable parser = Decoder $ \val -> do
  nil <- runDecoder isNull val
  if nil
    then pure Nothing
    else Just <$> runDecoder parser val
{-# INLINE nullable #-}

-- | Parse only a single character.
char :: Decoder Char
char = Decoder $ getText >=> justOne
  where
    justOne txt =
      case T.uncons txt of
        Just (c, "") ->
          pure c
        _ ->
          fail "Expected a single character"
{-# INLINE char #-}

-- | Parse a JSON string into a Haskell String.
-- For best performance you should use `text` instead.
string :: Decoder String
string = Decoder getString
{-# INLINE string #-}

-- | Parse a JSON string into Haskell Text.
text :: Decoder Text
text = Decoder getText
{-# INLINE text #-}

-- | Parse a JSON boolean into a Haskell Bool.
bool :: Decoder Bool
bool = Decoder getBool
{-# INLINE bool #-}

-- | Parse a JSON number into a signed Haskell Int.
int :: Decoder Int
int = Decoder getInt
{-# INLINE[2] int #-}

-- | Parse a JSON number into an unsigned Haskell Int (Word).
uint :: Decoder Word
uint = Decoder getUInt
{-# INLINE uint #-}

-- | Parse a JSON number into a Haskell Double.
double :: Decoder Double
double = Decoder getDouble
{-# INLINE[2] double #-}

-- | Parse a Scientific from a Value.
scientific :: Decoder Sci.Scientific
scientific = withRawText parseScientific
{-# INLINE scientific #-}

withScientific :: (Sci.Scientific -> Decoder a) -> Decoder a
withScientific f = Decoder $ \val -> runDecoder scientific val >>= \sci -> runDecoder (f sci) val
{-# INLINE withScientific #-}

-- | Parse a Scientific from UTF-8 text.
parseScientific :: Text -> Decoder Sci.Scientific
parseScientific = scanScientific
    (\sci rest -> if T.null rest then return sci else fail $ "Expecting end-of-input, got " ++ show (T.take 10 rest))
    fail
{-# INLINE parseScientific #-}

-- | Get the simdjson type of the Value.
getType :: Decoder ValueType
getType =
  Decoder $ \valPtr ->
    withRunInIO $ \run ->
      F.alloca $ \ptr -> do
        err <- getTypeImpl valPtr ptr
        run $ do
          handleErrorCode "" err
          fmap (toEnum . fromIntegral) . liftIO $ F.peek ptr
{-# INLINE getType #-}

withType :: (ValueType -> Decoder a) -> Decoder a
withType f = Decoder $ \val -> runDecoder getType val >>= \ty -> runDecoder (f ty) val
{-# INLINE withType #-}

-- Internal Functions

-- | Helper to work with an ArrayIter started from a Value assumed to be an Array.
withArrayIter :: (ArrayIter -> DecoderM a) -> Decoder a
withArrayIter f = Decoder $ \(Value valPtr) ->
  withRunInIO $ \run -> do
    err <- getArrayIterFromValueImpl (Value valPtr)
    run $ do
      handleErrorCode (typePrefix "array") err
      f (ArrayIter $ F.castPtr valPtr)
{-# INLINE withArrayIter #-}

-- | Execute a function on each Value in an ArrayIter and
-- accumulate the results into a list.
iterateOverArray :: Decoder a -> ArrayIter -> DecoderM [a]
iterateOverArray f iterPtr =
  withRunInIO $ \run ->
    allocaValue $ \valPtr -> run $ go (0 :: Int) DList.empty valPtr
  where
    go !n acc valPtr = do
      isOver <- fmap F.toBool . liftIO $ arrayIterIsDoneImpl iterPtr
      if not isOver
        then do
          r <- withIndex n $ do
            err <- liftIO $ arrayIterGetCurrentImpl iterPtr valPtr
            handleErrorCode "" err
            result <- runDecoder f valPtr
            liftIO $ arrayIterMoveNextImpl iterPtr
            pure result
          go (n + 1) (acc <> DList.singleton r) valPtr
        else
          pure $ DList.toList acc
{-# INLINE iterateOverArray #-}

-- | Helper to work with an ArrayIter and its length.
withArrayLenIter :: (ArrayIter -> Int -> DecoderM a) -> Decoder a
withArrayLenIter f = Decoder $ \(Value valPtr) ->
  withRunInIO $ \run -> do
    F.alloca $ \outLen -> do
      err <- getArrayIterLenFromValueImpl (Value valPtr) outLen
      len <- fmap fromIntegral $ F.peek outLen
      run $ do
        handleErrorCode "" err
        f (ArrayIter $ F.castPtr valPtr) len
{-# INLINE withArrayLenIter #-}

-- | Execute a function on each Value in an ArrayIter and
-- accumulate the results into a generic `Vector`.
iterateOverArrayLen :: G.Vector v a => Decoder a -> ArrayIter -> Int -> DecoderM (v a)
iterateOverArrayLen f iterPtr len =
  withRunInIO $ \run ->
    allocaValue $ \valPtr -> do
      v <- GM.new len
      _ <- run . runDecoderPrimM $ go (0 :: Int) v valPtr
      G.unsafeFreeze v
  where
    go !n acc valPtr = do
      isOver <- fmap F.toBool . DecoderPrimM . liftIO $ arrayIterIsDoneImpl iterPtr
      if not isOver
        then do
          _ <- DecoderPrimM . withIndex n . runDecoderPrimM $ do
            err <- DecoderPrimM . liftIO $ arrayIterGetCurrentImpl iterPtr valPtr
            DecoderPrimM $ handleErrorCode "" err
            !result <- DecoderPrimM $ runDecoder f valPtr
            DecoderPrimM . liftIO $ arrayIterMoveNextImpl iterPtr
            GM.unsafeWrite acc n result
          go (n + 1) acc valPtr
        else
          pure acc
{-# INLINE iterateOverArrayLen #-}

-- | Helper to work with an ObjectIter started from a Value assumed to be an Object.
withObjectIter :: (ObjectIter -> DecoderM a) -> Decoder a
withObjectIter f = Decoder $ \(Value valPtr) ->
  withRunInIO $ \run -> do
    err <- getObjectIterFromValueImpl (Value valPtr)
    run $ do
      handleErrorCode (typePrefix "object") err
      f (ObjectIter $ F.castPtr valPtr)
{-# INLINE withObjectIter #-}

-- | Execute a function on each Field in an ObjectIter and accumulate into a `Map`.
iterateOverFieldsMap
  :: Ord a
  => (Text -> Decoder a)
  -> Decoder b
  -> ObjectIter
  -> DecoderM (Map a b)
iterateOverFieldsMap fk fv iterPtr =
  withRunInIO $ \run ->
    F.alloca $ \lenPtr ->
      F.alloca $ \keyPtr ->
        allocaValue $ \valPtr -> run $ go M.empty keyPtr lenPtr valPtr
  where
    go !acc keyPtr lenPtr valPtr = do
      isOver <- fmap F.toBool . liftIO $ objectIterIsDoneImpl iterPtr
      if not isOver
        then do
          err <- liftIO $ objectIterGetCurrentImpl iterPtr keyPtr lenPtr valPtr
          handleErrorCode "" err
          kLen <- fmap fromIntegral . liftIO $ F.peek lenPtr
          kStr <- liftIO $ F.peek keyPtr
          keyTxt <- parseTextFromCStrLen (kStr, kLen)
          (k, v)
            <-
              withKey keyTxt $ do
                k <- runDecoder (fk keyTxt) valPtr
                v <- runDecoder fv valPtr
                pure (k, v)
          liftIO $ objectIterMoveNextImpl iterPtr
          go (M.insert k v acc) keyPtr lenPtr valPtr
        else
          pure acc
{-# INLINE iterateOverFieldsMap #-}

-- | Execute a function on each Field in an ObjectIter and
-- accumulate key-value tuples into a list.
iterateOverFields
  :: (Text -> Decoder a)
  -> Decoder b
  -> ObjectIter
  -> DecoderM [(a, b)]
iterateOverFields fk fv iterPtr =
  withRunInIO $ \run ->
    F.alloca $ \lenPtr ->
      F.alloca $ \keyPtr ->
        allocaValue $ \valPtr -> run $ go DList.empty keyPtr lenPtr valPtr
  where
    go acc keyPtr lenPtr valPtr = do
      isOver <- fmap F.toBool . liftIO $ objectIterIsDoneImpl iterPtr
      if not isOver
        then do
          err <- liftIO $ objectIterGetCurrentImpl iterPtr keyPtr lenPtr valPtr
          handleErrorCode "" err
          kLen <- fmap fromIntegral . liftIO $ F.peek lenPtr
          kStr <- liftIO $ F.peek keyPtr
          keyTxt <- parseTextFromCStrLen (kStr, kLen)
          kv <-
            withKey keyTxt $ do
              k <- runDecoder (fk keyTxt) valPtr
              v <- runDecoder fv valPtr
              pure (k, v)
          liftIO $ objectIterMoveNextImpl iterPtr
          go (acc <> DList.singleton kv) keyPtr lenPtr valPtr
        else
          pure $ DList.toList acc
{-# INLINE iterateOverFields #-}

withUnorderedField :: Value -> Decoder a -> Object -> Text -> DecoderM a
withUnorderedField vPtr f objPtr key =
  withRunInIO $ \run ->
    Unsafe.unsafeUseAsCStringLen (T.encodeUtf8 key) $ \(cstr, len) ->
      run $ withKey key $ do
        err <- liftIO $ findFieldUnorderedImpl objPtr cstr len vPtr
        handleErrorCode "" err
        runDecoder f vPtr
{-# INLINE withUnorderedField #-}

withUnorderedOptionalField :: Value -> Decoder a -> Object -> Text -> DecoderM (Maybe a)
withUnorderedOptionalField vPtr f objPtr key =
  withRunInIO $ \run ->
    Unsafe.unsafeUseAsCStringLen (T.encodeUtf8 key) $ \(cstr, len) ->
      run $ withKey key $ do
        err <- liftIO $ findFieldUnorderedImpl objPtr cstr len vPtr
        let errCode = toEnum $ fromIntegral err
        if | errCode == SUCCESS       -> Just <$> runDecoder f vPtr
           | errCode == NO_SUCH_FIELD -> pure Nothing
           | otherwise                -> Nothing <$ handleErrorCode "" err
{-# INLINE withUnorderedOptionalField #-}

withField :: Value -> Decoder a -> Object -> Text -> DecoderM a
withField vPtr f objPtr key =
  withRunInIO $ \run ->
    Unsafe.unsafeUseAsCStringLen (T.encodeUtf8 key) $ \(cstr, len) ->
      run $ withKey key $ do
        err <- liftIO $ findFieldImpl objPtr cstr len vPtr
        handleErrorCode "" err
        runDecoder f vPtr
{-# INLINE withField #-}

getInt :: Value -> DecoderM Int
getInt valPtr =
  withRunInIO $ \run ->
    F.alloca $ \ptr -> run $ do
      err <- liftIO $ getIntImpl valPtr ptr
      handleErrorCode (typePrefix "int") err
      liftIO $ F.peek ptr
{-# INLINE getInt #-}

getUInt :: Value -> DecoderM Word
getUInt valPtr =
  withRunInIO $ \run ->
    F.alloca $ \ptr -> run $ do
      err <- liftIO $ getUIntImpl valPtr ptr
      handleErrorCode (typePrefix "unsigned int") err
      liftIO $ F.peek ptr
{-# INLINE getUInt #-}

getDouble :: Value -> DecoderM Double
getDouble valPtr =
  withRunInIO $ \run ->
    F.alloca $ \ptr -> run $ do
      err <- liftIO $ getDoubleImpl valPtr ptr
      handleErrorCode (typePrefix "double") err
      liftIO $ F.peek ptr
{-# INLINE getDouble #-}

getBool :: Value -> DecoderM Bool
getBool valPtr =
  withRunInIO $ \run ->
    F.alloca $ \ptr -> run $ do
      err <- liftIO $ getBoolImpl valPtr ptr
      handleErrorCode (typePrefix "bool") err
      fmap F.toBool . liftIO $ F.peek ptr
{-# INLINE getBool #-}

withCStringLen :: Text -> (F.CStringLen -> DecoderM a) -> Value -> DecoderM a
withCStringLen lbl f valPtr =
  withRunInIO $ \run ->
    F.alloca $ \strPtr ->
      F.alloca $ \lenPtr -> run $ do
        err <- liftIO $ getStringImpl valPtr strPtr lenPtr
        handleErrorCode (typePrefix lbl) err
        len <- fmap fromIntegral . liftIO $ F.peek lenPtr
        str <- liftIO $ F.peek strPtr
        f (str, len)
{-# INLINE withCStringLen #-}

getString :: Value -> DecoderM String
getString = withCStringLen "string" (liftIO . F.peekCStringLen)
{-# INLINE getString #-}

getText :: Value -> DecoderM Text
getText = withCStringLen "text" parseTextFromCStrLen
{-# INLINE getText #-}

parseTextFromCStrLen :: F.CStringLen -> DecoderM Text
parseTextFromCStrLen (cstr, len) = liftIO $ T.fromPtr (F.castPtr cstr) (fromIntegral len)
{-# INLINE parseTextFromCStrLen #-}

getRawByteString :: Value -> DecoderM BS.ByteString
getRawByteString valPtr =
  liftIO $
    F.alloca $ \strPtr ->
      F.alloca $ \lenPtr -> do
        getRawJSONTokenImpl valPtr strPtr lenPtr
        len <- fmap fromIntegral $ F.peek lenPtr
        str <- F.peek strPtr
        Unsafe.unsafePackCStringLen (str, len)
{-# INLINE getRawByteString #-}

getRawText :: Value -> DecoderM Text
getRawText valPtr =
  liftIO $
    F.alloca $ \strPtr ->
      F.alloca $ \lenPtr -> do
        getRawJSONTokenImpl valPtr strPtr lenPtr
        len <- fmap fromIntegral $ F.peek lenPtr
        str <- F.peek strPtr
        T.fromPtr (F.castPtr str) len
{-# INLINE getRawText #-}

-- | Helper to work with an Array and its length parsed from a Value.
withArrayLen :: ((Array, Int) -> DecoderM a) -> Decoder a
withArrayLen f = Decoder $ \(Value val) ->
  withRunInIO $ \run ->
    F.alloca $ \outLen -> do
      err <- getArrayLenFromValueImpl (Value val) outLen
      len <- fmap fromIntegral $ F.peek outLen
      run $ do
        handleErrorCode (typePrefix "array") err
        f (Array $ F.castPtr val, len)
{-# INLINE withArrayLen #-}