diff --git a/.github/workflows/bindings_haskell.yml b/.github/workflows/bindings_haskell.yml index 7b8112b9c7b..9639ce81097 100644 --- a/.github/workflows/bindings_haskell.yml +++ b/.github/workflows/bindings_haskell.yml @@ -55,5 +55,4 @@ jobs: working-directory: "bindings/haskell" run: | cargo build - LIBRARY_PATH=../../target/debug cabal build - LD_LIBRARY_PATH=../../target/debug cabal test + LIBRARY_PATH=../../target/debug LD_LIBRARY_PATH=../../target/debug cabal test diff --git a/bindings/haskell/cabal.project.local b/bindings/haskell/cabal.project.local deleted file mode 100644 index b72cb7b111d..00000000000 --- a/bindings/haskell/cabal.project.local +++ /dev/null @@ -1,18 +0,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 --- regarding copyright ownership. The ASF licenses this file --- to you under the Apache License, Version 2.0 (the --- "License"); you may not use this file except in compliance --- with the License. You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, --- software distributed under the License is distributed on an --- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY --- KIND, either express or implied. See the License for the --- specific language governing permissions and limitations --- under the License. - -tests: True \ No newline at end of file diff --git a/bindings/haskell/haskell-src/OpenDAL.hs b/bindings/haskell/haskell-src/OpenDAL.hs index 773901f5ad3..1931e037c67 100644 --- a/bindings/haskell/haskell-src/OpenDAL.hs +++ b/bindings/haskell/haskell-src/OpenDAL.hs @@ -14,7 +14,6 @@ -- KIND, either express or implied. See the License for the -- specific language governing permissions and limitations -- under the License. -{-# LANGUAGE FlexibleInstances #-} -- | -- Module : OpenDAL @@ -27,17 +26,28 @@ -- -- This module provides Haskell bindings for OpenDAL. module OpenDAL - ( OperatorConfig (..), + ( -- * Operator APIs + + -- ** Types + OperatorConfig (..), Operator, Lister, OpenDALError (..), ErrorCode (..), EntryMode (..), Metadata (..), - OpMonad, + OperatorT (..), MonadOperation (..), + + -- ** Functions runOp, newOperator, + + -- * Lister APIs + nextLister, + + -- * Operator Raw APIs + -- $raw-operations readOpRaw, writeOpRaw, isExistOpRaw, @@ -48,13 +58,13 @@ module OpenDAL statOpRaw, listOpRaw, scanOpRaw, - nextLister, ) where import Colog (LogAction, Message, Msg (Msg), (<&)) -import Control.Monad.Except (ExceptT, runExceptT, throwError) -import Control.Monad.Reader (ReaderT, ask, liftIO, runReaderT) +import Control.Monad.Except (ExceptT, MonadError, MonadTrans, runExceptT, throwError) +import Control.Monad.Reader (MonadIO, MonadReader, ReaderT, ask, liftIO, runReaderT) +import Control.Monad.Trans (MonadTrans (lift)) import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.HashMap.Strict (HashMap) @@ -68,8 +78,28 @@ import Foreign.C.String import GHC.Stack (emptyCallStack) import OpenDAL.FFI --- | `OperatorConfig` is the configuration for an `Operator`. Currently, it contains the scheme, config and log action. --- Recommend using `OverloadedStrings` to construct a default config. +-- | `OperatorConfig` is the configuration for an `Operator`. +-- We recommend using `OverloadedStrings` to construct a default config. +-- +-- For example: +-- +-- default config +-- +-- @ +-- newOperator "memory" +-- @ +-- +-- custom services config +-- +-- @ +-- newOperator "memory" {ocConfig = HashMap.fromList [("root", "/tmp")]} +-- @ +-- +-- enable logging +-- +-- @ +-- newOperator "memory" {ocLogAction = Just simpleMessageAction} +-- @ data OperatorConfig = OperatorConfig { -- | The scheme of the operator. For example, "s3" or "gcs". ocScheme :: String, @@ -83,7 +113,7 @@ instance IsString OperatorConfig where fromString s = OperatorConfig s HashMap.empty Nothing -- | `Operator` is the entry for all public blocking APIs. --- Create an `Operator` with `newOp`. +-- Create an `Operator` with `newOperator`. newtype Operator = Operator (ForeignPtr RawOperator) -- | `Lister` is designed to list entries at given path in a blocking manner. @@ -149,8 +179,13 @@ data Metadata = Metadata } deriving (Eq, Show) --- | The monad used for OpenDAL operations. -type OpMonad = ReaderT Operator (ExceptT OpenDALError IO) +-- | @newtype@ wrapper 'ReaderT' that keeps 'Operator' in its context. +newtype OperatorT m a = OperatorT + {runOperatorT :: ReaderT Operator (ExceptT OpenDALError m) a} + deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader Operator, MonadError OpenDALError) + +instance MonadTrans OperatorT where + lift = OperatorT . lift . lift -- | A type class for monads that can perform OpenDAL operations. class (Monad m) => MonadOperation m where @@ -188,7 +223,7 @@ class (Monad m) => MonadOperation m where -- An error will be returned if given path doesn’t end with /. scanOp :: String -> m Lister -instance MonadOperation OpMonad where +instance (MonadIO m) => MonadOperation (OperatorT m) where readOp path = do op <- ask result <- liftIO $ readOpRaw op path @@ -288,11 +323,30 @@ 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`. +-- | Runner for 'OperatorT' monad. +-- This function will run given 'OperatorT' monad with given 'Operator'. +-- +-- Let's see an example: +-- +-- @ +-- operation :: MonadOperation m => m () +-- operation = __do__ +-- writeOp op "key1" "value1" +-- writeOp op "key2" "value2" +-- value1 <- readOp op "key1" +-- value2 <- readOp op "key2" +-- @ +-- +-- You can run this operation with 'runOp' function: +-- +-- @ +-- runOp operator operation +-- @ +runOp :: Operator -> OperatorT m a -> m (Either OpenDALError a) +runOp op = runExceptT . flip runReaderT op . runOperatorT +{-# INLINE runOp #-} + +-- | Creates a new OpenDAL operator via `OperatorConfig`. newOperator :: OperatorConfig -> IO (Either OpenDALError Operator) newOperator (OperatorConfig scheme hashMap maybeLogger) = do let keysAndValues = HashMap.toList hashMap @@ -322,6 +376,7 @@ newOperator (OperatorConfig scheme hashMap maybeLogger) = do str <- peekCString cStr logger <& Msg (toEnum (fromIntegral enumSeverity)) emptyCallStack (pack str) +-- $raw-operations -- 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. diff --git a/bindings/haskell/haskell-src/OpenDAL/FFI.hs b/bindings/haskell/haskell-src/OpenDAL/FFI.hs index 40f993fec68..ad28f451c9c 100644 --- a/bindings/haskell/haskell-src/OpenDAL/FFI.hs +++ b/bindings/haskell/haskell-src/OpenDAL/FFI.hs @@ -14,7 +14,6 @@ -- KIND, either express or implied. See the License for the -- specific language governing permissions and limitations -- under the License. -{-# LANGUAGE ForeignFunctionInterface #-} module OpenDAL.FFI where diff --git a/bindings/haskell/opendal-hs.cabal b/bindings/haskell/opendal-hs.cabal index 892a2d7d40b..5fae8c057e1 100644 --- a/bindings/haskell/opendal-hs.cabal +++ b/bindings/haskell/opendal-hs.cabal @@ -1,3 +1,4 @@ +cabal-version: 2.2 -- 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 @@ -15,7 +16,6 @@ -- 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 @@ -29,14 +29,7 @@ source-repository head type: git location: https://github.com/apache/incubator-opendal -library - exposed-modules: - OpenDAL - other-modules: - OpenDAL.FFI - hs-source-dirs: haskell-src - default-language: Haskell2010 - extra-libraries: opendal_hs +common base ghc-options: -Wall build-depends: base >=4.10 && <4.17, @@ -46,19 +39,30 @@ library time >=1.10, co-log >=0.5, text >=2 + default-language: Haskell2010 + default-extensions: + OverloadedStrings, + ForeignFunctionInterface, + DerivingStrategies, + GeneralizedNewtypeDeriving, + LambdaCase + +library + import: base + extra-libraries: opendal_hs + exposed-modules: + OpenDAL + other-modules: + OpenDAL.FFI + hs-source-dirs: haskell-src test-suite opendal-hs-test + import: base type: exitcode-stdio-1.0 main-is: Spec.hs other-modules: BasicTest hs-source-dirs: test - default-language: Haskell2010 - other-extensions: OverloadedStrings - ghc-options: -Wall build-depends: - base, opendal-hs, tasty, - tasty-hunit, - co-log, - text \ No newline at end of file + tasty-hunit \ No newline at end of file diff --git a/bindings/haskell/test/BasicTest.hs b/bindings/haskell/test/BasicTest.hs index dcf16d6d0fb..77e0a6586a2 100644 --- a/bindings/haskell/test/BasicTest.hs +++ b/bindings/haskell/test/BasicTest.hs @@ -14,8 +14,6 @@ -- 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