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}