switchyard/api/
builder.rs

1use crate::api::{DebugAttestor, DebugLockManager, DebugOwnershipOracle, DebugSmokeTestRunner};
2use crate::constants::DEFAULT_LOCK_TIMEOUT_MS;
3use crate::logging::{AuditSink, FactsEmitter};
4use crate::policy::Policy;
5
6/// Builder for constructing a Switchyard with ergonomic chaining.
7/// Mirrors `Switchyard::new(...).with_*` but avoids duplication at call sites.
8#[derive(Debug)]
9pub struct ApiBuilder<E: FactsEmitter, A: AuditSink> {
10    facts: E,
11    audit: A,
12    policy: Policy,
13    // Optional adapters/handles
14    lock: Option<Box<dyn DebugLockManager>>, // None in dev/test; required in production
15    owner: Option<Box<dyn DebugOwnershipOracle>>, // strict ownership gating
16    attest: Option<Box<dyn DebugAttestor>>,  // final summary attestation
17    smoke: Option<Box<dyn DebugSmokeTestRunner>>, // post-apply health verification
18    lock_timeout_ms: Option<u64>,
19}
20
21impl<E: FactsEmitter, A: AuditSink> ApiBuilder<E, A> {
22    pub fn new(facts: E, audit: A, policy: Policy) -> Self {
23        Self {
24            facts,
25            audit,
26            policy,
27            lock: None,
28            owner: None,
29            attest: None,
30            smoke: None,
31            lock_timeout_ms: None,
32        }
33    }
34
35    /// Build a `Switchyard` with the configured options.
36    ///
37    /// Example
38    /// ```rust
39    /// use switchyard::api::ApiBuilder;
40    /// use switchyard::policy::Policy;
41    /// use switchyard::logging::JsonlSink;
42    ///
43    /// let facts = JsonlSink::default();
44    /// let audit = JsonlSink::default();
45    /// let api = ApiBuilder::new(facts, audit, Policy::default())
46    ///     .with_lock_timeout_ms(500)
47    ///     .build();
48    /// ```
49    pub fn build(self) -> super::Switchyard<E, A> {
50        // Construct directly to avoid recursion when Switchyard::new delegates to ApiBuilder
51        let mut api = super::Switchyard {
52            facts: self.facts,
53            audit: self.audit,
54            policy: self.policy,
55            overrides: super::overrides::Overrides::default(),
56            lock: None,
57            owner: None,
58            attest: None,
59            smoke: None,
60            lock_timeout_ms: self.lock_timeout_ms.unwrap_or(DEFAULT_LOCK_TIMEOUT_MS),
61        };
62        if let Some(lock) = self.lock {
63            api.lock = Some(lock);
64        }
65        if let Some(owner) = self.owner {
66            api.owner = Some(owner);
67        }
68        if let Some(att) = self.attest {
69            api.attest = Some(att);
70        }
71        if let Some(smoke) = self.smoke {
72            api.smoke = Some(smoke);
73        }
74        api
75    }
76
77    #[must_use]
78    pub fn with_lock_manager(mut self, lock: Box<dyn DebugLockManager>) -> Self {
79        self.lock = Some(lock);
80        self
81    }
82
83    #[must_use]
84    pub fn with_ownership_oracle(mut self, owner: Box<dyn DebugOwnershipOracle>) -> Self {
85        self.owner = Some(owner);
86        self
87    }
88
89    #[must_use]
90    pub fn with_attestor(mut self, attest: Box<dyn DebugAttestor>) -> Self {
91        self.attest = Some(attest);
92        self
93    }
94
95    #[must_use]
96    pub fn with_smoke_runner(mut self, smoke: Box<dyn DebugSmokeTestRunner>) -> Self {
97        self.smoke = Some(smoke);
98        self
99    }
100
101    #[must_use]
102    pub const fn with_lock_timeout_ms(mut self, timeout_ms: u64) -> Self {
103        self.lock_timeout_ms = Some(timeout_ms);
104        self
105    }
106}