Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc(bindings/haskell): add module document #2566

Merged
merged 3 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions bindings/haskell/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- [Bring your own toolbox](#bring-your-own-toolbox)
- [Build](#build)
- [Test](#test)
- [Doc](#doc)
- [Misc](#misc)

## Setup
Expand Down Expand Up @@ -75,6 +76,16 @@ Test suite logged to:
1 of 1 test suites (1 of 1 test cases) passed.
```

## Doc

To generate the documentation:

```shell
cabal haddock
```

If your `cabal` version is greater than `3.8`, you can use `cabal haddock --open` to open the documentation in your browser. Otherwise, you can visit the documentation from `dist-newstyle/build/$ARCH/ghc-$VERSION/opendal-hs-$VERSION/doc/html/opendal-hs/index.html`.

## Misc

If you don't want to specify `LIBRARY_PATH` and `LD_LIBRARY_PATH` every time, you can use [`direnv`](https://direnv.net/) to set the environment variable automatically. Add the following to your `.envrc`:
Expand Down
30 changes: 23 additions & 7 deletions bindings/haskell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import qualified Data.HashMap.Strict as HashMap

main :: IO ()
main = do
Right op <- operator "memory" HashMap.empty
_ <- write op "key1" "value1"
_ <- write op "key2" "value2"
value1 <- read op "key1"
value2 <- read op "key2"
value1 @?= "value1"
value2 @?= "value2"
Right op <- operator "memory" HashMap.empty
runOp op operation
where
operation = do
writeOp op "key1" "value1"
writeOp op "key2" "value2"
value1 <- readOp op "key1"
value2 <- readOp op "key2"
```

## Build
Expand All @@ -34,3 +35,18 @@ If you don't want to install `libopendal_hs`, you need to specify library path m
```bash
LIBRARY_PATH=... cabal build
```

## Test

```bash
LD_LIBRARY_PATH=... cabal test
```

## Doc

To generate the documentation:
```bash
cabal haddock
```

If your `cabal` version is greater than `3.8`, you can use `cabal haddock --open` to open the documentation in your browser. Otherwise, you can visit the documentation from `dist-newstyle/build/$ARCH/ghc-$VERSION/opendal-hs-$VERSION/doc/html/opendal-hs/index.html`.
127 changes: 109 additions & 18 deletions bindings/haskell/haskell-src/OpenDAL.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{-# LANGUAGE FlexibleInstances #-}
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
Expand All @@ -15,9 +14,19 @@
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}

{- |
Module : OpenDAL
Description : Haskell bindings for OpenDAL
Copyright : (c) 2023 OpenDAL
License : Apache-2.0
Maintainer : OpenDAL Contributors <[email protected]>"
Stability : experimental
Portability : non - portable (GHC extensions)

This module provides Haskell bindings for OpenDAL.
-}
module OpenDAL (
Operator,
Lister,
Expand Down Expand Up @@ -54,53 +63,112 @@ import Foreign
import Foreign.C.String
import OpenDAL.FFI

{- | `Operator` is the entry for all public blocking APIs.
Create an `Operator` with `newOp`.
-}
newtype Operator = Operator (ForeignPtr RawOperator)

{- | `Lister` is designed to list entries at given path in a blocking manner.
Users can construct Lister by `listOp` or `scanOp`.
-}
newtype Lister = Lister (ForeignPtr RawLister)

-- | Represents the possible error codes that can be returned by OpenDAL.
data ErrorCode
= FFIError
| Unexpected
| Unsupported
| ConfigInvalid
| NotFound
| PermissionDenied
| IsADirectory
| NotADirectory
| AlreadyExists
| RateLimited
| IsSameFile
= -- | An error occurred in the FFI layer.
FFIError
| -- | OpenDAL don't know what happened here, and no actions other than just returning it back. For example, s3 returns an internal service error.
Unexpected
| -- | Underlying service doesn't support this operation.
Unsupported
| -- | The config for backend is invalid.
ConfigInvalid
| -- | The given path is not found.
NotFound
| -- | The given path doesn't have enough permission for this operation.
PermissionDenied
| -- | The given path is a directory.
IsADirectory
| -- | The given path is not a directory.
NotADirectory
| -- | The given path already exists thus we failed to the specified operation on it.
AlreadyExists
| -- | Requests that sent to this path is over the limit, please slow down.
RateLimited
| -- | The given file paths are same.
IsSameFile
deriving (Eq, Show)

data OpenDALError = OpenDALError {errorCode :: ErrorCode, message :: String}
-- | Represents an error that can occur when using OpenDAL.
data OpenDALError = OpenDALError
{ errorCode :: ErrorCode
-- ^ The error code.
, message :: String
-- ^ The error message.
}
deriving (Eq, Show)

-- | Represents the mode of an entry in a storage system (e.g., file or directory).
data EntryMode = File | Dir | Unknown deriving (Eq, Show)

-- | Represents metadata for an entry in a storage system.
data Metadata = Metadata
{ mMode :: EntryMode
-- ^ The mode of the entry.
, mCacheControl :: Maybe String
-- ^ The cache control of the entry.
, mContentDisposition :: Maybe String
-- ^ The content disposition of the entry.
, mContentLength :: Integer
-- ^ The content length of the entry.
, mContentMD5 :: Maybe String
-- ^ The content MD5 of the entry.
, mContentType :: Maybe String
-- ^ The content type of the entry.
, mETag :: Maybe String
-- ^ The ETag of the entry.
, mLastModified :: Maybe UTCTime
-- ^ The last modified time of the entry.
}
deriving (Eq, Show)

-- | The monad used for OpenDAL operations.
type OpMonad = ReaderT Operator (ExceptT OpenDALError IO)

-- | A type class for monads that can perform OpenDAL operations.
class (Monad m) => MonadOperation m where
-- | Read the whole path into a bytes.
readOp :: String -> m ByteString

-- | Write bytes into given path.
writeOp :: String -> ByteString -> m ()

-- | Check if this path exists or not.
isExistOp :: String -> m Bool

-- | Create a dir at given path.
createDirOp :: String -> m ()

-- | Copy a file from srcPath to dstPath.
copyOp :: String -> String -> m ()

-- | Rename a file from srcPath to dstPath.
renameOp :: String -> String -> m ()

-- | Delete given path.
deleteOp :: String -> m ()

-- | Get given path’s metadata without cache directly.
statOp :: String -> m Metadata

-- | List current dir path.
-- This function will create a new handle to list entries.
-- An error will be returned if path doesn’t end with /.
listOp :: String -> m Lister

-- | List dir in flat way.
-- Also, this function can be used to list a prefix.
-- An error will be returned if given path doesn’t end with /.
scanOp :: String -> m Lister

instance MonadOperation OpMonad where
Expand Down Expand Up @@ -203,9 +271,11 @@ parseFFIMetadata (FFIMetadata mode cacheControl contentDisposition contentLength

-- Exported functions

-- | Runs an OpenDAL operation in the 'OpMonad'.
runOp :: Operator -> OpMonad a -> IO (Either OpenDALError a)
runOp operator op = runExceptT $ runReaderT op operator

-- | Creates a new OpenDAL operator via `HashMap`.
newOp :: String -> HashMap String String -> IO (Either OpenDALError Operator)
newOp scheme hashMap = do
let keysAndValues = HashMap.toList hashMap
Expand All @@ -221,13 +291,18 @@ newOp scheme hashMap = do
ffiResult <- peek ffiResultPtr
if ffiCode ffiResult == 0
then do
op <- Operator <$> (newForeignPtr c_free_operator $ dataPtr ffiResult)
op <- Operator <$> newForeignPtr c_free_operator (dataPtr ffiResult)
return $ Right op
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- Functions for performing raw OpenDAL operations are defined below.
-- These functions are not meant to be used directly in most cases.
-- Instead, use the high-level interface provided by the 'MonadOperation' type class.

-- | Read the whole path into a bytes.
readOpRaw :: Operator -> String -> IO (Either OpenDALError ByteString)
readOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
Expand All @@ -245,6 +320,7 @@ readOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- | Write bytes into given path.
writeOpRaw :: Operator -> String -> ByteString -> IO (Either OpenDALError ())
writeOpRaw (Operator op) path byte = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
Expand All @@ -259,6 +335,7 @@ writeOpRaw (Operator op) path byte = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- | Check if this path exists or not.
isExistOpRaw :: Operator -> String -> IO (Either OpenDALError Bool)
isExistOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
Expand All @@ -275,6 +352,7 @@ isExistOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- | Create a dir at given path.
createDirOpRaw :: Operator -> String -> IO (Either OpenDALError ())
createDirOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
Expand All @@ -288,6 +366,7 @@ createDirOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- | Copy a file from srcPath to dstPath.
copyOpRaw :: Operator -> String -> String -> IO (Either OpenDALError ())
copyOpRaw (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
withCString srcPath $ \cSrcPath ->
Expand All @@ -302,6 +381,7 @@ copyOpRaw (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- | Rename a file from srcPath to dstPath.
renameOpRaw :: Operator -> String -> String -> IO (Either OpenDALError ())
renameOpRaw (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
withCString srcPath $ \cSrcPath ->
Expand All @@ -316,6 +396,7 @@ renameOpRaw (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- | Delete given path.
deleteOpRaw :: Operator -> String -> IO (Either OpenDALError ())
deleteOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
Expand All @@ -329,6 +410,7 @@ deleteOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- | Get given path’s metadata without cache directly.
statOpRaw :: Operator -> String -> IO (Either OpenDALError Metadata)
statOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
Expand All @@ -345,6 +427,10 @@ statOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

{- | List current dir path.
This function will create a new handle to list entries.
An error will be returned if path doesn’t end with /.
-}
listOpRaw :: Operator -> String -> IO (Either OpenDALError Lister)
listOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
Expand All @@ -354,13 +440,17 @@ listOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
if ffiCode ffiResult == 0
then do
ffilister <- peek $ dataPtr ffiResult
lister <- Lister <$> (newForeignPtr c_free_lister ffilister)
lister <- Lister <$> newForeignPtr c_free_lister ffilister
return $ Right lister
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

{- | List dir in flat way.
Also, this function can be used to list a prefix.
An error will be returned if given path doesn’t end with /.
-}
scanOpRaw :: Operator -> String -> IO (Either OpenDALError Lister)
scanOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
Expand All @@ -370,13 +460,14 @@ scanOpRaw (Operator op) path = withForeignPtr op $ \opptr ->
if ffiCode ffiResult == 0
then do
ffilister <- peek $ dataPtr ffiResult
lister <- Lister <$> (newForeignPtr c_free_lister ffilister)
lister <- Lister <$> newForeignPtr c_free_lister ffilister
return $ Right lister
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

-- | Get next entry path from `Lister`.
nextLister :: Lister -> IO (Either OpenDALError (Maybe String))
nextLister (Lister lister) = withForeignPtr lister $ \listerptr ->
alloca $ \ffiResultPtr -> do
Expand Down
4 changes: 1 addition & 3 deletions bindings/haskell/opendal-hs.cabal
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
cabal-version: 2.0

-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
Expand All @@ -17,13 +15,13 @@ cabal-version: 2.0
-- specific language governing permissions and limitations
-- under the License.

cabal-version: 2.0
name: opendal-hs
version: 0.1.0.0
license: Apache-2.0
synopsis: OpenDAL Haskell Binding
description:
OpenDAL Haskell Binding. Open Data Access Layer: Access data freely, painlessly, and efficiently

category: Storage, Binding
build-type: Simple

Expand Down
5 changes: 3 additions & 2 deletions bindings/haskell/test/BasicTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
-- KIND, either express or implied. See the License for the
-- specific language governing permissions and limitations
-- under the License.
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module BasicTest (basicTests) where
Expand Down Expand Up @@ -50,7 +51,7 @@ testRawOperation = do
renameOpRaw op "key2" "key4" >>= (@?= Right ())
isExistOpRaw op "key2" >>= (@?= Right False)
isExistOpRaw op "key4" >>= (@?= Right True)
statOpRaw op "key1" >>= \v -> case v of
statOpRaw op "key1" >>= \case
Right meta -> meta @?= except_meta
Left _ -> assertFailure "should not reach here"
deleteOpRaw op "key1" >>= (@?= Right ())
Expand Down Expand Up @@ -116,7 +117,7 @@ testMonad = do
testError :: Assertion
testError = do
Right op <- newOp "memory" HashMap.empty
runOp op operation >>= \v -> case v of
runOp op operation >>= \case
Left err -> errorCode err @?= NotFound
Right _ -> assertFailure "should not reach here"
where
Expand Down