cherenkov_model_data/discovery/dispatch/
cursor.rs1use super::StoreId;
4use crate::discovery::{Filter, SearchQuery};
5use anyhow::{Context, Result, ensure};
6use serde::{Deserialize, Serialize};
7
8#[derive(Serialize)]
11#[serde(tag = "operation", rename_all = "snake_case")]
12pub(super) enum RequestScope {
13 Search {
15 text: Option<String>,
16 filters: Vec<Filter>,
17 limit: usize,
18 },
19 Enumerate { limit: usize },
21}
22
23#[derive(Serialize, Deserialize)]
25#[serde(deny_unknown_fields)]
26struct Cursor {
27 store: StoreId,
28 request_key: String,
30 position: String,
31}
32
33impl RequestScope {
34 pub(super) fn search(query: &SearchQuery) -> Self {
36 Self::Search {
37 text: query.text.clone(),
38 filters: query.filters.clone(),
39 limit: query.page.limit,
40 }
41 }
42
43 pub(super) fn unwrap_cursor(
45 &self,
46 store: &StoreId,
47 cursor: Option<&str>,
48 ) -> Result<Option<String>> {
49 let Some(cursor) = cursor else {
50 return Ok(None);
51 };
52 let cursor: Cursor = serde_json::from_str(cursor).context("invalid discovery cursor")?;
53
54 ensure!(
55 cursor.store == *store,
56 "discovery cursor belongs to another store"
57 );
58 ensure!(
59 cursor.request_key == self.key()?,
60 "discovery cursor belongs to another request"
61 );
62
63 Ok(Some(cursor.position))
64 }
65
66 pub(super) fn wrap_cursor(
68 &self,
69 store: &StoreId,
70 position: Option<String>,
71 ) -> Result<Option<String>> {
72 position
73 .map(|position| {
74 serde_json::to_string(&Cursor {
75 store: store.clone(),
76 request_key: self.key()?,
77 position,
78 })
79 .context("encoding discovery cursor")
80 })
81 .transpose()
82 }
83
84 fn key(&self) -> Result<String> {
87 serde_json::to_string(self).context("encoding discovery request key")
88 }
89}