use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::DakeraClient;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateKeyRequest {
pub name: String,
pub scope: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub namespaces: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_in_days: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateKeyResponse {
pub key_id: String,
pub key: String,
pub name: String,
pub scope: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub namespaces: Option<Vec<String>>,
pub created_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
pub warning: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyInfo {
pub key_id: String,
pub name: String,
pub scope: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub namespaces: Option<Vec<String>>,
pub created_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
pub active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListKeysResponse {
pub keys: Vec<KeyInfo>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeySuccessResponse {
pub success: bool,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RotateKeyResponse {
pub new_key: String,
pub key_id: String,
pub warning: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyUsageResponse {
pub key_id: String,
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub rate_limited_requests: u64,
pub bytes_transferred: u64,
pub avg_latency_ms: f64,
#[serde(default)]
pub by_endpoint: Vec<EndpointUsageInfo>,
#[serde(default)]
pub by_namespace: Vec<NamespaceUsageInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndpointUsageInfo {
pub endpoint: String,
pub requests: u64,
pub avg_latency_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamespaceUsageInfo {
pub namespace: String,
pub requests: u64,
pub vectors_accessed: u64,
}
impl DakeraClient {
pub async fn create_key(&self, request: CreateKeyRequest) -> Result<CreateKeyResponse> {
let url = format!("{}/admin/keys", self.base_url);
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn list_keys(&self) -> Result<ListKeysResponse> {
let url = format!("{}/admin/keys", self.base_url);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn get_key(&self, key_id: &str) -> Result<KeyInfo> {
let url = format!("{}/admin/keys/{}", self.base_url, key_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn delete_key(&self, key_id: &str) -> Result<KeySuccessResponse> {
let url = format!("{}/admin/keys/{}", self.base_url, key_id);
let response = self.client.delete(&url).send().await?;
self.handle_response(response).await
}
pub async fn deactivate_key(&self, key_id: &str) -> Result<KeySuccessResponse> {
let url = format!("{}/admin/keys/{}/deactivate", self.base_url, key_id);
let response = self.client.post(&url).send().await?;
self.handle_response(response).await
}
pub async fn rotate_key(&self, key_id: &str) -> Result<RotateKeyResponse> {
let url = format!("{}/admin/keys/{}/rotate", self.base_url, key_id);
let response = self.client.post(&url).send().await?;
self.handle_response(response).await
}
pub async fn key_usage(&self, key_id: &str) -> Result<ApiKeyUsageResponse> {
let url = format!("{}/admin/keys/{}/usage", self.base_url, key_id);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn create_namespace_key(
&self,
namespace: &str,
request: CreateNamespaceKeyRequest,
) -> Result<CreateNamespaceKeyResponse> {
let url = format!("{}/v1/namespaces/{}/keys", self.base_url, namespace);
let response = self.client.post(&url).json(&request).send().await?;
self.handle_response(response).await
}
pub async fn list_namespace_keys(&self, namespace: &str) -> Result<ListNamespaceKeysResponse> {
let url = format!("{}/v1/namespaces/{}/keys", self.base_url, namespace);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
pub async fn delete_namespace_key(
&self,
namespace: &str,
key_id: &str,
) -> Result<KeySuccessResponse> {
let url = format!(
"{}/v1/namespaces/{}/keys/{}",
self.base_url, namespace, key_id
);
let response = self.client.delete(&url).send().await?;
self.handle_response(response).await
}
pub async fn namespace_key_usage(
&self,
namespace: &str,
key_id: &str,
) -> Result<NamespaceKeyUsageResponse> {
let url = format!(
"{}/v1/namespaces/{}/keys/{}/usage",
self.base_url, namespace, key_id
);
let response = self.client.get(&url).send().await?;
self.handle_response(response).await
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateNamespaceKeyRequest {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_in_days: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateNamespaceKeyResponse {
pub key_id: String,
pub key: String,
pub name: String,
pub namespace: String,
pub created_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
pub warning: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamespaceKeyInfo {
pub key_id: String,
pub name: String,
pub namespace: String,
pub created_at: u64,
pub active: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListNamespaceKeysResponse {
pub namespace: String,
pub keys: Vec<NamespaceKeyInfo>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamespaceKeyUsageResponse {
pub key_id: String,
pub namespace: String,
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub bytes_transferred: u64,
pub avg_latency_ms: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_namespace_key_request_serializes_without_expiry() {
let req = CreateNamespaceKeyRequest {
name: "ci-runner".to_string(),
expires_in_days: None,
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"name\":\"ci-runner\""));
assert!(!json.contains("expires_in_days"));
}
#[test]
fn test_create_namespace_key_request_serializes_with_expiry() {
let req = CreateNamespaceKeyRequest {
name: "ci-runner".to_string(),
expires_in_days: Some(30),
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"expires_in_days\":30"));
}
#[test]
fn test_namespace_key_info_deserializes() {
let json = r#"{
"key_id": "key-abc",
"name": "ci-runner",
"namespace": "prod-ns",
"created_at": 1774000000,
"active": true
}"#;
let info: NamespaceKeyInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.key_id, "key-abc");
assert_eq!(info.namespace, "prod-ns");
assert!(info.active);
assert!(info.expires_at.is_none());
}
#[test]
fn test_namespace_key_usage_response_deserializes() {
let json = r#"{
"key_id": "key-abc",
"namespace": "prod-ns",
"total_requests": 1000,
"successful_requests": 980,
"failed_requests": 20,
"bytes_transferred": 512000,
"avg_latency_ms": 12.4
}"#;
let usage: NamespaceKeyUsageResponse = serde_json::from_str(json).unwrap();
assert_eq!(usage.total_requests, 1000);
assert!((usage.avg_latency_ms - 12.4).abs() < 0.001);
}
#[test]
fn test_list_namespace_keys_response_deserializes() {
let json = r#"{
"namespace": "prod-ns",
"keys": [],
"total": 0
}"#;
let resp: ListNamespaceKeysResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.namespace, "prod-ns");
assert_eq!(resp.total, 0);
assert!(resp.keys.is_empty());
}
}