1use derive_builder::Builder;
2use reqwest::Client as HttpClient;
3use serde::{Deserialize, Serialize, Serializer, ser::SerializeMap};
4use serde_json::Value;
5use urlencoding::encode;
6
7use crate::{
8 error::OpenRouterError,
9 strip_option_vec_setter,
10 transport::{request as transport_request, response as transport_response},
11 types::{ApiResponse, PaginationOptions},
12};
13
14#[derive(Serialize)]
15struct ListObservabilityDestinationsQuery {
16 #[serde(skip_serializing_if = "Option::is_none")]
17 offset: Option<u32>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 limit: Option<u32>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 workspace_id: Option<String>,
22}
23
24#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
26#[builder(build_fn(error = "OpenRouterError"))]
27#[non_exhaustive]
28pub struct ObservabilityFilterRulesConfig {
29 pub groups: Vec<ObservabilityFilterGroup>,
30 #[builder(setter(strip_option), default)]
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub enabled: Option<bool>,
33}
34
35impl ObservabilityFilterRulesConfig {
36 pub fn builder() -> ObservabilityFilterRulesConfigBuilder {
37 ObservabilityFilterRulesConfigBuilder::default()
38 }
39}
40
41#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
43#[builder(build_fn(error = "OpenRouterError"))]
44#[non_exhaustive]
45pub struct ObservabilityFilterGroup {
46 pub rules: Vec<ObservabilityFilterRule>,
47 #[builder(setter(into, strip_option), default)]
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub logic: Option<String>,
50}
51
52impl ObservabilityFilterGroup {
53 pub fn builder() -> ObservabilityFilterGroupBuilder {
54 ObservabilityFilterGroupBuilder::default()
55 }
56}
57
58#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
60#[builder(build_fn(error = "OpenRouterError"))]
61#[non_exhaustive]
62pub struct ObservabilityFilterRule {
63 #[builder(setter(into))]
64 pub field: String,
65 #[builder(setter(into))]
66 pub operator: String,
67 #[builder(setter(strip_option), default)]
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub value: Option<Value>,
70}
71
72impl ObservabilityFilterRule {
73 pub fn builder() -> ObservabilityFilterRuleBuilder {
74 ObservabilityFilterRuleBuilder::default()
75 }
76}
77
78#[derive(Serialize, Deserialize, Debug, Clone)]
80#[non_exhaustive]
81pub struct ObservabilityDestination {
82 pub id: String,
83 pub workspace_id: String,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub name: Option<String>,
86 pub enabled: bool,
87 pub privacy_mode: bool,
88 pub sampling_rate: f64,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub api_key_hashes: Option<Vec<String>>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub filter_rules: Option<ObservabilityFilterRulesConfig>,
93 pub created_at: String,
94 pub updated_at: String,
95 #[serde(rename = "type")]
96 pub destination_type: String,
97 pub config: Value,
98}
99
100#[derive(Serialize, Deserialize, Debug, Clone)]
102#[non_exhaustive]
103pub struct ObservabilityDestinationListResponse {
104 pub data: Vec<ObservabilityDestination>,
105 pub total_count: u64,
106}
107
108#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
110#[builder(build_fn(error = "OpenRouterError"))]
111#[non_exhaustive]
112pub struct CreateObservabilityDestinationRequest {
113 #[serde(rename = "type")]
114 #[builder(setter(into))]
115 pub destination_type: String,
116 #[builder(setter(into))]
117 pub name: String,
118 pub config: Value,
119 #[builder(setter(into, strip_option), default)]
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub workspace_id: Option<String>,
122 #[builder(setter(custom), default)]
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub api_key_hashes: Option<Vec<String>>,
125 #[builder(setter(strip_option), default)]
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub enabled: Option<bool>,
128 #[builder(setter(strip_option), default)]
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub privacy_mode: Option<bool>,
131 #[builder(setter(strip_option), default)]
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub sampling_rate: Option<f64>,
134 #[builder(setter(strip_option), default)]
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub filter_rules: Option<ObservabilityFilterRulesConfig>,
137}
138
139impl CreateObservabilityDestinationRequest {
140 pub fn builder() -> CreateObservabilityDestinationRequestBuilder {
141 CreateObservabilityDestinationRequestBuilder::default()
142 }
143}
144
145impl CreateObservabilityDestinationRequestBuilder {
146 strip_option_vec_setter!(api_key_hashes, String);
147}
148
149#[derive(Deserialize, Debug, Clone, Builder)]
151#[builder(build_fn(error = "OpenRouterError"))]
152#[non_exhaustive]
153pub struct UpdateObservabilityDestinationRequest {
154 #[builder(setter(into, strip_option), default)]
155 pub name: Option<String>,
156 #[builder(setter(strip_option), default)]
157 pub config: Option<Value>,
158 #[builder(setter(custom), default)]
159 pub api_key_hashes: Option<Vec<String>>,
160 #[serde(skip)]
161 #[builder(setter(custom), default)]
162 clear_api_key_hashes: bool,
163 #[builder(setter(strip_option), default)]
164 pub enabled: Option<bool>,
165 #[builder(setter(strip_option), default)]
166 pub privacy_mode: Option<bool>,
167 #[builder(setter(strip_option), default)]
168 pub sampling_rate: Option<f64>,
169 #[builder(setter(custom), default)]
170 pub filter_rules: Option<ObservabilityFilterRulesConfig>,
171 #[serde(skip)]
172 #[builder(setter(custom), default)]
173 clear_filter_rules: bool,
174}
175
176impl Serialize for UpdateObservabilityDestinationRequest {
177 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
178 where
179 S: Serializer,
180 {
181 let mut map = serializer.serialize_map(None)?;
182 if let Some(value) = &self.name {
183 map.serialize_entry("name", value)?;
184 }
185 if let Some(value) = &self.config {
186 map.serialize_entry("config", value)?;
187 }
188 if self.clear_api_key_hashes {
189 map.serialize_entry("api_key_hashes", &Option::<Vec<String>>::None)?;
190 } else if let Some(value) = &self.api_key_hashes {
191 map.serialize_entry("api_key_hashes", value)?;
192 }
193 if let Some(value) = &self.enabled {
194 map.serialize_entry("enabled", value)?;
195 }
196 if let Some(value) = &self.privacy_mode {
197 map.serialize_entry("privacy_mode", value)?;
198 }
199 if let Some(value) = &self.sampling_rate {
200 map.serialize_entry("sampling_rate", value)?;
201 }
202 if self.clear_filter_rules {
203 map.serialize_entry(
204 "filter_rules",
205 &Option::<ObservabilityFilterRulesConfig>::None,
206 )?;
207 } else if let Some(value) = &self.filter_rules {
208 map.serialize_entry("filter_rules", value)?;
209 }
210 map.end()
211 }
212}
213
214impl UpdateObservabilityDestinationRequest {
215 pub fn builder() -> UpdateObservabilityDestinationRequestBuilder {
216 UpdateObservabilityDestinationRequestBuilder::default()
217 }
218}
219
220impl UpdateObservabilityDestinationRequestBuilder {
221 pub fn api_key_hashes<T, S>(&mut self, items: T) -> &mut Self
222 where
223 T: IntoIterator<Item = S>,
224 S: Into<String>,
225 {
226 self.api_key_hashes = Some(Some(items.into_iter().map(Into::into).collect()));
227 self.clear_api_key_hashes = Some(false);
228 self
229 }
230
231 pub fn clear_api_key_hashes(&mut self) -> &mut Self {
232 self.api_key_hashes = Some(None);
233 self.clear_api_key_hashes = Some(true);
234 self
235 }
236
237 pub fn filter_rules(&mut self, value: ObservabilityFilterRulesConfig) -> &mut Self {
238 self.filter_rules = Some(Some(value));
239 self.clear_filter_rules = Some(false);
240 self
241 }
242
243 pub fn clear_filter_rules(&mut self) -> &mut Self {
244 self.filter_rules = Some(None);
245 self.clear_filter_rules = Some(true);
246 self
247 }
248}
249
250#[derive(Serialize, Deserialize, Debug, Clone)]
251struct DeleteObservabilityDestinationResponse {
252 deleted: bool,
253}
254
255pub async fn list_observability_destinations(
257 base_url: &str,
258 management_key: &str,
259 pagination: Option<PaginationOptions>,
260 workspace_id: Option<&str>,
261) -> Result<ObservabilityDestinationListResponse, OpenRouterError> {
262 let http_client = crate::transport::new_client()?;
263 list_observability_destinations_with_client(
264 &http_client,
265 base_url,
266 management_key,
267 pagination,
268 workspace_id,
269 )
270 .await
271}
272
273pub(crate) async fn list_observability_destinations_with_client(
274 http_client: &HttpClient,
275 base_url: &str,
276 management_key: &str,
277 pagination: Option<PaginationOptions>,
278 workspace_id: Option<&str>,
279) -> Result<ObservabilityDestinationListResponse, OpenRouterError> {
280 let url = format!("{base_url}/observability/destinations");
281 let query = ListObservabilityDestinationsQuery {
282 offset: pagination.and_then(|p| p.offset),
283 limit: pagination.and_then(|p| p.limit),
284 workspace_id: workspace_id.map(ToOwned::to_owned),
285 };
286 let req = transport_request::with_bearer_auth(
287 transport_request::get(http_client, &url),
288 management_key,
289 );
290 let response =
291 if query.offset.is_none() && query.limit.is_none() && query.workspace_id.is_none() {
292 req.send().await?
293 } else {
294 req.query(&query).send().await?
295 };
296
297 if response.status().is_success() {
298 transport_response::parse_json_response(response, "observability destination list").await
299 } else {
300 transport_response::handle_error(response).await?;
301 unreachable!()
302 }
303}
304
305pub async fn create_observability_destination(
307 base_url: &str,
308 management_key: &str,
309 request: &CreateObservabilityDestinationRequest,
310) -> Result<ObservabilityDestination, OpenRouterError> {
311 let http_client = crate::transport::new_client()?;
312 create_observability_destination_with_client(&http_client, base_url, management_key, request)
313 .await
314}
315
316pub(crate) async fn create_observability_destination_with_client(
317 http_client: &HttpClient,
318 base_url: &str,
319 management_key: &str,
320 request: &CreateObservabilityDestinationRequest,
321) -> Result<ObservabilityDestination, OpenRouterError> {
322 let url = format!("{base_url}/observability/destinations");
323 let response = transport_request::with_bearer_auth(
324 transport_request::post(http_client, &url),
325 management_key,
326 )
327 .json(request)
328 .send()
329 .await?;
330
331 if response.status().is_success() {
332 let payload: ApiResponse<ObservabilityDestination> =
333 transport_response::parse_json_response(response, "observability destination creation")
334 .await?;
335 Ok(payload.data)
336 } else {
337 transport_response::handle_error(response).await?;
338 unreachable!()
339 }
340}
341
342pub async fn get_observability_destination(
344 base_url: &str,
345 management_key: &str,
346 id: &str,
347) -> Result<ObservabilityDestination, OpenRouterError> {
348 let http_client = crate::transport::new_client()?;
349 get_observability_destination_with_client(&http_client, base_url, management_key, id).await
350}
351
352pub(crate) async fn get_observability_destination_with_client(
353 http_client: &HttpClient,
354 base_url: &str,
355 management_key: &str,
356 id: &str,
357) -> Result<ObservabilityDestination, OpenRouterError> {
358 let url = format!("{base_url}/observability/destinations/{}", encode(id));
359 let response = transport_request::with_bearer_auth(
360 transport_request::get(http_client, &url),
361 management_key,
362 )
363 .send()
364 .await?;
365
366 if response.status().is_success() {
367 let payload: ApiResponse<ObservabilityDestination> =
368 transport_response::parse_json_response(response, "observability destination lookup")
369 .await?;
370 Ok(payload.data)
371 } else {
372 transport_response::handle_error(response).await?;
373 unreachable!()
374 }
375}
376
377pub async fn update_observability_destination(
379 base_url: &str,
380 management_key: &str,
381 id: &str,
382 request: &UpdateObservabilityDestinationRequest,
383) -> Result<ObservabilityDestination, OpenRouterError> {
384 let http_client = crate::transport::new_client()?;
385 update_observability_destination_with_client(
386 &http_client,
387 base_url,
388 management_key,
389 id,
390 request,
391 )
392 .await
393}
394
395pub(crate) async fn update_observability_destination_with_client(
396 http_client: &HttpClient,
397 base_url: &str,
398 management_key: &str,
399 id: &str,
400 request: &UpdateObservabilityDestinationRequest,
401) -> Result<ObservabilityDestination, OpenRouterError> {
402 let url = format!("{base_url}/observability/destinations/{}", encode(id));
403 let response = transport_request::with_bearer_auth(
404 transport_request::patch(http_client, &url),
405 management_key,
406 )
407 .json(request)
408 .send()
409 .await?;
410
411 if response.status().is_success() {
412 let payload: ApiResponse<ObservabilityDestination> =
413 transport_response::parse_json_response(response, "observability destination update")
414 .await?;
415 Ok(payload.data)
416 } else {
417 transport_response::handle_error(response).await?;
418 unreachable!()
419 }
420}
421
422pub async fn delete_observability_destination(
424 base_url: &str,
425 management_key: &str,
426 id: &str,
427) -> Result<bool, OpenRouterError> {
428 let http_client = crate::transport::new_client()?;
429 delete_observability_destination_with_client(&http_client, base_url, management_key, id).await
430}
431
432pub(crate) async fn delete_observability_destination_with_client(
433 http_client: &HttpClient,
434 base_url: &str,
435 management_key: &str,
436 id: &str,
437) -> Result<bool, OpenRouterError> {
438 let url = format!("{base_url}/observability/destinations/{}", encode(id));
439 let response = transport_request::with_bearer_auth(
440 transport_request::delete(http_client, &url),
441 management_key,
442 )
443 .send()
444 .await?;
445
446 if response.status().is_success() {
447 let payload: DeleteObservabilityDestinationResponse =
448 transport_response::parse_json_response(response, "observability destination deletion")
449 .await?;
450 Ok(payload.deleted)
451 } else {
452 transport_response::handle_error(response).await?;
453 unreachable!()
454 }
455}