1use std::marker::PhantomData;
8use std::path::{Path, PathBuf};
9use std::sync::{Arc, Mutex};
10
11use serde::de::DeserializeOwned;
12use serde::Serialize;
13
14use onevcs::{Error, Result};
15
16pub trait Checked {
23 fn check(&self) -> Result<()>;
25}
26
27pub trait Store<S> {
29 fn with<R, F>(&self, act: F) -> Result<R>
35 where
36 F: FnOnce(&mut S) -> Result<R>;
37
38 fn snapshot(&self) -> Result<S>;
40}
41
42#[derive(Debug)]
45pub struct MemoryStore<S>(Arc<Mutex<S>>);
46
47impl<S> MemoryStore<S> {
48 pub fn new(state: S) -> Self {
50 Self(Arc::new(Mutex::new(state)))
51 }
52}
53
54impl<S> Clone for MemoryStore<S> {
58 fn clone(&self) -> Self {
59 Self(Arc::clone(&self.0))
60 }
61}
62
63impl<S: Clone> Store<S> for MemoryStore<S> {
64 fn with<R, F>(&self, act: F) -> Result<R>
65 where
66 F: FnOnce(&mut S) -> Result<R>,
67 {
68 let mut guard = self
72 .0
73 .lock()
74 .unwrap_or_else(|poisoned| poisoned.into_inner());
75 act(&mut guard)
76 }
77
78 fn snapshot(&self) -> Result<S> {
79 let guard = self
80 .0
81 .lock()
82 .unwrap_or_else(|poisoned| poisoned.into_inner());
83 Ok(guard.clone())
84 }
85}
86
87#[derive(Debug)]
90pub struct FileStore<S> {
91 path: PathBuf,
92 marker: PhantomData<S>,
93}
94
95impl<S> Clone for FileStore<S> {
96 fn clone(&self) -> Self {
97 Self {
98 path: self.path.clone(),
99 marker: PhantomData,
100 }
101 }
102}
103
104impl<S: Serialize + DeserializeOwned + Checked> FileStore<S> {
105 pub fn attach(path: impl Into<PathBuf>, fallback: &S) -> Result<Self> {
112 let store = Self::at(path)?;
113 if store.path.exists() {
114 store.snapshot()?;
115 return Ok(store);
116 }
117 store.save(fallback)?;
118 Ok(store)
119 }
120
121 pub fn replace(path: impl Into<PathBuf>, state: &S) -> Result<Self> {
126 let store = Self::at(path)?;
127 store.save(state)?;
128 Ok(store)
129 }
130
131 fn at(path: impl Into<PathBuf>) -> Result<Self> {
132 let path = path.into();
133 if let Some(parent) = path
134 .parent()
135 .filter(|parent| !parent.as_os_str().is_empty())
136 {
137 std::fs::create_dir_all(parent).map_err(|e| Error::Invalid {
138 reason: format!("cannot create {}: {e}", parent.display()),
139 })?;
140 }
141 Ok(Self {
142 path,
143 marker: PhantomData,
144 })
145 }
146
147 pub fn path(&self) -> &Path {
149 &self.path
150 }
151
152 fn save(&self, state: &S) -> Result<()> {
153 let json = serde_json::to_string_pretty(state).map_err(|e| Error::Invalid {
154 reason: format!(
155 "cannot serialize the state for {}: {e}",
156 self.path.display()
157 ),
158 })?;
159 std::fs::write(&self.path, format!("{json}\n")).map_err(|e| Error::Invalid {
160 reason: format!("cannot write {}: {e}", self.path.display()),
161 })
162 }
163}
164
165impl<S: Serialize + DeserializeOwned + Checked> Store<S> for FileStore<S> {
166 fn with<R, F>(&self, act: F) -> Result<R>
167 where
168 F: FnOnce(&mut S) -> Result<R>,
169 {
170 let mut state = self.snapshot()?;
171 let outcome = act(&mut state)?;
172 self.save(&state)?;
173 Ok(outcome)
174 }
175
176 fn snapshot(&self) -> Result<S> {
177 let raw = std::fs::read_to_string(&self.path).map_err(|e| Error::Invalid {
178 reason: format!(
179 "cannot read the provider state at {}: {e}",
180 self.path.display()
181 ),
182 })?;
183 let state: S = serde_json::from_str(&raw).map_err(|e| Error::Invalid {
184 reason: format!(
185 "the provider state at {} is not the shape this crate writes: {e}",
186 self.path.display()
187 ),
188 })?;
189 state.check().map_err(|e| Error::Invalid {
190 reason: format!("the provider state at {}: {e}", self.path.display()),
191 })?;
192 Ok(state)
193 }
194}