xrl/
frontend.rs

1use crate::client::Client;
2use crate::protocol::{Client as InnerClient, IntoStaticFuture, Service, ServiceBuilder};
3use crate::structs::{
4    Alert, AvailableLanguages, AvailablePlugins, AvailableThemes, ConfigChanged, FindStatus,
5    LanguageChanged, MeasureWidth, PluginStarted, PluginStoped, ReplaceStatus, ScrollTo, Style,
6    ThemeChanged, Update, UpdateCmds,
7};
8use futures::{
9    future::{self, Either, FutureResult},
10    Future,
11};
12use serde_json::{from_value, to_value, Value};
13
14/// Represents all possible RPC messages recieved from xi-core.
15#[derive(Debug)]
16pub enum XiNotification {
17    Update(Update),
18    ScrollTo(ScrollTo),
19    DefStyle(Style),
20    AvailablePlugins(AvailablePlugins),
21    UpdateCmds(UpdateCmds),
22    PluginStarted(PluginStarted),
23    PluginStoped(PluginStoped),
24    ConfigChanged(ConfigChanged),
25    ThemeChanged(ThemeChanged),
26    Alert(Alert),
27    AvailableThemes(AvailableThemes),
28    FindStatus(FindStatus),
29    ReplaceStatus(ReplaceStatus),
30    AvailableLanguages(AvailableLanguages),
31    LanguageChanged(LanguageChanged),
32}
33
34/// The `Frontend` trait must be implemented by clients. It defines how the
35/// client handles notifications and requests coming from `xi-core`.
36pub trait Frontend {
37    type NotificationResult: IntoStaticFuture<Item = (), Error = ()>;
38    fn handle_notification(&mut self, notification: XiNotification) -> Self::NotificationResult;
39
40    type MeasureWidthResult: IntoStaticFuture<Item = Vec<Vec<f32>>, Error = ()>;
41    fn handle_measure_width(&mut self, request: MeasureWidth) -> Self::MeasureWidthResult;
42}
43
44/// A trait to build a type that implements `Frontend`.
45pub trait FrontendBuilder {
46    /// The type to build
47    type Frontend: Frontend;
48
49    /// Build the frontend with the given client.
50    fn build(self, client: Client) -> Self::Frontend;
51}
52
53impl<B> ServiceBuilder for B
54where
55    B: FrontendBuilder,
56    B::Frontend: Send,
57{
58    type Service = B::Frontend;
59
60    fn build(self, client: InnerClient) -> B::Frontend {
61        <Self as FrontendBuilder>::build(self, Client(client))
62    }
63}
64
65impl<F: Frontend + Send> Service for F {
66    type T = Value;
67    type E = Value;
68    type RequestFuture = Box<dyn Future<Item = Self::T, Error = Self::E> + 'static + Send>;
69    type NotificationFuture = Either<
70        <<F as Frontend>::NotificationResult as IntoStaticFuture>::Future,
71        FutureResult<(), ()>,
72    >;
73
74    fn handle_request(&mut self, method: &str, params: Value) -> Self::RequestFuture {
75        info!("<<< request: method={}, params={}", method, &params);
76        match method {
77            "measure_width" => {
78                match from_value::<MeasureWidth>(params) {
79                    Ok(request) => {
80                        let future = self
81                            .handle_measure_width(request)
82                            .into_static_future()
83                            .map(|response| {
84                                // TODO: justify why this can't fail
85                                // https://docs.serde.rs/serde_json/value/fn.to_value.html#errors
86                                to_value(response).expect("failed to convert response")
87                            })
88                            .map_err(|_| panic!("errors are not supported"));
89                        Box::new(future)
90                    }
91                    Err(e) => {
92                        warn!("failed to deserialize measure_width message: {:?}", e);
93                        let err_msg = to_value("invalid measure_width message")
94                            // TODO: justify why string serialization cannot fail
95                            .expect("failed to serialize string");
96                        Box::new(future::err(err_msg))
97                    }
98                }
99            }
100            _ => {
101                let err_msg = to_value(format!("unknown method \"{}\"", method))
102                    // TODO: justify why string serialization cannot fail
103                    .expect("failed to serialize string");
104                Box::new(future::err(err_msg))
105            }
106        }
107    }
108
109    #[allow(clippy::cognitive_complexity)]
110    fn handle_notification(&mut self, method: &str, params: Value) -> Self::NotificationFuture {
111        info!("<<< notification: method={}, params={}", method, &params);
112        match method {
113            "update" => match from_value::<Update>(params) {
114                Ok(update) => Either::A(
115                    self.handle_notification(XiNotification::Update(update))
116                        .into_static_future(),
117                ),
118                Err(e) => {
119                    error!("received invalid update notification: {:?}", e);
120                    Either::B(future::err(()))
121                }
122            },
123
124            "scroll_to" => match from_value::<ScrollTo>(params) {
125                Ok(scroll_to) => Either::A(
126                    self.handle_notification(XiNotification::ScrollTo(scroll_to))
127                        .into_static_future(),
128                ),
129                Err(e) => {
130                    error!("received invalid scroll_to notification: {:?}", e);
131                    Either::B(future::err(()))
132                }
133            },
134
135            "def_style" => match from_value::<Style>(params) {
136                Ok(style) => Either::A(
137                    self.handle_notification(XiNotification::DefStyle(style))
138                        .into_static_future(),
139                ),
140                Err(e) => {
141                    error!("received invalid def_style notification: {:?}", e);
142                    Either::B(future::err(()))
143                }
144            },
145            "available_plugins" => match from_value::<AvailablePlugins>(params) {
146                Ok(plugins) => Either::A(
147                    self.handle_notification(XiNotification::AvailablePlugins(plugins))
148                        .into_static_future(),
149                ),
150                Err(e) => {
151                    error!("received invalid available_plugins notification: {:?}", e);
152                    Either::B(future::err(()))
153                }
154            },
155            "plugin_started" => match from_value::<PluginStarted>(params) {
156                Ok(plugin) => Either::A(
157                    self.handle_notification(XiNotification::PluginStarted(plugin))
158                        .into_static_future(),
159                ),
160                Err(e) => {
161                    error!("received invalid plugin_started notification: {:?}", e);
162                    Either::B(future::err(()))
163                }
164            },
165            "plugin_stoped" => match from_value::<PluginStoped>(params) {
166                Ok(plugin) => Either::A(
167                    self.handle_notification(XiNotification::PluginStoped(plugin))
168                        .into_static_future(),
169                ),
170                Err(e) => {
171                    error!("received invalid plugin_stoped notification: {:?}", e);
172                    Either::B(future::err(()))
173                }
174            },
175            "update_cmds" => match from_value::<UpdateCmds>(params) {
176                Ok(cmds) => Either::A(
177                    self.handle_notification(XiNotification::UpdateCmds(cmds))
178                        .into_static_future(),
179                ),
180                Err(e) => {
181                    error!("received invalid update_cmds notification: {:?}", e);
182                    Either::B(future::err(()))
183                }
184            },
185            "config_changed" => match from_value::<ConfigChanged>(params) {
186                Ok(config) => Either::A(
187                    self.handle_notification(XiNotification::ConfigChanged(config))
188                        .into_static_future(),
189                ),
190                Err(e) => {
191                    error!("received invalid config_changed notification: {:?}", e);
192                    Either::B(future::err(()))
193                }
194            },
195            "theme_changed" => match from_value::<ThemeChanged>(params) {
196                Ok(theme) => Either::A(
197                    self.handle_notification(XiNotification::ThemeChanged(theme))
198                        .into_static_future(),
199                ),
200                Err(e) => {
201                    error!("received invalid theme_changed notification: {:?}", e);
202                    Either::B(future::err(()))
203                }
204            },
205            "alert" => match from_value::<Alert>(params) {
206                Ok(alert) => Either::A(
207                    self.handle_notification(XiNotification::Alert(alert))
208                        .into_static_future(),
209                ),
210                Err(e) => {
211                    error!("received invalid alert notification: {:?}", e);
212                    Either::B(future::err(()))
213                }
214            },
215            "available_themes" => match from_value::<AvailableThemes>(params) {
216                Ok(themes) => Either::A(
217                    self.handle_notification(XiNotification::AvailableThemes(themes))
218                        .into_static_future(),
219                ),
220                Err(e) => {
221                    error!("received invalid available_themes notification: {:?}", e);
222                    Either::B(future::err(()))
223                }
224            },
225            "find_status" => match from_value::<FindStatus>(params) {
226                Ok(find_status) => Either::A(
227                    self.handle_notification(XiNotification::FindStatus(find_status))
228                        .into_static_future(),
229                ),
230                Err(e) => {
231                    error!("received invalid find_status notification: {:?}", e);
232                    Either::B(future::err(()))
233                }
234            },
235            "replace_status" => match from_value::<ReplaceStatus>(params) {
236                Ok(replace_status) => Either::A(
237                    self.handle_notification(XiNotification::ReplaceStatus(replace_status))
238                        .into_static_future(),
239                ),
240                Err(e) => {
241                    error!("received invalid replace_status notification: {:?}", e);
242                    Either::B(future::err(()))
243                }
244            },
245            "available_languages" => match from_value::<AvailableLanguages>(params) {
246                Ok(available_langs) => Either::A(
247                    self.handle_notification(XiNotification::AvailableLanguages(available_langs))
248                        .into_static_future(),
249                ),
250                Err(e) => {
251                    error!("received invalid available_languages notification: {:?}", e);
252                    Either::B(future::err(()))
253                }
254            },
255            "language_changed" => match from_value::<LanguageChanged>(params) {
256                Ok(lang) => Either::A(
257                    self.handle_notification(XiNotification::LanguageChanged(lang))
258                        .into_static_future(),
259                ),
260                Err(e) => {
261                    error!("received invalid language_changed notification: {:?}", e);
262                    Either::B(future::err(()))
263                }
264            },
265            _ => Either::B(future::err(())),
266        }
267    }
268}