use bumpalo::Bump;
use bytes::Bytes;
use std::cell::RefCell;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
const DEFAULT_ARENA_SIZE: usize = 64 * 1024;
const MAX_ARENA_SIZE: usize = 1024 * 1024;
thread_local! {
static ARENA: RefCell<Bump> = RefCell::new(Bump::with_capacity(DEFAULT_ARENA_SIZE));
}
#[inline]
pub fn with_arena<F, R>(f: F) -> R
where
F: FnOnce(&Bump) -> R,
{
ARENA.with(|arena| {
let arena = arena.borrow();
f(&arena)
})
}
#[inline]
pub fn with_arena_mut<F, R>(f: F) -> R
where
F: FnOnce(&Bump) -> R,
{
ARENA.with(|arena| {
let arena = arena.borrow();
f(&arena)
})
}
#[inline]
pub fn reset_arena() {
ARENA.with(|arena| {
let mut arena = arena.borrow_mut();
if arena.allocated_bytes() > MAX_ARENA_SIZE {
*arena = Bump::with_capacity(DEFAULT_ARENA_SIZE);
} else {
arena.reset();
}
});
}
#[inline]
pub fn arena_allocated_bytes() -> usize {
ARENA.with(|arena| arena.borrow().allocated_bytes())
}
#[derive(Clone, Copy)]
pub struct ArenaStr<'a> {
inner: &'a str,
}
impl<'a> ArenaStr<'a> {
#[inline]
pub fn from_str(arena: &'a Bump, s: &str) -> Self {
Self {
inner: arena.alloc_str(s),
}
}
#[inline]
pub const fn empty() -> Self {
Self { inner: "" }
}
#[inline]
pub fn as_str(&self) -> &'a str {
self.inner
}
#[inline]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn into_string(&self) -> String {
self.inner.to_string()
}
}
impl<'a> Deref for ArenaStr<'a> {
type Target = str;
#[inline]
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<'a> AsRef<str> for ArenaStr<'a> {
#[inline]
fn as_ref(&self) -> &str {
self.inner
}
}
impl<'a> fmt::Debug for ArenaStr<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.inner)
}
}
impl<'a> fmt::Display for ArenaStr<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
impl<'a> PartialEq for ArenaStr<'a> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<'a> Eq for ArenaStr<'a> {}
impl<'a> PartialEq<str> for ArenaStr<'a> {
fn eq(&self, other: &str) -> bool {
self.inner == other
}
}
impl<'a> PartialEq<&str> for ArenaStr<'a> {
fn eq(&self, other: &&str) -> bool {
self.inner == *other
}
}
impl<'a> PartialEq<String> for ArenaStr<'a> {
fn eq(&self, other: &String) -> bool {
self.inner == other.as_str()
}
}
impl<'a> Hash for ArenaStr<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.hash(state);
}
}
pub type ArenaVec<'a, T> = bumpalo::collections::Vec<'a, T>;
pub struct ArenaMap<'a, K, V> {
entries: ArenaVec<'a, (K, V)>,
}
impl<'a, K: PartialEq, V> ArenaMap<'a, K, V> {
#[inline]
pub fn new_in(arena: &'a Bump) -> Self {
Self {
entries: ArenaVec::new_in(arena),
}
}
#[inline]
pub fn with_capacity_in(arena: &'a Bump, capacity: usize) -> Self {
Self {
entries: ArenaVec::with_capacity_in(capacity, arena),
}
}
#[inline]
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
for entry in self.entries.iter_mut() {
if entry.0 == key {
let old = std::mem::replace(&mut entry.1, value);
return Some(old);
}
}
self.entries.push((key, value));
None
}
#[inline]
pub fn get(&self, key: &K) -> Option<&V> {
self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
#[inline]
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.entries
.iter_mut()
.find(|(k, _)| k == key)
.map(|(_, v)| v)
}
#[inline]
pub fn contains_key(&self, key: &K) -> bool {
self.entries.iter().any(|(k, _)| k == key)
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &(K, V)> {
self.entries.iter()
}
#[inline]
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.entries.iter().map(|(k, _)| k)
}
#[inline]
pub fn values(&self) -> impl Iterator<Item = &V> {
self.entries.iter().map(|(_, v)| v)
}
}
impl<'a, K: PartialEq + fmt::Debug, V: fmt::Debug> fmt::Debug for ArenaMap<'a, K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.entries.iter().map(|(k, v)| (k, v)))
.finish()
}
}
pub struct ArenaRequest<'a> {
pub method: ArenaStr<'a>,
pub path: ArenaStr<'a>,
pub headers: ArenaMap<'a, ArenaStr<'a>, ArenaStr<'a>>,
pub path_params: ArenaMap<'a, ArenaStr<'a>, ArenaStr<'a>>,
pub query_params: ArenaMap<'a, ArenaStr<'a>, ArenaStr<'a>>,
pub body: &'a [u8],
}
impl<'a> ArenaRequest<'a> {
#[inline]
pub fn new(arena: &'a Bump, method: &str, path: &str) -> Self {
Self {
method: ArenaStr::from_str(arena, method),
path: ArenaStr::from_str(arena, path),
headers: ArenaMap::with_capacity_in(arena, 16), path_params: ArenaMap::with_capacity_in(arena, 4),
query_params: ArenaMap::with_capacity_in(arena, 8),
body: &[],
}
}
#[inline]
pub fn with_body(arena: &'a Bump, method: &str, path: &str, body: &'a [u8]) -> Self {
Self {
method: ArenaStr::from_str(arena, method),
path: ArenaStr::from_str(arena, path),
headers: ArenaMap::with_capacity_in(arena, 16),
path_params: ArenaMap::with_capacity_in(arena, 4),
query_params: ArenaMap::with_capacity_in(arena, 8),
body,
}
}
#[inline]
pub fn add_header(&mut self, arena: &'a Bump, name: &str, value: &str) {
let name = ArenaStr::from_str(arena, name);
let value = ArenaStr::from_str(arena, value);
self.headers.insert(name, value);
}
#[inline]
pub fn add_path_param(&mut self, arena: &'a Bump, name: &str, value: &str) {
let name = ArenaStr::from_str(arena, name);
let value = ArenaStr::from_str(arena, value);
self.path_params.insert(name, value);
}
#[inline]
pub fn add_query_param(&mut self, arena: &'a Bump, name: &str, value: &str) {
let name = ArenaStr::from_str(arena, name);
let value = ArenaStr::from_str(arena, value);
self.query_params.insert(name, value);
}
#[inline]
pub fn header(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|(k, _)| k.as_str().eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
}
#[inline]
pub fn param(&self, name: &str) -> Option<&str> {
self.path_params
.iter()
.find(|(k, _)| k.as_str() == name)
.map(|(_, v)| v.as_str())
}
#[inline]
pub fn query(&self, name: &str) -> Option<&str> {
self.query_params
.iter()
.find(|(k, _)| k.as_str() == name)
.map(|(_, v)| v.as_str())
}
#[inline]
pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, crate::Error> {
crate::json::from_slice(self.body).map_err(|e| crate::Error::Deserialization(e.to_string()))
}
pub fn to_http_request(&self) -> crate::HttpRequest {
let mut req = crate::HttpRequest::new(self.method.to_string(), self.path.to_string());
for (k, v) in self.headers.iter() {
req.headers.insert(k.as_str(), v.as_str());
}
req.path_params = self
.path_params
.iter()
.map(|(k, v)| {
(
crate::param_intern::intern(k.as_str()),
Bytes::copy_from_slice(v.as_str().as_bytes()),
)
})
.collect();
if !self.query_params.is_empty() {
let pairs: Vec<(&str, &str)> = self
.query_params
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
if let Ok(query) = serde_urlencoded::to_string(&pairs) {
req.path = crate::ByteStr::from(format!("{}?{}", self.path.as_str(), query));
}
}
req.body = Bytes::copy_from_slice(self.body);
req
}
}
impl<'a> fmt::Debug for ArenaRequest<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ArenaRequest")
.field("method", &self.method)
.field("path", &self.path)
.field("headers", &self.headers)
.field("path_params", &self.path_params)
.field("query_params", &self.query_params)
.field("body_len", &self.body.len())
.finish()
}
}
#[inline]
pub fn arena_request_from_hyper<'a>(
arena: &'a Bump,
method: &str,
path: &str,
headers: impl Iterator<Item = (&'a str, &'a str)>,
body: &'a [u8],
) -> ArenaRequest<'a> {
let mut request = ArenaRequest::with_body(arena, method, path, body);
for (name, value) in headers {
request.add_header(arena, name, value);
}
request
}
pub struct RequestScope {
_private: (),
}
impl RequestScope {
#[inline]
pub fn new() -> Self {
Self { _private: () }
}
}
impl Default for RequestScope {
fn default() -> Self {
Self::new()
}
}
impl Drop for RequestScope {
#[inline]
fn drop(&mut self) {
reset_arena();
}
}
#[derive(Debug, Clone, Copy)]
pub struct ArenaStats {
pub allocated_bytes: usize,
pub chunk_count: usize,
}
#[inline]
pub fn arena_stats() -> ArenaStats {
ARENA.with(|arena| {
let mut arena = arena.borrow_mut();
let chunk_count = arena.iter_allocated_chunks().count();
ArenaStats {
allocated_bytes: arena.allocated_bytes(),
chunk_count,
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arena_str() {
with_arena(|arena| {
let s = ArenaStr::from_str(arena, "hello world");
assert_eq!(s.as_str(), "hello world");
assert_eq!(s.len(), 11);
assert!(!s.is_empty());
assert_eq!(&*s, "hello world"); });
reset_arena();
}
#[test]
fn test_arena_str_empty() {
let s = ArenaStr::empty();
assert!(s.is_empty());
assert_eq!(s.len(), 0);
}
#[test]
fn test_arena_str_equality() {
with_arena(|arena| {
let s1 = ArenaStr::from_str(arena, "test");
let s2 = ArenaStr::from_str(arena, "test");
let s3 = ArenaStr::from_str(arena, "other");
assert_eq!(s1, s2);
assert_ne!(s1, s3);
assert!(s1 == "test"); assert!(s1 == "test"); assert_eq!(s1.as_str(), "test"); });
reset_arena();
}
#[test]
fn test_arena_map() {
with_arena(|arena| {
let mut map = ArenaMap::<ArenaStr, ArenaStr>::new_in(arena);
let key1 = ArenaStr::from_str(arena, "key1");
let val1 = ArenaStr::from_str(arena, "value1");
map.insert(key1, val1);
let key2 = ArenaStr::from_str(arena, "key2");
let val2 = ArenaStr::from_str(arena, "value2");
map.insert(key2, val2);
assert_eq!(map.len(), 2);
assert!(map.contains_key(&ArenaStr::from_str(arena, "key1")));
let lookup = ArenaStr::from_str(arena, "key1");
assert_eq!(map.get(&lookup).map(|v| v.as_str()), Some("value1"));
});
reset_arena();
}
#[test]
fn test_arena_request() {
with_arena(|arena| {
let mut request = ArenaRequest::new(arena, "POST", "/api/users");
request.add_header(arena, "Content-Type", "application/json");
request.add_header(arena, "Authorization", "Bearer token123");
request.add_query_param(arena, "page", "1");
request.add_path_param(arena, "id", "42");
assert_eq!(request.method.as_str(), "POST");
assert_eq!(request.path.as_str(), "/api/users");
assert_eq!(request.header("Content-Type"), Some("application/json"));
assert_eq!(request.header("content-type"), Some("application/json")); assert_eq!(request.query("page"), Some("1"));
assert_eq!(request.param("id"), Some("42"));
});
reset_arena();
}
#[test]
fn test_arena_request_to_http_request() {
with_arena(|arena| {
let mut request = ArenaRequest::new(arena, "GET", "/test");
request.add_header(arena, "Accept", "application/json");
let http_request = request.to_http_request();
assert_eq!(http_request.method, "GET");
assert_eq!(http_request.path, "/test");
assert_eq!(http_request.headers.get("Accept"), Some("application/json"));
});
reset_arena();
}
#[test]
fn test_request_scope() {
{
let _scope = RequestScope::new();
with_arena(|arena| {
let s1 = ArenaStr::from_str(arena, "test string that uses memory");
let s2 = ArenaStr::from_str(arena, "another string");
assert!(!s1.is_empty());
assert!(!s2.is_empty());
});
}
with_arena(|arena| {
let s = ArenaStr::from_str(arena, "new allocation after reset");
assert!(!s.is_empty());
});
reset_arena();
}
#[test]
fn test_arena_stats() {
reset_arena();
let stats = arena_stats();
assert!(stats.chunk_count >= 1);
with_arena(|arena| {
let mut vec: ArenaVec<u8> = ArenaVec::with_capacity_in(1024, arena);
for i in 0..255u8 {
vec.push(i);
}
assert_eq!(vec.len(), 255);
});
let stats_after = arena_stats();
assert!(stats_after.allocated_bytes > 0);
reset_arena();
}
#[test]
fn test_arena_vec() {
with_arena(|arena| {
let mut vec: ArenaVec<i32> = ArenaVec::new_in(arena);
vec.push(1);
vec.push(2);
vec.push(3);
assert_eq!(vec.len(), 3);
assert_eq!(vec[0], 1);
assert_eq!(vec[1], 2);
assert_eq!(vec[2], 3);
});
reset_arena();
}
}