gpui_query/core/infinite_query/accessors.rs
1//! Accessor (getter / setter) methods for [`InfiniteQueryResource`].
2
3use std::collections::VecDeque;
4use std::sync::Arc;
5
6use super::{FetchDirection, InfiniteQueryResource};
7use crate::core::{
8 CachePolicy, QueryKey, QuerySignal, QueryStatus, QueryTimestamp, RequestId, RequestPolicy,
9 RetryPolicy,
10};
11
12// ── Accessors ────────────────────────────────────────────────────────────
13
14impl<T, E> InfiniteQueryResource<T, E> {
15 /// All loaded pages, in order from first to last.
16 ///
17 /// Pages are stored internally as `Arc<T>` (audit #5) so that fetchers can
18 /// receive a cheap `Arc::clone` via [`first_page_arc`](Self::first_page_arc) /
19 /// [`last_page_arc`](Self::last_page_arc) instead of copying the page data.
20 /// Most call sites only need a `&T` view — use [`first_page`](Self::first_page)
21 /// / [`last_page`](Self::last_page), or iterate with `.iter().map(|a| a.as_ref())`.
22 ///
23 /// **Note**: When `status()` is `Failure`, previously loaded pages are still
24 /// present and valid — the failure applies only to the most recent page fetch.
25 /// Use [`is_page_data_valid`](Self::is_page_data_valid) to check whether the
26 /// current page data can be relied upon.
27 pub fn pages(&self) -> &VecDeque<Arc<T>> {
28 &self.pages
29 }
30
31 /// Number of loaded pages.
32 pub fn page_count(&self) -> usize {
33 self.pages.len()
34 }
35
36 /// The first loaded page, if any (borrowed view).
37 pub fn first_page(&self) -> Option<&T> {
38 self.pages.front().map(|a| a.as_ref())
39 }
40
41 /// The last loaded page, if any (borrowed view).
42 pub fn last_page(&self) -> Option<&T> {
43 self.pages.back().map(|a| a.as_ref())
44 }
45
46 /// Cheap `Arc::clone` of the first page, if any (audit #5).
47 ///
48 /// Hand this to a `fetch_previous_page` fetcher instead of cloning the full
49 /// page data — only the refcount is bumped.
50 pub fn first_page_arc(&self) -> Option<Arc<T>> {
51 self.pages.front().cloned()
52 }
53
54 /// Cheap `Arc::clone` of the last page, if any (audit #5).
55 ///
56 /// Hand this to a `fetch_next_page` fetcher instead of cloning the full
57 /// page data — only the refcount is bumped.
58 pub fn last_page_arc(&self) -> Option<Arc<T>> {
59 self.pages.back().cloned()
60 }
61
62 /// Whether there are more pages after the last loaded page.
63 pub fn has_next_page(&self) -> bool {
64 self.has_next_page
65 }
66
67 /// Whether there are more pages before the first loaded page.
68 pub fn has_previous_page(&self) -> bool {
69 self.has_previous_page
70 }
71
72 /// Whether a `fetch_next_page` request is in flight.
73 pub fn is_fetching_next_page(&self) -> bool {
74 matches!(
75 self.fetching_direction,
76 Some(super::lifecycle::PageDirection::Next)
77 )
78 }
79
80 /// Whether a `fetch_previous_page` request is in flight.
81 pub fn is_fetching_previous_page(&self) -> bool {
82 matches!(
83 self.fetching_direction,
84 Some(super::lifecycle::PageDirection::Previous)
85 )
86 }
87
88 /// Maximum number of pages to retain.
89 pub fn max_pages(&self) -> Option<usize> {
90 self.max_pages
91 }
92
93 /// The fetch direction mode for this query.
94 ///
95 /// **Audit 3**: Controls the default assumptions for `has_next_page` and
96 /// `has_previous_page` after construction and after `reset()`.
97 pub fn direction(&self) -> FetchDirection {
98 self.direction
99 }
100
101 /// Current status.
102 pub fn status(&self) -> QueryStatus {
103 self.status
104 }
105
106 /// Most recent error.
107 pub fn error(&self) -> Option<&E> {
108 self.error.as_ref()
109 }
110
111 /// Whether loading.
112 pub fn is_loading(&self) -> bool {
113 self.status.is_loading()
114 }
115
116 /// Cache key.
117 pub fn key(&self) -> &QueryKey {
118 &self.key
119 }
120
121 /// Active request id.
122 pub fn active_request_id(&self) -> Option<RequestId> {
123 self.active_request_id
124 }
125
126 /// Cache policy.
127 pub fn cache_policy(&self) -> CachePolicy {
128 self.cache_policy
129 }
130
131 /// Request policy.
132 pub fn request_policy(&self) -> RequestPolicy {
133 self.request_policy
134 }
135
136 /// Set the cache policy.
137 ///
138 /// This allows policy updates on existing resources when the same key is
139 /// reused with different policies (e.g., a different TTL).
140 pub fn set_cache_policy(&mut self, policy: CachePolicy) {
141 self.cache_policy = policy;
142 }
143
144 /// Set the request policy.
145 ///
146 /// This allows policy updates on existing resources when the same key is
147 /// reused with different request behavior.
148 pub fn set_request_policy(&mut self, policy: RequestPolicy) {
149 self.request_policy = policy;
150 }
151
152 /// The retry policy for page fetches.
153 pub fn retry_policy(&self) -> &RetryPolicy {
154 &self.retry_policy
155 }
156
157 /// Set the retry policy.
158 ///
159 /// Stored by `use_infinite_query` from [`InfiniteQueryOptions::retry_policy`]
160 /// so that fetch helpers can read it from the entity.
161 pub fn set_retry_policy(&mut self, policy: RetryPolicy) {
162 self.retry_policy = policy;
163 }
164
165 /// When the current request started (ms).
166 pub fn started_at_ms(&self) -> Option<u64> {
167 self.started_at.map(QueryTimestamp::as_millis)
168 }
169
170 /// When data was last updated (ms).
171 pub fn last_updated_at_ms(&self) -> Option<u64> {
172 self.last_updated_at.map(QueryTimestamp::as_millis)
173 }
174
175 /// Cache age in milliseconds (L6).
176 ///
177 /// Mirrors [`QueryResource::cache_age_ms`]: returns `None` when there is
178 /// no recorded `last_updated_at`, and also `None` on clock skew
179 /// (`now_ms` before the recorded timestamp) via `checked_sub`. Used by
180 /// `InfiniteQueryBucket::collect_diagnostics` so the infinite diagnostic
181 /// matches the regular query's `cache_age_ms` behavior (the previous
182 /// inline `saturating_sub` returned `Some(0)` on skew).
183 ///
184 /// [`QueryResource::cache_age_ms`]: crate::core::QueryResource::cache_age_ms
185 pub fn cache_age_ms(&self, now_ms: u64) -> Option<u64> {
186 QueryTimestamp::from(now_ms).elapsed_since(self.last_updated_at?)
187 }
188
189 /// Total cache hits.
190 pub fn cache_hits(&self) -> u64 {
191 self.cache_hits
192 }
193
194 /// Total cancelled requests.
195 pub fn cancelled_count(&self) -> u64 {
196 self.cancelled_count
197 }
198
199 /// Total ignored results (completed requests whose ID no longer matched).
200 ///
201 /// Incremented when `complete_page_success` or `complete_page_failure`
202 /// receives a stale request ID, i.e. the result was produced by a fetch
203 /// that was subsequently replaced by a newer one.
204 pub fn ignored_results(&self) -> u64 {
205 self.ignored_results
206 }
207
208 /// Number of retry attempts for the current page fetch.
209 pub fn retry_count(&self) -> u32 {
210 self.retry_count
211 }
212
213 /// Increment the retry counter.
214 pub fn increment_retry(&mut self) {
215 self.retry_count = self.retry_count.saturating_add(1);
216 }
217
218 /// Increment the ignored-results counter.
219 ///
220 /// Mirrors `QueryResource::mark_ignored_result` so the client layer's
221 /// bulk-cancel path can bump `ignored_results` for infinite queries the
222 /// same way it does for regular queries (M5 core half).
223 pub fn mark_ignored_result(&mut self) {
224 self.ignored_results = self.ignored_results.saturating_add(1);
225 }
226
227 /// Reset the retry counter to zero.
228 pub fn reset_retry_count(&mut self) {
229 self.retry_count = 0;
230 }
231
232 /// Whether any pages have been loaded.
233 pub fn has_data(&self) -> bool {
234 !self.pages.is_empty()
235 }
236
237 /// Whether the currently loaded page data is valid.
238 ///
239 /// Returns `true` when:
240 /// - Status is `Success` (pages are up to date), or
241 /// - Status is `LoadingWithData` or `LoadingEmpty` (pages from a previous
242 /// successful fetch are still valid while a new page is being fetched).
243 ///
244 /// Returns `false` when:
245 /// - Status is `Idle` (no pages have been fetched yet), or
246 /// - Status is `Cancelled` (data was explicitly cleared).
247 ///
248 /// **Important**: When status is `Failure`, this returns `true` if pages were
249 /// previously loaded. A `Failure` status means the *last page fetch* failed,
250 /// but all previously loaded pages remain valid. This is distinct from
251 /// `QueryResource` where `Failure` invalidates the single data slot.
252 pub fn is_page_data_valid(&self) -> bool {
253 match self.status {
254 QueryStatus::Success | QueryStatus::LoadingWithData => true,
255 QueryStatus::Failure => !self.pages.is_empty(),
256 QueryStatus::LoadingEmpty | QueryStatus::Idle | QueryStatus::Cancelled => false,
257 }
258 }
259
260 /// Cancellation signal.
261 pub fn signal(&self) -> Option<&QuerySignal> {
262 self.signal.as_ref()
263 }
264}