1use std::fmt;
4use std::sync::Arc;
5
6use bytes::Bytes;
7use tokio::sync::OnceCell;
8
9use crate::Result;
10use crate::backend::Client;
11use crate::dataset::{Data, Dataset, Datasets};
12use crate::frontier::memory::InMemFrontier;
13use crate::frontier::queue::Queue;
14use crate::request::{Request, Response, ResponseExt};
15
16pub struct Context<C> {
25 request: Request,
26 client: C,
27 queue: Queue,
28 datasets: Arc<Datasets>,
29 response: OnceCell<Response<()>>,
30}
31
32impl<C> Context<C> {
33 #[must_use]
39 pub fn new(request: Request, client: C) -> Self {
40 let queue = Queue::new(InMemFrontier::fifo()).child_of(&request);
41 Self::from_parts(request, client, queue, Arc::default())
42 }
43
44 pub(crate) fn from_parts(
45 request: Request,
46 client: C,
47 queue: Queue,
48 datasets: Arc<Datasets>,
49 ) -> Self {
50 Self {
51 request,
52 client,
53 queue,
54 datasets,
55 response: OnceCell::new(),
56 }
57 }
58
59 #[must_use]
61 pub fn with_queue(mut self, queue: &Queue) -> Self {
62 self.queue = queue.child_of(&self.request);
63 self
64 }
65
66 #[must_use]
68 pub fn with_dataset<T, D>(mut self, dataset: D) -> Self
69 where
70 T: Send + 'static,
71 D: Dataset<T>,
72 {
73 Arc::make_mut(&mut self.datasets).insert(dataset);
74 self
75 }
76
77 #[must_use]
79 pub const fn request(&self) -> &Request {
80 &self.request
81 }
82
83 pub const fn request_mut(&mut self) -> &mut Request {
85 &mut self.request
86 }
87
88 #[must_use]
90 pub const fn client(&self) -> &C {
91 &self.client
92 }
93
94 #[must_use]
96 pub const fn queue(&self) -> &Queue {
97 &self.queue
98 }
99
100 pub fn dataset<T: Send + 'static>(&self) -> Result<Data<T>> {
107 self.datasets.get()
108 }
109
110 pub async fn response(&self) -> Result<&Response<()>>
121 where
122 C: Client,
123 {
124 self.response
125 .get_or_try_init(|| async {
126 let response = self.client.fetch(&self.request).await?;
127 if let Some(uri) = response.final_uri() {
128 self.queue.rebase(uri.clone());
129 }
130 Ok(response)
131 })
132 .await
133 }
134
135 pub async fn body(&self) -> Result<Bytes>
142 where
143 C: Client,
144 {
145 self.response().await?;
146 self.client.body().await
147 }
148}
149
150impl<C> fmt::Debug for Context<C> {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 f.debug_struct("Context")
153 .field("method", self.request.method())
154 .field("uri", self.request.uri())
155 .field("queue", &self.queue)
156 .field("fetched", &self.response.initialized())
157 .finish_non_exhaustive()
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use std::future::Future;
164 use std::sync::atomic::{AtomicUsize, Ordering};
165
166 use super::*;
167 use crate::backend::Noop;
168 use crate::dataset::InMemDataset;
169 use crate::frontier::seed::Seed;
170 use crate::request::{Depth, RequestExt};
171
172 #[tokio::test]
173 async fn standalone_context() {
174 let request = Seed::get("https://example.com/").unwrap().into_request();
175 let cx = Context::new(request, Noop).with_dataset(InMemDataset::<u32>::new());
176
177 cx.dataset::<u32>().unwrap().write(1).await.unwrap();
178 assert!(cx.dataset::<String>().is_err());
179
180 cx.queue().follow("https://example.com/next").await.unwrap();
181 assert_eq!(cx.queue().size_hint(), Some(1));
182 assert_eq!(cx.request().depth(), Depth::ROOT);
183
184 let response = cx.response().await.unwrap();
185 assert!(response.status().is_success());
186 }
187
188 struct Counting {
191 fetches: AtomicUsize,
192 fail: usize,
193 body: std::sync::Mutex<&'static str>,
194 }
195
196 impl Client for Counting {
197 fn fetch(&self, _request: &Request) -> impl Future<Output = Result<Response<()>>> + Send {
198 let fetch = self.fetches.fetch_add(1, Ordering::SeqCst);
199 std::future::ready(if fetch < self.fail {
200 Err(crate::Error::new(crate::ErrorKind::Http))
201 } else {
202 Ok(Response::new(()))
203 })
204 }
205
206 fn body(&self) -> impl Future<Output = Result<Bytes>> + Send {
207 let body = *self.body.lock().unwrap();
208 std::future::ready(Ok(Bytes::from_static(body.as_bytes())))
209 }
210 }
211
212 #[tokio::test]
213 async fn fetches_once_and_reads_the_current_body() {
214 let request = Seed::get("https://example.com/").unwrap().into_request();
215 let client = Counting {
216 fetches: AtomicUsize::new(0),
217 fail: 1,
218 body: std::sync::Mutex::new("loaded"),
219 };
220 let cx = Context::new(request, client);
221
222 assert!(cx.response().await.is_err());
224 assert_eq!(cx.body().await.unwrap(), "loaded");
225 *cx.client().body.lock().unwrap() = "clicked";
226 assert_eq!(cx.body().await.unwrap(), "clicked");
227 assert!(cx.response().await.unwrap().status().is_success());
228 assert_eq!(cx.client().fetches.load(Ordering::SeqCst), 2);
229 }
230}