Skip to main content

update_package/
version.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Typesafe wrappers around reading the version file.
6
7use fidl_fuchsia_io as fio;
8use omaha_client::version::Version as SemanticVersion;
9use serde::de::{self, Visitor};
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use std::convert::Infallible;
12use std::fmt;
13use std::str::FromStr;
14use thiserror::Error;
15
16/// An error encountered while reading the version.
17#[derive(Debug, Error)]
18#[allow(missing_docs)]
19pub enum ReadVersionError {
20    #[error("while opening the file")]
21    OpenFile(#[source] fuchsia_fs::node::OpenError),
22
23    #[error("while reading the file")]
24    ReadFile(#[source] fuchsia_fs::file::ReadError),
25}
26
27struct SystemVersionVisitor;
28
29impl Visitor<'_> for SystemVersionVisitor {
30    type Value = SystemVersion;
31
32    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
33        formatter.write_str("a string")
34    }
35
36    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
37    where
38        E: de::Error,
39    {
40        Ok(SystemVersion::from_str(v).unwrap())
41    }
42}
43
44#[derive(Clone, Debug, Eq, PartialEq)]
45/// Represents the version of an update package.
46pub enum SystemVersion {
47    /// An unrecognised version format.
48    Opaque(String),
49    /// A version of the format "a.b.c.d".
50    Semantic(SemanticVersion),
51}
52
53impl SystemVersion {
54    /// Returns true if this SystemVersion is an empty string.
55    pub fn is_empty(&self) -> bool {
56        if let SystemVersion::Opaque(value) = self { value.is_empty() } else { false }
57    }
58}
59
60impl PartialOrd for SystemVersion {
61    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
62        let my_version = match &self {
63            SystemVersion::Opaque(_) => return None,
64            SystemVersion::Semantic(ver) => ver,
65        };
66
67        let other_version = match other {
68            SystemVersion::Opaque(_) => return None,
69            SystemVersion::Semantic(ver) => ver,
70        };
71
72        my_version.partial_cmp(other_version)
73    }
74}
75
76// We have manual implementations of deserialize/serialize so that we can parse simple strings.
77impl<'de> Deserialize<'de> for SystemVersion {
78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79    where
80        D: Deserializer<'de>,
81    {
82        deserializer.deserialize_str(SystemVersionVisitor)
83    }
84}
85
86impl Serialize for SystemVersion {
87    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
88    where
89        S: Serializer,
90    {
91        match self {
92            SystemVersion::Semantic(version) => serializer.serialize_str(&version.to_string()),
93            SystemVersion::Opaque(string) => serializer.serialize_str(string),
94        }
95    }
96}
97
98impl FromStr for SystemVersion {
99    type Err = Infallible;
100
101    fn from_str(s: &str) -> Result<Self, Self::Err> {
102        let result = match SemanticVersion::from_str(s) {
103            Ok(version) => SystemVersion::Semantic(version),
104            Err(_) => SystemVersion::Opaque(s.trim_end().to_owned()),
105        };
106
107        Ok(result)
108    }
109}
110
111impl fmt::Display for SystemVersion {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        match self {
114            SystemVersion::Opaque(string) => f.write_str(string),
115            SystemVersion::Semantic(version) => version.fmt(f),
116        }
117    }
118}
119
120pub(crate) async fn read_version(
121    proxy: &fio::DirectoryProxy,
122) -> Result<SystemVersion, ReadVersionError> {
123    let file = fuchsia_fs::directory::open_file(proxy, "version", fio::PERM_READABLE)
124        .await
125        .map_err(ReadVersionError::OpenFile)?;
126    let version_str =
127        fuchsia_fs::file::read_to_string(&file).await.map_err(ReadVersionError::ReadFile)?;
128
129    Ok(SystemVersion::from_str(&version_str).unwrap())
130}
131
132#[cfg(test)]
133#[allow(clippy::bool_assert_comparison)]
134mod tests {
135    use super::*;
136    use crate::TestUpdatePackage;
137    use assert_matches::assert_matches;
138
139    #[fuchsia::test]
140    async fn read_version_success_file_exists() {
141        let p = TestUpdatePackage::new().add_file("version", "123").await;
142        assert_eq!(
143            p.version().await.unwrap(),
144            SystemVersion::Semantic(SemanticVersion::from([123]))
145        );
146    }
147
148    #[fuchsia::test]
149    async fn read_version_success_opaque() {
150        let p = TestUpdatePackage::new().add_file("version", "2020-09-08T10:17:00+10:00").await;
151        assert_eq!(
152            p.version().await.unwrap(),
153            SystemVersion::Opaque("2020-09-08T10:17:00+10:00".to_owned())
154        );
155    }
156
157    #[fuchsia::test]
158    async fn read_version_trims_trailing_whitespace() {
159        let p = TestUpdatePackage::new().add_file("version", "2020-09-08T10:17:00+10:00\n").await;
160        assert_eq!(
161            p.version().await.unwrap(),
162            SystemVersion::Opaque("2020-09-08T10:17:00+10:00".to_owned())
163        );
164    }
165
166    #[fuchsia::test]
167    async fn read_version_fail_file_does_not_exist() {
168        let p = TestUpdatePackage::new();
169        assert_matches!(read_version(p.proxy()).await, Err(ReadVersionError::OpenFile(_)));
170    }
171
172    #[test]
173    fn test_deserialize_version() {
174        let version: SystemVersion =
175            serde_json::from_str(r#""not a real version number""#).unwrap();
176        assert_eq!(version, SystemVersion::Opaque("not a real version number".to_owned()));
177        let version: SystemVersion = serde_json::from_str(r#""1.2.3""#).unwrap();
178        assert_eq!(version, SystemVersion::Semantic(SemanticVersion::from([1, 2, 3])));
179    }
180
181    #[test]
182    fn test_serialize_version() {
183        let version = SystemVersion::Opaque("not a real version number".to_owned());
184        assert_eq!(
185            serde_json::to_string(&version).unwrap(),
186            r#""not a real version number""#.to_owned()
187        );
188        let version = SystemVersion::Semantic(SemanticVersion::from([1, 3, 4, 5]));
189        assert_eq!(serde_json::to_string(&version).unwrap(), r#""1.3.4.5""#.to_owned());
190    }
191
192    #[test]
193    fn test_version_order_both_opaque() {
194        // We don't attempt to assign ordering to opaque versions.
195        let a = SystemVersion::Opaque("version 1".to_string());
196        let b = SystemVersion::Opaque("another version".to_string());
197        assert_eq!(a < b, false);
198        assert_eq!(a > b, false);
199        assert_eq!(a >= b, false);
200        assert_eq!(a <= b, false);
201    }
202
203    #[test]
204    fn test_version_order_one_opaque() {
205        let a = SystemVersion::Opaque("opaque".to_string());
206        let b = SystemVersion::Semantic(SemanticVersion::from([1, 2, 3, 4]));
207        assert_eq!(a < b, false);
208        assert_eq!(a > b, false);
209        assert_eq!(a >= b, false);
210        assert_eq!(a <= b, false);
211        assert_eq!(b < a, false);
212        assert_eq!(b > a, false);
213        assert_eq!(b >= a, false);
214        assert_eq!(b <= a, false);
215    }
216
217    #[test]
218    fn test_version_order_both_semantic() {
219        let a = SystemVersion::Semantic(SemanticVersion::from([1, 2, 3, 4]));
220        let b = SystemVersion::Semantic(SemanticVersion::from([1, 2, 3, 5]));
221
222        assert_eq!(a < b, true);
223        assert_eq!(a <= b, true);
224        assert_eq!(a > b, false);
225        assert_eq!(a >= b, false);
226        assert_eq!(b < a, false);
227        assert_eq!(b <= a, false);
228        assert_eq!(b >= a, true);
229        assert_eq!(b > a, true);
230    }
231}