use std::borrow::Cow;
use std::collections::VecDeque;
use std::sync::Mutex;
pub struct StringPool {
pool: Mutex<VecDeque<String>>,
max_size: usize,
}
impl StringPool {
pub fn new(max_size: usize) -> Self {
Self {
pool: Mutex::new(VecDeque::with_capacity(max_size)),
max_size,
}
}
pub fn get_string(&self) -> String {
self.pool.lock().unwrap().pop_front().unwrap_or_default()
}
pub fn return_string(&self, mut s: String) {
s.clear();
let mut pool = self.pool.lock().unwrap();
if pool.len() < self.max_size {
pool.push_back(s);
}
}
pub fn pool_size(&self) -> usize {
self.pool.lock().unwrap().len()
}
pub fn clear(&self) {
self.pool.lock().unwrap().clear();
}
}
impl Default for StringPool {
fn default() -> Self {
Self::new(100) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CacheKey<'a> {
pub subject_id: Cow<'a, str>,
pub permission_key: Cow<'a, str>,
}
impl<'a> CacheKey<'a> {
pub fn borrowed(subject_id: &'a str, permission_key: &'a str) -> Self {
Self {
subject_id: Cow::Borrowed(subject_id),
permission_key: Cow::Borrowed(permission_key),
}
}
pub fn owned(subject_id: String, permission_key: String) -> Self {
Self {
subject_id: Cow::Owned(subject_id),
permission_key: Cow::Owned(permission_key),
}
}
pub fn into_owned(self) -> CacheKey<'static> {
CacheKey {
subject_id: Cow::Owned(self.subject_id.into_owned()),
permission_key: Cow::Owned(self.permission_key.into_owned()),
}
}
}
pub struct ObjectPool<T> {
pool: Mutex<VecDeque<T>>,
factory: Box<dyn Fn() -> T + Send + Sync>,
reset: Box<dyn Fn(&mut T) + Send + Sync>,
max_size: usize,
}
impl<T> ObjectPool<T> {
pub fn new<F, R>(max_size: usize, factory: F, reset: R) -> Self
where
F: Fn() -> T + Send + Sync + 'static,
R: Fn(&mut T) + Send + Sync + 'static,
{
Self {
pool: Mutex::new(VecDeque::with_capacity(max_size)),
factory: Box::new(factory),
reset: Box::new(reset),
max_size,
}
}
pub fn get<'a>(&'a self) -> PooledObject<'a, T> {
let obj = self
.pool
.lock()
.unwrap()
.pop_front()
.unwrap_or_else(|| (self.factory)());
PooledObject::new(obj, self)
}
fn return_object(&self, mut obj: T) {
(self.reset)(&mut obj);
let mut pool = self.pool.lock().unwrap();
if pool.len() < self.max_size {
pool.push_back(obj);
}
}
pub fn pool_size(&self) -> usize {
self.pool.lock().unwrap().len()
}
}
pub struct PooledObject<'a, T> {
obj: Option<T>,
pool: &'a ObjectPool<T>,
}
impl<'a, T> PooledObject<'a, T> {
fn new(obj: T, pool: &'a ObjectPool<T>) -> Self {
Self {
obj: Some(obj),
pool,
}
}
}
impl<'a, T> std::ops::Deref for PooledObject<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.obj.as_ref().unwrap()
}
}
impl<'a, T> std::ops::DerefMut for PooledObject<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.obj.as_mut().unwrap()
}
}
impl<'a, T> Drop for PooledObject<'a, T> {
fn drop(&mut self) {
if let Some(obj) = self.obj.take() {
self.pool.return_object(obj);
}
}
}
pub mod string_ops {
use super::*;
use std::collections::HashMap;
thread_local! {
static STRING_POOL: StringPool = StringPool::default();
}
pub fn get_pooled_string() -> String {
STRING_POOL.with(|pool| pool.get_string())
}
pub fn return_pooled_string(s: String) {
STRING_POOL.with(|pool| pool.return_string(s));
}
pub fn create_permission_key(action: &str, resource_id: &str, context_hash: &str) -> String {
if context_hash.is_empty() {
format!("{}:{}", action, resource_id)
} else {
format!("{}:{}:{}", action, resource_id, context_hash)
}
}
pub fn hash_context(context: &HashMap<String, String>) -> String {
if context.is_empty() {
String::new()
} else {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
let mut sorted_keys: Vec<_> = context.keys().collect();
sorted_keys.sort();
for key in sorted_keys {
key.hash(&mut hasher);
context[key].hash(&mut hasher);
}
format!("{:x}", hasher.finish())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_string_pool() {
let pool = StringPool::new(5);
let s1 = pool.get_string();
let s2 = pool.get_string();
pool.return_string(s1);
pool.return_string(s2);
assert_eq!(pool.pool_size(), 2);
for _ in 0..10 {
pool.return_string(String::new());
}
assert_eq!(pool.pool_size(), 5);
}
#[test]
fn test_cache_key() {
let key1 = CacheKey::borrowed("user1", "read:docs");
let key2 = CacheKey::owned("user1".to_string(), "read:docs".to_string());
assert_eq!(key1, key2);
let owned_key = key1.into_owned();
assert_eq!(owned_key.subject_id, "user1");
}
#[test]
fn test_object_pool() {
let pool = ObjectPool::new(3, Vec::<i32>::new, |v| v.clear());
{
let mut obj1 = pool.get();
obj1.push(42);
assert_eq!(obj1[0], 42);
}
{
let obj2 = pool.get();
assert!(obj2.is_empty()); }
assert_eq!(pool.pool_size(), 1);
}
#[test]
fn test_string_ops() {
use super::string_ops::*;
let key = create_permission_key("read", "doc1", "");
assert_eq!(key, "read:doc1");
let key_with_context = create_permission_key("read", "doc1", "abc123");
assert_eq!(key_with_context, "read:doc1:abc123");
let mut context = HashMap::new();
context.insert("user".to_string(), "admin".to_string());
context.insert("time".to_string(), "day".to_string());
let hash1 = hash_context(&context);
let hash2 = hash_context(&context);
assert_eq!(hash1, hash2);
let empty_hash = hash_context(&HashMap::new());
assert!(empty_hash.is_empty());
}
}