erltf/
errors.rs

1// Copyright (C) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt;
16use std::result::Result as StdResult;
17use std::str::Utf8Error;
18use thiserror::Error;
19
20pub type Result<T> = StdResult<T, Error>;
21
22#[derive(Error, Debug)]
23pub enum Error {
24    #[error("decode error: {0}")]
25    Decode(#[from] DecodeError),
26    #[error("encode error: {0}")]
27    Encode(#[from] EncodeError),
28}
29
30#[derive(Error, Debug, Clone, PartialEq)]
31pub enum DecodeError {
32    #[error("unexpected end of input")]
33    UnexpectedEof,
34    #[error("invalid tag: {0}")]
35    InvalidTag(u8),
36    #[error("invalid version: expected {expected}, got {actual}")]
37    InvalidVersion { expected: u8, actual: u8 },
38    #[error("invalid UTF-8 in atom: {0}")]
39    InvalidUtf8(String),
40    #[error("atom too large: {size} bytes (max {max})")]
41    AtomTooLarge { size: usize, max: usize },
42    #[error("list too large: {size} elements (max {max})")]
43    ListTooLarge { size: usize, max: usize },
44    #[error("tuple too large: {size} elements (max {max})")]
45    TupleTooLarge { size: usize, max: usize },
46    #[error("map too large: {size} entries (max {max})")]
47    MapTooLarge { size: usize, max: usize },
48    #[error("binary too large: {size} bytes (max {max})")]
49    BinaryTooLarge { size: usize, max: usize },
50    #[error("invalid list structure")]
51    InvalidList,
52    #[error("invalid map structure")]
53    InvalidMap,
54    #[error("unsupported term type: {0}")]
55    UnsupportedType(String),
56    #[error("buffer too small: need {needed} bytes, have {available}")]
57    BufferTooSmall { needed: usize, available: usize },
58    #[error("invalid format: {0}")]
59    InvalidFormat(String),
60    #[error("trailing data: {0} bytes after term")]
61    TrailingData(usize),
62    #[error("invalid PID format: {0}")]
63    InvalidPidFormat(String),
64}
65
66#[derive(Debug, Clone, PartialEq)]
67pub struct ParsingContext {
68    pub byte_offset: usize,
69    pub path: Vec<PathSegment>,
70}
71
72#[derive(Debug, Clone, PartialEq)]
73pub enum PathSegment {
74    TupleElement(usize),
75    ListElement(usize),
76    MapKey,
77    MapValue(String),
78    ImproperListTail,
79    FunFreeVar(usize),
80}
81
82impl ParsingContext {
83    pub fn new() -> Self {
84        ParsingContext {
85            byte_offset: 0,
86            path: Vec::new(),
87        }
88    }
89
90    pub fn with_offset(offset: usize) -> Self {
91        ParsingContext {
92            byte_offset: offset,
93            path: Vec::new(),
94        }
95    }
96
97    pub fn push(&mut self, segment: PathSegment) {
98        self.path.push(segment);
99    }
100
101    pub fn pop(&mut self) {
102        self.path.pop();
103    }
104
105    pub fn display_path(&self) -> String {
106        if self.path.is_empty() {
107            return "root".to_string();
108        }
109
110        let mut result = String::from("root");
111        for segment in &self.path {
112            match segment {
113                PathSegment::TupleElement(i) => result.push_str(&format!("[{}]", i)),
114                PathSegment::ListElement(i) => result.push_str(&format!("[{}]", i)),
115                PathSegment::MapKey => result.push_str(".key"),
116                PathSegment::MapValue(k) => result.push_str(&format!(".{}", k)),
117                PathSegment::ImproperListTail => result.push_str(".tail"),
118                PathSegment::FunFreeVar(i) => result.push_str(&format!(".free_var[{}]", i)),
119            }
120        }
121        result
122    }
123}
124
125impl Default for ParsingContext {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131#[derive(Error, Debug, Clone, PartialEq)]
132pub struct ContextualDecodeError {
133    pub error: DecodeError,
134    pub context: ParsingContext,
135}
136
137impl fmt::Display for ContextualDecodeError {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        write!(
140            f,
141            "{} at byte offset {} in path {}",
142            self.error,
143            self.context.byte_offset,
144            self.context.display_path()
145        )
146    }
147}
148
149impl ContextualDecodeError {
150    pub fn new(error: DecodeError, context: ParsingContext) -> Self {
151        ContextualDecodeError { error, context }
152    }
153}
154
155#[derive(Error, Debug)]
156pub enum EncodeError {
157    #[error("atom too large: {size} bytes (max 65535)")]
158    AtomTooLarge { size: usize },
159    #[error("string too large: {size} bytes")]
160    StringTooLarge { size: usize },
161    #[error("list too large: {size} elements")]
162    ListTooLarge { size: usize },
163    #[error("map too large: {size} entries")]
164    MapTooLarge { size: usize },
165    #[error("binary too large: {size} bytes")]
166    BinaryTooLarge { size: usize },
167    #[error("tuple too large: {size} elements (max 4294967295)")]
168    TupleTooLarge { size: usize },
169    #[error("reference has too many IDs: {size} (max 65535)")]
170    ReferenceTooLarge { size: usize },
171    #[error("too many atoms for DIST_HEADER: {count} (max 255)")]
172    TooManyAtoms { count: usize },
173    #[error("I/O error: {0}")]
174    IoError(#[from] std::io::Error),
175    #[error("buffer overflow")]
176    BufferOverflow,
177}
178
179#[derive(Error, Debug, Clone, PartialEq)]
180pub enum TermConversionError {
181    #[error("expected {expected}, got {actual}")]
182    WrongType {
183        expected: &'static str,
184        actual: &'static str,
185    },
186    #[error("value out of range for target type")]
187    OutOfRange,
188}
189
190impl From<Utf8Error> for DecodeError {
191    fn from(e: Utf8Error) -> Self {
192        DecodeError::InvalidUtf8(e.to_string())
193    }
194}