1use std::fmt;
4use std::sync::{Arc, Mutex};
5use std::time::Duration;
6
7use bytes::Bytes;
8use tokio::sync::OnceCell;
9use tokio::time::Instant;
10
11use crate::Result;
12use crate::backend::Client;
13use crate::dataset::{Data, Dataset, Datasets};
14use crate::frontier::memory::InMemFrontier;
15use crate::frontier::queue::Queue;
16use crate::request::{Request, Response, ResponseExt};
17use crate::sync::lock;
18
19pub struct Context<C> {
28 request: Request,
29 client: C,
30 queue: Queue,
31 datasets: Arc<Datasets>,
32 response: OnceCell<Response<()>>,
33 fetch_time: Option<FetchTime>,
35}
36
37pub(crate) type FetchTime = Arc<Mutex<Option<Duration>>>;
39
40impl<C> Context<C> {
41 #[must_use]
47 pub fn new(request: Request, client: C) -> Self {
48 let queue = Queue::new(InMemFrontier::fifo()).child_of(&request);
49 Self::from_parts(request, client, queue, Arc::default())
50 }
51
52 pub(crate) fn from_parts(
53 request: Request,
54 client: C,
55 queue: Queue,
56 datasets: Arc<Datasets>,
57 ) -> Self {
58 Self {
59 request,
60 client,
61 queue,
62 datasets,
63 response: OnceCell::new(),
64 fetch_time: None,
65 }
66 }
67
68 pub(crate) fn with_fetch_time(mut self, fetch_time: FetchTime) -> Self {
70 self.fetch_time = Some(fetch_time);
71 self
72 }
73
74 #[must_use]
76 pub fn with_queue(mut self, queue: &Queue) -> Self {
77 self.queue = queue.child_of(&self.request);
78 self
79 }
80
81 #[must_use]
83 pub fn with_dataset<T, D>(mut self, dataset: D) -> Self
84 where
85 T: Send + 'static,
86 D: Dataset<T>,
87 {
88 Arc::make_mut(&mut self.datasets).insert(dataset);
89 self
90 }
91
92 #[must_use]
94 pub const fn request(&self) -> &Request {
95 &self.request
96 }
97
98 pub const fn request_mut(&mut self) -> &mut Request {
100 &mut self.request
101 }
102
103 #[must_use]
105 pub const fn client(&self) -> &C {
106 &self.client
107 }
108
109 #[must_use]
111 pub const fn queue(&self) -> &Queue {
112 &self.queue
113 }
114
115 pub fn dataset<T: Send + 'static>(&self) -> Result<Data<T>> {
122 self.datasets.get()
123 }
124
125 pub async fn response(&self) -> Result<&Response<()>>
136 where
137 C: Client,
138 {
139 self.response
140 .get_or_try_init(|| async {
141 let started = Instant::now();
142 let response = self.client.fetch(&self.request).await;
143 if let Some(fetch_time) = &self.fetch_time {
144 *lock(fetch_time) = Some(started.elapsed());
145 }
146 let response = response?;
147 if let Some(uri) = response.final_uri() {
148 self.queue.rebase(uri.clone());
149 }
150 Ok(response)
151 })
152 .await
153 }
154
155 pub async fn body(&self) -> Result<Bytes>
162 where
163 C: Client,
164 {
165 self.response().await?;
166 self.client.body().await
167 }
168}
169
170impl<C> fmt::Debug for Context<C> {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 f.debug_struct("Context")
173 .field("method", self.request.method())
174 .field("uri", self.request.uri())
175 .field("queue", &self.queue)
176 .field("fetched", &self.response.initialized())
177 .finish_non_exhaustive()
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use std::future::Future;
184 use std::sync::atomic::{AtomicUsize, Ordering};
185
186 use super::*;
187 use crate::backend::Noop;
188 use crate::dataset::InMemDataset;
189 use crate::frontier::seed::Seed;
190 use crate::request::{Depth, RequestExt};
191
192 #[tokio::test]
193 async fn standalone_context() {
194 let request = Seed::get("https://example.com/").unwrap().into_request();
195 let cx = Context::new(request, Noop).with_dataset(InMemDataset::<u32>::new());
196
197 cx.dataset::<u32>().unwrap().write(1).await.unwrap();
198 assert!(cx.dataset::<String>().is_err());
199
200 cx.queue().follow("https://example.com/next").await.unwrap();
201 assert_eq!(cx.queue().size_hint(), Some(1));
202 assert_eq!(cx.request().depth(), Depth::ROOT);
203
204 let response = cx.response().await.unwrap();
205 assert!(response.status().is_success());
206 }
207
208 struct Counting {
211 fetches: AtomicUsize,
212 fail: usize,
213 body: std::sync::Mutex<&'static str>,
214 }
215
216 impl Client for Counting {
217 fn fetch(&self, _request: &Request) -> impl Future<Output = Result<Response<()>>> + Send {
218 let fetch = self.fetches.fetch_add(1, Ordering::SeqCst);
219 std::future::ready(if fetch < self.fail {
220 Err(crate::Error::new(crate::ErrorKind::Http))
221 } else {
222 Ok(Response::new(()))
223 })
224 }
225
226 fn body(&self) -> impl Future<Output = Result<Bytes>> + Send {
227 let body = *self.body.lock().unwrap();
228 std::future::ready(Ok(Bytes::from_static(body.as_bytes())))
229 }
230 }
231
232 #[tokio::test]
233 async fn fetches_once_and_reads_the_current_body() {
234 let request = Seed::get("https://example.com/").unwrap().into_request();
235 let client = Counting {
236 fetches: AtomicUsize::new(0),
237 fail: 1,
238 body: std::sync::Mutex::new("loaded"),
239 };
240 let cx = Context::new(request, client);
241
242 assert!(cx.response().await.is_err());
244 assert_eq!(cx.body().await.unwrap(), "loaded");
245 *cx.client().body.lock().unwrap() = "clicked";
246 assert_eq!(cx.body().await.unwrap(), "clicked");
247 assert!(cx.response().await.unwrap().status().is_success());
248 assert_eq!(cx.client().fetches.load(Ordering::SeqCst), 2);
249 }
250}