mcpls_core/bridge/
signals.rs1use schemars::JsonSchema;
7use serde::Serialize;
8
9use super::{IndexingState, NotificationCache};
10use crate::config::ServerId;
11use crate::redaction::{Redactions, ServerText};
12
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]
19pub struct IndexingSignal {
20 pub indexing_in_progress: bool,
23}
24
25impl IndexingSignal {
26 #[must_use]
31 pub fn sample(cache: &NotificationCache, route_id: Option<&ServerId>) -> Self {
32 Self {
33 indexing_in_progress: route_id
34 .is_some_and(|id| cache.indexing_state(id) == IndexingState::Loading),
35 }
36 }
37
38 #[must_use]
41 pub const fn union(self, later: Self) -> Self {
42 Self {
43 indexing_in_progress: self.indexing_in_progress || later.indexing_in_progress,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, JsonSchema)]
50pub struct Indexed<T> {
51 #[serde(flatten)]
53 pub result: T,
54 #[serde(flatten)]
56 pub indexing: IndexingSignal,
57}
58
59impl<T> Indexed<T> {
60 #[must_use]
62 pub const fn new(result: T, indexing: IndexingSignal) -> Self {
63 Self { result, indexing }
64 }
65}
66
67impl<T: ServerText> ServerText for Indexed<T> {
68 fn redact_server_text(&mut self, redactions: &Redactions) {
69 let Self {
70 result,
71 indexing: _,
72 } = self;
73 result.redact_server_text(redactions);
74 }
75}
76
77#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, JsonSchema)]
81pub struct RouteSignals {
82 pub push_notifications_degraded: bool,
87 #[serde(flatten)]
89 pub indexing: IndexingSignal,
90}
91
92impl RouteSignals {
93 #[must_use]
95 pub fn sample(cache: &NotificationCache, route_id: Option<&ServerId>) -> Self {
96 Self {
97 push_notifications_degraded: route_id.is_some_and(|id| cache.is_push_degraded(id)),
98 indexing: IndexingSignal::sample(cache, route_id),
99 }
100 }
101
102 #[must_use]
105 pub const fn union(self, later: Self) -> Self {
106 Self {
107 push_notifications_degraded: self.push_notifications_degraded
108 || later.push_notifications_degraded,
109 indexing: self.indexing.union(later.indexing),
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_unknown_route_samples_no_signal() {
120 let cache = NotificationCache::new();
121 assert_eq!(RouteSignals::sample(&cache, None), RouteSignals::default());
122 let id = ServerId::from_static("rust");
123 assert_eq!(
124 RouteSignals::sample(&cache, Some(&id)),
125 RouteSignals::default()
126 );
127 }
128
129 #[test]
130 fn test_union_keeps_a_signal_set_in_either_sample() {
131 let degraded = RouteSignals {
132 push_notifications_degraded: true,
133 ..RouteSignals::default()
134 };
135 let loading = RouteSignals {
136 indexing: IndexingSignal {
137 indexing_in_progress: true,
138 },
139 ..RouteSignals::default()
140 };
141 let both = degraded.union(loading);
142 assert!(both.push_notifications_degraded);
143 assert!(both.indexing.indexing_in_progress);
144 assert_eq!(loading.union(degraded), both);
145 }
146
147 #[test]
148 fn test_indexed_flattens_the_signal_beside_the_result() {
149 #[derive(Serialize)]
150 struct Items {
151 items: Vec<u8>,
152 }
153 let indexed = Indexed::new(
154 Items { items: vec![1] },
155 IndexingSignal {
156 indexing_in_progress: true,
157 },
158 );
159 assert_eq!(
160 serde_json::to_string(&indexed).unwrap(),
161 r#"{"items":[1],"indexing_in_progress":true}"#
162 );
163 }
164
165 #[test]
166 fn test_indexing_half_flattens_into_one_object() {
167 let signals = RouteSignals {
168 push_notifications_degraded: true,
169 indexing: IndexingSignal {
170 indexing_in_progress: true,
171 },
172 };
173 assert_eq!(
174 serde_json::to_string(&signals).unwrap(),
175 r#"{"push_notifications_degraded":true,"indexing_in_progress":true}"#
176 );
177 assert_eq!(
178 serde_json::to_string(&IndexingSignal::default()).unwrap(),
179 r#"{"indexing_in_progress":false}"#
180 );
181 }
182}