use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum GuardrailResetInterval {
Daily,
Weekly,
Monthly,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Guardrail {
pub id: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit_usd: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reset_interval: Option<GuardrailResetInterval>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_providers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignored_providers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_models: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enforce_zdr: Option<bool>,
pub created_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GuardrailResponse {
pub data: Guardrail,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GuardrailsListResponse {
pub data: Vec<Guardrail>,
pub total_count: u32,
}
impl GuardrailsListResponse {
#[must_use]
pub fn count(&self) -> usize {
self.data.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GuardrailCreateRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit_usd: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reset_interval: Option<GuardrailResetInterval>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_providers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignored_providers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_models: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enforce_zdr: Option<bool>,
}
impl GuardrailCreateRequest {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: None,
limit_usd: None,
reset_interval: None,
allowed_providers: None,
ignored_providers: None,
allowed_models: None,
enforce_zdr: None,
}
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn with_limit_usd(mut self, limit_usd: f64) -> Self {
self.limit_usd = Some(limit_usd);
self
}
#[must_use]
pub fn with_reset_interval(mut self, reset_interval: GuardrailResetInterval) -> Self {
self.reset_interval = Some(reset_interval);
self
}
#[must_use]
pub fn with_allowed_providers(mut self, allowed_providers: Vec<String>) -> Self {
self.allowed_providers = Some(allowed_providers);
self
}
#[must_use]
pub fn with_ignored_providers(mut self, ignored_providers: Vec<String>) -> Self {
self.ignored_providers = Some(ignored_providers);
self
}
#[must_use]
pub fn with_allowed_models(mut self, allowed_models: Vec<String>) -> Self {
self.allowed_models = Some(allowed_models);
self
}
#[must_use]
pub fn with_enforce_zdr(mut self, enforce_zdr: bool) -> Self {
self.enforce_zdr = Some(enforce_zdr);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct GuardrailUpdateRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit_usd: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reset_interval: Option<GuardrailResetInterval>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_providers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ignored_providers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_models: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub enforce_zdr: Option<bool>,
}
impl GuardrailUpdateRequest {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.name.is_none()
&& self.description.is_none()
&& self.limit_usd.is_none()
&& self.reset_interval.is_none()
&& self.allowed_providers.is_none()
&& self.ignored_providers.is_none()
&& self.allowed_models.is_none()
&& self.enforce_zdr.is_none()
}
#[must_use]
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
#[must_use]
pub fn with_limit_usd(mut self, limit_usd: f64) -> Self {
self.limit_usd = Some(limit_usd);
self
}
#[must_use]
pub fn with_reset_interval(mut self, reset_interval: GuardrailResetInterval) -> Self {
self.reset_interval = Some(reset_interval);
self
}
#[must_use]
pub fn with_allowed_providers(mut self, allowed_providers: Vec<String>) -> Self {
self.allowed_providers = Some(allowed_providers);
self
}
#[must_use]
pub fn with_ignored_providers(mut self, ignored_providers: Vec<String>) -> Self {
self.ignored_providers = Some(ignored_providers);
self
}
#[must_use]
pub fn with_allowed_models(mut self, allowed_models: Vec<String>) -> Self {
self.allowed_models = Some(allowed_models);
self
}
#[must_use]
pub fn with_enforce_zdr(mut self, enforce_zdr: bool) -> Self {
self.enforce_zdr = Some(enforce_zdr);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GuardrailKeyAssignment {
pub id: String,
pub key_hash: String,
pub guardrail_id: String,
pub key_name: String,
pub key_label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigned_by: Option<String>,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GuardrailMemberAssignment {
pub id: String,
pub user_id: String,
pub organization_id: String,
pub guardrail_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigned_by: Option<String>,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GuardrailKeyAssignmentsResponse {
pub data: Vec<GuardrailKeyAssignment>,
pub total_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GuardrailMemberAssignmentsResponse {
pub data: Vec<GuardrailMemberAssignment>,
pub total_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BulkAssignKeysRequest {
pub key_hashes: Vec<String>,
}
impl BulkAssignKeysRequest {
#[must_use]
pub fn new(key_hashes: Vec<String>) -> Self {
Self { key_hashes }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BulkAssignMembersRequest {
pub member_user_ids: Vec<String>,
}
impl BulkAssignMembersRequest {
#[must_use]
pub fn new(member_user_ids: Vec<String>) -> Self {
Self { member_user_ids }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkAssignResponse {
pub assigned_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkUnassignResponse {
pub unassigned_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GuardrailDeleteResponse {
pub deleted: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_guardrail_reset_interval_roundtrip() {
let json = serde_json::to_string(&GuardrailResetInterval::Weekly).unwrap();
assert_eq!(json, "\"weekly\"");
let interval: GuardrailResetInterval = serde_json::from_str(&json).unwrap();
assert_eq!(interval, GuardrailResetInterval::Weekly);
}
#[test]
fn test_guardrail_create_request_skips_none_fields() {
let request = GuardrailCreateRequest::new("Production");
let json = serde_json::to_value(&request).unwrap();
assert_eq!(json["name"], "Production");
assert!(json.get("description").is_none());
assert!(json.get("limit_usd").is_none());
}
#[test]
fn test_guardrail_update_request_is_empty() {
let empty = GuardrailUpdateRequest::new();
assert!(empty.is_empty());
let non_empty = GuardrailUpdateRequest::new().with_enforce_zdr(true);
assert!(!non_empty.is_empty());
}
#[test]
fn test_guardrail_assignment_request_roundtrip() {
let request =
BulkAssignMembersRequest::new(vec!["user_123".to_string(), "user_456".to_string()]);
let json = serde_json::to_string(&request).unwrap();
let parsed: BulkAssignMembersRequest = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, request);
}
}