use std::{
fmt::{Display, Formatter},
net::IpAddr,
};
use derive_more::From;
use indexmap::IndexMap;
use orion_variate::vars::ValueType;
use serde_derive::{Deserialize, Serialize};
use unicase::UniCase;
use crate::types::{UniCaseMap, UniString};
const SECRET_MASK: &str = "********";
pub trait ToUniCase<T> {
fn to_unicase(self) -> UniCase<T>;
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct SecValue<T> {
is_secret: bool,
value: T,
}
impl<T> SecValue<T> {
pub fn value(&self) -> &T {
&self.value
}
pub fn is_secret(&self) -> bool {
self.is_secret
}
}
impl<T> PartialOrd for SecValue<T>
where
T: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.value.partial_cmp(&other.value)
}
}
pub trait NoSecConv<T> {
fn no_sec(self) -> T;
}
pub trait SecConv {
#[must_use]
fn to_nor(self) -> Self;
#[must_use]
fn to_sec(self) -> Self;
}
impl<T> SecConv for SecValue<T> {
fn to_nor(mut self) -> Self {
self.is_secret = false;
self
}
fn to_sec(mut self) -> Self {
self.is_secret = true;
self
}
}
impl<T> SecConv for Vec<SecValue<T>> {
fn to_nor(mut self) -> Self {
for x in self.iter_mut() {
x.is_secret = false;
}
self
}
fn to_sec(mut self) -> Self {
for x in self.iter_mut() {
x.is_secret = true;
}
self
}
}
impl<T> SecConv for UniCaseMap<SecValue<T>> {
fn to_nor(mut self) -> Self {
self.iter_mut().for_each(|(_, x)| x.is_secret = false);
self
}
fn to_sec(mut self) -> Self {
self.iter_mut().for_each(|(_, x)| x.is_secret = true);
self
}
}
impl SecFrom<IndexMap<String, ValueType>> for SecValueType {
fn sec_from(value: IndexMap<String, ValueType>) -> Self {
SecValueType::Obj(
value
.into_iter()
.map(|(k, v)| (UniString::from(k), SecValueType::sec_from(v)))
.collect(),
)
}
fn nor_from(value: IndexMap<String, ValueType>) -> Self {
SecValueType::Obj(
value
.into_iter()
.map(|(k, v)| (UniString::from(k), SecValueType::nor_from(v)))
.collect(),
)
}
}
impl SecFrom<Vec<ValueType>> for SecValueType {
fn sec_from(value: Vec<ValueType>) -> Self {
SecValueType::List(value.into_iter().map(SecValueType::sec_from).collect())
}
fn nor_from(value: Vec<ValueType>) -> Self {
SecValueType::List(value.into_iter().map(SecValueType::nor_from).collect())
}
}
impl SecFrom<ValueType> for SecValueType {
fn nor_from(value: ValueType) -> Self {
match value {
ValueType::String(v) => SecValueType::nor_from(v),
ValueType::Bool(v) => SecValueType::nor_from(v),
ValueType::Number(v) => SecValueType::nor_from(v),
ValueType::Float(v) => SecValueType::nor_from(v),
ValueType::Ip(v) => SecValueType::nor_from(v),
ValueType::Obj(v) => SecValueType::nor_from(v),
ValueType::List(v) => SecValueType::nor_from(v),
}
}
fn sec_from(value: ValueType) -> Self {
match value {
ValueType::String(v) => SecValueType::sec_from(v),
ValueType::Bool(v) => SecValueType::sec_from(v),
ValueType::Number(v) => SecValueType::sec_from(v),
ValueType::Float(v) => SecValueType::sec_from(v),
ValueType::Ip(v) => SecValueType::sec_from(v),
ValueType::Obj(v) => SecValueType::sec_from(v),
ValueType::List(v) => SecValueType::sec_from(v),
}
}
}
pub trait SecFrom<T> {
fn sec_from(value: T) -> Self;
fn nor_from(value: T) -> Self;
}
impl<T> SecFrom<T> for SecValue<T> {
fn sec_from(value: T) -> Self {
Self {
is_secret: true,
value,
}
}
fn nor_from(value: T) -> Self {
Self {
is_secret: false,
value,
}
}
}
impl<T> Display for SecValue<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.is_secret {
write!(f, "{}", SECRET_MASK)
} else {
write!(f, "{}", self.value)
}
}
}
pub type SecString = SecValue<String>;
pub type SecBool = SecValue<bool>;
pub type SecIpAddr = SecValue<IpAddr>;
pub type SecU64 = SecValue<u64>;
pub type SecF64 = SecValue<f64>;
pub type SecValueObj = UniCaseMap<SecValueType>;
pub type SecValueVec = Vec<SecValueType>;
impl Display for SecValueType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SecValueType::String(v) => write!(f, "{v}"),
SecValueType::Bool(v) => write!(f, "{v}"),
SecValueType::Number(v) => write!(f, "{v}"),
SecValueType::Float(v) => write!(f, "{v}"),
SecValueType::Ip(v) => write!(f, "{v}"),
SecValueType::Obj(v) => write!(f, "obj[{}]", v.len()),
SecValueType::List(v) => write!(f, "list[{}]", v.len()),
}
}
}
#[derive(Debug, Clone, PartialEq, From, Serialize, Deserialize)]
pub enum SecValueType {
String(SecString),
Bool(SecBool),
Number(SecU64),
Float(SecF64),
Ip(SecIpAddr),
Obj(SecValueObj),
List(SecValueVec),
}
impl PartialOrd for SecValueType {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(SecValueType::String(f), SecValueType::String(s)) => f.partial_cmp(s),
(SecValueType::Bool(f), SecValueType::Bool(s)) => f.partial_cmp(s),
(SecValueType::Number(f), SecValueType::Number(s)) => f.partial_cmp(s),
(SecValueType::Float(f), SecValueType::Float(s)) => f.partial_cmp(s),
(SecValueType::Ip(f), SecValueType::Ip(s)) => f.partial_cmp(s),
_ => None,
}
}
}
impl<T> SecFrom<T> for SecValueType
where
SecValue<T>: SecFrom<T>,
SecValueType: From<SecValue<T>>,
{
fn sec_from(value: T) -> Self {
SecValueType::from(SecValue::sec_from(value))
}
fn nor_from(value: T) -> Self {
SecValueType::from(SecValue::nor_from(value))
}
}
impl SecValueType {
#[must_use]
pub fn to_nor(self) -> Self {
match self {
SecValueType::String(v) => Self::String(v.to_nor()),
SecValueType::Bool(v) => Self::Bool(v.to_nor()),
SecValueType::Number(v) => Self::Number(v.to_nor()),
SecValueType::Float(v) => Self::Float(v.to_nor()),
SecValueType::Ip(v) => Self::Ip(v.to_nor()),
SecValueType::Obj(v) => Self::Obj(v.to_nor()),
SecValueType::List(v) => Self::List(v.to_nor()),
}
}
#[must_use]
pub fn to_sec(self) -> Self {
match self {
SecValueType::String(v) => Self::String(v.to_sec()),
SecValueType::Bool(v) => Self::Bool(v.to_sec()),
SecValueType::Number(v) => Self::Number(v.to_sec()),
SecValueType::Float(v) => Self::Float(v.to_sec()),
SecValueType::Ip(v) => Self::Ip(v.to_sec()),
SecValueType::Obj(v) => Self::Obj(v.to_sec()),
SecValueType::List(v) => Self::List(v.to_sec()),
}
}
}
impl SecConv for Vec<SecValueType> {
fn to_nor(self) -> Self {
self.into_iter().map(SecValueType::to_nor).collect()
}
fn to_sec(self) -> Self {
self.into_iter().map(SecValueType::to_sec).collect()
}
}
impl SecConv for UniCaseMap<SecValueType> {
fn to_nor(self) -> Self {
self.into_iter().map(|(k, x)| (k, x.to_nor())).collect()
}
fn to_sec(self) -> Self {
self.into_iter().map(|(k, x)| (k, x.to_sec())).collect()
}
}
impl NoSecConv<ValueType> for SecValueType {
fn no_sec(self) -> ValueType {
match self {
SecValueType::String(v) => ValueType::from(v.value),
SecValueType::Bool(v) => ValueType::from(v.value),
SecValueType::Number(v) => ValueType::from(v.value),
SecValueType::Float(v) => ValueType::from(v.value),
SecValueType::Ip(v) => ValueType::from(v.value),
SecValueType::Obj(v) => ValueType::from(v.no_sec()),
SecValueType::List(v) => ValueType::from(v.no_sec()),
}
}
}
impl NoSecConv<Vec<ValueType>> for Vec<SecValueType> {
fn no_sec(self) -> Vec<ValueType> {
self.into_iter().map(NoSecConv::no_sec).collect()
}
}
impl NoSecConv<IndexMap<String, ValueType>> for UniCaseMap<SecValueType> {
fn no_sec(self) -> IndexMap<String, ValueType> {
self.into_iter()
.map(|(k, x)| (k.as_str().to_string(), x.no_sec()))
.collect()
}
}
pub trait ValueGetter<T> {
fn value_get(&self, path: &str) -> Option<T>;
}
impl ValueGetter<SecValueType> for SecValueObj {
fn value_get(&self, path: &str) -> Option<SecValueType> {
let parts: Vec<&str> = path.split('.').collect();
if parts.is_empty() {
return None;
}
let mut current_value: Option<&SecValueType> = None;
let mut current_obj: Option<&SecValueObj> = Some(self);
for part in parts {
if let Some((key, index)) = parse_index(part) {
let obj = current_obj.or_else(|| current_value.and_then(as_obj))?;
let value = obj.get(&UniString::from(key.to_string()))?;
let list = match value {
SecValueType::List(list) => list,
_ => return None,
};
let item = list.get(index)?;
current_value = Some(item);
current_obj = as_obj(item);
} else {
let obj = current_obj.or_else(|| current_value.and_then(as_obj))?;
let found = obj.get(&UniString::from(part.to_string()))?;
current_value = Some(found);
current_obj = as_obj(found);
}
}
current_value.cloned()
}
}
fn parse_index(part: &str) -> Option<(&str, usize)> {
let start = part.find('[')?;
if !part.ends_with(']') {
return None;
}
let key = &part[..start];
let index_str = &part[start + 1..part.len() - 1];
let index = index_str.parse::<usize>().ok()?;
Some((key, index))
}
fn as_obj(value: &SecValueType) -> Option<&SecValueObj> {
match value {
SecValueType::Obj(map) => Some(map),
_ => None,
}
}
#[cfg(test)]
mod tests {
use crate::types::UniCaseMap;
use super::*;
use std::net::IpAddr;
use std::str::FromStr;
#[test]
fn test_sec_value_display() {
let secret_str = SecString::sec_from("password".to_string());
assert_eq!(format!("{secret_str}"), "********");
let public_str = SecString::nor_from("username".to_string());
assert_eq!(format!("{public_str}"), "username");
}
#[test]
fn test_obj_get_with_array() {
let mut obj = UniCaseMap::new();
let list = vec![
SecValueType::nor_from(42u64),
SecValueType::sec_from("secret".to_string()),
];
obj.insert("A".into(), SecValueType::List(list));
let mut nested_obj = UniCaseMap::new();
nested_obj.insert("rust".into(), SecValueType::nor_from("awesome".to_string()));
obj.insert("B".into(), SecValueType::Obj(nested_obj));
assert_eq!(obj.value_get("A[0]"), Some(SecValueType::nor_from(42u64)));
assert_eq!(
obj.value_get("A[1]"),
Some(SecValueType::sec_from("secret".to_string()))
);
assert_eq!(
obj.value_get("B.rust"),
Some(SecValueType::nor_from("awesome".to_string()))
);
assert_eq!(obj.value_get("B.rust.not_exists"), None);
assert_eq!(obj.value_get("A[invalid]"), None);
assert_eq!(obj.value_get("A[2]"), None); }
#[test]
fn test_obj_get_with_dot_notation() {
let mut root = UniCaseMap::new();
let mut nested_c = UniCaseMap::new();
nested_c.insert("c".into(), SecValueType::nor_from("value_c".to_string()));
let mut nested_b = UniCaseMap::new();
nested_b.insert("b".into(), SecValueType::Obj(nested_c.clone()));
nested_b.insert("d".into(), SecValueType::nor_from(42u64));
root.insert("a".into(), SecValueType::Obj(nested_b));
root.insert("x".into(), SecValueType::nor_from("value_x".to_string()));
assert_eq!(
root.value_get("x"),
Some(SecValueType::nor_from("value_x".to_string()))
);
if let Some(SecValueType::Obj(map)) = root.value_get("a") {
assert_eq!(
map.value_get("b"),
Some(SecValueType::from(nested_c)) );
} else {
panic!("Expected 'a' to be an object");
}
assert_eq!(
root.value_get("a.b.c"),
Some(SecValueType::nor_from("value_c".to_string()))
);
assert_eq!(root.value_get("a.d.c"), None);
assert_eq!(root.value_get("not_exists"), None);
assert_eq!(root.value_get("a.not_exists"), None);
assert_eq!(root.value_get("a.b.not_exists"), None);
}
#[test]
fn test_sec_value_type_conversions() {
let secret_num = SecValueType::sec_from(42u64);
assert!(matches!(secret_num, SecValueType::Number(v) if v.is_secret && v.value == 42));
let public_bool = SecValueType::nor_from(true);
assert!(matches!(public_bool, SecValueType::Bool(v) if !v.is_secret && v.value));
let ip = IpAddr::from_str("192.168.1.1").unwrap();
let secret_ip = SecValueType::sec_from(ip);
assert!(matches!(secret_ip, SecValueType::Ip(v) if v.is_secret));
}
#[test]
fn test_bool_secret_flip() {
let bool_value = SecValueType::nor_from(true);
let secret_bool = bool_value.clone().to_sec();
assert!(matches!(secret_bool, SecValueType::Bool(ref v) if v.is_secret()));
let public_bool = secret_bool.to_nor();
assert!(matches!(public_bool, SecValueType::Bool(ref v) if !v.is_secret()));
}
#[test]
fn test_nested_conversions() {
let mut obj = IndexMap::new();
obj.insert("key".to_string(), ValueType::String("value".to_string()));
let secret_obj = SecValueType::sec_from(obj.clone());
if let SecValueType::Obj(_map) = secret_obj {
} else {
panic!("Expected Obj variant");
}
let list = vec![ValueType::Bool(true), ValueType::Number(10)];
let public_list = SecValueType::nor_from(list.clone());
if let SecValueType::List(vec) = public_list {
assert!(!vec[0].is_secret());
} else {
panic!("Expected List variant");
}
}
#[test]
fn test_sec_conv_traits() {
let values = vec![SecValueType::nor_from(10u64), SecValueType::sec_from(20u64)];
let secret_values = values.clone().to_sec();
for val in secret_values {
assert!(val.is_secret());
}
let public_values = values.to_nor();
for val in public_values {
assert!(!val.is_secret());
}
}
#[test]
fn test_no_sec_conv() {
let secret_str = SecValueType::sec_from("secret".to_string());
let normal_str: ValueType = secret_str.no_sec();
assert_eq!(normal_str, ValueType::String("secret".to_string()));
let mut obj = UniCaseMap::new();
obj.insert("nested".into(), SecValueType::nor_from(100u64));
let sec_obj = SecValueType::Obj(obj);
if let ValueType::Obj(normal_obj) = sec_obj.no_sec() {
assert_eq!(normal_obj["NESTED"], ValueType::Number(100));
}
}
#[test]
fn test_partial_cmp_mismatched_variants() {
let number = SecValueType::nor_from(1u64);
let string = SecValueType::nor_from("one".to_string());
assert_eq!(number.partial_cmp(&string), None);
let mut obj = UniCaseMap::new();
obj.insert("nested".into(), SecValueType::nor_from(2u64));
let list = vec![SecValueType::nor_from(3u64)];
assert_eq!(
SecValueType::Obj(obj).partial_cmp(&SecValueType::List(list)),
None
);
}
trait SecretCheck {
fn is_secret(&self) -> bool;
}
impl SecretCheck for SecValueType {
fn is_secret(&self) -> bool {
match self {
SecValueType::String(v) => v.is_secret,
SecValueType::Bool(v) => v.is_secret,
SecValueType::Number(v) => v.is_secret,
SecValueType::Float(v) => v.is_secret,
SecValueType::Ip(v) => v.is_secret,
SecValueType::Obj(_) => false, SecValueType::List(_) => false, }
}
}
}