eips/
error.rs

1/*
2 * Copyright (C) 2025 taylor.fish <contact@taylor.fish>
3 *
4 * This file is part of Eips.
5 *
6 * Eips is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Eips is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with Eips. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20//! Error types.
21
22#[cfg(doc)]
23use crate::options::EipsOptions;
24use core::fmt::{self, Debug, Display};
25
26/// An error encountered while [applying a remote change][apply].
27///
28/// [apply]: crate::Eips::apply_change
29#[non_exhaustive]
30#[derive(Clone, Copy, Debug)]
31pub enum ChangeError<Id> {
32    /// The remote change's parent ID was invalid.
33    BadParentId(Id),
34
35    /// The remote change has no parent but its direction is
36    /// [`Before`](crate::change::Direction::Before).
37    BadDirection(Id),
38
39    /// The remote change corresponds to an existing item, but there was a
40    /// conflict between the old and new data.
41    MergeConflict(Id),
42
43    /// The remote change contains move information, but move operations are
44    /// not supported.
45    ///
46    /// Remember that the value of [`EipsOptions::SupportsMove`] must be the
47    /// same for all clients in a distributed system. Clients that support move
48    /// operations cannot talk to clients that don't.
49    UnsupportedMove(Id),
50
51    /// The remote change's old location ID was invalid.
52    BadOldLocation(Id),
53
54    /// The remote change has no move information but refers to the destination
55    /// of a move operation.
56    UnexpectedMove(Id),
57
58    /// The remote change's old location ID incorrectly corresponds to an
59    /// the destination of a move operation.
60    OldLocationIsMove(Id),
61
62    /// The remote change represents an item to move but is incorrectly marked
63    /// as hidden.
64    HiddenMove(Id),
65
66    /// The change's move timestamp was greater than [`usize::MAX`].
67    ///
68    /// This should not happen under normal circumstances. Eips requires causal
69    /// delivery, which means if a client receives a change with a move
70    /// timestamp greater than [`usize::MAX`], it must have already received at
71    /// least [`usize::MAX`] prior move operations on this element, but that
72    /// would use more than [`usize::MAX`] bytes of memory, which is not
73    /// possible.
74    TimestampOverflow {
75        id: Id,
76        timestamp: crate::MoveTimestamp,
77    },
78}
79
80impl<Id> ChangeError<Id> {
81    pub(crate) fn to_basic(&self) -> BasicChangeError {
82        use BasicChangeError as Basic;
83        match self {
84            Self::BadParentId(_) => Basic::BadParentId,
85            Self::BadDirection(_) => Basic::BadDirection,
86            Self::MergeConflict(_) => Basic::MergeConflict,
87            Self::UnsupportedMove(_) => Basic::UnsupportedMove,
88            Self::BadOldLocation(_) => Basic::BadOldLocation,
89            Self::UnexpectedMove(_) => Basic::UnexpectedMove,
90            Self::OldLocationIsMove(_) => Basic::OldLocationIsMove,
91            Self::HiddenMove(_) => Basic::HiddenMove,
92            Self::TimestampOverflow {
93                timestamp,
94                ..
95            } => Basic::TimestampOverflow {
96                timestamp: *timestamp,
97            },
98        }
99    }
100}
101
102impl<Id: Display> Display for ChangeError<Id> {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        let basic = self.to_basic();
105        match self {
106            Self::BadParentId(id) => write!(f, "{basic}: {id}"),
107            Self::BadDirection(id) => write!(f, "{basic}: {id}"),
108            Self::MergeConflict(id) => write!(f, "{basic}: {id}"),
109            Self::UnsupportedMove(id) => write!(f, "{basic}: {id}"),
110            Self::BadOldLocation(id) => write!(f, "{basic}: {id}"),
111            Self::UnexpectedMove(id) => write!(f, "{basic}: {id}"),
112            Self::OldLocationIsMove(id) => write!(f, "{basic}: {id}"),
113            Self::HiddenMove(id) => write!(f, "{basic}: {id}"),
114            Self::TimestampOverflow {
115                id,
116                ..
117            } => write!(f, "{basic} (id {id})"),
118        }
119    }
120}
121
122#[cfg(feature = "std")]
123#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "std")))]
124impl<Id: Debug + Display> std::error::Error for ChangeError<Id> {}
125
126/// An error encountered due to an invalid or out-of-bounds index.
127#[non_exhaustive]
128#[derive(Clone, Copy, Debug)]
129pub struct IndexError {
130    /// The invalid index.
131    pub index: usize,
132}
133
134impl Display for IndexError {
135    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(fmt, "bad index: {}", self.index)
137    }
138}
139
140#[cfg(feature = "std")]
141#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "std")))]
142impl std::error::Error for IndexError {}
143
144/// An error encountered due to an invalid or missing ID.
145#[non_exhaustive]
146#[derive(Clone, Copy, Debug)]
147pub struct IdError<Id> {
148    /// The invalid ID.
149    pub id: Id,
150}
151
152impl<Id: Display> Display for IdError<Id> {
153    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
154        write!(fmt, "bad id: {}", self.id)
155    }
156}
157
158#[cfg(feature = "std")]
159#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "std")))]
160impl<Id: Debug + Display> std::error::Error for IdError<Id> {}
161
162#[derive(Clone, Copy, Debug)]
163pub(crate) enum BasicChangeError {
164    BadParentId,
165    BadDirection,
166    MergeConflict,
167    UnsupportedMove,
168    BadOldLocation,
169    UnexpectedMove,
170    OldLocationIsMove,
171    HiddenMove,
172    TimestampOverflow {
173        timestamp: crate::MoveTimestamp,
174    },
175}
176
177impl Display for BasicChangeError {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        match self {
180            Self::BadParentId => write!(f, "bad parent id"),
181            Self::BadDirection => {
182                write!(f, "change has no parent but its direction is 'before'")
183            }
184            Self::MergeConflict => {
185                write!(f, "conflict between change and existing data")
186            }
187            Self::UnsupportedMove => {
188                write!(f, "change has move info but moves are unsupported")
189            }
190            Self::BadOldLocation => write!(f, "bad old location"),
191            Self::UnexpectedMove => {
192                write!(f, "change has no move info but is a move destination")
193            }
194            Self::OldLocationIsMove => {
195                write!(f, "old location is a move destination")
196            }
197            Self::HiddenMove => {
198                write!(f, "change is a move destination but is hidden")
199            }
200            Self::TimestampOverflow {
201                timestamp,
202            } => write!(f, "move timestamp is too large: {timestamp}"),
203        }
204    }
205}