Skip to main content

uselesskey_core/
factory.rs

1use alloc::sync::Arc;
2use core::any::Any;
3use core::fmt;
4
5use crate::id::{ArtifactDomain, Seed};
6use uselesskey_core_factory::Factory as CoreFactory;
7
8/// How a [`Factory`] generates artifacts.
9///
10/// # Examples
11///
12/// ```
13/// use uselesskey_core::{Factory, Mode, Seed};
14///
15/// // Check if a factory is in random or deterministic mode
16/// let fx = Factory::random();
17/// assert!(matches!(fx.mode(), Mode::Random));
18///
19/// let seed = Seed::from_env_value("test").unwrap();
20/// let fx = Factory::deterministic(seed);
21/// assert!(matches!(fx.mode(), Mode::Deterministic { .. }));
22/// ```
23#[derive(Clone)]
24pub struct Factory {
25    inner: CoreFactory,
26}
27
28pub use uselesskey_core_factory::Mode;
29
30impl fmt::Debug for Factory {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        self.inner.fmt(f)
33    }
34}
35
36impl Factory {
37    /// Create a new factory with the specified mode.
38    pub fn new(mode: Mode) -> Self {
39        Self {
40            inner: CoreFactory::new(mode),
41        }
42    }
43
44    /// Create a factory in random mode.
45    ///
46    /// Each process run produces different artifacts, but within a process,
47    /// artifacts are cached by `(domain, label, spec, variant)`.
48    pub fn random() -> Self {
49        Self::new(Mode::Random)
50    }
51
52    /// Create a deterministic factory from an existing seed.
53    pub fn deterministic(master: Seed) -> Self {
54        Self::new(Mode::Deterministic { master })
55    }
56
57    /// Create a deterministic factory from plain text.
58    ///
59    /// This hashes the provided string verbatim with BLAKE3. Unlike
60    /// [`Seed::from_env_value`], it does not trim whitespace or interpret
61    /// hex-shaped strings specially.
62    pub fn deterministic_from_str(text: &str) -> Self {
63        Self::deterministic(Seed::from_text(text))
64    }
65
66    /// Create a deterministic factory from an environment variable.
67    ///
68    /// The environment variable can contain:
69    /// - A 64-character hex string (with optional `0x` prefix)
70    /// - Any other string (hashed to produce a 32-byte seed)
71    ///
72    /// # Errors
73    ///
74    /// Returns an error if the environment variable is not set.
75    #[cfg(feature = "std")]
76    pub fn deterministic_from_env(var: &str) -> Result<Self, crate::Error> {
77        let raw = std::env::var(var).map_err(|_| crate::Error::MissingEnvVar {
78            var: var.to_string(),
79        })?;
80
81        let seed = Seed::from_env_value(&raw).map_err(|message| crate::Error::InvalidSeed {
82            var: var.to_string(),
83            message,
84        })?;
85
86        Ok(Self::deterministic(seed))
87    }
88
89    /// Returns the mode this factory is operating in.
90    pub fn mode(&self) -> &Mode {
91        self.inner.mode()
92    }
93
94    /// Clear the artifact cache.
95    pub fn clear_cache(&self) {
96        self.inner.clear_cache()
97    }
98
99    /// Get a cached artifact by `(domain, label, spec, variant)` or generate one.
100    ///
101    /// The initializer receives the derived seed for this artifact identity.
102    /// Callers that need an RNG should instantiate it privately from that seed.
103    pub fn get_or_init<T, F>(
104        &self,
105        domain: ArtifactDomain,
106        label: &str,
107        spec_bytes: &[u8],
108        variant: &str,
109        init: F,
110    ) -> Arc<T>
111    where
112        T: Any + Send + Sync + 'static,
113        F: FnOnce(Seed) -> T,
114    {
115        self.inner
116            .get_or_init(domain, label, spec_bytes, variant, init)
117    }
118}