kernel/discard/
request.rs1use serde::{Deserialize, Serialize};
11
12use crate::address::Address;
13use crate::budget::ByteLen;
14use crate::error::{AxCode, AxError};
15use crate::locator::Locator;
16use crate::taint::TaintSet;
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "snake_case")]
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
23pub enum Restoration {
24 Tracked(Locator),
25 Interred(Locator),
26 Rebuildable { reason: String },
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct Discard {
33 paths: Vec<Address>,
34 plan: Restoration,
35 taint: TaintSet,
36 total_bytes: ByteLen,
37}
38
39impl Discard {
40 pub fn new(
45 paths: Vec<Address>,
46 plan: Restoration,
47 taint: TaintSet,
48 total_bytes: ByteLen,
49 ) -> Result<Discard, AxError> {
50 if paths.is_empty() {
51 return Err(AxError::failure(
52 AxCode::InvalidArgs,
53 "construct discard",
54 "empty path list",
55 )
56 .with_recovery("name at least one path in the `paths` argument of the call"));
57 }
58 let plan_ok = match &plan {
59 Restoration::Tracked(locator) => matches!(locator, Locator::File { .. }),
60 Restoration::Interred(locator) => matches!(locator, Locator::Cas { .. }),
61 Restoration::Rebuildable { reason } => !reason.is_empty(),
62 };
63 if !plan_ok {
64 return Err(AxError::failure(
65 AxCode::DiscardIrreversible,
66 "construct discard",
67 "restoration plan does not resolve",
68 )
69 .with_recovery(
70 "Tracked cites a file: locator, Interred a cas: locator, \
71 Rebuildable a non-empty reason",
72 ));
73 }
74 Ok(Discard {
75 paths,
76 plan,
77 taint,
78 total_bytes,
79 })
80 }
81
82 pub fn paths(&self) -> &[Address] {
83 &self.paths
84 }
85
86 pub fn plan(&self) -> &Restoration {
87 &self.plan
88 }
89
90 pub fn taint(&self) -> &TaintSet {
91 &self.taint
92 }
93
94 pub fn total_bytes(&self) -> ByteLen {
95 self.total_bytes
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum DiscardRequest {
103 Planned(Discard),
104 Unplanned {
105 paths: Vec<Address>,
106 taint: TaintSet,
107 total_bytes: ByteLen,
108 },
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum DenyReason {
116 NoRestoration,
117 Tainted,
120}
121
122#[cfg(test)]
123#[allow(
124 clippy::unwrap_used,
125 clippy::expect_used,
126 clippy::panic,
127 clippy::indexing_slicing,
128 reason = "test code"
129)]
130mod tests {
131 use super::*;
132 fn addr(raw: &str) -> Address {
133 Address::parse(raw).unwrap()
134 }
135 fn tracked() -> Restoration {
136 Restoration::Tracked(Locator::parse(&format!("file:b/x.md@{}", "ab".repeat(20))).unwrap())
137 }
138 fn interred() -> Restoration {
139 Restoration::Interred(Locator::parse(&format!("cas:b3-{}", "cd".repeat(32))).unwrap())
140 }
141 #[test]
142 fn restoration_schemes_are_checked_at_construction() {
143 assert!(
144 Discard::new(
145 vec![addr("b/x.md")],
146 tracked(),
147 TaintSet::empty(),
148 ByteLen::new(1)
149 )
150 .is_ok()
151 );
152 assert!(
153 Discard::new(
154 vec![addr("b/x.md")],
155 interred(),
156 TaintSet::empty(),
157 ByteLen::new(1)
158 )
159 .is_ok()
160 );
161 let wrong =
163 Restoration::Tracked(Locator::parse(&format!("cas:b3-{}", "cd".repeat(32))).unwrap());
164 let err = Discard::new(
165 vec![addr("b/x.md")],
166 wrong,
167 TaintSet::empty(),
168 ByteLen::new(1),
169 )
170 .unwrap_err();
171 assert_eq!(err.code(), &AxCode::DiscardIrreversible);
172 let empty_reason = Restoration::Rebuildable {
173 reason: String::new(),
174 };
175 assert!(
176 Discard::new(
177 vec![addr("b/x.md")],
178 empty_reason,
179 TaintSet::empty(),
180 ByteLen::new(1)
181 )
182 .is_err()
183 );
184 let no_paths = Discard::new(vec![], tracked(), TaintSet::empty(), ByteLen::new(1));
185 assert_eq!(no_paths.unwrap_err().code(), &AxCode::InvalidArgs);
186 }
187}