1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
use std::fmt;

use crate::types::prelude::{ArticleNumber, NntpCommand};

/// Retrieve an article's header and body
#[derive(Clone, Debug)]
pub enum Article {
    /// Globally unique message ID
    MessageId(String),
    /// Article number relative to the current group
    Number(ArticleNumber),
    /// Currently selected article
    Current,
}

impl fmt::Display for Article {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Article::MessageId(id) => write!(f, "ARTICLE {}", id),
            Article::Number(num) => write!(f, "ARTICLE {}", num),
            Article::Current => write!(f, "ARTICLE"),
        }
    }
}

impl NntpCommand for Article {}

/// Retrieve the body for an Article
#[derive(Clone, Debug)]
pub enum Body {
    /// Globally unique message ID
    MessageId(String),
    /// Article number relative to the current group
    Number(ArticleNumber),
    /// Currently selected article
    Current,
}

impl fmt::Display for Body {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Body::MessageId(id) => write!(f, "BODY {}", id),
            Body::Number(num) => write!(f, "BODY {}", num),
            Body::Current => write!(f, "BODY"),
        }
    }
}

impl NntpCommand for Body {}

/// Get the capabilities provided by the server
#[derive(Clone, Copy, Debug)]
pub struct Capabilities;

impl fmt::Display for Capabilities {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "CAPABILITIES")
    }
}

impl NntpCommand for Capabilities {}

/// Get the server time
#[derive(Clone, Copy, Debug)]
struct Date;

impl fmt::Display for Date {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "DATE")
    }
}

impl NntpCommand for Date {}

/// Select a group
#[derive(Clone, Debug)]
pub struct Group(pub String);

impl fmt::Display for Group {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "GROUP {}", self.0)
    }
}

impl NntpCommand for Group {}

/// Retrieve a specific header from one or more articles
#[derive(Clone, Debug)]
pub enum Hdr {
    /// A single article by message ID
    MessageId {
        /// The name of the header
        field: String,
        /// The unique message id of the article
        id: String,
    },
    /// A range of articles
    Range {
        /// The name of the header
        field: String,
        /// The low number of the article range
        low: ArticleNumber,
        /// The high number of the article range
        high: ArticleNumber,
    },
    /// The current article
    Current {
        /// The name of the header
        field: String,
    },
}

impl fmt::Display for Hdr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Hdr::MessageId { field, id } => write!(f, "HDR {} {}", field, id),
            Hdr::Range { field, low, high } => write!(f, "HDR {} {}-{}", field, low, high),
            Hdr::Current { field } => write!(f, "HDR {}", field),
        }
    }
}

impl NntpCommand for Hdr {}

/// Retrieve the headers for an article
#[derive(Clone, Debug)]
pub enum Head {
    /// Globally unique message ID
    MessageId(String),
    /// Article number relative to the current group
    Number(ArticleNumber),
    /// Currently selected article
    Current,
}

impl fmt::Display for Head {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Head::MessageId(id) => write!(f, "HEAD {}", id),
            Head::Number(num) => write!(f, "HEAD {}", num),
            Head::Current => write!(f, "HEAD"),
        }
    }
}

impl NntpCommand for Head {}

/// Retrieve help text about the servers capabilities
struct Help;

impl fmt::Display for Help {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "HELP")
    }
}

impl NntpCommand for Help {}

/// Inform the server that you have an article for upload
struct IHave(String);

impl fmt::Display for IHave {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "IHAVE {}", self.0)
    }
}

impl NntpCommand for IHave {}

/// Attempt to set the current article to the previous article number
struct Last;

impl fmt::Display for Last {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "LAST")
    }
}

impl NntpCommand for Last {}

/// Retrieve a list of information from the server
///
/// Not all LIST keywords are supported by all servers.
///
/// ## Usage Note
///
/// If you want to send LIST without any keywords simply send [`List::Active`] as they are equivalent.
#[derive(Clone, Debug)]
#[allow(missing_docs)]
pub enum List {
    /// Return a list of active newsgroups
    ///
    /// [RFC 3977 7.6.3](https://tools.ietf.org/html/rfc3977#section-7.6.3)
    Active { wildmat: Option<String> },
    /// Return information about when news groups were created
    ///
    /// [RFC 3977 7.6.4](https://tools.ietf.org/html/rfc3977#section-7.6.4)
    ActiveTimes { wildmat: Option<String> },
    /// List descriptions of newsgroups available on the server
    ///
    /// [RFC 3977 7.6.6](https://tools.ietf.org/html/rfc3977#section-7.6.6)
    Newsgroups { wildmat: Option<String> },
    /// Retrieve information about the Distribution header for news articles
    ///
    /// [RFC 3977 7.6.5](https://tools.ietf.org/html/rfc3977#section-7.6.5)
    DistribPats,
    /// Return field descriptors for headers returned by OVER/XOVER
    ///
    /// [RFC 3977 8.4](https://tools.ietf.org/html/rfc3977#section-8.4)
    OverviewFmt,
}

impl fmt::Display for List {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fn print_wildmat(f: &mut fmt::Formatter<'_>, wildmat: Option<&String>) -> fmt::Result {
            if let Some(w) = wildmat.as_ref() {
                write!(f, " {}", w)
            } else {
                Ok(())
            }
        }

        write!(f, "LIST")?;
        match self {
            List::Active { wildmat } => {
                write!(f, " ACTIVE")?;
                print_wildmat(f, wildmat.as_ref())
            }
            List::OverviewFmt => write!(f, " OVERVIEW.FMT"),
            List::ActiveTimes { wildmat } => {
                write!(f, " ACTIVE TIMES")?;
                print_wildmat(f, wildmat.as_ref())
            }
            List::Newsgroups { wildmat } => {
                write!(f, " ACTIVE TIMES")?;
                print_wildmat(f, wildmat.as_ref())
            }
            List::DistribPats => write!(f, " DISTRIB.PATS"),
        }
    }
}

impl NntpCommand for List {}

/// Enable reader mode on a mode switching server
#[derive(Clone, Copy, Debug)]
pub struct ModeReader;

impl fmt::Display for ModeReader {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "MODE READER")
    }
}

impl NntpCommand for ModeReader {}

// TODO(commands) implement NEWGROUPS

// TODO(commands) implement NEWNEWS

/// Attempt to set the current article to the next article number
#[derive(Clone, Copy, Debug)]
pub struct Next;

impl fmt::Display for Next {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "NEXT")
    }
}

impl NntpCommand for Next {}

/// Retrieve all of the fields (e.g. headers/metadata) for one or more articles
#[derive(Clone, Debug)]
pub enum Over {
    /// A single article by message ID
    MessageId(String),
    /// A range of articles
    Range {
        /// The low number of the article
        low: ArticleNumber,
        /// The high number of the article
        high: ArticleNumber,
    },
    /// The current article
    Current,
}

impl fmt::Display for Over {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Over::MessageId(id) => write!(f, "OVER {}", id),
            Over::Range { low, high } => write!(f, "OVER {}-{}", low, high),
            Over::Current => write!(f, "OVER"),
        }
    }
}

impl NntpCommand for Over {}

// TODO(commands) complete POST implementation
/*
/// Post an article to the news server
///
/// POSTING is a two part exchange. The [`Initial`](Post::Initial) variant is used
/// to determine if the server will accept the article while the [`Article`](Post::Article) is
/// used to send the data.
///
///
/// For more information see [RFC 3977 6.3.1](https://tools.ietf.org/html/rfc3977#section-6.3.1)
#[derive(Clone, Debug)]
enum Post<'a> {
    Initial,
    /// The article body NOT including the terminating sequence
    Article(&'a [u8])
}

impl NntpCommand for Post<'_> {}

impl fmt::Display for Post<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Post::Initial => write!(f, "POST"),
            Post::Article(body) => {
                unimplemented!()
            },
        }
    }
}
*/

/// Close the connection
#[derive(Clone, Copy, Debug)]
pub struct Quit;

impl fmt::Display for Quit {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "QUIT")
    }
}

impl NntpCommand for Quit {}

/// Check if an article exists in the newsgroup
#[derive(Clone, Debug)]
pub enum Stat {
    /// Globally unique message ID
    MessageId(String),
    /// Article number relative to the current group
    Number(ArticleNumber),
    /// Currently selected article
    Current,
}

impl fmt::Display for Stat {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Stat::MessageId(id) => write!(f, "STAT {}", id),
            Stat::Number(num) => write!(f, "STAT {}", num),
            Stat::Current => write!(f, "STAT"),
        }
    }
}

impl NntpCommand for Stat {}