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;
}
}