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"));
+    }
+}

Reply via email to