http_cache_darkbird/
lib.rs1mod error;
2
3use http_cache::{CacheManager, HttpResponse, Result};
4
5use std::{fmt, sync::Arc, time::SystemTime};
6
7use darkbird::{
8 document::{self, RangeField},
9 Options, Storage, StorageType,
10};
11use http_cache_semantics::CachePolicy;
12use serde::{Deserialize, Serialize};
13
14#[derive(Clone)]
16pub struct DarkbirdManager {
17 pub cache: Arc<Storage<String, Store>>,
19 pub full_text: bool,
21}
22
23impl fmt::Debug for DarkbirdManager {
24 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25 f.debug_struct("DarkbirdManager").finish_non_exhaustive()
27 }
28}
29
30#[derive(Clone, Debug, Deserialize, Serialize)]
32pub struct Store {
33 pub response: HttpResponse,
35 pub policy: CachePolicy,
37 pub cache_key: String,
39 full_text: bool,
40}
41
42impl document::Document for Store {}
43
44impl document::Indexer for Store {
45 fn extract(&self) -> Vec<String> {
46 vec![self.cache_key.clone()]
47 }
48}
49
50impl document::Tags for Store {
51 fn get_tags(&self) -> Vec<String> {
52 vec![self.response.url.to_string()]
53 }
54}
55
56impl document::Range for Store {
57 fn get_fields(&self) -> Vec<RangeField> {
58 vec![
59 RangeField {
60 name: String::from("age"),
61 value: self.policy.age(SystemTime::now()).as_secs().to_string(),
62 },
63 RangeField {
64 name: String::from("time_to_live"),
65 value: self
66 .policy
67 .time_to_live(SystemTime::now())
68 .as_secs()
69 .to_string(),
70 },
71 ]
72 }
73}
74
75impl document::MaterializedView for Store {
76 fn filter(&self) -> Option<String> {
77 if self.policy.is_stale(SystemTime::now()) {
78 Some(String::from("stale"))
79 } else {
80 None
81 }
82 }
83}
84
85impl document::FullText for Store {
86 fn get_content(&self) -> Option<String> {
87 if self.full_text {
88 Some(String::from_utf8_lossy(&self.response.body).to_string())
89 } else {
90 None
91 }
92 }
93}
94
95impl DarkbirdManager {
96 pub async fn new(options: Options<'_>, full_text: bool) -> Result<Self> {
98 Ok(Self {
99 cache: Arc::new(Storage::<String, Store>::open(options).await?),
100 full_text,
101 })
102 }
103
104 pub async fn new_with_defaults() -> Result<Self> {
106 let ops = Options::new(
107 ".",
108 "http-darkbird",
109 42,
110 StorageType::RamCopies,
111 true,
112 );
113 Self::new(ops, false).await
114 }
115}
116
117#[async_trait::async_trait]
118impl CacheManager for DarkbirdManager {
119 async fn get(
120 &self,
121 cache_key: &str,
122 ) -> Result<Option<(HttpResponse, CachePolicy)>> {
123 let store: Store = match self.cache.lookup(&cache_key.to_string()) {
124 Some(d) => d.value().clone(),
125 None => return Ok(None),
126 };
127 Ok(Some((store.response, store.policy)))
128 }
129
130 async fn put(
131 &self,
132 cache_key: String,
133 response: HttpResponse,
134 policy: CachePolicy,
135 ) -> Result<HttpResponse> {
136 let data = Store {
137 response: response.clone(),
138 policy,
139 cache_key: cache_key.clone(),
140 full_text: self.full_text,
141 };
142 let mut exists = false;
143 if self.cache.lookup(&cache_key.to_string()).is_some() {
144 exists = true;
145 }
146 if exists {
147 self.delete(&cache_key).await?;
148 }
149 match self.cache.insert(cache_key, data).await {
150 Ok(_) => {}
151 Err(e) => {
152 return Err(Box::new(error::Error::Put(e.to_string())));
153 }
154 };
155 Ok(response)
156 }
157
158 async fn delete(&self, cache_key: &str) -> Result<()> {
159 match self.cache.remove(cache_key.to_string()).await {
160 Ok(_) => {}
161 Err(e) => {
162 return Err(Box::new(error::Error::Delete(e.to_string())));
163 }
164 };
165 Ok(())
166 }
167}
168
169#[cfg(test)]
170mod test;