misp_client/
client_factory.rs

1//! Factory for creating MISP API clients with shared configuration.
2
3use std::time::Duration;
4use tracing::debug;
5
6use crate::attributes::AttributesClient;
7use crate::client::MispClient;
8use crate::error::MispError;
9use crate::events::EventsClient;
10use crate::galaxies::GalaxiesClient;
11use crate::sightings::SightingsClient;
12use crate::warninglists::WarninglistsClient;
13
14#[derive(Clone)]
15pub struct MispClientFactory {
16    base_url: String,
17    api_key: String,
18    verify_ssl: bool,
19    timeout: Duration,
20}
21
22impl MispClientFactory {
23    pub fn builder() -> MispClientFactoryBuilder {
24        MispClientFactoryBuilder::default()
25    }
26
27    fn create_client(&self) -> MispClient {
28        MispClient::with_timeout(&self.base_url, &self.api_key, self.verify_ssl, self.timeout)
29    }
30
31    pub fn events(&self) -> EventsClient {
32        debug!("Creating EventsClient");
33        EventsClient::new(self.create_client())
34    }
35
36    pub fn attributes(&self) -> AttributesClient {
37        debug!("Creating AttributesClient");
38        AttributesClient::new(self.create_client())
39    }
40
41    pub fn galaxies(&self) -> GalaxiesClient {
42        debug!("Creating GalaxiesClient");
43        GalaxiesClient::new(self.create_client())
44    }
45
46    pub fn sightings(&self) -> SightingsClient {
47        debug!("Creating SightingsClient");
48        SightingsClient::new(self.create_client())
49    }
50
51    pub fn warninglists(&self) -> WarninglistsClient {
52        debug!("Creating WarninglistsClient");
53        WarninglistsClient::new(self.create_client())
54    }
55
56    pub fn all(&self) -> MispClients {
57        debug!("Creating all MISP clients");
58        MispClients {
59            events: self.events(),
60            attributes: self.attributes(),
61            galaxies: self.galaxies(),
62            sightings: self.sightings(),
63            warninglists: self.warninglists(),
64        }
65    }
66
67    pub async fn test_connection(&self) -> Result<ServerInfo, MispError> {
68        debug!("Testing MISP connection");
69        let client = self.create_client();
70        let resp = client.get("/servers/getVersion").await?;
71
72        let version = resp
73            .get("version")
74            .and_then(|v| v.as_str())
75            .unwrap_or("unknown")
76            .to_string();
77
78        let perm_sync = resp
79            .get("perm_sync")
80            .and_then(|v| v.as_bool())
81            .unwrap_or(false);
82
83        let perm_sighting = resp
84            .get("perm_sighting")
85            .and_then(|v| v.as_bool())
86            .unwrap_or(false);
87
88        Ok(ServerInfo {
89            version,
90            perm_sync,
91            perm_sighting,
92        })
93    }
94}
95
96impl std::fmt::Debug for MispClientFactory {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        f.debug_struct("MispClientFactory")
99            .field("base_url", &self.base_url)
100            .field("verify_ssl", &self.verify_ssl)
101            .field("timeout", &self.timeout)
102            .finish()
103    }
104}
105
106#[derive(Default)]
107pub struct MispClientFactoryBuilder {
108    base_url: Option<String>,
109    api_key: Option<String>,
110    verify_ssl: Option<bool>,
111    timeout: Option<Duration>,
112}
113
114impl MispClientFactoryBuilder {
115    pub fn base_url(mut self, url: impl Into<String>) -> Self {
116        self.base_url = Some(url.into());
117        self
118    }
119
120    pub fn api_key(mut self, key: impl Into<String>) -> Self {
121        self.api_key = Some(key.into());
122        self
123    }
124
125    pub fn verify_ssl(mut self, verify: bool) -> Self {
126        self.verify_ssl = Some(verify);
127        self
128    }
129
130    pub fn timeout(mut self, timeout: Duration) -> Self {
131        self.timeout = Some(timeout);
132        self
133    }
134
135    pub fn build(self) -> MispClientFactory {
136        MispClientFactory {
137            base_url: self.base_url.expect("base_url is required"),
138            api_key: self.api_key.expect("api_key is required"),
139            verify_ssl: self.verify_ssl.unwrap_or(true),
140            timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
141        }
142    }
143}
144
145#[derive(Debug)]
146pub struct MispClients {
147    pub events: EventsClient,
148    pub attributes: AttributesClient,
149    pub galaxies: GalaxiesClient,
150    pub sightings: SightingsClient,
151    pub warninglists: WarninglistsClient,
152}
153
154#[derive(Debug, Clone)]
155pub struct ServerInfo {
156    pub version: String,
157    pub perm_sync: bool,
158    pub perm_sighting: bool,
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn builder_creates_factory() {
167        let factory = MispClientFactory::builder()
168            .base_url("https://misp.local")
169            .api_key("test-key")
170            .verify_ssl(false)
171            .build();
172
173        let _events = factory.events();
174        let _attributes = factory.attributes();
175        let _galaxies = factory.galaxies();
176    }
177
178    #[test]
179    #[should_panic(expected = "base_url is required")]
180    fn builder_requires_base_url() {
181        MispClientFactory::builder().api_key("test-key").build();
182    }
183
184    #[test]
185    #[should_panic(expected = "api_key is required")]
186    fn builder_requires_api_key() {
187        MispClientFactory::builder()
188            .base_url("https://misp.local")
189            .build();
190    }
191}