serde_hkx_features/
fs.rs

1//! Convenience file I/O.
2
3use crate::error::{FailedReadFileSnafu, Result};
4use snafu::ResultExt as _;
5use std::borrow::Cow;
6use std::io;
7use std::{future::Future, io::Read as _, path::Path};
8use tokio::fs;
9
10/// Reads a file with some encoded string trait.
11pub trait ReadExt {
12    /// Reads a file with some encoded string.
13    fn read_any_string(&self) -> impl Future<Output = Result<String>>;
14
15    /// Read bytes.
16    ///
17    /// # Errors
18    /// - If the path does not exist.
19    /// - When an interrupt is received during reading.
20    fn read_bytes(&self) -> impl Future<Output = Result<Vec<u8>>>;
21}
22
23impl<P> ReadExt for P
24where
25    P: AsRef<Path>,
26{
27    async fn read_any_string(&self) -> Result<String> {
28        let input = self.as_ref();
29        let mut string = String::new();
30
31        let bytes = input.read_bytes().await?;
32        let mut decoder = encoding_rs_io::DecodeReaderBytes::new(bytes.as_slice());
33        decoder.read_to_string(&mut string)?;
34        Ok(string)
35    }
36
37    async fn read_bytes(&self) -> Result<Vec<u8>> {
38        let input = self.as_ref();
39        fs::read(input).await.context(FailedReadFileSnafu {
40            path: input.to_path_buf(),
41        })
42    }
43}
44
45/// Write specified or same location.
46///
47/// - `ext`: If `output` is unspecified, rewrite the `input` extension and make it the `output`. In that case, the extension.
48///
49/// # Errors
50/// - Conflict error due to simultaneous dir creation.
51/// - No write permission to the given path.
52pub async fn write<I, O>(
53    input: I,
54    output: Option<O>,
55    ext: &str,
56    contents: impl AsRef<[u8]>,
57) -> io::Result<()>
58where
59    I: AsRef<Path>,
60    O: AsRef<Path>,
61{
62    let input = input.as_ref();
63
64    let output = output.as_ref().map_or_else(
65        || {
66            let mut output = input.to_path_buf();
67            output.set_extension(ext);
68            Cow::Owned(output)
69        },
70        |output| Cow::Borrowed(output.as_ref()),
71    );
72
73    if let Some(parent) = output.parent() {
74        fs::create_dir_all(parent).await?;
75    }
76    fs::write(&output, contents).await?;
77
78    #[cfg(feature = "tracing")]
79    tracing::info!(
80        "Write Input: {} -> Output: {}",
81        input.display(),
82        output.display()
83    );
84    Ok(())
85}
86
87/// Write specified or same location.
88///
89/// - `ext`: If `output` is unspecified, rewrite the `input` extension and make it the `output`. In that case, the extension.
90///
91/// # Errors
92/// - Conflict error due to simultaneous dir creation.
93/// - No write permission to the given path.
94pub fn write_sync<I, O>(
95    input: I,
96    output: Option<O>,
97    ext: &str,
98    contents: impl AsRef<[u8]>,
99) -> io::Result<()>
100where
101    I: AsRef<Path>,
102    O: AsRef<Path>,
103{
104    let input = input.as_ref();
105
106    let output = output.as_ref().map_or_else(
107        || {
108            let mut output = input.to_path_buf();
109            output.set_extension(ext);
110            Cow::Owned(output)
111        },
112        |output| Cow::Borrowed(output.as_ref()),
113    );
114
115    if let Some(parent) = output.parent() {
116        std::fs::create_dir_all(parent)?;
117    }
118    std::fs::write(&output, contents)?;
119
120    #[cfg(feature = "tracing")]
121    tracing::info!(
122        "Write Input: {} -> Output: {}",
123        input.display(),
124        output.display()
125    );
126    Ok(())
127}