diff --git a/Cargo.lock b/Cargo.lock index 85e2f35..b79e7c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,9 +46,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" @@ -295,6 +295,7 @@ dependencies = [ name = "fastly-sys" version = "0.6.0" dependencies = [ + "bytes", "cxx", "cxx-build", "esi", diff --git a/Cargo.toml b/Cargo.toml index 00e5bc9..5a8dc6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ log-fastly = "0.11.9" thiserror = "2.0.12" esi = "0.6.1" quick-xml = "0.38.3" +bytes = "1.11.1" [build-dependencies] cxx-build = "1.0" diff --git a/include/fastly/detail/rust_bridge_tags.h b/include/fastly/detail/rust_bridge_tags.h index 98f92a1..d97b44f 100644 --- a/include/fastly/detail/rust_bridge_tags.h +++ b/include/fastly/detail/rust_bridge_tags.h @@ -25,6 +25,14 @@ struct ProcessFragmentResponseFnTag { ProcessFragmentResponseFnTag() = default; }; } // namespace esi + +namespace cache::simple { +struct GetOrSetWithFnTag { +protected: + GetOrSetWithFnTag() = default; +}; + +} // namespace cache::simple } // namespace fastly::detail::rust_bridge_tags -#endif \ No newline at end of file +#endif diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 0000000..fa1c4a4 --- /dev/null +++ b/src/cache.rs @@ -0,0 +1,140 @@ +use std::pin::{Pin, pin}; +use std::ptr; +use std::{fmt::Write as _, time::Duration}; + +use cxx::CxxString; + +use crate::ffi::{GetOrSetWithFnResult, GetOrSetWithFnTag}; +use crate::{ffi::SimpleCacheErrorCode, http::body::Body}; + +pub struct SimpleCacheError(fastly::cache::simple::CacheError); + +impl SimpleCacheError { + pub fn error_msg(&self, mut out: Pin<&mut CxxString>) { + write!(out, "{}", self.0).expect("This should never fail."); + } + pub fn error_code(&self) -> SimpleCacheErrorCode { + match self.0 { + fastly::cache::simple::CacheError::LimitExceeded => SimpleCacheErrorCode::LimitExceeded, + fastly::cache::simple::CacheError::InvalidOperation => { + SimpleCacheErrorCode::InvalidOperation + } + fastly::cache::simple::CacheError::Unsupported => SimpleCacheErrorCode::Unsupported, + fastly::cache::simple::CacheError::Io(..) => SimpleCacheErrorCode::Io, + fastly::cache::simple::CacheError::Purge(..) => SimpleCacheErrorCode::Purge, + fastly::cache::simple::CacheError::GetOrSet(..) => SimpleCacheErrorCode::GetOrSet, + fastly::cache::simple::CacheError::Other(..) => SimpleCacheErrorCode::Other, + _ => unimplemented!("Unknown cache error"), + } + } +} + +#[macro_export] +macro_rules! try_sce { + ( $err:ident, $x:expr ) => { + match $x { + std::result::Result::Ok(val) => { + $err.set(std::ptr::null_mut()); + val + } + std::result::Result::Err(e) => { + $err.set(Box::into_raw(Box::new(SimpleCacheError(e)))); + return Default::default(); + } + } + }; +} + +pub fn f_cache_simple_get( + key: &[u8], + mut out: Pin<&mut *mut Body>, + mut err: Pin<&mut *mut SimpleCacheError>, +) -> bool { + try_sce!(err, fastly::cache::simple::get(key.to_owned())) + .map(|body| out.set(Box::into_raw(Box::new(Body(body))))) + .is_some() +} + +pub fn f_cache_simple_get_or_set( + key: &[u8], + value: Box, + ttl: u32, + mut out: Pin<&mut *mut Body>, + mut err: Pin<&mut *mut SimpleCacheError>, +) -> bool { + out.set(Box::into_raw(Box::new(Body(try_sce!( + err, + fastly::cache::simple::get_or_set( + key.to_owned(), + value.0, + Duration::from_millis(ttl as u64) + ) + ))))); + true +} + +pub struct SimpleCacheEntry(fastly::cache::simple::CacheEntry); + +type GetOrSetWithFnType = dyn Fn() -> Result; +fn shim_get_or_set_with_fn(func: *const GetOrSetWithFnTag) -> Box { + Box::new(move || { + let mut out = pin!(ptr::null_mut()); + let result = unsafe { + crate::manual_ffi::fastly_esi_manualbridge_GetOrSetWithFn_call(func, &mut out) + }; + match result { + GetOrSetWithFnResult::Ok => Ok(unsafe { out.read().0 }), + GetOrSetWithFnResult::Err => Err(fastly::Error::msg("error during get_or_set_with")), + _ => unreachable!(), + } + }) +} + +pub fn f_cache_simple_get_or_set_with( + key: &[u8], + make_entry: *const GetOrSetWithFnTag, + mut out: Pin<&mut *mut Body>, + mut err: Pin<&mut *mut SimpleCacheError>, +) -> bool { + try_sce!( + err, + fastly::cache::simple::get_or_set_with(key.to_owned(), shim_get_or_set_with_fn(make_entry)) + ) + .map(|bod| out.set(Box::into_raw(Box::new(Body(bod))))) + .is_some() +} + +pub fn f_cache_simple_purge(key: &[u8], mut err: Pin<&mut *mut SimpleCacheError>) -> bool { + try_sce!(err, fastly::cache::simple::purge(key.to_owned())); + true +} + +pub struct PurgeOptions(fastly::cache::simple::PurgeOptions); + +pub fn m_static_cache_simple_purge_options_pop_scope() -> Box { + Box::new(PurgeOptions( + fastly::cache::simple::PurgeOptions::pop_scope(), + )) +} + +pub fn m_static_cache_simple_purge_options_global_scope() -> Box { + Box::new(PurgeOptions( + fastly::cache::simple::PurgeOptions::global_scope(), + )) +} + +pub fn f_cache_simple_purge_with_opts( + key: &[u8], + opts: Box, + mut err: Pin<&mut *mut SimpleCacheError>, +) -> bool { + try_sce!( + err, + fastly::cache::simple::purge_with_opts(key.to_owned(), opts.0) + ); + true +} + +pub fn f_cache_simple_error_force_symbols(x: Box) -> Box { + x +} diff --git a/src/lib.rs b/src/lib.rs index 414aa42..2a2f5d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![allow(clippy::boxed_local, clippy::needless_lifetimes)] use backend::*; +use cache::*; use config_store::*; use device_detection::*; use error::*; @@ -17,6 +18,7 @@ use secret_store::*; use security::*; mod backend; +mod cache; mod config_store; mod device_detection; mod error; @@ -1115,6 +1117,12 @@ mod ffi { type ProcessFragmentResponseFnTag; } + #[namespace = "fastly::detail::rust_bridge_tags::cache::simple"] + unsafe extern "C++" { + include!("fastly/detail/rust_bridge_tags.h"); + type GetOrSetWithFnTag; + } + #[namespace = "fastly::sys::esi"] extern "Rust" { type Processor; @@ -1146,13 +1154,74 @@ mod ffi { is_escaped_content: bool, ) -> Box; } + + #[namespace = "fastly::sys::cache::simple"] + #[repr(usize)] + pub enum GetOrSetWithFnResult { + Ok = 0, + Err = 1, + } + + #[namespace = "fastly::sys::cache::simple"] + #[derive(Debug, Clone, Copy)] + #[repr(usize)] + pub enum SimpleCacheErrorCode { + LimitExceeded, + InvalidOperation, + Unsupported, + Io, + Purge, + GetOrSet, + Other, + } + + #[namespace = "fastly::sys::cache::simple"] + extern "Rust" { + type SimpleCacheError; + fn error_msg(&self, mut out: Pin<&mut CxxString>); + fn error_code(&self) -> SimpleCacheErrorCode; + fn f_cache_simple_error_force_symbols(x: Box) -> Box; + } + + #[namespace = "fastly::sys::cache::simple"] + extern "Rust" { + type SimpleCacheEntry; + type PurgeOptions; + fn f_cache_simple_get( + key: &[u8], + mut out: Pin<&mut *mut Body>, + mut err: Pin<&mut *mut SimpleCacheError>, + ) -> bool; + fn f_cache_simple_get_or_set( + key: &[u8], + value: Box, + ttl: u32, + mut out: Pin<&mut *mut Body>, + mut err: Pin<&mut *mut SimpleCacheError>, + ) -> bool; + unsafe fn f_cache_simple_get_or_set_with( + key: &[u8], + make_entry: *const GetOrSetWithFnTag, + mut out: Pin<&mut *mut Body>, + mut err: Pin<&mut *mut SimpleCacheError>, + ) -> bool; + fn f_cache_simple_purge(key: &[u8], mut err: Pin<&mut *mut SimpleCacheError>) -> bool; + fn f_cache_simple_purge_with_opts( + key: &[u8], + opts: Box, + mut err: Pin<&mut *mut SimpleCacheError>, + ) -> bool; + fn m_static_cache_simple_purge_options_pop_scope() -> Box; + fn m_static_cache_simple_purge_options_global_scope() -> Box; + } } // Some types (notably callback functions) are not supported by CXX at all, so we // define manual FFI bindings for them here. mod manual_ffi { use crate::ffi::{ - DispatchFragmentRequestFnResult, DispatchFragmentRequestFnTag, ProcessFragmentResponseFnTag, + DispatchFragmentRequestFnResult, DispatchFragmentRequestFnTag, GetOrSetWithFnResult, + GetOrSetWithFnTag, ProcessFragmentResponseFnTag, }; // We never rely on the layout of Rust types passed to these functions, @@ -1175,5 +1244,11 @@ mod manual_ffi { response: *mut crate::Response, out_response: &mut *mut crate::Response, ) -> bool; + + #[link_name = "fastly$cache$simple$manualbridge$GetOrSetWithFn$call"] + pub(crate) fn fastly_esi_manualbridge_GetOrSetWithFn_call( + func: *const GetOrSetWithFnTag, + out: &mut *mut crate::SimpleCacheEntry, + ) -> GetOrSetWithFnResult; } }