1use super::{BoxFuture, Context, Error, activity, boxed, empty};
14use http::StatusCode;
15use serde_json::Value;
16use std::future::Future;
17use std::sync::Arc;
18use url::Url;
19
20#[derive(Clone, Debug, Default, PartialEq)]
22pub struct Page {
23 pub items: Vec<Value>,
25 pub next: Option<String>,
27 pub prev: Option<String>,
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
33pub enum First {
34 At(String),
36 Hidden,
39}
40
41type PageFn<D> = Arc<
42 dyn Fn(Context<D>, String, Option<String>) -> BoxFuture<'static, Result<Option<Page>, Error>>
43 + Send
44 + Sync,
45>;
46type CountFn<D> =
47 Arc<dyn Fn(Context<D>, String) -> BoxFuture<'static, Result<Option<u64>, Error>> + Send + Sync>;
48type FirstFn<D> = Arc<
49 dyn Fn(Context<D>, String) -> BoxFuture<'static, Result<Option<First>, Error>> + Send + Sync,
50>;
51type UriFn<D> =
52 Arc<dyn Fn(Context<D>, String) -> BoxFuture<'static, Result<Option<Url>, Error>> + Send + Sync>;
53type ContextFn = Arc<dyn Fn(&[Value]) -> Value + Send + Sync>;
54type ToQueryFn = Arc<dyn Fn(&str) -> String + Send + Sync>;
55type FromQueryFn = Arc<dyn Fn(&str) -> Option<String> + Send + Sync>;
56type LastFn<D> = Arc<
57 dyn Fn(Context<D>, String) -> BoxFuture<'static, Result<Option<String>, Error>> + Send + Sync,
58>;
59
60pub struct Collection<D> {
62 page: PageFn<D>,
63 count: Option<CountFn<D>>,
64 first: Option<FirstFn<D>>,
65 last: Option<LastFn<D>>,
66 uri: Option<UriFn<D>>,
67 ordered: bool,
68 context: Option<ContextFn>,
69 query: Option<(ToQueryFn, FromQueryFn)>,
70 count_pages: bool,
71}
72
73impl<D> Clone for Collection<D> {
74 fn clone(&self) -> Self {
75 Self {
76 page: self.page.clone(),
77 count: self.count.clone(),
78 first: self.first.clone(),
79 last: self.last.clone(),
80 uri: self.uri.clone(),
81 ordered: self.ordered,
82 context: self.context.clone(),
83 query: self.query.clone(),
84 count_pages: self.count_pages,
85 }
86 }
87}
88
89impl<D: Clone + Send + Sync + 'static> Collection<D> {
90 pub fn new<F, Fut, E>(page: F) -> Self
97 where
98 F: Fn(Context<D>, String, Option<String>) -> Fut + Send + Sync + 'static,
99 Fut: Future<Output = Result<Option<Page>, E>> + Send + 'static,
100 E: Into<Error>,
101 {
102 let page = boxed(move |(context, identifier, cursor)| page(context, identifier, cursor));
103 Self {
104 page: Arc::new(move |context, identifier, cursor| page((context, identifier, cursor))),
105 count: None,
106 first: None,
107 last: None,
108 uri: None,
109 ordered: true,
110 context: None,
111 query: None,
112 count_pages: false,
113 }
114 }
115
116 #[must_use]
121 pub fn unordered(mut self) -> Self {
122 self.ordered = false;
123 self
124 }
125
126 #[must_use]
134 pub fn context(mut self, context: impl Fn(&[Value]) -> Value + Send + Sync + 'static) -> Self {
135 self.context = Some(Arc::new(context));
136 self
137 }
138
139 #[must_use]
147 pub fn page_query(
148 mut self,
149 to_query: impl Fn(&str) -> String + Send + Sync + 'static,
150 from_query: impl Fn(&str) -> Option<String> + Send + Sync + 'static,
151 ) -> Self {
152 self.query = Some((Arc::new(to_query), Arc::new(from_query)));
153 self
154 }
155
156 #[must_use]
159 pub fn count_pages(mut self) -> Self {
160 self.count_pages = true;
161 self
162 }
163
164 fn page_uri(&self, collection: &Url, cursor: &str) -> Url {
166 let mut url = collection.clone();
167 match &self.query {
168 Some((to_query, _)) => {
169 let query = to_query(cursor);
170 url.set_query((!query.is_empty()).then_some(query.as_str()));
171 }
172 None => {
173 url.query_pairs_mut().clear().append_pair("cursor", cursor);
174 }
175 }
176 url
177 }
178
179 fn with_context(&self, document: &CollectionDocument) -> Value {
182 let value = document.to_value();
183 let Some(context) = &self.context else {
184 return value;
185 };
186 let mut with_context = serde_json::Map::new();
187 with_context.insert(
188 "@context".into(),
189 context(document.items.as_deref().unwrap_or_default()),
190 );
191 if let Value::Object(members) = value {
192 with_context.extend(members);
193 }
194 Value::Object(with_context)
195 }
196
197 #[must_use]
199 pub fn count<F, Fut, E>(mut self, count: F) -> Self
200 where
201 F: Fn(Context<D>, String) -> Fut + Send + Sync + 'static,
202 Fut: Future<Output = Result<Option<u64>, E>> + Send + 'static,
203 E: Into<Error>,
204 {
205 let count = boxed(move |(context, identifier)| count(context, identifier));
206 self.count = Some(Arc::new(move |context, identifier| {
207 count((context, identifier))
208 }));
209 self
210 }
211
212 #[must_use]
215 pub fn first_cursor<F, Fut, E>(mut self, first: F) -> Self
216 where
217 F: Fn(Context<D>, String) -> Fut + Send + Sync + 'static,
218 Fut: Future<Output = Result<Option<First>, E>> + Send + 'static,
219 E: Into<Error>,
220 {
221 let first = boxed(move |(context, identifier)| first(context, identifier));
222 self.first = Some(Arc::new(move |context, identifier| {
223 first((context, identifier))
224 }));
225 self
226 }
227
228 #[must_use]
230 pub fn last_cursor<F, Fut, E>(mut self, last: F) -> Self
231 where
232 F: Fn(Context<D>, String) -> Fut + Send + Sync + 'static,
233 Fut: Future<Output = Result<Option<String>, E>> + Send + 'static,
234 E: Into<Error>,
235 {
236 let last = boxed(move |(context, identifier)| last(context, identifier));
237 self.last = Some(Arc::new(move |context, identifier| {
238 last((context, identifier))
239 }));
240 self
241 }
242
243 #[must_use]
249 pub fn uri<F, Fut, E>(mut self, uri: F) -> Self
250 where
251 F: Fn(Context<D>, String) -> Fut + Send + Sync + 'static,
252 Fut: Future<Output = Result<Option<Url>, E>> + Send + 'static,
253 E: Into<Error>,
254 {
255 let uri = boxed(move |(context, identifier)| uri(context, identifier));
256 self.uri = Some(Arc::new(move |context, identifier| {
257 uri((context, identifier))
258 }));
259 self
260 }
261
262 pub(super) async fn serve(
263 &self,
264 context: &Context<D>,
265 kind: &str,
266 identifier: &str,
267 query: Option<&str>,
268 ) -> Result<http::Response<Vec<u8>>, Error> {
269 let own = match &self.uri {
270 Some(uri) => uri(context.clone(), identifier.to_owned()).await?,
271 None => None,
272 };
273 let uri = match own {
274 Some(uri) => uri,
275 None => context.collection_uri(kind, identifier)?,
276 };
277 let cursor = match &self.query {
278 Some((_, from_query)) => from_query(query.unwrap_or_default()),
279 None => query.and_then(|query| {
280 url::form_urlencoded::parse(query.as_bytes())
281 .find(|(name, _)| name == "cursor")
282 .map(|(_, value)| value.into_owned())
283 }),
284 };
285 let identifier = identifier.to_owned();
286
287 if let Some(cursor) = cursor {
288 let Some(page) =
289 (self.page)(context.clone(), identifier.clone(), Some(cursor.clone())).await?
290 else {
291 return Ok(empty(StatusCode::NOT_FOUND));
292 };
293 let total_items = match &self.count {
294 Some(count) if self.count_pages => {
295 count(context.clone(), identifier.clone()).await?
296 }
297 _ => None,
298 };
299 let document = CollectionDocument {
300 id: Some(self.page_uri(&uri, &cursor)),
301 total_items,
302 ordered: self.ordered,
303 part_of: Some(uri.clone()),
304 next: page.next.map(|next| self.page_uri(&uri, &next)),
305 prev: page.prev.map(|prev| self.page_uri(&uri, &prev)),
306 items: Some(page.items),
307 ..CollectionDocument::default()
308 };
309 return Ok(activity(StatusCode::OK, self.with_context(&document)));
310 }
311
312 let Some(first) = &self.first else {
313 let Some(page) = (self.page)(context.clone(), identifier.clone(), None).await? else {
314 return Ok(empty(StatusCode::NOT_FOUND));
315 };
316 let total = match &self.count {
317 Some(count) => count(context.clone(), identifier).await?,
318 None => None,
319 }
320 .unwrap_or(page.items.len() as u64);
321 let document = CollectionDocument {
322 id: Some(uri),
323 ordered: self.ordered,
324 total_items: Some(total),
325 items: Some(page.items),
326 ..CollectionDocument::default()
327 };
328 return Ok(activity(StatusCode::OK, self.with_context(&document)));
329 };
330
331 let Some(start) = first(context.clone(), identifier.clone()).await? else {
332 return Ok(empty(StatusCode::NOT_FOUND));
333 };
334 let mut document = CollectionDocument {
335 ordered: self.ordered,
336 ..CollectionDocument::default()
337 };
338 if let Some(count) = &self.count {
339 document.total_items = count(context.clone(), identifier.clone()).await?;
340 }
341 if let First::At(cursor) = start {
342 document.first = Some(self.page_uri(&uri, &cursor).as_str().into());
343 if let Some(last) = &self.last
344 && let Some(cursor) = last(context.clone(), identifier).await?
345 {
346 document.last = Some(self.page_uri(&uri, &cursor).as_str().into());
347 }
348 }
349 document.id = Some(uri);
350 Ok(activity(StatusCode::OK, self.with_context(&document)))
351 }
352}
353
354#[derive(Clone, Debug, Default, PartialEq)]
369pub struct CollectionDocument {
370 pub id: Option<Url>,
372 pub ordered: bool,
376 pub page: bool,
378 pub total_items: Option<u64>,
380 pub next: Option<Url>,
382 pub prev: Option<Url>,
384 pub part_of: Option<Url>,
386 pub first: Option<Value>,
388 pub last: Option<Value>,
390 pub items: Option<Vec<Value>>,
392}
393
394impl CollectionDocument {
395 #[must_use]
397 pub fn is_page(&self) -> bool {
398 self.page || self.part_of.is_some()
399 }
400
401 #[must_use]
403 pub fn type_name(&self) -> &'static str {
404 match (self.ordered, self.is_page()) {
405 (true, true) => "OrderedCollectionPage",
406 (true, false) => "OrderedCollection",
407 (false, true) => "CollectionPage",
408 (false, false) => "Collection",
409 }
410 }
411
412 #[must_use]
416 pub fn to_value(&self) -> Value {
417 let mut document = serde_json::Map::new();
418 if let Some(id) = &self.id {
419 document.insert("id".into(), id.as_str().into());
420 }
421 document.insert("type".into(), self.type_name().into());
422 if let Some(total) = self.total_items {
423 document.insert("totalItems".into(), total.into());
424 }
425 if let Some(next) = &self.next {
426 document.insert("next".into(), next.as_str().into());
427 }
428 if let Some(prev) = &self.prev {
429 document.insert("prev".into(), prev.as_str().into());
430 }
431 if let Some(part_of) = &self.part_of {
432 document.insert("partOf".into(), part_of.as_str().into());
433 }
434 if let Some(first) = &self.first {
435 document.insert("first".into(), first.clone());
436 }
437 if let Some(last) = &self.last {
438 document.insert("last".into(), last.clone());
439 }
440 if let Some(items) = &self.items {
441 let key = if self.ordered {
442 "orderedItems"
443 } else {
444 "items"
445 };
446 document.insert(key.into(), Value::Array(items.clone()));
447 }
448 Value::Object(document)
449 }
450}