use crate::utils;
use chrono::{DateTime, Utc};
use semver::Version;
use serde::ser::SerializeSeq;
use serde::{Serialize, Serializer};
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::ops::Index;
#[derive(Clone, Debug)]
pub enum UserValue {
String(String),
Int(i64),
UInt(u64),
Float(f64),
DateTime(DateTime<Utc>),
StringVec(Vec<String>),
SemVer(Version),
}
#[derive(Serialize, Clone, Debug)]
pub struct User {
attributes: HashMap<String, UserValue>,
}
impl User {
pub const IDENTIFIER: &'static str = "Identifier";
pub const EMAIL: &'static str = "Email";
pub const COUNTRY: &'static str = "Country";
pub fn new(identifier: &str) -> Self {
Self {
attributes: HashMap::from([(Self::IDENTIFIER.to_owned(), UserValue::from(identifier))]),
}
}
pub(crate) fn from_map(map: HashMap<String, UserValue>) -> Self {
Self { attributes: map }
}
pub fn email(mut self, email: &str) -> Self {
self.attributes.insert(Self::EMAIL.to_owned(), email.into());
self
}
pub fn country(mut self, country: &str) -> Self {
self.attributes
.insert(Self::COUNTRY.to_owned(), country.into());
self
}
pub fn custom<T: Into<UserValue>>(mut self, key: &str, value: T) -> Self {
if key == Self::IDENTIFIER || key == Self::EMAIL || key == Self::COUNTRY {
return self;
}
self.attributes.insert(key.to_owned(), value.into());
self
}
pub fn get(&self, key: &str) -> Option<&UserValue> {
self.attributes.get(key)
}
}
impl UserValue {
#![allow(clippy::cast_precision_loss)]
pub(crate) fn as_str(&self) -> (String, bool) {
match self {
UserValue::String(val) => (val.clone(), false),
UserValue::Float(val) => {
if val.is_nan() {
("NaN".to_owned(), true)
} else if val.is_infinite() && val.is_sign_positive() {
("Infinity".to_owned(), true)
} else if val.is_infinite() && val.is_sign_negative() {
("-Infinity".to_owned(), true)
} else if (1e-6..1e21).contains(&val.abs()) {
(val.to_string(), true)
} else {
let sc = format!("{val:+e}");
if val.abs() > 1.0 {
(sc.replace('e', "e+"), true)
} else {
(sc, true)
}
}
}
UserValue::SemVer(val) => (val.to_string(), true),
UserValue::Int(val) => (val.to_string(), true),
UserValue::UInt(val) => (val.to_string(), true),
UserValue::DateTime(val) => {
(((val.timestamp_millis() as f64) / 1000.0).to_string(), true)
}
UserValue::StringVec(val) => {
let ser = serde_json::to_string(val);
match ser {
Ok(val) => (val, true),
Err(_) => (String::default(), true),
}
}
}
}
#[allow(clippy::cast_precision_loss)]
pub(crate) fn as_float(&self) -> Option<f64> {
match self {
UserValue::String(val) => {
let trimmed = val.trim();
match trimmed {
"Infinity" | "+Infinity" => Some(f64::INFINITY),
"-Infinity" => Some(f64::NEG_INFINITY),
"NaN" => Some(f64::NAN),
_ => trimmed.replace(',', ".").parse().ok(),
}
}
UserValue::Int(val) => Some(*val as f64),
UserValue::UInt(val) => Some(*val as f64),
UserValue::Float(val) => Some(*val),
_ => None,
}
}
#[allow(clippy::cast_precision_loss)]
pub(crate) fn as_timestamp(&self) -> Option<f64> {
match self {
UserValue::DateTime(val) => Some((val.timestamp_millis() as f64) / 1000.0),
_ => self.as_float(),
}
}
pub(crate) fn as_semver(&self) -> Option<Version> {
match self {
UserValue::SemVer(val) => Some(val.clone()),
UserValue::String(val) => utils::parse_semver(val).ok(),
_ => None,
}
}
pub(crate) fn as_str_vec(&self) -> Option<Vec<String>> {
match self {
UserValue::StringVec(val) => Some(val.clone()),
UserValue::String(val) => {
let result = serde_json::from_str::<Vec<String>>(val);
result.ok()
}
_ => None,
}
}
}
impl Display for User {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match serde_json::to_string(&self.attributes) {
Ok(str) => f.write_str(str.as_str()),
Err(_) => f.write_str("<invalid user>"),
}
}
}
impl From<HashMap<String, UserValue>> for User {
fn from(value: HashMap<String, UserValue>) -> Self {
Self::from_map(value)
}
}
impl Index<&str> for User {
type Output = UserValue;
fn index(&self, index: &str) -> &Self::Output {
&self.attributes[index]
}
}
impl Display for UserValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UserValue::String(val) => f.write_str(val),
UserValue::Int(val) => write!(f, "{val}"),
UserValue::UInt(val) => write!(f, "{val}"),
UserValue::Float(val) => write!(f, "{val}"),
UserValue::DateTime(val) => f.write_str(val.to_string().as_str()),
UserValue::StringVec(_) => f.write_str("<vec of strings>"),
UserValue::SemVer(val) => f.write_str(val.to_string().as_str()),
}
}
}
impl Serialize for UserValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
UserValue::String(val) => serializer.serialize_str(val),
UserValue::Int(val) => serializer.serialize_i64(*val),
UserValue::UInt(val) => serializer.serialize_u64(*val),
UserValue::Float(val) => serializer.serialize_f64(*val),
UserValue::DateTime(val) => serializer.serialize_str(val.to_string().as_str()),
UserValue::StringVec(val) => {
let mut seq = serializer.serialize_seq(Some(val.len()))?;
for element in val {
seq.serialize_element(element)?;
}
seq.end()
}
UserValue::SemVer(val) => serializer.serialize_str(val.to_string().as_str()),
}
}
}
impl From<Vec<&str>> for UserValue {
fn from(value: Vec<&str>) -> Self {
let str_vec = value.iter().map(|v| (*v).to_string()).collect();
Self::StringVec(str_vec)
}
}
impl<const N: usize> From<[&str; N]> for UserValue {
fn from(arr: [&str; N]) -> Self {
arr.to_vec().into()
}
}
from_val_to_enum!(UserValue String String);
from_val_to_enum!(UserValue DateTime DateTime<Utc>);
from_val_to_enum!(UserValue SemVer Version);
from_val_to_enum!(UserValue StringVec Vec<String>);
from_val_to_enum_into!(UserValue Float f64 f32);
from_val_to_enum_into!(UserValue UInt u8 u16 u32 u64);
from_val_to_enum_into!(UserValue Int i8 i16 i32 i64);
from_val_to_enum_into!(UserValue String &str);