This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 318051086 feat(core): expose registered schemes via
OperatorRegistry::schemes (#7908)
318051086 is described below
commit 318051086ba99a2c02ac2492105faf4aceb73815
Author: Chitral Verma <[email protected]>
AuthorDate: Mon Jul 13 16:54:02 2026 +0530
feat(core): expose registered schemes via OperatorRegistry::schemes (#7908)
* feat(core): expose registered schemes via OperatorRegistry::schemes
Add a public OperatorRegistry::schemes() accessor that returns the set
of registered schemes (HashSet<String>) as a thin read over the existing
factories map. For the global registry this is exactly the set that
Operator::from_uri can construct.
Enrich the from_uri 'scheme is not registered' error with an 'available'
context listing the registered schemes, so a wrong dialect or missing
services-* feature surfaces an actionable hint instead of a bare error.
* fix(core): avoid self-deadlock in registry load() on unsupported scheme
load() held the factories mutex while the ok_or_else closure called
schemes(), which re-locked the same non-reentrant mutex and deadlocked
on the unsupported-scheme path. Collect the available schemes from the
guard already held instead of re-locking.
---
core/core/src/types/operator/registry.rs | 80 +++++++++++++++++++++++++++-----
1 file changed, 69 insertions(+), 11 deletions(-)
diff --git a/core/core/src/types/operator/registry.rs
b/core/core/src/types/operator/registry.rs
index d7ba45ff9..cbce30701 100644
--- a/core/core/src/types/operator/registry.rs
+++ b/core/core/src/types/operator/registry.rs
@@ -18,7 +18,7 @@
use crate::types::builder::{Builder, Configurator};
use crate::types::{IntoOperatorUri, OperatorUri};
use crate::{Error, ErrorKind, Operator, Result};
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
use std::sync::{LazyLock, Mutex};
/// Factory signature used to construct [`Operator`] from a URI and extra
options.
@@ -59,21 +59,53 @@ impl OperatorRegistry {
guard.insert(key, factory::<B::Config>);
}
+ /// Return the set of schemes currently registered.
+ ///
+ /// For the global registry returned by [`OperatorRegistry::get`], this is
+ /// exactly the set of schemes that [`Operator::from_uri`] can construct,
as
+ /// determined by the compiled-in `services-*` features.
+ ///
+ /// ```
+ /// use opendal_core::OperatorRegistry;
+ ///
+ /// let schemes = OperatorRegistry::get().schemes();
+ /// assert!(schemes.contains("memory"));
+ /// ```
+ pub fn schemes(&self) -> HashSet<String> {
+ self.factories
+ .lock()
+ .expect("operator registry mutex poisoned")
+ .keys()
+ .cloned()
+ .collect()
+ }
+
/// Load an [`Operator`] via the factory registered for the URI's scheme.
pub fn load(&self, uri: impl IntoOperatorUri) -> Result<Operator> {
let parsed = uri.into_operator_uri()?;
let scheme = parsed.scheme();
- let factory = self
- .factories
- .lock()
- .expect("operator registry mutex poisoned")
- .get(scheme)
- .copied()
- .ok_or_else(|| {
- Error::new(ErrorKind::Unsupported, "scheme is not registered")
- .with_context("scheme", scheme.to_string())
- })?;
+ let factory = {
+ let guard = self
+ .factories
+ .lock()
+ .expect("operator registry mutex poisoned");
+
+ match guard.get(scheme).copied() {
+ Some(factory) => factory,
+ None => {
+ // Collect the available schemes from the guard we already
hold
+ // to avoid re-locking the mutex (which would
self-deadlock).
+ let mut available: Vec<String> =
guard.keys().cloned().collect();
+ available.sort();
+ return Err(
+ Error::new(ErrorKind::Unsupported, "scheme is not
registered")
+ .with_context("scheme", scheme.to_string())
+ .with_context("available", available.join(", ")),
+ );
+ }
+ }
+ };
factory(&parsed)
}
@@ -84,3 +116,29 @@ fn factory<C: Configurator>(uri: &OperatorUri) ->
Result<Operator> {
let cfg = C::from_uri(uri)?;
Operator::from_config(cfg)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn global_registry_exposes_memory_scheme() {
+ let schemes = OperatorRegistry::get().schemes();
+ assert!(schemes.contains("memory"));
+ }
+
+ #[test]
+ fn unregistered_scheme_error_lists_available_schemes() {
+ let err = OperatorRegistry::get()
+ .load("no-such-scheme://bucket/path")
+ .unwrap_err();
+
+ assert_eq!(err.kind(), ErrorKind::Unsupported);
+
+ let msg = err.to_string();
+ assert!(msg.contains("scheme is not registered"));
+ assert!(msg.contains("no-such-scheme"));
+ assert!(msg.contains("available"));
+ assert!(msg.contains("memory"));
+ }
+}