greentic_types/
secrets.rs1use crate::{ErrorCode, GResult, GreenticError};
5use alloc::{format, string::String, vec::Vec};
6use core::ops::Deref;
7#[cfg(feature = "schemars")]
8use schemars::JsonSchema;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
14#[cfg_attr(feature = "serde", derive(Serialize))]
15#[cfg_attr(feature = "serde", serde(transparent))]
16#[cfg_attr(feature = "schemars", derive(JsonSchema))]
17pub struct SecretKey(String);
18
19impl SecretKey {
20 pub fn new(key: impl Into<String>) -> GResult<Self> {
22 let key = key.into();
23 Self::parse(&key).map_err(|err| {
24 GreenticError::new(
25 ErrorCode::InvalidInput,
26 format!("invalid secret key: {err}"),
27 )
28 })
29 }
30
31 pub fn as_str(&self) -> &str {
33 &self.0
34 }
35
36 pub fn parse(value: &str) -> Result<Self, SecretKeyError> {
44 if value.is_empty() {
45 return Err(SecretKeyError::Empty);
46 }
47 if value.starts_with('/') {
48 return Err(SecretKeyError::LeadingSlash);
49 }
50 for c in value.chars() {
51 if !(c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-' | '/')) {
52 return Err(SecretKeyError::InvalidChar { c });
53 }
54 }
55 if value.split('/').any(|segment| segment == "..") {
56 return Err(SecretKeyError::DotDotSegment);
57 }
58 Ok(Self(value.to_owned()))
59 }
60}
61
62#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
64pub enum SecretKeyError {
65 #[error("secret key must not be empty")]
67 Empty,
68 #[error("secret key must not start with '/'")]
70 LeadingSlash,
71 #[error("secret key must not contain '..' segments")]
73 DotDotSegment,
74 #[error("secret key contains invalid character '{c}'")]
76 InvalidChar {
77 c: char,
79 },
80}
81
82impl Deref for SecretKey {
83 type Target = str;
84
85 fn deref(&self) -> &Self::Target {
86 &self.0
87 }
88}
89
90impl From<String> for SecretKey {
91 fn from(key: String) -> Self {
92 Self(key)
93 }
94}
95
96impl From<&str> for SecretKey {
97 fn from(key: &str) -> Self {
98 Self(key.to_owned())
99 }
100}
101
102impl From<SecretKey> for String {
103 fn from(key: SecretKey) -> Self {
104 key.0
105 }
106}
107
108#[cfg(feature = "serde")]
109impl<'de> Deserialize<'de> for SecretKey {
110 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111 where
112 D: serde::Deserializer<'de>,
113 {
114 let value = String::deserialize(deserializer)?;
115 SecretKey::parse(&value).map_err(serde::de::Error::custom)
116 }
117}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
121#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
122#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
123#[cfg_attr(feature = "schemars", derive(JsonSchema))]
124pub struct SecretScope {
125 pub env: String,
127 pub tenant: String,
129 #[cfg_attr(
131 feature = "serde",
132 serde(default, skip_serializing_if = "Option::is_none")
133 )]
134 pub team: Option<String>,
135}
136
137#[derive(Clone, Debug, PartialEq, Eq)]
139#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
140#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
141#[cfg_attr(feature = "schemars", derive(JsonSchema))]
142pub enum SecretFormat {
143 Bytes,
145 Text,
147 Json,
149}
150
151#[non_exhaustive]
153#[derive(Clone, Debug, PartialEq, Eq)]
154#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
155#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
156#[cfg_attr(feature = "schemars", derive(JsonSchema))]
157pub struct SecretRequirement {
158 pub key: SecretKey,
160 #[cfg_attr(
162 feature = "serde",
163 serde(default = "SecretRequirement::default_required")
164 )]
165 pub required: bool,
166 #[cfg_attr(
168 feature = "serde",
169 serde(default, skip_serializing_if = "Option::is_none")
170 )]
171 pub description: Option<String>,
172 #[cfg_attr(
174 feature = "serde",
175 serde(default, skip_serializing_if = "Option::is_none")
176 )]
177 pub scope: Option<SecretScope>,
178 #[cfg_attr(
180 feature = "serde",
181 serde(default, skip_serializing_if = "Option::is_none")
182 )]
183 pub format: Option<SecretFormat>,
184 #[cfg_attr(
186 feature = "serde",
187 serde(default, skip_serializing_if = "Option::is_none")
188 )]
189 pub schema: Option<serde_json::Value>,
190 #[cfg_attr(
192 feature = "serde",
193 serde(default, skip_serializing_if = "Vec::is_empty")
194 )]
195 pub examples: Vec<String>,
196}
197
198impl Default for SecretRequirement {
199 fn default() -> Self {
200 Self {
201 key: SecretKey::default(),
202 required: true,
203 description: None,
204 scope: None,
205 format: None,
206 schema: None,
207 examples: Vec::new(),
208 }
209 }
210}
211
212impl SecretRequirement {
213 const fn default_required() -> bool {
214 true
215 }
216}