Skip to main content

clankerdiff_client/
state.rs

1use 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}