Skip to main content

openrouter_rs/api/
guardrails.rs

1use derive_builder::Builder;
2use reqwest::Client as HttpClient;
3use serde::{Deserialize, Serialize, Serializer, ser::SerializeMap};
4use urlencoding::encode;
5
6use crate::{
7    error::OpenRouterError,
8    strip_option_vec_setter,
9    transport::{request as transport_request, response as transport_response},
10    types::{ApiResponse, PaginationOptions},
11};
12
13/// Action for a custom regex content filter.
14#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
15#[non_exhaustive]
16#[serde(rename_all = "lowercase")]
17pub enum ContentFilterAction {
18    Redact,
19    Block,
20}
21
22/// Action for a built-in content filter.
23#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
24#[non_exhaustive]
25#[serde(rename_all = "lowercase")]
26pub enum ContentFilterBuiltinAction {
27    Redact,
28    Block,
29    Flag,
30}
31
32/// Built-in content filter identifier.
33#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
34#[non_exhaustive]
35#[serde(rename_all = "kebab-case")]
36pub enum ContentFilterBuiltinSlug {
37    Email,
38    Phone,
39    Ssn,
40    CreditCard,
41    IpAddress,
42    PersonName,
43    Address,
44    RegexPromptInjection,
45}
46
47/// Built-in content filter entry used by guardrail create/update requests and responses.
48#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
49#[non_exhaustive]
50pub struct ContentFilterBuiltinEntry {
51    pub slug: ContentFilterBuiltinSlug,
52    pub action: ContentFilterBuiltinAction,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub label: Option<String>,
55}
56
57impl ContentFilterBuiltinEntry {
58    pub fn new(slug: ContentFilterBuiltinSlug, action: ContentFilterBuiltinAction) -> Self {
59        Self {
60            slug,
61            action,
62            label: None,
63        }
64    }
65
66    pub fn label(mut self, label: impl Into<String>) -> Self {
67        self.label = Some(label.into());
68        self
69    }
70}
71
72/// Custom regex content filter entry used by guardrail create/update requests and responses.
73#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
74#[non_exhaustive]
75pub struct ContentFilterEntry {
76    pub pattern: String,
77    pub action: ContentFilterAction,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub label: Option<String>,
80}
81
82impl ContentFilterEntry {
83    pub fn new(pattern: impl Into<String>, action: ContentFilterAction) -> Self {
84        Self {
85            pattern: pattern.into(),
86            action,
87            label: None,
88        }
89    }
90
91    pub fn label(mut self, label: impl Into<String>) -> Self {
92        self.label = Some(label.into());
93        self
94    }
95}
96
97/// Guardrail model.
98#[derive(Serialize, Deserialize, Debug, Clone)]
99#[non_exhaustive]
100pub struct Guardrail {
101    pub id: String,
102    pub name: String,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub description: Option<String>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub limit_usd: Option<f64>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub reset_interval: Option<String>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub allowed_providers: Option<Vec<String>>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub allowed_models: Option<Vec<String>>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub content_filter_builtins: Option<Vec<ContentFilterBuiltinEntry>>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub content_filters: Option<Vec<ContentFilterEntry>>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub enforce_zdr: Option<bool>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub enforce_zdr_anthropic: Option<bool>,
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub enforce_zdr_openai: Option<bool>,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub enforce_zdr_google: Option<bool>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub enforce_zdr_other: Option<bool>,
127    pub created_at: String,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub updated_at: Option<String>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub workspace_id: Option<String>,
132}
133
134/// Paginated guardrails list response.
135#[derive(Serialize, Deserialize, Debug, Clone)]
136#[non_exhaustive]
137pub struct GuardrailListResponse {
138    pub data: Vec<Guardrail>,
139    pub total_count: f64,
140}
141
142/// Request payload for creating a guardrail (`POST /guardrails`).
143#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
144#[builder(build_fn(error = "OpenRouterError"))]
145#[non_exhaustive]
146pub struct CreateGuardrailRequest {
147    #[builder(setter(into))]
148    name: String,
149    #[builder(setter(into, strip_option), default)]
150    #[serde(skip_serializing_if = "Option::is_none")]
151    description: Option<String>,
152    #[builder(setter(strip_option), default)]
153    #[serde(skip_serializing_if = "Option::is_none")]
154    limit_usd: Option<f64>,
155    #[builder(setter(into, strip_option), default)]
156    #[serde(skip_serializing_if = "Option::is_none")]
157    reset_interval: Option<String>,
158    #[builder(setter(custom), default)]
159    #[serde(skip_serializing_if = "Option::is_none")]
160    allowed_providers: Option<Vec<String>>,
161    #[builder(setter(custom), default)]
162    #[serde(skip_serializing_if = "Option::is_none")]
163    allowed_models: Option<Vec<String>>,
164    #[builder(setter(custom), default)]
165    #[serde(skip_serializing_if = "Option::is_none")]
166    content_filter_builtins: Option<Vec<ContentFilterBuiltinEntry>>,
167    #[builder(setter(custom), default)]
168    #[serde(skip_serializing_if = "Option::is_none")]
169    content_filters: Option<Vec<ContentFilterEntry>>,
170    #[builder(setter(strip_option), default)]
171    #[serde(skip_serializing_if = "Option::is_none")]
172    enforce_zdr: Option<bool>,
173    #[builder(setter(strip_option), default)]
174    #[serde(skip_serializing_if = "Option::is_none")]
175    enforce_zdr_anthropic: Option<bool>,
176    #[builder(setter(strip_option), default)]
177    #[serde(skip_serializing_if = "Option::is_none")]
178    enforce_zdr_openai: Option<bool>,
179    #[builder(setter(strip_option), default)]
180    #[serde(skip_serializing_if = "Option::is_none")]
181    enforce_zdr_google: Option<bool>,
182    #[builder(setter(strip_option), default)]
183    #[serde(skip_serializing_if = "Option::is_none")]
184    enforce_zdr_other: Option<bool>,
185    #[builder(setter(into, strip_option), default)]
186    #[serde(skip_serializing_if = "Option::is_none")]
187    workspace_id: Option<String>,
188}
189
190impl CreateGuardrailRequestBuilder {
191    strip_option_vec_setter!(allowed_providers, String);
192    strip_option_vec_setter!(allowed_models, String);
193    strip_option_vec_setter!(content_filter_builtins, ContentFilterBuiltinEntry);
194    strip_option_vec_setter!(content_filters, ContentFilterEntry);
195}
196
197impl CreateGuardrailRequest {
198    pub fn builder() -> CreateGuardrailRequestBuilder {
199        CreateGuardrailRequestBuilder::default()
200    }
201}
202
203/// Request payload for updating a guardrail (`PATCH /guardrails/{id}`).
204#[derive(Deserialize, Debug, Clone, Builder)]
205#[builder(build_fn(error = "OpenRouterError"))]
206#[non_exhaustive]
207pub struct UpdateGuardrailRequest {
208    #[builder(setter(into, strip_option), default)]
209    name: Option<String>,
210    #[builder(setter(into, strip_option), default)]
211    description: Option<String>,
212    #[builder(setter(strip_option), default)]
213    limit_usd: Option<f64>,
214    #[builder(setter(into, strip_option), default)]
215    reset_interval: Option<String>,
216    #[builder(setter(custom), default)]
217    allowed_providers: Option<Vec<String>>,
218    #[serde(skip)]
219    #[builder(setter(custom), default)]
220    clear_allowed_providers: bool,
221    #[builder(setter(custom), default)]
222    allowed_models: Option<Vec<String>>,
223    #[serde(skip)]
224    #[builder(setter(custom), default)]
225    clear_allowed_models: bool,
226    #[builder(setter(custom), default)]
227    content_filter_builtins: Option<Vec<ContentFilterBuiltinEntry>>,
228    #[serde(skip)]
229    #[builder(setter(custom), default)]
230    clear_content_filter_builtins: bool,
231    #[builder(setter(custom), default)]
232    content_filters: Option<Vec<ContentFilterEntry>>,
233    #[serde(skip)]
234    #[builder(setter(custom), default)]
235    clear_content_filters: bool,
236    #[builder(setter(strip_option), default)]
237    enforce_zdr: Option<bool>,
238    #[builder(setter(strip_option), default)]
239    enforce_zdr_anthropic: Option<bool>,
240    #[builder(setter(strip_option), default)]
241    enforce_zdr_openai: Option<bool>,
242    #[builder(setter(strip_option), default)]
243    enforce_zdr_google: Option<bool>,
244    #[builder(setter(strip_option), default)]
245    enforce_zdr_other: Option<bool>,
246}
247
248impl Serialize for UpdateGuardrailRequest {
249    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
250    where
251        S: Serializer,
252    {
253        let mut map = serializer.serialize_map(None)?;
254        if let Some(value) = &self.name {
255            map.serialize_entry("name", value)?;
256        }
257        if let Some(value) = &self.description {
258            map.serialize_entry("description", value)?;
259        }
260        if let Some(value) = &self.limit_usd {
261            map.serialize_entry("limit_usd", value)?;
262        }
263        if let Some(value) = &self.reset_interval {
264            map.serialize_entry("reset_interval", value)?;
265        }
266        if self.clear_allowed_providers {
267            map.serialize_entry("allowed_providers", &Option::<Vec<String>>::None)?;
268        } else if let Some(value) = &self.allowed_providers {
269            map.serialize_entry("allowed_providers", value)?;
270        }
271        if self.clear_allowed_models {
272            map.serialize_entry("allowed_models", &Option::<Vec<String>>::None)?;
273        } else if let Some(value) = &self.allowed_models {
274            map.serialize_entry("allowed_models", value)?;
275        }
276        if self.clear_content_filter_builtins {
277            map.serialize_entry(
278                "content_filter_builtins",
279                &Option::<Vec<ContentFilterBuiltinEntry>>::None,
280            )?;
281        } else if let Some(value) = &self.content_filter_builtins {
282            map.serialize_entry("content_filter_builtins", value)?;
283        }
284        if self.clear_content_filters {
285            map.serialize_entry("content_filters", &Option::<Vec<ContentFilterEntry>>::None)?;
286        } else if let Some(value) = &self.content_filters {
287            map.serialize_entry("content_filters", value)?;
288        }
289        if let Some(value) = &self.enforce_zdr {
290            map.serialize_entry("enforce_zdr", value)?;
291        }
292        if let Some(value) = &self.enforce_zdr_anthropic {
293            map.serialize_entry("enforce_zdr_anthropic", value)?;
294        }
295        if let Some(value) = &self.enforce_zdr_openai {
296            map.serialize_entry("enforce_zdr_openai", value)?;
297        }
298        if let Some(value) = &self.enforce_zdr_google {
299            map.serialize_entry("enforce_zdr_google", value)?;
300        }
301        if let Some(value) = &self.enforce_zdr_other {
302            map.serialize_entry("enforce_zdr_other", value)?;
303        }
304        map.end()
305    }
306}
307
308impl UpdateGuardrailRequestBuilder {
309    pub fn allowed_providers<T, S>(&mut self, items: T) -> &mut Self
310    where
311        T: IntoIterator<Item = S>,
312        S: Into<String>,
313    {
314        self.allowed_providers = Some(Some(items.into_iter().map(Into::into).collect()));
315        self.clear_allowed_providers = Some(false);
316        self
317    }
318
319    pub fn allowed_models<T, S>(&mut self, items: T) -> &mut Self
320    where
321        T: IntoIterator<Item = S>,
322        S: Into<String>,
323    {
324        self.allowed_models = Some(Some(items.into_iter().map(Into::into).collect()));
325        self.clear_allowed_models = Some(false);
326        self
327    }
328
329    pub fn clear_allowed_providers(&mut self) -> &mut Self {
330        self.allowed_providers = Some(None);
331        self.clear_allowed_providers = Some(true);
332        self
333    }
334
335    pub fn clear_allowed_models(&mut self) -> &mut Self {
336        self.allowed_models = Some(None);
337        self.clear_allowed_models = Some(true);
338        self
339    }
340
341    pub fn content_filter_builtins<T, S>(&mut self, items: T) -> &mut Self
342    where
343        T: IntoIterator<Item = S>,
344        S: Into<ContentFilterBuiltinEntry>,
345    {
346        self.content_filter_builtins = Some(Some(items.into_iter().map(Into::into).collect()));
347        self.clear_content_filter_builtins = Some(false);
348        self
349    }
350
351    pub fn content_filters<T, S>(&mut self, items: T) -> &mut Self
352    where
353        T: IntoIterator<Item = S>,
354        S: Into<ContentFilterEntry>,
355    {
356        self.content_filters = Some(Some(items.into_iter().map(Into::into).collect()));
357        self.clear_content_filters = Some(false);
358        self
359    }
360
361    pub fn clear_content_filter_builtins(&mut self) -> &mut Self {
362        self.content_filter_builtins = Some(None);
363        self.clear_content_filter_builtins = Some(true);
364        self
365    }
366
367    pub fn clear_content_filters(&mut self) -> &mut Self {
368        self.content_filters = Some(None);
369        self.clear_content_filters = Some(true);
370        self
371    }
372}
373
374impl UpdateGuardrailRequest {
375    pub fn builder() -> UpdateGuardrailRequestBuilder {
376        UpdateGuardrailRequestBuilder::default()
377    }
378}
379
380#[derive(Serialize, Deserialize, Debug, Clone)]
381struct DeleteGuardrailResponse {
382    deleted: bool,
383}
384
385/// Key assignment model.
386#[derive(Serialize, Deserialize, Debug, Clone)]
387#[non_exhaustive]
388pub struct GuardrailKeyAssignment {
389    pub id: String,
390    pub key_hash: String,
391    pub guardrail_id: String,
392    pub key_name: String,
393    pub key_label: String,
394    pub assigned_by: String,
395    pub created_at: String,
396}
397
398/// Member assignment model.
399#[derive(Serialize, Deserialize, Debug, Clone)]
400#[non_exhaustive]
401pub struct GuardrailMemberAssignment {
402    pub id: String,
403    pub user_id: String,
404    pub organization_id: String,
405    pub guardrail_id: String,
406    pub assigned_by: String,
407    pub created_at: String,
408}
409
410/// Paginated key assignment list response.
411#[derive(Serialize, Deserialize, Debug, Clone)]
412#[non_exhaustive]
413pub struct GuardrailKeyAssignmentsResponse {
414    pub data: Vec<GuardrailKeyAssignment>,
415    pub total_count: f64,
416}
417
418/// Paginated member assignment list response.
419#[derive(Serialize, Deserialize, Debug, Clone)]
420#[non_exhaustive]
421pub struct GuardrailMemberAssignmentsResponse {
422    pub data: Vec<GuardrailMemberAssignment>,
423    pub total_count: f64,
424}
425
426/// Request payload for key bulk assignment endpoints.
427#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
428#[builder(build_fn(error = "OpenRouterError"))]
429#[non_exhaustive]
430pub struct BulkKeyAssignmentRequest {
431    key_hashes: Vec<String>,
432}
433
434impl BulkKeyAssignmentRequest {
435    pub fn builder() -> BulkKeyAssignmentRequestBuilder {
436        BulkKeyAssignmentRequestBuilder::default()
437    }
438}
439
440/// Request payload for member bulk assignment endpoints.
441#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
442#[builder(build_fn(error = "OpenRouterError"))]
443#[non_exhaustive]
444pub struct BulkMemberAssignmentRequest {
445    member_user_ids: Vec<String>,
446}
447
448impl BulkMemberAssignmentRequest {
449    pub fn builder() -> BulkMemberAssignmentRequestBuilder {
450        BulkMemberAssignmentRequestBuilder::default()
451    }
452}
453
454/// Response payload for assignment endpoints.
455#[derive(Serialize, Deserialize, Debug, Clone)]
456#[non_exhaustive]
457pub struct AssignedCountResponse {
458    pub assigned_count: f64,
459}
460
461/// Response payload for unassignment endpoints.
462#[derive(Serialize, Deserialize, Debug, Clone)]
463#[non_exhaustive]
464pub struct UnassignedCountResponse {
465    pub unassigned_count: f64,
466}
467
468#[derive(Serialize)]
469struct ListGuardrailsQuery {
470    #[serde(skip_serializing_if = "Option::is_none")]
471    offset: Option<u32>,
472    #[serde(skip_serializing_if = "Option::is_none")]
473    limit: Option<u32>,
474    #[serde(skip_serializing_if = "Option::is_none")]
475    workspace_id: Option<String>,
476}
477
478fn with_pagination(url: String, pagination: Option<PaginationOptions>) -> String {
479    let params = pagination
480        .map(PaginationOptions::to_query_pairs)
481        .unwrap_or_default()
482        .into_iter()
483        .map(|(key, value)| format!("{key}={value}"))
484        .collect::<Vec<_>>();
485
486    if params.is_empty() {
487        url
488    } else {
489        format!("{url}?{}", params.join("&"))
490    }
491}
492
493pub async fn list_guardrails(
494    base_url: &str,
495    management_key: &str,
496    pagination: Option<PaginationOptions>,
497) -> Result<GuardrailListResponse, OpenRouterError> {
498    let http_client = crate::transport::new_client()?;
499    list_guardrails_in_workspace_with_client(
500        &http_client,
501        base_url,
502        management_key,
503        pagination,
504        None,
505    )
506    .await
507}
508
509pub(crate) async fn list_guardrails_with_client(
510    http_client: &HttpClient,
511    base_url: &str,
512    management_key: &str,
513    pagination: Option<PaginationOptions>,
514) -> Result<GuardrailListResponse, OpenRouterError> {
515    list_guardrails_in_workspace_with_client(
516        http_client,
517        base_url,
518        management_key,
519        pagination,
520        None,
521    )
522    .await
523}
524
525pub async fn list_guardrails_in_workspace(
526    base_url: &str,
527    management_key: &str,
528    pagination: Option<PaginationOptions>,
529    workspace_id: Option<&str>,
530) -> Result<GuardrailListResponse, OpenRouterError> {
531    let http_client = crate::transport::new_client()?;
532    list_guardrails_in_workspace_with_client(
533        &http_client,
534        base_url,
535        management_key,
536        pagination,
537        workspace_id,
538    )
539    .await
540}
541
542pub(crate) async fn list_guardrails_in_workspace_with_client(
543    http_client: &HttpClient,
544    base_url: &str,
545    management_key: &str,
546    pagination: Option<PaginationOptions>,
547    workspace_id: Option<&str>,
548) -> Result<GuardrailListResponse, OpenRouterError> {
549    let url = format!("{base_url}/guardrails");
550    let query = ListGuardrailsQuery {
551        offset: pagination.and_then(|p| p.offset),
552        limit: pagination.and_then(|p| p.limit),
553        workspace_id: workspace_id.map(ToOwned::to_owned),
554    };
555    let req = transport_request::with_bearer_auth(
556        transport_request::get(http_client, &url),
557        management_key,
558    );
559    let response =
560        if query.offset.is_none() && query.limit.is_none() && query.workspace_id.is_none() {
561            req.send().await?
562        } else {
563            req.query(&query).send().await?
564        };
565
566    if response.status().is_success() {
567        transport_response::parse_json_response(response, "guardrail list").await
568    } else {
569        transport_response::handle_error(response).await?;
570        unreachable!()
571    }
572}
573
574pub async fn create_guardrail(
575    base_url: &str,
576    management_key: &str,
577    request: &CreateGuardrailRequest,
578) -> Result<Guardrail, OpenRouterError> {
579    let http_client = crate::transport::new_client()?;
580    create_guardrail_with_client(&http_client, base_url, management_key, request).await
581}
582
583pub(crate) async fn create_guardrail_with_client(
584    http_client: &HttpClient,
585    base_url: &str,
586    management_key: &str,
587    request: &CreateGuardrailRequest,
588) -> Result<Guardrail, OpenRouterError> {
589    let url = format!("{base_url}/guardrails");
590    let response = transport_request::with_bearer_auth(
591        transport_request::post(http_client, &url),
592        management_key,
593    )
594    .json(request)
595    .send()
596    .await?;
597
598    if response.status().is_success() {
599        let payload: ApiResponse<Guardrail> =
600            transport_response::parse_json_response(response, "guardrail creation").await?;
601        Ok(payload.data)
602    } else {
603        transport_response::handle_error(response).await?;
604        unreachable!()
605    }
606}
607
608pub async fn get_guardrail(
609    base_url: &str,
610    management_key: &str,
611    id: &str,
612) -> Result<Guardrail, OpenRouterError> {
613    let http_client = crate::transport::new_client()?;
614    get_guardrail_with_client(&http_client, base_url, management_key, id).await
615}
616
617pub(crate) async fn get_guardrail_with_client(
618    http_client: &HttpClient,
619    base_url: &str,
620    management_key: &str,
621    id: &str,
622) -> Result<Guardrail, OpenRouterError> {
623    let url = format!("{base_url}/guardrails/{}", encode(id));
624    let response = transport_request::with_bearer_auth(
625        transport_request::get(http_client, &url),
626        management_key,
627    )
628    .send()
629    .await?;
630
631    if response.status().is_success() {
632        let payload: ApiResponse<Guardrail> =
633            transport_response::parse_json_response(response, "guardrail lookup").await?;
634        Ok(payload.data)
635    } else {
636        transport_response::handle_error(response).await?;
637        unreachable!()
638    }
639}
640
641pub async fn update_guardrail(
642    base_url: &str,
643    management_key: &str,
644    id: &str,
645    request: &UpdateGuardrailRequest,
646) -> Result<Guardrail, OpenRouterError> {
647    let http_client = crate::transport::new_client()?;
648    update_guardrail_with_client(&http_client, base_url, management_key, id, request).await
649}
650
651pub(crate) async fn update_guardrail_with_client(
652    http_client: &HttpClient,
653    base_url: &str,
654    management_key: &str,
655    id: &str,
656    request: &UpdateGuardrailRequest,
657) -> Result<Guardrail, OpenRouterError> {
658    let url = format!("{base_url}/guardrails/{}", encode(id));
659    let response = transport_request::with_bearer_auth(
660        transport_request::patch(http_client, &url),
661        management_key,
662    )
663    .json(request)
664    .send()
665    .await?;
666
667    if response.status().is_success() {
668        let payload: ApiResponse<Guardrail> =
669            transport_response::parse_json_response(response, "guardrail update").await?;
670        Ok(payload.data)
671    } else {
672        transport_response::handle_error(response).await?;
673        unreachable!()
674    }
675}
676
677pub async fn delete_guardrail(
678    base_url: &str,
679    management_key: &str,
680    id: &str,
681) -> Result<bool, OpenRouterError> {
682    let http_client = crate::transport::new_client()?;
683    delete_guardrail_with_client(&http_client, base_url, management_key, id).await
684}
685
686pub(crate) async fn delete_guardrail_with_client(
687    http_client: &HttpClient,
688    base_url: &str,
689    management_key: &str,
690    id: &str,
691) -> Result<bool, OpenRouterError> {
692    let url = format!("{base_url}/guardrails/{}", encode(id));
693    let response = transport_request::with_bearer_auth(
694        transport_request::delete(http_client, &url),
695        management_key,
696    )
697    .send()
698    .await?;
699
700    if response.status().is_success() {
701        let payload: DeleteGuardrailResponse =
702            transport_response::parse_json_response(response, "guardrail deletion").await?;
703        Ok(payload.deleted)
704    } else {
705        transport_response::handle_error(response).await?;
706        unreachable!()
707    }
708}
709
710pub async fn list_guardrail_key_assignments(
711    base_url: &str,
712    management_key: &str,
713    id: &str,
714    pagination: Option<PaginationOptions>,
715) -> Result<GuardrailKeyAssignmentsResponse, OpenRouterError> {
716    let http_client = crate::transport::new_client()?;
717    list_guardrail_key_assignments_with_client(
718        &http_client,
719        base_url,
720        management_key,
721        id,
722        pagination,
723    )
724    .await
725}
726
727pub(crate) async fn list_guardrail_key_assignments_with_client(
728    http_client: &HttpClient,
729    base_url: &str,
730    management_key: &str,
731    id: &str,
732    pagination: Option<PaginationOptions>,
733) -> Result<GuardrailKeyAssignmentsResponse, OpenRouterError> {
734    let url = with_pagination(
735        format!("{base_url}/guardrails/{}/assignments/keys", encode(id)),
736        pagination,
737    );
738    let response = transport_request::with_bearer_auth(
739        transport_request::get(http_client, &url),
740        management_key,
741    )
742    .send()
743    .await?;
744
745    if response.status().is_success() {
746        transport_response::parse_json_response(response, "guardrail key assignments").await
747    } else {
748        transport_response::handle_error(response).await?;
749        unreachable!()
750    }
751}
752
753pub async fn bulk_assign_keys_to_guardrail(
754    base_url: &str,
755    management_key: &str,
756    id: &str,
757    request: &BulkKeyAssignmentRequest,
758) -> Result<AssignedCountResponse, OpenRouterError> {
759    let http_client = crate::transport::new_client()?;
760    bulk_assign_keys_to_guardrail_with_client(&http_client, base_url, management_key, id, request)
761        .await
762}
763
764pub(crate) async fn bulk_assign_keys_to_guardrail_with_client(
765    http_client: &HttpClient,
766    base_url: &str,
767    management_key: &str,
768    id: &str,
769    request: &BulkKeyAssignmentRequest,
770) -> Result<AssignedCountResponse, OpenRouterError> {
771    let url = format!("{base_url}/guardrails/{}/assignments/keys", encode(id));
772    let response = transport_request::with_bearer_auth(
773        transport_request::post(http_client, &url),
774        management_key,
775    )
776    .json(request)
777    .send()
778    .await?;
779
780    if response.status().is_success() {
781        transport_response::parse_json_response(response, "guardrail key bulk assignment").await
782    } else {
783        transport_response::handle_error(response).await?;
784        unreachable!()
785    }
786}
787
788pub async fn bulk_unassign_keys_from_guardrail(
789    base_url: &str,
790    management_key: &str,
791    id: &str,
792    request: &BulkKeyAssignmentRequest,
793) -> Result<UnassignedCountResponse, OpenRouterError> {
794    let http_client = crate::transport::new_client()?;
795    bulk_unassign_keys_from_guardrail_with_client(
796        &http_client,
797        base_url,
798        management_key,
799        id,
800        request,
801    )
802    .await
803}
804
805pub(crate) async fn bulk_unassign_keys_from_guardrail_with_client(
806    http_client: &HttpClient,
807    base_url: &str,
808    management_key: &str,
809    id: &str,
810    request: &BulkKeyAssignmentRequest,
811) -> Result<UnassignedCountResponse, OpenRouterError> {
812    let url = format!(
813        "{base_url}/guardrails/{}/assignments/keys/remove",
814        encode(id)
815    );
816    let response = transport_request::with_bearer_auth(
817        transport_request::post(http_client, &url),
818        management_key,
819    )
820    .json(request)
821    .send()
822    .await?;
823
824    if response.status().is_success() {
825        transport_response::parse_json_response(response, "guardrail key bulk removal").await
826    } else {
827        transport_response::handle_error(response).await?;
828        unreachable!()
829    }
830}
831
832pub async fn list_guardrail_member_assignments(
833    base_url: &str,
834    management_key: &str,
835    id: &str,
836    pagination: Option<PaginationOptions>,
837) -> Result<GuardrailMemberAssignmentsResponse, OpenRouterError> {
838    let http_client = crate::transport::new_client()?;
839    list_guardrail_member_assignments_with_client(
840        &http_client,
841        base_url,
842        management_key,
843        id,
844        pagination,
845    )
846    .await
847}
848
849pub(crate) async fn list_guardrail_member_assignments_with_client(
850    http_client: &HttpClient,
851    base_url: &str,
852    management_key: &str,
853    id: &str,
854    pagination: Option<PaginationOptions>,
855) -> Result<GuardrailMemberAssignmentsResponse, OpenRouterError> {
856    let url = with_pagination(
857        format!("{base_url}/guardrails/{}/assignments/members", encode(id)),
858        pagination,
859    );
860    let response = transport_request::with_bearer_auth(
861        transport_request::get(http_client, &url),
862        management_key,
863    )
864    .send()
865    .await?;
866
867    if response.status().is_success() {
868        transport_response::parse_json_response(response, "guardrail member assignments").await
869    } else {
870        transport_response::handle_error(response).await?;
871        unreachable!()
872    }
873}
874
875pub async fn bulk_assign_members_to_guardrail(
876    base_url: &str,
877    management_key: &str,
878    id: &str,
879    request: &BulkMemberAssignmentRequest,
880) -> Result<AssignedCountResponse, OpenRouterError> {
881    let http_client = crate::transport::new_client()?;
882    bulk_assign_members_to_guardrail_with_client(
883        &http_client,
884        base_url,
885        management_key,
886        id,
887        request,
888    )
889    .await
890}
891
892pub(crate) async fn bulk_assign_members_to_guardrail_with_client(
893    http_client: &HttpClient,
894    base_url: &str,
895    management_key: &str,
896    id: &str,
897    request: &BulkMemberAssignmentRequest,
898) -> Result<AssignedCountResponse, OpenRouterError> {
899    let url = format!("{base_url}/guardrails/{}/assignments/members", encode(id));
900    let response = transport_request::with_bearer_auth(
901        transport_request::post(http_client, &url),
902        management_key,
903    )
904    .json(request)
905    .send()
906    .await?;
907
908    if response.status().is_success() {
909        transport_response::parse_json_response(response, "guardrail member bulk assignment").await
910    } else {
911        transport_response::handle_error(response).await?;
912        unreachable!()
913    }
914}
915
916pub async fn bulk_unassign_members_from_guardrail(
917    base_url: &str,
918    management_key: &str,
919    id: &str,
920    request: &BulkMemberAssignmentRequest,
921) -> Result<UnassignedCountResponse, OpenRouterError> {
922    let http_client = crate::transport::new_client()?;
923    bulk_unassign_members_from_guardrail_with_client(
924        &http_client,
925        base_url,
926        management_key,
927        id,
928        request,
929    )
930    .await
931}
932
933pub(crate) async fn bulk_unassign_members_from_guardrail_with_client(
934    http_client: &HttpClient,
935    base_url: &str,
936    management_key: &str,
937    id: &str,
938    request: &BulkMemberAssignmentRequest,
939) -> Result<UnassignedCountResponse, OpenRouterError> {
940    let url = format!(
941        "{base_url}/guardrails/{}/assignments/members/remove",
942        encode(id)
943    );
944    let response = transport_request::with_bearer_auth(
945        transport_request::post(http_client, &url),
946        management_key,
947    )
948    .json(request)
949    .send()
950    .await?;
951
952    if response.status().is_success() {
953        transport_response::parse_json_response(response, "guardrail member bulk removal").await
954    } else {
955        transport_response::handle_error(response).await?;
956        unreachable!()
957    }
958}
959
960pub async fn list_key_assignments(
961    base_url: &str,
962    management_key: &str,
963    pagination: Option<PaginationOptions>,
964) -> Result<GuardrailKeyAssignmentsResponse, OpenRouterError> {
965    let http_client = crate::transport::new_client()?;
966    list_key_assignments_with_client(&http_client, base_url, management_key, pagination).await
967}
968
969pub(crate) async fn list_key_assignments_with_client(
970    http_client: &HttpClient,
971    base_url: &str,
972    management_key: &str,
973    pagination: Option<PaginationOptions>,
974) -> Result<GuardrailKeyAssignmentsResponse, OpenRouterError> {
975    let url = with_pagination(
976        format!("{base_url}/guardrails/assignments/keys"),
977        pagination,
978    );
979    let response = transport_request::with_bearer_auth(
980        transport_request::get(http_client, &url),
981        management_key,
982    )
983    .send()
984    .await?;
985
986    if response.status().is_success() {
987        transport_response::parse_json_response(response, "global key assignments").await
988    } else {
989        transport_response::handle_error(response).await?;
990        unreachable!()
991    }
992}
993
994pub async fn list_member_assignments(
995    base_url: &str,
996    management_key: &str,
997    pagination: Option<PaginationOptions>,
998) -> Result<GuardrailMemberAssignmentsResponse, OpenRouterError> {
999    let http_client = crate::transport::new_client()?;
1000    list_member_assignments_with_client(&http_client, base_url, management_key, pagination).await
1001}
1002
1003pub(crate) async fn list_member_assignments_with_client(
1004    http_client: &HttpClient,
1005    base_url: &str,
1006    management_key: &str,
1007    pagination: Option<PaginationOptions>,
1008) -> Result<GuardrailMemberAssignmentsResponse, OpenRouterError> {
1009    let url = with_pagination(
1010        format!("{base_url}/guardrails/assignments/members"),
1011        pagination,
1012    );
1013    let response = transport_request::with_bearer_auth(
1014        transport_request::get(http_client, &url),
1015        management_key,
1016    )
1017    .send()
1018    .await?;
1019
1020    if response.status().is_success() {
1021        transport_response::parse_json_response(response, "global member assignments").await
1022    } else {
1023        transport_response::handle_error(response).await?;
1024        unreachable!()
1025    }
1026}