You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2023/06/18 06:24:13 UTC
[incubator-opendal] branch main updated: feat(bindings/haskell): add `Monad` wrapper (#2482)
This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
The following commit(s) were added to refs/heads/main by this push:
new fcda0be68 feat(bindings/haskell): add `Monad` wrapper (#2482)
fcda0be68 is described below
commit fcda0be68eb3d90bcaa68d9c2dc9c07d43b80a2d
Author: silver-ymz <yi...@gmail.com>
AuthorDate: Sun Jun 18 14:24:09 2023 +0800
feat(bindings/haskell): add `Monad` wrapper (#2482)
* feat(bindings/haskell): add `Monad` wrapper
Signed-off-by: silver-ymz <yi...@gmail.com>
* add tests for memory
Signed-off-by: silver-ymz <yi...@gmail.com>
* only test for memory
Signed-off-by: silver-ymz <yi...@gmail.com>
---------
Signed-off-by: silver-ymz <yi...@gmail.com>
---
bindings/haskell/haskell-src/OpenDAL.hs | 102 +++++++++++++++++++++++++-------
bindings/haskell/opendal-hs.cabal | 4 +-
bindings/haskell/test/BasicTest.hs | 63 +++++++++++++-------
3 files changed, 126 insertions(+), 43 deletions(-)
diff --git a/bindings/haskell/haskell-src/OpenDAL.hs b/bindings/haskell/haskell-src/OpenDAL.hs
index 5697b2ba3..293215a36 100644
--- a/bindings/haskell/haskell-src/OpenDAL.hs
+++ b/bindings/haskell/haskell-src/OpenDAL.hs
@@ -14,21 +14,27 @@
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module OpenDAL (
Operator,
- OpenDALError,
+ OpenDALError (..),
ErrorCode (..),
+ OpMonad,
+ MonadOperation (..),
+ runOp,
newOp,
- readOp,
- writeOp,
- isExistOp,
- createDirOp,
- copyOp,
- renameOp,
- deleteOp,
+ readOpRaw,
+ writeOpRaw,
+ isExistOpRaw,
+ createDirOpRaw,
+ copyOpRaw,
+ renameOpRaw,
+ deleteOpRaw,
) where
+import Control.Monad.Except (ExceptT, MonadError, runExceptT, throwError)
+import Control.Monad.Reader (MonadIO, MonadReader, ReaderT, ask, liftIO, runReaderT)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.HashMap.Strict (HashMap)
@@ -57,6 +63,55 @@ data ErrorCode
data OpenDALError = OpenDALError {errorCode :: ErrorCode, message :: String}
deriving (Eq, Show)
+newtype OpMonad a = OpMonad (ReaderT Operator (ExceptT OpenDALError IO) a)
+ deriving
+ ( Functor
+ , Applicative
+ , Monad
+ , MonadReader Operator
+ , MonadError OpenDALError
+ , MonadIO
+ )
+
+class (Monad m) => MonadOperation m where
+ readOp :: String -> m ByteString
+ writeOp :: String -> ByteString -> m ()
+ isExistOp :: String -> m Bool
+ createDirOp :: String -> m ()
+ copyOp :: String -> String -> m ()
+ renameOp :: String -> String -> m ()
+ deleteOp :: String -> m ()
+
+instance MonadOperation OpMonad where
+ readOp path = do
+ op <- ask
+ result <- liftIO $ readOpRaw op path
+ either throwError return result
+ writeOp path byte = do
+ op <- ask
+ result <- liftIO $ writeOpRaw op path byte
+ either throwError return result
+ isExistOp path = do
+ op <- ask
+ result <- liftIO $ isExistOpRaw op path
+ either throwError return result
+ createDirOp path = do
+ op <- ask
+ result <- liftIO $ createDirOpRaw op path
+ either throwError return result
+ copyOp src dst = do
+ op <- ask
+ result <- liftIO $ copyOpRaw op src dst
+ either throwError return result
+ renameOp src dst = do
+ op <- ask
+ result <- liftIO $ renameOpRaw op src dst
+ either throwError return result
+ deleteOp path = do
+ op <- ask
+ result <- liftIO $ deleteOpRaw op path
+ either throwError return result
+
byteSliceToByteString :: ByteSlice -> IO ByteString
byteSliceToByteString (ByteSlice bsDataPtr len) = BS.packCStringLen (bsDataPtr, fromIntegral len)
@@ -74,6 +129,9 @@ parseErrorCode 10 = RateLimited
parseErrorCode 11 = IsSameFile
parseErrorCode _ = FFIError
+runOp :: Operator -> OpMonad a -> IO (Either OpenDALError a)
+runOp operator (OpMonad op) = runExceptT $ runReaderT op operator
+
-- | Create a new Operator.
newOp :: String -> HashMap String String -> IO (Either OpenDALError Operator)
newOp scheme hashMap = do
@@ -97,8 +155,8 @@ newOp scheme hashMap = do
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg
-readOp :: Operator -> String -> IO (Either OpenDALError ByteString)
-readOp (Operator op) path = withForeignPtr op $ \opptr ->
+readOpRaw :: Operator -> String -> IO (Either OpenDALError ByteString)
+readOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
alloca $ \ffiResultPtr -> do
c_blocking_read opptr cPath ffiResultPtr
@@ -114,8 +172,8 @@ readOp (Operator op) path = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg
-writeOp :: Operator -> String -> ByteString -> IO (Either OpenDALError ())
-writeOp (Operator op) path byte = withForeignPtr op $ \opptr ->
+writeOpRaw :: Operator -> String -> ByteString -> IO (Either OpenDALError ())
+writeOpRaw (Operator op) path byte = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
BS.useAsCStringLen byte $ \(cByte, len) ->
alloca $ \ffiResultPtr -> do
@@ -128,8 +186,8 @@ writeOp (Operator op) path byte = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg
-isExistOp :: Operator -> String -> IO (Either OpenDALError Bool)
-isExistOp (Operator op) path = withForeignPtr op $ \opptr ->
+isExistOpRaw :: Operator -> String -> IO (Either OpenDALError Bool)
+isExistOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
alloca $ \ffiResultPtr -> do
c_blocking_is_exist opptr cPath ffiResultPtr
@@ -145,8 +203,8 @@ isExistOp (Operator op) path = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg
-createDirOp :: Operator -> String -> IO (Either OpenDALError ())
-createDirOp (Operator op) path = withForeignPtr op $ \opptr ->
+createDirOpRaw :: Operator -> String -> IO (Either OpenDALError ())
+createDirOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
alloca $ \ffiResultPtr -> do
c_blocking_create_dir opptr cPath ffiResultPtr
@@ -158,8 +216,8 @@ createDirOp (Operator op) path = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg
-copyOp :: Operator -> String -> String -> IO (Either OpenDALError ())
-copyOp (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
+copyOpRaw :: Operator -> String -> String -> IO (Either OpenDALError ())
+copyOpRaw (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
withCString srcPath $ \cSrcPath ->
withCString dstPath $ \cDstPath ->
alloca $ \ffiResultPtr -> do
@@ -172,8 +230,8 @@ copyOp (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg
-renameOp :: Operator -> String -> String -> IO (Either OpenDALError ())
-renameOp (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
+renameOpRaw :: Operator -> String -> String -> IO (Either OpenDALError ())
+renameOpRaw (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
withCString srcPath $ \cSrcPath ->
withCString dstPath $ \cDstPath ->
alloca $ \ffiResultPtr -> do
@@ -186,8 +244,8 @@ renameOp (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg
-deleteOp :: Operator -> String -> IO (Either OpenDALError ())
-deleteOp (Operator op) path = withForeignPtr op $ \opptr ->
+deleteOpRaw :: Operator -> String -> IO (Either OpenDALError ())
+deleteOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
alloca $ \ffiResultPtr -> do
c_blocking_delete opptr cPath ffiResultPtr
diff --git a/bindings/haskell/opendal-hs.cabal b/bindings/haskell/opendal-hs.cabal
index dce0fb162..25351697b 100644
--- a/bindings/haskell/opendal-hs.cabal
+++ b/bindings/haskell/opendal-hs.cabal
@@ -43,7 +43,8 @@ library
build-depends:
base >=4.10.0.0 && <5,
unordered-containers >=0.2.0.0,
- bytestring >=0.11.0.0
+ bytestring >=0.11.0.0,
+ mtl >=2.2.0.0
test-suite opendal-hs-test
type: exitcode-stdio-1.0
@@ -56,6 +57,7 @@ test-suite opendal-hs-test
build-depends:
base,
unordered-containers,
+ mtl,
opendal-hs,
tasty,
tasty-hunit
\ No newline at end of file
diff --git a/bindings/haskell/test/BasicTest.hs b/bindings/haskell/test/BasicTest.hs
index 135a5e6fe..8c1197ff4 100644
--- a/bindings/haskell/test/BasicTest.hs
+++ b/bindings/haskell/test/BasicTest.hs
@@ -18,6 +18,7 @@
module BasicTest (basicTests) where
+import Control.Monad.IO.Class (liftIO)
import qualified Data.HashMap.Strict as HashMap
import OpenDAL
import Test.Tasty
@@ -27,25 +28,47 @@ basicTests :: TestTree
basicTests =
testGroup
"Basic Tests"
- [ testCase "testBasicOperation" testBasicOperation
+ [ testCase "testBasicOperation" testRawOperation
+ , testCase "testMonad" testMonad
+ , testCase "testError" testError
]
-testBasicOperation :: Assertion
-testBasicOperation = do
- Right op <- newOp "fs" $ HashMap.fromList [("root", "/tmp/opendal-test")]
- writeOp op "key1" "value1" >>= (@?= Right ())
- writeOp op "key2" "value2" >>= (@?= Right ())
- readOp op "key1" >>= (@?= Right "value1")
- readOp op "key2" >>= (@?= Right "value2")
- isExistOp op "key1" >>= (@?= Right True)
- isExistOp op "key2" >>= (@?= Right True)
- createDirOp op "dir1/" >>= (@?= Right ())
- isExistOp op "dir1/" >>= (@?= Right True)
- copyOp op "key1" "key3" >>= (@?= Right ())
- isExistOp op "key3" >>= (@?= Right True)
- isExistOp op "key1" >>= (@?= Right True)
- renameOp op "key2" "key4" >>= (@?= Right ())
- isExistOp op "key4" >>= (@?= Right True)
- isExistOp op "key2" >>= (@?= Right False)
- deleteOp op "key1" >>= (@?= Right ())
- isExistOp op "key1" >>= (@?= Right False)
\ No newline at end of file
+testRawOperation :: Assertion
+testRawOperation = do
+ Right op <- newOp "memory" HashMap.empty
+ writeOpRaw op "key1" "value1" >>= (@?= Right ())
+ writeOpRaw op "key2" "value2" >>= (@?= Right ())
+ readOpRaw op "key1" >>= (@?= Right "value1")
+ readOpRaw op "key2" >>= (@?= Right "value2")
+ isExistOpRaw op "key1" >>= (@?= Right True)
+ isExistOpRaw op "key2" >>= (@?= Right True)
+ createDirOpRaw op "dir1/" >>= (@?= Right ())
+ isExistOpRaw op "dir1/" >>= (@?= Right True)
+ deleteOpRaw op "key1" >>= (@?= Right ())
+ isExistOpRaw op "key1" >>= (@?= Right False)
+
+testMonad :: Assertion
+testMonad = do
+ Right op <- newOp "memory" HashMap.empty
+ runOp op operation >>= (@?= Right ())
+ where
+ operation = do
+ writeOp "key1" "value1"
+ writeOp "key2" "value2"
+ readOp "key1" >>= liftIO . (@?= "value1")
+ readOp "key2" >>= liftIO . (@?= "value2")
+ isExistOp "key1" >>= liftIO . (@?= True)
+ isExistOp "key2" >>= liftIO . (@?= True)
+ createDirOp "dir1/"
+ isExistOp "dir1/" >>= liftIO . (@?= True)
+ deleteOp "key1"
+ isExistOp "key1" >>= liftIO . (@?= False)
+
+testError :: Assertion
+testError = do
+ Right op <- newOp "memory" HashMap.empty
+ runOp op operation >>= \v -> case v of
+ Left err -> errorCode err @?= NotFound
+ Right _ -> assertFailure "should not reach here"
+ where
+ operation = readOp "non-exist-path"