1use reqwest::{header, Client, Method};
4use serde_json::Value;
5use std::time::Duration;
6use tracing::{debug, error};
7
8use crate::error::MispError;
9
10#[derive(Clone)]
11pub struct MispClient {
12 base_url: String,
13 api_key: String,
14 http: Client,
15}
16
17impl MispClient {
18 pub fn new(base_url: impl Into<String>, api_key: impl Into<String>, verify_ssl: bool) -> Self {
19 let base_url = base_url.into().trim_end_matches('/').to_string();
20
21 let http = Client::builder()
22 .danger_accept_invalid_certs(!verify_ssl)
23 .timeout(Duration::from_secs(30))
24 .build()
25 .expect("failed to create HTTP client");
26
27 Self {
28 base_url,
29 api_key: api_key.into(),
30 http,
31 }
32 }
33
34 pub fn with_timeout(
35 base_url: impl Into<String>,
36 api_key: impl Into<String>,
37 verify_ssl: bool,
38 timeout: Duration,
39 ) -> Self {
40 let base_url = base_url.into().trim_end_matches('/').to_string();
41
42 let http = Client::builder()
43 .danger_accept_invalid_certs(!verify_ssl)
44 .timeout(timeout)
45 .build()
46 .expect("failed to create HTTP client");
47
48 Self {
49 base_url,
50 api_key: api_key.into(),
51 http,
52 }
53 }
54
55 pub async fn get(&self, endpoint: &str) -> Result<Value, MispError> {
56 self.request(Method::GET, endpoint, None).await
57 }
58
59 pub async fn post(&self, endpoint: &str, body: Option<Value>) -> Result<Value, MispError> {
60 self.request(Method::POST, endpoint, body).await
61 }
62
63 pub async fn delete(&self, endpoint: &str) -> Result<Value, MispError> {
64 self.request(Method::DELETE, endpoint, None).await
65 }
66
67 async fn request(
68 &self,
69 method: Method,
70 endpoint: &str,
71 body: Option<Value>,
72 ) -> Result<Value, MispError> {
73 let url = format!("{}{}", self.base_url, endpoint);
74 debug!(%method, %url, "MISP API request");
75
76 let mut req = self
77 .http
78 .request(method, &url)
79 .header(header::AUTHORIZATION, &self.api_key)
80 .header(header::ACCEPT, "application/json")
81 .header(header::CONTENT_TYPE, "application/json");
82
83 if let Some(json) = body {
84 req = req.json(&json);
85 }
86
87 let resp = req.send().await?;
88 let status = resp.status();
89
90 if status == 401 || status == 403 {
91 return Err(MispError::Authentication(format!(
92 "API key rejected ({})",
93 status
94 )));
95 }
96
97 let text = resp.text().await?;
98 debug!(response_len = text.len(), "MISP API response received");
99
100 if !status.is_success() {
101 error!(%url, %status, "MISP API error");
102 return Err(MispError::Api {
103 status: status.as_u16(),
104 message: text,
105 });
106 }
107
108 serde_json::from_str(&text).map_err(|e| {
109 error!(%url, "Failed to parse MISP response");
110 MispError::Parse(e)
111 })
112 }
113}
114
115impl std::fmt::Debug for MispClient {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("MispClient")
118 .field("base_url", &self.base_url)
119 .field("api_key", &"[redacted]")
120 .finish()
121 }
122}