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 a83e4ed1e feat(binding/go): add presign with options (#7814)
a83e4ed1e is described below

commit a83e4ed1e3dd19fdc16f61306db33f402ca88412
Author: dentiny <[email protected]>
AuthorDate: Tue Jun 23 02:03:53 2026 -0700

    feat(binding/go): add presign with options (#7814)
---
 bindings/c/include/opendal.h                     |  32 ++
 bindings/c/src/presign.rs                        | 193 +++++++----
 bindings/c/tests/test_suites_presign.cpp         |  83 +++++
 bindings/go/delete.go                            |  48 ++-
 bindings/go/presign.go                           | 169 +++++++++-
 bindings/go/string_ownership_test.go             | 409 +++++++++++++++++++++++
 bindings/go/tests/behavior_tests/presign_test.go |  48 ++-
 core/core/src/blocking/operator.rs               |  48 +++
 8 files changed, 947 insertions(+), 83 deletions(-)

diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h
index 266677444..71f2e2a8f 100644
--- a/bindings/c/include/opendal.h
+++ b/bindings/c/include/opendal.h
@@ -2161,6 +2161,14 @@ struct opendal_result_presign 
opendal_operator_presign_read(const struct opendal
                                                             const char *path,
                                                             uint64_t 
expire_secs);
 
+/**
+ * \brief Presign a read operation with options.
+ */
+struct opendal_result_presign opendal_operator_presign_read_with(const struct 
opendal_operator *op,
+                                                                 const char 
*path,
+                                                                 uint64_t 
expire_secs,
+                                                                 const struct 
opendal_read_options *opts);
+
 /**
  * \brief Presign a write operation.
  */
@@ -2168,6 +2176,14 @@ struct opendal_result_presign 
opendal_operator_presign_write(const struct openda
                                                              const char *path,
                                                              uint64_t 
expire_secs);
 
+/**
+ * \brief Presign a write operation with options.
+ */
+struct opendal_result_presign opendal_operator_presign_write_with(const struct 
opendal_operator *op,
+                                                                  const char 
*path,
+                                                                  uint64_t 
expire_secs,
+                                                                  const struct 
opendal_write_options *opts);
+
 /**
  * \brief Presign a delete operation.
  */
@@ -2175,6 +2191,14 @@ struct opendal_result_presign 
opendal_operator_presign_delete(const struct opend
                                                               const char *path,
                                                               uint64_t 
expire_secs);
 
+/**
+ * \brief Presign a delete operation with options.
+ */
+struct opendal_result_presign opendal_operator_presign_delete_with(const 
struct opendal_operator *op,
+                                                                   const char 
*path,
+                                                                   uint64_t 
expire_secs,
+                                                                   const 
struct opendal_delete_options *opts);
+
 /**
  * \brief Presign a stat operation.
  */
@@ -2182,6 +2206,14 @@ struct opendal_result_presign 
opendal_operator_presign_stat(const struct opendal
                                                             const char *path,
                                                             uint64_t 
expire_secs);
 
+/**
+ * \brief Presign a stat operation with options.
+ */
+struct opendal_result_presign opendal_operator_presign_stat_with(const struct 
opendal_operator *op,
+                                                                 const char 
*path,
+                                                                 uint64_t 
expire_secs,
+                                                                 const struct 
opendal_stat_options *opts);
+
 /**
  * Get the method of the presigned request.
  */
diff --git a/bindings/c/src/presign.rs b/bindings/c/src/presign.rs
index 705f3334a..43ce897d4 100644
--- a/bindings/c/src/presign.rs
+++ b/bindings/c/src/presign.rs
@@ -18,10 +18,14 @@
 use std::ffi::{c_char, CStr, CString};
 use std::time::Duration;
 
+use ::opendal as core;
 use opendal::raw::PresignedRequest as ocorePresignedRequest;
 
 use crate::error::opendal_error;
 use crate::operator::opendal_operator;
+use crate::types::{
+    opendal_delete_options, opendal_read_options, opendal_stat_options, 
opendal_write_options,
+};
 
 /// \brief The key-value pair for the headers of the presigned request.
 #[repr(C)]
@@ -81,6 +85,25 @@ impl opendal_presigned_request_inner {
     }
 }
 
+fn make_presign_result(result: core::Result<ocorePresignedRequest>) -> 
opendal_result_presign {
+    match result {
+        Ok(req) => {
+            let inner = Box::new(opendal_presigned_request_inner::new(req));
+            let presigned_req = Box::new(opendal_presigned_request {
+                inner: Box::into_raw(inner),
+            });
+            opendal_result_presign {
+                req: Box::into_raw(presigned_req),
+                error: std::ptr::null_mut(),
+            }
+        }
+        Err(e) => opendal_result_presign {
+            req: std::ptr::null_mut(),
+            error: opendal_error::new(e),
+        },
+    }
+}
+
 /// \brief The underlying presigned request, which contains the HTTP method, 
URI, and headers.
 /// This is an opaque struct, please use the accessor functions to get the 
fields.
 #[repr(C)]
@@ -110,22 +133,29 @@ pub unsafe extern "C" fn opendal_operator_presign_read(
     let path = CStr::from_ptr(path).to_str().expect("malformed path");
     let duration = Duration::from_secs(expire_secs);
 
-    match op.presign_read(path, duration) {
-        Ok(req) => {
-            let inner = Box::new(opendal_presigned_request_inner::new(req));
-            let presigned_req = Box::new(opendal_presigned_request {
-                inner: Box::into_raw(inner),
-            });
-            opendal_result_presign {
-                req: Box::into_raw(presigned_req),
-                error: std::ptr::null_mut(),
-            }
-        }
-        Err(e) => opendal_result_presign {
-            req: std::ptr::null_mut(),
-            error: opendal_error::new(e),
-        },
-    }
+    make_presign_result(op.presign_read(path, duration))
+}
+
+/// \brief Presign a read operation with options.
+#[no_mangle]
+pub unsafe extern "C" fn opendal_operator_presign_read_with(
+    op: &opendal_operator,
+    path: *const c_char,
+    expire_secs: u64,
+    opts: *const opendal_read_options,
+) -> opendal_result_presign {
+    assert!(!path.is_null());
+
+    let op = op.deref();
+    let path = CStr::from_ptr(path).to_str().expect("malformed path");
+    let duration = Duration::from_secs(expire_secs);
+    let opts = if opts.is_null() {
+        core::options::ReadOptions::default()
+    } else {
+        (&*opts).into()
+    };
+
+    make_presign_result(op.presign_read_options(path, duration, opts))
 }
 
 /// \brief Presign a write operation.
@@ -141,22 +171,29 @@ pub unsafe extern "C" fn opendal_operator_presign_write(
     let path = CStr::from_ptr(path).to_str().expect("malformed path");
     let duration = Duration::from_secs(expire_secs);
 
-    match op.presign_write(path, duration) {
-        Ok(req) => {
-            let inner = Box::new(opendal_presigned_request_inner::new(req));
-            let presigned_req = Box::new(opendal_presigned_request {
-                inner: Box::into_raw(inner),
-            });
-            opendal_result_presign {
-                req: Box::into_raw(presigned_req),
-                error: std::ptr::null_mut(),
-            }
-        }
-        Err(e) => opendal_result_presign {
-            req: std::ptr::null_mut(),
-            error: opendal_error::new(e),
-        },
-    }
+    make_presign_result(op.presign_write(path, duration))
+}
+
+/// \brief Presign a write operation with options.
+#[no_mangle]
+pub unsafe extern "C" fn opendal_operator_presign_write_with(
+    op: &opendal_operator,
+    path: *const c_char,
+    expire_secs: u64,
+    opts: *const opendal_write_options,
+) -> opendal_result_presign {
+    assert!(!path.is_null());
+
+    let op = op.deref();
+    let path = CStr::from_ptr(path).to_str().expect("malformed path");
+    let duration = Duration::from_secs(expire_secs);
+    let opts = if opts.is_null() {
+        core::options::WriteOptions::default()
+    } else {
+        (&*opts).into()
+    };
+
+    make_presign_result(op.presign_write_options(path, duration, opts))
 }
 
 /// \brief Presign a delete operation.
@@ -171,22 +208,43 @@ pub unsafe extern "C" fn opendal_operator_presign_delete(
     let op = op.deref();
     let path = CStr::from_ptr(path).to_str().expect("malformed path");
     let duration = Duration::from_secs(expire_secs);
-    match op.presign_delete(path, duration) {
-        Ok(req) => {
-            let inner = Box::new(opendal_presigned_request_inner::new(req));
-            let presigned_req = Box::new(opendal_presigned_request {
-                inner: Box::into_raw(inner),
-            });
-            opendal_result_presign {
-                req: Box::into_raw(presigned_req),
-                error: std::ptr::null_mut(),
-            }
+    make_presign_result(op.presign_delete(path, duration))
+}
+
+/// \brief Presign a delete operation with options.
+#[no_mangle]
+pub unsafe extern "C" fn opendal_operator_presign_delete_with(
+    op: &opendal_operator,
+    path: *const c_char,
+    expire_secs: u64,
+    opts: *const opendal_delete_options,
+) -> opendal_result_presign {
+    assert!(!path.is_null());
+
+    let op = op.deref();
+    let path = CStr::from_ptr(path).to_str().expect("malformed path");
+    let duration = Duration::from_secs(expire_secs);
+    let opts = if opts.is_null() {
+        core::options::DeleteOptions::default()
+    } else {
+        let opts = &*opts;
+        let version = if opts.version.is_null() {
+            None
+        } else {
+            Some(
+                CStr::from_ptr(opts.version)
+                    .to_str()
+                    .expect("malformed version")
+                    .to_owned(),
+            )
+        };
+        core::options::DeleteOptions {
+            version,
+            recursive: opts.recursive,
         }
-        Err(e) => opendal_result_presign {
-            req: std::ptr::null_mut(),
-            error: opendal_error::new(e),
-        },
-    }
+    };
+
+    make_presign_result(op.presign_delete_options(path, duration, opts))
 }
 
 /// \brief Presign a stat operation.
@@ -202,22 +260,29 @@ pub unsafe extern "C" fn opendal_operator_presign_stat(
     let path = CStr::from_ptr(path).to_str().expect("malformed path");
     let duration = Duration::from_secs(expire_secs);
 
-    match op.presign_stat(path, duration) {
-        Ok(req) => {
-            let inner = Box::new(opendal_presigned_request_inner::new(req));
-            let presigned_req = Box::new(opendal_presigned_request {
-                inner: Box::into_raw(inner),
-            });
-            opendal_result_presign {
-                req: Box::into_raw(presigned_req),
-                error: std::ptr::null_mut(),
-            }
-        }
-        Err(e) => opendal_result_presign {
-            req: std::ptr::null_mut(),
-            error: opendal_error::new(e),
-        },
-    }
+    make_presign_result(op.presign_stat(path, duration))
+}
+
+/// \brief Presign a stat operation with options.
+#[no_mangle]
+pub unsafe extern "C" fn opendal_operator_presign_stat_with(
+    op: &opendal_operator,
+    path: *const c_char,
+    expire_secs: u64,
+    opts: *const opendal_stat_options,
+) -> opendal_result_presign {
+    assert!(!path.is_null());
+
+    let op = op.deref();
+    let path = CStr::from_ptr(path).to_str().expect("malformed path");
+    let duration = Duration::from_secs(expire_secs);
+    let opts = if opts.is_null() {
+        core::options::StatOptions::default()
+    } else {
+        (&*opts).into()
+    };
+
+    make_presign_result(op.presign_stat_options(path, duration, opts))
 }
 
 /// Get the method of the presigned request.
@@ -256,9 +321,7 @@ pub unsafe extern "C" fn 
opendal_presigned_request_headers_len(
 #[no_mangle]
 pub unsafe extern "C" fn opendal_presigned_request_free(req: *mut 
opendal_presigned_request) {
     if !req.is_null() {
-        // Drop the inner struct
         drop(Box::from_raw((*req).inner));
-        // Drop the outer struct
         drop(Box::from_raw(req));
     }
 }
diff --git a/bindings/c/tests/test_suites_presign.cpp 
b/bindings/c/tests/test_suites_presign.cpp
index 6fc39e582..9c1777504 100644
--- a/bindings/c/tests/test_suites_presign.cpp
+++ b/bindings/c/tests/test_suites_presign.cpp
@@ -475,6 +475,88 @@ void test_presign_read(opendal_test_context* ctx)
     OPENDAL_ASSERT(mismatch == 0, "Downloaded content should match stored 
content");
 }
 
+// Test: Presign read operation with options
+void test_presign_read_with_range(opendal_test_context* ctx)
+{
+    const char* path = "test_presign_read_with_range.txt";
+    const char* content = "0123456789";
+    const char* expected = "2345";
+    size_t content_len = strlen(content);
+    size_t expected_len = strlen(expected);
+
+    opendal_bytes data;
+    data.data = (uint8_t*)content;
+    data.len = content_len;
+    data.capacity = content_len;
+
+    opendal_error* error = 
opendal_operator_write(ctx->config->operator_instance, path, &data);
+    OPENDAL_ASSERT_NO_ERROR(error, "Write operation should succeed");
+
+    opendal_read_options* opts = opendal_read_options_new();
+    OPENDAL_ASSERT_NOT_NULL(opts, "Read options should not be null");
+    opendal_read_options_set_range(opts, 2, expected_len);
+
+    opendal_result_presign presign_result = 
opendal_operator_presign_read_with(ctx->config->operator_instance, path, 3600, 
opts);
+    opendal_read_options_free(opts);
+    OPENDAL_ASSERT_NO_ERROR(presign_result.error, "Presign read with range 
should succeed");
+    OPENDAL_ASSERT_NOT_NULL(presign_result.req, "Presigned request should not 
be null");
+
+    const char* method = opendal_presigned_request_method(presign_result.req);
+    const char* url = opendal_presigned_request_uri(presign_result.req);
+    const opendal_http_header_pair* headers = 
opendal_presigned_request_headers(presign_result.req);
+    uintptr_t headers_len = 
opendal_presigned_request_headers_len(presign_result.req);
+    OPENDAL_ASSERT_NOT_NULL(method, "Presigned method should not be null");
+    OPENDAL_ASSERT_STR_EQ("GET", method, "Presigned method should be GET");
+    OPENDAL_ASSERT_NOT_NULL(url, "Presigned URL should not be null");
+    OPENDAL_ASSERT(headers_len == 0 || headers != NULL, "Headers pointer must 
be valid when headers exist");
+
+    CURL* curl = curl_easy_init();
+    OPENDAL_ASSERT_NOT_NULL(curl, "CURL initialization should succeed");
+
+    struct curl_slist* chunk = NULL;
+    CURLcode setup_error = CURLE_OK;
+    presign_prepare_result prepare_res = presign_prepare_curl(curl, url, 
method,
+        headers, headers_len, PRESIGN_NO_OVERRIDE, &chunk, &setup_error);
+    PRESIGN_ASSERT_PREPARE_OK(curl, chunk, prepare_res, setup_error);
+
+    presign_body_context body_ctx;
+    body_ctx.expected = expected;
+    body_ctx.expected_len = expected_len;
+    body_ctx.offset = 0;
+    body_ctx.mismatch = 0;
+
+    CURLcode opt_res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, 
presign_write_callback);
+    if (opt_res != CURLE_OK) {
+        presign_cleanup_curl(curl, chunk);
+        OPENDAL_ASSERT_EQ(CURLE_OK, opt_res, "Setting CURL write callback 
should succeed");
+    }
+    opt_res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body_ctx);
+    if (opt_res != CURLE_OK) {
+        presign_cleanup_curl(curl, chunk);
+        OPENDAL_ASSERT_EQ(CURLE_OK, opt_res, "Setting CURL write data should 
succeed");
+    }
+
+    CURLcode res = curl_easy_perform(curl);
+    if (res != CURLE_OK) {
+        presign_cleanup_curl(curl, chunk);
+        OPENDAL_ASSERT_EQ(CURLE_OK, res, "CURL perform should succeed");
+    }
+
+    presign_cleanup_curl(curl, chunk);
+
+    size_t received_len = body_ctx.offset;
+    int mismatch = body_ctx.mismatch;
+
+    opendal_presigned_request_free(presign_result.req);
+
+    opendal_error* delete_error = 
opendal_operator_delete(ctx->config->operator_instance, path);
+    OPENDAL_ASSERT_NO_ERROR(delete_error, "Cleanup delete should succeed");
+
+    OPENDAL_ASSERT_EQ(expected_len, received_len,
+        "Downloaded range length should match requested range");
+    OPENDAL_ASSERT(mismatch == 0, "Downloaded range content should match");
+}
+
 // Test: Presign write operation
 void test_presign_write(opendal_test_context* ctx)
 {
@@ -723,6 +805,7 @@ void test_presign_delete(opendal_test_context* ctx)
 
 opendal_test_case presign_tests[] = {
     { "presign_read", test_presign_read, make_capability_presign() },
+    { "presign_read_with_range", test_presign_read_with_range, 
make_capability_presign() },
     { "presign_write", test_presign_write, make_capability_presign() },
     { "presign_stat", test_presign_stat, make_capability_presign() },
     { "presign_delete", test_presign_delete, make_capability_presign() },
diff --git a/bindings/go/delete.go b/bindings/go/delete.go
index 137f6d0ef..26284f3e9 100644
--- a/bindings/go/delete.go
+++ b/bindings/go/delete.go
@@ -21,6 +21,7 @@ package opendal
 
 import (
        "context"
+       "runtime"
        "unsafe"
 
        "github.com/jupiterrider/ffi"
@@ -93,17 +94,44 @@ func (op *Operator) Delete(path string, opts 
...WithDeleteFn) error {
        if len(opts) == 0 {
                return ffiOperatorDelete.symbol(op.ctx)(op.inner, path)
        }
+
+       o := parseDeleteOptions(opts...)
+       cOpts, keepAlive, err := newOpendalDeleteOptions(op.ctx, o)
+       if err != nil {
+               return err
+       }
+       defer ffiDeleteOptionsFree.symbol(op.ctx)(cOpts)
+       err = ffiOperatorDeleteWith.symbol(op.ctx)(op.inner, path, cOpts)
+       runtime.KeepAlive(keepAlive)
+       return err
+}
+
+func parseDeleteOptions(opts ...WithDeleteFn) *deleteOptions {
        o := &deleteOptions{}
        for _, opt := range opts {
                opt(o)
        }
-       cOpts := ffiDeleteOptionsNew.symbol(op.ctx)()
-       defer ffiDeleteOptionsFree.symbol(op.ctx)(cOpts)
-       ffiDeleteOptionsSetRecursive.symbol(op.ctx)(cOpts, o.recursive)
+       return o
+}
+
+func newOpendalDeleteOptions(ctx context.Context, o *deleteOptions) 
(*opendalDeleteOptions, [][]byte, error) {
+       cOpts := ffiDeleteOptionsNew.symbol(ctx)()
+       var keepAlive [][]byte
+
+       fail := func(err error) (*opendalDeleteOptions, [][]byte, error) {
+               ffiDeleteOptionsFree.symbol(ctx)(cOpts)
+               return nil, nil, err
+       }
+
+       ffiDeleteOptionsSetRecursive.symbol(ctx)(cOpts, o.recursive)
        if o.version != nil {
-               ffiDeleteOptionsSetVersion.symbol(op.ctx)(cOpts, *o.version)
+               data, err := ffiDeleteOptionsSetVersion.symbol(ctx)(cOpts, 
*o.version)
+               if err != nil {
+                       return fail(err)
+               }
+               keepAlive = append(keepAlive, data)
        }
-       return ffiOperatorDeleteWith.symbol(op.ctx)(op.inner, path, cOpts)
+       return cOpts, keepAlive, nil
 }
 
 var ffiOperatorDelete = newFFI(ffiOpts{
@@ -141,17 +169,19 @@ var ffiDeleteOptionsSetVersion = newFFI(ffiOpts{
        sym:    "opendal_delete_options_set_version",
        rType:  &ffi.TypeVoid,
        aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer},
-}, func(_ context.Context, ffiCall ffiCall) func(opts *opendalDeleteOptions, 
version string) {
-       return func(opts *opendalDeleteOptions, version string) {
-               bytePtr, err := BytePtrFromString(version)
+}, func(_ context.Context, ffiCall ffiCall) func(opts *opendalDeleteOptions, 
version string) ([]byte, error) {
+       return func(opts *opendalDeleteOptions, version string) ([]byte, error) 
{
+               data, err := byteSliceFromString(version)
                if err != nil {
-                       return
+                       return nil, err
                }
+               bytePtr := &data[0]
                ffiCall(
                        nil,
                        unsafe.Pointer(&opts),
                        unsafe.Pointer(&bytePtr),
                )
+               return data, nil
        }
 })
 
diff --git a/bindings/go/presign.go b/bindings/go/presign.go
index 4c506829a..b04f38d29 100644
--- a/bindings/go/presign.go
+++ b/bindings/go/presign.go
@@ -23,6 +23,7 @@ import (
        "context"
        "fmt"
        "net/http"
+       "runtime"
        "time"
        "unsafe"
 
@@ -32,29 +33,81 @@ import (
 type presignFunc func(op *opendalOperator, path string, expire uint64) 
(*opendalPresignedRequest, error)
 
 // PresignRead returns a presigned HTTP request that can be used to read the 
object at the given path.
-func (op *Operator) PresignRead(path string, expire time.Duration) 
(*http.Request, error) {
-       return op.presign(path, expire, ffiOperatorPresignRead.symbol(op.ctx))
+func (op *Operator) PresignRead(path string, expire time.Duration, opts 
...WithReadFn) (*http.Request, error) {
+       if len(opts) == 0 {
+               return op.presign(path, expire, 
ffiOperatorPresignRead.symbol(op.ctx))
+       }
+
+       o := parseReadOptions(opts...)
+       cOpts, keepAlive, err := newOpendalReadOptions(op.ctx, o)
+       if err != nil {
+               return nil, err
+       }
+       defer ffiReadOptionsFree.symbol(op.ctx)(cOpts)
+       req, err := ffiOperatorPresignReadWith.symbol(op.ctx)(op.inner, path, 
uint64(expire/time.Second), cOpts)
+       runtime.KeepAlive(keepAlive)
+       return op.buildPresignedRequest(req, err)
 }
 
 // PresignWrite returns a presigned HTTP request that can be used to write the 
object at the given path.
-func (op *Operator) PresignWrite(path string, expire time.Duration) 
(*http.Request, error) {
-       return op.presign(path, expire, ffiOperatorPresignWrite.symbol(op.ctx))
+func (op *Operator) PresignWrite(path string, expire time.Duration, opts 
...WithWriteFn) (*http.Request, error) {
+       if len(opts) == 0 {
+               return op.presign(path, expire, 
ffiOperatorPresignWrite.symbol(op.ctx))
+       }
+
+       o := parseWriteOptions(opts...)
+       cOpts, keepAlive, err := newOpendalWriteOptions(op.ctx, o)
+       if err != nil {
+               return nil, err
+       }
+       defer ffiWriteOptionsFree.symbol(op.ctx)(cOpts)
+       req, err := ffiOperatorPresignWriteWith.symbol(op.ctx)(op.inner, path, 
uint64(expire/time.Second), cOpts)
+       runtime.KeepAlive(keepAlive)
+       return op.buildPresignedRequest(req, err)
 }
 
 // PresignDelete returns a presigned HTTP request that can be used to delete 
the object at the given path.
-func (op *Operator) PresignDelete(path string, expire time.Duration) 
(*http.Request, error) {
-       return op.presign(path, expire, ffiOperatorPresignDelete.symbol(op.ctx))
+func (op *Operator) PresignDelete(path string, expire time.Duration, opts 
...WithDeleteFn) (*http.Request, error) {
+       if len(opts) == 0 {
+               return op.presign(path, expire, 
ffiOperatorPresignDelete.symbol(op.ctx))
+       }
+
+       o := parseDeleteOptions(opts...)
+       cOpts, keepAlive, err := newOpendalDeleteOptions(op.ctx, o)
+       if err != nil {
+               return nil, err
+       }
+       defer ffiDeleteOptionsFree.symbol(op.ctx)(cOpts)
+       req, err := ffiOperatorPresignDeleteWith.symbol(op.ctx)(op.inner, path, 
uint64(expire/time.Second), cOpts)
+       runtime.KeepAlive(keepAlive)
+       return op.buildPresignedRequest(req, err)
 }
 
 // PresignStat returns a presigned HTTP request that can be used to stat the 
object at the given path.
-func (op *Operator) PresignStat(path string, expire time.Duration) 
(*http.Request, error) {
-       return op.presign(path, expire, ffiOperatorPresignStat.symbol(op.ctx))
+func (op *Operator) PresignStat(path string, expire time.Duration, opts 
...WithStatFn) (*http.Request, error) {
+       if len(opts) == 0 {
+               return op.presign(path, expire, 
ffiOperatorPresignStat.symbol(op.ctx))
+       }
+
+       o := parseStatOptions(opts...)
+       cOpts, keepAlive, err := newOpendalStatOptions(op.ctx, o)
+       if err != nil {
+               return nil, err
+       }
+       defer ffiStatOptionsFree.symbol(op.ctx)(cOpts)
+       req, err := ffiOperatorPresignStatWith.symbol(op.ctx)(op.inner, path, 
uint64(expire/time.Second), cOpts)
+       runtime.KeepAlive(keepAlive)
+       return op.buildPresignedRequest(req, err)
 }
 
 func (op *Operator) presign(path string, expire time.Duration, call 
presignFunc) (*http.Request, error) {
        secs := uint64(expire / time.Second)
 
        req, err := call(op.inner, path, secs)
+       return op.buildPresignedRequest(req, err)
+}
+
+func (op *Operator) buildPresignedRequest(req *opendalPresignedRequest, err 
error) (*http.Request, error) {
        if err != nil {
                return nil, err
        }
@@ -121,6 +174,31 @@ var ffiOperatorPresignRead = newFFI(ffiOpts{
        }
 })
 
+var ffiOperatorPresignReadWith = newFFI(ffiOpts{
+       sym:    "opendal_operator_presign_read_with",
+       rType:  &typeResultPresign,
+       aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, 
&ffi.TypeUint64, &ffi.TypePointer},
+}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path 
string, expire uint64, opts *opendalReadOptions) (*opendalPresignedRequest, 
error) {
+       return func(op *opendalOperator, path string, expire uint64, opts 
*opendalReadOptions) (*opendalPresignedRequest, error) {
+               bytePath, err := BytePtrFromString(path)
+               if err != nil {
+                       return nil, err
+               }
+               var result resultPresign
+               ffiCall(
+                       unsafe.Pointer(&result),
+                       unsafe.Pointer(&op),
+                       unsafe.Pointer(&bytePath),
+                       unsafe.Pointer(&expire),
+                       unsafe.Pointer(&opts),
+               )
+               if result.error != nil {
+                       return nil, parseError(ctx, result.error)
+               }
+               return result.req, nil
+       }
+})
+
 var ffiOperatorPresignWrite = newFFI(ffiOpts{
        sym:    "opendal_operator_presign_write",
        rType:  &typeResultPresign,
@@ -145,6 +223,31 @@ var ffiOperatorPresignWrite = newFFI(ffiOpts{
        }
 })
 
+var ffiOperatorPresignWriteWith = newFFI(ffiOpts{
+       sym:    "opendal_operator_presign_write_with",
+       rType:  &typeResultPresign,
+       aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, 
&ffi.TypeUint64, &ffi.TypePointer},
+}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path 
string, expire uint64, opts *opendalWriteOptions) (*opendalPresignedRequest, 
error) {
+       return func(op *opendalOperator, path string, expire uint64, opts 
*opendalWriteOptions) (*opendalPresignedRequest, error) {
+               bytePath, err := BytePtrFromString(path)
+               if err != nil {
+                       return nil, err
+               }
+               var result resultPresign
+               ffiCall(
+                       unsafe.Pointer(&result),
+                       unsafe.Pointer(&op),
+                       unsafe.Pointer(&bytePath),
+                       unsafe.Pointer(&expire),
+                       unsafe.Pointer(&opts),
+               )
+               if result.error != nil {
+                       return nil, parseError(ctx, result.error)
+               }
+               return result.req, nil
+       }
+})
+
 var ffiOperatorPresignDelete = newFFI(ffiOpts{
        sym:    "opendal_operator_presign_delete",
        rType:  &typeResultPresign,
@@ -169,6 +272,31 @@ var ffiOperatorPresignDelete = newFFI(ffiOpts{
        }
 })
 
+var ffiOperatorPresignDeleteWith = newFFI(ffiOpts{
+       sym:    "opendal_operator_presign_delete_with",
+       rType:  &typeResultPresign,
+       aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, 
&ffi.TypeUint64, &ffi.TypePointer},
+}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path 
string, expire uint64, opts *opendalDeleteOptions) (*opendalPresignedRequest, 
error) {
+       return func(op *opendalOperator, path string, expire uint64, opts 
*opendalDeleteOptions) (*opendalPresignedRequest, error) {
+               bytePath, err := BytePtrFromString(path)
+               if err != nil {
+                       return nil, err
+               }
+               var result resultPresign
+               ffiCall(
+                       unsafe.Pointer(&result),
+                       unsafe.Pointer(&op),
+                       unsafe.Pointer(&bytePath),
+                       unsafe.Pointer(&expire),
+                       unsafe.Pointer(&opts),
+               )
+               if result.error != nil {
+                       return nil, parseError(ctx, result.error)
+               }
+               return result.req, nil
+       }
+})
+
 var ffiOperatorPresignStat = newFFI(ffiOpts{
        sym:    "opendal_operator_presign_stat",
        rType:  &typeResultPresign,
@@ -193,6 +321,31 @@ var ffiOperatorPresignStat = newFFI(ffiOpts{
        }
 })
 
+var ffiOperatorPresignStatWith = newFFI(ffiOpts{
+       sym:    "opendal_operator_presign_stat_with",
+       rType:  &typeResultPresign,
+       aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer, 
&ffi.TypeUint64, &ffi.TypePointer},
+}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path 
string, expire uint64, opts *opendalStatOptions) (*opendalPresignedRequest, 
error) {
+       return func(op *opendalOperator, path string, expire uint64, opts 
*opendalStatOptions) (*opendalPresignedRequest, error) {
+               bytePath, err := BytePtrFromString(path)
+               if err != nil {
+                       return nil, err
+               }
+               var result resultPresign
+               ffiCall(
+                       unsafe.Pointer(&result),
+                       unsafe.Pointer(&op),
+                       unsafe.Pointer(&bytePath),
+                       unsafe.Pointer(&expire),
+                       unsafe.Pointer(&opts),
+               )
+               if result.error != nil {
+                       return nil, parseError(ctx, result.error)
+               }
+               return result.req, nil
+       }
+})
+
 var ffiPresignedRequestMethod = newFFI(ffiOpts{
        sym:    "opendal_presigned_request_method",
        rType:  &ffi.TypePointer,
diff --git a/bindings/go/string_ownership_test.go 
b/bindings/go/string_ownership_test.go
index 34b648c39..0a258cb8a 100644
--- a/bindings/go/string_ownership_test.go
+++ b/bindings/go/string_ownership_test.go
@@ -21,6 +21,7 @@ package opendal
 
 import (
        "context"
+       "net/http"
        "testing"
        "time"
        "unsafe"
@@ -1060,3 +1061,411 @@ func TestReadOptionsSetterArgTypes(t *testing.T) {
                t.Fatalf("ffiReadOptionsSetRangeFrom aTypes = %v, want 
TypePointer, TypeUint64", rangeFromATypes)
        }
 }
+
+func TestFfiOperatorPresignWithSignatures(t *testing.T) {
+       for _, tc := range []struct {
+               name string
+               opts ffiOpts
+       }{
+               {
+                       name: "ffiOperatorPresignReadWith",
+                       opts: ffiOperatorPresignReadWith.opts,
+               },
+               {
+                       name: "ffiOperatorPresignWriteWith",
+                       opts: ffiOperatorPresignWriteWith.opts,
+               },
+               {
+                       name: "ffiOperatorPresignDeleteWith",
+                       opts: ffiOperatorPresignDeleteWith.opts,
+               },
+               {
+                       name: "ffiOperatorPresignStatWith",
+                       opts: ffiOperatorPresignStatWith.opts,
+               },
+       } {
+               if tc.opts.rType != &typeResultPresign {
+                       t.Fatalf("%s rType = %v, want typeResultPresign", 
tc.name, tc.opts.rType)
+               }
+               if len(tc.opts.aTypes) != 4 {
+                       t.Fatalf("%s aTypes len = %d, want 4", tc.name, 
len(tc.opts.aTypes))
+               }
+               if tc.opts.aTypes[0] != &ffi.TypePointer ||
+                       tc.opts.aTypes[1] != &ffi.TypePointer ||
+                       tc.opts.aTypes[2] != &ffi.TypeUint64 ||
+                       tc.opts.aTypes[3] != &ffi.TypePointer {
+                       t.Fatalf("%s aTypes = %v, want pointer, pointer, 
uint64, pointer", tc.name, tc.opts.aTypes)
+               }
+       }
+}
+
+func TestPresignWithoutOptionsUsesSimpleSymbols(t *testing.T) {
+       ctx, reqInner, freeCount := presignTestContext(t, "GET", 
"https://example.com/simple";)
+       opInner := &opendalOperator{}
+       op := &Operator{ctx: ctx, inner: opInner}
+
+       ctx = context.WithValue(ctx, ffiOperatorPresignRead.opts.sym, func(op 
*opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) 
{
+               assertPresignCall(t, opInner, op, "file.txt", path, uint64(60), 
expire)
+               return reqInner, nil
+       })
+       ctx = context.WithValue(ctx, ffiOperatorPresignWrite.opts.sym, func(op 
*opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) 
{
+               assertPresignCall(t, opInner, op, "file.txt", path, uint64(60), 
expire)
+               return reqInner, nil
+       })
+       ctx = context.WithValue(ctx, ffiOperatorPresignDelete.opts.sym, func(op 
*opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) 
{
+               assertPresignCall(t, opInner, op, "file.txt", path, uint64(60), 
expire)
+               return reqInner, nil
+       })
+       ctx = context.WithValue(ctx, ffiOperatorPresignStat.opts.sym, func(op 
*opendalOperator, path string, expire uint64) (*opendalPresignedRequest, error) 
{
+               assertPresignCall(t, opInner, op, "file.txt", path, uint64(60), 
expire)
+               return reqInner, nil
+       })
+       op.ctx = ctx
+
+       for name, call := range map[string]func() (*http.Request, error){
+               "read":   func() (*http.Request, error) { return 
op.PresignRead("file.txt", time.Minute) },
+               "write":  func() (*http.Request, error) { return 
op.PresignWrite("file.txt", time.Minute) },
+               "delete": func() (*http.Request, error) { return 
op.PresignDelete("file.txt", time.Minute) },
+               "stat":   func() (*http.Request, error) { return 
op.PresignStat("file.txt", time.Minute) },
+       } {
+               req, err := call()
+               if err != nil {
+                       t.Fatalf("Presign%s() failed: %v", name, err)
+               }
+               if req.Method != "GET" || req.URL.String() != 
"https://example.com/simple"; {
+                       t.Fatalf("Presign%s() request = %s %s", name, 
req.Method, req.URL.String())
+               }
+       }
+       if *freeCount != 4 {
+               t.Fatalf("presigned request freed %d times, want 4", *freeCount)
+       }
+}
+
+func TestPresignReadWithOptionsKeepsStringsUntilCall(t *testing.T) {
+       ctx, reqInner, _ := presignTestContext(t, "GET", 
"https://example.com/read";)
+       opInner := &opendalOperator{}
+       cOpts := &opendalReadOptions{}
+       var versionPtr *byte
+       var overrideContentTypePtr *byte
+       var rangeOffset, rangeLength uint64
+
+       ctx = context.WithValue(ctx, ffiReadOptionsNew.opts.sym, func() 
*opendalReadOptions { return cOpts })
+       ctx = context.WithValue(ctx, ffiReadOptionsFree.opts.sym, func(opts 
*opendalReadOptions) {
+               assertReadOptionsPointer(t, cOpts, opts)
+       })
+       ctx = context.WithValue(ctx, ffiReadOptionsSetRange.opts.sym, func(opts 
*opendalReadOptions, offset, length uint64) {
+               assertReadOptionsPointer(t, cOpts, opts)
+               rangeOffset = offset
+               rangeLength = length
+       })
+       ctx = context.WithValue(ctx, ffiReadOptionsSetVersion.opts.sym, 
ffiReadOptionsSetVersion.withFunc(ctx, func(_ unsafe.Pointer, aValues 
...unsafe.Pointer) {
+               assertReadOptionsPointerFromArgs(t, cOpts, aValues...)
+               versionPtr = *(**byte)(aValues[1])
+       }))
+       ctx = context.WithValue(ctx, ffiReadOptionsSetIfMatch.opts.sym, 
noopReadOptionsSetString)
+       ctx = context.WithValue(ctx, ffiReadOptionsSetIfNoneMatch.opts.sym, 
noopReadOptionsSetString)
+       ctx = context.WithValue(ctx, 
ffiReadOptionsSetOverrideContentType.opts.sym, 
ffiReadOptionsSetOverrideContentType.withFunc(ctx, func(_ unsafe.Pointer, 
aValues ...unsafe.Pointer) {
+               assertReadOptionsPointerFromArgs(t, cOpts, aValues...)
+               overrideContentTypePtr = *(**byte)(aValues[1])
+       }))
+       ctx = context.WithValue(ctx, 
ffiReadOptionsSetOverrideCacheControl.opts.sym, noopReadOptionsSetString)
+       ctx = context.WithValue(ctx, 
ffiReadOptionsSetOverrideContentDisposition.opts.sym, noopReadOptionsSetString)
+       ctx = context.WithValue(ctx, ffiOperatorPresignReadWith.opts.sym, 
func(op *opendalOperator, path string, expire uint64, opts *opendalReadOptions) 
(*opendalPresignedRequest, error) {
+               assertPresignCall(t, opInner, op, "file.txt", path, uint64(60), 
expire)
+               assertReadOptionsPointer(t, cOpts, opts)
+               if rangeOffset != 1 || rangeLength != 2 {
+                       t.Fatalf("read range = %d, %d, want 1, 2", rangeOffset, 
rangeLength)
+               }
+               if got := BytePtrToString(versionPtr); got != "v1" {
+                       t.Fatalf("read version pointer = %q, want v1", got)
+               }
+               if got := BytePtrToString(overrideContentTypePtr); got != 
"text/plain" {
+                       t.Fatalf("read override content type pointer = %q, want 
text/plain", got)
+               }
+               return reqInner, nil
+       })
+
+       op := &Operator{ctx: ctx, inner: opInner}
+       _, err := op.PresignRead("file.txt", time.Minute,
+               ReadWithRange(1, 2),
+               ReadWithVersion("v1"),
+               ReadWithOverrideContentType("text/plain"),
+       )
+       if err != nil {
+               t.Fatalf("PresignRead with options failed: %v", err)
+       }
+}
+
+func TestPresignWriteWithOptionsKeepsStringsUntilCall(t *testing.T) {
+       ctx, reqInner, _ := presignTestContext(t, "PUT", 
"https://example.com/write";)
+       opInner := &opendalOperator{}
+       cOpts := &opendalWriteOptions{}
+       var contentTypePtr *byte
+       var userMetadata []opendalWriteUserMetadataPair
+
+       ctx = context.WithValue(ctx, ffiWriteOptionsNew.opts.sym, func() 
*opendalWriteOptions { return cOpts })
+       ctx = context.WithValue(ctx, ffiWriteOptionsFree.opts.sym, func(opts 
*opendalWriteOptions) {
+               assertWriteOptionsPointer(t, cOpts, opts)
+       })
+       ctx = context.WithValue(ctx, ffiWriteOptionsSetAppend.opts.sym, 
func(opts *opendalWriteOptions, append bool) {
+               assertWriteOptionsPointer(t, cOpts, opts)
+       })
+       ctx = context.WithValue(ctx, ffiWriteOptionsSetIfNotExists.opts.sym, 
func(opts *opendalWriteOptions, ifNotExists bool) {
+               assertWriteOptionsPointer(t, cOpts, opts)
+       })
+       ctx = context.WithValue(ctx, ffiWriteOptionsSetContentType.opts.sym, 
ffiWriteOptionsSetContentType.withFunc(ctx, func(_ unsafe.Pointer, aValues 
...unsafe.Pointer) {
+               assertWriteOptionsPointerFromArgs(t, cOpts, aValues...)
+               contentTypePtr = *(**byte)(aValues[1])
+       }))
+       ctx = context.WithValue(ctx, ffiWriteOptionsSetCacheControl.opts.sym, 
noopWriteOptionsSetString)
+       ctx = context.WithValue(ctx, 
ffiWriteOptionsSetContentDisposition.opts.sym, noopWriteOptionsSetString)
+       ctx = context.WithValue(ctx, 
ffiWriteOptionsSetContentEncoding.opts.sym, noopWriteOptionsSetString)
+       ctx = context.WithValue(ctx, ffiWriteOptionsSetIfMatch.opts.sym, 
noopWriteOptionsSetString)
+       ctx = context.WithValue(ctx, ffiWriteOptionsSetIfNoneMatch.opts.sym, 
noopWriteOptionsSetString)
+       ctx = context.WithValue(ctx, ffiWriteOptionsSetUserMetadata.opts.sym, 
func(opts *opendalWriteOptions, pairs []opendalWriteUserMetadataPair) {
+               assertWriteOptionsPointer(t, cOpts, opts)
+               userMetadata = pairs
+       })
+       ctx = context.WithValue(ctx, ffiOperatorPresignWriteWith.opts.sym, 
func(op *opendalOperator, path string, expire uint64, opts 
*opendalWriteOptions) (*opendalPresignedRequest, error) {
+               assertPresignCall(t, opInner, op, "file.txt", path, uint64(60), 
expire)
+               assertWriteOptionsPointer(t, cOpts, opts)
+               if got := BytePtrToString(contentTypePtr); got != "text/plain" {
+                       t.Fatalf("write content type pointer = %q, want 
text/plain", got)
+               }
+               if len(userMetadata) != 1 {
+                       t.Fatalf("write user metadata len = %d, want 1", 
len(userMetadata))
+               }
+               if got := BytePtrToString(userMetadata[0].key); got != "foo" {
+                       t.Fatalf("write user metadata key = %q, want foo", got)
+               }
+               if got := BytePtrToString(userMetadata[0].value); got != "bar" {
+                       t.Fatalf("write user metadata value = %q, want bar", 
got)
+               }
+               return reqInner, nil
+       })
+
+       op := &Operator{ctx: ctx, inner: opInner}
+       _, err := op.PresignWrite("file.txt", time.Minute,
+               WriteWithContentType("text/plain"),
+               WriteWithUserMetadata(map[string]string{"foo": "bar"}),
+       )
+       if err != nil {
+               t.Fatalf("PresignWrite with options failed: %v", err)
+       }
+}
+
+func TestPresignStatWithOptionsKeepsStringsUntilCall(t *testing.T) {
+       ctx, reqInner, _ := presignTestContext(t, "HEAD", 
"https://example.com/stat";)
+       opInner := &opendalOperator{}
+       cOpts := &opendalStatOptions{}
+       var versionPtr *byte
+       var overrideContentTypePtr *byte
+
+       ctx = context.WithValue(ctx, ffiStatOptionsNew.opts.sym, func() 
*opendalStatOptions { return cOpts })
+       ctx = context.WithValue(ctx, ffiStatOptionsFree.opts.sym, func(opts 
*opendalStatOptions) {
+               assertStatOptionsPointer(t, cOpts, opts)
+       })
+       ctx = context.WithValue(ctx, ffiStatOptionsSetVersion.opts.sym, 
ffiStatOptionsSetVersion.withFunc(ctx, func(_ unsafe.Pointer, aValues 
...unsafe.Pointer) {
+               assertStatOptionsPointerFromArgs(t, cOpts, aValues...)
+               versionPtr = *(**byte)(aValues[1])
+       }))
+       ctx = context.WithValue(ctx, ffiStatOptionsSetIfMatch.opts.sym, 
noopStatOptionsSetString)
+       ctx = context.WithValue(ctx, ffiStatOptionsSetIfNoneMatch.opts.sym, 
noopStatOptionsSetString)
+       ctx = context.WithValue(ctx, 
ffiStatOptionsSetOverrideContentType.opts.sym, 
ffiStatOptionsSetOverrideContentType.withFunc(ctx, func(_ unsafe.Pointer, 
aValues ...unsafe.Pointer) {
+               assertStatOptionsPointerFromArgs(t, cOpts, aValues...)
+               overrideContentTypePtr = *(**byte)(aValues[1])
+       }))
+       ctx = context.WithValue(ctx, 
ffiStatOptionsSetOverrideCacheControl.opts.sym, noopStatOptionsSetString)
+       ctx = context.WithValue(ctx, 
ffiStatOptionsSetOverrideContentDisposition.opts.sym, noopStatOptionsSetString)
+       ctx = context.WithValue(ctx, ffiOperatorPresignStatWith.opts.sym, 
func(op *opendalOperator, path string, expire uint64, opts *opendalStatOptions) 
(*opendalPresignedRequest, error) {
+               assertPresignCall(t, opInner, op, "file.txt", path, uint64(60), 
expire)
+               assertStatOptionsPointer(t, cOpts, opts)
+               if got := BytePtrToString(versionPtr); got != "v1" {
+                       t.Fatalf("stat version pointer = %q, want v1", got)
+               }
+               if got := BytePtrToString(overrideContentTypePtr); got != 
"text/plain" {
+                       t.Fatalf("stat override content type pointer = %q, want 
text/plain", got)
+               }
+               return reqInner, nil
+       })
+
+       op := &Operator{ctx: ctx, inner: opInner}
+       _, err := op.PresignStat("file.txt", time.Minute,
+               StatWithVersion("v1"),
+               StatWithOverrideContentType("text/plain"),
+       )
+       if err != nil {
+               t.Fatalf("PresignStat with options failed: %v", err)
+       }
+}
+
+func TestPresignDeleteWithOptionsKeepsStringsUntilCall(t *testing.T) {
+       ctx, reqInner, _ := presignTestContext(t, "DELETE", 
"https://example.com/delete";)
+       opInner := &opendalOperator{}
+       cOpts := &opendalDeleteOptions{}
+       var versionPtr *byte
+       var recursive bool
+
+       ctx = context.WithValue(ctx, ffiDeleteOptionsNew.opts.sym, func() 
*opendalDeleteOptions { return cOpts })
+       ctx = context.WithValue(ctx, ffiDeleteOptionsFree.opts.sym, func(opts 
*opendalDeleteOptions) {
+               assertDeleteOptionsPointer(t, cOpts, opts)
+       })
+       ctx = context.WithValue(ctx, ffiDeleteOptionsSetRecursive.opts.sym, 
func(opts *opendalDeleteOptions, value bool) {
+               assertDeleteOptionsPointer(t, cOpts, opts)
+               recursive = value
+       })
+       ctx = context.WithValue(ctx, ffiDeleteOptionsSetVersion.opts.sym, 
ffiDeleteOptionsSetVersion.withFunc(ctx, func(_ unsafe.Pointer, aValues 
...unsafe.Pointer) {
+               assertDeleteOptionsPointerFromArgs(t, cOpts, aValues...)
+               versionPtr = *(**byte)(aValues[1])
+       }))
+       ctx = context.WithValue(ctx, ffiOperatorPresignDeleteWith.opts.sym, 
func(op *opendalOperator, path string, expire uint64, opts 
*opendalDeleteOptions) (*opendalPresignedRequest, error) {
+               assertPresignCall(t, opInner, op, "file.txt", path, uint64(60), 
expire)
+               assertDeleteOptionsPointer(t, cOpts, opts)
+               if !recursive {
+                       t.Fatal("delete recursive = false, want true")
+               }
+               if got := BytePtrToString(versionPtr); got != "v1" {
+                       t.Fatalf("delete version pointer = %q, want v1", got)
+               }
+               return reqInner, nil
+       })
+
+       op := &Operator{ctx: ctx, inner: opInner}
+       _, err := op.PresignDelete("file.txt", time.Minute,
+               DeleteWithVersion("v1"),
+               DeleteWithRecursive(true),
+       )
+       if err != nil {
+               t.Fatalf("PresignDelete with options failed: %v", err)
+       }
+}
+
+func presignTestContext(t *testing.T, method, uri string) (context.Context, 
*opendalPresignedRequest, *int) {
+       t.Helper()
+
+       methodData, err := byteSliceFromString(method)
+       if err != nil {
+               t.Fatalf("byteSliceFromString(%q) failed: %v", method, err)
+       }
+       uriData, err := byteSliceFromString(uri)
+       if err != nil {
+               t.Fatalf("byteSliceFromString(%q) failed: %v", uri, err)
+       }
+       reqInner := &opendalPresignedRequest{}
+       freeCount := 0
+
+       ctx := context.Background()
+       ctx = context.WithValue(ctx, ffiPresignedRequestMethod.opts.sym, 
func(req *opendalPresignedRequest) *byte {
+               assertPresignedRequestPointer(t, reqInner, req)
+               return &methodData[0]
+       })
+       ctx = context.WithValue(ctx, ffiPresignedRequestUri.opts.sym, func(req 
*opendalPresignedRequest) *byte {
+               assertPresignedRequestPointer(t, reqInner, req)
+               return &uriData[0]
+       })
+       ctx = context.WithValue(ctx, ffiPresignedRequestHeaders.opts.sym, 
func(req *opendalPresignedRequest) *opendalHttpHeaderPair {
+               assertPresignedRequestPointer(t, reqInner, req)
+               return nil
+       })
+       ctx = context.WithValue(ctx, ffiPresignedRequestHeadersLen.opts.sym, 
func(req *opendalPresignedRequest) uintptr {
+               assertPresignedRequestPointer(t, reqInner, req)
+               return 0
+       })
+       ctx = context.WithValue(ctx, ffiPresignedRequestFree.opts.sym, func(req 
*opendalPresignedRequest) {
+               assertPresignedRequestPointer(t, reqInner, req)
+               freeCount++
+       })
+       return ctx, reqInner, &freeCount
+}
+
+func assertPresignCall(t *testing.T, wantOp *opendalOperator, gotOp 
*opendalOperator, wantPath, gotPath string, wantExpire, gotExpire uint64) {
+       t.Helper()
+       if gotOp != wantOp {
+               t.Fatalf("presign op = %p, want %p", gotOp, wantOp)
+       }
+       if gotPath != wantPath {
+               t.Fatalf("presign path = %q, want %q", gotPath, wantPath)
+       }
+       if gotExpire != wantExpire {
+               t.Fatalf("presign expire = %d, want %d", gotExpire, wantExpire)
+       }
+}
+
+func assertPresignedRequestPointer(t *testing.T, want 
*opendalPresignedRequest, got *opendalPresignedRequest) {
+       t.Helper()
+       if got != want {
+               t.Fatalf("presigned request = %p, want %p", got, want)
+       }
+}
+
+func assertReadOptionsPointer(t *testing.T, want *opendalReadOptions, got 
*opendalReadOptions) {
+       t.Helper()
+       if got != want {
+               t.Fatalf("read options = %p, want %p", got, want)
+       }
+}
+
+func assertReadOptionsPointerFromArgs(t *testing.T, want *opendalReadOptions, 
aValues ...unsafe.Pointer) {
+       t.Helper()
+       if len(aValues) != 2 {
+               t.Fatalf("read option setter received %d arguments, want 2", 
len(aValues))
+       }
+       assertReadOptionsPointer(t, want, *(**opendalReadOptions)(aValues[0]))
+}
+
+func noopReadOptionsSetString(*opendalReadOptions, string) ([]byte, error) {
+       return nil, nil
+}
+
+func assertWriteOptionsPointer(t *testing.T, want *opendalWriteOptions, got 
*opendalWriteOptions) {
+       t.Helper()
+       if got != want {
+               t.Fatalf("write options = %p, want %p", got, want)
+       }
+}
+
+func assertWriteOptionsPointerFromArgs(t *testing.T, want 
*opendalWriteOptions, aValues ...unsafe.Pointer) {
+       t.Helper()
+       if len(aValues) != 2 {
+               t.Fatalf("write option setter received %d arguments, want 2", 
len(aValues))
+       }
+       assertWriteOptionsPointer(t, want, *(**opendalWriteOptions)(aValues[0]))
+}
+
+func noopWriteOptionsSetString(*opendalWriteOptions, string) ([]byte, error) {
+       return nil, nil
+}
+
+func assertStatOptionsPointer(t *testing.T, want *opendalStatOptions, got 
*opendalStatOptions) {
+       t.Helper()
+       if got != want {
+               t.Fatalf("stat options = %p, want %p", got, want)
+       }
+}
+
+func assertStatOptionsPointerFromArgs(t *testing.T, want *opendalStatOptions, 
aValues ...unsafe.Pointer) {
+       t.Helper()
+       if len(aValues) != 2 {
+               t.Fatalf("stat option setter received %d arguments, want 2", 
len(aValues))
+       }
+       assertStatOptionsPointer(t, want, *(**opendalStatOptions)(aValues[0]))
+}
+
+func noopStatOptionsSetString(*opendalStatOptions, string) ([]byte, error) {
+       return nil, nil
+}
+
+func assertDeleteOptionsPointer(t *testing.T, want *opendalDeleteOptions, got 
*opendalDeleteOptions) {
+       t.Helper()
+       if got != want {
+               t.Fatalf("delete options = %p, want %p", got, want)
+       }
+}
+
+func assertDeleteOptionsPointerFromArgs(t *testing.T, want 
*opendalDeleteOptions, aValues ...unsafe.Pointer) {
+       t.Helper()
+       if len(aValues) != 2 {
+               t.Fatalf("delete option setter received %d arguments, want 2", 
len(aValues))
+       }
+       assertDeleteOptionsPointer(t, want, 
*(**opendalDeleteOptions)(aValues[0]))
+}
diff --git a/bindings/go/tests/behavior_tests/presign_test.go 
b/bindings/go/tests/behavior_tests/presign_test.go
index a8f8fd13c..39f80d6c9 100644
--- a/bindings/go/tests/behavior_tests/presign_test.go
+++ b/bindings/go/tests/behavior_tests/presign_test.go
@@ -35,15 +35,19 @@ func testsPresign(cap *opendal.Capability) []behaviorTest {
                return nil
        }
 
-       tests := make([]behaviorTest, 0, 4)
+       tests := make([]behaviorTest, 0, 6)
        if cap.PresignWrite() && cap.Stat() {
                tests = append(tests, testPresignWrite)
        }
        if cap.PresignRead() && cap.Write() {
                tests = append(tests, testPresignRead)
+               tests = append(tests, testPresignReadWithRange)
        }
        if cap.PresignStat() && cap.Write() {
                tests = append(tests, testPresignStat)
+               if isCapEnabled(cap.StatWithOverrideContentType, 
"stat_with_override_content_type") {
+                       tests = append(tests, 
testPresignStatWithOverrideContentType)
+               }
        }
        if cap.PresignDelete() {
                tests = append(tests, testPresignDelete)
@@ -92,6 +96,32 @@ func testPresignRead(assert *require.Assertions, op 
*opendal.Operator, fixture *
        assert.Equal(content, bs)
 }
 
+func testPresignReadWithRange(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
+       const (
+               offset = 3
+               length = 7
+       )
+
+       path := fixture.NewFilePath()
+       content := genFixedBytes(32)
+
+       assert.Nil(op.Write(path, content))
+
+       req, err := op.PresignRead(path, time.Hour, 
opendal.ReadWithRange(offset, length))
+       assert.Nil(err)
+
+       resp, err := http.DefaultClient.Do(req)
+       assert.Nil(err)
+       defer resp.Body.Close()
+
+       bs, err := io.ReadAll(resp.Body)
+       assert.Nil(err)
+       assert.GreaterOrEqual(resp.StatusCode, 200)
+       assert.Less(resp.StatusCode, 300)
+       assert.Equal(int(length), len(bs))
+       assert.Equal(content[offset:offset+length], bs)
+}
+
 func testPresignStat(assert *require.Assertions, op *opendal.Operator, fixture 
*fixture) {
        path, content, size := fixture.NewFile()
 
@@ -112,6 +142,22 @@ func testPresignStat(assert *require.Assertions, op 
*opendal.Operator, fixture *
        assert.EqualValues(size, length)
 }
 
+func testPresignStatWithOverrideContentType(assert *require.Assertions, op 
*opendal.Operator, fixture *fixture) {
+       path, content, _ := fixture.NewFile()
+       contentType := "application/octet-stream"
+
+       assert.Nil(op.Write(path, content))
+
+       req, err := op.PresignStat(path, time.Hour, 
opendal.StatWithOverrideContentType(contentType))
+       assert.Nil(err)
+
+       resp, err := http.DefaultClient.Do(req)
+       assert.Nil(err)
+       defer resp.Body.Close()
+       assert.Equal(http.StatusOK, resp.StatusCode)
+       assert.Equal(contentType, resp.Header.Get("Content-Type"))
+}
+
 func testPresignDelete(assert *require.Assertions, op *opendal.Operator, 
fixture *fixture) {
        path, content, _ := fixture.NewFile()
 
diff --git a/core/core/src/blocking/operator.rs 
b/core/core/src/blocking/operator.rs
index 751bf1f29..ddae26fff 100644
--- a/core/core/src/blocking/operator.rs
+++ b/core/core/src/blocking/operator.rs
@@ -193,6 +193,18 @@ impl Operator {
         self.handle.block_on(self.op.presign_stat(path, expire))
     }
 
+    /// Create a presigned request for stat with additional options.
+    pub fn presign_stat_options(
+        &self,
+        path: &str,
+        expire: Duration,
+        opts: options::StatOptions,
+    ) -> Result<PresignedRequest> {
+        let op = self.op.clone();
+        let path = path.to_string();
+        self.spawn_block(async move { op.presign_stat_options(&path, expire, 
opts).await })?
+    }
+
     /// Create a presigned request for read.
     ///
     /// See [`Operator::presign_read`] for more details.
@@ -200,6 +212,18 @@ impl Operator {
         self.handle.block_on(self.op.presign_read(path, expire))
     }
 
+    /// Create a presigned request for read with additional options.
+    pub fn presign_read_options(
+        &self,
+        path: &str,
+        expire: Duration,
+        opts: options::ReadOptions,
+    ) -> Result<PresignedRequest> {
+        let op = self.op.clone();
+        let path = path.to_string();
+        self.spawn_block(async move { op.presign_read_options(&path, expire, 
opts).await })?
+    }
+
     /// Create a presigned request for write.
     ///
     /// See [`Operator::presign_write`] for more details.
@@ -207,6 +231,18 @@ impl Operator {
         self.handle.block_on(self.op.presign_write(path, expire))
     }
 
+    /// Create a presigned request for write with additional options.
+    pub fn presign_write_options(
+        &self,
+        path: &str,
+        expire: Duration,
+        opts: options::WriteOptions,
+    ) -> Result<PresignedRequest> {
+        let op = self.op.clone();
+        let path = path.to_string();
+        self.spawn_block(async move { op.presign_write_options(&path, expire, 
opts).await })?
+    }
+
     /// Create a presigned request for delete.
     ///
     /// See [`Operator::presign_delete`] for more details.
@@ -214,6 +250,18 @@ impl Operator {
         self.handle.block_on(self.op.presign_delete(path, expire))
     }
 
+    /// Create a presigned request for delete with additional options.
+    pub fn presign_delete_options(
+        &self,
+        path: &str,
+        expire: Duration,
+        opts: options::DeleteOptions,
+    ) -> Result<PresignedRequest> {
+        let op = self.op.clone();
+        let path = path.to_string();
+        self.spawn_block(async move { op.presign_delete_options(&path, expire, 
opts).await })?
+    }
+
     /// Get given path's metadata.
     ///
     /// # Behavior

Reply via email to