lib.rs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. //! Incremental file based incremental rendering
  2. #![allow(non_snake_case)]
  3. mod config;
  4. mod freshness;
  5. #[cfg(not(target_arch = "wasm32"))]
  6. mod fs_cache;
  7. mod memory_cache;
  8. use std::time::Duration;
  9. use chrono::Utc;
  10. pub use config::*;
  11. pub use freshness::*;
  12. use self::memory_cache::InMemoryCache;
  13. /// A render that was cached from a previous render.
  14. pub struct CachedRender<'a> {
  15. /// The route that was rendered
  16. pub route: String,
  17. /// The freshness information for the rendered response
  18. pub freshness: RenderFreshness,
  19. /// The rendered response
  20. pub response: &'a [u8],
  21. }
  22. /// An incremental renderer.
  23. pub struct IncrementalRenderer {
  24. pub(crate) memory_cache: InMemoryCache,
  25. #[cfg(not(target_arch = "wasm32"))]
  26. pub(crate) file_system_cache: fs_cache::FileSystemCache,
  27. invalidate_after: Option<Duration>,
  28. }
  29. impl IncrementalRenderer {
  30. /// Create a new incremental renderer builder.
  31. pub fn builder() -> IncrementalRendererConfig {
  32. IncrementalRendererConfig::new()
  33. }
  34. /// Remove a route from the cache.
  35. pub fn invalidate(&mut self, route: &str) {
  36. self.memory_cache.invalidate(route);
  37. #[cfg(not(target_arch = "wasm32"))]
  38. self.file_system_cache.invalidate(route);
  39. }
  40. /// Remove all routes from the cache.
  41. pub fn invalidate_all(&mut self) {
  42. self.memory_cache.clear();
  43. #[cfg(not(target_arch = "wasm32"))]
  44. self.file_system_cache.clear();
  45. }
  46. /// Cache a rendered response.
  47. ///
  48. /// ```rust
  49. /// # use dioxus_isrg::IncrementalRenderer;
  50. /// # let mut renderer = IncrementalRenderer::builder().build();
  51. /// let route = "/index".to_string();
  52. /// let response = b"<html><body>Hello world</body></html>";
  53. /// renderer.cache(route, response).unwrap();
  54. /// ```
  55. pub fn cache(
  56. &mut self,
  57. route: String,
  58. html: impl Into<Vec<u8>>,
  59. ) -> Result<RenderFreshness, IncrementalRendererError> {
  60. let timestamp = Utc::now();
  61. let html = html.into();
  62. #[cfg(not(target_arch = "wasm32"))]
  63. self.file_system_cache
  64. .put(route.clone(), timestamp, html.clone())?;
  65. self.memory_cache.put(route, timestamp, html);
  66. Ok(RenderFreshness::created_at(
  67. timestamp,
  68. self.invalidate_after,
  69. ))
  70. }
  71. /// Try to get a cached response for a route.
  72. ///
  73. /// ```rust
  74. /// # use dioxus_isrg::IncrementalRenderer;
  75. /// # let mut renderer = IncrementalRenderer::builder().build();
  76. /// # let route = "/index".to_string();
  77. /// # let response = b"<html><body>Hello world</body></html>";
  78. /// # renderer.cache(route, response).unwrap();
  79. /// let route = "/index";
  80. /// let response = renderer.get(route).unwrap();
  81. /// assert_eq!(response.unwrap().response, b"<html><body>Hello world</body></html>");
  82. /// ```
  83. ///
  84. /// If the route is not cached, `None` is returned.
  85. ///
  86. /// ```rust
  87. /// # use dioxus_isrg::IncrementalRenderer;
  88. /// # let mut renderer = IncrementalRenderer::builder().build();
  89. /// let route = "/index";
  90. /// let response = renderer.get(route).unwrap();
  91. /// assert!(response.is_none());
  92. /// ```
  93. pub fn get<'a>(
  94. &'a mut self,
  95. route: &str,
  96. ) -> Result<Option<CachedRender<'a>>, IncrementalRendererError> {
  97. let Self {
  98. memory_cache,
  99. #[cfg(not(target_arch = "wasm32"))]
  100. file_system_cache,
  101. ..
  102. } = self;
  103. #[allow(unused)]
  104. enum FsGetError {
  105. NotPresent,
  106. Error(IncrementalRendererError),
  107. }
  108. // The borrow checker prevents us from simply using a match/if and returning early. Instead we need to use the more complex closure API
  109. // non lexical lifetimes will make this possible (it works with polonius)
  110. let or_insert = || {
  111. // check the file cache
  112. #[cfg(not(target_arch = "wasm32"))]
  113. return match file_system_cache.get(route) {
  114. Ok(Some((freshness, bytes))) => Ok((freshness.timestamp(), bytes)),
  115. Ok(None) => Err(FsGetError::NotPresent),
  116. Err(e) => Err(FsGetError::Error(e)),
  117. };
  118. #[allow(unreachable_code)]
  119. Err(FsGetError::NotPresent)
  120. };
  121. match memory_cache.try_get_or_insert(route, or_insert) {
  122. Ok(Some((freshness, bytes))) => Ok(Some(CachedRender {
  123. route: route.to_string(),
  124. freshness,
  125. response: bytes,
  126. })),
  127. Err(FsGetError::NotPresent) | Ok(None) => Ok(None),
  128. Err(FsGetError::Error(e)) => Err(e),
  129. }
  130. }
  131. }
  132. /// An error that can occur while rendering a route or retrieving a cached route.
  133. #[derive(Debug, thiserror::Error)]
  134. #[non_exhaustive]
  135. pub enum IncrementalRendererError {
  136. /// An formatting error occurred while rendering a route.
  137. #[error("RenderError: {0}")]
  138. RenderError(#[from] std::fmt::Error),
  139. /// An IO error occurred while rendering a route.
  140. #[error("IoError: {0}")]
  141. IoError(#[from] std::io::Error),
  142. /// An error occurred while rendering a route.
  143. #[error("Other: {0}")]
  144. Other(#[from] Box<dyn std::error::Error + Send + Sync>),
  145. }