ietfdata_rs/lib.rs
1// Copyright (C) 2019 University of Glasgow
2//
3// Redistribution and use in source and binary forms, with or without
4// modification, are permitted provided that the following conditions
5// are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9//
10// 2. Redistributions in binary form must reproduce the above copyright
11// notice, this list of conditions and the following disclaimer in the
12// documentation and/or other materials provided with the distribution.
13//
14// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24// POSSIBILITY OF SUCH DAMAGE.
25//
26// SPDX-License-Identifier: BSD-2-Clause
27
28// This library contains code to interact with the IETF Datatracker
29// (https://datatracker.ietf.org/release/about)
30//
31// The Datatracker API is at https://datatracker.ietf.org/api/v1 and is
32// a REST API implemented using Django Tastypie (http://tastypieapi.org)
33//
34// It's possible to do time range queries on many of these values, for example:
35// https://datatracker.ietf.org/api/v1/person/person/?time__gt=2018-03-27T14:07:36
36//
37// See also:
38// https://datatracker.ietf.org/api/
39// https://trac.tools.ietf.org/tools/ietfdb/wiki/DatabaseSchemaDescription
40// https://trac.tools.ietf.org/tools/ietfdb/wiki/DatatrackerDrafts
41// RFC 6174 "Definition of IETF Working Group Document States"
42// RFC 6175 "Requirements to Extend the Datatracker for IETF Working Group Chairs and Authors"
43// RFC 6292 "Requirements for a Working Group Charter Tool"
44// RFC 6293 "Requirements for Internet-Draft Tracking by the IETF Community in the Datatracker"
45// RFC 6322 "Datatracker States and Annotations for the IAB, IRTF, and Independent Submission Streams"
46// RFC 6359 "Datatracker Extensions to Include IANA and RFC Editor Processing Information"
47// RFC 7760 "Statement of Work for Extensions to the IETF Datatracker for Author Statistics"
48
49extern crate chrono;
50extern crate reqwest;
51extern crate serde;
52extern crate serde_json;
53
54use chrono::prelude::*;
55use serde::{Deserialize, Deserializer};
56use std::error;
57use std::fmt;
58
59// =================================================================================================================================
60// Helper functions:
61
62fn deserialize_time<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
63 where D: Deserializer<'de>
64{
65 let s = String::deserialize(deserializer)?;
66 Utc.datetime_from_str(&s, "%Y-%m-%dT%H:%M:%S%.f").map_err(serde::de::Error::custom)
67}
68
69// Generic types representing a paginated list of responses from the Datatracker:
70
71#[derive(Deserialize, Debug)]
72struct Meta {
73 total_count : u32,
74 limit : u32,
75 offset : u32,
76 previous : Option<String>,
77 next : Option<String>
78}
79
80#[derive(Deserialize, Debug)]
81struct Page<T> {
82 meta : Meta,
83 objects : Vec<T>
84}
85
86pub struct PaginatedList<'a, T> {
87 iter : <Vec<T> as IntoIterator>::IntoIter,
88 next : Option<String>,
89 dt : &'a Datatracker
90}
91
92impl<'a, T> PaginatedList<'a, T>
93 where for<'de> T: Deserialize<'de>
94{
95 pub fn new(dt: &'a Datatracker, url : String) -> Result<Self, DatatrackerError> {
96 let mut res = dt.connection.get(&url).send()?;
97 let pl : Page<T> = res.json()?;
98
99 Ok(Self {
100 next : pl.meta.next.clone(),
101 iter : pl.objects.into_iter(),
102 dt : dt
103 })
104 }
105
106 fn try_next(&mut self) -> Result<Option<T>, DatatrackerError> {
107 match self.iter.next() {
108 Some(x) => {
109 Ok(Some(x))
110 }
111 None => {
112 match self.next.clone() {
113 Some(ref url_frag) => {
114 let url = format!("https://datatracker.ietf.org/{}", url_frag);
115 let mut res = self.dt.connection.get(&url).send()?;
116 let pl : Page<T> = res.json()?;
117 self.next = pl.meta.next.clone();
118 self.iter = pl.objects.into_iter();
119 self.try_next()
120 }
121 None => {
122 Ok(None)
123 }
124 }
125 }
126 }
127 }
128}
129
130impl<'a, T> Iterator for PaginatedList<'a, T>
131 where for<'de> T: Deserialize<'de>
132{
133 type Item = Result<T, DatatrackerError>;
134
135 fn next(&mut self) -> Option<Self::Item> {
136 match self.try_next() {
137 Ok(None) => None,
138 Ok(Some(x)) => Some(Ok(x)),
139 Err(e) => Some(Err(e))
140 }
141 }
142}
143
144// =================================================================================================================================
145// IETF Datatracker types:
146
147#[derive(Deserialize, Debug, Eq, PartialEq)]
148pub struct EmailUri(String);
149
150/// A mapping from email address to person in the IETF datatracker.
151#[derive(Deserialize, Debug)]
152pub struct Email {
153 pub resource_uri : EmailUri,
154 pub address : String,
155 pub person : PersonUri,
156 #[serde(deserialize_with="deserialize_time")]
157 pub time : DateTime<Utc>,
158 pub origin : String,
159 pub primary : bool,
160 pub active : bool
161}
162
163#[derive(Deserialize, Debug, Eq, PartialEq)]
164pub struct HistoricalEmailUri(String);
165
166#[derive(Deserialize, Debug)]
167pub struct HistoricalEmail {
168 // Fields common with Email:
169 pub resource_uri : HistoricalEmailUri,
170 pub address : String,
171 pub person : PersonUri,
172 #[serde(deserialize_with="deserialize_time")]
173 pub time : DateTime<Utc>,
174 pub origin : String,
175 pub primary : bool,
176 pub active : bool,
177 // Fields recording the history:
178 pub history_change_reason : Option<String>,
179 pub history_user : Option<String>,
180 pub history_id : u64,
181 pub history_type : String,
182 #[serde(deserialize_with="deserialize_time")]
183 pub history_date : DateTime<Utc>
184}
185
186
187#[derive(Deserialize, Debug, Eq, PartialEq)]
188pub struct PersonUri(String);
189
190/// A person in the IETF datatracker.
191#[derive(Deserialize, Debug)]
192#[serde(deny_unknown_fields)]
193pub struct Person {
194 pub id : u64,
195 pub resource_uri : PersonUri,
196 pub name : String,
197 pub name_from_draft : Option<String>,
198 pub biography : String,
199 pub ascii : String,
200 pub ascii_short : Option<String>,
201 #[serde(deserialize_with="deserialize_time")]
202 pub time : DateTime<Utc>,
203 pub photo : Option<String>, // Actually a URL
204 pub photo_thumb : Option<String>, // Actually a URL
205 pub user : Option<String>,
206 pub consent : Option<bool>
207}
208
209#[derive(Deserialize, Debug, Eq, PartialEq)]
210pub struct HistoricalPersonUri(String);
211
212/// A historical person in the IETF datatracker.
213#[derive(Deserialize, Debug)]
214#[serde(deny_unknown_fields)]
215pub struct HistoricalPerson {
216 // Fields common with Person:
217 pub id : u64,
218 pub resource_uri : HistoricalPersonUri,
219 pub name : String,
220 pub name_from_draft : String,
221 pub biography : String,
222 pub ascii : String,
223 pub ascii_short : Option<String>,
224 #[serde(deserialize_with="deserialize_time")]
225 pub time : DateTime<Utc>,
226 pub photo : Option<String>, // Actually a URL
227 pub photo_thumb : Option<String>, // Actually a URL
228 pub user : String,
229 pub consent : Option<bool>,
230 // Fields recording the history:
231 pub history_change_reason : Option<String>,
232 pub history_user : String,
233 pub history_type : String,
234 pub history_id : u64,
235 #[serde(deserialize_with="deserialize_time")]
236 pub history_date : DateTime<Utc>,
237}
238
239#[derive(Deserialize, Debug, Eq, PartialEq)]
240pub struct PersonAliasUri(String);
241
242/// An alias in the IETF datatracker.
243#[derive(Deserialize, Debug)]
244#[serde(deny_unknown_fields)]
245pub struct PersonAlias {
246 pub id : u64,
247 pub resource_uri : PersonAliasUri,
248 pub person : PersonUri,
249 pub name : String,
250}
251
252// =================================================================================================================================
253// The DatatrackerError type:
254
255#[derive(Debug)]
256pub enum DatatrackerError {
257 NotFound,
258 IoError(reqwest::Error)
259}
260
261impl fmt::Display for DatatrackerError {
262 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
263 match *self {
264 DatatrackerError::NotFound => write!(f, "Not found"),
265 DatatrackerError::IoError(ref e) => e.fmt(f)
266 }
267 }
268}
269
270impl error::Error for DatatrackerError {
271 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
272 match *self {
273 DatatrackerError::NotFound => None,
274 DatatrackerError::IoError(ref e) => Some(e)
275 }
276 }
277}
278
279impl From<reqwest::Error> for DatatrackerError {
280 fn from(err: reqwest::Error) -> DatatrackerError {
281 DatatrackerError::IoError(err)
282 }
283}
284
285// =================================================================================================================================
286// IETF Datatracker API:
287
288pub struct Datatracker {
289 connection : reqwest::Client
290}
291
292impl Datatracker {
293 fn retrieve<T>(&self, url : &str) -> Result<T, DatatrackerError>
294 where for<'de> T: Deserialize<'de>
295 {
296 let mut res = self.connection.get(url).send()?;
297 if res.status().is_success() {
298 let res : T = res.json()?;
299 Ok(res)
300 } else {
301 Err(DatatrackerError::NotFound)
302 }
303 }
304
305 pub fn new() -> Self {
306 Datatracker {
307 connection : reqwest::Client::new()
308 }
309 }
310
311 // ----------------------------------------------------------------------------------------------------------------------------
312 // Datatracker API endpoints returning information about email addresses:
313 // * https://datatracker.ietf.org/api/v1/person/email/csp@csperkins.org/
314 // * https://datatracker.ietf.org/api/v1/person/historicalemail/
315
316 /// Retrieve information about an email address.
317 ///
318 /// This returns the information held about a particular email address.
319 /// If you want information about the person with a particular address,
320 /// use `person_from_email()`.
321 pub fn email(&self, email : &str) -> Result<Email, DatatrackerError> {
322 let url = format!("https://datatracker.ietf.org/api/v1/person/email/{}/", email);
323 self.retrieve::<Email>(&url)
324 }
325
326 pub fn email_history_for_address<'a>(&'a self, email : &'a str) -> Result<PaginatedList<HistoricalEmail>, DatatrackerError> {
327 let url = format!("https://datatracker.ietf.org/api/v1/person/historicalemail/?address={}", email);
328 PaginatedList::<'a, HistoricalEmail>::new(self, url)
329 }
330
331 pub fn email_history_for_person<'a>(&'a self, person : &'a Person) -> Result<PaginatedList<HistoricalEmail>, DatatrackerError> {
332 let url = format!("https://datatracker.ietf.org/api/v1/person/historicalemail/?person={}", person.id);
333 PaginatedList::<'a, HistoricalEmail>::new(self, url)
334 }
335
336 // ----------------------------------------------------------------------------------------------------------------------------
337 // Datatracker API endpoints returning information about people:
338 // * https://datatracker.ietf.org/api/v1/person/person/20209/
339 // * https://datatracker.ietf.org/api/v1/person/person/
340 // * https://datatracker.ietf.org/api/v1/person/historicalperson/
341 // * https://datatracker.ietf.org/api/v1/person/alias/
342
343 pub fn person(&self, person_uri : &PersonUri) -> Result<Person, DatatrackerError> {
344 assert!(person_uri.0.starts_with("/api/v1/person/person/"));
345 let url = format!("https://datatracker.ietf.org/{}/", person_uri.0);
346 self.retrieve::<Person>(&url)
347 }
348
349 pub fn person_from_email(&self, email : &str) -> Result<Person, DatatrackerError> {
350 let person = self.email(email)?.person;
351 self.person(&person)
352 }
353
354 pub fn person_aliases<'a>(&'a self, person : &'a Person) -> Result<PaginatedList<PersonAlias>, DatatrackerError> {
355 let url = format!("https://datatracker.ietf.org/api/v1/person/alias/?person={}", person.id);
356 PaginatedList::<'a, PersonAlias>::new(self, url)
357 }
358
359 pub fn person_history<'a>(&'a self, person : &'a Person) -> Result<PaginatedList<HistoricalPerson>, DatatrackerError> {
360 let url = format!("https://datatracker.ietf.org/api/v1/person/historicalperson/?id={}", person.id);
361 PaginatedList::<'a, HistoricalPerson>::new(self, url)
362 }
363
364 pub fn people<'a>(&'a self) -> Result<PaginatedList<'a, Person>, DatatrackerError> {
365 let url = format!("https://datatracker.ietf.org/api/v1/person/person/");
366 PaginatedList::<'a, Person>::new(self, url)
367 }
368
369 pub fn people_with_name<'a>(&'a self, name: &'a str) -> Result<PaginatedList<'a, Person>, DatatrackerError> {
370 let url = format!("https://datatracker.ietf.org/api/v1/person/person/?name={}", name);
371 PaginatedList::<'a, Person>::new(self, url)
372 }
373
374 pub fn people_with_name_containing<'a>(&'a self, name_contains: &'a str)
375 -> Result<PaginatedList<'a, Person>, DatatrackerError>
376 {
377 let url = format!("https://datatracker.ietf.org/api/v1/person/person/?name__contains={}", name_contains);
378 PaginatedList::<'a, Person>::new(self, url)
379 }
380
381 pub fn people_between<'a>(&'a self, start: DateTime<Utc>, before: DateTime<Utc>)
382 -> Result<PaginatedList<'a, Person>, DatatrackerError>
383 {
384 let s = start.format("%Y-%m-%dT%H:%M:%S");
385 let b = before.format("%Y-%m-%dT%H:%M:%S");
386 let url = format!("https://datatracker.ietf.org/api/v1/person/person/?time__gte={}&time__lt={}", &s, &b);
387 PaginatedList::<'a, Person>::new(self, url)
388 }
389
390 // ----------------------------------------------------------------------------------------------------------------------------
391 // Datatracker API endpoints returning information about documents:
392 // https://datatracker.ietf.org/api/v1/doc/document/ - list of documents
393 // https://datatracker.ietf.org/api/v1/doc/document/draft-ietf-avt-rtp-new/ - info about document
394 // https://datatracker.ietf.org/api/v1/doc/docalias/?name=/ - draft that became the given RFC
395 // https://datatracker.ietf.org/api/v1/doc/state/ - Types of state a document can be in
396 // https://datatracker.ietf.org/api/v1/doc/statetype/ - Possible types of state for a document
397 // https://datatracker.ietf.org/api/v1/doc/docevent/ - list of document events
398 // https://datatracker.ietf.org/api/v1/doc/docevent/?doc=... - events for a document
399 // https://datatracker.ietf.org/api/v1/doc/docevent/?by=... - events by a person (as /api/v1/person/person)
400 // https://datatracker.ietf.org/api/v1/doc/docevent/?time=... - events by time
401 // https://datatracker.ietf.org/api/v1/doc/documentauthor/?document=... - authors of a document
402 // https://datatracker.ietf.org/api/v1/doc/documentauthor/?person=... - documents by person (as /api/v1/person/person)
403 // https://datatracker.ietf.org/api/v1/doc/documentauthor/?email=... - documents by person with particular email
404 // https://datatracker.ietf.org/api/v1/doc/dochistory/
405 // https://datatracker.ietf.org/api/v1/doc/dochistoryauthor/
406 // https://datatracker.ietf.org/api/v1/doc/docreminder/
407 // https://datatracker.ietf.org/api/v1/doc/documenturl/
408 // https://datatracker.ietf.org/api/v1/doc/statedocevent/ - subset of /api/v1/doc/docevent/; same parameters
409 // https://datatracker.ietf.org/api/v1/doc/ballotdocevent/ - " "
410 // https://datatracker.ietf.org/api/v1/doc/newrevisiondocevent/ - " "
411 // https://datatracker.ietf.org/api/v1/doc/submissiondocevent/ - " "
412 // https://datatracker.ietf.org/api/v1/doc/writeupdocevent/ - " "
413 // https://datatracker.ietf.org/api/v1/doc/consensusdocevent/ - " "
414 // https://datatracker.ietf.org/api/v1/doc/ballotpositiondocevent/ - " "
415 // https://datatracker.ietf.org/api/v1/doc/reviewrequestdocevent/ - " "
416 // https://datatracker.ietf.org/api/v1/doc/lastcalldocevent/ - " "
417 // https://datatracker.ietf.org/api/v1/doc/telechatdocevent/ - " "
418 // https://datatracker.ietf.org/api/v1/doc/relateddocument/?source=... - documents that source draft relates to (references, replaces, etc)
419 // https://datatracker.ietf.org/api/v1/doc/relateddocument/?target=... - documents that relate to target draft
420 // https://datatracker.ietf.org/api/v1/doc/ballottype/ - Types of ballot that can be issued on a document
421 // https://datatracker.ietf.org/api/v1/doc/relateddochistory/
422 // https://datatracker.ietf.org/api/v1/doc/initialreviewdocevent/
423 // https://datatracker.ietf.org/api/v1/doc/deletedevent/
424 // https://datatracker.ietf.org/api/v1/doc/addedmessageevent/
425 // https://datatracker.ietf.org/api/v1/doc/editedauthorsdocevent/
426
427
428
429 // ----------------------------------------------------------------------------------------------------------------------------
430 // Datatracker API endpoints returning information about names:
431 // https://datatracker.ietf.org/api/v1/name/doctypename/
432 // https://datatracker.ietf.org/api/v1/name/streamname/
433 // https://datatracker.ietf.org/api/v1/name/dbtemplatetypename/
434 // https://datatracker.ietf.org/api/v1/name/docrelationshipname/
435 // https://datatracker.ietf.org/api/v1/name/doctagname/
436 // https://datatracker.ietf.org/api/v1/name/docurltagname/
437 // https://datatracker.ietf.org/api/v1/name/groupstatename/
438 // https://datatracker.ietf.org/api/v1/name/formallanguagename/
439 // https://datatracker.ietf.org/api/v1/name/timeslottypename/
440 // https://datatracker.ietf.org/api/v1/name/liaisonstatementeventtypename/
441 // https://datatracker.ietf.org/api/v1/name/stdlevelname/
442 // https://datatracker.ietf.org/api/v1/name/ballotpositionname/
443 // https://datatracker.ietf.org/api/v1/name/reviewrequeststatename/
444 // https://datatracker.ietf.org/api/v1/name/groupmilestonestatename/
445 // https://datatracker.ietf.org/api/v1/name/iprlicensetypename/
446 // https://datatracker.ietf.org/api/v1/name/feedbacktypename/
447 // https://datatracker.ietf.org/api/v1/name/reviewtypename/
448 // https://datatracker.ietf.org/api/v1/name/iprdisclosurestatename/
449 // https://datatracker.ietf.org/api/v1/name/reviewresultname/
450 // https://datatracker.ietf.org/api/v1/name/liaisonstatementstate/
451 // https://datatracker.ietf.org/api/v1/name/roomresourcename/
452 // https://datatracker.ietf.org/api/v1/name/liaisonstatementtagname/
453 // https://datatracker.ietf.org/api/v1/name/topicaudiencename/
454 // https://datatracker.ietf.org/api/v1/name/continentname/
455 // https://datatracker.ietf.org/api/v1/name/nomineepositionstatename/
456 // https://datatracker.ietf.org/api/v1/name/importantdatename/
457 // https://datatracker.ietf.org/api/v1/name/liaisonstatementpurposename/
458 // https://datatracker.ietf.org/api/v1/name/constraintname/
459 // https://datatracker.ietf.org/api/v1/name/sessionstatusname/
460 // https://datatracker.ietf.org/api/v1/name/ipreventtypename/
461 // https://datatracker.ietf.org/api/v1/name/agendatypename/
462 // https://datatracker.ietf.org/api/v1/name/docremindertypename/
463 // https://datatracker.ietf.org/api/v1/name/intendedstdlevelname/
464 // https://datatracker.ietf.org/api/v1/name/countryname/
465 // https://datatracker.ietf.org/api/v1/name/grouptypename/
466 // https://datatracker.ietf.org/api/v1/name/draftsubmissionstatename/
467 // https://datatracker.ietf.org/api/v1/name/rolename/
468
469
470
471 // ----------------------------------------------------------------------------------------------------------------------------
472 // Datatracker API endpoints returning information about working groups:
473 // https://datatracker.ietf.org/api/v1/group/group/ - list of groups
474 // https://datatracker.ietf.org/api/v1/group/group/2161/ - info about group 2161
475 // https://datatracker.ietf.org/api/v1/group/grouphistory/?group=2161 - history
476 // https://datatracker.ietf.org/api/v1/group/groupurl/?group=2161 - URLs
477 // https://datatracker.ietf.org/api/v1/group/groupevent/?group=2161 - events
478 // https://datatracker.ietf.org/api/v1/group/groupmilestone/?group=2161 - Current milestones
479 // https://datatracker.ietf.org/api/v1/group/groupmilestonehistory/?group=2161 - Previous milestones
480 // https://datatracker.ietf.org/api/v1/group/milestonegroupevent/?group=2161 - changed milestones
481 // https://datatracker.ietf.org/api/v1/group/role/?group=2161 - The current WG chairs and ADs of a group
482 // https://datatracker.ietf.org/api/v1/group/role/?person=20209 - Groups a person is currently involved with
483 // https://datatracker.ietf.org/api/v1/group/role/?email=csp@csperkins.org - Groups a person is currently involved with
484 // https://datatracker.ietf.org/api/v1/group/rolehistory/?group=2161 - The previous WG chairs and ADs of a group
485 // https://datatracker.ietf.org/api/v1/group/rolehistory/?person=20209 - Groups person was previously involved with
486 // https://datatracker.ietf.org/api/v1/group/rolehistory/?email=csp@csperkins.org - Groups person was previously involved with
487 // https://datatracker.ietf.org/api/v1/group/changestategroupevent/?group=2161 - Group state changes
488 // https://datatracker.ietf.org/api/v1/group/groupstatetransitions - ???
489
490
491
492 // ----------------------------------------------------------------------------------------------------------------------------
493 // Datatracker API endpoints returning information about meetings:
494 // https://datatracker.ietf.org/api/v1/meeting/meeting/ - list of meetings
495 // https://datatracker.ietf.org/api/v1/meeting/meeting/747/ - information about meeting number 747
496 // https://datatracker.ietf.org/api/v1/meeting/session/ - list of all sessions in meetings
497 // https://datatracker.ietf.org/api/v1/meeting/session/25886/ - a session in a meeting
498 // https://datatracker.ietf.org/api/v1/meeting/session/?meeting=747 - sessions in meeting number 747
499 // https://datatracker.ietf.org/api/v1/meeting/session/?meeting=747&group=2161 - sessions in meeting number 747 for group 2161
500 // https://datatracker.ietf.org/api/v1/meeting/schedtimesessassignment/59003/ - a schededuled session within a meeting
501 // https://datatracker.ietf.org/api/v1/meeting/timeslot/9480/ - a time slot within a meeting (time, duration, location)
502 // https://datatracker.ietf.org/api/v1/meeting/schedule/791/ - a draft of the meeting agenda
503 // https://datatracker.ietf.org/api/v1/meeting/room/537/ - a room at a meeting
504 // https://datatracker.ietf.org/api/v1/meeting/floorplan/14/ - floor plan for a meeting venue
505 // https://datatracker.ietf.org/api/v1/name/meetingtypename/
506
507
508
509}
510// =================================================================================================================================
511// Test suite:
512
513#[cfg(test)]
514mod ietfdata_tests {
515 use super::*;
516
517 #[test]
518 fn test_email() -> Result<(), DatatrackerError> {
519 let dt = Datatracker::new();
520 let e = dt.email("csp@csperkins.org")?;
521
522 assert_eq!(e.resource_uri, EmailUri("/api/v1/person/email/csp@csperkins.org/".to_string()));
523 assert_eq!(e.address, "csp@csperkins.org");
524 assert_eq!(e.person, PersonUri("/api/v1/person/person/20209/".to_string()));
525 assert_eq!(e.time, Utc.ymd(1970, 1, 1).and_hms(23, 59, 59));
526 assert_eq!(e.origin, "author: draft-ietf-mmusic-rfc4566bis");
527 assert_eq!(e.primary, true);
528 assert_eq!(e.active, true);
529
530 // Lookup a non-existing address; this should fail
531 assert!(dt.email("nobody@example.com").is_err());
532
533 Ok(())
534 }
535
536 #[test]
537 fn test_email_history_for_address() -> Result<(), DatatrackerError> {
538 let dt = Datatracker::new();
539 let h = dt.email_history_for_address("csp@isi.edu")?.collect::<Result<Vec<_>, _>>()?;
540
541 assert_eq!(h.len(), 1);
542 assert_eq!(h[0].address, "csp@isi.edu");
543 assert_eq!(h[0].person, PersonUri("/api/v1/person/person/20209/".to_string()));
544
545 Ok(())
546 }
547
548/*
549 #[test]
550 fn test_email_history_for_person() -> Result<(), DatatrackerError> {
551 let dt = Datatracker::new();
552 let p = dt.person_from_email("csp@csperkins.org")?;
553 for h in dt.email_history_for_person(&p) {
554 println!("{:?}", h);
555 }
556 Ok(())
557 }
558*/
559
560 #[test]
561 fn test_person() -> Result<(), DatatrackerError> {
562 let dt = Datatracker::new();
563 let p = dt.person(&PersonUri("/api/v1/person/person/20209/".to_string()))?;
564
565 assert_eq!(p.id, 20209);
566 assert_eq!(p.resource_uri, PersonUri("/api/v1/person/person/20209/".to_string()));
567 assert_eq!(p.name, "Colin Perkins");
568 assert_eq!(p.name_from_draft, Some("Colin Perkins".to_string()));
569 assert_eq!(p.biography, "Colin Perkins is a Senior Lecturer (Associate Professor) in the School of Computing Science at the University of Glasgow. His research interests are on transport protocols for real-time and interactive multimedia, and on network protocol design, implementation, and specification. He’s been a participant in the IETF and IRTF since 1996, working primarily in the transport area where he co-chairs the RMCAT working group and is a past chair of the AVT and MMUSIC working groups, and in related IRTF research groups. He proposed and co-chaired the first Applied Networking Research Workshop (ANRW), and has been a long-term participant in the Applied Networking Research Prize (ANRP) awarding committee. He received his BEng in Electronic Engineering in 1992, and my PhD in 1996, both from the Department of Electronics at the University of York.");
570 assert_eq!(p.ascii, "Colin Perkins");
571 assert_eq!(p.ascii_short, None);
572 assert_eq!(p.time, Utc.ymd(2012,2,26).and_hms(0,3,54));
573 assert_eq!(p.photo, Some("https://www.ietf.org/lib/dt/media/photo/Colin-Perkins-sm.jpg".to_string()));
574 assert_eq!(p.photo_thumb, Some("https://www.ietf.org/lib/dt/media/photo/Colin-Perkins-sm_PMIAhXi.jpg".to_string()));
575 assert_eq!(p.user, Some("".to_string()));
576 assert_eq!(p.consent, Some(true));
577 Ok(())
578 }
579
580 #[test]
581 fn test_person_from_email() -> Result<(), DatatrackerError> {
582 let dt = Datatracker::new();
583 let p = dt.person_from_email("csp@csperkins.org")?;
584
585 assert_eq!(p.id, 20209);
586 assert_eq!(p.name, "Colin Perkins");
587
588 Ok(())
589 }
590
591/*
592 #[test]
593 fn test_people() {
594 let dt = Datatracker::new();
595 let people = dt.people();
596 for person in people.into_iter() {
597 println!("{:?}", person);
598 }
599 }
600*/
601
602 #[test]
603 fn test_people_with_name() -> Result<(), DatatrackerError> {
604 let dt = Datatracker::new();
605 let people = dt.people_with_name("Colin Perkins")?.collect::<Result<Vec<_>, _>>()?;
606
607 assert_eq!(people[0].id, 20209);
608 assert_eq!(people[0].name, "Colin Perkins");
609
610 Ok(())
611 }
612
613 #[test]
614 fn test_people_with_name_containing() -> Result<(), DatatrackerError> {
615 let dt = Datatracker::new();
616 let people = dt.people_with_name_containing("Perkins")?.collect::<Result<Vec<_>, _>>()?;
617
618 // As of 2019-08-17, there are six people named Perkins in the datatracker.
619 assert_eq!(people.len(), 6);
620
621 Ok(())
622 }
623
624 #[test]
625 fn test_people_between() -> Result<(), DatatrackerError> {
626 let start = Utc.ymd(2019, 7, 1).and_hms( 0, 0, 0);
627 let until = Utc.ymd(2019, 7, 7).and_hms(23, 59, 59);
628
629 let dt = Datatracker::new();
630 let people = dt.people_between(start, until)?.collect::<Result<Vec<_>, _>>()?;
631
632 // There are 26 people in the tracker with dates in the first week of July 2019
633 assert_eq!(people.len(), 26);
634
635 Ok(())
636 }
637
638 #[test]
639 fn test_person_history() -> Result<(), DatatrackerError> {
640 let dt = Datatracker::new();
641 let p = dt.person_from_email("csp@csperkins.org")?;
642 let h = dt.person_history(&p)?.collect::<Result<Vec<_>, _>>()?;
643
644 // As of 2019-08-18, there are two history items for csp@csperkins.org
645 assert_eq!(h.len(), 2);
646
647 Ok(())
648 }
649
650 #[test]
651 fn test_person_aliases() -> Result<(), DatatrackerError> {
652 let dt = Datatracker::new();
653 let p = dt.person_from_email("csp@csperkins.org")?;
654 let h = dt.person_aliases(&p)?.collect::<Result<Vec<_>, _>>()?;
655
656 // As of 2019-08-18, there are two aliases for csp@csperkins.org
657 assert_eq!(h.len(), 2);
658 assert_eq!(h[0].name, "Dr. Colin Perkins");
659 assert_eq!(h[1].name, "Colin Perkins");
660
661 Ok(())
662 }
663}
664
665// =================================================================================================================================