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 84fcc61d0 feat(binding/ocaml): Add support for operator reader and 
metadata (#2881)
84fcc61d0 is described below

commit 84fcc61d0986f5a0ffc4e584821ddddecf24e346
Author: ran <[email protected]>
AuthorDate: Sun Aug 20 21:53:27 2023 +0800

    feat(binding/ocaml): Add support for operator reader and metadata (#2881)
    
    * add metadata and operator reader
    
    * format code
    
    * update reader seek api
    
    * make lint happy
---
 bindings/ocaml/build.rs                            |  4 ++
 bindings/ocaml/lib/operator.ml                     | 26 +++++++++
 bindings/ocaml/lib/operator.mli                    | 60 ++++++++++++++++++++
 bindings/ocaml/src/lib.rs                          |  1 +
 bindings/ocaml/src/operator.ml                     | 25 +++++++-
 bindings/ocaml/src/operator.mli                    | 25 +++++++-
 bindings/ocaml/{build.rs => src/operator/_type.rs} | 21 ++++---
 bindings/ocaml/src/operator/metadata.rs            | 66 ++++++++++++++++++++++
 bindings/ocaml/src/operator/mod.rs                 | 27 +++++++--
 .../ocaml/{build.rs => src/operator/reader.rs}     | 21 +++++--
 bindings/ocaml/src/seek_from.ml                    | 27 +++++++++
 bindings/ocaml/src/seek_from.mli                   | 27 +++++++++
 bindings/ocaml/src/seek_from/mod.rs                | 58 +++++++++++++++++++
 bindings/ocaml/test/test.ml                        | 26 +++++++++
 14 files changed, 395 insertions(+), 19 deletions(-)

diff --git a/bindings/ocaml/build.rs b/bindings/ocaml/build.rs
index 9b35ddef4..de16a9e2f 100644
--- a/bindings/ocaml/build.rs
+++ b/bindings/ocaml/build.rs
@@ -19,6 +19,10 @@ use std::path::PathBuf;
 
 pub fn main() -> std::io::Result<()> {
     let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
+
+    ocaml_build::Sigs::new("src/seek_from.ml")
+        .with_source_dir(root.join("src/seek_from"))
+        .generate()?;
     ocaml_build::Sigs::new("src/operator.ml")
         .with_source_dir(root.join("src/operator"))
         .generate()
diff --git a/bindings/ocaml/lib/operator.ml b/bindings/ocaml/lib/operator.ml
index ccd6ec29d..a22a09d24 100644
--- a/bindings/ocaml/lib/operator.ml
+++ b/bindings/ocaml/lib/operator.ml
@@ -18,12 +18,38 @@
 *)
 
 let new_operator = Opendal_core.Operator.operator
+let stat = Opendal_core.Operator.blocking_stat
 let is_exist = Opendal_core.Operator.blocking_is_exist
 let create_dir = Opendal_core.Operator.blocking_create_dir
 let read = Opendal_core.Operator.blocking_read
+let reader = Opendal_core.Operator.blocking_reader
 let write = Opendal_core.Operator.blocking_write
 let copy = Opendal_core.Operator.blocking_copy
 let rename = Opendal_core.Operator.blocking_rename
 let delete = Opendal_core.Operator.blocking_delete
 let remove = Opendal_core.Operator.blocking_remove
 let remove_all = Opendal_core.Operator.blocking_remove_all
+
+module Reader = struct
+  let read = Opendal_core.Operator.reader_read
+
+  let seek reader pos mode =
+    let inner_pos =
+      match mode with
+      | Unix.SEEK_CUR -> Opendal_core.Seek_from.Current pos
+      | Unix.SEEK_END -> Opendal_core.Seek_from.End pos
+      | Unix.SEEK_SET -> Opendal_core.Seek_from.Start pos
+    in
+    Opendal_core.Operator.reader_seek reader inner_pos
+end
+
+module Metadata = struct
+  let is_file = Opendal_core.Operator.metadata_is_file
+  let is_dir = Opendal_core.Operator.metadata_is_dir
+  let content_length = Opendal_core.Operator.metadata_content_length
+  let content_md5 = Opendal_core.Operator.metadata_content_md5
+  let content_type = Opendal_core.Operator.metadata_content_type
+  let content_disposition = Opendal_core.Operator.metadata_content_disposition
+  let etag = Opendal_core.Operator.metadata_etag
+  let last_modified = Opendal_core.Operator.metadata_last_modified
+end
diff --git a/bindings/ocaml/lib/operator.mli b/bindings/ocaml/lib/operator.mli
index b94434c5b..e2493b89c 100644
--- a/bindings/ocaml/lib/operator.mli
+++ b/bindings/ocaml/lib/operator.mli
@@ -28,6 +28,17 @@ val new_operator :
     @return The block operator
 *)
 
+val stat :
+  Opendal_core.Operator.operator ->
+  string ->
+  (Opendal_core.Operator.metadata, string) result
+(** [is_exist operator path] Get current path's metadata **without cache** 
directly.
+    
+    @param operator The operator
+    @param path want to stat
+    @return metadata
+*)
+
 val is_exist : Opendal_core.Operator.operator -> string -> (bool, string) 
result
 (** [is_exist operator path] Check if this path exists or not.
     
@@ -63,6 +74,17 @@ val read :
     @return data of path
 *)
 
+val reader :
+  Opendal_core.Operator.operator ->
+  string ->
+  (Opendal_core.Operator.reader, string) result
+(** [read operator path] Create a new reader which can read the whole path.
+    
+    @param operator The operator
+    @param path want to read
+    @return reader
+*)
+
 val write :
   Opendal_core.Operator.operator -> string -> bytes -> (unit, string) result
 (** [write operator path data] Write bytes into given path.
@@ -117,3 +139,41 @@ val remove_all :
     @param operator The block operator
     @param path file path
 *)
+
+module Reader : sig
+  val read : Opendal_core.Operator.reader -> bytes -> (int, string) result
+  (** [read reader buf] Read data to [buf] and return data size.*)
+
+  val seek :
+    Opendal_core.Operator.reader ->
+    int64 ->
+    Unix.seek_command ->
+    (int64, string) result
+  (** [seek reader pos mode] is a function that seeks data to the given 
position [pos].*)
+end
+
+module Metadata : sig
+  val is_file : Opendal_core.Operator.metadata -> bool
+  (** [is_file metadata] Returns `true` if this metadata is for a file.*)
+
+  val is_dir : Opendal_core.Operator.metadata -> bool
+  (** [is_dir metadata] Returns `true` if this metadata is for a directory.*)
+
+  val content_length : Opendal_core.Operator.metadata -> int64
+  (** [content_length metadata] Content length of this entry.*)
+
+  val content_md5 : Opendal_core.Operator.metadata -> string option
+  (** [content_md5 metadata] Content MD5 of this entry.*)
+
+  val content_type : Opendal_core.Operator.metadata -> string option
+  (** [content_type metadata] Content Type of this entry.*)
+
+  val content_disposition : Opendal_core.Operator.metadata -> string option
+  (** [content_disposition metadata] Content-Disposition of this entry*)
+
+  val etag : Opendal_core.Operator.metadata -> string option
+  (** [etag metadata] ETag of this entry.*)
+
+  val last_modified : Opendal_core.Operator.metadata -> int64 option
+  (** [last_modified metadata] Last modified of this entry.*)
+end
diff --git a/bindings/ocaml/src/lib.rs b/bindings/ocaml/src/lib.rs
index f6ec3f2b8..a69c584f4 100644
--- a/bindings/ocaml/src/lib.rs
+++ b/bindings/ocaml/src/lib.rs
@@ -22,6 +22,7 @@ use std::str::FromStr;
 use ::opendal as od;
 
 mod operator;
+mod seek_from;
 
 pub fn new_operator(
     scheme_str: String,
diff --git a/bindings/ocaml/src/operator.ml b/bindings/ocaml/src/operator.ml
index daa339786..d911209b0 100644
--- a/bindings/ocaml/src/operator.ml
+++ b/bindings/ocaml/src/operator.ml
@@ -2,16 +2,39 @@
 
 open! Bigarray
 
-(* file: mod.rs *)
+(* file: _type.rs *)
 
 type operator
+type reader
+type metadata
+
+(* file: metadata.rs *)
+
+external metadata_is_file: metadata -> bool  = "metadata_is_file"
+external metadata_is_dir: metadata -> bool  = "metadata_is_dir"
+external metadata_content_length: metadata -> int64  = 
"metadata_content_length"
+external metadata_content_md5: metadata -> string option  = 
"metadata_content_md5"
+external metadata_content_type: metadata -> string option  = 
"metadata_content_type"
+external metadata_content_disposition: metadata -> string option  = 
"metadata_content_disposition"
+external metadata_etag: metadata -> string option  = "metadata_etag"
+external metadata_last_modified: metadata -> int64 option  = 
"metadata_last_modified"
+
+(* file: mod.rs *)
+
 external operator: string -> (string * string) list -> (operator, string) 
Result.t  = "operator"
+external blocking_stat: operator -> string -> (metadata, string) Result.t  = 
"blocking_stat"
 external blocking_is_exist: operator -> string -> (bool, string) Result.t  = 
"blocking_is_exist"
 external blocking_create_dir: operator -> string -> (bool, string) Result.t  = 
"blocking_create_dir"
 external blocking_read: operator -> string -> (char array, string) Result.t  = 
"blocking_read"
+external blocking_reader: operator -> string -> (reader, string) Result.t  = 
"blocking_reader"
 external blocking_write: operator -> string -> bytes -> (unit, string) 
Result.t  = "blocking_write"
 external blocking_copy: operator -> string -> string -> (unit, string) 
Result.t  = "blocking_copy"
 external blocking_rename: operator -> string -> string -> (unit, string) 
Result.t  = "blocking_rename"
 external blocking_delete: operator -> string -> (unit, string) Result.t  = 
"blocking_delete"
 external blocking_remove: operator -> string array -> (unit, string) Result.t  
= "blocking_remove"
 external blocking_remove_all: operator -> string -> (unit, string) Result.t  = 
"blocking_remove_all"
+
+(* file: reader.rs *)
+
+external reader_read: reader -> bytes -> (int, string) Result.t  = 
"reader_read"
+external reader_seek: reader -> Seek_from.seek_from -> (int64, string) 
Result.t  = "reader_seek"
diff --git a/bindings/ocaml/src/operator.mli b/bindings/ocaml/src/operator.mli
index daa339786..d911209b0 100644
--- a/bindings/ocaml/src/operator.mli
+++ b/bindings/ocaml/src/operator.mli
@@ -2,16 +2,39 @@
 
 open! Bigarray
 
-(* file: mod.rs *)
+(* file: _type.rs *)
 
 type operator
+type reader
+type metadata
+
+(* file: metadata.rs *)
+
+external metadata_is_file: metadata -> bool  = "metadata_is_file"
+external metadata_is_dir: metadata -> bool  = "metadata_is_dir"
+external metadata_content_length: metadata -> int64  = 
"metadata_content_length"
+external metadata_content_md5: metadata -> string option  = 
"metadata_content_md5"
+external metadata_content_type: metadata -> string option  = 
"metadata_content_type"
+external metadata_content_disposition: metadata -> string option  = 
"metadata_content_disposition"
+external metadata_etag: metadata -> string option  = "metadata_etag"
+external metadata_last_modified: metadata -> int64 option  = 
"metadata_last_modified"
+
+(* file: mod.rs *)
+
 external operator: string -> (string * string) list -> (operator, string) 
Result.t  = "operator"
+external blocking_stat: operator -> string -> (metadata, string) Result.t  = 
"blocking_stat"
 external blocking_is_exist: operator -> string -> (bool, string) Result.t  = 
"blocking_is_exist"
 external blocking_create_dir: operator -> string -> (bool, string) Result.t  = 
"blocking_create_dir"
 external blocking_read: operator -> string -> (char array, string) Result.t  = 
"blocking_read"
+external blocking_reader: operator -> string -> (reader, string) Result.t  = 
"blocking_reader"
 external blocking_write: operator -> string -> bytes -> (unit, string) 
Result.t  = "blocking_write"
 external blocking_copy: operator -> string -> string -> (unit, string) 
Result.t  = "blocking_copy"
 external blocking_rename: operator -> string -> string -> (unit, string) 
Result.t  = "blocking_rename"
 external blocking_delete: operator -> string -> (unit, string) Result.t  = 
"blocking_delete"
 external blocking_remove: operator -> string array -> (unit, string) Result.t  
= "blocking_remove"
 external blocking_remove_all: operator -> string -> (unit, string) Result.t  = 
"blocking_remove_all"
+
+(* file: reader.rs *)
+
+external reader_read: reader -> bytes -> (int, string) Result.t  = 
"reader_read"
+external reader_seek: reader -> Seek_from.seek_from -> (int64, string) 
Result.t  = "reader_seek"
diff --git a/bindings/ocaml/build.rs b/bindings/ocaml/src/operator/_type.rs
similarity index 61%
copy from bindings/ocaml/build.rs
copy to bindings/ocaml/src/operator/_type.rs
index 9b35ddef4..0dda2e119 100644
--- a/bindings/ocaml/build.rs
+++ b/bindings/ocaml/src/operator/_type.rs
@@ -15,11 +15,18 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::path::PathBuf;
+// For ocaml-rs, the build order in the same build group is the order of the 
file names.
+// In order to use the type in the function in the generated ocaml file, it 
must be defined on the first generated file.
+use super::*;
 
-pub fn main() -> std::io::Result<()> {
-    let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
-    ocaml_build::Sigs::new("src/operator.ml")
-        .with_source_dir(root.join("src/operator"))
-        .generate()
-}
+#[ocaml::sig]
+pub struct Operator(pub(crate) od::BlockingOperator);
+ocaml::custom!(Operator);
+
+#[ocaml::sig]
+pub struct Reader(pub(crate) od::BlockingReader);
+ocaml::custom!(Reader);
+
+#[ocaml::sig]
+pub struct Metadata(pub(crate) od::Metadata);
+ocaml::custom!(Metadata);
diff --git a/bindings/ocaml/src/operator/metadata.rs 
b/bindings/ocaml/src/operator/metadata.rs
new file mode 100644
index 000000000..551df21af
--- /dev/null
+++ b/bindings/ocaml/src/operator/metadata.rs
@@ -0,0 +1,66 @@
+// 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.
+
+use super::*;
+
+#[ocaml::func]
+#[ocaml::sig("metadata -> bool ")]
+pub fn metadata_is_file(metadata: &mut Metadata) -> bool {
+    metadata.0.is_file()
+}
+
+#[ocaml::func]
+#[ocaml::sig("metadata -> bool ")]
+pub fn metadata_is_dir(metadata: &mut Metadata) -> bool {
+    metadata.0.is_dir()
+}
+
+#[ocaml::func]
+#[ocaml::sig("metadata -> int64 ")]
+pub fn metadata_content_length(metadata: &mut Metadata) -> u64 {
+    metadata.0.content_length()
+}
+
+#[ocaml::func]
+#[ocaml::sig("metadata -> string option ")]
+pub fn metadata_content_md5(metadata: &mut Metadata) -> Option<String> {
+    metadata.0.content_md5().map(String::from)
+}
+
+#[ocaml::func]
+#[ocaml::sig("metadata -> string option ")]
+pub fn metadata_content_type(metadata: &mut Metadata) -> Option<String> {
+    metadata.0.content_type().map(String::from)
+}
+
+#[ocaml::func]
+#[ocaml::sig("metadata -> string option ")]
+pub fn metadata_content_disposition(metadata: &mut Metadata) -> Option<String> 
{
+    metadata.0.content_disposition().map(String::from)
+}
+
+#[ocaml::func]
+#[ocaml::sig("metadata -> string option ")]
+pub fn metadata_etag(metadata: &mut Metadata) -> Option<String> {
+    metadata.0.etag().map(String::from)
+}
+
+#[ocaml::func]
+#[ocaml::sig("metadata -> int64 option ")]
+pub fn metadata_last_modified(metadata: &mut Metadata) -> Option<i64> {
+    metadata.0.last_modified().map(|t| t.timestamp())
+}
diff --git a/bindings/ocaml/src/operator/mod.rs 
b/bindings/ocaml/src/operator/mod.rs
index 5915c916b..833cf6391 100644
--- a/bindings/ocaml/src/operator/mod.rs
+++ b/bindings/ocaml/src/operator/mod.rs
@@ -15,11 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use super::*;
+mod _type;
+mod metadata;
+mod reader;
 
-#[ocaml::sig]
-pub struct Operator(od::BlockingOperator);
-ocaml::custom!(Operator);
+use super::*;
+use _type::*;
 
 #[ocaml::func]
 #[ocaml::sig("string -> (string * string) list -> (operator, string) Result.t 
")]
@@ -31,6 +32,15 @@ pub fn operator(
     Ok(Operator(op.blocking()).into())
 }
 
+#[ocaml::func]
+#[ocaml::sig("operator -> string -> (metadata, string) Result.t ")]
+pub fn blocking_stat(
+    operator: &mut Operator,
+    path: String,
+) -> Result<ocaml::Pointer<Metadata>, String> {
+    map_res_error(operator.0.stat(path.as_str()).map(|m| Metadata(m).into()))
+}
+
 #[ocaml::func]
 #[ocaml::sig("operator -> string -> (bool, string) Result.t ")]
 pub fn blocking_is_exist(operator: &mut Operator, path: String) -> 
Result<bool, String> {
@@ -49,6 +59,15 @@ pub fn blocking_read(operator: &mut Operator, path: String) 
-> Result<Vec<u8>, S
     map_res_error(operator.0.read(path.as_str()))
 }
 
+#[ocaml::func]
+#[ocaml::sig("operator -> string -> (reader, string) Result.t ")]
+pub fn blocking_reader(
+    operator: &mut Operator,
+    path: String,
+) -> Result<ocaml::Pointer<Reader>, String> {
+    map_res_error(operator.0.reader(path.as_str())).map(|op| Reader(op).into())
+}
+
 #[ocaml::func]
 #[ocaml::sig("operator -> string -> bytes -> (unit, string) Result.t ")]
 pub fn blocking_write(
diff --git a/bindings/ocaml/build.rs b/bindings/ocaml/src/operator/reader.rs
similarity index 61%
copy from bindings/ocaml/build.rs
copy to bindings/ocaml/src/operator/reader.rs
index 9b35ddef4..bb75f7b38 100644
--- a/bindings/ocaml/build.rs
+++ b/bindings/ocaml/src/operator/reader.rs
@@ -15,11 +15,20 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::path::PathBuf;
+use std::io;
 
-pub fn main() -> std::io::Result<()> {
-    let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
-    ocaml_build::Sigs::new("src/operator.ml")
-        .with_source_dir(root.join("src/operator"))
-        .generate()
+use super::*;
+
+use opendal::raw::oio::BlockingRead;
+
+#[ocaml::func]
+#[ocaml::sig("reader -> bytes -> (int, string) Result.t ")]
+pub fn reader_read(reader: &mut Reader, buf: &mut [u8]) -> Result<usize, 
String> {
+    map_res_error(reader.0.read(buf))
+}
+
+#[ocaml::func]
+#[ocaml::sig("reader -> Seek_from.seek_from -> (int64, string) Result.t ")]
+pub fn reader_seek(reader: &mut Reader, pos: seek_from::SeekFrom) -> 
Result<u64, String> {
+    map_res_error(reader.0.seek(io::SeekFrom::from(pos)))
 }
diff --git a/bindings/ocaml/src/seek_from.ml b/bindings/ocaml/src/seek_from.ml
new file mode 100644
index 000000000..64b6f0cb9
--- /dev/null
+++ b/bindings/ocaml/src/seek_from.ml
@@ -0,0 +1,27 @@
+(* Generated by ocaml-rs *)
+
+open! Bigarray
+
+(* file: mod.rs *)
+
+type seek_from = 
+| Start of int64  (** 
+    [Start]: Sets the offset to the provided number of bytes.
+    *)
+
+| End of int64 (** 
+    [End]: Sets the offset to the size of this object plus the specified 
number of
+    bytes.
+
+    It is possible to seek beyond the end of an object, but it's an error to
+    seek before byte 0.
+    *)
+
+| Current of int64 (** 
+    [Current]: Sets the offset to the current position plus the specified 
number of
+    bytes.
+
+    It is possible to seek beyond the end of an object, but it's an error to
+    seek before byte 0.  
+    *)
+
diff --git a/bindings/ocaml/src/seek_from.mli b/bindings/ocaml/src/seek_from.mli
new file mode 100644
index 000000000..64b6f0cb9
--- /dev/null
+++ b/bindings/ocaml/src/seek_from.mli
@@ -0,0 +1,27 @@
+(* Generated by ocaml-rs *)
+
+open! Bigarray
+
+(* file: mod.rs *)
+
+type seek_from = 
+| Start of int64  (** 
+    [Start]: Sets the offset to the provided number of bytes.
+    *)
+
+| End of int64 (** 
+    [End]: Sets the offset to the size of this object plus the specified 
number of
+    bytes.
+
+    It is possible to seek beyond the end of an object, but it's an error to
+    seek before byte 0.
+    *)
+
+| Current of int64 (** 
+    [Current]: Sets the offset to the current position plus the specified 
number of
+    bytes.
+
+    It is possible to seek beyond the end of an object, but it's an error to
+    seek before byte 0.  
+    *)
+
diff --git a/bindings/ocaml/src/seek_from/mod.rs 
b/bindings/ocaml/src/seek_from/mod.rs
new file mode 100644
index 000000000..e9b0290b3
--- /dev/null
+++ b/bindings/ocaml/src/seek_from/mod.rs
@@ -0,0 +1,58 @@
+// 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.
+
+use std::io;
+
+#[derive(ocaml::FromValue, ocaml::ToValue, Clone, Copy)]
+#[ocaml::sig(
+    "
+| Start of int64  (** 
+    [Start]: Sets the offset to the provided number of bytes.
+    *)
+
+| End of int64 (** 
+    [End]: Sets the offset to the size of this object plus the specified 
number of
+    bytes.
+
+    It is possible to seek beyond the end of an object, but it's an error to
+    seek before byte 0.
+    *)
+
+| Current of int64 (** 
+    [Current]: Sets the offset to the current position plus the specified 
number of
+    bytes.
+
+    It is possible to seek beyond the end of an object, but it's an error to
+    seek before byte 0.  
+    *)
+"
+)]
+pub enum SeekFrom {
+    Start(u64),
+    End(i64),
+    Current(i64),
+}
+
+impl From<SeekFrom> for io::SeekFrom {
+    fn from(value: SeekFrom) -> Self {
+        match value {
+            SeekFrom::Start(v) => io::SeekFrom::Start(v),
+            SeekFrom::End(v) => io::SeekFrom::End(v),
+            SeekFrom::Current(v) => io::SeekFrom::Current(v),
+        }
+    }
+}
diff --git a/bindings/ocaml/test/test.ml b/bindings/ocaml/test/test.ml
index a92fc470b..a13e44b88 100644
--- a/bindings/ocaml/test/test.ml
+++ b/bindings/ocaml/test/test.ml
@@ -58,6 +58,30 @@ let test_copy_and_read test_ctxt =
   let got_res = test_check_result (Operator.read bo "bar") in
   assert_equal data (got_res |> Array.to_seq |> Bytes.of_seq |> 
Bytes.to_string)
 
+let test_operator_reader test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+  ignore
+    (test_check_result
+       (Operator.write bo "tempfile" (Bytes.of_string "helloworld")));
+  let reader = Operator.reader bo "tempfile" |> test_check_result in
+  let s = Operator.Reader.seek reader 5L SEEK_CUR |> test_check_result in
+  assert_equal 5 (Int64.to_int s);
+  let data = Bytes.create 5 in
+  let i = Operator.Reader.read reader data |> test_check_result in
+  assert_equal 5 i;
+  assert_equal "world" (Bytes.to_string data)
+
+let test_operator_stat test_ctxt =
+  let bo = new_test_block_operator test_ctxt in
+  ignore
+    (test_check_result
+       (Operator.write bo "tempfile" (Bytes.of_string "helloworld")));
+  let metadata = Operator.stat bo "tempfile" |> test_check_result in
+  assert_equal false (Operator.Metadata.is_dir metadata);
+  assert_equal true (Operator.Metadata.is_file metadata);
+  assert_equal 10L (Operator.Metadata.content_length metadata);
+  ()
+
 let suite =
   "suite"
   >::: [
@@ -65,6 +89,8 @@ let suite =
          "test_create_dir_and_remove_all" >:: test_create_dir_and_remove_all;
          "test_block_write_and_read" >:: test_block_write_and_read;
          "test_copy_and_read" >:: test_copy_and_read;
+         "test_operator_reader" >:: test_operator_reader;
+         "test_operator_stat" >:: test_operator_stat;
        ]
 
 let () = run_test_tt_main suite

Reply via email to