clankerdiff_client/
state.rs1use crate::{
2 ClientError, DiffScope, DiffSnapshot, RemoteError, ReviewCapabilities,
3 protocol::{client::capabilities, server::ServerEvent, shared::Event},
4};
5use std::sync::Arc;
6
7#[derive(Debug, Clone, Copy, Default)]
8pub enum ReconnectPolicy {
9 Never,
10 #[default]
11 Retry,
12}
13
14#[derive(Debug, Clone, Default)]
15pub struct ClientOptions {
16 pub scope: DiffScope,
17 pub reconnect: ReconnectPolicy,
18}
19
20impl From<DiffScope> for ClientOptions {
21 fn from(scope: DiffScope) -> Self {
22 Self {
23 scope,
24 ..Self::default()
25 }
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum ConnectionState {
31 Connecting,
32 Connected,
33 Failed(ClientError),
34}
35
36#[derive(Debug, Clone)]
37pub struct ClientState {
38 pub snapshot: Option<Arc<DiffSnapshot>>,
39 pub connection: ConnectionState,
40 pub capabilities: ReviewCapabilities,
41 pub error: Option<RemoteError>,
42}
43
44impl ClientState {
45 #[must_use]
46 pub fn apply(self, event: &ServerEvent) -> Self {
47 match event {
48 Event::Initialize { .. } => Self {
49 error: None,
50 ..self
51 }
52 .with_capabilities(),
53 Event::Document(snapshot) => Self {
54 snapshot: Some(snapshot.clone()),
55 connection: ConnectionState::Connected,
56 ..self
57 }
58 .with_capabilities(),
59 Event::Health { error } => Self {
60 error: error.clone(),
61 ..self
62 },
63 Event::Error(error) => Self {
64 connection: ConnectionState::Failed(ClientError::Remote(error.clone())),
65 ..self
66 }
67 .with_capabilities(),
68 Event::RequestResult(_) => self,
69 }
70 }
71
72 pub(crate) fn set_connection(&mut self, connection: ConnectionState) {
73 self.connection = connection;
74 self.refresh_capabilities();
75 }
76
77 fn with_capabilities(mut self) -> Self {
78 self.refresh_capabilities();
79 self
80 }
81
82 fn refresh_capabilities(&mut self) {
83 let connected = matches!(self.connection, ConnectionState::Connected);
84 self.capabilities = capabilities(connected, self.snapshot.is_some());
85 }
86
87 #[must_use]
88 pub fn snapshot_if_changed(
89 &self,
90 installed: &mut Option<Arc<DiffSnapshot>>,
91 ) -> Option<&DiffSnapshot> {
92 let snapshot = self.snapshot.as_ref()?;
93 if installed
94 .as_ref()
95 .is_some_and(|old| Arc::ptr_eq(old, snapshot))
96 {
97 return None;
98 }
99 *installed = Some(snapshot.clone());
100 Some(snapshot)
101 }
102
103 #[must_use]
104 pub fn status(&self) -> Option<String> {
105 match &self.connection {
106 ConnectionState::Connected => self.error.as_ref().map(ToString::to_string),
107 ConnectionState::Connecting if self.snapshot.is_some() => {
108 Some("Disconnected; reconnecting…".to_owned())
109 }
110 ConnectionState::Connecting => Some("Loading repository…".to_owned()),
111 ConnectionState::Failed(error) => Some(error.to_string()),
112 }
113 }
114
115 #[must_use]
116 pub fn label(&self) -> &'static str {
117 match self.connection {
118 ConnectionState::Connecting if self.snapshot.is_some() => "reconnecting",
119 ConnectionState::Connecting => "loading",
120 ConnectionState::Connected => "connected",
121 ConnectionState::Failed(_) => "failed",
122 }
123 }
124}
125
126impl Default for ClientState {
127 fn default() -> Self {
128 Self {
129 snapshot: None,
130 connection: ConnectionState::Connecting,
131 capabilities: capabilities(false, false),
132 error: None,
133 }
134 }
135}
136
137impl PartialEq for ClientState {
138 fn eq(&self, other: &Self) -> bool {
139 self.connection == other.connection
140 && self.capabilities == other.capabilities
141 && self.error == other.error
142 && match (&self.snapshot, &other.snapshot) {
143 (Some(snapshot), Some(other)) => Arc::ptr_eq(snapshot, other),
144 (None, None) => true,
145 _ => false,
146 }
147 }
148}