Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion crates/trusted-server-adapter-fastly/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,12 @@ fn fastly_response_to_platform(
// FastlyPlatformHttpClient
// ---------------------------------------------------------------------------

fn apply_fastly_cache_bypass(request: &mut fastly::Request, bypass_cache: bool) {
if bypass_cache {
request.set_pass(true);
}
}

/// Fastly implementation of [`PlatformHttpClient`].
///
/// - [`send`](PlatformHttpClient::send) converts the platform request to a
Expand All @@ -529,10 +535,12 @@ impl PlatformHttpClient for FastlyPlatformHttpClient {
let backend_name = request.backend_name.clone();
let image_optimizer = request.image_optimizer;
let stream_response = request.stream_response;
let bypass_cache = request.bypass_cache;
let mut fastly_req = edge_request_to_fastly(request.request)?;
if let Some(options) = image_optimizer {
apply_fastly_image_optimizer(&mut fastly_req, options)?;
}
apply_fastly_cache_bypass(&mut fastly_req, bypass_cache);
let fastly_resp = fastly_req
.send(&backend_name)
.change_context(PlatformError::HttpClient)?;
Expand All @@ -552,7 +560,9 @@ impl PlatformHttpClient for FastlyPlatformHttpClient {
return Err(Report::new(PlatformError::HttpClient)
.attach("streaming responses are not supported with Fastly send_async"));
}
let fastly_req = edge_request_to_fastly(request.request)?;
let bypass_cache = request.bypass_cache;
let mut fastly_req = edge_request_to_fastly(request.request)?;
apply_fastly_cache_bypass(&mut fastly_req, bypass_cache);
let pending = fastly_req
.send_async(&backend_name)
.change_context(PlatformError::HttpClient)?;
Expand Down Expand Up @@ -873,6 +883,26 @@ mod tests {

// --- FastlyPlatformHttpClient -------------------------------------------

#[test]
fn apply_fastly_cache_bypass_sets_pass_when_enabled() {
let mut request = fastly::Request::get("https://example.com/");
apply_fastly_cache_bypass(&mut request, true);
assert!(
format!("{request:?}").contains("cache_override: Pass"),
"enabled bypass should select Fastly pass mode"
);
}

#[test]
fn apply_fastly_cache_bypass_preserves_default_when_disabled() {
let mut request = fastly::Request::get("https://example.com/");
apply_fastly_cache_bypass(&mut request, false);
assert!(
format!("{request:?}").contains("cache_override: None"),
"disabled bypass should preserve Fastly read-through caching"
);
}

#[test]
fn fastly_platform_http_client_send_returns_error_for_unregistered_backend() {
let client = FastlyPlatformHttpClient;
Expand Down
48 changes: 48 additions & 0 deletions crates/trusted-server-core/src/platform/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ pub struct PlatformHttpRequest {
/// Adapters that cannot attach this metadata to their send path should
/// return an error rather than silently dropping transformations.
pub image_optimizer: Option<PlatformImageOptimizerOptions>,
/// Whether the platform's intermediary response cache must be bypassed.
///
/// Adapters without an intermediary outbound cache may treat this as already
/// satisfied. The option defaults to `false` so existing call sites preserve
/// their current cache behavior.
pub bypass_cache: bool,
/// Whether the response body should stay streaming in the platform response.
///
/// Adapters that cannot preserve streaming response bodies should return an
Expand All @@ -38,6 +44,7 @@ impl PlatformHttpRequest {
request,
backend_name: backend_name.into(),
image_optimizer: None,
bypass_cache: false,
stream_response: false,
}
}
Expand All @@ -53,6 +60,13 @@ impl PlatformHttpRequest {
self
}

/// Bypass the platform's intermediary response cache for this request.
#[must_use]
pub fn with_cache_bypass(mut self) -> Self {
self.bypass_cache = true;
self
}

/// Preserve the upstream response body as a stream when the adapter supports it.
///
/// Asset routes use this to avoid materializing large image/static responses
Expand Down Expand Up @@ -306,8 +320,42 @@ pub trait PlatformHttpClient: Send + Sync {

#[cfg(test)]
mod tests {
use edgezero_core::body::Body;
use edgezero_core::http::request_builder;

use super::*;

#[test]
fn platform_http_request_cache_bypass_defaults_to_false() {
let request = PlatformHttpRequest::new(
request_builder()
.body(Body::empty())
.expect("should build request"),
"stub-backend",
);

assert!(
!request.bypass_cache,
"should preserve existing cache behavior by default"
);
}

#[test]
fn platform_http_request_cache_bypass_builder_enables_bypass() {
let request = PlatformHttpRequest::new(
request_builder()
.body(Body::empty())
.expect("should build request"),
"stub-backend",
)
.with_cache_bypass();

assert!(
request.bypass_cache,
"should enable intermediary cache bypass"
);
}

// ---------------------------------------------------------------------------
// Error-correlation interim scope (before EdgeZero #213)
// ---------------------------------------------------------------------------
Expand Down
33 changes: 31 additions & 2 deletions crates/trusted-server-core/src/platform/test_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ pub(crate) struct StubHttpClient {
// platforms whose send_async executes eagerly (e.g. Cloudflare Workers).
concurrent_fanout: std::sync::atomic::AtomicBool,
image_optimizer_options: Mutex<Vec<Option<PlatformImageOptimizerOptions>>>,
cache_bypass_flags: Mutex<Vec<bool>>,
stream_response_flags: Mutex<Vec<bool>>,
request_methods: Mutex<Vec<String>>,
request_uris: Mutex<Vec<String>>,
Expand All @@ -247,6 +248,7 @@ impl StubHttpClient {
select_errors: Mutex::new(VecDeque::new()),
concurrent_fanout: std::sync::atomic::AtomicBool::new(true),
image_optimizer_options: Mutex::new(Vec::new()),
cache_bypass_flags: Mutex::new(Vec::new()),
stream_response_flags: Mutex::new(Vec::new()),
request_methods: Mutex::new(Vec::new()),
request_uris: Mutex::new(Vec::new()),
Expand Down Expand Up @@ -319,6 +321,14 @@ impl StubHttpClient {
.clone()
}

/// Return cache-bypass flags captured per `send` or `send_async` call, in order.
pub(crate) fn recorded_cache_bypass_flags(&self) -> Vec<bool> {
self.cache_bypass_flags
.lock()
.expect("should lock cache bypass flags")
.clone()
}

/// Return streaming-response flags captured per `send` call, in order.
pub fn recorded_stream_response_flags(&self) -> Vec<bool> {
self.stream_response_flags
Expand Down Expand Up @@ -376,6 +386,10 @@ impl PlatformHttpClient for StubHttpClient {
.lock()
.expect("should lock image optimizer options")
.push(request.image_optimizer.clone());
self.cache_bypass_flags
.lock()
.expect("should lock cache bypass flags")
.push(request.bypass_cache);
self.stream_response_flags
.lock()
.expect("should lock stream response flags")
Expand Down Expand Up @@ -456,6 +470,10 @@ impl PlatformHttpClient for StubHttpClient {
.lock()
.expect("should lock calls")
.push(backend_name.clone());
self.cache_bypass_flags
.lock()
.expect("should lock cache bypass flags")
.push(request.bypass_cache);

let headers: Vec<(String, String)> = request
.request
Expand Down Expand Up @@ -742,6 +760,11 @@ mod tests {
vec!["stub-backend"],
"should record the backend name"
);
assert_eq!(
stub.recorded_cache_bypass_flags(),
vec![false],
"should record the default cache-bypass flag"
);
}

#[test]
Expand Down Expand Up @@ -787,8 +810,9 @@ mod tests {

let pending_a = futures::executor::block_on(stub.send_async(make_req("backend-a")))
.expect("should start request a");
let pending_b = futures::executor::block_on(stub.send_async(make_req("backend-b")))
.expect("should start request b");
let pending_b =
futures::executor::block_on(stub.send_async(make_req("backend-b").with_cache_bypass()))
.expect("should start request b");

assert_eq!(
pending_a.backend_name(),
Expand Down Expand Up @@ -827,6 +851,11 @@ mod tests {
vec!["backend-a", "backend-b"],
"should record both send_async calls in order"
);
assert_eq!(
stub.recorded_cache_bypass_flags(),
vec![false, true],
"should record both send_async cache-bypass flags in order"
);
}

#[test]
Expand Down
Loading
Loading