Skip to main content

postgresql_extensions/
model.rs

1use crate::Error::IoError;
2use crate::Result;
3use semver::Version;
4use serde::{Deserialize, Serialize};
5#[cfg(test)]
6use std::ffi::OsString;
7use std::fmt::Display;
8#[cfg(not(feature = "tokio"))]
9use std::io::Write;
10use std::path::PathBuf;
11#[cfg(feature = "tokio")]
12use tokio::io::{AsyncReadExt, AsyncWriteExt};
13
14/// A struct representing an available extension.
15#[derive(Debug)]
16pub struct AvailableExtension {
17    namespace: String,
18    name: String,
19    description: String,
20}
21
22impl AvailableExtension {
23    /// Creates a new available extension.
24    #[must_use]
25    pub fn new(namespace: &str, name: &str, description: &str) -> Self {
26        Self {
27            namespace: namespace.to_string(),
28            name: name.to_string(),
29            description: description.to_string(),
30        }
31    }
32
33    /// Gets the namespace of the extension.
34    #[must_use]
35    pub fn namespace(&self) -> &str {
36        &self.namespace
37    }
38
39    /// Gets the name of the extension.
40    #[must_use]
41    pub fn name(&self) -> &str {
42        &self.name
43    }
44
45    /// Gets the description of the extension.
46    #[must_use]
47    pub fn description(&self) -> &str {
48        &self.description
49    }
50}
51
52impl Display for AvailableExtension {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        write!(f, "{}:{} {}", self.namespace, self.name, self.description)
55    }
56}
57
58/// A struct representing an installed configuration.
59#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
60pub struct InstalledConfiguration {
61    extensions: Vec<InstalledExtension>,
62}
63
64impl InstalledConfiguration {
65    /// Creates a new installed configuration.
66    #[must_use]
67    pub fn new(extensions: Vec<InstalledExtension>) -> Self {
68        Self { extensions }
69    }
70
71    /// Reads the configuration from the specified `path`.
72    ///
73    /// # Errors
74    /// * If an error occurs while reading the configuration.
75    pub async fn read<P: Into<PathBuf>>(path: P) -> Result<Self> {
76        #[cfg(feature = "tokio")]
77        {
78            let mut file = tokio::fs::File::open(path.into())
79                .await
80                .map_err(|error| IoError(error.to_string()))?;
81            let mut contents = vec![];
82            file.read_to_end(&mut contents)
83                .await
84                .map_err(|error| IoError(error.to_string()))?;
85            let config = serde_json::from_slice(&contents)?;
86            Ok(config)
87        }
88        #[cfg(not(feature = "tokio"))]
89        {
90            let file =
91                std::fs::File::open(path.into()).map_err(|error| IoError(error.to_string()))?;
92            let reader = std::io::BufReader::new(file);
93            let config =
94                serde_json::from_reader(reader).map_err(|error| IoError(error.to_string()))?;
95            Ok(config)
96        }
97    }
98
99    /// Writes the configuration to the specified `path`.
100    ///
101    /// # Errors
102    /// * If an error occurs while writing the configuration.
103    pub async fn write<P: Into<PathBuf>>(&self, path: P) -> Result<()> {
104        let content = serde_json::to_string_pretty(&self)?;
105
106        #[cfg(feature = "tokio")]
107        {
108            let mut file = tokio::fs::File::create(path.into())
109                .await
110                .map_err(|error| IoError(error.to_string()))?;
111            file.write_all(content.as_bytes())
112                .await
113                .map_err(|error| IoError(error.to_string()))?;
114        }
115        #[cfg(not(feature = "tokio"))]
116        {
117            let mut file =
118                std::fs::File::create(path.into()).map_err(|error| IoError(error.to_string()))?;
119            file.write_all(content.as_bytes())
120                .map_err(|error| IoError(error.to_string()))?;
121        }
122        Ok(())
123    }
124
125    /// Gets the extensions of the configuration.
126    #[must_use]
127    pub fn extensions(&self) -> &Vec<InstalledExtension> {
128        &self.extensions
129    }
130
131    /// Gets the extensions of the configuration.
132    #[must_use]
133    pub fn extensions_mut(&mut self) -> &mut Vec<InstalledExtension> {
134        &mut self.extensions
135    }
136}
137
138/// A struct representing an installed extension.
139#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
140pub struct InstalledExtension {
141    namespace: String,
142    name: String,
143    version: Version,
144    files: Vec<PathBuf>,
145}
146
147impl InstalledExtension {
148    /// Creates a new installed extension.
149    #[must_use]
150    pub fn new(namespace: &str, name: &str, version: Version, files: Vec<PathBuf>) -> Self {
151        Self {
152            namespace: namespace.to_string(),
153            name: name.to_string(),
154            version,
155            files,
156        }
157    }
158
159    /// Gets the namespace of the extension.
160    #[must_use]
161    pub fn namespace(&self) -> &str {
162        &self.namespace
163    }
164
165    /// Gets the name of the extension.
166    #[must_use]
167    pub fn name(&self) -> &str {
168        &self.name
169    }
170
171    /// Gets the version of the extension.
172    #[must_use]
173    pub fn version(&self) -> &Version {
174        &self.version
175    }
176
177    /// Gets the files of the extension.
178    #[must_use]
179    pub fn files(&self) -> &Vec<PathBuf> {
180        &self.files
181    }
182}
183
184impl Display for InstalledExtension {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        write!(f, "{}:{}:{}", self.namespace, self.name, self.version)
187    }
188}
189
190#[cfg(test)]
191pub struct TestSettings;
192
193#[cfg(test)]
194impl postgresql_commands::Settings for TestSettings {
195    fn get_binary_dir(&self) -> PathBuf {
196        PathBuf::from(".")
197    }
198
199    fn get_host(&self) -> OsString {
200        "localhost".into()
201    }
202
203    fn get_port(&self) -> u16 {
204        5432
205    }
206
207    fn get_username(&self) -> OsString {
208        "postgres".into()
209    }
210
211    fn get_password(&self) -> OsString {
212        "password".into()
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use postgresql_commands::Settings;
220
221    #[test]
222    fn test_settings() {
223        let settings = TestSettings;
224        assert_eq!(settings.get_binary_dir(), PathBuf::from("."));
225        assert_eq!(settings.get_host(), "localhost");
226        assert_eq!(settings.get_port(), 5432);
227        assert_eq!(settings.get_username(), "postgres");
228        assert_eq!(settings.get_password(), "password");
229    }
230
231    #[test]
232    fn test_available_extension() {
233        let available_extension = AvailableExtension::new("namespace", "name", "description");
234        assert_eq!(available_extension.namespace(), "namespace");
235        assert_eq!(available_extension.name(), "name");
236        assert_eq!(available_extension.description(), "description");
237        assert_eq!(
238            available_extension.to_string(),
239            "namespace:name description"
240        );
241    }
242
243    #[test]
244    fn test_installed_configuration() {
245        let installed_configuration = InstalledConfiguration::new(vec![]);
246        assert!(installed_configuration.extensions.is_empty());
247    }
248
249    #[cfg(target_os = "linux")]
250    #[tokio::test]
251    async fn test_installed_configuration_io() -> Result<()> {
252        let temp_file =
253            tempfile::NamedTempFile::new().map_err(|error| IoError(error.to_string()))?;
254        let file = temp_file.as_ref();
255        let extensions = vec![InstalledExtension::new(
256            "namespace",
257            "name",
258            Version::new(1, 0, 0),
259            vec![PathBuf::from("file")],
260        )];
261        let expected_configuration = InstalledConfiguration::new(extensions);
262        expected_configuration.write(file).await?;
263        let configuration = InstalledConfiguration::read(file).await?;
264        assert_eq!(expected_configuration, configuration);
265        tokio::fs::remove_file(file)
266            .await
267            .map_err(|error| IoError(error.to_string()))?;
268        Ok(())
269    }
270
271    #[test]
272    fn test_installed_extension() {
273        let installed_extension = InstalledExtension::new(
274            "namespace",
275            "name",
276            Version::new(1, 0, 0),
277            vec![PathBuf::from("file")],
278        );
279        assert_eq!(installed_extension.namespace(), "namespace");
280        assert_eq!(installed_extension.name(), "name");
281        assert_eq!(installed_extension.version(), &Version::new(1, 0, 0));
282        assert_eq!(installed_extension.files(), &vec![PathBuf::from("file")]);
283        assert_eq!(installed_extension.to_string(), "namespace:name:1.0.0");
284    }
285}