use std::{
fmt::{self, Display, Formatter},
path::PathBuf,
};
use indexmap::IndexMap;
use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
use crate::{Extensions, ListOrMap, MapKey, Resource, StringOrNumber};
impl From<Secret> for Resource<Secret> {
fn from(value: Secret) -> Self {
Self::Compose(value)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Secret {
#[serde(flatten)]
pub source: Source,
#[serde(default, skip_serializing_if = "ListOrMap::is_empty")]
pub labels: ListOrMap,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub driver: Option<String>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub driver_opts: IndexMap<MapKey, StringOrNumber>,
#[serde(flatten)]
pub extensions: Extensions,
}
impl From<Source> for Secret {
fn from(source: Source) -> Self {
Self {
source,
labels: ListOrMap::default(),
driver: None,
driver_opts: IndexMap::default(),
extensions: Extensions::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Source {
File(PathBuf),
Environment(String),
}
impl Source {
const NAME: &'static str = "Source";
}
impl From<PathBuf> for Source {
fn from(value: PathBuf) -> Self {
Self::File(value)
}
}
#[derive(Debug, Clone, Copy)]
enum SourceField {
File,
Environment,
}
impl SourceField {
const fn as_str(self) -> &'static str {
match self {
Self::File => "file",
Self::Environment => "environment",
}
}
}
impl Display for SourceField {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
macro_rules! format_fields {
($args:literal) => {
format_args!($args, SourceField::File, SourceField::Environment,)
};
}
impl Serialize for Source {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut state = serializer.serialize_struct(Self::NAME, 1)?;
match self {
Self::File(source) => state.serialize_field(SourceField::File.as_str(), source)?,
Self::Environment(source) => {
state.serialize_field(SourceField::Environment.as_str(), source)?;
}
}
state.end()
}
}
impl<'de> Deserialize<'de> for Source {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let SourceFlat { file, environment } = SourceFlat::deserialize(deserializer)?;
match (file, environment) {
(Some(file), None) => Ok(file.into()),
(None, Some(environment)) => Ok(Self::Environment(environment)),
(None, None) => Err(de::Error::custom(format_fields!(
"missing required field `{}` or `{}`"
))),
(Some(_), Some(_)) => Err(de::Error::custom(format_fields!(
"can only set one of `{}` or `{}`"
))),
}
}
}
#[derive(Deserialize)]
#[serde(
rename = "Source",
expecting = "a struct with a `file` or `environment` field"
)]
struct SourceFlat {
#[serde(default)]
file: Option<PathBuf>,
#[serde(default)]
environment: Option<String>,
}