1use secrecy::SecretString;
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone)]
47pub struct Secret {
48 pub namespace: String,
50 pub key: String,
52 pub value: SecretString,
54 pub version: i32,
56 pub expires_at: Option<time::OffsetDateTime>,
58 pub metadata: serde_json::Value,
60 pub updated_at: time::OffsetDateTime,
62 pub etag: Option<String>,
64 pub last_modified: Option<String>,
66 pub request_id: Option<String>,
68}
69
70#[derive(Debug, Clone, Deserialize, Serialize)]
72pub struct SecretKeyInfo {
73 pub key: String,
75 pub version: i32,
77 pub updated_at: String,
79 pub kid: Option<String>,
81}
82
83#[derive(Debug, Clone)]
108pub struct GetOpts {
109 pub use_cache: bool,
111 pub if_none_match: Option<String>,
113 pub if_modified_since: Option<String>,
115}
116
117impl Default for GetOpts {
118 fn default() -> Self {
119 Self {
120 use_cache: true,
121 if_none_match: None,
122 if_modified_since: None,
123 }
124 }
125}
126
127#[derive(Debug, Clone, Default)]
152pub struct PutOpts {
153 pub ttl_seconds: Option<i64>,
155 pub metadata: Option<serde_json::Value>,
157 pub idempotency_key: Option<String>,
159}
160
161#[derive(Debug, Clone, Deserialize)]
163pub struct PutResult {
164 pub message: String,
166 pub namespace: String,
168 pub key: String,
170 pub created_at: String,
172 pub request_id: String,
174}
175
176#[derive(Debug, Clone)]
178pub struct DeleteResult {
179 pub deleted: bool,
181 pub request_id: Option<String>,
183}
184
185#[derive(Debug, Clone, Default)]
187pub struct ListOpts {
188 pub prefix: Option<String>,
190 pub limit: Option<usize>,
192}
193
194#[derive(Debug, Clone, Deserialize)]
196pub struct ListSecretsResult {
197 pub namespace: String,
199 pub secrets: Vec<SecretKeyInfo>,
201 pub total: usize,
203 #[serde(default)]
205 pub limit: usize,
206 #[serde(default)]
208 pub has_more: bool,
209 #[serde(default)]
211 pub request_id: Option<String>,
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
216pub enum ExportFormat {
217 #[default]
219 Json,
220 Dotenv,
222 Shell,
224 DockerCompose,
226}
227
228impl ExportFormat {
229 pub fn as_str(&self) -> &'static str {
231 match self {
232 ExportFormat::Json => "json",
233 ExportFormat::Dotenv => "dotenv",
234 ExportFormat::Shell => "shell",
235 ExportFormat::DockerCompose => "docker-compose",
236 }
237 }
238}
239
240#[derive(Debug, Clone)]
242pub enum BatchKeys {
243 Keys(Vec<String>),
245 All,
247}
248
249#[derive(Debug, Clone)]
253pub enum BatchGetResult {
254 Json(BatchGetJsonResult),
256 Text(String),
258}
259
260#[derive(Debug, Clone, Deserialize, Serialize)]
262pub struct BatchGetJsonResult {
263 pub namespace: String,
265 pub secrets: std::collections::HashMap<String, String>,
267 #[serde(default)]
269 pub missing: Vec<String>,
270 pub total: usize,
272 pub request_id: String,
274}
275
276#[derive(Debug, Clone, Serialize)]
278pub struct BatchOp {
279 pub action: String,
281 pub key: String,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub value: Option<String>,
286 #[serde(skip_serializing_if = "Option::is_none")]
288 pub ttl_seconds: Option<i64>,
289 #[serde(skip_serializing_if = "Option::is_none")]
291 pub metadata: Option<serde_json::Value>,
292}
293
294impl BatchOp {
295 pub fn put(key: impl Into<String>, value: impl Into<String>) -> Self {
297 Self {
298 action: "put".to_string(),
299 key: key.into(),
300 value: Some(value.into()),
301 ttl_seconds: None,
302 metadata: None,
303 }
304 }
305
306 pub fn delete(key: impl Into<String>) -> Self {
308 Self {
309 action: "delete".to_string(),
310 key: key.into(),
311 value: None,
312 ttl_seconds: None,
313 metadata: None,
314 }
315 }
316
317 pub fn read(key: impl Into<String>) -> Self {
322 Self {
323 action: "read".to_string(),
324 key: key.into(),
325 value: None,
326 ttl_seconds: None,
327 metadata: None,
328 }
329 }
330
331 pub fn with_ttl(mut self, ttl_seconds: i64) -> Self {
333 self.ttl_seconds = Some(ttl_seconds);
334 self
335 }
336
337 pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
339 self.metadata = Some(metadata);
340 self
341 }
342}
343
344#[derive(Debug, Clone, Deserialize)]
346pub struct BatchOperateResult {
347 pub namespace: String,
349 pub results: BatchResultSummary,
351 pub success_rate: f64,
353}
354
355#[derive(Debug, Clone, Deserialize)]
357pub struct BatchResultSummary {
358 pub succeeded: Vec<BatchOperationResult>,
360 pub failed: Vec<BatchOperationResult>,
362 pub total: usize,
364}
365
366#[derive(Debug, Clone, Deserialize)]
368pub struct BatchOperationResult {
369 pub key: String,
371 pub action: String,
373 pub success: bool,
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub error: Option<String>,
378}
379
380#[derive(Debug, Clone)]
382pub enum EnvExport {
383 Json(EnvJsonExport),
385 Text(String),
387}
388
389#[derive(Debug, Clone, Default)]
391pub struct ExportEnvOpts {
392 pub format: ExportFormat,
394 pub use_cache: bool,
396 pub if_none_match: Option<String>,
398}
399
400#[derive(Debug, Clone, Deserialize)]
402pub struct EnvJsonExport {
403 pub namespace: String,
405 pub environment: std::collections::HashMap<String, String>,
407 pub etag: String,
409 pub total: usize,
411 pub request_id: String,
413}
414
415#[derive(Debug, Clone, Deserialize)]
417pub struct ListNamespacesResult {
418 pub namespaces: Vec<NamespaceListItem>,
420 pub total: usize,
422 pub request_id: String,
424}
425
426#[derive(Debug, Clone, Deserialize)]
428pub struct NamespaceListItem {
429 pub name: String,
431 pub created_at: String,
433 pub updated_at: String,
435 pub secret_count: usize,
437}
438
439#[derive(Debug, Clone, Deserialize)]
441pub struct NamespaceInfo {
442 pub name: String,
444 pub created_at: String,
446 pub updated_at: String,
448 pub secret_count: usize,
450 pub total_size: usize,
452 #[serde(default)]
454 pub metadata: serde_json::Value,
455 pub request_id: String,
457}
458
459#[derive(Debug, Clone, Serialize, Default)]
461pub struct NamespaceTemplate {
462 pub template: String,
464 #[serde(flatten)]
466 pub params: serde_json::Value,
467}
468
469#[derive(Debug, Clone, Deserialize)]
471pub struct InitNamespaceResult {
472 pub message: String,
474 pub namespace: String,
476 pub secrets_created: usize,
478 pub request_id: String,
480}
481
482#[derive(Debug, Clone, Serialize)]
484pub struct CreateNamespaceRequest {
485 pub name: String,
487 #[serde(skip_serializing_if = "Option::is_none")]
489 pub description: Option<String>,
490}
491
492#[derive(Debug, Clone, Deserialize)]
494pub struct CreateNamespaceResult {
495 pub message: String,
497 pub namespace: String,
499 pub request_id: String,
501}
502
503#[derive(Debug, Clone, Deserialize)]
505pub struct DeleteNamespaceResult {
506 pub message: String,
508 pub namespace: String,
510 pub secrets_deleted: usize,
512 #[serde(skip_serializing_if = "Option::is_none")]
514 pub request_id: Option<String>,
515}
516
517#[derive(Debug, Clone, Deserialize)]
519pub struct VersionList {
520 pub namespace: String,
522 pub key: String,
524 pub versions: Vec<VersionInfo>,
526 pub total: usize,
528 pub request_id: String,
530}
531
532#[derive(Debug, Clone, Deserialize)]
534pub struct VersionInfo {
535 pub version: i32,
537 pub created_at: String,
539 pub created_by: String,
541 #[serde(skip_serializing_if = "Option::is_none")]
543 pub comment: Option<String>,
544 pub is_current: bool,
546}
547
548#[derive(Debug, Clone, Deserialize)]
550pub struct RollbackResult {
551 pub message: String,
553 pub namespace: String,
555 pub key: String,
557 pub from_version: i32,
559 pub to_version: i32,
561 pub request_id: String,
563}
564
565#[derive(Debug, Clone, Serialize, Default)]
567pub struct AuditQuery {
568 #[serde(skip_serializing_if = "Option::is_none")]
570 pub namespace: Option<String>,
571 #[serde(skip_serializing_if = "Option::is_none")]
573 pub actor: Option<String>,
574 #[serde(skip_serializing_if = "Option::is_none")]
576 pub action: Option<String>,
577 #[serde(skip_serializing_if = "Option::is_none")]
579 pub from: Option<String>,
580 #[serde(skip_serializing_if = "Option::is_none")]
582 pub to: Option<String>,
583 #[serde(skip_serializing_if = "Option::is_none")]
585 pub success: Option<bool>,
586 #[serde(skip_serializing_if = "Option::is_none")]
588 pub limit: Option<usize>,
589 #[serde(skip_serializing_if = "Option::is_none")]
591 pub offset: Option<usize>,
592}
593
594#[derive(Debug, Clone, Deserialize)]
596pub struct AuditResult {
597 #[serde(rename = "logs")]
599 pub entries: Vec<AuditEntry>,
600 pub total: usize,
602 pub limit: usize,
604 pub offset: usize,
606 pub has_more: bool,
608 pub request_id: String,
610}
611
612#[derive(Debug, Clone, Deserialize)]
614pub struct AuditEntry {
615 pub id: i64,
617 pub timestamp: String,
619 #[serde(skip_serializing_if = "Option::is_none")]
621 pub actor: Option<String>,
622 pub action: String,
624 #[serde(rename = "namespace", skip_serializing_if = "Option::is_none")]
626 pub namespace: Option<String>,
627 #[serde(rename = "key_name", skip_serializing_if = "Option::is_none")]
629 pub key_name: Option<String>,
630 #[serde(rename = "success")]
632 pub success: bool,
633 #[serde(rename = "ip_address", skip_serializing_if = "Option::is_none")]
635 pub ip_address: Option<String>,
636 #[serde(rename = "user_agent", skip_serializing_if = "Option::is_none")]
638 pub user_agent: Option<String>,
639 #[serde(rename = "error", skip_serializing_if = "Option::is_none")]
641 pub error: Option<String>,
642}
643
644#[derive(Debug, Clone, Deserialize)]
646pub struct Discovery {
647 pub service: String,
649 pub version: String,
651 pub api_version: String,
653 pub features: Vec<String>,
655 pub build: BuildInfo,
657 pub endpoints: EndpointInfo,
659}
660
661#[derive(Debug, Clone, Deserialize)]
663pub struct BuildInfo {
664 pub commit: String,
666 pub timestamp: String,
668 pub rust_version: String,
670}
671
672#[derive(Debug, Clone, Deserialize)]
674pub struct EndpointInfo {
675 pub base_url: String,
677 pub health_url: String,
679 pub metrics_url: String,
681}
682
683#[derive(Debug, Clone, Deserialize, Serialize)]
685pub struct HealthStatus {
686 pub status: String,
688 pub timestamp: String,
690 pub version: Option<String>,
692 #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
694 pub checks: std::collections::HashMap<String, HealthCheckResult>,
695}
696
697#[derive(Debug, Clone, Deserialize, Serialize)]
699pub struct HealthCheckResult {
700 pub status: String,
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub message: Option<String>,
705 #[serde(skip_serializing_if = "Option::is_none")]
707 pub error: Option<String>,
708 #[serde(skip_serializing_if = "Option::is_none")]
710 pub duration_ms: Option<u64>,
711}
712
713#[derive(Debug, Clone, Serialize)]
715pub struct CreateApiKeyRequest {
716 pub name: String,
718 #[serde(skip_serializing_if = "Option::is_none")]
720 pub expires_at: Option<String>,
721 #[serde(default)]
723 pub namespaces: Vec<String>,
724 pub permissions: Vec<String>,
726 #[serde(skip_serializing_if = "Option::is_none")]
728 pub metadata: Option<serde_json::Value>,
729}
730
731#[derive(Debug, Clone, Deserialize)]
733pub struct ApiKeyInfo {
734 pub id: String,
736 pub name: String,
738 #[serde(skip_serializing_if = "Option::is_none")]
740 pub key: Option<SecretString>,
741 pub created_at: String,
743 #[serde(skip_serializing_if = "Option::is_none")]
745 pub expires_at: Option<String>,
746 #[serde(skip_serializing_if = "Option::is_none")]
748 pub last_used_at: Option<String>,
749 pub active: bool,
751 pub namespaces: Vec<String>,
753 pub permissions: Vec<String>,
755 #[serde(skip_serializing_if = "Option::is_none")]
757 pub metadata: Option<serde_json::Value>,
758}
759
760#[derive(Debug, Clone, Deserialize)]
762pub struct ListApiKeysResult {
763 pub keys: Vec<ApiKeyInfo>,
765 pub total: usize,
767 pub request_id: Option<String>,
769}
770
771#[derive(Debug, Clone, Deserialize)]
773pub struct RevokeApiKeyResult {
774 pub message: String,
776 pub key_id: String,
778 pub request_id: Option<String>,
780}
781
782#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
786pub enum SearchMode {
787 Exact,
789 Prefix,
791 #[default]
793 Contains,
794}
795
796impl SearchMode {
797 pub fn as_str(&self) -> &'static str {
799 match self {
800 SearchMode::Exact => "exact",
801 SearchMode::Prefix => "prefix",
802 SearchMode::Contains => "contains",
803 }
804 }
805}
806
807#[derive(Debug, Clone, Default)]
809pub struct SearchSecretsOpts {
810 pub pattern: String,
812 pub mode: SearchMode,
814 pub namespaces: Vec<String>,
816 pub include_values: bool,
818}
819
820#[derive(Debug, Clone, Deserialize)]
822pub struct SearchMatch {
823 pub namespace: String,
825 pub key: String,
827 pub version: i32,
829 pub updated_at: String,
831 #[serde(skip_serializing_if = "Option::is_none")]
833 pub value: Option<String>,
834}
835
836#[derive(Debug, Clone, Deserialize)]
838pub struct SearchSecretsResult {
839 pub matches: Vec<SearchMatch>,
841 pub total: usize,
843}
844
845#[derive(Debug, Clone)]
847pub struct BulkDeleteOpts {
848 pub key: String,
850 pub namespaces: Vec<String>,
852}
853
854#[derive(Debug, Clone, Deserialize)]
856pub struct BulkDeleteFailure {
857 pub namespace: String,
859 pub error: String,
861}
862
863#[derive(Debug, Clone, Deserialize)]
865pub struct BulkDeleteResult {
866 pub key: String,
868 pub deleted: Vec<String>,
870 pub failed: Vec<serde_json::Value>,
872}
873
874#[cfg(test)]
875mod tests {
876 use super::*;
877
878 #[test]
879 fn test_export_format() {
880 assert_eq!(ExportFormat::Json.as_str(), "json");
881 assert_eq!(ExportFormat::Dotenv.as_str(), "dotenv");
882 assert_eq!(ExportFormat::Shell.as_str(), "shell");
883 assert_eq!(ExportFormat::DockerCompose.as_str(), "docker-compose");
884 }
885
886 #[test]
887 fn test_search_mode() {
888 assert_eq!(SearchMode::Exact.as_str(), "exact");
889 assert_eq!(SearchMode::Prefix.as_str(), "prefix");
890 assert_eq!(SearchMode::Contains.as_str(), "contains");
891 }
892
893 #[test]
894 fn test_batch_op_read() {
895 let op = BatchOp::read("my-key");
896 assert_eq!(op.action, "read");
897 assert_eq!(op.key, "my-key");
898 assert!(op.value.is_none());
899 }
900}