use crate::{ErrorCode, GResult, GreenticError};
use alloc::{format, string::String, vec::Vec};
use core::ops::Deref;
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct SecretKey(String);
impl SecretKey {
pub fn new(key: impl Into<String>) -> GResult<Self> {
let key = key.into();
Self::parse(&key).map_err(|err| {
GreenticError::new(
ErrorCode::InvalidInput,
format!("invalid secret key: {err}"),
)
})
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn parse(value: &str) -> Result<Self, SecretKeyError> {
if value.is_empty() {
return Err(SecretKeyError::Empty);
}
if value.starts_with('/') {
return Err(SecretKeyError::LeadingSlash);
}
for c in value.chars() {
if !(c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-' | '/')) {
return Err(SecretKeyError::InvalidChar { c });
}
}
if value.split('/').any(|segment| segment == "..") {
return Err(SecretKeyError::DotDotSegment);
}
Ok(Self(value.to_owned()))
}
}
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum SecretKeyError {
#[error("secret key must not be empty")]
Empty,
#[error("secret key must not start with '/'")]
LeadingSlash,
#[error("secret key must not contain '..' segments")]
DotDotSegment,
#[error("secret key contains invalid character '{c}'")]
InvalidChar {
c: char,
},
}
impl Deref for SecretKey {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<String> for SecretKey {
fn from(key: String) -> Self {
Self(key)
}
}
impl From<&str> for SecretKey {
fn from(key: &str) -> Self {
Self(key.to_owned())
}
}
impl From<SecretKey> for String {
fn from(key: SecretKey) -> Self {
key.0
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for SecretKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
SecretKey::parse(&value).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct SecretScope {
pub env: String,
pub tenant: String,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub team: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub enum SecretFormat {
Bytes,
Text,
Json,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
pub struct SecretRequirement {
pub key: SecretKey,
#[cfg_attr(
feature = "serde",
serde(default = "SecretRequirement::default_required")
)]
pub required: bool,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub description: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub scope: Option<SecretScope>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub format: Option<SecretFormat>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub schema: Option<serde_json::Value>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub examples: Vec<String>,
}
impl Default for SecretRequirement {
fn default() -> Self {
Self {
key: SecretKey::default(),
required: true,
description: None,
scope: None,
format: None,
schema: None,
examples: Vec::new(),
}
}
}
impl SecretRequirement {
const fn default_required() -> bool {
true
}
}