Skip to content

Commit 9c32f92

Browse files
committed
snowcap: remove expect() from API
1 parent e41dfdc commit 9c32f92

File tree

6 files changed

+213
-72
lines changed

6 files changed

+213
-72
lines changed

snowcap/src/api/decoration/v1.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use anyhow::Context;
12
use snowcap_api_defs::snowcap::decoration::v1::{
23
CloseRequest, CloseResponse, NewDecorationRequest, NewDecorationResponse,
34
OperateDecorationRequest, OperateDecorationResponse, UpdateDecorationRequest,
@@ -9,7 +10,7 @@ use tracing::warn;
910
use crate::{
1011
api::{run_unary, widget::v1::widget_def_to_fn},
1112
decoration::{DecorationId, SnowcapDecoration},
12-
util::convert::FromApi,
13+
util::convert::TryFromApi,
1314
};
1415

1516
#[tonic::async_trait]
@@ -151,7 +152,17 @@ impl decoration_service_server::DecorationService for super::DecorationService {
151152
return Ok(OperateDecorationResponse {});
152153
};
153154

154-
decoration.operate(FromApi::from_api(operation));
155+
let operation =
156+
TryFromApi::try_from_api(operation).context("While processing OperateLayerRequest");
157+
let operation = match operation {
158+
Err(e) => {
159+
tracing::error!("{e:?}");
160+
return Ok(OperateDecorationResponse {});
161+
}
162+
Ok(o) => o,
163+
};
164+
165+
decoration.operate(operation);
155166

156167
Ok(OperateDecorationResponse {})
157168
})

snowcap/src/api/layer/v1.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::num::NonZeroU32;
22

3+
use anyhow::Context;
34
use smithay_client_toolkit::shell::wlr_layer;
45
use snowcap_api_defs::snowcap::layer::{
56
self,
@@ -13,7 +14,7 @@ use tonic::{Request, Response, Status};
1314
use crate::{
1415
api::{run_unary, run_unary_no_response, widget::v1::widget_def_to_fn},
1516
layer::{ExclusiveZone, LayerId, SnowcapLayer},
16-
util::convert::FromApi,
17+
util::convert::TryFromApi,
1718
};
1819

1920
#[tonic::async_trait]
@@ -198,7 +199,17 @@ impl layer_service_server::LayerService for super::LayerService {
198199
return Ok(OperateLayerResponse {});
199200
};
200201

201-
layer.operate(FromApi::from_api(operation));
202+
let operation =
203+
TryFromApi::try_from_api(operation).context("While processing OperateLayerRequest");
204+
let operation = match operation {
205+
Err(e) => {
206+
tracing::error!("{e:?}");
207+
return Ok(OperateLayerResponse {});
208+
}
209+
Ok(o) => o,
210+
};
211+
212+
layer.operate(operation);
202213

203214
Ok(OperateLayerResponse {})
204215
})

snowcap/src/api/operation/v1.rs

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,51 @@
11
//! Widget operations
22
3+
use anyhow::Context;
34
use iced_runtime::core::widget;
45
use snowcap_api_defs::snowcap::operation;
56

6-
use crate::util::convert::FromApi;
7+
use crate::util::convert::{FromApi, TryFromApi};
78

8-
impl FromApi<operation::v1::Operation> for Box<dyn widget::Operation + 'static> {
9-
fn from_api(api_type: operation::v1::Operation) -> Self {
10-
// FIXME remove expect
11-
api_type
12-
.target
13-
.map(FromApi::from_api)
14-
.expect("Operations should have a target")
9+
impl TryFromApi<operation::v1::Operation> for Box<dyn widget::Operation + 'static> {
10+
type Error = anyhow::Error;
11+
12+
fn try_from_api(api_type: operation::v1::Operation) -> Result<Self, Self::Error> {
13+
const MESSAGE: &str = "snowcap.operation.v1.Operation";
14+
const FIELD: &str = "target";
15+
16+
let Some(target) = api_type.target else {
17+
anyhow::bail!("While converting {MESSAGE}: missing field '{FIELD}")
18+
};
19+
20+
TryFromApi::try_from_api(target)
21+
.with_context(|| format!("While converting {MESSAGE}.{FIELD}"))
1522
}
1623
}
1724

18-
impl FromApi<operation::v1::operation::Target> for Box<dyn widget::Operation + 'static> {
19-
fn from_api(api_type: operation::v1::operation::Target) -> Self {
25+
impl TryFromApi<operation::v1::operation::Target> for Box<dyn widget::Operation + 'static> {
26+
type Error = anyhow::Error;
27+
28+
fn try_from_api(api_type: operation::v1::operation::Target) -> Result<Self, Self::Error> {
2029
use operation::v1::operation::Target;
2130

2231
match api_type {
23-
Target::Focusable(focusable) => FromApi::from_api(focusable),
24-
Target::TextInput(text_input) => FromApi::from_api(text_input),
32+
Target::Focusable(focusable) => TryFromApi::try_from_api(focusable),
33+
Target::TextInput(text_input) => TryFromApi::try_from_api(text_input),
2534
}
2635
}
2736
}
2837

29-
impl FromApi<operation::v1::Focusable> for Box<dyn widget::Operation + 'static> {
30-
fn from_api(api_type: operation::v1::Focusable) -> Self {
31-
// FIXME remove expect
32-
api_type
33-
.op
34-
.map(FromApi::from_api)
35-
.expect("Focusable should have an operation")
38+
impl TryFromApi<operation::v1::Focusable> for Box<dyn widget::Operation + 'static> {
39+
type Error = anyhow::Error;
40+
41+
fn try_from_api(api_type: operation::v1::Focusable) -> Result<Self, Self::Error> {
42+
const MESSAGE: &str = "snowcap.operation.v1.Focusable";
43+
44+
let Some(op) = api_type.op else {
45+
anyhow::bail!("While converting {MESSAGE}: missing field 'op'")
46+
};
47+
48+
Ok(FromApi::from_api(op))
3649
}
3750
}
3851

@@ -51,13 +64,17 @@ impl FromApi<operation::v1::focusable::Op> for Box<dyn widget::Operation + 'stat
5164
}
5265
}
5366

54-
impl FromApi<operation::v1::TextInput> for Box<dyn widget::Operation + 'static> {
55-
fn from_api(api_type: operation::v1::TextInput) -> Self {
56-
// FIXME remove expect
57-
api_type
58-
.op
59-
.map(FromApi::from_api)
60-
.expect("TextInput should have an operation")
67+
impl TryFromApi<operation::v1::TextInput> for Box<dyn widget::Operation + 'static> {
68+
type Error = anyhow::Error;
69+
70+
fn try_from_api(api_type: operation::v1::TextInput) -> Result<Self, Self::Error> {
71+
const MESSAGE: &str = "snowcap.operation.v1.TextInput";
72+
73+
let Some(op) = api_type.op else {
74+
anyhow::bail!("While converting {MESSAGE}: missing field 'op");
75+
};
76+
77+
Ok(FromApi::from_api(op))
6178
}
6279
}
6380

snowcap/src/api/widget/v1.rs

Lines changed: 117 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use anyhow::Context;
12
use iced::widget::{
23
Column, Container, Row, Scrollable, button, image::FilterMethod, scrollable::Scrollbar,
34
};
@@ -14,7 +15,7 @@ use crate::{
1415
api::{ResponseStream, run_server_streaming_mapped},
1516
decoration::DecorationId,
1617
layer::LayerId,
17-
util::convert::FromApi,
18+
util::convert::{FromApi, TryFromApi},
1819
widget::{MouseAreaEvent, TextInputEvent, ViewFn, WidgetEvent, WidgetId},
1920
};
2021

@@ -848,11 +849,14 @@ pub fn widget_def_to_fn(def: WidgetDef) -> Option<ViewFn> {
848849
}
849850

850851
if let Some(style) = style.clone() {
852+
use crate::widget::text_input::{Style, Styles};
853+
854+
let style = Styles::from_api(style);
851855
let style = move |theme: &iced::Theme, status| {
852856
use iced::widget::text_input;
853-
let mut s = <iced::Theme as text_input::Catalog>::default()(theme, status);
857+
let s = <iced::Theme as text_input::Catalog>::default()(theme, status);
854858

855-
let widget::v1::text_input::Style {
859+
let crate::widget::text_input::Styles {
856860
active,
857861
hovered,
858862
focused,
@@ -864,37 +868,34 @@ pub fn widget_def_to_fn(def: WidgetDef) -> Option<ViewFn> {
864868
text_input::Status::Active => active,
865869
text_input::Status::Hovered => hovered.or(active),
866870
text_input::Status::Focused { is_hovered } => {
867-
let inner =
871+
let hover =
868872
if is_hovered { hover_focused.or(hovered) } else { None };
869873

870-
inner.or(focused).or(active)
874+
hover.or(focused).or(active)
871875
}
872876
text_input::Status::Disabled => disabled,
873877
};
874878

875-
if let Some(style) = inner {
876-
if let Some(background) = style.background {
877-
s.background = FromApi::from_api(background);
878-
}
879-
if let Some(border) = style.border {
880-
s.border = FromApi::from_api(border);
881-
}
882-
883-
if let Some(icon) = style.icon {
884-
s.icon = FromApi::from_api(icon);
885-
}
886-
if let Some(placeholder) = style.placeholder {
887-
s.placeholder = FromApi::from_api(placeholder);
888-
}
889-
if let Some(value) = style.value {
890-
s.value = FromApi::from_api(value);
891-
}
892-
if let Some(selection) = style.selection {
893-
s.selection = FromApi::from_api(selection);
879+
if let Some(Style {
880+
background,
881+
border,
882+
icon,
883+
placeholder,
884+
value,
885+
selection,
886+
}) = inner
887+
{
888+
iced::widget::text_input::Style {
889+
background: background.unwrap_or(s.background),
890+
border: border.unwrap_or(s.border),
891+
icon: icon.unwrap_or(s.icon),
892+
placeholder: placeholder.unwrap_or(s.placeholder),
893+
value: value.unwrap_or(s.value),
894+
selection: selection.unwrap_or(s.selection),
894895
}
896+
} else {
897+
s
895898
}
896-
897-
s
898899
};
899900

900901
text_input = text_input.style(style);
@@ -1259,34 +1260,50 @@ impl FromApi<widget::v1::LineHeight> for iced::widget::text::LineHeight {
12591260
fn from_api(api_type: widget::v1::LineHeight) -> Self {
12601261
use widget::v1::line_height::LineHeight;
12611262

1262-
let line_height = api_type
1263-
.line_height
1264-
.expect("LineHeight should not be empty");
1265-
1266-
match line_height {
1263+
let line_height = api_type.line_height.map(|lh| match lh {
12671264
LineHeight::Relative(v) => Self::Relative(v),
12681265
LineHeight::Absolute(v) => Self::Absolute(v.into()),
1266+
});
1267+
1268+
if line_height.is_none() {
1269+
tracing::warn!("Invalid snowcap.widget.v1.LineHeight. Using default value");
12691270
}
1271+
1272+
line_height.unwrap_or_default()
12701273
}
12711274
}
12721275

1273-
impl FromApi<widget::v1::Background> for iced::Background {
1274-
fn from_api(api_type: widget::v1::Background) -> Self {
1276+
impl TryFromApi<widget::v1::Background> for iced::Background {
1277+
type Error = anyhow::Error;
1278+
1279+
fn try_from_api(api_type: widget::v1::Background) -> Result<Self, Self::Error> {
12751280
use widget::v1::background::Background;
12761281

1277-
let background = api_type.background.expect("Background should not be empty");
1282+
const MESSAGE: &str = "snowcap.widget.v1.Background";
1283+
const FIELD: &str = "background";
12781284

1279-
match background {
1280-
Background::Color(color) => Self::Color(iced::Color::from_api(color)),
1281-
Background::Gradient(gradient) => Self::Gradient(iced::Gradient::from_api(gradient)),
1282-
}
1285+
let Some(background) = api_type.background else {
1286+
anyhow::bail!("While converting {MESSAGE}: missing field 'FIELD'")
1287+
};
1288+
1289+
let background = match background {
1290+
Background::Color(color) => Ok(Self::Color(iced::Color::from_api(color))),
1291+
Background::Gradient(gradient) => {
1292+
iced::Gradient::try_from_api(gradient).map(Self::Gradient)
1293+
}
1294+
};
1295+
1296+
background.with_context(|| format!("While converting {MESSAGE}.{FIELD}"))
12831297
}
12841298
}
12851299

1286-
impl FromApi<widget::v1::Gradient> for iced::Gradient {
1287-
fn from_api(api_type: widget::v1::Gradient) -> Self {
1300+
impl TryFromApi<widget::v1::Gradient> for iced::Gradient {
1301+
type Error = anyhow::Error;
1302+
fn try_from_api(api_type: widget::v1::Gradient) -> Result<Self, Self::Error> {
12881303
use widget::v1::gradient::{Gradient, Linear};
1289-
let gradient = api_type.gradient.expect("Gradient should not be empty");
1304+
let Some(gradient) = api_type.gradient else {
1305+
anyhow::bail!("Missing field 'gradient'")
1306+
};
12901307

12911308
match gradient {
12921309
Gradient::Linear(Linear { radians, stops }) => {
@@ -1298,8 +1315,66 @@ impl FromApi<widget::v1::Gradient> for iced::Gradient {
12981315
}
12991316
}));
13001317

1301-
iced::Gradient::Linear(lin)
1318+
Ok(iced::Gradient::Linear(lin))
13021319
}
13031320
}
13041321
}
13051322
}
1323+
1324+
impl FromApi<widget::v1::text_input::Style> for crate::widget::text_input::Styles {
1325+
fn from_api(api_type: widget::v1::text_input::Style) -> Self {
1326+
use crate::widget::text_input::Style;
1327+
use widget::v1::text_input::style::Inner;
1328+
1329+
fn convert_inner(name: &str, inner: widget::v1::text_input::style::Inner) -> Style {
1330+
let Inner {
1331+
background,
1332+
border,
1333+
icon,
1334+
placeholder,
1335+
value,
1336+
selection,
1337+
} = inner;
1338+
1339+
let background = if let Some(background) = background {
1340+
let from_api = TryFromApi::try_from_api(background).with_context(|| {
1341+
format!("While converting 'snowcap.widget.v1.text_input.Style.{name}'")
1342+
});
1343+
match from_api {
1344+
Err(e) => {
1345+
tracing::error!("{e:?}");
1346+
None
1347+
}
1348+
Ok(b) => Some(b),
1349+
}
1350+
} else {
1351+
None
1352+
};
1353+
1354+
Style {
1355+
background,
1356+
border: border.map(FromApi::from_api),
1357+
icon: icon.map(FromApi::from_api),
1358+
placeholder: placeholder.map(FromApi::from_api),
1359+
value: value.map(FromApi::from_api),
1360+
selection: selection.map(FromApi::from_api),
1361+
}
1362+
}
1363+
1364+
let widget::v1::text_input::Style {
1365+
active,
1366+
hovered,
1367+
focused,
1368+
hover_focused,
1369+
disabled,
1370+
} = api_type;
1371+
1372+
Self {
1373+
active: active.map(|inner| convert_inner("active", inner)),
1374+
hovered: hovered.map(|inner| convert_inner("hovered", inner)),
1375+
focused: focused.map(|inner| convert_inner("focused", inner)),
1376+
hover_focused: hover_focused.map(|inner| convert_inner("hover_focused", inner)),
1377+
disabled: disabled.map(|inner| convert_inner("disabled", inner)),
1378+
}
1379+
}
1380+
}

snowcap/src/util/convert.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@
33
pub trait FromApi<T> {
44
fn from_api(api_type: T) -> Self;
55
}
6+
7+
pub trait TryFromApi<T>: Sized {
8+
type Error;
9+
10+
fn try_from_api(api_type: T) -> Result<Self, Self::Error>;
11+
}

0 commit comments

Comments
 (0)