This is an automated email from the ASF dual-hosted git repository.

dentiny pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new dc0069760 feat(binding/go): add write with metadata (#7827)
dc0069760 is described below

commit dc0069760c7632bff80bc6a6142bfb633ca4dc9f
Author: Ryan Huang <[email protected]>
AuthorDate: Sat Jul 4 21:06:45 2026 +0900

    feat(binding/go): add write with metadata (#7827)
---
 bindings/c/include/opendal.h                       |  71 +++++++++++++
 bindings/c/src/lib.rs                              |   1 +
 bindings/c/src/operator.rs                         |  67 ++++++++++++
 bindings/c/src/result.rs                           |  14 +++
 bindings/c/src/writer.rs                           |  38 +++++++
 bindings/go/examples/guide/getting_started.go      |   2 +-
 bindings/go/opendal.go                             |   4 +-
 bindings/go/string_ownership_test.go               |  36 +++++--
 bindings/go/tests/behavior_tests/benchmark_test.go |   3 +-
 bindings/go/tests/behavior_tests/copy_test.go      |  70 +++++++-----
 bindings/go/tests/behavior_tests/delete_test.go    |  12 ++-
 bindings/go/tests/behavior_tests/list_test.go      |  45 +++++---
 bindings/go/tests/behavior_tests/presign_test.go   |  15 ++-
 bindings/go/tests/behavior_tests/read_test.go      |  60 +++++++----
 bindings/go/tests/behavior_tests/rename_test.go    |  28 +++--
 bindings/go/tests/behavior_tests/stat_test.go      |  31 ++++--
 bindings/go/tests/behavior_tests/write_test.go     |  99 +++++++++++++----
 bindings/go/types.go                               |  14 +++
 bindings/go/writer.go                              | 118 +++++++++++----------
 19 files changed, 547 insertions(+), 181 deletions(-)

diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h
index c835d980c..692b164fb 100644
--- a/bindings/c/include/opendal.h
+++ b/bindings/c/include/opendal.h
@@ -384,6 +384,25 @@ typedef struct opendal_write_options {
   uintptr_t user_metadata_len;
 } opendal_write_options;
 
+/**
+ * \brief The result type returned by the metadata-returning write operations.
+ *
+ * Returned by opendal_operator_write_with_metadata() and
+ * opendal_writer_close_with_metadata(). On success the `meta` field holds the
+ * metadata of the just-written object (e.g. etag, version, last modified) and
+ * `error` is null; on failure `meta` is null and `error` is set.
+ */
+typedef struct opendal_result_write {
+  /**
+   * The metadata of the written object, or null on error.
+   */
+  struct opendal_metadata *meta;
+  /**
+   * The error, if ok, it is null
+   */
+  struct opendal_error *error;
+} opendal_result_write;
+
 /**
  * \brief The result type returned by opendal's read operation.
  *
@@ -1511,6 +1530,43 @@ struct opendal_error *opendal_operator_write_with(const 
struct opendal_operator
                                                   const struct opendal_bytes 
*bytes,
                                                   const struct 
opendal_write_options *opts);
 
+/**
+ * \brief Blocking write raw bytes to `path`, returning the written object's 
metadata.
+ *
+ * Like `opendal_operator_write_with`, but on success returns the metadata of 
the
+ * just-written object (e.g. etag, version, last modified) instead of 
discarding it.
+ * A NULL `opts` is treated as the default options, behaving like a plain 
write.
+ *
+ * @param op The opendal_operator created previously
+ * @param path The designated path where the data will be written
+ * @param bytes The data to write
+ * @param opts The write options, or NULL to use the defaults
+ * @see opendal_operator
+ * @see opendal_write_options
+ * @see opendal_result_write
+ * @return Returns opendal_result_write, containing the metadata and an 
opendal_error.
+ * If the operation succeeds, the `meta` field holds the metadata and the 
`error` field
+ * is null. Otherwise, the `meta` will be null and the `error` will be set 
correspondingly.
+ *
+ * \note The returned metadata must be freed with opendal_metadata_free().
+ *
+ * # Safety
+ *
+ * It is **safe** under the cases below
+ * * The memory pointed to by `path` must contain a valid nul terminator at 
the end of
+ *   the string.
+ * * The `bytes` provided has valid byte in the `data` field and the `len` 
field is set
+ *   correctly.
+ *
+ * # Panic
+ *
+ * * If the `path` points to NULL, this function panics, i.e. exits with 
information
+ */
+struct opendal_result_write opendal_operator_write_with_metadata(const struct 
opendal_operator *op,
+                                                                 const char 
*path,
+                                                                 const struct 
opendal_bytes *bytes,
+                                                                 const struct 
opendal_write_options *opts);
+
 /**
  * \brief Blocking read the data from `path`.
  *
@@ -2879,6 +2935,21 @@ struct opendal_result_writer_write 
opendal_writer_write(struct opendal_writer *s
  */
 struct opendal_error *opendal_writer_close(struct opendal_writer *ptr);
 
+/**
+ * \brief Close the writer and return the written object's metadata.
+ *
+ * Like `opendal_writer_close`, but on success returns the metadata of the
+ * written object (e.g. etag, version, last modified) instead of discarding it.
+ *
+ * @param ptr The opendal_writer to close
+ * @see opendal_result_write
+ * @return Returns opendal_result_write. On success the `meta` field holds the
+ * metadata and `error` is null; on failure `meta` is null and `error` is set.
+ *
+ * \note The returned metadata must be freed with opendal_metadata_free().
+ */
+struct opendal_result_write opendal_writer_close_with_metadata(struct 
opendal_writer *ptr);
+
 /**
  * \brief Frees the heap memory used by the opendal_writer.
  */
diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs
index 9948055c5..ac44831fc 100644
--- a/bindings/c/src/lib.rs
+++ b/bindings/c/src/lib.rs
@@ -68,6 +68,7 @@ pub use result::opendal_result_operator_writer;
 pub use result::opendal_result_read;
 pub use result::opendal_result_reader_read;
 pub use result::opendal_result_stat;
+pub use result::opendal_result_write;
 pub use result::opendal_result_writer_write;
 
 mod types;
diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs
index ef2480818..9f7991c43 100644
--- a/bindings/c/src/operator.rs
+++ b/bindings/c/src/operator.rs
@@ -312,6 +312,73 @@ pub unsafe extern "C" fn opendal_operator_write_with(
     }
 }
 
+/// \brief Blocking write raw bytes to `path`, returning the written object's 
metadata.
+///
+/// Like `opendal_operator_write_with`, but on success returns the metadata of 
the
+/// just-written object (e.g. etag, version, last modified) instead of 
discarding it.
+/// A NULL `opts` is treated as the default options, behaving like a plain 
write.
+///
+/// @param op The opendal_operator created previously
+/// @param path The designated path where the data will be written
+/// @param bytes The data to write
+/// @param opts The write options, or NULL to use the defaults
+/// @see opendal_operator
+/// @see opendal_write_options
+/// @see opendal_result_write
+/// @return Returns opendal_result_write, containing the metadata and an 
opendal_error.
+/// If the operation succeeds, the `meta` field holds the metadata and the 
`error` field
+/// is null. Otherwise, the `meta` will be null and the `error` will be set 
correspondingly.
+///
+/// \note The returned metadata must be freed with opendal_metadata_free().
+///
+/// # Safety
+///
+/// It is **safe** under the cases below
+/// * The memory pointed to by `path` must contain a valid nul terminator at 
the end of
+///   the string.
+/// * The `bytes` provided has valid byte in the `data` field and the `len` 
field is set
+///   correctly.
+///
+/// # Panic
+///
+/// * If the `path` points to NULL, this function panics, i.e. exits with 
information
+#[no_mangle]
+pub unsafe extern "C" fn opendal_operator_write_with_metadata(
+    op: &opendal_operator,
+    path: *const c_char,
+    bytes: &opendal_bytes,
+    opts: *const opendal_write_options,
+) -> opendal_result_write {
+    assert!(!path.is_null());
+    let path = std::ffi::CStr::from_ptr(path)
+        .to_str()
+        .expect("malformed path");
+    let opts = if opts.is_null() {
+        core::options::WriteOptions::default()
+    } else {
+        (&*opts).into()
+    };
+    let bytes = match bytes.to_buffer() {
+        Ok(bytes) => bytes,
+        Err(e) => {
+            return opendal_result_write {
+                meta: std::ptr::null_mut(),
+                error: opendal_error::new(e),
+            }
+        }
+    };
+    match op.deref().write_options(path, bytes, opts) {
+        Ok(m) => opendal_result_write {
+            meta: Box::into_raw(Box::new(opendal_metadata::new(m))),
+            error: std::ptr::null_mut(),
+        },
+        Err(e) => opendal_result_write {
+            meta: std::ptr::null_mut(),
+            error: opendal_error::new(e),
+        },
+    }
+}
+
 /// \brief Blocking read the data from `path`.
 ///
 /// Read the data out from `path` blocking by operator.
diff --git a/bindings/c/src/result.rs b/bindings/c/src/result.rs
index 601a80138..0caaa40de 100644
--- a/bindings/c/src/result.rs
+++ b/bindings/c/src/result.rs
@@ -180,3 +180,17 @@ pub struct opendal_result_writer_write {
     /// The error, if ok, it is null
     pub error: *mut opendal_error,
 }
+
+/// \brief The result type returned by the metadata-returning write operations.
+///
+/// Returned by opendal_operator_write_with_metadata() and
+/// opendal_writer_close_with_metadata(). On success the `meta` field holds the
+/// metadata of the just-written object (e.g. etag, version, last modified) and
+/// `error` is null; on failure `meta` is null and `error` is set.
+#[repr(C)]
+pub struct opendal_result_write {
+    /// The metadata of the written object, or null on error.
+    pub meta: *mut opendal_metadata,
+    /// The error, if ok, it is null
+    pub error: *mut opendal_error,
+}
diff --git a/bindings/c/src/writer.rs b/bindings/c/src/writer.rs
index acd5e39c8..3c61d8738 100644
--- a/bindings/c/src/writer.rs
+++ b/bindings/c/src/writer.rs
@@ -92,6 +92,44 @@ impl opendal_writer {
         }
     }
 
+    /// \brief Close the writer and return the written object's metadata.
+    ///
+    /// Like `opendal_writer_close`, but on success returns the metadata of the
+    /// written object (e.g. etag, version, last modified) instead of 
discarding it.
+    ///
+    /// @param ptr The opendal_writer to close
+    /// @see opendal_result_write
+    /// @return Returns opendal_result_write. On success the `meta` field 
holds the
+    /// metadata and `error` is null; on failure `meta` is null and `error` is 
set.
+    ///
+    /// \note The returned metadata must be freed with opendal_metadata_free().
+    #[no_mangle]
+    pub unsafe extern "C" fn opendal_writer_close_with_metadata(
+        ptr: *mut opendal_writer,
+    ) -> opendal_result_write {
+        unsafe {
+            if ptr.is_null() {
+                return opendal_result_write {
+                    meta: std::ptr::null_mut(),
+                    error: std::ptr::null_mut(),
+                };
+            }
+            match (*ptr).deref_mut().close() {
+                Ok(m) => opendal_result_write {
+                    meta: Box::into_raw(Box::new(opendal_metadata::new(m))),
+                    error: std::ptr::null_mut(),
+                },
+                Err(e) => opendal_result_write {
+                    meta: std::ptr::null_mut(),
+                    error: opendal_error::new(
+                        core::Error::new(core::ErrorKind::Unexpected, "close 
writer failed")
+                            .set_source(e),
+                    ),
+                },
+            }
+        }
+    }
+
     /// \brief Frees the heap memory used by the opendal_writer.
     #[no_mangle]
     pub unsafe extern "C" fn opendal_writer_free(ptr: *mut opendal_writer) {
diff --git a/bindings/go/examples/guide/getting_started.go 
b/bindings/go/examples/guide/getting_started.go
index 49d20ebf8..21536d706 100644
--- a/bindings/go/examples/guide/getting_started.go
+++ b/bindings/go/examples/guide/getting_started.go
@@ -42,7 +42,7 @@ func main() {
        defer op.Close() // Always close the operator to release resources.
 
        // The same verbs work on every service.
-       if err := op.Write("hello.txt", []byte("Hello, World!")); err != nil {
+       if _, err := op.Write("hello.txt", []byte("Hello, World!")); err != nil 
{
                log.Fatal(err)
        }
 
diff --git a/bindings/go/opendal.go b/bindings/go/opendal.go
index d550faee7..bffcea0f2 100644
--- a/bindings/go/opendal.go
+++ b/bindings/go/opendal.go
@@ -46,7 +46,7 @@
 //             defer op.Close()
 //
 //             // Perform operations using the operator
-//             err = op.Write("example.txt", []byte("Hello, OpenDAL!"))
+//             _, err = op.Write("example.txt", []byte("Hello, OpenDAL!"))
 //             if err != nil {
 //                     log.Fatal(err)
 //             }
@@ -122,7 +122,7 @@ type OperatorOptions map[string]string
 //             defer op.Close() // Ensure the operator is closed when done
 //
 //             // Perform operations using the operator
-//             err = op.Write("example.txt", []byte("Hello, OpenDAL!"))
+//             _, err = op.Write("example.txt", []byte("Hello, OpenDAL!"))
 //             if err != nil {
 //                     log.Fatal(err)
 //             }
diff --git a/bindings/go/string_ownership_test.go 
b/bindings/go/string_ownership_test.go
index 3b074c6f6..3c01bf536 100644
--- a/bindings/go/string_ownership_test.go
+++ b/bindings/go/string_ownership_test.go
@@ -674,30 +674,46 @@ func TestWriteWithOptions(t *testing.T) {
        }
 }
 
-func TestFfiOperatorWriteWithArgTypes(t *testing.T) {
-       aTypes := ffiOperatorWriteWith.opts.aTypes
-       if len(aTypes) != 4 {
-               t.Fatalf("ffiOperatorWriteWith aTypes len = %d, want 4", 
len(aTypes))
+func TestFfiOperatorWriterWithArgTypes(t *testing.T) {
+       aTypes := ffiOperatorWriterWith.opts.aTypes
+       if len(aTypes) != 3 {
+               t.Fatalf("ffiOperatorWriterWith aTypes len = %d, want 3", 
len(aTypes))
        }
        for i, at := range aTypes {
                if at != &ffi.TypePointer {
-                       t.Fatalf("ffiOperatorWriteWith aTypes[%d] = %v, want 
TypePointer", i, at)
+                       t.Fatalf("ffiOperatorWriterWith aTypes[%d] = %v, want 
TypePointer", i, at)
                }
        }
 }
 
-func TestFfiOperatorWriterWithArgTypes(t *testing.T) {
-       aTypes := ffiOperatorWriterWith.opts.aTypes
-       if len(aTypes) != 3 {
-               t.Fatalf("ffiOperatorWriterWith aTypes len = %d, want 3", 
len(aTypes))
+func TestFfiOperatorWriteWithMetadataSignature(t *testing.T) {
+       if ffiOperatorWriteWithMetadata.opts.rType != &typeResultWrite {
+               t.Fatalf("ffiOperatorWriteWithMetadata rType = %v, want 
typeResultWrite", ffiOperatorWriteWithMetadata.opts.rType)
+       }
+       aTypes := ffiOperatorWriteWithMetadata.opts.aTypes
+       if len(aTypes) != 4 {
+               t.Fatalf("ffiOperatorWriteWithMetadata aTypes len = %d, want 
4", len(aTypes))
        }
        for i, at := range aTypes {
                if at != &ffi.TypePointer {
-                       t.Fatalf("ffiOperatorWriterWith aTypes[%d] = %v, want 
TypePointer", i, at)
+                       t.Fatalf("ffiOperatorWriteWithMetadata aTypes[%d] = %v, 
want TypePointer", i, at)
                }
        }
 }
 
+func TestFfiWriterCloseWithMetadataSignature(t *testing.T) {
+       if ffiWriterCloseWithMetadata.opts.rType != &typeResultWrite {
+               t.Fatalf("ffiWriterCloseWithMetadata rType = %v, want 
typeResultWrite", ffiWriterCloseWithMetadata.opts.rType)
+       }
+       aTypes := ffiWriterCloseWithMetadata.opts.aTypes
+       if len(aTypes) != 1 {
+               t.Fatalf("ffiWriterCloseWithMetadata aTypes len = %d, want 1", 
len(aTypes))
+       }
+       if aTypes[0] != &ffi.TypePointer {
+               t.Fatalf("ffiWriterCloseWithMetadata aTypes[0] = %v, want 
TypePointer", aTypes[0])
+       }
+}
+
 func TestWriteOptionsSetterArgTypes(t *testing.T) {
        stringSetters := []*FFI[func(*opendalWriteOptions, string) ([]byte, 
error)]{
                ffiWriteOptionsSetCacheControl,
diff --git a/bindings/go/tests/behavior_tests/benchmark_test.go 
b/bindings/go/tests/behavior_tests/benchmark_test.go
index 830e95103..a0aa164ea 100644
--- a/bindings/go/tests/behavior_tests/benchmark_test.go
+++ b/bindings/go/tests/behavior_tests/benchmark_test.go
@@ -137,7 +137,8 @@ func NewOpenDALReadWriter(op *opendal.Operator) ReadWriter {
 }
 
 func (rw *OpenDALReadWriter) Write(path string, data []byte) error {
-       return rw.Operator.Write(path, data)
+       _, err := rw.Operator.Write(path, data)
+       return err
 }
 
 func (rw *OpenDALReadWriter) Read(path string) ([]byte, error) {
diff --git a/bindings/go/tests/behavior_tests/copy_test.go 
b/bindings/go/tests/behavior_tests/copy_test.go
index 5f5ed1d1a..eb81559d5 100644
--- a/bindings/go/tests/behavior_tests/copy_test.go
+++ b/bindings/go/tests/behavior_tests/copy_test.go
@@ -60,7 +60,8 @@ func testsCopy(cap *opendal.Capability) []behaviorTest {
 func testCopyFileWithASCIIName(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath := fixture.NewFilePath()
 
@@ -75,7 +76,8 @@ func testCopyFileWithNonASCIIName(assert *require.Assertions, 
op *opendal.Operat
        sourcePath, sourceContent, _ := fixture.NewFileWithPath("🐂🍺中文.docx")
        targetPath := fixture.PushPath("😈🐅Français.docx")
 
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
        assert.Nil(op.Copy(sourcePath, targetPath))
 
        targetContent, err := op.Read(targetPath)
@@ -106,13 +108,14 @@ func testCopySourceDir(assert *require.Assertions, op 
*opendal.Operator, fixture
 func testCopyTargetDir(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath := fixture.NewDirPath()
 
        assert.Nil(op.CreateDir(targetPath))
 
-       err := op.Copy(sourcePath, targetPath)
+       err = op.Copy(sourcePath, targetPath)
        assert.NotNil(err, "copy must fail")
        assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err))
 }
@@ -120,9 +123,10 @@ func testCopyTargetDir(assert *require.Assertions, op 
*opendal.Operator, fixture
 func testCopySelf(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
-       err := op.Copy(sourcePath, sourcePath)
+       err = op.Copy(sourcePath, sourcePath)
        assert.NotNil(err, "copy must fail")
        assert.Equal(opendal.CodeIsSameFile, assertErrorCode(err))
 }
@@ -130,7 +134,8 @@ func testCopySelf(assert *require.Assertions, op 
*opendal.Operator, fixture *fix
 func testCopyNested(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath := fixture.PushPath(fmt.Sprintf(
                "%s/%s/%s",
@@ -149,23 +154,26 @@ func testCopyNested(assert *require.Assertions, op 
*opendal.Operator, fixture *f
 func testCopyOverwrite(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath, targetContent, _ := fixture.NewFile()
        assert.NotEqual(sourceContent, targetContent)
 
-       assert.Nil(op.Write(targetPath, targetContent))
+       _, err = op.Write(targetPath, targetContent)
+       assert.Nil(err)
 
        assert.Nil(op.Copy(sourcePath, targetPath))
 
-       targetContent, err := op.Read(targetPath)
+       targetContent, err = op.Read(targetPath)
        assert.Nil(err, "read must succeed")
        assert.Equal(sourceContent, targetContent)
 }
 
 func testCopyWithIfNotExistsToNewFile(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath := fixture.NewFilePath()
        assert.Nil(op.Copy(sourcePath, targetPath, 
opendal.CopyWithIfNotExists(true)))
@@ -177,12 +185,14 @@ func testCopyWithIfNotExistsToNewFile(assert 
*require.Assertions, op *opendal.Op
 
 func testCopyWithIfNotExistsToExistingFile(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath, targetContent, _ := fixture.NewFile()
-       assert.Nil(op.Write(targetPath, targetContent))
+       _, err = op.Write(targetPath, targetContent)
+       assert.Nil(err)
 
-       err := op.Copy(sourcePath, targetPath, 
opendal.CopyWithIfNotExists(true))
+       err = op.Copy(sourcePath, targetPath, opendal.CopyWithIfNotExists(true))
        assert.NotNil(err)
        assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err))
 
@@ -193,11 +203,13 @@ func testCopyWithIfNotExistsToExistingFile(assert 
*require.Assertions, op *opend
 
 func testCopyWithIfMatchMatch(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath, targetContent, _ := fixture.NewFile()
        assert.NotEqual(sourceContent, targetContent)
-       assert.Nil(op.Write(targetPath, targetContent))
+       _, err = op.Write(targetPath, targetContent)
+       assert.Nil(err)
 
        meta, err := op.Stat(targetPath)
        assert.Nil(err, "stat must succeed")
@@ -212,13 +224,15 @@ func testCopyWithIfMatchMatch(assert *require.Assertions, 
op *opendal.Operator,
 
 func testCopyWithIfMatchMismatch(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath, targetContent, _ := fixture.NewFile()
        assert.NotEqual(sourceContent, targetContent)
-       assert.Nil(op.Write(targetPath, targetContent))
+       _, err = op.Write(targetPath, targetContent)
+       assert.Nil(err)
 
-       err := op.Copy(sourcePath, targetPath, 
opendal.CopyWithIfMatch("wrong-etag"))
+       err = op.Copy(sourcePath, targetPath, 
opendal.CopyWithIfMatch("wrong-etag"))
        assert.NotNil(err)
        assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err))
 
@@ -229,7 +243,8 @@ func testCopyWithIfMatchMismatch(assert 
*require.Assertions, op *opendal.Operato
 
 func testCopyWithSourceVersionToNewFile(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        meta, err := op.Stat(sourcePath)
        assert.Nil(err, "stat must succeed")
@@ -239,7 +254,8 @@ func testCopyWithSourceVersionToNewFile(assert 
*require.Assertions, op *opendal.
        }
 
        newContent := genFixedBytes(uint(len(sourceContent)) + 1)
-       assert.Nil(op.Write(sourcePath, newContent), "overwrite must succeed")
+       _, err = op.Write(sourcePath, newContent)
+       assert.Nil(err, "overwrite must succeed")
 
        targetPath := fixture.NewFilePath()
        assert.Nil(op.Copy(sourcePath, targetPath, 
opendal.CopyWithSourceVersion(version)))
@@ -251,7 +267,8 @@ func testCopyWithSourceVersionToNewFile(assert 
*require.Assertions, op *opendal.
 
 func testCopyWithSourceVersionToSameFile(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        meta, err := op.Stat(sourcePath)
        assert.Nil(err, "stat must succeed")
@@ -261,7 +278,8 @@ func testCopyWithSourceVersionToSameFile(assert 
*require.Assertions, op *opendal
        }
 
        newContent := genFixedBytes(uint(len(sourceContent)) + 1)
-       assert.Nil(op.Write(sourcePath, newContent), "overwrite must succeed")
+       _, err = op.Write(sourcePath, newContent)
+       assert.Nil(err, "overwrite must succeed")
 
        assert.Nil(op.Copy(sourcePath, sourcePath, 
opendal.CopyWithSourceVersion(version)))
 
@@ -290,7 +308,8 @@ func testCopyWithChunk(assert *require.Assertions, op 
*opendal.Operator, fixture
 
        sourcePath := fixture.NewFilePath()
        sourceContent := genFixedBytes(sourceSize)
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath := fixture.NewFilePath()
        assert.Nil(op.Copy(sourcePath, targetPath, 
opendal.CopyWithChunk(uint(chunk))))
@@ -308,7 +327,8 @@ func testCopyWithChunkAndConcurrent(assert 
*require.Assertions, op *opendal.Oper
 
        sourcePath := fixture.NewFilePath()
        sourceContent := genFixedBytes(sourceSize)
-       assert.Nil(op.Write(sourcePath, sourceContent))
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err)
 
        targetPath := fixture.NewFilePath()
        assert.Nil(op.Copy(sourcePath, targetPath, 
opendal.CopyWithChunk(uint(chunk)), opendal.CopyWithConcurrent(4)))
diff --git a/bindings/go/tests/behavior_tests/delete_test.go 
b/bindings/go/tests/behavior_tests/delete_test.go
index 32bd75b8f..702e59079 100644
--- a/bindings/go/tests/behavior_tests/delete_test.go
+++ b/bindings/go/tests/behavior_tests/delete_test.go
@@ -49,7 +49,8 @@ func testsDelete(cap *opendal.Capability) []behaviorTest {
 func testDeleteFile(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        assert.Nil(op.Delete(path))
 
@@ -72,7 +73,8 @@ func testDeleteWithSpecialChars(assert *require.Assertions, 
op *opendal.Operator
        path := uuid.NewString() + " !@#$%^&()_+-=;',.txt"
        path, content, _ := fixture.NewFileWithPath(path)
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        assert.Nil(op.Delete(path))
 
@@ -97,7 +99,8 @@ func testDeleteWithRecursive(assert *require.Assertions, op 
*opendal.Operator, f
        var filePaths []string
        for i := range 3 {
                path, content, _ := 
fixture.NewFileWithPath(fmt.Sprintf("%sfile-%d.txt", dir, i))
-               assert.Nil(op.Write(path, content), "write must succeed")
+               _, err := op.Write(path, content)
+               assert.Nil(err, "write must succeed")
                filePaths = append(filePaths, path)
        }
 
@@ -112,7 +115,8 @@ func testDeleteWithRecursive(assert *require.Assertions, op 
*opendal.Operator, f
 func testDeleteWithVersion(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
diff --git a/bindings/go/tests/behavior_tests/list_test.go 
b/bindings/go/tests/behavior_tests/list_test.go
index c681ba856..7a3f60488 100644
--- a/bindings/go/tests/behavior_tests/list_test.go
+++ b/bindings/go/tests/behavior_tests/list_test.go
@@ -71,7 +71,8 @@ func testListDir(assert *require.Assertions, op 
*opendal.Operator, fixture *fixt
        parent := fixture.NewDirPath()
        path, content, size := fixture.NewFileWithPath(fmt.Sprintf("%s%s", 
parent, uuid.NewString()))
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        obs, err := op.List(parent)
        assert.Nil(err)
@@ -104,7 +105,8 @@ func testListRichDir(assert *require.Assertions, op 
*opendal.Operator, fixture *
        for range 10 {
                path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", 
parent, uuid.NewString()))
                expected = append(expected, path)
-               assert.Nil(op.Write(path, content))
+               _, err := op.Write(path, content)
+               assert.Nil(err)
        }
 
        obs, err := op.List(parent)
@@ -197,7 +199,8 @@ func testListNestedDir(assert *require.Assertions, op 
*opendal.Operator, fixture
 
        assert.Nil(op.CreateDir(parent), "create must succeed")
        assert.Nil(op.CreateDir(dir), "create must succeed")
-       assert.Nil(op.Write(filePath, []byte("test_list_nested_dir")), "write 
must succeed")
+       _, err := op.Write(filePath, []byte("test_list_nested_dir"))
+       assert.Nil(err, "write must succeed")
        assert.Nil(op.CreateDir(dirPath), "create must succeed")
 
        obs, err := op.List(parent)
@@ -260,7 +263,8 @@ func testListEntryMetadata(assert *require.Assertions, op 
*opendal.Operator, fix
        assert.Nil(op.CreateDir(parent))
 
        filePath, content, size := fixture.NewFileWithPath(fmt.Sprintf("%s%s", 
parent, uuid.NewString()))
-       assert.Nil(op.Write(filePath, content))
+       _, err := op.Write(filePath, content)
+       assert.Nil(err)
 
        obs, err := op.List(parent)
        assert.Nil(err)
@@ -294,7 +298,8 @@ func testListDirWithFilePath(assert *require.Assertions, op 
*opendal.Operator, f
        parent := fixture.NewDirPath()
        path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, 
uuid.NewString()))
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        obs, err := op.List(strings.TrimSuffix(parent, "/"))
        assert.Nil(err)
@@ -315,8 +320,10 @@ func testListWithDefaultOptions(assert 
*require.Assertions, op *opendal.Operator
 
        assert.Nil(op.CreateDir(parent))
        assert.Nil(op.CreateDir(subDir))
-       assert.Nil(op.Write(fileInParent, content))
-       assert.Nil(op.Write(fileInSub, content))
+       _, err := op.Write(fileInParent, content)
+       assert.Nil(err)
+       _, err = op.Write(fileInSub, content)
+       assert.Nil(err)
 
        obs, err := op.List(parent)
        assert.Nil(err)
@@ -348,9 +355,12 @@ func testListWithRecursive(assert *require.Assertions, op 
*opendal.Operator, fix
        assert.Nil(op.CreateDir(parent))
        assert.Nil(op.CreateDir(subDir))
        assert.Nil(op.CreateDir(deepDir))
-       assert.Nil(op.Write(fileTop, content))
-       assert.Nil(op.Write(fileMid, content))
-       assert.Nil(op.Write(fileDeep, content))
+       _, err := op.Write(fileTop, content)
+       assert.Nil(err)
+       _, err = op.Write(fileMid, content)
+       assert.Nil(err)
+       _, err = op.Write(fileDeep, content)
+       assert.Nil(err)
 
        obs, err := op.List(parent, opendal.ListWithRecursive(true))
        assert.Nil(err)
@@ -374,7 +384,8 @@ func testListWithLimit(assert *require.Assertions, op 
*opendal.Operator, fixture
        // Write 5 files.
        for range 5 {
                path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", 
parent, uuid.NewString()))
-               assert.Nil(op.Write(path, content))
+               _, err := op.Write(path, content)
+               assert.Nil(err)
        }
 
        // List with limit=2; the operation must succeed without error.
@@ -400,7 +411,8 @@ func testListWithStartAfter(assert *require.Assertions, op 
*opendal.Operator, fi
        for i := range 5 {
                name := fmt.Sprintf("%sfile-%02d", parent, i)
                filePaths = append(filePaths, name)
-               assert.Nil(op.Write(name, []byte("content")))
+               _, err := op.Write(name, []byte("content"))
+               assert.Nil(err)
        }
        slices.Sort(filePaths)
 
@@ -426,8 +438,10 @@ func testListWithVersions(assert *require.Assertions, op 
*opendal.Operator, fixt
        parent := fixture.NewDirPath()
        path, _, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, 
uuid.NewString()))
 
-       assert.Nil(op.Write(path, []byte("version-1")), "first write must 
succeed")
-       assert.Nil(op.Write(path, []byte("version-2")), "second write must 
succeed")
+       _, err := op.Write(path, []byte("version-1"))
+       assert.Nil(err, "first write must succeed")
+       _, err = op.Write(path, []byte("version-2"))
+       assert.Nil(err, "second write must succeed")
 
        obs, err := op.List(path, opendal.ListWithVersions(true))
        assert.Nil(err, "list with versions must succeed")
@@ -457,7 +471,8 @@ func testListWithDeleted(assert *require.Assertions, op 
*opendal.Operator, fixtu
        parent := fixture.NewDirPath()
        path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, 
uuid.NewString()))
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        obs, err := op.List(path, opendal.ListWithDeleted(true))
        assert.Nil(err, "list with deleted must succeed before deletion")
diff --git a/bindings/go/tests/behavior_tests/presign_test.go 
b/bindings/go/tests/behavior_tests/presign_test.go
index 39f80d6c9..eff7df225 100644
--- a/bindings/go/tests/behavior_tests/presign_test.go
+++ b/bindings/go/tests/behavior_tests/presign_test.go
@@ -80,7 +80,8 @@ func testPresignWrite(assert *require.Assertions, op 
*opendal.Operator, fixture
 func testPresignRead(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        req, err := op.PresignRead(path, time.Hour)
        assert.Nil(err)
@@ -105,7 +106,8 @@ func testPresignReadWithRange(assert *require.Assertions, 
op *opendal.Operator,
        path := fixture.NewFilePath()
        content := genFixedBytes(32)
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        req, err := op.PresignRead(path, time.Hour, 
opendal.ReadWithRange(offset, length))
        assert.Nil(err)
@@ -125,7 +127,8 @@ func testPresignReadWithRange(assert *require.Assertions, 
op *opendal.Operator,
 func testPresignStat(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        req, err := op.PresignStat(path, time.Hour)
        assert.Nil(err)
@@ -146,7 +149,8 @@ func testPresignStatWithOverrideContentType(assert 
*require.Assertions, op *open
        path, content, _ := fixture.NewFile()
        contentType := "application/octet-stream"
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        req, err := op.PresignStat(path, time.Hour, 
opendal.StatWithOverrideContentType(contentType))
        assert.Nil(err)
@@ -161,7 +165,8 @@ func testPresignStatWithOverrideContentType(assert 
*require.Assertions, op *open
 func testPresignDelete(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        req, err := op.PresignDelete(path, time.Hour)
        assert.Nil(err)
diff --git a/bindings/go/tests/behavior_tests/read_test.go 
b/bindings/go/tests/behavior_tests/read_test.go
index b7c49ade2..f5b7b8f1f 100644
--- a/bindings/go/tests/behavior_tests/read_test.go
+++ b/bindings/go/tests/behavior_tests/read_test.go
@@ -72,7 +72,8 @@ func testsRead(cap *opendal.Capability) []behaviorTest {
 func testReadWithConcurrentChunkGap(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        bs, err := op.Read(path,
                opendal.ReadWithConcurrent(2),
@@ -89,7 +90,8 @@ func testReadWithWriteOptions(assert *require.Assertions, op 
*opendal.Operator,
        content := genFixedBytes(1024 * 1024)
        offset, length := genOffsetLength(uint(len(content)))
 
-       assert.Nil(op.Write(path, content, opendal.WriteWithChunk(256*1024), 
opendal.WriteWithConcurrent(2)))
+       _, err := op.Write(path, content, opendal.WriteWithChunk(256*1024), 
opendal.WriteWithConcurrent(2))
+       assert.Nil(err)
 
        bs, err := op.Read(path,
                opendal.ReadWithRange(uint64(offset), uint64(length)),
@@ -105,7 +107,8 @@ func testReadWithWriteOptions(assert *require.Assertions, 
op *opendal.Operator,
 func testReadWithIfModifiedSince(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
@@ -123,7 +126,8 @@ func testReadWithIfModifiedSince(assert 
*require.Assertions, op *opendal.Operato
 func testReadWithIfUnmodifiedSince(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
@@ -141,7 +145,8 @@ func testReadWithIfUnmodifiedSince(assert 
*require.Assertions, op *opendal.Opera
 func testReadWithVersion(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
@@ -155,7 +160,8 @@ func testReadWithVersion(assert *require.Assertions, op 
*opendal.Operator, fixtu
        assert.Equal(content, data, "read content")
 
        // After overwriting, the previous version data is still readable.
-       assert.Nil(op.Write(path, []byte("1")), "overwrite must succeed")
+       _, err = op.Write(path, []byte("1"))
+       assert.Nil(err, "overwrite must succeed")
        second, err := op.Read(path, opendal.ReadWithVersion(version))
        assert.Nil(err)
        assert.Equal(content, second, "read old version content")
@@ -165,7 +171,8 @@ func testReadWithRange(assert *require.Assertions, op 
*opendal.Operator, fixture
        path, content, size := fixture.NewFile()
        offset, length := genOffsetLength(size)
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        bs, err := op.Read(path, opendal.ReadWithRange(uint64(offset), 
uint64(length)))
        assert.Nil(err)
@@ -177,7 +184,8 @@ func testReadWithRangeFrom(assert *require.Assertions, op 
*opendal.Operator, fix
        path, content, size := fixture.NewFile()
        offset, _ := genOffsetLength(size)
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        bs, err := op.Read(path, opendal.ReadWithRangeFrom(uint64(offset)))
        assert.Nil(err)
@@ -188,7 +196,8 @@ func testReadWithRangeFrom(assert *require.Assertions, op 
*opendal.Operator, fix
 func testReadWithContentLengthHint(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        // An accurate hint must not change the result of a full read.
        bs, err := op.Read(path, 
opendal.ReadWithContentLengthHint(uint64(size)))
@@ -210,7 +219,8 @@ func testReadWithContentLengthHint(assert 
*require.Assertions, op *opendal.Opera
 func testReadWithIfMatch(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
@@ -231,7 +241,8 @@ func testReadWithIfMatch(assert *require.Assertions, op 
*opendal.Operator, fixtu
 func testReadWithIfNoneMatch(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
@@ -252,7 +263,8 @@ func testReadWithIfNoneMatch(assert *require.Assertions, op 
*opendal.Operator, f
 func testReadFull(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        bs, err := op.Read(path)
        assert.Nil(err)
@@ -263,7 +275,8 @@ func testReadFull(assert *require.Assertions, op 
*opendal.Operator, fixture *fix
 func testReader(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        r, err := op.Reader(path)
        assert.Nil(err)
@@ -300,7 +313,8 @@ func testReadWithDirPath(assert *require.Assertions, op 
*opendal.Operator, fixtu
 func testReadWithSpecialChars(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        path, content, size := fixture.NewFileWithPath(uuid.NewString() + " 
!@#$%^&()_+-=;',.txt")
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        bs, err := op.Read(path)
        assert.Nil(err)
@@ -311,7 +325,8 @@ func testReadWithSpecialChars(assert *require.Assertions, 
op *opendal.Operator,
 func testIOCopy(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        r, err := op.Reader(path)
        assert.Nil(err)
@@ -326,7 +341,8 @@ func testIOCopy(assert *require.Assertions, op 
*opendal.Operator, fixture *fixtu
        assert.Equal(size, uint(n), "read size")
 
        assert.Nil(r.Close(), "close reader must succeed")
-       assert.Nil(w.Close(), "close writer must succeed")
+       _, err = w.Close()
+       assert.Nil(err, "close writer must succeed")
 
        copyContent, err := op.Read(pathCopy)
        assert.Nil(err)
@@ -338,7 +354,8 @@ func testReaderSeek(assert *require.Assertions, op 
*opendal.Operator, fixture *f
        path, content, size := fixture.NewFile()
        offset, length := genOffsetLength(size)
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        r, err := op.Reader(path)
        assert.Nil(err)
@@ -375,7 +392,8 @@ func testReaderSeek(assert *require.Assertions, op 
*opendal.Operator, fixture *f
 func testReaderWithConcurrentChunkGap(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        r, err := op.Reader(path,
                opendal.ReaderWithConcurrent(2),
@@ -396,7 +414,8 @@ func testReaderWithConcurrentChunkGap(assert 
*require.Assertions, op *opendal.Op
 func testReaderWithIfMatch(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
@@ -430,7 +449,8 @@ func testReaderWithIfMatch(assert *require.Assertions, op 
*opendal.Operator, fix
 func testReaderWithVersion(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
diff --git a/bindings/go/tests/behavior_tests/rename_test.go 
b/bindings/go/tests/behavior_tests/rename_test.go
index 5a6f79b63..7775c25d6 100644
--- a/bindings/go/tests/behavior_tests/rename_test.go
+++ b/bindings/go/tests/behavior_tests/rename_test.go
@@ -45,13 +45,14 @@ func testsRename(cap *opendal.Capability) []behaviorTest {
 func testRenameFile(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed")
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err, "write must succeed")
 
        targetPath := fixture.NewFilePath()
 
        assert.Nil(op.Rename(sourcePath, targetPath))
 
-       _, err := op.Stat(sourcePath)
+       _, err = op.Stat(sourcePath)
        assert.NotNil(err, "stat must fail")
        assert.Equal(opendal.CodeNotFound, assertErrorCode(err))
 
@@ -91,12 +92,13 @@ func testRenameTargetDir(assert *require.Assertions, op 
*opendal.Operator, fixtu
 
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed")
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err, "write must succeed")
 
        targetPath := fixture.NewDirPath()
        assert.Nil(op.CreateDir(targetPath))
 
-       err := op.Rename(sourcePath, targetPath)
+       err = op.Rename(sourcePath, targetPath)
        assert.NotNil(err)
        assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err))
 }
@@ -104,9 +106,10 @@ func testRenameTargetDir(assert *require.Assertions, op 
*opendal.Operator, fixtu
 func testRenameSelf(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed")
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err, "write must succeed")
 
-       err := op.Rename(sourcePath, sourcePath)
+       err = op.Rename(sourcePath, sourcePath)
        assert.NotNil(err)
        assert.Equal(opendal.CodeIsSameFile, assertErrorCode(err))
 }
@@ -114,7 +117,8 @@ func testRenameSelf(assert *require.Assertions, op 
*opendal.Operator, fixture *f
 func testRenameNested(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed")
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err, "write must succeed")
 
        targetPath := fixture.PushPath(fmt.Sprintf(
                "%s/%s/%s",
@@ -125,7 +129,7 @@ func testRenameNested(assert *require.Assertions, op 
*opendal.Operator, fixture
 
        assert.Nil(op.Rename(sourcePath, targetPath))
 
-       _, err := op.Stat(sourcePath)
+       _, err = op.Stat(sourcePath)
        assert.NotNil(err, "stat must fail")
        assert.Equal(opendal.CodeNotFound, assertErrorCode(err))
 
@@ -137,16 +141,18 @@ func testRenameNested(assert *require.Assertions, op 
*opendal.Operator, fixture
 func testRenameOverwrite(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        sourcePath, sourceContent, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(sourcePath, sourceContent), "write must succeed")
+       _, err := op.Write(sourcePath, sourceContent)
+       assert.Nil(err, "write must succeed")
 
        targetPath, targetContent, _ := fixture.NewFile()
        assert.NotEqual(sourceContent, targetContent)
 
-       assert.Nil(op.Write(targetPath, targetContent), "write must succeed")
+       _, err = op.Write(targetPath, targetContent)
+       assert.Nil(err, "write must succeed")
 
        assert.Nil(op.Rename(sourcePath, targetPath))
 
-       _, err := op.Stat(sourcePath)
+       _, err = op.Stat(sourcePath)
        assert.NotNil(err, "stat must fail")
        assert.Equal(opendal.CodeNotFound, assertErrorCode(err))
 
diff --git a/bindings/go/tests/behavior_tests/stat_test.go 
b/bindings/go/tests/behavior_tests/stat_test.go
index c47c84949..7bf9c3ad1 100644
--- a/bindings/go/tests/behavior_tests/stat_test.go
+++ b/bindings/go/tests/behavior_tests/stat_test.go
@@ -71,7 +71,8 @@ func assertOptionalMetaString(assert *require.Assertions, 
name string, accessor
 func testStatFile(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err)
@@ -113,7 +114,8 @@ func testStatNestedParentDir(assert *require.Assertions, op 
*opendal.Operator, f
        parent := fixture.NewDirPath()
        path, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s", parent, 
uuid.NewString()))
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(parent)
        assert.Nil(err)
@@ -123,7 +125,8 @@ func testStatNestedParentDir(assert *require.Assertions, op 
*opendal.Operator, f
 func testStatWithSpecialChars(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        path, content, size := fixture.NewFileWithPath(uuid.NewString() + " 
!@#$%^&()_+-=;',.txt")
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err)
@@ -134,7 +137,8 @@ func testStatWithSpecialChars(assert *require.Assertions, 
op *opendal.Operator,
 func testStatNotCleanedPath(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(fmt.Sprintf("//%s", path))
        assert.Nil(err)
@@ -199,10 +203,13 @@ func testStatFileMetadata(assert *require.Assertions, op 
*opendal.Operator, fixt
        }
 
        before := time.Now().Add(-time.Hour)
+       var err error
        if len(writeOpts) == 0 {
-               assert.Nil(op.Write(path, content), "write must succeed")
+               _, err = op.Write(path, content)
+               assert.Nil(err, "write must succeed")
        } else {
-               assert.Nil(op.Write(path, content, writeOpts...), "write with 
metadata must succeed")
+               _, err = op.Write(path, content, writeOpts...)
+               assert.Nil(err, "write with metadata must succeed")
        }
 
        meta, err := op.Stat(path)
@@ -274,7 +281,8 @@ func testStatWithIfMatch(assert *require.Assertions, op 
*opendal.Operator, fixtu
        if isCapEnabled(cap.WriteWithContentType, "write_with_content_type") {
                writeOpts = append(writeOpts, 
opendal.WriteWithContentType("text/plain"))
        }
-       assert.Nil(op.Write(path, content, writeOpts...), "write must succeed")
+       _, err := op.Write(path, content, writeOpts...)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -300,7 +308,8 @@ func testStatWithIfNoneMatch(assert *require.Assertions, op 
*opendal.Operator, f
 
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -326,7 +335,8 @@ func testStatWithIfModifiedSince(assert 
*require.Assertions, op *opendal.Operato
 
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -353,7 +363,8 @@ func testStatWithIfUnmodifiedSince(assert 
*require.Assertions, op *opendal.Opera
 
        path, content, _ := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content), "write must succeed")
+       _, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
diff --git a/bindings/go/tests/behavior_tests/write_test.go 
b/bindings/go/tests/behavior_tests/write_test.go
index 72084ad3b..976cbd35a 100644
--- a/bindings/go/tests/behavior_tests/write_test.go
+++ b/bindings/go/tests/behavior_tests/write_test.go
@@ -46,13 +46,16 @@ func testsWrite(cap *opendal.Capability) []behaviorTest {
                testWriterWrite,
                testWriteWithChunkAndConcurrent,
                testWriterWithAppend,
+               testWriteReturnsMetadata,
+               testWriterCloseReturnsMetadata,
        }
 }
 
 func testWriteOnly(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, size := fixture.NewFile()
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -65,7 +68,8 @@ func testWriteWithEmptyContent(assert *require.Assertions, op 
*opendal.Operator,
        }
 
        path := fixture.NewFilePath()
-       assert.Nil(op.Write(path, []byte{}))
+       _, err := op.Write(path, []byte{})
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -75,7 +79,7 @@ func testWriteWithEmptyContent(assert *require.Assertions, op 
*opendal.Operator,
 func testWriteWithDirPath(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path := fixture.NewDirPath()
 
-       err := op.Write(path, []byte("1"))
+       _, err := op.Write(path, []byte("1"))
        assert.NotNil(err)
        assert.Equal(opendal.CodeIsADirectory, assertErrorCode(err))
 }
@@ -83,7 +87,8 @@ func testWriteWithDirPath(assert *require.Assertions, op 
*opendal.Operator, fixt
 func testWriteWithSpecialChars(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
        path, content, size := fixture.NewFileWithPath(uuid.NewString() + " 
!@#$%^&()_+-=;',.txt")
 
-       assert.Nil(op.Write(path, content))
+       _, err := op.Write(path, content)
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -99,12 +104,14 @@ func testWriteOverwrite(assert *require.Assertions, op 
*opendal.Operator, fixtur
        size := uint(5 * 1024 * 1024)
        contentOne, contentTwo := genFixedBytes(size), genFixedBytes(size)
 
-       assert.Nil(op.Write(path, contentOne))
+       _, err := op.Write(path, contentOne)
+       assert.Nil(err)
        bs, err := op.Read(path)
        assert.Nil(err, "read must succeed")
        assert.Equal(contentOne, bs, "read content_one")
 
-       assert.Nil(op.Write(path, contentTwo))
+       _, err = op.Write(path, contentTwo)
+       assert.Nil(err)
        bs, err = op.Read(path)
        assert.Nil(err, "read must succeed")
        assert.NotEqual(contentOne, bs, "content_one must be overwrote")
@@ -118,7 +125,8 @@ func testWriteWithCacheControl(assert *require.Assertions, 
op *opendal.Operator,
 
        path := fixture.NewFilePath()
        content := []byte("hello")
-       assert.Nil(op.Write(path, content, 
opendal.WriteWithCacheControl("max-age=60")))
+       _, err := op.Write(path, content, 
opendal.WriteWithCacheControl("max-age=60"))
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -135,7 +143,8 @@ func testWriteWithContentType(assert *require.Assertions, 
op *opendal.Operator,
 
        path := fixture.NewFilePath()
        content := []byte("hello")
-       assert.Nil(op.Write(path, content, 
opendal.WriteWithContentType("text/plain")))
+       _, err := op.Write(path, content, 
opendal.WriteWithContentType("text/plain"))
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -152,7 +161,8 @@ func testWriteWithContentDisposition(assert 
*require.Assertions, op *opendal.Ope
 
        path := fixture.NewFilePath()
        content := []byte("hello")
-       assert.Nil(op.Write(path, content, 
opendal.WriteWithContentDisposition("attachment; filename=hello.txt")))
+       _, err := op.Write(path, content, 
opendal.WriteWithContentDisposition("attachment; filename=hello.txt"))
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -169,7 +179,8 @@ func testWriteWithContentEncoding(assert 
*require.Assertions, op *opendal.Operat
 
        path := fixture.NewFilePath()
        content := []byte("hello")
-       assert.Nil(op.Write(path, content, 
opendal.WriteWithContentEncoding("gzip")))
+       _, err := op.Write(path, content, 
opendal.WriteWithContentEncoding("gzip"))
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -186,10 +197,11 @@ func testWriteWithUserMetadata(assert 
*require.Assertions, op *opendal.Operator,
 
        path := fixture.NewFilePath()
        content := []byte("hello")
-       assert.Nil(op.Write(path, content, 
opendal.WriteWithUserMetadata(map[string]string{
+       _, err := op.Write(path, content, 
opendal.WriteWithUserMetadata(map[string]string{
                "language": "go",
                "project":  "opendal",
-       })))
+       }))
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -206,13 +218,15 @@ func testWriteWithIfMatch(assert *require.Assertions, op 
*opendal.Operator, fixt
        }
 
        path := fixture.NewFilePath()
-       assert.Nil(op.Write(path, []byte("hello")))
+       _, err := op.Write(path, []byte("hello"))
+       assert.Nil(err)
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
        etag, ok := meta.ETag()
        assert.True(ok, "etag must exist")
 
-       assert.Nil(op.Write(path, []byte("world"), 
opendal.WriteWithIfMatch(etag)))
+       _, err = op.Write(path, []byte("world"), opendal.WriteWithIfMatch(etag))
+       assert.Nil(err)
        bs, err := op.Read(path)
        assert.Nil(err, "read must succeed")
        assert.Equal([]byte("world"), bs)
@@ -224,13 +238,14 @@ func testWriteWithIfNoneMatch(assert *require.Assertions, 
op *opendal.Operator,
        }
 
        path := fixture.NewFilePath()
-       assert.Nil(op.Write(path, []byte("hello")))
+       _, err := op.Write(path, []byte("hello"))
+       assert.Nil(err)
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
        etag, ok := meta.ETag()
        assert.True(ok, "etag must exist")
 
-       err = op.Write(path, []byte("world"), 
opendal.WriteWithIfNoneMatch(etag))
+       _, err = op.Write(path, []byte("world"), 
opendal.WriteWithIfNoneMatch(etag))
        assert.NotNil(err)
        assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err))
 
@@ -245,8 +260,9 @@ func testWriteWithIfNotExists(assert *require.Assertions, 
op *opendal.Operator,
        }
 
        path := fixture.NewFilePath()
-       assert.Nil(op.Write(path, []byte("hello"), 
opendal.WriteWithIfNotExists(true)))
-       err := op.Write(path, []byte("world"), 
opendal.WriteWithIfNotExists(true))
+       _, err := op.Write(path, []byte("hello"), 
opendal.WriteWithIfNotExists(true))
+       assert.Nil(err)
+       _, err = op.Write(path, []byte("world"), 
opendal.WriteWithIfNotExists(true))
        assert.NotNil(err)
        assert.Equal(opendal.CodeConditionNotMatch, assertErrorCode(err))
 
@@ -271,7 +287,8 @@ func testWriterWrite(assert *require.Assertions, op 
*opendal.Operator, fixture *
        assert.Nil(err)
        _, err = w.Write(contentB)
        assert.Nil(err)
-       assert.Nil(w.Close())
+       _, err = w.Close()
+       assert.Nil(err)
 
        meta, err := op.Stat(path)
        assert.Nil(err, "stat must succeed")
@@ -291,7 +308,8 @@ func testWriteWithChunkAndConcurrent(assert 
*require.Assertions, op *opendal.Ope
 
        path := fixture.NewFilePath()
        content := genFixedBytes(1024 * 1024)
-       assert.Nil(op.Write(path, content, opendal.WriteWithChunk(256*1024), 
opendal.WriteWithConcurrent(2)))
+       _, err := op.Write(path, content, opendal.WriteWithChunk(256*1024), 
opendal.WriteWithConcurrent(2))
+       assert.Nil(err)
 
        bs, err := op.Read(path)
        assert.Nil(err, "read must succeed")
@@ -309,15 +327,52 @@ func testWriterWithAppend(assert *require.Assertions, op 
*opendal.Operator, fixt
        assert.Nil(err)
        _, err = w.Write([]byte("hello"))
        assert.Nil(err)
-       assert.Nil(w.Close())
+       _, err = w.Close()
+       assert.Nil(err)
 
        w, err = op.Writer(path, opendal.WriteWithAppend(true))
        assert.Nil(err)
        _, err = w.Write([]byte(" world"))
        assert.Nil(err)
-       assert.Nil(w.Close())
+       _, err = w.Close()
+       assert.Nil(err)
 
        bs, err := op.Read(path)
        assert.Nil(err, "read must succeed")
        assert.Equal([]byte("hello world"), bs)
 }
+
+func testWriteReturnsMetadata(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
+       path, content, _ := fixture.NewFile()
+
+       // Write returns the metadata of the written object. Which fields are
+       // populated is service-dependent (e.g. content length may be 0), so 
only
+       // assert that metadata is returned and the content round-trips.
+       meta, err := op.Write(path, content)
+       assert.Nil(err, "write must succeed")
+       assert.NotNil(meta, "write must return metadata")
+
+       data, err := op.Read(path)
+       assert.Nil(err)
+       assert.Equal(content, data, "written content")
+}
+
+func testWriterCloseReturnsMetadata(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
+       path := fixture.NewFilePath()
+       content := []byte("hello opendal write metadata")
+
+       w, err := op.Writer(path)
+       assert.Nil(err)
+       _, err = w.Write(content)
+       assert.Nil(err)
+
+       // Close returns the metadata of the written object; fields are
+       // service-dependent, so only assert that metadata is returned.
+       meta, err := w.Close()
+       assert.Nil(err, "close must succeed")
+       assert.NotNil(meta, "close must return metadata")
+
+       data, err := op.Read(path)
+       assert.Nil(err)
+       assert.Equal(content, data, "written content")
+}
diff --git a/bindings/go/types.go b/bindings/go/types.go
index c00e7dab8..8af6abaa2 100644
--- a/bindings/go/types.go
+++ b/bindings/go/types.go
@@ -63,6 +63,15 @@ var (
                }[0],
        }
 
+       typeResultWrite = ffi.Type{
+               Type: ffi.Struct,
+               Elements: &[]*ffi.Type{
+                       &ffi.TypePointer,
+                       &ffi.TypePointer,
+                       nil,
+               }[0],
+       }
+
        typeResultList = ffi.Type{
                Type: ffi.Struct,
                Elements: &[]*ffi.Type{
@@ -320,6 +329,11 @@ type resultStat struct {
        error *opendalError
 }
 
+type resultWrite struct {
+       meta  *opendalMetadata
+       error *opendalError
+}
+
 type resultPresign struct {
        req   *opendalPresignedRequest
        error *opendalError
diff --git a/bindings/go/writer.go b/bindings/go/writer.go
index 9c4415fe0..ef77f2135 100644
--- a/bindings/go/writer.go
+++ b/bindings/go/writer.go
@@ -30,10 +30,13 @@ import (
        "github.com/jupiterrider/ffi"
 )
 
-// Write writes the given bytes to the specified path.
+// Write writes the given bytes to the specified path and returns the metadata
+// of the written object (such as etag, version, or last modified) as reported
+// by the underlying service.
 //
-// Write is a wrapper around the C-binding function `opendal_operator_write`.
-// When options are provided, it uses `opendal_operator_write_with`.
+// Write is a wrapper around the C-binding function
+// `opendal_operator_write_with_metadata`. Which fields of the returned 
Metadata
+// are populated depends on the service.
 //
 // # Parameters
 //
@@ -43,32 +46,41 @@ import (
 //
 // # Returns
 //
+//   - *Metadata: Metadata of the written object. Which fields are populated
+//     depends on the service.
 //   - error: An error if the write operation fails, or nil if successful.
 //
 // # Example
 //
 //     func exampleWrite(op *opendal.Operator) {
-//             err = op.Write("test", []byte("Hello opendal go binding!"))
+//             _, err := op.Write("test", []byte("Hello opendal go binding!"))
 //             if err != nil {
 //                     log.Fatal(err)
 //             }
 //     }
 //
 // Note: This example assumes proper error handling and import statements.
-func (op *Operator) Write(path string, data []byte, opts ...WithWriteFn) error 
{
+func (op *Operator) Write(path string, data []byte, opts ...WithWriteFn) 
(*Metadata, error) {
        if len(opts) == 0 {
-               return ffiOperatorWrite.symbol(op.ctx)(op.inner, path, data)
+               meta, err := 
ffiOperatorWriteWithMetadata.symbol(op.ctx)(op.inner, path, data, nil)
+               if err != nil {
+                       return nil, err
+               }
+               return newMetadata(op.ctx, meta), nil
        }
 
        o := parseWriteOptions(opts...)
        cOpts, keepAlive, err := newOpendalWriteOptions(op.ctx, o)
        if err != nil {
-               return err
+               return nil, err
        }
        defer ffiWriteOptionsFree.symbol(op.ctx)(cOpts)
-       err = ffiOperatorWriteWith.symbol(op.ctx)(op.inner, path, data, cOpts)
+       meta, err := ffiOperatorWriteWithMetadata.symbol(op.ctx)(op.inner, 
path, data, cOpts)
        runtime.KeepAlive(keepAlive)
-       return err
+       if err != nil {
+               return nil, err
+       }
+       return newMetadata(op.ctx, meta), nil
 }
 
 // WithWriteFn is a functional option for write operations.
@@ -373,7 +385,7 @@ type Writer struct {
 // # Example
 //
 //     func exampleWrite(op *opendal.Operator) {
-//             err = op.Write("test", []byte("Hello opendal go binding!"))
+//             _, err = op.Write("test", []byte("Hello opendal go binding!"))
 //             if err != nil {
 //                     log.Fatal(err)
 //             }
@@ -384,58 +396,51 @@ func (w *Writer) Write(p []byte) (n int, err error) {
        return ffiWriterWrite.symbol(w.ctx)(w.inner, p)
 }
 
-// Close finishes the write and releases the resources associated with the 
Writer.
-// It is important to call Close after writing all the data to ensure that the 
data is
-// properly flushed and written to the storage. Otherwise, the data may be 
lost.
-func (w *Writer) Close() error {
+// Close finishes the write and releases the resources associated with the
+// Writer, returning the metadata of the written object (such as etag, version,
+// or last modified) as reported by the underlying service.
+//
+// It is important to call Close after writing all the data to ensure that the
+// data is properly flushed and written to the storage. Otherwise, the data may
+// be lost.
+//
+// Close is a wrapper around the C-binding function
+// `opendal_writer_close_with_metadata`. Which fields of the returned Metadata
+// are populated depends on the service.
+func (w *Writer) Close() (*Metadata, error) {
        defer ffiWriterFree.symbol(w.ctx)(w.inner)
-       return ffiWriterClose.symbol(w.ctx)(w.inner)
+       meta, err := ffiWriterCloseWithMetadata.symbol(w.ctx)(w.inner)
+       if err != nil {
+               return nil, err
+       }
+       return newMetadata(w.ctx, meta), nil
 }
 
-var _ io.WriteCloser = (*Writer)(nil)
+var _ io.Writer = (*Writer)(nil)
 
-var ffiOperatorWrite = newFFI(ffiOpts{
-       sym:    "opendal_operator_write",
-       rType:  &ffi.TypePointer,
-       aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, 
&ffi.TypePointer},
-}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path 
string, data []byte) error {
-       return func(op *opendalOperator, path string, data []byte) error {
-               bytePath, err := BytePtrFromString(path)
-               if err != nil {
-                       return err
-               }
-               bytes := toOpendalBytes(data)
-               var e *opendalError
-               ffiCall(
-                       unsafe.Pointer(&e),
-                       unsafe.Pointer(&op),
-                       unsafe.Pointer(&bytePath),
-                       unsafe.Pointer(&bytes),
-               )
-               return parseError(ctx, e)
-       }
-})
-
-var ffiOperatorWriteWith = newFFI(ffiOpts{
-       sym:    "opendal_operator_write_with",
-       rType:  &ffi.TypePointer,
+var ffiOperatorWriteWithMetadata = newFFI(ffiOpts{
+       sym:    "opendal_operator_write_with_metadata",
+       rType:  &typeResultWrite,
        aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, 
&ffi.TypePointer, &ffi.TypePointer},
-}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path 
string, data []byte, opts *opendalWriteOptions) error {
-       return func(op *opendalOperator, path string, data []byte, opts 
*opendalWriteOptions) error {
+}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path 
string, data []byte, opts *opendalWriteOptions) (*opendalMetadata, error) {
+       return func(op *opendalOperator, path string, data []byte, opts 
*opendalWriteOptions) (*opendalMetadata, error) {
                bytePath, err := BytePtrFromString(path)
                if err != nil {
-                       return err
+                       return nil, err
                }
                bytes := toOpendalBytes(data)
-               var e *opendalError
+               var result resultWrite
                ffiCall(
-                       unsafe.Pointer(&e),
+                       unsafe.Pointer(&result),
                        unsafe.Pointer(&op),
                        unsafe.Pointer(&bytePath),
                        unsafe.Pointer(&bytes),
                        unsafe.Pointer(&opts),
                )
-               return parseError(ctx, e)
+               if result.error != nil {
+                       return nil, parseError(ctx, result.error)
+               }
+               return result.meta, nil
        }
 })
 
@@ -658,17 +663,20 @@ var ffiWriterWrite = newFFI(ffiOpts{
        }
 })
 
-var ffiWriterClose = newFFI(ffiOpts{
-       sym:    "opendal_writer_close",
-       rType:  &ffi.TypePointer,
+var ffiWriterCloseWithMetadata = newFFI(ffiOpts{
+       sym:    "opendal_writer_close_with_metadata",
+       rType:  &typeResultWrite,
        aTypes: []*ffi.Type{&ffi.TypePointer},
-}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalWriter) error {
-       return func(r *opendalWriter) error {
-               var e *opendalError
+}, func(ctx context.Context, ffiCall ffiCall) func(r *opendalWriter) 
(*opendalMetadata, error) {
+       return func(r *opendalWriter) (*opendalMetadata, error) {
+               var result resultWrite
                ffiCall(
-                       unsafe.Pointer(&e),
+                       unsafe.Pointer(&result),
                        unsafe.Pointer(&r),
                )
-               return parseError(ctx, e)
+               if result.error != nil {
+                       return nil, parseError(ctx, result.error)
+               }
+               return result.meta, nil
        }
 })

Reply via email to