返回 CodeWhale
context.rs
根目录 / crates / tui / src / credentials / context.rs
1 //! Injectable ambient-environment access for credential resolution.
2 //!
3 //! Ported from pi-mono `packages/ai/src/auth/context.ts` and the `AuthContext`
4 //! interface in `packages/ai/src/auth/types.ts` (MIT, Copyright (c) 2025 Mario
5 //! Zechner — full notice in the parent module).
6 //!
7 //! pi's motivation applies here unchanged: resolution that reads
8 //! `process.env` / `std::env` directly is untestable without mutating the real
9 //! process. Every ambient read the resolver performs itself goes through this
10 //! trait, so a test can state exactly which variables exist. Resolution never
11 //! stats the filesystem (#5772): external credential discovery is
12 //! metadata-only, so the trait deliberately has no file probe.
13
14 #[cfg(test)]
15 use std::collections::BTreeMap;
16
17 /// Ambient environment access for credential resolution.
18 pub(crate) trait AuthContext: Send + Sync {
19 /// Read an environment variable, treating blank values as unset — a blank
20 /// `DEEPSEEK_API_KEY=` is a leftover export, not a credential.
21 fn env(&self, name: &str) -> Option<String>;
22 }
23
24 /// The real process environment.
25 #[derive(Debug, Clone, Copy, Default)]
26 pub(crate) struct ProcessAuthContext;
27
28 impl AuthContext for ProcessAuthContext {
29 fn env(&self, name: &str) -> Option<String> {
30 std::env::var(name)
31 .ok()
32 .filter(|value| !value.trim().is_empty())
33 }
34 }
35
36 /// Test double: a fixed set of variables.
37 #[cfg(test)]
38 #[derive(Debug, Clone, Default)]
39 pub(crate) struct MapAuthContext {
40 env: BTreeMap<String, String>,
41 }
42
43 #[cfg(test)]
44 impl MapAuthContext {
45 pub(crate) fn new() -> Self {
46 Self::default()
47 }
48
49 pub(crate) fn with_env(mut self, name: &str, value: &str) -> Self {
50 self.env.insert(name.to_string(), value.to_string());
51 self
52 }
53 }
54
55 #[cfg(test)]
56 impl AuthContext for MapAuthContext {
57 fn env(&self, name: &str) -> Option<String> {
58 self.env
59 .get(name)
60 .filter(|value| !value.trim().is_empty())
61 .cloned()
62 }
63 }
64
64 lines RUST