Skip to main content

nntp_proxy/protocol/
responses.rs

1//! NNTP response message constants and construction helpers
2//!
3//! This module provides pre-defined NNTP response messages and helpers
4//! for constructing responses according to RFC 3977.
5
6/// Multiline response terminator: "\r\n.\r\n" (RFC 3977)
7pub const MULTILINE_TERMINATOR: &[u8] = b"\r\n.\r\n";
8
9/// Line ending: "\r\n"
10pub const CRLF: &[u8] = b"\r\n";
11
12/// Terminator tail size for spanning terminator detection
13pub const TERMINATOR_TAIL_SIZE: usize = 4;
14
15/// Minimum response length (3-digit code + CRLF)
16pub const MIN_RESPONSE_LENGTH: usize = 5;
17
18// Authentication responses (RFC 4643)
19
20/// Authentication required response (381)
21pub const AUTH_REQUIRED: &[u8] = b"381 Password required\r\n";
22
23/// Authentication accepted response (281)
24pub const AUTH_ACCEPTED: &[u8] = b"281 Authentication accepted\r\n";
25
26/// Authentication failed response (481)
27pub const AUTH_FAILED: &[u8] = b"481 Authentication failed\r\n";
28
29/// Authentication required for this command (480)
30pub const AUTH_REQUIRED_FOR_COMMAND: &[u8] = b"480 Authentication required\r\n";
31
32// Standard responses
33
34/// Proxy greeting for per-command routing mode (200)
35pub const PROXY_GREETING_PCR: &[u8] = b"200 NNTP Proxy Ready (Per-Command Routing)\r\n";
36
37/// Connection closing response (205)
38pub const CONNECTION_CLOSING: &[u8] = b"205 Connection closing\r\n";
39
40/// Goodbye message (205) - common in tests
41pub const GOODBYE: &[u8] = b"205 Goodbye\r\n";
42
43// Error responses
44
45/// Command not supported response (500)
46pub const COMMAND_NOT_SUPPORTED: &[u8] = b"500 Command not supported by this proxy\r\n";
47
48/// Backend error response (503)
49pub const BACKEND_ERROR: &[u8] = b"503 Backend error\r\n";
50
51/// Backend server unavailable response (400)
52pub const BACKEND_UNAVAILABLE: &[u8] = b"400 Backend server unavailable\r\n";
53
54/// No such article response (430)
55pub const NO_SUCH_ARTICLE: &[u8] = b"430 No such article\r\n";
56
57/// Command not supported in stateless proxy mode (500)
58pub const COMMAND_NOT_SUPPORTED_STATELESS: &[u8] =
59    b"500 Command not supported by this proxy (stateless proxy mode)\r\n";
60
61// Response construction helpers
62
63/// Construct a greeting response (200)
64///
65/// # Examples
66/// ```
67/// use nntp_proxy::protocol::greeting;
68///
69/// let msg = greeting("news.example.com ready");
70/// assert_eq!(msg, "200 news.example.com ready\r\n");
71/// ```
72#[inline]
73pub fn greeting(message: &str) -> String {
74    format!("200 {}\r\n", message)
75}
76
77/// Construct a read-only greeting response (201)
78#[inline]
79pub fn greeting_readonly(message: &str) -> String {
80    format!("201 {}\r\n", message)
81}
82
83/// Construct a generic OK response (200)
84#[inline]
85pub fn ok_response(message: &str) -> String {
86    format!("200 {}\r\n", message)
87}
88
89/// Construct a generic error response with custom code and message
90///
91/// # Examples
92/// ```
93/// use nntp_proxy::protocol::error_response;
94///
95/// let msg = error_response(430, "No such article");
96/// assert_eq!(msg, "430 No such article\r\n");
97/// ```
98#[inline]
99pub fn error_response(code: u16, message: &str) -> String {
100    format!("{} {}\r\n", code, message)
101}
102
103/// Construct a response with custom status code and message
104#[inline]
105pub fn response(code: u16, message: &str) -> String {
106    format!("{} {}\r\n", code, message)
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_constants() {
115        assert_eq!(CRLF, b"\r\n");
116        assert_eq!(MULTILINE_TERMINATOR, b"\r\n.\r\n");
117        assert_eq!(MULTILINE_TERMINATOR.len(), 5);
118        assert_eq!(TERMINATOR_TAIL_SIZE, 4);
119    }
120
121    #[test]
122    fn test_greeting() {
123        assert_eq!(greeting("Ready"), "200 Ready\r\n");
124        assert_eq!(
125            greeting("news.example.com ready"),
126            "200 news.example.com ready\r\n"
127        );
128        assert_eq!(greeting_readonly("Read only"), "201 Read only\r\n");
129    }
130
131    #[test]
132    fn test_ok_response() {
133        assert_eq!(ok_response("OK"), "200 OK\r\n");
134        assert_eq!(ok_response("Command accepted"), "200 Command accepted\r\n");
135    }
136
137    #[test]
138    fn test_error_response() {
139        assert_eq!(
140            error_response(430, "No such article"),
141            "430 No such article\r\n"
142        );
143        assert_eq!(
144            error_response(500, "Command not recognized"),
145            "500 Command not recognized\r\n"
146        );
147    }
148
149    #[test]
150    fn test_response() {
151        assert_eq!(
152            response(215, "Newsgroups follow"),
153            "215 Newsgroups follow\r\n"
154        );
155        assert_eq!(
156            response(381, "Password required"),
157            "381 Password required\r\n"
158        );
159    }
160
161    #[test]
162    fn test_auth_constants() {
163        assert_eq!(AUTH_REQUIRED, b"381 Password required\r\n");
164        assert_eq!(AUTH_ACCEPTED, b"281 Authentication accepted\r\n");
165        assert_eq!(AUTH_FAILED, b"481 Authentication failed\r\n");
166        assert_eq!(
167            AUTH_REQUIRED_FOR_COMMAND,
168            b"480 Authentication required\r\n"
169        );
170    }
171
172    #[test]
173    fn test_standard_responses() {
174        assert!(PROXY_GREETING_PCR.starts_with(b"200"));
175        assert!(CONNECTION_CLOSING.starts_with(b"205"));
176        assert!(GOODBYE.starts_with(b"205"));
177    }
178
179    #[test]
180    fn test_error_constants() {
181        assert!(COMMAND_NOT_SUPPORTED.starts_with(b"500"));
182        assert!(BACKEND_ERROR.starts_with(b"503"));
183        assert!(BACKEND_UNAVAILABLE.starts_with(b"400"));
184        assert!(COMMAND_NOT_SUPPORTED_STATELESS.starts_with(b"500"));
185    }
186
187    #[test]
188    fn test_all_responses_end_with_crlf() {
189        assert!(AUTH_REQUIRED.ends_with(CRLF));
190        assert!(AUTH_ACCEPTED.ends_with(CRLF));
191        assert!(AUTH_FAILED.ends_with(CRLF));
192        assert!(AUTH_REQUIRED_FOR_COMMAND.ends_with(CRLF));
193        assert!(PROXY_GREETING_PCR.ends_with(CRLF));
194        assert!(CONNECTION_CLOSING.ends_with(CRLF));
195        assert!(GOODBYE.ends_with(CRLF));
196        assert!(COMMAND_NOT_SUPPORTED.ends_with(CRLF));
197        assert!(BACKEND_ERROR.ends_with(CRLF));
198        assert!(BACKEND_UNAVAILABLE.ends_with(CRLF));
199        assert!(COMMAND_NOT_SUPPORTED_STATELESS.ends_with(CRLF));
200    }
201}