cherenkov_model_data/discovery/
dispatch.rs1use super::{
4 DiscoveryCapabilities, ModelCandidate, PageRequest, SearchPage, SearchQuery, StoreDiscovery,
5};
6use anyhow::{Context, Result, ensure};
7use serde::{Deserialize, Serialize};
8
9mod cursor;
10use cursor::RequestScope;
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(transparent)]
18pub struct StoreId(pub String);
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct StoredCandidate {
23 pub store: StoreId,
25 #[serde(flatten)]
27 pub candidate: ModelCandidate,
28}
29
30pub struct ValidatedSearch<'a>(&'a SearchQuery);
34
35impl ValidatedSearch<'_> {
36 pub fn query(&self) -> &SearchQuery {
38 self.0
39 }
40}
41
42pub struct ValidatedPage<'a>(&'a PageRequest);
45
46impl ValidatedPage<'_> {
47 pub fn page(&self) -> &PageRequest {
49 self.0
50 }
51}
52
53pub struct Discovery<'a> {
58 store: StoreId,
59 adapter: &'a dyn StoreDiscovery,
60}
61
62impl<'a> Discovery<'a> {
63 pub fn new(store: StoreId, adapter: &'a dyn StoreDiscovery) -> Self {
66 Self { store, adapter }
67 }
68
69 pub fn capabilities(&self) -> DiscoveryCapabilities {
71 self.adapter.capabilities()
72 }
73
74 pub fn search(&self, query: &SearchQuery) -> Result<SearchPage<StoredCandidate>> {
76 let capabilities = self.capabilities();
77 let schema = capabilities
78 .search
79 .context("this store does not support search")?;
80
81 schema.validate(query)?;
82
83 let scope = RequestScope::search(query);
84 let mut native = query.clone();
85
86 native.page.cursor = scope.unwrap_cursor(&self.store, query.page.cursor.as_deref())?;
87
88 let page = self.adapter.search(ValidatedSearch(&native))?;
89
90 self.finish(page, &scope, &native.page)
91 }
92
93 pub fn enumerate(&self, request: &PageRequest) -> Result<SearchPage<StoredCandidate>> {
95 let maximum = self
96 .capabilities()
97 .enumeration_max_page_size
98 .context("this store does not support enumeration")?;
99
100 request.validate(maximum)?;
101
102 let scope = RequestScope::Enumerate {
103 limit: request.limit,
104 };
105 let mut native = request.clone();
106
107 native.cursor = scope.unwrap_cursor(&self.store, request.cursor.as_deref())?;
108
109 let page = self.adapter.enumerate(ValidatedPage(&native))?;
110
111 self.finish(page, &scope, &native)
112 }
113
114 fn finish(
117 &self,
118 page: SearchPage,
119 scope: &RequestScope,
120 request: &PageRequest,
121 ) -> Result<SearchPage<StoredCandidate>> {
122 ensure!(
123 page.items.len() <= request.limit,
124 "store exceeded the requested page limit"
125 );
126 ensure!(
127 page.next_cursor.is_none() || page.next_cursor != request.cursor,
128 "store returned a continuation cursor that did not advance"
129 );
130
131 Ok(SearchPage {
132 items: page
133 .items
134 .into_iter()
135 .map(|candidate| StoredCandidate {
136 store: self.store.clone(),
137 candidate,
138 })
139 .collect(),
140 next_cursor: scope.wrap_cursor(&self.store, page.next_cursor)?,
141 total: page.total,
142 gaps: page.gaps,
143 })
144 }
145}