vortex_protocol/tlv/
error.rs

1/*
2 *     Copyright 2025 The Dragonfly Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::error::{Error as VortexError, Result as VortexResult};
18use bytes::{BufMut, Bytes, BytesMut};
19
20/// CODE_SIZE is the size of the error code in bytes.
21const CODE_SIZE: usize = 1;
22
23/// Code represents a error code.
24#[repr(u8)]
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Code {
27    /// Unknown error.
28    Unknown = 0,
29
30    /// Invalid argument, such as an invalid task ID or piece number.
31    InvalidArgument = 1,
32
33    /// Resource not found, such as a missing piece.
34    NotFound = 2,
35
36    /// Internal error, such as an unexpected error.
37    Internal = 3,
38
39    /// Reserved for future use.
40    Reserved(u8), // For tags 4-253
41}
42
43/// Implement TryFrom<u8> for Code.
44impl TryFrom<u8> for Code {
45    type Error = VortexError;
46
47    /// Converts a u8 to a Code enum.
48    fn try_from(value: u8) -> VortexResult<Self> {
49        match value {
50            0 => Ok(Code::Unknown),
51            1 => Ok(Code::InvalidArgument),
52            2 => Ok(Code::NotFound),
53            3 => Ok(Code::Internal),
54            4..=255 => Ok(Code::Reserved(value)),
55        }
56    }
57}
58
59/// Implement From<Code> for u8.
60impl From<Code> for u8 {
61    /// Converts a Code enum to a u8.
62    fn from(code: Code) -> u8 {
63        match code {
64            Code::Unknown => 0,
65            Code::InvalidArgument => 1,
66            Code::NotFound => 2,
67            Code::Internal => 3,
68            Code::Reserved(value) => value,
69        }
70    }
71}
72
73/// Error represents a error request.
74///
75/// Value Format:
76///  - Error Code (1 bytes): Error code.
77///  - Error Message (variable): Error message.
78///
79/// ```text
80/// -------------------------------------------------
81/// | Error Code (1 bytes) |      Error Message      |
82/// -------------------------------------------------
83/// ```
84#[derive(Debug, Clone)]
85pub struct Error {
86    code: Code,
87    message: String,
88}
89
90/// Error implements the Error functions.
91impl Error {
92    /// new creates a new Error request.
93    pub fn new(code: Code, message: String) -> Self {
94        Self { code, message }
95    }
96
97    /// code returns the error code.
98    pub fn code(&self) -> Code {
99        self.code
100    }
101
102    /// message returns the error message.
103    pub fn message(&self) -> &str {
104        &self.message
105    }
106
107    /// len returns the length of the error request, including the error code, error
108    /// message.
109    pub fn len(&self) -> usize {
110        self.message.len() + CODE_SIZE
111    }
112
113    /// is_empty returns whether the error request is empty.
114    pub fn is_empty(&self) -> bool {
115        self.len() == 0
116    }
117}
118
119/// Implement From<Error> for Bytes.
120impl From<Error> for Bytes {
121    /// from converts the error request to a byte slice.
122    fn from(err: Error) -> Self {
123        let mut bytes = BytesMut::with_capacity(err.len());
124        bytes.put_u8(err.code.into());
125        bytes.extend_from_slice(err.message.as_bytes());
126        bytes.freeze()
127    }
128}
129
130/// Implement TryFrom<Bytes> for Error.
131impl TryFrom<Bytes> for Error {
132    type Error = VortexError;
133
134    /// try_from decodes the error request from the byte slice.
135    fn try_from(bytes: Bytes) -> VortexResult<Self> {
136        if bytes.len() < CODE_SIZE {
137            return Err(VortexError::InvalidLength(format!(
138                "expected at least {} bytes for Error, got {}",
139                CODE_SIZE,
140                bytes.len()
141            )));
142        }
143
144        Ok(Error {
145            code: Code::try_from(
146                bytes
147                    .first()
148                    .ok_or(VortexError::InvalidPacket(
149                        "insufficient bytes for code".to_string(),
150                    ))?
151                    .to_owned(),
152            )?,
153            message: String::from_utf8(
154                bytes
155                    .get(CODE_SIZE..)
156                    .ok_or(VortexError::InvalidPacket(
157                        "insufficient bytes for message".to_string(),
158                    ))?
159                    .to_vec(),
160            )?,
161        })
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use bytes::Bytes;
169
170    #[test]
171    fn test_new() {
172        let code = Code::InvalidArgument;
173        let message = "Invalid argument".to_string();
174        let error = Error::new(code, message.clone());
175
176        assert_eq!(error.code(), code);
177        assert_eq!(error.message(), message);
178        assert_eq!(error.len(), message.len() + CODE_SIZE);
179    }
180
181    #[test]
182    fn test_is_non_empty() {
183        let error_non_empty = Error::new(Code::Unknown, "Error message".to_string());
184        assert!(!error_non_empty.is_empty());
185    }
186
187    #[test]
188    fn test_to_bytes_and_from_bytes() {
189        let code = Code::NotFound;
190        let message = "Resource not found".to_string();
191        let error = Error::new(code, message.clone());
192
193        let bytes: Bytes = error.into();
194        let error = Error::try_from(bytes).unwrap();
195
196        assert_eq!(error.code(), code);
197        assert_eq!(error.message(), message);
198    }
199
200    #[test]
201    fn test_from_bytes_invalid_input() {
202        let invalid_bytes = Bytes::from("");
203        assert!(Error::try_from(invalid_bytes).is_err());
204    }
205}