cache_manager/interceptors/
cache_interceptor.rs1use std::collections::BTreeMap;
4use std::sync::Arc;
5
6use serde::Serialize;
7use serde_json::Value;
8
9use crate::cache_constants::{CACHE_KEY_METADATA, CACHE_TTL_METADATA};
10use crate::cache_providers::{CacheManager, CacheManagerError, CacheValue};
11use crate::decorators::{CacheKeyMetadata, CacheTTLMetadata};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Request {
15 pub method: String,
16 pub url: String,
17}
18
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct Response {
21 pub headers: BTreeMap<String, String>,
22}
23
24#[derive(Debug, Clone, Default, PartialEq, Eq)]
25pub struct HttpAdapter;
26
27impl HttpAdapter {
28 pub fn getRequestMethod(&self, request: &Request) -> String {
29 request.method.clone()
30 }
31
32 pub fn getRequestUrl(&self, request: &Request) -> String {
33 request.url.clone()
34 }
35
36 pub fn setHeader(&self, response: &mut Response, name: &str, value: &str) {
37 response.headers.insert(name.to_string(), value.to_string());
38 }
39}
40
41#[derive(Clone, Default)]
42pub struct ExecutionContext {
43 pub handler: String,
44 pub class_name: String,
45 pub request: Option<Request>,
46 pub response: Option<Response>,
47 pub http_adapter: Option<HttpAdapter>,
48 cache_key_metadata: Option<CacheKeyMetadata>,
49 cache_ttl_metadata: Option<CacheTTLMetadata>,
50 class_cache_ttl_metadata: Option<CacheTTLMetadata>,
51}
52
53impl ExecutionContext {
54 pub fn http(
55 handler: impl Into<String>,
56 class_name: impl Into<String>,
57 request: Request,
58 ) -> Self {
59 Self {
60 handler: handler.into(),
61 class_name: class_name.into(),
62 request: Some(request),
63 response: Some(Response::default()),
64 http_adapter: Some(HttpAdapter),
65 ..Self::default()
66 }
67 }
68
69 pub fn set_metadata(&mut self, key: &str, value: Metadata) {
70 match (key, value) {
71 (CACHE_KEY_METADATA, Metadata::CacheKey(value)) => {
72 self.cache_key_metadata = Some(value)
73 }
74 (CACHE_TTL_METADATA, Metadata::CacheTTL(value)) => {
75 self.cache_ttl_metadata = Some(value)
76 }
77 _ => {}
78 }
79 }
80
81 pub fn set_class_cache_ttl(&mut self, value: CacheTTLMetadata) {
82 self.class_cache_ttl_metadata = Some(value);
83 }
84
85 pub fn response(&self) -> Option<&Response> {
86 self.response.as_ref()
87 }
88}
89
90#[derive(Clone)]
91pub enum Metadata {
92 CacheKey(CacheKeyMetadata),
93 CacheTTL(CacheTTLMetadata),
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub enum InterceptorResponse {
98 Cached(CacheValue),
99 Handled(CacheValue),
100 StreamableFile,
101}
102
103pub type CallHandler =
104 Arc<dyn Fn() -> Result<InterceptorResponse, CacheManagerError> + Send + Sync + 'static>;
105
106#[derive(Clone)]
107pub struct CacheInterceptor {
108 pub cacheManager: CacheManager,
109 pub allowedMethods: Vec<String>,
110}
111
112impl CacheInterceptor {
113 pub fn new(cacheManager: CacheManager) -> Self {
114 Self {
115 cacheManager,
116 allowedMethods: vec!["GET".to_string()],
117 }
118 }
119
120 pub async fn intercept(
121 &self,
122 context: &mut ExecutionContext,
123 next: CallHandler,
124 ) -> Result<InterceptorResponse, CacheManagerError> {
125 let key = self.trackBy(context);
126 let ttlValueOrFactory = context
127 .cache_ttl_metadata
128 .clone()
129 .or_else(|| context.class_cache_ttl_metadata.clone());
130
131 let Some(key) = key else {
132 return next();
133 };
134
135 match self.cacheManager.get(&key).await {
136 Ok(value) => {
137 self.setHeadersWhenHttp(context, value.as_ref());
138
139 if let Some(value) = value {
140 return Ok(InterceptorResponse::Cached(value));
141 }
142
143 let response = next()?;
144 if !matches!(response, InterceptorResponse::StreamableFile) {
145 let ttl = ttlValueOrFactory.map(|ttl| ttl.resolve(context));
146 if let Some(value) = response_value(&response) {
147 if let Err(err) = self
148 .cacheManager
149 .set_value(
150 &key,
151 value.clone(),
152 ttl.map(std::time::Duration::from_millis),
153 )
154 .await
155 {
156 eprintln!(
157 "An error has occurred when inserting \"key: {key}\", \"value: {value}\""
158 );
159 eprintln!("{err}");
160 }
161 }
162 }
163 Ok(response)
164 }
165 Err(_) => next(),
166 }
167 }
168
169 pub fn trackBy(&self, context: &ExecutionContext) -> Option<String> {
170 let isHttpApp = context.http_adapter.is_some();
171 let cacheMetadataOrFactory = context.cache_key_metadata.clone();
172
173 if !isHttpApp || cacheMetadataOrFactory.is_some() {
174 return cacheMetadataOrFactory.and_then(|metadata| metadata.resolve(context));
175 }
176
177 if !self.isRequestCacheable(context) {
178 return None;
179 }
180
181 let request = context.request.as_ref()?;
182 let http_adapter = context.http_adapter.as_ref()?;
183 Some(http_adapter.getRequestUrl(request))
184 }
185
186 pub fn isRequestCacheable(&self, context: &ExecutionContext) -> bool {
187 context
188 .request
189 .as_ref()
190 .map(|req| self.allowedMethods.contains(&req.method))
191 .unwrap_or(false)
192 }
193
194 pub fn setHeadersWhenHttp(&self, context: &mut ExecutionContext, value: Option<&CacheValue>) {
195 let Some(http_adapter) = context.http_adapter.as_ref() else {
196 return;
197 };
198 let Some(response) = context.response.as_mut() else {
199 return;
200 };
201 http_adapter.setHeader(
202 response,
203 "X-Cache",
204 if value.is_some() { "HIT" } else { "MISS" },
205 );
206 }
207}
208
209fn response_value(response: &InterceptorResponse) -> Option<&Value> {
210 match response {
211 InterceptorResponse::Cached(value) | InterceptorResponse::Handled(value) => Some(value),
212 InterceptorResponse::StreamableFile => None,
213 }
214}
215
216pub fn handled<T: Serialize>(value: T) -> Result<InterceptorResponse, CacheManagerError> {
217 Ok(InterceptorResponse::Handled(serde_json::to_value(value)?))
218}