1#![allow(non_snake_case)]
4
5mod config;
6mod freshness;
7#[cfg(not(target_arch = "wasm32"))]
8mod fs_cache;
9mod memory_cache;
10
11use std::time::Duration;
12
13use chrono::Utc;
14pub use config::*;
15pub use freshness::*;
16
17use self::memory_cache::InMemoryCache;
18
19pub struct CachedRender<'a> {
21 pub route: String,
23 pub freshness: RenderFreshness,
25 pub response: &'a [u8],
27}
28
29pub struct IncrementalRenderer {
31 pub(crate) memory_cache: InMemoryCache,
32 #[cfg(not(target_arch = "wasm32"))]
33 pub(crate) file_system_cache: fs_cache::FileSystemCache,
34 invalidate_after: Option<Duration>,
35}
36
37impl IncrementalRenderer {
38 pub fn builder() -> IncrementalRendererConfig {
40 IncrementalRendererConfig::new()
41 }
42
43 pub fn invalidate(&mut self, route: &str) {
45 self.memory_cache.invalidate(route);
46 #[cfg(not(target_arch = "wasm32"))]
47 self.file_system_cache.invalidate(route);
48 }
49
50 pub fn invalidate_all(&mut self) {
52 self.memory_cache.clear();
53 #[cfg(not(target_arch = "wasm32"))]
54 self.file_system_cache.clear();
55 }
56
57 pub fn cache(
67 &mut self,
68 route: String,
69 html: impl Into<Vec<u8>>,
70 ) -> Result<RenderFreshness, IncrementalRendererError> {
71 let timestamp = Utc::now();
72 let html = html.into();
73 #[cfg(not(target_arch = "wasm32"))]
74 self.file_system_cache
75 .put(route.clone(), timestamp, html.clone())?;
76 self.memory_cache.put(route, timestamp, html);
77 Ok(RenderFreshness::created_at(
78 timestamp,
79 self.invalidate_after,
80 ))
81 }
82
83 pub fn get<'a>(
106 &'a mut self,
107 route: &str,
108 ) -> Result<Option<CachedRender<'a>>, IncrementalRendererError> {
109 let Self {
110 memory_cache,
111 #[cfg(not(target_arch = "wasm32"))]
112 file_system_cache,
113 ..
114 } = self;
115
116 #[allow(unused)]
117 enum FsGetError {
118 NotPresent,
119 Error(IncrementalRendererError),
120 }
121
122 let or_insert = || {
125 #[cfg(not(target_arch = "wasm32"))]
127 return match file_system_cache.get(route) {
128 Ok(Some((freshness, bytes))) => Ok((freshness.timestamp(), bytes)),
129 Ok(None) => Err(FsGetError::NotPresent),
130 Err(e) => Err(FsGetError::Error(e)),
131 };
132
133 #[allow(unreachable_code)]
134 Err(FsGetError::NotPresent)
135 };
136
137 match memory_cache.try_get_or_insert(route, or_insert) {
138 Ok(Some((freshness, bytes))) => Ok(Some(CachedRender {
139 route: route.to_string(),
140 freshness,
141 response: bytes,
142 })),
143 Err(FsGetError::NotPresent) | Ok(None) => Ok(None),
144 Err(FsGetError::Error(e)) => Err(e),
145 }
146 }
147}
148
149#[derive(Debug, thiserror::Error)]
151#[non_exhaustive]
152pub enum IncrementalRendererError {
153 #[error("RenderError: {0}")]
155 RenderError(#[from] std::fmt::Error),
156 #[error("IoError: {0}")]
158 IoError(#[from] std::io::Error),
159 #[error("Other: {0}")]
161 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
162}