user.rs

  1// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
  2// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  3//
  4// This Source Code Form is subject to the terms of the Mozilla Public
  5// License, v. 2.0. If a copy of the MPL was not distributed with this
  6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  7
  8use try_from::{TryFrom, TryInto};
  9use std::str::FromStr;
 10
 11use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
 12
 13use jid::Jid;
 14
 15use error::Error;
 16
 17use ns;
 18
 19#[derive(Debug, Clone, PartialEq)]
 20pub enum Status {
 21    /// Status: 100
 22    NonAnonymousRoom,
 23
 24    /// Status: 101
 25    AffiliationChange,
 26
 27    /// Status: 102
 28    ConfigShowsUnavailableMembers,
 29
 30    /// Status: 103
 31    ConfigHidesUnavailableMembers,
 32
 33    /// Status: 104
 34    ConfigNonPrivacyRelated,
 35
 36    /// Status: 110
 37    SelfPresence,
 38
 39    /// Status: 170
 40    ConfigRoomLoggingEnabled,
 41
 42    /// Status: 171
 43    ConfigRoomLoggingDisabled,
 44
 45    /// Status: 172
 46    ConfigRoomNonAnonymous,
 47
 48    /// Status: 173
 49    ConfigRoomSemiAnonymous,
 50
 51    /// Status: 201
 52    RoomHasBeenCreated,
 53
 54    /// Status: 210
 55    AssignedNick,
 56
 57    /// Status: 301
 58    Banned,
 59
 60    /// Status: 303
 61    NewNick,
 62
 63    /// Status: 307
 64    Kicked,
 65
 66    /// Status: 321
 67    RemovalFromRoom,
 68
 69    /// Status: 322
 70    ConfigMembersOnly,
 71
 72    /// Status: 332
 73    ServiceShutdown,
 74}
 75
 76impl TryFrom<Element> for Status {
 77    type Err = Error;
 78
 79    fn try_from(elem: Element) -> Result<Status, Error> {
 80        if !elem.is("status", ns::MUC_USER) {
 81            return Err(Error::ParseError("This is not a status element."));
 82        }
 83        for _ in elem.children() {
 84            return Err(Error::ParseError("Unknown child in status element."));
 85        }
 86        for (attr, _) in elem.attrs() {
 87            if attr != "code" {
 88                return Err(Error::ParseError("Unknown attribute in status element."));
 89            }
 90        }
 91        let code = get_attr!(elem, "code", required);
 92
 93        Ok(match code {
 94             100 => Status::NonAnonymousRoom,
 95             101 => Status::AffiliationChange,
 96             102 => Status::ConfigShowsUnavailableMembers,
 97             103 => Status::ConfigHidesUnavailableMembers,
 98             104 => Status::ConfigNonPrivacyRelated,
 99             110 => Status::SelfPresence,
100             170 => Status::ConfigRoomLoggingEnabled,
101             171 => Status::ConfigRoomLoggingDisabled,
102             172 => Status::ConfigRoomNonAnonymous,
103             173 => Status::ConfigRoomSemiAnonymous,
104             201 => Status::RoomHasBeenCreated,
105             210 => Status::AssignedNick,
106             301 => Status::Banned,
107             303 => Status::NewNick,
108             307 => Status::Kicked,
109             321 => Status::RemovalFromRoom,
110             322 => Status::ConfigMembersOnly,
111             332 => Status::ServiceShutdown,
112             _ => return Err(Error::ParseError("Invalid status code.")),
113        })
114    }
115}
116
117impl From<Status> for Element {
118    fn from(status: Status) -> Element {
119        Element::builder("status")
120                .ns(ns::MUC_USER)
121                .attr("code", match status {
122                     Status::NonAnonymousRoom => 100,
123                     Status::AffiliationChange => 101,
124                     Status::ConfigShowsUnavailableMembers => 102,
125                     Status::ConfigHidesUnavailableMembers => 103,
126                     Status::ConfigNonPrivacyRelated => 104,
127                     Status::SelfPresence => 110,
128                     Status::ConfigRoomLoggingEnabled => 170,
129                     Status::ConfigRoomLoggingDisabled => 171,
130                     Status::ConfigRoomNonAnonymous => 172,
131                     Status::ConfigRoomSemiAnonymous => 173,
132                     Status::RoomHasBeenCreated => 201,
133                     Status::AssignedNick => 210,
134                     Status::Banned => 301,
135                     Status::NewNick => 303,
136                     Status::Kicked => 307,
137                     Status::RemovalFromRoom => 321,
138                     Status::ConfigMembersOnly => 322,
139                     Status::ServiceShutdown => 332,
140                })
141                .build()
142    }
143}
144
145impl IntoElements for Status {
146    fn into_elements(self, emitter: &mut ElementEmitter) {
147        emitter.append_child(self.into());
148    }
149}
150
151/// Optional <actor/> element used in <item/> elements inside presence stanzas of type
152/// "unavailable" that are sent to users who are kick or banned, as well as within IQs for tracking
153/// purposes. -- CHANGELOG  0.17 (2002-10-23)
154/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real
155/// JID or to a roomnick. -- CHANGELOG  1.25 (2012-02-08)
156#[derive(Debug, Clone, PartialEq)]
157pub enum Actor {
158    Jid(Jid),
159    Nick(String),
160}
161
162impl TryFrom<Element> for Actor {
163    type Err = Error;
164
165    fn try_from(elem: Element) -> Result<Actor, Error> {
166        if !elem.is("actor", ns::MUC_USER) {
167            return Err(Error::ParseError("This is not a actor element."));
168        }
169        for _ in elem.children() {
170            return Err(Error::ParseError("Unknown child in actor element."));
171        }
172        for (attr, _) in elem.attrs() {
173            if attr != "jid" && attr != "nick" {
174                return Err(Error::ParseError("Unknown attribute in actor element."));
175            }
176        }
177        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
178        let nick = get_attr!(elem, "nick", optional);
179
180        match (jid, nick) {
181            (Some(_), Some(_))
182          | (None, None) =>
183                return Err(Error::ParseError("Either 'jid' or 'nick' attribute is required.")),
184            (Some(jid), _) => Ok(Actor::Jid(jid)),
185            (_, Some(nick)) => Ok(Actor::Nick(nick)),
186        }
187    }
188}
189
190impl From<Actor> for Element {
191    fn from(actor: Actor) -> Element {
192        let elem = Element::builder("actor").ns(ns::MUC_USER);
193
194        (match actor {
195            Actor::Jid(jid) => elem.attr("jid", String::from(jid)),
196            Actor::Nick(nick) => elem.attr("nick", nick),
197        }).build()
198    }
199}
200
201impl IntoElements for Actor {
202    fn into_elements(self, emitter: &mut ElementEmitter) {
203        emitter.append_child(self.into());
204    }
205}
206
207#[derive(Debug, Clone, PartialEq)]
208pub struct Continue {
209    thread: Option<String>,
210}
211
212impl TryFrom<Element> for Continue {
213    type Err = Error;
214
215    fn try_from(elem: Element) -> Result<Continue, Error> {
216        if !elem.is("continue", ns::MUC_USER) {
217            return Err(Error::ParseError("This is not a continue element."));
218        }
219        for _ in elem.children() {
220            return Err(Error::ParseError("Unknown child in continue element."));
221        }
222        for (attr, _) in elem.attrs() {
223            if attr != "thread" {
224                return Err(Error::ParseError("Unknown attribute in continue element."));
225            }
226        }
227        Ok(Continue { thread: get_attr!(elem, "thread", optional) })
228    }
229}
230
231impl From<Continue> for Element {
232    fn from(cont: Continue) -> Element {
233        Element::builder("continue")
234                .ns(ns::MUC_USER)
235                .attr("thread", cont.thread)
236                .build()
237    }
238}
239
240impl IntoElements for Continue {
241    fn into_elements(self, emitter: &mut ElementEmitter) {
242        emitter.append_child(self.into());
243    }
244}
245
246#[derive(Debug, Clone, PartialEq)]
247pub struct Reason(String);
248
249impl TryFrom<Element> for Reason {
250    type Err = Error;
251
252    fn try_from(elem: Element) -> Result<Reason, Error> {
253        if !elem.is("reason", ns::MUC_USER) {
254            return Err(Error::ParseError("This is not a reason element."));
255        }
256        for _ in elem.children() {
257            return Err(Error::ParseError("Unknown child in reason element."));
258        }
259        for _ in elem.attrs() {
260            return Err(Error::ParseError("Unknown attribute in reason element."));
261        }
262        Ok(Reason(elem.text()))
263    }
264}
265
266impl From<Reason> for Element {
267    fn from(reason: Reason) -> Element {
268        Element::builder("reason")
269                .ns(ns::MUC_USER)
270                .append(reason.0)
271                .build()
272    }
273}
274
275impl IntoElements for Reason {
276    fn into_elements(self, emitter: &mut ElementEmitter) {
277        emitter.append_child(self.into());
278    }
279}
280
281generate_attribute!(Affiliation, "affiliation", {
282    Owner => "owner",
283    Admin => "admin",
284    Member => "member",
285    Outcast => "outcast",
286    None => "none",
287}, Default = None);
288
289generate_attribute!(Role, "role", {
290    Moderator => "moderator",
291    Participant => "participant",
292    Visitor => "visitor",
293    None => "none",
294}, Default = None);
295
296#[derive(Debug, Clone)]
297pub struct Item {
298    pub affiliation: Affiliation,
299    pub jid: Option<Jid>,
300    pub nick: Option<String>,
301    pub role: Role,
302    pub actor: Option<Actor>,
303    pub continue_: Option<Continue>,
304    pub reason: Option<Reason>,
305}
306
307impl TryFrom<Element> for Item {
308    type Err = Error;
309
310    fn try_from(elem: Element) -> Result<Item, Error> {
311        if !elem.is("item", ns::MUC_USER) {
312            return Err(Error::ParseError("This is not a item element."));
313        }
314        let mut actor: Option<Actor> = None;
315        let mut continue_: Option<Continue> = None;
316        let mut reason: Option<Reason> = None;
317        for child in elem.children() {
318            if child.is("actor", ns::MUC_USER) {
319                actor = Some(child.clone().try_into()?);
320            } else if child.is("continue", ns::MUC_USER) {
321                continue_ = Some(child.clone().try_into()?);
322            } else if child.is("reason", ns::MUC_USER) {
323                reason = Some(child.clone().try_into()?);
324            } else {
325                return Err(Error::ParseError("Unknown child in item element."));
326            }
327        }
328        for (attr, _) in elem.attrs() {
329            if attr != "affiliation" && attr != "jid" &&
330               attr != "nick" && attr != "role" {
331                return Err(Error::ParseError("Unknown attribute in item element."));
332            }
333        }
334
335        let affiliation: Affiliation = get_attr!(elem, "affiliation", required);
336        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
337        let nick: Option<String> = get_attr!(elem, "nick", optional);
338        let role: Role = get_attr!(elem, "role", required);
339
340        Ok(Item{
341            affiliation: affiliation,
342            jid: jid,
343            nick: nick,
344            role: role,
345            actor: actor,
346            continue_: continue_,
347            reason: reason,
348        })
349    }
350}
351
352impl From<Item> for Element {
353    fn from(item: Item) -> Element {
354        Element::builder("item")
355                .ns(ns::MUC_USER)
356                .attr("affiliation", item.affiliation)
357                .attr("jid", match item.jid {
358                    Some(jid) => Some(String::from(jid)),
359                    None => None,
360                })
361                .attr("nick", item.nick)
362                .attr("role", item.role)
363                .append(item.actor)
364                .append(item.continue_)
365                .append(item.reason)
366                .build()
367    }
368}
369
370#[derive(Debug, Clone)]
371pub struct MucUser {
372    pub status: Vec<Status>,
373    pub items: Vec<Item>,
374}
375
376impl TryFrom<Element> for MucUser {
377    type Err = Error;
378
379    fn try_from(elem: Element) -> Result<MucUser, Error> {
380        if !elem.is("x", ns::MUC_USER) {
381            return Err(Error::ParseError("This is not an x element."));
382        }
383        let mut status = vec!();
384        let mut items = vec!();
385        for child in elem.children() {
386            if child.is("status", ns::MUC_USER) {
387                status.push(Status::try_from(child.clone())?);
388            } else if child.is("item", ns::MUC_USER) {
389                items.push(Item::try_from(child.clone())?);
390            } else {
391                return Err(Error::ParseError("Unknown child in x element."));
392            }
393        }
394        for _ in elem.attrs() {
395            return Err(Error::ParseError("Unknown attribute in x element."));
396        }
397        Ok(MucUser {
398            status,
399            items,
400        })
401    }
402}
403
404impl From<MucUser> for Element {
405    fn from(muc_user: MucUser) -> Element {
406        Element::builder("x")
407                .ns(ns::MUC_USER)
408                .append(muc_user.status)
409                .build()
410    }
411}
412
413#[cfg(test)]
414mod tests {
415    use super::*;
416    use std::error::Error as StdError;
417
418    #[test]
419    fn test_simple() {
420        let elem: Element = "
421            <x xmlns='http://jabber.org/protocol/muc#user'/>
422        ".parse().unwrap();
423        MucUser::try_from(elem).unwrap();
424    }
425
426    #[test]
427    fn test_invalid_child() {
428        let elem: Element = "
429            <x xmlns='http://jabber.org/protocol/muc#user'>
430                <coucou/>
431            </x>
432        ".parse().unwrap();
433        let error = MucUser::try_from(elem).unwrap_err();
434        let message = match error {
435            Error::ParseError(string) => string,
436            _ => panic!(),
437        };
438        assert_eq!(message, "Unknown child in x element.");
439    }
440
441    #[test]
442    fn test_serialise() {
443        let elem: Element = "
444            <x xmlns='http://jabber.org/protocol/muc#user'/>
445        ".parse().unwrap();
446        let muc = MucUser { status: vec!(), items: vec!() };
447        let elem2 = muc.into();
448        assert_eq!(elem, elem2);
449    }
450
451    #[test]
452    fn test_invalid_attribute() {
453        let elem: Element = "
454            <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
455        ".parse().unwrap();
456        let error = MucUser::try_from(elem).unwrap_err();
457        let message = match error {
458            Error::ParseError(string) => string,
459            _ => panic!(),
460        };
461        assert_eq!(message, "Unknown attribute in x element.");
462    }
463
464    #[test]
465    fn test_status_simple() {
466        let elem: Element = "
467            <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
468        ".parse().unwrap();
469        Status::try_from(elem).unwrap();
470    }
471
472    #[test]
473    fn test_status_invalid() {
474        let elem: Element = "
475            <status xmlns='http://jabber.org/protocol/muc#user'/>
476        ".parse().unwrap();
477        let error = Status::try_from(elem).unwrap_err();
478        let message = match error {
479            Error::ParseError(string) => string,
480            _ => panic!(),
481        };
482        assert_eq!(message, "Required attribute 'code' missing.");
483    }
484
485    #[test]
486    fn test_status_invalid_child() {
487        let elem: Element = "
488            <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
489                <foo/>
490            </status>
491        ".parse().unwrap();
492        let error = Status::try_from(elem).unwrap_err();
493        let message = match error {
494            Error::ParseError(string) => string,
495            _ => panic!(),
496        };
497        assert_eq!(message, "Unknown child in status element.");
498    }
499
500    #[test]
501    fn test_status_simple_code() {
502        let elem: Element = "
503            <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
504        ".parse().unwrap();
505        let status = Status::try_from(elem).unwrap();
506        assert_eq!(status, Status::Kicked);
507    }
508
509    #[test]
510    fn test_status_invalid_code() {
511        let elem: Element = "
512            <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
513        ".parse().unwrap();
514        let error = Status::try_from(elem).unwrap_err();
515        let message = match error {
516            Error::ParseError(string) => string,
517            _ => panic!(),
518        };
519        assert_eq!(message, "Invalid status code.");
520    }
521
522    #[test]
523    fn test_status_invalid_code2() {
524        let elem: Element = "
525            <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
526        ".parse().unwrap();
527        let error = Status::try_from(elem).unwrap_err();
528        let error = match error {
529            Error::ParseIntError(error) => error,
530            _ => panic!(),
531        };
532        assert_eq!(error.description(), "invalid digit found in string");
533    }
534
535    #[test]
536    fn test_actor_required_attributes() {
537        let elem: Element = "
538            <actor xmlns='http://jabber.org/protocol/muc#user'/>
539        ".parse().unwrap();
540        let error = Actor::try_from(elem).unwrap_err();
541        let message = match error {
542            Error::ParseError(string) => string,
543            _ => panic!(),
544        };
545        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
546    }
547
548    #[test]
549    fn test_actor_required_attributes2() {
550        let elem: Element = "
551            <actor xmlns='http://jabber.org/protocol/muc#user'
552                   jid='foo@bar/baz'
553                   nick='baz'/>
554        ".parse().unwrap();
555        let error = Actor::try_from(elem).unwrap_err();
556        let message = match error {
557            Error::ParseError(string) => string,
558            _ => panic!(),
559        };
560        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
561    }
562
563    #[test]
564    fn test_actor_jid() {
565        let elem: Element = "
566            <actor xmlns='http://jabber.org/protocol/muc#user'
567                   jid='foo@bar/baz'/>
568        ".parse().unwrap();
569        let actor = Actor::try_from(elem).unwrap();
570        let jid = match actor {
571            Actor::Jid(jid) => jid,
572            _ => panic!(),
573        };
574        assert_eq!(jid, "foo@bar/baz".parse::<Jid>().unwrap());
575    }
576
577    #[test]
578    fn test_actor_nick() {
579        let elem: Element = "
580            <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
581        ".parse().unwrap();
582        let actor = Actor::try_from(elem).unwrap();
583        let nick = match actor {
584            Actor::Nick(nick) => nick,
585            _ => panic!(),
586        };
587        assert_eq!(nick, "baz".to_owned());
588    }
589
590    #[test]
591    fn test_continue_simple() {
592        let elem: Element = "
593            <continue xmlns='http://jabber.org/protocol/muc#user'/>
594        ".parse().unwrap();
595        Continue::try_from(elem).unwrap();
596    }
597
598    #[test]
599    fn test_continue_thread_attribute() {
600        let elem: Element = "
601            <continue xmlns='http://jabber.org/protocol/muc#user'
602                      thread='foo'/>
603        ".parse().unwrap();
604        let continue_ = Continue::try_from(elem).unwrap();
605        assert_eq!(continue_, Continue { thread: Some("foo".to_owned()) });
606    }
607
608    #[test]
609    fn test_continue_invalid() {
610        let elem: Element = "
611            <continue xmlns='http://jabber.org/protocol/muc#user'>
612                <foobar/>
613            </continue>
614        ".parse().unwrap();
615        let continue_ = Continue::try_from(elem).unwrap_err();
616        let message = match continue_ {
617            Error::ParseError(string) => string,
618            _ => panic!(),
619        };
620        assert_eq!(message, "Unknown child in continue element.".to_owned());
621    }
622
623    #[test]
624    fn test_reason_simple() {
625        let elem: Element = "
626            <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
627        .parse().unwrap();
628        let reason = Reason::try_from(elem).unwrap();
629        assert_eq!(reason.0, "Reason".to_owned());
630    }
631
632    #[test]
633    fn test_reason_invalid_attribute() {
634        let elem: Element = "
635            <reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
636        ".parse().unwrap();
637        let error = Reason::try_from(elem).unwrap_err();
638        let message = match error {
639            Error::ParseError(string) => string,
640            _ => panic!(),
641        };
642        assert_eq!(message, "Unknown attribute in reason element.".to_owned());
643    }
644
645    #[test]
646    fn test_reason_invalid() {
647        let elem: Element = "
648            <reason xmlns='http://jabber.org/protocol/muc#user'>
649                <foobar/>
650            </reason>
651        ".parse().unwrap();
652        let error = Reason::try_from(elem).unwrap_err();
653        let message = match error {
654            Error::ParseError(string) => string,
655            _ => panic!(),
656        };
657        assert_eq!(message, "Unknown child in reason element.".to_owned());
658    }
659
660    #[test]
661    fn test_item_invalid_attr(){
662        let elem: Element = "
663            <item xmlns='http://jabber.org/protocol/muc#user'
664                  foo='bar'/>
665        ".parse().unwrap();
666        let error = Item::try_from(elem).unwrap_err();
667        let message = match error {
668            Error::ParseError(string) => string,
669            _ => panic!(),
670        };
671        assert_eq!(message, "Unknown attribute in item element.".to_owned());
672    }
673
674    #[test]
675    fn test_item_affiliation_role_attr(){
676        let elem: Element = "
677            <item xmlns='http://jabber.org/protocol/muc#user'
678                  affiliation='member'
679                  role='moderator'/>
680        ".parse().unwrap();
681        Item::try_from(elem).unwrap();
682    }
683
684    #[test]
685    fn test_item_affiliation_role_invalid_attr(){
686        let elem: Element = "
687            <item xmlns='http://jabber.org/protocol/muc#user'
688                  affiliation='member'/>
689        ".parse().unwrap();
690        let error = Item::try_from(elem).unwrap_err();
691        let message = match error {
692            Error::ParseError(string) => string,
693            _ => panic!(),
694        };
695        assert_eq!(message, "Required attribute 'role' missing.".to_owned());
696    }
697
698    #[test]
699    fn test_item_nick_attr(){
700        let elem: Element = "
701            <item xmlns='http://jabber.org/protocol/muc#user'
702                  affiliation='member'
703                  role='moderator'
704                  nick='foobar'/>
705        ".parse().unwrap();
706        let item = Item::try_from(elem).unwrap();
707        match item {
708            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
709        }
710    }
711
712    #[test]
713    fn test_item_affiliation_role_invalid_attr2(){
714        let elem: Element = "
715            <item xmlns='http://jabber.org/protocol/muc#user'
716                  role='moderator'/>
717        ".parse().unwrap();
718        let error = Item::try_from(elem).unwrap_err();
719        let message = match error {
720            Error::ParseError(string) => string,
721            _ => panic!(),
722        };
723        assert_eq!(message, "Required attribute 'affiliation' missing.".to_owned());
724    }
725
726    #[test]
727    fn test_item_role_actor_child(){
728        let elem: Element = "
729            <item xmlns='http://jabber.org/protocol/muc#user'
730                  affiliation='member'
731                  role='moderator'>
732                <actor nick='foobar'/>
733            </item>
734        ".parse().unwrap();
735        let item = Item::try_from(elem).unwrap();
736        match item {
737            Item { actor, .. } =>
738                assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
739        }
740    }
741
742    #[test]
743    fn test_item_role_continue_child(){
744        let elem: Element = "
745            <item xmlns='http://jabber.org/protocol/muc#user'
746                  affiliation='member'
747                  role='moderator'>
748                <continue thread='foobar'/>
749            </item>
750        ".parse().unwrap();
751        let item = Item::try_from(elem).unwrap();
752        let continue_1 = Continue { thread: Some("foobar".to_owned()) };
753        match item {
754            Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2, continue_1),
755            _ => panic!(),
756        }
757    }
758
759    #[test]
760    fn test_item_role_reason_child(){
761        let elem: Element = "
762            <item xmlns='http://jabber.org/protocol/muc#user'
763                  affiliation='member'
764                  role='moderator'>
765                <reason>foobar</reason>
766            </item>
767        ".parse().unwrap();
768        let item = Item::try_from(elem).unwrap();
769        match item {
770            Item { reason, .. } =>
771                assert_eq!(reason, Some(Reason("foobar".to_owned()))),
772        }
773    }
774}