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