This is an automated email from the ASF dual-hosted git repository.
erickguan 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 6738df27f feat(bindings/ruby): support layers (#5874)
6738df27f is described below
commit 6738df27fd9d3636850aa9c98b3b309021c6f29b
Author: Erick Guan <[email protected]>
AuthorDate: Sat Apr 5 16:19:34 2025 +0200
feat(bindings/ruby): support layers (#5874)
* doc(bindings/ruby): doc updates and thread-safety notes
Also various improvements over #inspect methods to be friendly within
interactive consoles.
* feat(bindings/ruby): support layers
* feat(bindings/ruby): add mode to Metadata
Also simplifies the implementation
* Use the term middleware to refer layer
* Use duck typing to apply layers
---
bindings/ruby/Cargo.toml | 1 +
bindings/ruby/lib/opendal.rb | 3 +
bindings/ruby/lib/opendal_ruby/entry.rb | 5 +
.../lib/opendal_ruby/{entry.rb => metadata.rb} | 28 +++-
.../lib/opendal_ruby/{entry.rb => operator.rb} | 13 +-
.../opendal_ruby/{entry.rb => operator_info.rb} | 10 +-
bindings/ruby/src/lib.rs | 2 +
bindings/ruby/src/lister.rs | 16 +-
bindings/ruby/src/metadata.rs | 32 ++--
bindings/ruby/src/middlewares.rs | 167 +++++++++++++++++++++
bindings/ruby/src/operator.rs | 67 ++++++---
bindings/ruby/test/blocking_op_test.rb | 6 +
.../entry.rb => test/middlewares_test.rb} | 32 +++-
13 files changed, 315 insertions(+), 67 deletions(-)
diff --git a/bindings/ruby/Cargo.toml b/bindings/ruby/Cargo.toml
index 133cc41bf..83e35a4bd 100644
--- a/bindings/ruby/Cargo.toml
+++ b/bindings/ruby/Cargo.toml
@@ -35,6 +35,7 @@ name = "opendal_ruby"
magnus = { version = "0.7.1", features = ["bytes"] }
# this crate won't be published, we always use the local version
opendal = { version = ">=0", path = "../../core", features = [
+ "layers-throttle",
# These are default features before v0.46. TODO: change to optional features
"services-azblob",
"services-azdls",
diff --git a/bindings/ruby/lib/opendal.rb b/bindings/ruby/lib/opendal.rb
index 079721860..78604e0ea 100644
--- a/bindings/ruby/lib/opendal.rb
+++ b/bindings/ruby/lib/opendal.rb
@@ -20,3 +20,6 @@
require_relative "opendal_ruby/opendal_ruby"
require_relative "opendal_ruby/io"
require_relative "opendal_ruby/entry"
+require_relative "opendal_ruby/metadata"
+require_relative "opendal_ruby/operator"
+require_relative "opendal_ruby/operator_info"
diff --git a/bindings/ruby/lib/opendal_ruby/entry.rb
b/bindings/ruby/lib/opendal_ruby/entry.rb
index 7dfc86d85..2bbd91ddb 100644
--- a/bindings/ruby/lib/opendal_ruby/entry.rb
+++ b/bindings/ruby/lib/opendal_ruby/entry.rb
@@ -20,11 +20,16 @@
module OpenDAL
class Entry
# Returns the canonical data about an entry
+ # @return [Hash]
def to_h
{
path: path,
metadata: metadata
}
end
+
+ def inspect
+ "#<#{self.class.name} path: \"#{path}\", metadata: #{metadata.inspect}>"
+ end
end
end
diff --git a/bindings/ruby/lib/opendal_ruby/entry.rb
b/bindings/ruby/lib/opendal_ruby/metadata.rb
similarity index 63%
copy from bindings/ruby/lib/opendal_ruby/entry.rb
copy to bindings/ruby/lib/opendal_ruby/metadata.rb
index 7dfc86d85..1bc576cee 100644
--- a/bindings/ruby/lib/opendal_ruby/entry.rb
+++ b/bindings/ruby/lib/opendal_ruby/metadata.rb
@@ -18,13 +18,27 @@
# frozen_string_literal: true
module OpenDAL
- class Entry
- # Returns the canonical data about an entry
- def to_h
- {
- path: path,
- metadata: metadata
- }
+ class Metadata
+ FILE = "File"
+ DIRECTORY = "Directory"
+
+ # Returns `True` if this is a file.
+ # @return [Boolean]
+ def file?
+ mode == FILE
+ end
+
+ # Returns `True` if this is a directory.
+ # @return [Boolean]
+ def dir?
+ mode == DIRECTORY
+ end
+
+ def inspect
+ # Be concise to keep a few attributes
+ "#<#{self.class.name} mode: #{entry_mode}, \
+ content_type: #{content_type}, \
+ content_length: #{content_length}>"
end
end
end
diff --git a/bindings/ruby/lib/opendal_ruby/entry.rb
b/bindings/ruby/lib/opendal_ruby/operator.rb
similarity index 77%
copy from bindings/ruby/lib/opendal_ruby/entry.rb
copy to bindings/ruby/lib/opendal_ruby/operator.rb
index 7dfc86d85..416ecacf7 100644
--- a/bindings/ruby/lib/opendal_ruby/entry.rb
+++ b/bindings/ruby/lib/opendal_ruby/operator.rb
@@ -18,13 +18,12 @@
# frozen_string_literal: true
module OpenDAL
- class Entry
- # Returns the canonical data about an entry
- def to_h
- {
- path: path,
- metadata: metadata
- }
+ class Operator
+ # Applies a middleware to the operator.
+ # @param middleware [Middleware]
+ # @return [Operator]
+ def middleware(middleware)
+ middleware.apply_to(self) # duck typing, expects a middleware to
implement `#apply_to`
end
end
end
diff --git a/bindings/ruby/lib/opendal_ruby/entry.rb
b/bindings/ruby/lib/opendal_ruby/operator_info.rb
similarity index 86%
copy from bindings/ruby/lib/opendal_ruby/entry.rb
copy to bindings/ruby/lib/opendal_ruby/operator_info.rb
index 7dfc86d85..8951d9be6 100644
--- a/bindings/ruby/lib/opendal_ruby/entry.rb
+++ b/bindings/ruby/lib/opendal_ruby/operator_info.rb
@@ -18,13 +18,9 @@
# frozen_string_literal: true
module OpenDAL
- class Entry
- # Returns the canonical data about an entry
- def to_h
- {
- path: path,
- metadata: metadata
- }
+ class OperatorInfo
+ def inspect
+ "#<#{self.class.name} scheme: \"#{scheme}\", root: \"#{root}\">"
end
end
end
diff --git a/bindings/ruby/src/lib.rs b/bindings/ruby/src/lib.rs
index f9bb91af3..d2a3f8d67 100644
--- a/bindings/ruby/src/lib.rs
+++ b/bindings/ruby/src/lib.rs
@@ -28,6 +28,7 @@ mod capability;
mod io;
mod lister;
mod metadata;
+mod middlewares;
mod operator;
mod operator_info;
@@ -45,6 +46,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
let _ = io::include(&gem_module);
let _ = lister::include(ruby, &gem_module);
let _ = operator_info::include(&gem_module);
+ let _ = middlewares::include(&gem_module);
Ok(())
}
diff --git a/bindings/ruby/src/lister.rs b/bindings/ruby/src/lister.rs
index 0516c92ed..bc4d30e9a 100644
--- a/bindings/ruby/src/lister.rs
+++ b/bindings/ruby/src/lister.rs
@@ -22,7 +22,6 @@
#![allow(rustdoc::invalid_html_tags, reason = "YARD's syntax for
documentation")]
#![allow(rustdoc::bare_urls, reason = "YARD's syntax for documentation")]
-use std::borrow::BorrowMut;
use std::sync::Arc;
use std::sync::Mutex;
@@ -80,6 +79,10 @@ impl Entry {
/// Represents the result when list a directory
///
/// This class is an enumerable.
+///
+/// # Safety
+///
+/// `Lister` is thread-safe.
#[magnus::wrap(class = "OpenDAL::Lister", free_immediately, size)]
pub struct Lister(Arc<Mutex<ocore::BlockingLister>>);
@@ -87,7 +90,8 @@ impl Iterator for Lister {
type Item = Entry;
fn next(&mut self) -> Option<Self::Item> {
- if let Ok(mut inner) = self.0.borrow_mut().lock() {
+ // assumes low contention. also we want an entry eventually
+ if let Ok(mut inner) = self.0.lock() {
match inner.next() {
Some(Ok(entry)) => Some(Entry(entry)),
_ => None,
@@ -109,6 +113,14 @@ impl Lister {
/// Returns the next element.
/// @return [Entry]
fn each(&self) -> Result<Yield<Lister>, Error> {
+ // Magnus handles yielding to Ruby using an unsafe internal function,
+ // so we don’t manage the actual iteration loop ourselves.
+ //
+ // Since Ruby controls when values are pulled from the iterator,
+ // and could potentially call `each` from multiple threads or fibers,
+ // we wrap the underlying lister in `Arc<Mutex<_>>` to ensure thread
safety.
+ //
+ // Multi-threaded iteration is rare in Ruby, but this design ensures
thread safety.
Ok(Yield::Iter(Lister(self.0.clone())))
}
}
diff --git a/bindings/ruby/src/metadata.rs b/bindings/ruby/src/metadata.rs
index dc1a7cf59..32ff89d51 100644
--- a/bindings/ruby/src/metadata.rs
+++ b/bindings/ruby/src/metadata.rs
@@ -42,6 +42,19 @@ impl Metadata {
}
impl Metadata {
+ /// @yard
+ /// @def mode
+ /// The entry mode of this Metadata.
+ /// @return [String] one of `File`, `Directory`, `Unknown`.
+ /// `Unknown` means OpenDAL doesn't have actions to the entry.
+ pub fn mode(&self) -> &str {
+ match self.0.mode() {
+ ocore::EntryMode::FILE => "File",
+ ocore::EntryMode::DIR => "Directory",
+ ocore::EntryMode::Unknown => "Unknown",
+ }
+ }
+
/// @yard
/// @def content_disposition
/// Content-Disposition of this object
@@ -81,26 +94,11 @@ impl Metadata {
pub fn etag(&self) -> Option<&str> {
self.0.etag()
}
-
- /// @yard
- /// @def file?
- /// Returns `True` if this is a file.
- /// @return [Boolean]
- pub fn is_file(&self) -> bool {
- self.0.is_file()
- }
-
- /// @yard
- /// @def dir?
- /// Returns `True` if this is a directory.
- /// @return [Boolean]
- pub fn is_dir(&self) -> bool {
- self.0.is_dir()
- }
}
pub fn include(gem_module: &RModule) -> Result<(), Error> {
let class = gem_module.define_class("Metadata", class::object())?;
+ class.define_method("mode", method!(Metadata::mode, 0))?;
class.define_method(
"content_disposition",
method!(Metadata::content_disposition, 0),
@@ -109,8 +107,6 @@ pub fn include(gem_module: &RModule) -> Result<(), Error> {
class.define_method("content_md5", method!(Metadata::content_md5, 0))?;
class.define_method("content_type", method!(Metadata::content_type, 0))?;
class.define_method("etag", method!(Metadata::etag, 0))?;
- class.define_method("file?", method!(Metadata::is_file, 0))?;
- class.define_method("dir?", method!(Metadata::is_dir, 0))?;
Ok(())
}
diff --git a/bindings/ruby/src/middlewares.rs b/bindings/ruby/src/middlewares.rs
new file mode 100644
index 000000000..0b271aece
--- /dev/null
+++ b/bindings/ruby/src/middlewares.rs
@@ -0,0 +1,167 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::time::Duration;
+
+use magnus::class;
+use magnus::method;
+use magnus::prelude::*;
+use magnus::Error;
+use magnus::RModule;
+use magnus::Ruby;
+
+use opendal::raw::Accessor as OCoreAccessor;
+use opendal::raw::Layer as OCoreLayer;
+
+use crate::operator::Operator;
+use crate::*;
+
+// Applies an OpenDAL layer (treated as middleware in Ruby).
+//
+// Magnus provides `TypedData` and the `magnus::wrap` macro for strong typing
between Ruby and Rust,
+// but we avoid exposing Rust types directly in Ruby to allow packaging layers
in separate gems.
+//
+// Instead, we rely on duck typing: each Ruby-defined layer must implement
`#apply_to(operator)`.
+// This internal function helps apply a Rust layer by locking the inner Layer
and wrapping the result
+// back into an `Operator`.
+fn apply_layer<T>(
+ ruby: &Ruby,
+ inner: &Arc<Mutex<T>>,
+ operator: &Operator,
+ name: &'static str,
+) -> Result<Operator, Error>
+where
+ T: OCoreLayer<OCoreAccessor> + Clone + Send + Sync + 'static,
+{
+ let guard = inner.lock().map_err(|_| {
+ Error::new(
+ ruby.exception_runtime_error(),
+ format!("poisoned {} mutex", name),
+ )
+ })?;
+
+ let layered = operator.operator.clone().layer(guard.clone());
+
+ Ok(Operator::from_operator(layered))
+}
+
+/// @yard
+/// Adds retry for temporary failed operations.
+///
+/// See [`opendal::layers::RetryLayer`] for more information.
+#[magnus::wrap(class = "OpenDAL::RetryMiddleware")]
+struct RetryMiddleware(Arc<Mutex<ocore::layers::RetryLayer>>);
+
+impl RetryMiddleware {
+ fn new() -> Self {
+ Self(Arc::new(Mutex::new(ocore::layers::RetryLayer::default())))
+ }
+
+ fn apply_to(ruby: &Ruby, rb_self: &Self, operator: &Operator) ->
Result<Operator, Error> {
+ apply_layer(ruby, &rb_self.0, operator, "RetryMiddleware")
+ }
+}
+
+/// @yard
+/// Adds concurrent request limit.
+///
+/// See [`opendal::layers::ConcurrentLimitLayer`] for more information.
+#[magnus::wrap(class = "OpenDAL::ConcurrentLimitMiddleware")]
+struct
ConcurrentLimitMiddleware(Arc<Mutex<ocore::layers::ConcurrentLimitLayer>>);
+
+impl ConcurrentLimitMiddleware {
+ fn new(permits: usize) -> Self {
+ Self(Arc::new(Mutex::new(
+ ocore::layers::ConcurrentLimitLayer::new(permits),
+ )))
+ }
+
+ fn apply_to(ruby: &Ruby, rb_self: &Self, operator: &Operator) ->
Result<Operator, Error> {
+ apply_layer(ruby, &rb_self.0, operator, "ConcurrentLimitMiddleware")
+ }
+}
+
+/// @yard
+/// Adds a bandwidth rate limiter to the underlying services.
+///
+/// See [`opendal::layers::ThrottleLayer`] for more information.
+#[magnus::wrap(class = "OpenDAL::ThrottleMiddleware")]
+struct ThrottleMiddleware(Arc<Mutex<ocore::layers::ThrottleLayer>>);
+
+impl ThrottleMiddleware {
+ fn new(bandwidth: u32, burst: u32) -> Self {
+ Self(Arc::new(Mutex::new(ocore::layers::ThrottleLayer::new(
+ bandwidth, burst,
+ ))))
+ }
+
+ fn apply_to(ruby: &Ruby, rb_self: &Self, operator: &Operator) ->
Result<Operator, Error> {
+ apply_layer(ruby, &rb_self.0, operator, "ConcurrentLimitMiddleware")
+ }
+}
+
+fn parse_duration(ruby: &Ruby, val: f64) -> Result<Duration, Error> {
+ Duration::try_from_secs_f64(val).map_err(|e| {
+ Error::new(
+ ruby.exception_arg_error(),
+ format!("invalid float duration: {e}"),
+ )
+ })
+}
+
+/// @yard
+/// Adds timeout for every operation to avoid slow or unexpected hang
operations.
+///
+/// See [`opendal::layers::TimeoutLayer`] for more information.
+#[magnus::wrap(class = "OpenDAL::TimeoutMiddleware")]
+struct TimeoutMiddleware(Arc<Mutex<ocore::layers::TimeoutLayer>>);
+
+impl TimeoutMiddleware {
+ fn new(ruby: &Ruby, timeout: f64, io_timeout: f64) -> Result<Self, Error> {
+ let layer = ocore::layers::TimeoutLayer::new()
+ .with_timeout(parse_duration(ruby, timeout)?)
+ .with_io_timeout(parse_duration(ruby, io_timeout)?);
+ Ok(Self(Arc::new(Mutex::new(layer))))
+ }
+
+ fn apply_to(ruby: &Ruby, rb_self: &Self, operator: &Operator) ->
Result<Operator, Error> {
+ apply_layer(ruby, &rb_self.0, operator, "TimeoutMiddleware")
+ }
+}
+
+pub fn include(gem_module: &RModule) -> Result<(), Error> {
+ let retry = gem_module.define_class("RetryMiddleware", class::object())?;
+ retry.define_singleton_method("new", function!(RetryMiddleware::new, 0))?;
+ retry.define_method("apply_to", method!(RetryMiddleware::apply_to, 1))?;
+
+ let concurrent_limit =
gem_module.define_class("ConcurrentLimitMiddleware", class::object())?;
+ concurrent_limit
+ .define_singleton_method("new",
function!(ConcurrentLimitMiddleware::new, 1))?;
+ concurrent_limit.define_method("apply_to",
method!(ConcurrentLimitMiddleware::apply_to, 1))?;
+
+ let throttle_middleware = gem_module.define_class("ThrottleMiddleware",
class::object())?;
+ throttle_middleware.define_singleton_method("new",
function!(ThrottleMiddleware::new, 2))?;
+ throttle_middleware.define_method("apply_to",
method!(ThrottleMiddleware::apply_to, 1))?;
+
+ let timeout_middleware = gem_module.define_class("TimeoutMiddleware",
class::object())?;
+ timeout_middleware.define_singleton_method("new",
function!(TimeoutMiddleware::new, 2))?;
+ timeout_middleware.define_method("apply_to",
method!(TimeoutMiddleware::apply_to, 1))?;
+
+ Ok(())
+}
diff --git a/bindings/ruby/src/operator.rs b/bindings/ruby/src/operator.rs
index f43a98b7b..75bc89d04 100644
--- a/bindings/ruby/src/operator.rs
+++ b/bindings/ruby/src/operator.rs
@@ -46,8 +46,36 @@ use crate::*;
/// @yard
/// The entrypoint for operating with file services and files.
#[magnus::wrap(class = "OpenDAL::Operator", free_immediately, size)]
-#[derive(Clone, Debug)]
-pub struct Operator(ocore::BlockingOperator);
+pub struct Operator {
+ // We keep a reference to an `Operator` because:
+ // 1. Some builder functions exist only with the `Operator` struct.
+ // 2. Some builder functions don't exist in the `BlockingOperator`, e.g.,
`Operator::layer`, builder methods.
+ //
+ // We don't support async because:
+ // 1. Ractor and async is not stable.
+ // 2. magnus doesn't release GVL lock during operations yet.
+ // 3. Majority of use cases are still blocking.
+ //
+ // In practice, we will not use operator directly because of the async
support.
+ pub operator: ocore::Operator,
+ // The cached blocking operator coming from `Operator::blocking`.
+ // Important to keep in sync after making changes to the `Operator`.
+ //
+ // We declare this `BlockingOperator` to state the intent of assumptions
instead of
+ // getting a `BlockingOperator` for an operation dynamically every time.
+ pub blocking: ocore::BlockingOperator,
+}
+
+impl Operator {
+ // Convenience helper to construct operator
+ #[inline]
+ pub(crate) fn from_operator(operator: ocore::Operator) -> Operator {
+ // doesn't change `operator`'s state, but creates a new pointer to the
inner accessor
+ let blocking = operator.blocking();
+
+ Operator { operator, blocking }
+ }
+}
impl Operator {
fn new(
@@ -64,9 +92,9 @@ impl Operator {
let options = options.unwrap_or_default();
let op = ocore::Operator::via_iter(scheme, options)
- .map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))?
- .blocking();
- Ok(Operator(op))
+ .map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))?;
+
+ Ok(Operator::from_operator(op))
}
/// @yard
@@ -76,7 +104,7 @@ impl Operator {
/// @return [String]
fn read(ruby: &Ruby, rb_self: &Self, path: String) -> Result<bytes::Bytes,
Error> {
let buffer = rb_self
- .0
+ .blocking
.read(&path)
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))?;
Ok(buffer.to_bytes())
@@ -90,7 +118,7 @@ impl Operator {
/// @return [nil]
fn write(ruby: &Ruby, rb_self: &Self, path: String, bs: RString) ->
Result<(), Error> {
rb_self
- .0
+ .blocking
.write(&path, bs.to_bytes())
.map(|_| ())
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))
@@ -103,7 +131,7 @@ impl Operator {
/// @return [Metadata]
fn stat(ruby: &Ruby, rb_self: &Self, path: String) -> Result<Metadata,
Error> {
rb_self
- .0
+ .blocking
.stat(&path)
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))
.map(Metadata::new)
@@ -114,7 +142,7 @@ impl Operator {
/// Gets capabilities of the current operator
/// @return [Capability]
fn capability(&self) -> Result<Capability, Error> {
- let capability = self.0.info().full_capability();
+ let capability = self.blocking.info().full_capability();
Ok(Capability::new(capability))
}
@@ -126,7 +154,7 @@ impl Operator {
/// @return [nil]
fn create_dir(ruby: &Ruby, rb_self: &Self, path: String) -> Result<(),
Error> {
rb_self
- .0
+ .blocking
.create_dir(&path)
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))
}
@@ -138,7 +166,7 @@ impl Operator {
/// @return [nil]
fn delete(ruby: &Ruby, rb_self: &Self, path: String) -> Result<(), Error> {
rb_self
- .0
+ .blocking
.delete(&path)
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))
}
@@ -150,7 +178,7 @@ impl Operator {
/// @return [Boolean]
fn exists(ruby: &Ruby, rb_self: &Self, path: String) -> Result<bool,
Error> {
rb_self
- .0
+ .blocking
.exists(&path)
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))
}
@@ -163,7 +191,7 @@ impl Operator {
/// @return [nil]
fn rename(ruby: &Ruby, rb_self: &Self, from: String, to: String) ->
Result<(), Error> {
rb_self
- .0
+ .blocking
.rename(&from, &to)
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))
}
@@ -175,7 +203,7 @@ impl Operator {
/// @return [nil]
fn remove_all(ruby: &Ruby, rb_self: &Self, path: String) -> Result<(),
Error> {
rb_self
- .0
+ .blocking
.remove_all(&path)
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))
}
@@ -188,7 +216,7 @@ impl Operator {
/// @return [nil]
fn copy(ruby: &Ruby, rb_self: &Self, from: String, to: String) ->
Result<(), Error> {
rb_self
- .0
+ .blocking
.copy(&from, &to)
.map_err(|err| Error::new(ruby.exception_runtime_error(),
err.to_string()))
}
@@ -201,7 +229,7 @@ impl Operator {
/// @raise [ArgumentError] invalid mode, or when the mode is not unique
/// @return [OpenDAL::IO]
fn open(ruby: &Ruby, rb_self: &Self, path: String, mode: String) ->
Result<Io, Error> {
- let operator = rb_self.0.clone();
+ let operator = rb_self.blocking.clone();
Io::new(ruby, operator, path, mode)
}
@@ -222,7 +250,7 @@ impl Operator {
)?;
let (limit, start_after, recursive) = kwargs.optional;
- let mut builder = rb_self.0.clone().lister_with(&path);
+ let mut builder = rb_self.blocking.lister_with(&path);
if let Some(limit) = limit {
builder = builder.limit(limit);
@@ -243,9 +271,12 @@ impl Operator {
Ok(Lister::new(lister))
}
+ /// @yard
+ /// @def info
/// Gets meta information of the underlying accessor.
+ /// @return [OperatorInfo]
fn info(&self) -> Result<OperatorInfo, Error> {
- Ok(OperatorInfo(self.0.info()))
+ Ok(OperatorInfo(self.blocking.info()))
}
}
diff --git a/bindings/ruby/test/blocking_op_test.rb
b/bindings/ruby/test/blocking_op_test.rb
index 0bdb5bd85..f0c1d71d2 100644
--- a/bindings/ruby/test/blocking_op_test.rb
+++ b/bindings/ruby/test/blocking_op_test.rb
@@ -103,4 +103,10 @@ class OpenDalTest < ActiveSupport::TestCase
io.close
end
+
+ test "middleware applies a middleware" do
+ @op.middleware(OpenDAL::RetryMiddleware.new)
+
+ assert @op.is_a?(OpenDAL::Operator)
+ end
end
diff --git a/bindings/ruby/lib/opendal_ruby/entry.rb
b/bindings/ruby/test/middlewares_test.rb
similarity index 59%
copy from bindings/ruby/lib/opendal_ruby/entry.rb
copy to bindings/ruby/test/middlewares_test.rb
index 7dfc86d85..bf239819e 100644
--- a/bindings/ruby/lib/opendal_ruby/entry.rb
+++ b/bindings/ruby/test/middlewares_test.rb
@@ -17,14 +17,30 @@
# frozen_string_literal: true
-module OpenDAL
- class Entry
- # Returns the canonical data about an entry
- def to_h
- {
- path: path,
- metadata: metadata
- }
+require "test_helper"
+
+class MiddlewaresTest < ActiveSupport::TestCase
+ test "builds a retry middleware" do
+ assert_nothing_raised do
+ OpenDAL::RetryMiddleware.new
+ end
+ end
+
+ test "builds a concurrent limit middleware" do
+ assert_nothing_raised do
+ OpenDAL::ConcurrentLimitMiddleware.new(10)
+ end
+ end
+
+ test "builds a throttle middleware" do
+ assert_nothing_raised do
+ OpenDAL::ThrottleMiddleware.new(100, 10)
+ end
+ end
+
+ test "builds a timeout middleware" do
+ assert_nothing_raised do
+ OpenDAL::TimeoutMiddleware.new(1.23, 1.0)
end
end
end