1use atomicwrites::{AtomicFile, DisallowOverwrite};
2use camino::{Utf8Path, Utf8PathBuf};
3use std::{
4 fmt::{self, Display},
5 io::{self, Write},
6};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum WriteStage {
12 Write,
14
15 Persist,
18}
19
20#[derive(Debug)]
22pub struct WriteError {
23 path: Utf8PathBuf,
24 stage: WriteStage,
25 source: io::Error,
26}
27
28impl WriteError {
29 fn new(path: &Utf8Path, error: atomicwrites::Error<io::Error>) -> Self {
30 let (stage, source) = match error {
31 atomicwrites::Error::Internal(source) => {
32 (WriteStage::Persist, source)
33 }
34 atomicwrites::Error::User(source) => (WriteStage::Write, source),
35 };
36 WriteError { path: path.to_owned(), stage, source }
37 }
38
39 pub fn path(&self) -> &Utf8Path {
41 &self.path
42 }
43
44 pub fn stage(&self) -> WriteStage {
46 self.stage
47 }
48}
49
50impl fmt::Display for WriteError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self.stage {
53 WriteStage::Write => write!(
54 f,
55 "writing value to temporary file for port file {}",
56 self.path
57 ),
58 WriteStage::Persist => write!(
59 f,
60 "creating, syncing, or renaming temporary file for port \
61 file {}",
62 self.path
63 ),
64 }
65 }
66}
67
68impl std::error::Error for WriteError {
69 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70 Some(&self.source)
71 }
72}
73
74pub fn write<T: Display>(path: &Utf8Path, value: T) -> Result<(), WriteError> {
94 AtomicFile::new(path, DisallowOverwrite)
95 .write(|f| writeln!(f, "{value}"))
96 .map_err(|error| WriteError::new(path, error))
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn user_error_is_write_stage() {
105 let path = Utf8Path::new("port");
106 let error = atomicwrites::Error::User(io::Error::from(
107 io::ErrorKind::StorageFull,
108 ));
109 let err = WriteError::new(path, error);
110 assert_eq!(err.path(), path);
111 assert_eq!(err.stage(), WriteStage::Write);
112 assert!(
113 err.to_string().contains("writing value to temporary file"),
114 "unexpected error: {err}"
115 );
116 assert_eq!(err.source.kind(), io::ErrorKind::StorageFull);
117 }
118
119 #[test]
120 fn internal_error_is_persist_stage() {
121 let path = Utf8Path::new("port");
122 let error = atomicwrites::Error::Internal(io::Error::from(
123 io::ErrorKind::PermissionDenied,
124 ));
125 let err = WriteError::new(path, error);
126 assert_eq!(err.path(), path);
127 assert_eq!(err.stage(), WriteStage::Persist);
128 assert!(
129 err.to_string()
130 .contains("creating, syncing, or renaming temporary file"),
131 "unexpected error: {err}"
132 );
133 assert_eq!(err.source.kind(), io::ErrorKind::PermissionDenied);
134 }
135}