1use std::collections::hash_map::DefaultHasher;
6use std::hash::{Hash, Hasher};
7
8use fidl::prelude::*;
9use fidl_fuchsia_settings::{
10 LightError, LightGroup, LightRequest, LightSetLightGroupValuesResponder,
11 LightSetLightGroupValuesResult, LightState, LightWatchLightGroupResponder,
12 LightWatchLightGroupsResponder,
13};
14use zx::Status;
15
16use crate::base::{SettingInfo, SettingType};
17use crate::handler;
18use crate::handler::base::{Request, Response};
19use crate::ingress::{request, watch, Scoped};
20use crate::job::source::Error as JobError;
21use crate::job::Job;
22use crate::light::light_controller::ARG_NAME;
23
24impl watch::Responder<Vec<LightGroup>, zx::Status> for LightWatchLightGroupsResponder {
25 fn respond(self, response: Result<Vec<LightGroup>, zx::Status>) {
26 match response {
27 Ok(light_groups) => {
28 let _ = self.send(&light_groups);
29 }
30 Err(error) => {
31 self.control_handle().shutdown_with_epitaph(error);
32 }
33 }
34 }
35}
36
37impl watch::Responder<Vec<LightGroup>, zx::Status> for IndividualLightGroupResponder {
38 fn respond(self, response: Result<Vec<LightGroup>, zx::Status>) {
39 let light_group_name = self.light_group_name;
40 match response {
41 Ok(light_groups) => {
42 match light_groups.into_iter().find(|group| {
43 group.name.as_ref().map(|n| *n == light_group_name).unwrap_or(false)
44 }) {
45 Some(group) => {
46 let _ = self.responder.send(&group);
47 }
48 None => {
49 self.responder.control_handle().shutdown_with_epitaph(Status::NOT_FOUND);
51 }
52 }
53 }
54 Err(error) => {
55 self.responder.control_handle().shutdown_with_epitaph(error);
56 }
57 }
58 }
59}
60
61impl From<SettingInfo> for Vec<LightGroup> {
62 fn from(info: SettingInfo) -> Self {
63 if let SettingInfo::Light(light_info) = info {
64 light_info.light_groups.values().cloned().map(LightGroup::from).collect::<Vec<_>>()
66 } else {
67 panic!("incorrect value sent to light: {info:?}");
68 }
69 }
70}
71
72impl From<Response> for Scoped<LightSetLightGroupValuesResult> {
73 fn from(response: Response) -> Self {
74 Scoped(response.map(|_| ()).map_err(|e| match e {
75 handler::base::Error::InvalidArgument(_, argument, _) => {
76 if ARG_NAME == argument {
77 LightError::InvalidName
78 } else {
79 LightError::InvalidValue
80 }
81 }
82 _ => LightError::Failed,
83 }))
84 }
85}
86
87impl request::Responder<Scoped<LightSetLightGroupValuesResult>>
88 for LightSetLightGroupValuesResponder
89{
90 fn respond(self, Scoped(response): Scoped<LightSetLightGroupValuesResult>) {
91 let _ = self.send(response);
92 }
93}
94
95impl TryFrom<LightRequest> for Job {
96 type Error = JobError;
97
98 fn try_from(item: LightRequest) -> Result<Self, Self::Error> {
99 #[allow(unreachable_patterns)]
100 match item {
101 LightRequest::WatchLightGroups { responder } => {
102 Ok(watch::Work::new_job(SettingType::Light, responder))
103 }
104 LightRequest::WatchLightGroup { name, responder } => {
105 let mut hasher = DefaultHasher::new();
106 name.hash(&mut hasher);
107 let name_clone = name.clone();
108 Ok(watch::Work::new_job_with_change_function(
109 SettingType::Light,
110 IndividualLightGroupResponder { responder, light_group_name: name },
111 watch::ChangeFunction::new(
112 hasher.finish(),
113 Box::new(move |old: &SettingInfo, new: &SettingInfo| match (old, new) {
114 (SettingInfo::Light(old_info), SettingInfo::Light(new_info)) => {
115 let old_light_group = old_info.light_groups.get(&name_clone);
116 let new_light_group = new_info.light_groups.get(&name_clone);
117 old_light_group != new_light_group
118 }
119 _ => false,
120 }),
121 ),
122 ))
123 }
124 LightRequest::SetLightGroupValues { name, state, responder } => Ok(request::Work::new(
125 SettingType::Light,
126 Request::SetLightGroupValue(
127 name,
128 state.into_iter().map(LightState::into).collect::<Vec<_>>(),
129 ),
130 responder,
131 )
132 .into()),
133 _ => {
134 log::warn!("Received a call to an unsupported API: {:?}", item);
135 Err(JobError::Unsupported)
136 }
137 }
138 }
139}
140struct IndividualLightGroupResponder {
143 responder: LightWatchLightGroupResponder,
144 light_group_name: String,
145}
146
147#[cfg(test)]
148mod tests {
149 use std::collections::HashMap;
150
151 use fidl_fuchsia_settings::{LightMarker, LightRequestStream};
152 use futures::StreamExt;
153
154 use assert_matches::assert_matches;
155
156 use crate::job::{execution, work, Signature};
157 use crate::light::types::{LightGroup, LightInfo, LightState, LightType, LightValue};
158
159 use super::*;
160
161 #[fuchsia::test]
162 fn test_response_to_vector_empty() {
163 let response: Vec<fidl_fuchsia_settings::LightGroup> =
164 SettingInfo::into((LightInfo { light_groups: Default::default() }).into());
165
166 assert_eq!(response, vec![]);
167 }
168
169 #[fuchsia::test]
170 fn test_response_to_vector() {
171 let light_group_1 = LightGroup {
172 name: "test".to_string(),
173 enabled: true,
174 light_type: LightType::Simple,
175 lights: vec![LightState { value: Some(LightValue::Simple(true)) }],
176 hardware_index: vec![],
177 disable_conditions: vec![],
178 };
179 let light_group_2 = LightGroup {
180 name: "test2".to_string(),
181 enabled: false,
182 light_type: LightType::Rgb,
183 lights: vec![LightState { value: Some(LightValue::Brightness(0.42)) }],
184 hardware_index: vec![],
185 disable_conditions: vec![],
186 };
187
188 let light_groups: HashMap<_, _> = IntoIterator::into_iter([
189 (String::from("test"), light_group_1.clone()),
190 (String::from("test2"), light_group_2.clone()),
191 ])
192 .collect();
193
194 let mut response: Vec<fidl_fuchsia_settings::LightGroup> =
195 SettingInfo::into((LightInfo { light_groups }).into());
196
197 response.sort_by_key(|l| l.name.clone());
199
200 assert_eq!(response, vec![light_group_1.into(), light_group_2.into()]);
201 }
202
203 #[fuchsia::test(allow_stalls = false)]
205 async fn try_from_watch_light_groups_request() {
206 let (proxy, server) = fidl::endpoints::create_proxy::<LightMarker>();
208 let _fut = proxy.watch_light_groups();
209 let mut request_stream: LightRequestStream = server.into_stream();
210 let request = request_stream
211 .next()
212 .await
213 .expect("should have an request before stream is closed")
214 .expect("should have gotten a request");
215
216 let job = Job::try_from(request).expect("job conversion should succeed");
218 assert_matches!(job.workload(), work::Load::Sequential(_, _));
219 assert_matches!(job.execution_type(), execution::Type::Sequential(_));
220 }
221
222 async fn signature_from_watch_light_group_request(light_name: &str) -> Signature {
228 let (proxy, server) = fidl::endpoints::create_proxy::<LightMarker>();
229 let _fut = proxy.watch_light_group(light_name);
230 let mut request_stream: LightRequestStream = server.into_stream();
231 let request = request_stream
232 .next()
233 .await
234 .expect("should have an request before stream is closed")
235 .expect("should have gotten a request");
236 let job = Job::try_from(request).expect("job conversion should succeed");
237 assert_matches!(job.workload(), work::Load::Sequential(_, _));
238 assert_matches!(job.execution_type(), execution::Type::Sequential(signature) => signature)
239 }
240
241 #[fuchsia::test(allow_stalls = false)]
242 async fn try_from_watch_individual_light_group_request() {
243 const TEST_LIGHT_NAME: &str = "test_light";
244
245 let signature = signature_from_watch_light_group_request(TEST_LIGHT_NAME).await;
247
248 let signature2 = signature_from_watch_light_group_request(TEST_LIGHT_NAME).await;
250
251 assert_eq!(signature, signature2);
254
255 let signature3 = signature_from_watch_light_group_request("different_name").await;
257
258 assert_ne!(signature, signature3);
261 }
262
263 #[fuchsia::test(allow_stalls = false)]
265 async fn try_from_set_light_group_values_request() {
266 let (proxy, server) = fidl::endpoints::create_proxy::<LightMarker>();
268 let _fut = proxy.set_light_group_values("arbitrary name", &[]);
269 let mut request_stream: LightRequestStream = server.into_stream();
270 let request = request_stream
271 .next()
272 .await
273 .expect("should have an request before stream is closed")
274 .expect("should have gotten a request");
275
276 let job = Job::try_from(request).expect("job conversion should succeed");
278 assert_matches!(job.workload(), work::Load::Independent(_));
279 assert_matches!(job.execution_type(), execution::Type::Independent);
280 }
281}