1use event_queue::Event;
6use fidl_fuchsia_update as fidl;
7use proptest::prelude::*;
8use proptest_derive::Arbitrary;
9use std::fmt;
10use thiserror::Error;
11use typed_builder::TypedBuilder;
12
13#[allow(missing_docs)] #[derive(Clone, Debug, PartialEq, Arbitrary)]
21pub enum State {
22 CheckingForUpdates,
23 ErrorCheckingForUpdate,
24 NoUpdateAvailable,
25 InstallationDeferredByPolicy(InstallationDeferredData),
26 InstallingUpdate(InstallingData),
27 WaitingForReboot(InstallingData),
28 InstallationError(InstallationErrorData),
29}
30
31impl State {
32 pub fn is_error(&self) -> bool {
34 match self {
35 State::ErrorCheckingForUpdate | State::InstallationError(_) => true,
36 State::CheckingForUpdates
37 | State::NoUpdateAvailable
38 | State::InstallationDeferredByPolicy(_)
39 | State::InstallingUpdate(_)
40 | State::WaitingForReboot(_) => false,
41 }
42 }
43
44 pub fn is_terminal(&self) -> bool {
46 match self {
47 State::CheckingForUpdates | State::InstallingUpdate(_) => false,
48 State::ErrorCheckingForUpdate
49 | State::InstallationError(_)
50 | State::NoUpdateAvailable
51 | State::InstallationDeferredByPolicy(_)
52 | State::WaitingForReboot(_) => true,
53 }
54 }
55}
56
57impl fmt::Display for State {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 State::CheckingForUpdates => write!(f, "Checking for updates...")?,
61 State::ErrorCheckingForUpdate => write!(f, "Error checking for update!")?,
62 State::InstallationError(_) => write!(f, "Installation error: {self:?}")?,
63 State::NoUpdateAvailable => write!(f, "No update available.")?,
64 State::InstallationDeferredByPolicy(InstallationDeferredData {
65 deferral_reason,
66 ..
67 }) => {
68 write!(f, "Installation deferred by policy: {deferral_reason:?}")?;
69 }
70 State::InstallingUpdate(InstallingData { update, installation_progress }) => {
71 if let Some(UpdateInfo { version_available: Some(version), .. }) = update {
72 write!(f, "Installing {version}")?;
73 } else {
74 write!(f, "Installing update")?;
75 }
76 if let Some(InstallationProgress { fraction_completed: Some(fraction) }) =
77 installation_progress
78 {
79 write!(f, " ({:.2}%)", fraction * 100.0)?;
80 }
81 }
82 State::WaitingForReboot(_) => write!(f, "Waiting for reboot...")?,
83 }
84 Ok(())
85 }
86}
87
88impl Event for State {
89 fn can_merge(&self, other: &State) -> bool {
90 if self == other {
91 return true;
92 }
93 if let State::InstallingUpdate(InstallingData { update: update0, .. }) = self {
96 if let State::InstallingUpdate(InstallingData { update: update1, .. }) = other {
97 return update0 == update1;
98 }
99 }
100 false
101 }
102}
103
104impl From<State> for fidl::State {
105 fn from(other: State) -> Self {
106 match other {
107 State::CheckingForUpdates => {
108 fidl::State::CheckingForUpdates(fidl::CheckingForUpdatesData::default())
109 }
110 State::ErrorCheckingForUpdate => {
111 fidl::State::ErrorCheckingForUpdate(fidl::ErrorCheckingForUpdateData::default())
112 }
113 State::NoUpdateAvailable => {
114 fidl::State::NoUpdateAvailable(fidl::NoUpdateAvailableData::default())
115 }
116 State::InstallationDeferredByPolicy(data) => {
117 fidl::State::InstallationDeferredByPolicy(data.into())
118 }
119 State::InstallingUpdate(data) => fidl::State::InstallingUpdate(data.into()),
120 State::WaitingForReboot(data) => fidl::State::WaitingForReboot(data.into()),
121 State::InstallationError(data) => fidl::State::InstallationError(data.into()),
122 }
123 }
124}
125
126impl From<fidl::State> for State {
127 fn from(fidl_state: fidl::State) -> Self {
128 match fidl_state {
129 fidl::State::CheckingForUpdates(_) => State::CheckingForUpdates,
130 fidl::State::ErrorCheckingForUpdate(_) => State::ErrorCheckingForUpdate,
131 fidl::State::NoUpdateAvailable(_) => State::NoUpdateAvailable,
132 fidl::State::InstallationDeferredByPolicy(data) => {
133 State::InstallationDeferredByPolicy(data.into())
134 }
135 fidl::State::InstallingUpdate(data) => State::InstallingUpdate(data.into()),
136 fidl::State::WaitingForReboot(data) => State::WaitingForReboot(data.into()),
137 fidl::State::InstallationError(data) => State::InstallationError(data.into()),
138 }
139 }
140}
141
142#[derive(Debug, Error, PartialEq, Eq)]
144pub enum AttemptOptionsDecodeError {
145 #[error("missing field 'initiator'")]
147 MissingInitiator,
148}
149
150#[derive(Clone, Debug, PartialEq, Arbitrary)]
156pub struct AttemptOptions {
157 pub initiator: Initiator,
158}
159
160impl Event for AttemptOptions {
161 fn can_merge(&self, _other: &AttemptOptions) -> bool {
162 true
163 }
164}
165
166impl TryFrom<fidl::AttemptOptions> for AttemptOptions {
167 type Error = AttemptOptionsDecodeError;
168
169 fn try_from(o: fidl::AttemptOptions) -> Result<Self, Self::Error> {
170 Ok(Self {
171 initiator: o.initiator.ok_or(AttemptOptionsDecodeError::MissingInitiator)?.into(),
172 })
173 }
174}
175
176impl From<AttemptOptions> for fidl::AttemptOptions {
177 fn from(o: AttemptOptions) -> Self {
178 Self { initiator: Some(o.initiator.into()), ..Default::default() }
179 }
180}
181
182impl From<CheckOptions> for AttemptOptions {
183 fn from(o: CheckOptions) -> Self {
184 Self { initiator: o.initiator }
185 }
186}
187
188#[derive(Clone, Debug, Default, PartialEq, Arbitrary)]
193pub struct InstallationErrorData {
194 pub update: Option<UpdateInfo>,
195 pub installation_progress: Option<InstallationProgress>,
196}
197impl From<InstallationErrorData> for fidl::InstallationErrorData {
198 fn from(other: InstallationErrorData) -> Self {
199 fidl::InstallationErrorData {
200 update: other.update.map(|ext| ext.into()),
201 installation_progress: other.installation_progress.map(|ext| ext.into()),
202 ..Default::default()
203 }
204 }
205}
206impl From<fidl::InstallationErrorData> for InstallationErrorData {
207 fn from(data: fidl::InstallationErrorData) -> Self {
208 Self {
209 update: data.update.map(|o| o.into()),
210 installation_progress: data.installation_progress.map(|o| o.into()),
211 }
212 }
213}
214
215#[derive(Clone, Debug, PartialEq, Arbitrary)]
220pub struct InstallationProgress {
221 #[proptest(strategy = "prop::option::of(prop::num::f32::NORMAL)")]
222 pub fraction_completed: Option<f32>,
223}
224impl From<InstallationProgress> for fidl::InstallationProgress {
225 fn from(other: InstallationProgress) -> Self {
226 fidl::InstallationProgress {
227 fraction_completed: other.fraction_completed,
228 ..Default::default()
229 }
230 }
231}
232impl From<fidl::InstallationProgress> for InstallationProgress {
233 fn from(progress: fidl::InstallationProgress) -> Self {
234 Self { fraction_completed: progress.fraction_completed }
235 }
236}
237
238#[derive(Clone, Debug, Default, PartialEq, Arbitrary)]
243pub struct InstallingData {
244 pub update: Option<UpdateInfo>,
245 pub installation_progress: Option<InstallationProgress>,
246}
247impl From<InstallingData> for fidl::InstallingData {
248 fn from(other: InstallingData) -> Self {
249 fidl::InstallingData {
250 update: other.update.map(|ext| ext.into()),
251 installation_progress: other.installation_progress.map(|ext| ext.into()),
252 ..Default::default()
253 }
254 }
255}
256impl From<fidl::InstallingData> for InstallingData {
257 fn from(data: fidl::InstallingData) -> Self {
258 Self {
259 update: data.update.map(|o| o.into()),
260 installation_progress: data.installation_progress.map(|o| o.into()),
261 }
262 }
263}
264
265#[derive(Clone, Debug, Default, PartialEq)]
270pub struct InstallationDeferredData {
271 pub update: Option<UpdateInfo>,
272 pub deferral_reason: Option<fidl::InstallationDeferralReason>,
273}
274
275impl From<InstallationDeferredData> for fidl::InstallationDeferredData {
276 fn from(other: InstallationDeferredData) -> Self {
277 fidl::InstallationDeferredData {
278 update: other.update.map(|ext| ext.into()),
279 deferral_reason: other.deferral_reason,
280 ..Default::default()
281 }
282 }
283}
284impl From<fidl::InstallationDeferredData> for InstallationDeferredData {
285 fn from(data: fidl::InstallationDeferredData) -> Self {
286 Self { update: data.update.map(|o| o.into()), deferral_reason: data.deferral_reason }
287 }
288}
289
290impl Arbitrary for InstallationDeferredData {
295 type Parameters = ();
296 type Strategy = BoxedStrategy<Self>;
297
298 fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
299 any::<UpdateInfo>()
300 .prop_flat_map(|info| {
301 (
302 proptest::option::of(Just(info)),
303 proptest::option::of(Just(
304 fidl::InstallationDeferralReason::CurrentSystemNotCommitted,
305 )),
306 )
307 })
308 .prop_map(|(update, deferral_reason)| Self { update, deferral_reason })
309 .boxed()
310 }
311}
312
313#[derive(Clone, Debug, PartialEq, Arbitrary)]
318pub struct UpdateInfo {
319 #[proptest(strategy = "proptest_util::random_version_available()")]
320 pub version_available: Option<String>,
321 pub download_size: Option<u64>,
322}
323impl From<UpdateInfo> for fidl::UpdateInfo {
324 fn from(other: UpdateInfo) -> Self {
325 fidl::UpdateInfo {
326 version_available: other.version_available,
327 download_size: other.download_size,
328 ..Default::default()
329 }
330 }
331}
332impl From<fidl::UpdateInfo> for UpdateInfo {
333 fn from(info: fidl::UpdateInfo) -> Self {
334 Self { version_available: info.version_available, download_size: info.download_size }
335 }
336}
337
338#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
343pub enum Initiator {
344 User,
345 Service,
346}
347
348impl From<fidl::Initiator> for Initiator {
349 fn from(initiator: fidl::Initiator) -> Self {
350 match initiator {
351 fidl::Initiator::User => Initiator::User,
352 fidl::Initiator::Service => Initiator::Service,
353 }
354 }
355}
356
357impl From<Initiator> for fidl::Initiator {
358 fn from(initiator: Initiator) -> Self {
359 match initiator {
360 Initiator::User => fidl::Initiator::User,
361 Initiator::Service => fidl::Initiator::Service,
362 }
363 }
364}
365
366#[derive(Debug, Error, PartialEq, Eq)]
368pub enum CheckOptionsDecodeError {
369 #[error("missing field 'initiator'")]
371 MissingInitiator,
372}
373
374#[derive(Clone, Debug, PartialEq, Arbitrary, TypedBuilder)]
380pub struct CheckOptions {
381 pub initiator: Initiator,
382
383 #[builder(default)]
384 pub allow_attaching_to_existing_update_check: bool,
385}
386
387impl TryFrom<fidl::CheckOptions> for CheckOptions {
388 type Error = CheckOptionsDecodeError;
389
390 fn try_from(o: fidl::CheckOptions) -> Result<Self, Self::Error> {
391 Ok(Self {
392 initiator: o.initiator.ok_or(CheckOptionsDecodeError::MissingInitiator)?.into(),
393 allow_attaching_to_existing_update_check: o
394 .allow_attaching_to_existing_update_check
395 .unwrap_or(false),
396 })
397 }
398}
399
400impl From<CheckOptions> for fidl::CheckOptions {
401 fn from(o: CheckOptions) -> Self {
402 Self {
403 initiator: Some(o.initiator.into()),
404 allow_attaching_to_existing_update_check: Some(
405 o.allow_attaching_to_existing_update_check,
406 ),
407 ..Default::default()
408 }
409 }
410}
411
412pub mod proptest_util {
413 use proptest::prelude::*;
414
415 prop_compose! {
416 pub fn random_version_available()(
418 version_available in proptest::option::of("[0-9A-Z]{10,20}")
419 ) -> Option<String> {
420 version_available
421 }
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 proptest! {
430 #[test]
432 fn test_state_can_merge(
433 update_info: Option<UpdateInfo>,
434 progress0: Option<InstallationProgress>,
435 progress1: Option<InstallationProgress>,
436 ) {
437 let event0 = State::InstallingUpdate(
438 InstallingData {
439 update: update_info.clone(),
440 installation_progress: progress0,
441 }
442 );
443 let event1 = State::InstallingUpdate(
444 InstallingData {
445 update: update_info,
446 installation_progress: progress1,
447 }
448 );
449 prop_assert!(event0.can_merge(&event1));
450 }
451
452 #[test]
453 fn test_attempt_options_can_merge(
454 initiator0: Initiator,
455 initiator1: Initiator,
456 ) {
457 let attempt_options0 = AttemptOptions {
458 initiator: initiator0,
459 };
460 let attempt_options1 = AttemptOptions {
461 initiator: initiator1,
462 };
463 prop_assert!(attempt_options0.can_merge(&attempt_options1));
464 }
465
466 #[test]
467 fn test_state_roundtrips(state: State) {
468 let state0: State = state.clone();
469 let fidl_intermediate: fidl::State = state.into();
470 let state1: State = fidl_intermediate.into();
471 prop_assert_eq!(state0, state1);
472 }
473
474 #[test]
475 fn test_initiator_roundtrips(initiator: Initiator) {
476 prop_assert_eq!(
477 Initiator::from(fidl::Initiator::from(initiator)),
478 initiator
479 );
480 }
481
482 #[test]
483 fn test_check_options_roundtrips(check_options: CheckOptions) {
484 prop_assert_eq!(
485 CheckOptions::try_from(fidl::CheckOptions::from(check_options.clone())),
486 Ok(check_options)
487 );
488 }
489
490 #[test]
491 fn test_check_options_initiator_required(allow_attaching_to_existing_update_check: bool) {
492 prop_assert_eq!(
493 CheckOptions::try_from(fidl::CheckOptions {
494 initiator: None,
495 allow_attaching_to_existing_update_check: Some(allow_attaching_to_existing_update_check),
496 ..Default::default()
497 }),
498 Err(CheckOptionsDecodeError::MissingInitiator)
499 );
500 }
501 }
502
503 #[test]
504 fn check_options_builder() {
505 assert_eq!(
506 CheckOptions::builder()
507 .initiator(Initiator::User)
508 .allow_attaching_to_existing_update_check(true)
509 .build(),
510 CheckOptions {
511 initiator: Initiator::User,
512 allow_attaching_to_existing_update_check: true,
513 }
514 );
515 }
516}