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