packages feed

wasmedge-hs-0.17.1.0: src/WasmEdge/Internal/Utils.hs

{- |
Module      : WasmEdge.Internal.Utils
Description : Internal utility functions shared across FFI modules
Copyright   : (c) 2026 Tushar Adhatrao
License     : MIT
Stability   : internal

__Do not import this module in user code.__
-}
module WasmEdge.Internal.Utils
  ( -- * Text / String conversion
    withWasmString
  , fromWasmString
  , peekWasmString
  , withWasmBytes

    -- * Checked result conversion
  , checkResult
  , throwOnFail

    -- * Pointer utilities
  , maybeNullPtr
  , fromNullablePtr
  ) where

import Control.Exception (throwIO)
import Data.ByteString qualified as BS
import Data.ByteString.Unsafe qualified as UBS
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE
import Foreign.C (CBool (..))
import Foreign.C.String (peekCString)
import Foreign.Ptr (Ptr, castPtr, nullPtr)

import WasmEdge.Error (WasmEdgeError (..), mkErrCategory)
import WasmEdge.Internal.FFI.Basic
  ( c_BytesWrap
  , c_ResultGetCategory
  , c_ResultGetCode
  , c_ResultGetMessage
  , c_ResultOK
  , c_StringCreateByBuffer
  , c_StringDelete
  , c_StringGetBuf
  , c_StringGetLength
  )
import WasmEdge.Internal.Storable

-- ---------------------------------------------------------------------------
-- Text <-> WasmEdge_String
-- ---------------------------------------------------------------------------

{- | Convert a Haskell 'Text' to a 'WasmEdge_String' (on the stack),
call the action, then delete the string.
-}
withWasmString :: Text -> (Ptr CString_ -> IO a) -> IO a
withWasmString t action =
  BS.useAsCStringLen (TE.encodeUtf8 t) $ \(buf, len) ->
    allocaCString $ \strPtr -> do
      c_StringCreateByBuffer strPtr buf (fromIntegral len)
      result <- action strPtr
      c_StringDelete strPtr
      pure result

{- | Peek a 'WasmEdge_String' (by pointer) and convert to Haskell 'Text'.
Does NOT free the string.
-}
peekWasmString :: Ptr CString_ -> IO Text
peekWasmString strPtr = do
  len <- c_StringGetLength strPtr
  buf <- c_StringGetBuf strPtr
  if buf == nullPtr || len == 0
    then pure mempty
    else do
      bs <- BS.packCStringLen (buf, fromIntegral len)
      pure (TE.decodeUtf8Lenient bs)

-- | Alias for 'peekWasmString' — reads a WasmEdge_String and returns Text.
fromWasmString :: Ptr CString_ -> IO Text
fromWasmString = peekWasmString

{- | Wrap a 'BS.ByteString' as a stack-allocated 'WasmEdge_Bytes' (zero copy),
call the action, and return its result.
-}
withWasmBytes :: BS.ByteString -> (Ptr CBytes_ -> IO a) -> IO a
withWasmBytes bs action =
  UBS.unsafeUseAsCStringLen bs $ \(buf, len) ->
    allocaCBytes $ \bytesPtr -> do
      c_BytesWrap bytesPtr (castPtr buf) (fromIntegral len)
      action bytesPtr

-- ---------------------------------------------------------------------------
-- Result checking
-- ---------------------------------------------------------------------------

{- | Check a 'WasmEdge_Result' pointer and return 'Right ()' on success
or 'Left WasmEdgeError' on failure.
-}
checkResult :: Ptr CResult_ -> IO (Either WasmEdgeError ())
checkResult resPtr = do
  CBool ok <- c_ResultOK resPtr
  if ok /= 0
    then pure (Right ())
    else do
      code <- c_ResultGetCode resPtr
      category <- c_ResultGetCategory resPtr
      msgPtr <- c_ResultGetMessage resPtr
      msg <-
        if msgPtr == nullPtr
          then pure "unknown error"
          else T.pack <$> peekCString msgPtr
      pure $
        Left
          WasmEdgeError
            { weErrCategory = mkErrCategory category
            , weErrCode = code
            , weErrMessage = msg
            }

-- | Like 'checkResult', but throws 'WasmEdgeError' on failure.
throwOnFail :: Ptr CResult_ -> IO ()
throwOnFail resPtr =
  checkResult resPtr >>= \case
    Right () -> pure ()
    Left err -> throwIO err

-- ---------------------------------------------------------------------------
-- Pointer utilities
-- ---------------------------------------------------------------------------

-- | Convert a 'Maybe (Ptr t)' to a nullable C pointer.
maybeNullPtr :: Maybe (Ptr t) -> Ptr t
maybeNullPtr = fromMaybe nullPtr

-- | Convert a nullable C pointer to 'Maybe'.
fromNullablePtr :: Ptr t -> Maybe (Ptr t)
fromNullablePtr p
  | p == nullPtr = Nothing
  | otherwise = Just p