extern crate alloc;
use alloc::{
collections::BTreeMap,
string::{String, ToString},
vec::Vec,
};
use borsh::{BorshDeserialize, BorshSerialize};
use rialo_s_pubkey::Pubkey;
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)]
pub struct FeaturesState {
authority: Pubkey,
pub features_map: BTreeMap<String, (u64, u64)>, }
#[cfg(test)]
pub const DETERMINISTIC_TEST_KEYPAIR: &str =
"57Vqb7tHij5NhQnTgrgYXA19pC8ZVHQoCHpapSiQ8LJaeUvTcSBzKoB6CazhR6VtxmyVAbWnoeDSzD1Vm672NaKp";
impl FeaturesState {
pub fn new(authority: Pubkey) -> Self {
Self {
authority,
features_map: BTreeMap::new(),
}
}
#[cfg(test)]
pub fn new_for_test() -> Self {
use rialo_s_keypair::Keypair;
use rialo_s_signer::Signer;
let deterministic_test_pubkey: Pubkey =
Keypair::from_base58_string(DETERMINISTIC_TEST_KEYPAIR)
.try_pubkey()
.expect("Failed to get pubkey from deterministic test keypair");
Self {
authority: deterministic_test_pubkey,
features_map: BTreeMap::new(),
}
}
pub fn get_authority(&self) -> &rialo_s_pubkey::Pubkey {
&self.authority
}
pub fn set_authority(&mut self, new_authority: rialo_s_pubkey::Pubkey) {
self.authority = new_authority;
}
pub fn serialize(&self) -> Result<Vec<u8>, borsh::io::Error> {
borsh::to_vec(self)
}
pub fn deserialize(data: &[u8]) -> Result<Self, borsh::io::Error> {
borsh::from_slice(data)
}
pub fn is_active(&self, feature_name: &str, current_time: u64) -> bool {
if let Some((start_time, end_time)) = self.features_map.get(feature_name) {
current_time >= *start_time && current_time < *end_time
} else {
false
}
}
pub fn upsert(&mut self, name: String, start_time: u64, end_time: u64) -> Result<(), String> {
if start_time >= end_time {
return Err("Invalid time range: start_time must be less than end_time".to_string());
}
if !self.features_map.contains_key(&name)
&& self.features_map.len() >= crate::MAX_FEATURE_COUNT
{
return Err(alloc::format!(
"Maximum feature count ({}) exceeded",
crate::MAX_FEATURE_COUNT
));
}
self.features_map.insert(name, (start_time, end_time));
Ok(())
}
pub fn get(&self, name: &str) -> Option<&(u64, u64)> {
self.features_map.get(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_state() {
let authority = Pubkey::new_unique();
let state = FeaturesState::new(authority);
assert_eq!(state.get_authority(), &authority);
assert!(state.features_map.is_empty());
}
#[test]
fn test_upsert_feature() {
let mut state = FeaturesState::new_for_test();
let result = state.upsert("feature1".to_string(), 100, 200);
assert!(result.is_ok());
assert_eq!(state.get("feature1"), Some(&(100, 200)));
}
#[test]
fn test_upsert_invalid_time_range() {
let mut state = FeaturesState::new_for_test();
let result = state.upsert("feature1".to_string(), 200, 100);
assert!(result.is_err());
let result = state.upsert("feature2".to_string(), 100, 100);
assert!(result.is_err());
}
#[test]
fn test_is_active() {
let mut state = FeaturesState::new_for_test();
state.upsert("feature1".to_string(), 100, 200).unwrap();
assert!(!state.is_active("feature1", 50));
assert!(state.is_active("feature1", 100));
assert!(state.is_active("feature1", 150));
assert!(!state.is_active("feature1", 200));
assert!(!state.is_active("feature1", 250));
assert!(!state.is_active("nonexistent", 150));
}
#[test]
fn test_serialize_deserialize() {
let mut state = FeaturesState::new_for_test();
state.upsert("feature1".to_string(), 100, 200).unwrap();
state.upsert("feature2".to_string(), 300, 400).unwrap();
let serialized = state.serialize().expect("Serialization failed");
let deserialized = FeaturesState::deserialize(&serialized).expect("Deserialization failed");
assert_eq!(state, deserialized);
}
#[test]
fn test_set_authority() {
let mut state = FeaturesState::new_for_test();
let new_authority = Pubkey::new_unique();
state.set_authority(new_authority);
assert_eq!(state.get_authority(), &new_authority);
}
}