greentic_secrets_api/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6#[cfg(not(feature = "std"))]
7use alloc::{string::String, vec::Vec};
8#[cfg(feature = "std")]
9use std::{string::String, vec::Vec};
10
11pub mod value {
12    use super::String;
13    #[cfg(not(feature = "std"))]
14    use core::cmp;
15    #[cfg(feature = "std")]
16    use std::cmp;
17
18    #[derive(Clone, Debug, PartialEq, Eq)]
19    pub struct SecretValue(pub String);
20
21    impl SecretValue {
22        pub fn into_string(self) -> String {
23            self.0
24        }
25
26        pub fn redact_preview(&self) -> String {
27            let n = self.0.len();
28            match n {
29                0..=4 => "****".into(),
30                _ => {
31                    let prefix = cmp::min(4, n);
32                    format!("{prefix_head}****", prefix_head = &self.0[..prefix])
33                }
34            }
35        }
36    }
37}
38
39pub mod error {
40    use super::String;
41    use thiserror::Error;
42
43    #[derive(Error, Debug)]
44    pub enum SecretsError {
45        #[error("secret not found: {0}")]
46        NotFound(String),
47        #[error("backend error: {0}")]
48        Backend(String),
49        #[error("invalid secret value for {0}: {1}")]
50        Invalid(String, String),
51    }
52
53    pub type Result<T> = core::result::Result<T, SecretsError>;
54}
55
56pub mod spec {
57    use super::{
58        String, Vec,
59        error::{Result, SecretsError},
60    };
61
62    pub struct SecretSpec {
63        pub name: String,
64        pub description: Option<String>,
65        pub required: bool,
66        validator: Option<fn(&str) -> bool>,
67    }
68
69    impl SecretSpec {
70        pub fn new<N: Into<String>>(name: N) -> Self {
71            Self {
72                name: name.into(),
73                description: None,
74                required: false,
75                validator: None,
76            }
77        }
78
79        pub fn description<D: Into<String>>(mut self, description: D) -> Self {
80            self.description = Some(description.into());
81            self
82        }
83
84        pub fn required(mut self) -> Self {
85            self.required = true;
86            self
87        }
88
89        pub fn validator(mut self, f: fn(&str) -> bool) -> Self {
90            self.validator = Some(f);
91            self
92        }
93
94        pub fn validate(&self, value: &str) -> Result<()> {
95            if let Some(f) = self.validator {
96                if !f(value) {
97                    return Err(SecretsError::Invalid(
98                        self.name.clone(),
99                        "validator failed".into(),
100                    ));
101                }
102            }
103            Ok(())
104        }
105    }
106
107    #[derive(Default)]
108    pub struct SecretSpecRegistry {
109        specs: Vec<SecretSpec>,
110    }
111
112    impl SecretSpecRegistry {
113        pub fn register(&mut self, spec: SecretSpec) {
114            self.specs.push(spec);
115        }
116
117        pub fn validate_value(&self, name: &str, value: &str) -> Result<()> {
118            if let Some(spec) = self.specs.iter().find(|s| s.name == name) {
119                spec.validate(value)
120            } else {
121                Ok(())
122            }
123        }
124
125        pub fn to_markdown(&self) -> String {
126            let mut md = String::from("| Name | Description | Required |\n|---|---|---|\n");
127            for spec in &self.specs {
128                md.push_str(&format!(
129                    "| {} | {} | {} |\n",
130                    spec.name,
131                    spec.description.clone().unwrap_or_default(),
132                    if spec.required { "yes" } else { "no" }
133                ));
134            }
135            md
136        }
137    }
138}
139
140pub mod backend {
141    use super::{error::Result, value::SecretValue};
142
143    /// Minimal backend trait providers implement.
144    pub trait SecretsBackend: Send + Sync + 'static {
145        /// keys like "env:DB_URL", "aws:ssm:/path", etc.
146        fn get(&self, key: &str) -> Result<SecretValue>;
147    }
148}
149
150pub use backend::SecretsBackend;
151pub use error::{Result, SecretsError};
152pub use spec::{SecretSpec, SecretSpecRegistry};
153pub use value::SecretValue;