Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 69 additions & 11 deletions core/core/src/types/operator/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
Expand All @@ -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"));
}
}
Loading