Skip to main content

lattice_protocol/
error.rs

1//! Protocol-level error type.
2//!
3//! Crate-level errors compose with this via `thiserror::Error` `#[from]`.
4
5use thiserror::Error;
6
7use crate::ids::DocumentId;
8use crate::position::Position;
9
10/// A request that is structurally well-formed but does not fit the document
11/// it addresses. Crate errors wrap it (`lattice_core`'s `CoreError::Protocol`)
12/// rather than redefine these cases.
13#[derive(Debug, Error)]
14pub enum ProtocolError {
15    /// No document with this id exists (it was never opened, or was closed).
16    #[error("unknown document {0}")]
17    UnknownDocument(DocumentId),
18
19    /// A [`Position`] names a line past the end of the document, or a byte
20    /// past the end of its line. `lattice_core::Buffer` raises it on edit
21    /// and position conversion.
22    #[error("position {position:?} is out of bounds (document has {line_count} lines)")]
23    PositionOutOfBounds {
24        /// The offending position.
25        position: Position,
26        /// How many lines the document actually has.
27        line_count: u32,
28    },
29
30    /// A write was computed against an older document version than the
31    /// current one (optimistic concurrency).
32    #[error("stale version: client supplied {client}, document is at {actual}")]
33    StaleVersion {
34        /// The version the caller based its request on.
35        client: u64,
36        /// The document's current version.
37        actual: u64,
38    },
39
40    /// A [`Range`](crate::Range) is malformed — typically `end` before
41    /// `start`. The payload is a fixed, human-readable reason.
42    #[error("invalid range: {0}")]
43    InvalidRange(&'static str),
44}
45
46/// `Result` specialised to [`ProtocolError`].
47pub type Result<T> = std::result::Result<T, ProtocolError>;
48
49#[cfg(test)]
50mod tests {
51    #![allow(clippy::unwrap_used, clippy::panic)]
52    use super::*;
53    use crate::ids::DocumentId;
54
55    #[test]
56    fn unknown_document_renders_id() {
57        let err = ProtocolError::UnknownDocument(DocumentId::new(7));
58        assert_eq!(format!("{err}"), "unknown document DocumentId#7");
59    }
60
61    #[test]
62    fn position_out_of_bounds_includes_line_count() {
63        let err = ProtocolError::PositionOutOfBounds {
64            position: Position::new(99, 0),
65            line_count: 3,
66        };
67        let msg = format!("{err}");
68        assert!(msg.contains("99"), "msg = {msg}");
69        assert!(msg.contains("3 lines"), "msg = {msg}");
70    }
71
72    #[test]
73    fn stale_version_includes_both_versions() {
74        let err = ProtocolError::StaleVersion {
75            client: 4,
76            actual: 7,
77        };
78        let msg = format!("{err}");
79        assert!(msg.contains("4"));
80        assert!(msg.contains("7"));
81    }
82
83    #[test]
84    fn invalid_range_carries_static_reason() {
85        let err = ProtocolError::InvalidRange("end < start");
86        assert_eq!(format!("{err}"), "invalid range: end < start");
87    }
88}