ephemeris_documents/
ownership.rs1use std::fmt;
4use std::str::FromStr;
5
6use facet::Facet;
7use facet_value::{VObject, Value};
8
9use crate::error::OwnershipError;
10use crate::path::{KeyPath, PathTokens};
11
12#[derive(Debug, Clone, Facet, PartialEq, Eq)]
14#[facet(proxy = Vec<String>)]
15pub struct ManagedPath(Vec<String>);
16
17impl ManagedPath {
18 #[must_use]
20 pub fn single(key: String) -> Self {
21 Self(vec![key])
22 }
23
24 #[must_use]
26 pub fn segments(&self) -> &[String] {
27 &self.0
28 }
29
30 pub(crate) fn nest(&self, value: Value) -> Value {
32 self.0.iter().rev().fold(value, |inner, key| {
33 let mut object = VObject::new();
34 object.insert(key, inner);
35 Value::from(object)
36 })
37 }
38}
39
40impl TryFrom<Vec<String>> for ManagedPath {
41 type Error = OwnershipError;
42
43 fn try_from(segments: Vec<String>) -> Result<Self, Self::Error> {
44 if segments.is_empty() {
45 Err(OwnershipError::PathEmpty)
46 } else {
47 Ok(Self(segments))
48 }
49 }
50}
51
52impl FromStr for ManagedPath {
53 type Err = OwnershipError;
54
55 fn from_str(text: &str) -> Result<Self, Self::Err> {
56 Ok(Self(PathTokens::parse(text)?))
57 }
58}
59
60impl fmt::Display for ManagedPath {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 KeyPath::from(self).fmt(f)
63 }
64}
65
66impl From<&ManagedPath> for KeyPath {
67 fn from(path: &ManagedPath) -> Self {
68 Self::from(path.segments())
69 }
70}
71
72impl From<&ManagedPath> for Vec<String> {
73 fn from(path: &ManagedPath) -> Self {
74 path.0.clone()
75 }
76}
77
78#[derive(Debug, Clone, Facet, PartialEq, Eq)]
80#[facet(tag = "type", deny_unknown_fields)]
81#[repr(u8)]
82pub enum DocumentOwnership {
83 #[facet(rename = "whole")]
86 Whole {
87 #[facet(default)]
89 unmanaged: Vec<ManagedPath>,
90 },
91 #[facet(rename = "selected")]
93 Selected {
94 owned: Vec<ManagedPath>,
96 },
97}
98
99impl Default for DocumentOwnership {
100 fn default() -> Self {
101 Self::Whole {
102 unmanaged: Vec::new(),
103 }
104 }
105}
106
107impl DocumentOwnership {
108 #[must_use]
110 pub fn whole<I: IntoIterator<Item = ManagedPath>>(unmanaged: I) -> Self {
111 Self::Whole {
112 unmanaged: unmanaged.into_iter().collect(),
113 }
114 }
115
116 #[must_use]
118 pub fn selected<I: IntoIterator<Item = ManagedPath>>(owned: I) -> Self {
119 Self::Selected {
120 owned: owned.into_iter().collect(),
121 }
122 }
123
124 pub(crate) const fn is_partial(&self) -> bool {
126 match self {
127 Self::Whole { unmanaged } => !unmanaged.is_empty(),
128 Self::Selected { owned: _ } => true,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub(crate) enum Claim {
136 Owned,
138 Unmanaged,
140 Mixed,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub(crate) enum DefaultClaim {
147 Owned,
149 Unmanaged,
151}
152
153impl DefaultClaim {
154 pub(crate) const fn is_owned(self) -> bool {
155 matches!(self, Self::Owned)
156 }
157
158 const fn opposite(self) -> Self {
159 match self {
160 Self::Owned => Self::Unmanaged,
161 Self::Unmanaged => Self::Owned,
162 }
163 }
164
165 const fn claim(self) -> Claim {
166 match self {
167 Self::Owned => Claim::Owned,
168 Self::Unmanaged => Claim::Unmanaged,
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
175struct PathSet(Vec<ManagedPath>);
176
177impl TryFrom<&[ManagedPath]> for PathSet {
178 type Error = OwnershipError;
179
180 fn try_from(paths: &[ManagedPath]) -> Result<Self, Self::Error> {
181 for (index, left) in paths.iter().enumerate() {
182 if let Some(right) = paths
183 .iter()
184 .skip(index.saturating_add(1))
185 .find(|right| left.0.starts_with(&right.0) || right.0.starts_with(&left.0))
186 {
187 return Err(OwnershipError::PathOverlap {
188 first: left.clone(),
189 second: right.clone(),
190 });
191 }
192 }
193 Ok(Self(paths.to_vec()))
194 }
195}
196
197impl PathSet {
198 fn covers(&self, path: &[String]) -> bool {
200 self.0
201 .iter()
202 .any(|member| path.starts_with(member.segments()))
203 }
204
205 fn has_below(&self, path: &[String]) -> bool {
207 self.0.iter().any(|member| {
208 member.segments().len() > path.len() && member.segments().starts_with(path)
209 })
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Eq)]
216pub(crate) struct Ownership {
217 default: DefaultClaim,
218 exceptions: PathSet,
219}
220
221impl TryFrom<&DocumentOwnership> for Ownership {
222 type Error = OwnershipError;
223
224 fn try_from(ownership: &DocumentOwnership) -> Result<Self, Self::Error> {
225 let (default, paths) = match ownership {
226 DocumentOwnership::Whole { unmanaged } => (DefaultClaim::Owned, unmanaged),
227 DocumentOwnership::Selected { owned } => (DefaultClaim::Unmanaged, owned),
228 };
229 Ok(Self {
230 default,
231 exceptions: PathSet::try_from(paths.as_slice())?,
232 })
233 }
234}
235
236impl Ownership {
237 pub(crate) const fn default_claim(&self) -> DefaultClaim {
239 self.default
240 }
241
242 pub(crate) fn claim(&self, path: &[String]) -> Claim {
244 if self.exceptions.covers(path) {
245 self.default.opposite().claim()
246 } else if self.exceptions.has_below(path) {
247 Claim::Mixed
248 } else {
249 self.default.claim()
250 }
251 }
252}
253
254#[cfg(test)]
255mod tests;