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