fmri/
lib.rs

1use std::cmp::Ordering;
2use std::cmp::Ordering::Equal;
3use std::fmt::{Debug, Display, Formatter};
4
5use serde::{Deserialize, Serialize};
6
7use crate::helpers::{check_character_collision, remove_first_and_last_characters};
8
9pub use self::{fmri_list::FMRIList, publisher::Publisher, version::Version};
10
11pub mod fmri_list;
12mod helpers;
13pub mod publisher;
14#[cfg(test)]
15mod tests;
16pub mod version;
17
18/// [`FMRI`] represents pkg fmri versioning system
19///
20/// # Examples
21///
22/// ```plain
23/// pkg:/audio/audacity
24/// pkg:/audio/audacity@2.3.2,5.11-2022.0.0.1
25/// pkg://solaris/system/library
26/// pkg://solaris/system/library@0.5.11-0.175.1.0.0.2.1:20120919T082311Z
27/// ```
28#[derive(PartialEq, Serialize, Deserialize, Clone, Eq, Hash)]
29pub struct FMRI {
30    /// Publisher is optional
31    publisher: Option<Publisher>,
32    // TODO: add package_name struct?
33    package_name: String,
34    /// Version is optional
35    version: Option<Version>,
36}
37
38impl FMRI {
39    /// Returns [`FMRI`] with given package name
40    pub fn new_from_package_name(mut package_name: String) -> Result<Self, String> {
41        if package_name.is_empty() {
42            panic!("package name can't be empty")
43        }
44
45        check_character_collision(&package_name)?;
46        package_name = remove_first_and_last_characters(&package_name, '/').to_owned();
47
48        Ok(Self {
49            publisher: None,
50            package_name,
51            version: None,
52        })
53    }
54
55    /// Returns [`FMRI`] from raw fmri
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// use fmri::FMRI;
61    /// FMRI::parse_raw(&"fmri=test@1-1:20220913T082027Z".to_owned()).unwrap();
62    /// FMRI::parse_raw(&"pkg://publisher/test@1-1:20220913T082027Z".to_owned()).unwrap();
63    /// ```
64    ///
65    /// # Error
66    ///
67    /// Returns a string with error message if one of the segments is invalid
68    pub fn parse_raw(raw_fmri: &str) -> Result<Self, String> {
69        let mut publisher: Option<Publisher> = None;
70        let mut version: Option<Version> = None;
71        let mut package_name: String = raw_fmri.to_owned().trim_start_matches("fmri=").to_owned();
72
73        match Publisher::parse_publisher_from_raw_fmri(raw_fmri.to_owned()) {
74            Ok(None) => {
75                package_name = package_name.trim_start_matches("pkg:/").to_owned();
76            }
77            Ok(Some(p)) => {
78                publisher = Some(p);
79                let (_, end_str) = package_name
80                    .trim_start_matches("pkg://")
81                    .split_once('/')
82                    .expect("Fmri must contain \"/package_name\"");
83                package_name = end_str.to_owned()
84            }
85            Err(e) => return Err(e),
86        }
87
88        match Version::parse_version_from_raw_fmri(raw_fmri.to_owned()) {
89            Ok(None) => {}
90            Ok(Some(v)) => {
91                version = Some(v);
92                let (start_str, _) = package_name.split_once('@').expect("error");
93                package_name = start_str.to_owned()
94            }
95            Err(e) => return Err(e),
96        }
97
98        let mut fmri = Self::new_from_package_name(package_name)?;
99        if let Some(p) = publisher {
100            fmri.change_publisher(p);
101        }
102        if let Some(v) = version {
103            fmri.change_version(v);
104        }
105        Ok(fmri)
106    }
107
108    /// Checks if package names are same
109    pub fn package_name_eq(&self, comparing_to: &FMRI) -> bool {
110        self.get_package_name_as_ref_string()
111            .eq(comparing_to.get_package_name_as_ref_string())
112    }
113
114    pub fn get_package_name_as_string(self) -> String {
115        self.package_name
116    }
117
118    pub fn get_package_name_as_ref_string(&self) -> &String {
119        &self.package_name
120    }
121
122    pub fn get_package_name_as_ref_mut_string(&mut self) -> &mut String {
123        &mut self.package_name
124    }
125
126    pub fn get_publisher(self) -> Option<Publisher> {
127        self.publisher
128    }
129
130    pub fn get_publisher_ref(&self) -> &Option<Publisher> {
131        &self.publisher
132    }
133
134    pub fn get_publisher_ref_mut(&mut self) -> &mut Option<Publisher> {
135        &mut self.publisher
136    }
137
138    pub fn has_publisher(&self) -> bool {
139        self.publisher.is_some()
140    }
141
142    pub fn change_publisher(&mut self, publisher: Publisher) {
143        self.publisher = Some(publisher);
144    }
145
146    /// Returns [`None`] if there isn't [`Publisher`]
147    pub fn get_publisher_as_ref_string(&self) -> Option<&String> {
148        if let Some(publisher) = &self.publisher {
149            return Some(publisher.get_as_ref_string());
150        }
151        None
152    }
153
154    pub fn remove_publisher(&mut self) {
155        self.publisher = None
156    }
157
158    pub fn get_version(self) -> Option<Version> {
159        self.version
160    }
161
162    pub fn get_version_ref(&self) -> &Option<Version> {
163        &self.version
164    }
165    pub fn get_version_ref_mut(&mut self) -> &mut Option<Version> {
166        &mut self.version
167    }
168
169    pub fn has_version(&self) -> bool {
170        self.version.is_some()
171    }
172
173    pub fn change_version(&mut self, version: Version) {
174        self.version = Some(version)
175    }
176
177    /// Returns [`None`] if there isn't [`Version`]
178    pub fn get_version_as_string(&self) -> Option<String> {
179        if let Some(version) = &self.version {
180            return Some(format!("{}", version));
181        }
182        None
183    }
184
185    pub fn remove_version(&mut self) -> &mut FMRI {
186        self.version = None;
187        self
188    }
189}
190
191impl PartialOrd<Self> for FMRI {
192    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
193        Some(self.cmp(other))
194    }
195}
196
197impl Ord for FMRI {
198    /// Compares versions of FMRI
199    fn cmp(&self, other: &Self) -> Ordering {
200        self.version
201            .as_ref()
202            .and_then(|ver| other.version.as_ref().map(|ver2| ver.cmp(ver2)))
203            .unwrap_or(Equal)
204    }
205}
206
207impl Display for FMRI {
208    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
209        let mut string: String = "".to_owned();
210
211        if let Some(publisher) = self.get_publisher_as_ref_string() {
212            string.push_str("pkg://");
213            string.push_str(publisher);
214            string.push('/');
215        } else {
216            string.push_str("pkg:/");
217        }
218
219        string.push_str(self.get_package_name_as_ref_string());
220
221        if let Some(version) = self.get_version_as_string() {
222            string.push_str(&version)
223        }
224
225        write!(f, "{}", string)
226    }
227}
228
229impl Debug for FMRI {
230    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231        write!(f, "{}", self)
232    }
233}