use super::account::{UserAccountArgs, UserAccountData};
use crate::error::UserStateError;
use candid::Principal;
use ic_cdk::export::{candid::CandidType, serde::Deserialize};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
#[derive(Debug, CandidType, Deserialize, Default, Clone)]
pub struct UserData {
password: Option<u64>,
profile: UserProfile,
pub name: String,
pub email: String,
pub balance: u128,
pub accounts: Vec<UserAccountData>,
pub settings: HashMap<String, UserDataSettingValue>,
}
#[derive(Debug, CandidType, Deserialize, Default, Clone)]
pub struct UserProfile {
pub full_name: Option<String>,
pub address: Option<String>,
pub phone_number: Option<String>,
pub attributes: HashMap<String, String>,
}
impl UserData {
pub fn new(user_args: UserDataArgs, account_args: UserAccountArgs) -> Self {
let mut user_data = UserData::default();
user_data.update(user_args);
user_data.accounts = Vec::with_capacity(256);
let name = if let Some(name) = account_args.name {
name
} else {
"Account 0".to_string()
};
user_data
.accounts
.insert(0, UserAccountData::new(account_args.public_key, name));
user_data
}
pub fn update(&mut self, args: UserDataArgs) -> UserDataArgs {
if let Some(name) = args.name {
self.name = name;
}
if let Some(email) = args.email {
self.email = email;
}
if let Some(balance) = args.balance {
self.balance = balance;
}
if let Some(settings) = args.settings {
for (key, value) in settings {
self.settings.insert(key, value);
}
}
if let Some(profile) = args.profile {
self.update_profile(profile);
}
if let Some(password) = args.password {
self.password = Some(hash_password(&password));
}
UserDataArgs {
name: Some(self.name.clone()),
email: Some(self.email.clone()),
balance: Some(self.balance),
settings: Some(self.settings.clone()),
profile: Some(UserProfileArgs {
full_name: self.profile.full_name.clone(),
address: self.profile.address.clone(),
phone_number: self.profile.phone_number.clone(),
attributes: Some(self.profile.attributes.clone()),
}),
password: None,
}
}
pub fn update_profile(&mut self, args: UserProfileArgs) -> UserProfileArgs {
if let Some(full_name) = args.full_name {
self.profile.full_name = Some(full_name);
}
if let Some(account) = args.address {
self.profile.address = Some(account);
}
if let Some(phone_number) = args.phone_number {
self.profile.phone_number = Some(phone_number);
}
if let Some(attributes) = args.attributes {
for (key, value) in attributes {
self.profile.attributes.insert(key, value);
}
}
UserProfileArgs {
full_name: self.profile.full_name.clone(),
address: self.profile.address.clone(),
phone_number: self.profile.phone_number.clone(),
attributes: Some(self.profile.attributes.clone()),
}
}
pub fn create_account(
&mut self,
args: UserAccountArgs,
) -> Result<UserAccountData, UserStateError> {
let key = self.get_new_key()?;
let public_key = args.public_key;
let name = if let Some(name) = args.name {
name
} else {
format!("Account {}", key)
};
let account_data = UserAccountData::new(public_key, name);
self.accounts.insert(key, account_data.clone());
Ok(account_data)
}
pub fn get_new_key(&self) -> Result<usize, UserStateError> {
if self.accounts.len() > 255 {
return Err(UserStateError::AccountLimitReached);
}
let key = self.accounts.len();
Ok(key)
}
pub fn get_derivation_path(
&self,
principal: Principal,
key: u8,
) -> Result<Vec<u8>, UserStateError> {
if key as usize > self.accounts.len() {
return Err(UserStateError::InvalidAccountKey);
}
let mut derivation_path = principal.as_slice().to_vec();
derivation_path.push(key);
Ok(derivation_path)
}
pub fn get_key_is_valid(&self, key: usize) -> Result<(), UserStateError> {
if key >= self.accounts.len() {
return Err(UserStateError::InvalidAccountKey);
}
Ok(())
}
pub fn get_account(&self, key: usize) -> Result<UserAccountData, UserStateError> {
self.get_key_is_valid(key)?;
Ok(self.accounts[key].clone())
}
pub fn get_account_mut(&mut self, key: usize) -> Result<&mut UserAccountData, UserStateError> {
self.get_key_is_valid(key)?;
Ok(&mut self.accounts[key])
}
pub fn get_accounts(&self) -> &Vec<UserAccountData> {
&self.accounts
}
pub fn set_password(&mut self, password: &str) -> Result<(), UserStateError> {
let hashed_password = hash_password(password);
self.password = Some(hashed_password);
Ok(())
}
pub fn check_password(&self, password: &str) -> Result<bool, UserStateError> {
let input_hash = hash_password(password);
if let Some(password_hash) = self.password {
Ok(input_hash == password_hash)
} else {
Err(UserStateError::PasswordNotSet)
}
}
pub fn get_setting(&self, key: &str) -> Result<UserDataSettingValue, UserStateError> {
if let Some(value) = self.settings.get(key) {
Ok(value.clone())
} else {
Err(UserStateError::SettingNotFound)
}
}
pub fn get_setting_mut(
&mut self,
key: &str,
) -> Result<&mut UserDataSettingValue, UserStateError> {
if let Some(value) = self.settings.get_mut(key) {
Ok(value)
} else {
Err(UserStateError::SettingNotFound)
}
}
pub fn get_settings(&self) -> &HashMap<String, UserDataSettingValue> {
&self.settings
}
pub fn set_setting(&mut self, key: String, value: UserDataSettingValue) -> () {
let value = value.into();
self.settings.insert(key, value);
}
pub fn remove_setting(&mut self, key: &str) -> Result<bool, UserStateError> {
if self.settings.remove(key).is_some() {
Ok(true)
} else {
Err(UserStateError::SettingNotFound)
}
}
pub fn update_settings(&mut self, new_settings: HashMap<String, UserDataSettingValue>) {
self.settings = new_settings;
}
}
fn hash_password(password: &str) -> u64 {
let mut hasher = DefaultHasher::new();
password.hash(&mut hasher);
hasher.finish()
}
#[derive(CandidType, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct UserDataArgs {
pub name: Option<String>,
pub email: Option<String>,
pub balance: Option<u128>,
pub password: Option<String>,
pub profile: Option<UserProfileArgs>,
pub settings: Option<HashMap<String, UserDataSettingValue>>,
}
#[derive(Debug, CandidType, Clone, Default, Deserialize, PartialEq)]
pub struct UserProfileArgs {
pub full_name: Option<String>,
pub address: Option<String>,
pub phone_number: Option<String>,
pub attributes: Option<HashMap<String, String>>,
}
#[derive(Debug, CandidType, Deserialize, Clone, PartialEq)]
pub enum UserDataSettingValue {
StringValue(String),
NumberValue(u128),
FloatValue(f64),
BoolValue(bool),
}
impl From<String> for UserDataSettingValue {
fn from(value: String) -> Self {
UserDataSettingValue::StringValue(value)
}
}
impl From<f64> for UserDataSettingValue {
fn from(value: f64) -> Self {
UserDataSettingValue::FloatValue(value)
}
}
impl From<u128> for UserDataSettingValue {
fn from(value: u128) -> Self {
UserDataSettingValue::NumberValue(value)
}
}
impl From<bool> for UserDataSettingValue {
fn from(value: bool) -> Self {
UserDataSettingValue::BoolValue(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_user_data_arbitrary( user_args: UserDataArgs,account_args: UserAccountArgs) {
let user_data = UserData::new(user_args.clone(), account_args);
assert_eq!(user_data.name, user_args.name.unwrap_or_default());
assert_eq!(user_data.email, user_args.email.unwrap_or_default());
assert_eq!(user_data.balance, user_args.balance.unwrap_or_default());
}
#[test]
fn test_create_account(account_args: UserAccountArgs, account_args2: UserAccountArgs, profile: UserProfileArgs) {
let args = UserDataArgs {
name: Some("test".to_string()),
email: Some("test@example.com".to_string()),
balance: Some(0),
password: Some("password".to_string()),
profile: Some(profile),
settings: None,
};
let mut user_data = UserData::new(args, account_args);
let account_data = user_data.create_account(account_args2.clone()).unwrap();
assert_eq!(account_data.public_key, account_args2.public_key);
}
#[test]
fn test_get_account(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
let mut user_data = UserData::new(user_args, account_args.clone());
let account_data = user_data.get_account(0).unwrap();
assert_eq!(account_data.public_key, account_args.public_key);
let mut account_data2 = user_data.get_account_mut(0).unwrap();
account_data2.name = name.clone();
assert_eq!(user_data.get_account(0).unwrap().name, name);
}
#[test]
fn test_update_account(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
let mut user_data = UserData::new(user_args, account_args.clone());
let mut account_data = user_data.get_account_mut(0).unwrap();
account_data.name = name.clone();
assert_eq!(user_data.get_account(0).unwrap().name, name);
}
#[test]
fn test_update_settings(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
let mut user_data = UserData::new(user_args, account_args.clone());
let mut settings = HashMap::new();
settings.insert("name".to_string(), name.clone().into());
user_data.update_settings(settings);
assert_eq!(user_data.settings.get("name").unwrap(), &name.into());
}
#[test]
fn test_update_password(user_args: UserDataArgs, account_args: UserAccountArgs, password: String) {
let mut user_data = UserData::new(user_args, account_args.clone());
user_data.set_password(&password.clone()).unwrap();
assert!(user_data.check_password(&password).unwrap());
}
#[test]
fn test_update_email(user_args: UserDataArgs, account_args: UserAccountArgs, email: String) {
let mut user_data = UserData::new(user_args, account_args.clone());
user_data.email = email.clone();
assert_eq!(user_data.email, email);
}
#[test]
fn test_update_name(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
let mut user_data = UserData::new(user_args, account_args.clone());
user_data.name = name.clone();
assert_eq!(user_data.name, name);
}
#[test]
fn test_update_balance(user_args: UserDataArgs, account_args: UserAccountArgs, balance: u128) {
let mut user_data = UserData::new(user_args, account_args.clone());
user_data.balance = balance;
assert_eq!(user_data.balance, balance);
}
#[test]
fn test_update_profile(user_args: UserDataArgs, account_args: UserAccountArgs, name: String) {
let mut user_data = UserData::new(user_args, account_args.clone());
let mut profile = UserProfileArgs::default();
profile.full_name = Some(name.clone());
profile.address = Some("account".to_string());
user_data.update_profile(profile);
assert_eq!(user_data.profile.full_name.unwrap(), name);
}
#[test]
fn test_update_profile_attributes(user_args: UserDataArgs, account_args: UserAccountArgs, key: String, value: String) {
let mut user_data = UserData::new(user_args, account_args);
let mut profile = UserProfileArgs::default();
let mut attributes = HashMap::new();
attributes.insert(key.clone(), value.clone());
profile.attributes = Some(attributes);
user_data.update_profile(profile);
assert_eq!(user_data.profile.attributes.get(&key).unwrap(), &value);
}
}
}