Skip to main content

vault_client_rs/types/
secret.rs

1use serde::{Deserialize, Serialize};
2
3use crate::VaultError;
4
5pub use secrecy::SecretString;
6
7fn validate_vault_path(s: &str, kind: &str) -> Result<(), VaultError> {
8    if s.is_empty()
9        || s.contains("..")
10        || s.starts_with('/')
11        || s.ends_with('/')
12        || s.contains('\0')
13        || s.contains("%2e")
14        || s.contains("%2E")
15        || s.contains("%2f")
16        || s.contains("%2F")
17        || s.chars().any(|c| c.is_control())
18    {
19        return Err(VaultError::Config(format!("invalid {kind}: {s:?}")));
20    }
21    Ok(())
22}
23
24macro_rules! vault_path_type {
25    ($Name:ident, $label:literal) => {
26        impl $Name {
27            pub fn new(s: impl Into<String>) -> Result<Self, VaultError> {
28                let s = s.into();
29                validate_vault_path(&s, $label)?;
30                Ok(Self(s))
31            }
32
33            pub fn as_str(&self) -> &str {
34                &self.0
35            }
36        }
37
38        impl TryFrom<String> for $Name {
39            type Error = VaultError;
40            fn try_from(s: String) -> Result<Self, Self::Error> {
41                Self::new(s)
42            }
43        }
44
45        impl TryFrom<&str> for $Name {
46            type Error = VaultError;
47            fn try_from(s: &str) -> Result<Self, Self::Error> {
48                Self::new(s)
49            }
50        }
51
52        impl<'de> Deserialize<'de> for $Name {
53            fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
54                let s = String::deserialize(d)?;
55                Self::new(s).map_err(serde::de::Error::custom)
56            }
57        }
58
59        impl std::fmt::Display for $Name {
60            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61                f.write_str(&self.0)
62            }
63        }
64
65        impl AsRef<str> for $Name {
66            fn as_ref(&self) -> &str {
67                &self.0
68            }
69        }
70
71        impl std::borrow::Borrow<str> for $Name {
72            fn borrow(&self) -> &str {
73                &self.0
74            }
75        }
76
77        impl From<$Name> for String {
78            fn from(p: $Name) -> Self {
79                p.0
80            }
81        }
82    };
83}
84
85/// A Vault mount path like "secret" or "transit"
86///
87/// Validated: non-empty, no leading/trailing slashes, no `..` traversal,
88/// no null bytes, no percent-encoded special characters, no control chars
89#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
90pub struct MountPath(String);
91vault_path_type!(MountPath, "mount path");
92
93/// A secret path within a mount, using the same validation as MountPath
94///
95/// Exists as a distinct type for semantic clarity at call sites
96#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
97pub struct SecretPath(String);
98vault_path_type!(SecretPath, "secret path");