use crate::seal::ConfigFormat;
use serde::de::DeserializeOwned;
use zeroize::Zeroizing;
use crate::error::{Error, code};
pub struct SecretValue {
pub(crate) name: String,
pub(crate) version: u64,
pub(crate) value: Zeroizing<Vec<u8>>,
}
impl SecretValue {
pub fn name(&self) -> &str {
&self.name
}
pub fn version(&self) -> u64 {
self.version
}
pub fn len(&self) -> usize {
self.value.len()
}
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
pub fn expose(&self) -> &[u8] {
&self.value
}
}
impl std::fmt::Debug for SecretValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SecretValue")
.field("name", &self.name)
.field("version", &self.version)
.field("size", &self.value.len())
.finish()
}
}
#[derive(Debug)]
pub struct SecretSet {
pub(crate) values: Vec<SecretValue>,
}
impl SecretSet {
pub fn get(&self, name: &str) -> Option<&SecretValue> {
self.values.iter().find(|v| v.name == name)
}
pub fn iter(&self) -> impl Iterator<Item = &SecretValue> {
self.values.iter()
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
}
impl IntoIterator for SecretSet {
type Item = SecretValue;
type IntoIter = std::vec::IntoIter<SecretValue>;
fn into_iter(self) -> Self::IntoIter {
self.values.into_iter()
}
}
pub struct ConfigDocument {
pub(crate) name: String,
pub(crate) version: u64,
pub(crate) format: ConfigFormat,
pub(crate) body: Zeroizing<Vec<u8>>,
}
impl ConfigDocument {
pub fn name(&self) -> &str {
&self.name
}
pub fn version(&self) -> u64 {
self.version
}
pub fn format(&self) -> ConfigFormat {
self.format
}
pub fn len(&self) -> usize {
self.body.len()
}
pub fn is_empty(&self) -> bool {
self.body.is_empty()
}
pub fn expose(&self) -> &[u8] {
&self.body
}
pub fn text(&self) -> Result<&str, Error> {
std::str::from_utf8(&self.body)
.map_err(|_| Error::local(code::NOT_TEXT, format!("config {} is not UTF-8", self.name)))
}
pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T, Error> {
match self.format {
ConfigFormat::Toml => toml::from_str::<T>(self.text()?).map_err(|e| {
let line = e.span().map_or(1, |s| {
self.body[..s.start.min(self.body.len())]
.iter()
.filter(|b| **b == b'\n')
.count()
+ 1
});
Error::local(
code::INVALID_CONFIG,
format!(
"config {}, line {line}: the toml does not fit the type",
self.name
),
)
}),
ConfigFormat::Json => serde_json::from_slice::<T>(&self.body).map_err(|e| {
Error::local(
code::INVALID_CONFIG,
format!(
"config {}, line {}: the json does not fit the type",
self.name,
e.line()
),
)
}),
other => Err(Error::local(
code::UNSUPPORTED_FORMAT,
format!(
"config {} is {other}; only toml and json deserialize, so parse it from expose()",
self.name
),
)),
}
}
}
impl std::fmt::Debug for ConfigDocument {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConfigDocument")
.field("name", &self.name)
.field("version", &self.version)
.field("format", &self.format)
.field("size", &self.body.len())
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Expect {
Version(u64),
Absent,
}
pub struct NewConfig {
pub(crate) format: ConfigFormat,
pub(crate) body: Zeroizing<Vec<u8>>,
pub(crate) allow_literals: bool,
pub(crate) expect: Option<Expect>,
}
impl NewConfig {
pub fn new(format: ConfigFormat, body: impl Into<Vec<u8>>) -> NewConfig {
NewConfig {
format,
body: Zeroizing::new(body.into()),
allow_literals: false,
expect: None,
}
}
pub fn toml(body: impl Into<Vec<u8>>) -> NewConfig {
NewConfig::new(ConfigFormat::Toml, body)
}
pub fn json(body: impl Into<Vec<u8>>) -> NewConfig {
NewConfig::new(ConfigFormat::Json, body)
}
pub fn yaml(body: impl Into<Vec<u8>>) -> NewConfig {
NewConfig::new(ConfigFormat::Yaml, body)
}
pub fn text(body: impl Into<Vec<u8>>) -> NewConfig {
NewConfig::new(ConfigFormat::Text, body)
}
pub fn allow_literals(mut self) -> NewConfig {
self.allow_literals = true;
self
}
pub fn expect_version(mut self, version: u64) -> NewConfig {
self.expect = Some(Expect::Version(version));
self
}
pub fn expect_absent(mut self) -> NewConfig {
self.expect = Some(Expect::Absent);
self
}
}
impl std::fmt::Debug for NewConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NewConfig")
.field("format", &self.format)
.field("size", &self.body.len())
.field("allow_literals", &self.allow_literals)
.field("expect", &self.expect)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Entry {
pub name: String,
pub version: u64,
pub updated_at: i64,
pub size: u32,
pub deleted: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Expiry {
pub token_expires_at: Option<i64>,
pub vault_expires_at: Option<i64>,
}