postgresql_extensions/
model.rs1use 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#[derive(Debug)]
16pub struct AvailableExtension {
17 namespace: String,
18 name: String,
19 description: String,
20}
21
22impl AvailableExtension {
23 #[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 #[must_use]
35 pub fn namespace(&self) -> &str {
36 &self.namespace
37 }
38
39 #[must_use]
41 pub fn name(&self) -> &str {
42 &self.name
43 }
44
45 #[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#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
60pub struct InstalledConfiguration {
61 extensions: Vec<InstalledExtension>,
62}
63
64impl InstalledConfiguration {
65 #[must_use]
67 pub fn new(extensions: Vec<InstalledExtension>) -> Self {
68 Self { extensions }
69 }
70
71 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 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 #[must_use]
127 pub fn extensions(&self) -> &Vec<InstalledExtension> {
128 &self.extensions
129 }
130
131 #[must_use]
133 pub fn extensions_mut(&mut self) -> &mut Vec<InstalledExtension> {
134 &mut self.extensions
135 }
136}
137
138#[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 #[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 #[must_use]
161 pub fn namespace(&self) -> &str {
162 &self.namespace
163 }
164
165 #[must_use]
167 pub fn name(&self) -> &str {
168 &self.name
169 }
170
171 #[must_use]
173 pub fn version(&self) -> &Version {
174 &self.version
175 }
176
177 #[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}