aboutsummaryrefslogblamecommitdiffstats
path: root/filamento/src/disco.rs
blob: 580f6479c6226f27628bdc131c6591e8aa409be4 (plain) (tree)
1
2
3
4
5
6
7
8

                                    



                           
                       
                 


                                  

 




                                         
                                       



















                                            
                                                          










                                            
                                   



         
                       


























                                                                              
                       

























                                         
                 

                               
                                                     
                           






























































                                         













                                               




















































































































































































































































































































































                                                                                                    
                           















                                                                                  
                           


















                                                                                    
                           


















                                                                                               
                           












                                                                                 
                           



















                                                                         
                           












                                                                                
                           












                                                              
                           














                                                                         
                           
















































                                                                               
                           




















































































































                                                                                                    
                           












                                                                       
                           











                                                                                               
                           
















                                                                                                
                           














                                                                 
                           
































                                                     
                           


















                                                     
                           














                                                                        
                           














                                                                      
                           


















                                                             
                           














                                                               
                           











                                                                     
                           











                                                                                
                           















                                                                         
                  

                               
                           
                         

                                 











                                                                                            





                                                     
                           



             


                                                                                                       
                           



































































































                                                                                    
                           





























                                                    
                           






































                                               
                           























                                                
                           



































                                                          
                           















































                                               
                           























                                                          
                           


















































                                                  
                           


























                                               
                           
































                                                         
                           





































































































                                                    
                           





























                                                
                           


























                                               
                           























                                                    
                           

































                                                       
                           























                                               
                           









































                                               
use jid::JID;
use stanza::xep_0030::{info, items};

pub use feature::Feature;
pub use identity::Identity;

#[derive(Debug, Clone)]
pub struct Info {
    pub node: Option<String>,
    pub features: Vec<String>,
    pub identities: Vec<Identity>,
}

impl From<info::Query> for Info {
    fn from(value: info::Query) -> Self {
        let features = value
            .features
            .into_iter()
            .map(|feature| feature.var)
            .collect();
        let identities = value
            .identities
            .into_iter()
            .map(|identity| identity.into())
            .collect();

        Self {
            node: value.node,
            features,
            identities,
        }
    }
}

impl From<Info> for info::Query {
    fn from(value: Info) -> Self {
        let features = value
            .features
            .into_iter()
            .map(|feature| info::Feature { var: feature })
            .collect();
        let identities = value
            .identities
            .into_iter()
            .map(|identity| identity.into())
            .collect();

        Self {
            node: value.node,
            features,
            identities,
            extensions: Vec::new(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Items {
    node: Option<String>,
    items: Vec<Item>,
}

impl From<items::Query> for Items {
    fn from(value: items::Query) -> Self {
        let items = value.items.into_iter().map(|item| item.into()).collect();

        Self {
            node: value.node,
            items,
        }
    }
}

impl From<Items> for items::Query {
    fn from(value: Items) -> Self {
        let items = value.items.into_iter().map(|item| item.into()).collect();

        Self {
            node: value.node,
            items,
        }
    }
}

#[derive(Debug, Clone)]
pub struct Item {
    jid: JID,
    name: Option<String>,
    node: Option<String>,
}

impl From<items::Item> for Item {
    fn from(value: items::Item) -> Self {
        Self {
            jid: value.jid,
            name: value.name,
            node: value.node,
        }
    }
}

impl From<Item> for items::Item {
    fn from(value: Item) -> Self {
        Self {
            jid: value.jid,
            name: value.name,
            node: value.node,
        }
    }
}

pub mod feature {
    use stanza::xep_0030::info;

    // https://xmpp.org/registrar/disco-features.html
    #[derive(Debug, Clone)]
    pub enum Feature {
        DNSSRV,
        FullUnicode,
        GC1,
        Activity,
        Address,
        Amp(Option<Amp>),
        Bytestreams(Option<Bytestreams>),
        Caps(CapsVersion),
        ChatStates,
        Commands,
        Compress,
        Disco(Disco),
        FeatureNeg,
        Geoloc,
        HttpAuth,
        HttpBind,
        IBB,
        Mood,
        MUC(Option<MUC>),
        Offline,
        PubSub(PubSub),
        RosterX,
        SIPub,
        SOAP(Option<SOAP>),
        WaitingList(Option<WaitingList>),
        XHTMLIM,
        XDataLayout,
        XDataValidate,
        IPv6,
        Client,
        Component(Component),
        Iq(Iq),
        Server,
        X(X),
        MsgLog,
        MsgOffline,
        RosterDelimiter,
        SSLC2S,
        StringPrep,
        XMPPBind,
        XMPPE2E,
        XMPPSASL(Option<XMPPSASL>),
        XMPPSession,
        XMPPStanzas,
        XMPPStreams,
        XMPPTLS(Option<XMPPTLS>),
        RFC3264,
        Archive(Archive),
        Avatar(Avatar),
        Delay,
        Jingle(Jingle),
        Ping,
        Receipts,
        SSN,
        Time,
        XMLLang,
        VCardTemp,
        Styling(i8),
        SID(i8),
        Unknown(String),
    }

    impl From<info::Feature> for Feature {
        fn from(value: info::Feature) -> Self {
            value.var.as_str().into()
        }
    }

    impl From<Feature> for info::Feature {
        fn from(value: Feature) -> Self {
            Self {
                var: value.to_string(),
            }
        }
    }

    impl From<&str> for Feature {
        fn from(value: &str) -> Self {
            match value {
                "dnssrv" => Self::DNSSRV,
                "fullunicode" => Self::FullUnicode,
                "gc-1.0" => Self::GC1,
                "http://jabber.org/protocol/activity" => Self::Activity,
                "http://jabber.org/protocol/address" => Self::Address,
                "http://jabber.org/protocol/amp" => Self::Amp(None),
                "http://jabber.org/protocol/amp#errors" => Self::Amp(Some(Amp::Errors)),
                "http://jabber.org/protocol/amp?action=alert" => {
                    Self::Amp(Some(Amp::Action(AmpAction::Alert)))
                }
                "http://jabber.org/protocol/amp?action=drop" => {
                    Self::Amp(Some(Amp::Action(AmpAction::Drop)))
                }
                "http://jabber.org/protocol/amp?action=error" => {
                    Self::Amp(Some(Amp::Action(AmpAction::Error)))
                }
                "http://jabber.org/protocol/amp?action=notify" => {
                    Self::Amp(Some(Amp::Action(AmpAction::Notify)))
                }
                "http://jabber.org/protocol/amp?condition=deliver" => {
                    Self::Amp(Some(Amp::Condition(AmpCondition::Deliver)))
                }
                "http://jabber.org/protocol/amp?condition=expire-at" => {
                    Self::Amp(Some(Amp::Condition(AmpCondition::ExpireAt)))
                }
                "http://jabber.org/protocol/amp?condition=match-resource" => {
                    Self::Amp(Some(Amp::Condition(AmpCondition::MatchResource)))
                }
                "http://jabber.org/protocol/bytestreams" => Self::Bytestreams(None),
                "http://jabber.org/protocol/bytestreams#udp" => {
                    Self::Bytestreams(Some(Bytestreams::UDP))
                }
                "http://jabber.org/protocol/caps" => Self::Caps(CapsVersion::One(None)),
                "http://jabber.org/protocol/caps#optimize" => {
                    Self::Caps(CapsVersion::One(Some(CapsOne::Optimize)))
                }
                "http://jabber.org/protocol/chatstates" => Self::ChatStates,
                "http://jabber.org/protocol/commands" => Self::Commands,
                "http://jabber.org/protocol/compress" => Self::Compress,
                "http://jabber.org/protocol/disco#info" => Self::Disco(Disco::Info),
                "http://jabber.org/protocol/disco#items" => Self::Disco(Disco::Items),
                "http://jabber.org/protocol/feature-neg" => Self::FeatureNeg,
                "http://jabber.org/protocol/geoloc" => Self::Geoloc,
                "http://jabber.org/protocol/http-auth" => Self::HttpAuth,
                "http://jabber.org/protocol/httpbind" => Self::HttpBind,
                "http://jabber.org/protocol/ibb" => Self::IBB,
                "http://jabber.org/protocol/mood" => Self::Mood,
                "http://jabber.org/protocol/muc" => Self::MUC(None),
                "http://jabber.org/protocol/muc#admin" => Self::MUC(Some(MUC::Admin)),
                "http://jabber.org/protocol/muc#owner" => Self::MUC(Some(MUC::Owner)),
                "http://jabber.org/protocol/muc#register" => Self::MUC(Some(MUC::Register)),
                "http://jabber.org/protocol/muc#roomconfig" => Self::MUC(Some(MUC::RoomConfig)),
                "http://jabber.org/protocol/muc#roominfo" => Self::MUC(Some(MUC::RoomInfo)),
                "http://jabber.org/protocol/muc#user" => Self::MUC(Some(MUC::User)),
                "http://jabber.org/protocol/offline" => Self::Offline,
                "http://jabber.org/protocol/pubsub#access-authorize" => {
                    Self::PubSub(PubSub::AccessAuthorize)
                }
                "http://jabber.org/protocol/pubsub#access-open" => Self::PubSub(PubSub::AccessOpen),
                "http://jabber.org/protocol/pubsub#access-presence" => {
                    Self::PubSub(PubSub::AccessPresence)
                }
                "http://jabber.org/protocol/pubsub#access-roster" => {
                    Self::PubSub(PubSub::AccessRoster)
                }
                "http://jabber.org/protocol/pubsub#access-whitelist" => {
                    Self::PubSub(PubSub::AccessWhitelist)
                }
                "http://jabber.org/protocol/pubsub#auto-create" => Self::PubSub(PubSub::AutoCreate),
                "http://jabber.org/protocol/pubsub#auto-subscribe" => {
                    Self::PubSub(PubSub::AutoSubscribe)
                }
                "http://jabber.org/protocol/pubsub#collections" => {
                    Self::PubSub(PubSub::Collections)
                }
                "http://jabber.org/protocol/pubsub#config-node" => Self::PubSub(PubSub::ConfigNode),
                "http://jabber.org/protocol/pubsub#create-and-configure" => {
                    Self::PubSub(PubSub::CreateAndConfigure)
                }
                "http://jabber.org/protocol/pubsub#create-nodes" => {
                    Self::PubSub(PubSub::CreateNodes)
                }
                "http://jabber.org/protocol/pubsub#delete-any" => Self::PubSub(PubSub::DeleteAny),
                "http://jabber.org/protocol/pubsub#delete-nodes" => {
                    Self::PubSub(PubSub::DeleteNodes)
                }
                "http://jabber.org/protocol/pubsub#filtered-notifications" => {
                    Self::PubSub(PubSub::FilteredNotifications)
                }
                "http://jabber.org/protocol/pubsub#get-pending" => Self::PubSub(PubSub::GetPending),
                "http://jabber.org/protocol/pubsub#instant-nodes" => {
                    Self::PubSub(PubSub::InstantNodes)
                }
                "http://jabber.org/protocol/pubsub#item-ids" => Self::PubSub(PubSub::ItemIDs),
                "http://jabber.org/protocol/pubsub#last-published" => {
                    Self::PubSub(PubSub::LastPublished)
                }
                "http://jabber.org/protocol/pubsub#leased-subscription" => {
                    Self::PubSub(PubSub::LeasedSubscription)
                }
                "http://jabber.org/protocol/pubsub#manage-subscription" => {
                    Self::PubSub(PubSub::ManageSubscription)
                }
                "http://jabber.org/protocol/pubsub#member-affiliation" => {
                    Self::PubSub(PubSub::MemberAffiliation)
                }
                "http://jabber.org/protocol/pubsub#meta-data" => Self::PubSub(PubSub::MetaData),
                "http://jabber.org/protocol/pubsub#modify-affiliations" => {
                    Self::PubSub(PubSub::ModifyAffiliations)
                }
                "http://jabber.org/protocol/pubsub#multi-collection" => {
                    Self::PubSub(PubSub::MultiCollection)
                }
                "http://jabber.org/protocol/pubsub#multi-subscribe" => {
                    Self::PubSub(PubSub::MultiSubscribe)
                }
                "http://jabber.org/protocol/pubsub#outcast-affiliation" => {
                    Self::PubSub(PubSub::OutcastAffiliation)
                }
                "http://jabber.org/protocol/pubsub#persistent-items" => {
                    Self::PubSub(PubSub::PersistentItems)
                }
                "http://jabber.org/protocol/pubsub#presence-notifications" => {
                    Self::PubSub(PubSub::PresenceNotifications)
                }
                "http://jabber.org/protocol/pubsub#presence-subscribe" => {
                    Self::PubSub(PubSub::PresenceSubscribe)
                }
                "http://jabber.org/protocol/pubsub#publish" => Self::PubSub(PubSub::Publish),
                "http://jabber.org/protocol/pubsub#publish-options" => {
                    Self::PubSub(PubSub::PublishOptions)
                }
                "http://jabber.org/protocol/pubsub#publisher-affiliation" => {
                    Self::PubSub(PubSub::PublisherAffiliation)
                }
                "http://jabber.org/protocol/pubsub#purge-nodes" => Self::PubSub(PubSub::PurgeNodes),
                "http://jabber.org/protocol/pubsub#retract-items" => {
                    Self::PubSub(PubSub::RetractItems)
                }
                "http://jabber.org/protocol/pubsub#retrieve-affiliations" => {
                    Self::PubSub(PubSub::RetrieveAffiliations)
                }
                "http://jabber.org/protocol/pubsub#retrieve-default" => {
                    Self::PubSub(PubSub::RetrieveDefault)
                }
                "http://jabber.org/protocol/pubsub#retrieve-items" => {
                    Self::PubSub(PubSub::RetrieveItems)
                }
                "http://jabber.org/protocol/pubsub#retrieve-subscriptions" => {
                    Self::PubSub(PubSub::RetrieveSubscriptions)
                }
                "http://jabber.org/protocol/pubsub#subscribe" => Self::PubSub(PubSub::Subscribe),
                "http://jabber.org/protocol/pubsub#subscription-options" => {
                    Self::PubSub(PubSub::SubscriptionOptions)
                }
                "http://jabber.org/protocol/pubsub#subscription-notifications" => {
                    Self::PubSub(PubSub::SubscriptionNotifications)
                }
                "http://jabber.org/protocol/rosterx" => Self::RosterX,
                "http://jabber.org/protocol/sipub" => Self::SIPub,
                "http://jabber.org/protocol/soap" => Self::SOAP(None),
                "http://jabber.org/protocol/soap#fault" => Self::SOAP(Some(SOAP::Fault)),
                "http://jabber.org/protocol/waitinglist" => Self::WaitingList(None),
                "http://jabber.org/protocol/waitinglist/schemes/mailto" => {
                    Self::WaitingList(Some(WaitingList::Schemes(WaitingListSchemes::Mailto)))
                }
                "http://jabber.org/protocol/waitinglist/schemes/tel" => {
                    Self::WaitingList(Some(WaitingList::Schemes(WaitingListSchemes::Tel)))
                }
                "http://jabber.org/protocol/xhtml-im" => Self::XHTMLIM,
                "http://jabber.org/protocol/xdata-layout" => Self::XDataLayout,
                "http://jabber.org/protocol/xdata-validate" => Self::XDataValidate,
                "ipv6" => Self::IPv6,
                "jabber:client" => Self::Client,
                "jabber:component:accept" => Self::Component(Component::Accept),
                "jabber:component:connect" => Self::Component(Component::Connect),
                "jabber:iq:auth" => Self::Iq(Iq::Auth),
                "jabber:iq:gateway" => Self::Iq(Iq::Gateway),
                "jabber:iq:last" => Self::Iq(Iq::Last),
                "jabber:iq:oob" => Self::Iq(Iq::OOB),
                "jabber:iq:privacy" => Self::Iq(Iq::Privacy),
                "jabber:iq:private" => Self::Iq(Iq::Private),
                "jabber:iq:register" => Self::Iq(Iq::Register),
                "jabber:iq:roster" => Self::Iq(Iq::Roster),
                "jabber:iq:rpc" => Self::Iq(Iq::RPC),
                "jabber:iq:search" => Self::Iq(Iq::Search),
                "jabber:iq:version" => Self::Iq(Iq::Version),
                "jabber:server" => Self::Server,
                "jabber:x:data" => Self::X(X::Data),
                "jabber:x:encrypted" => Self::X(X::Encrypted),
                "jabber:x:oob" => Self::X(X::OOB),
                "jabber:x:signed" => Self::X(X::Signed),
                "msglog" => Self::MsgLog,
                "msgoffline" => Self::MsgOffline,
                "muc_hidden" => Self::MUC(Some(MUC::Hidden)),
                "muc_membersonly" => Self::MUC(Some(MUC::MembersOnly)),
                "muc_moderated" => Self::MUC(Some(MUC::Moderated)),
                "muc_nonanonymous" => Self::MUC(Some(MUC::NonAnonymous)),
                "muc_open" => Self::MUC(Some(MUC::Open)),
                "muc_passwordprotected" => Self::MUC(Some(MUC::PasswordProtected)),
                "muc_persistent" => Self::MUC(Some(MUC::Persistent)),
                "muc_public" => Self::MUC(Some(MUC::Public)),
                "muc_rooms" => Self::MUC(Some(MUC::Rooms)),
                "muc_semianonymous" => Self::MUC(Some(MUC::SemiAnonymous)),
                "muc_temporary" => Self::MUC(Some(MUC::Temporary)),
                "muc_unmoderated" => Self::MUC(Some(MUC::Unmoderated)),
                "muc_unsecured" => Self::MUC(Some(MUC::Unsecured)),
                "roster:delimiter" => Self::RosterDelimiter,
                "sslc2s" => Self::SSLC2S,
                "stringprep" => Self::StringPrep,
                "urn:ietf:params:xml:ns:xmpp-bind" => Self::XMPPBind,
                "urn:ietf:params:xml:ns:xmpp-e2e" => Self::XMPPE2E,
                "urn:ietf:params:xml:ns:xmpp-sasl" => Self::XMPPSASL(None),
                "urn:ietf:params:xml:ns:xmpp-sasl#c2s" => Self::XMPPSASL(Some(XMPPSASL::C2S)),
                "urn:ietf:params:xml:ns:xmpp-sasl#s2s" => Self::XMPPSASL(Some(XMPPSASL::S2S)),
                "urn:ietf:params:xml:ns:xmpp-session" => Self::XMPPSession,
                "urn:ietf:params:xml:ns:xmpp-stanzas" => Self::XMPPStanzas,
                "urn:ietf:params:xml:ns:xmpp-streams" => Self::XMPPStreams,
                "urn:ietf:params:xml:ns:xmpp-tls" => Self::XMPPTLS(None),
                "urn:ietf:params:xml:ns:xmpp-tls#c2s" => Self::XMPPTLS(Some(XMPPTLS::C2S)),
                "urn:ietf:params:xml:ns:xmpp-tls#s2s" => Self::XMPPTLS(Some(XMPPTLS::S2S)),
                "urn:ietf:rfc:3264" => Self::RFC3264,
                "urn:xmpp:archive:auto" => Self::Archive(Archive::Auto),
                "urn:xmpp:archive:manage" => Self::Archive(Archive::Manage),
                "urn:xmpp:archive:manual" => Self::Archive(Archive::Manual),
                "urn:xmpp:archive:pref" => Self::Archive(Archive::Pref),
                "urn:xmpp:avatar:data" => Self::Avatar(Avatar::Data),
                "urn:xmpp:avatar:metadata" => Self::Avatar(Avatar::Metadata),
                "urn:xmpp:delay" => Self::Delay,
                "urn:xmpp:jingle:apps:rtp:audio" => {
                    Self::Jingle(Jingle::Apps(JingleApps::RTP(JingleAppsRTP::Audio)))
                }
                "urn:xmpp:jingle:apps:rtp:video" => {
                    Self::Jingle(Jingle::Apps(JingleApps::RTP(JingleAppsRTP::Video)))
                }
                "urn:xmpp:ping" => Self::Ping,
                "urn:xmpp:receipts" => Self::Receipts,
                "urn:xmpp:ssn" => Self::SSN,
                "urn:xmpp:time" => Self::Time,
                "xmllang" => Self::XMLLang,
                "vcard-temp" => Self::VCardTemp,
                "urn:xmpp:styling:0" => Self::Styling(0),
                "urn:xmpp:sid:0" => Self::SID(0),
                "urn:xmpp:caps" => Self::Caps(CapsVersion::Two(None)),
                "urn:xmpp:caps:optimize" => Self::Caps(CapsVersion::Two(Some(CapsTwo::Optimize))),
                s => Self::Unknown(s.to_owned()),
            }
        }
    }

    impl ToString for Feature {
        fn to_string(&self) -> String {
            match self {
                Feature::DNSSRV => "dnssrv".to_owned(),
                Feature::FullUnicode => "fullunicode".to_owned(),
                Feature::GC1 => "gc-1.0".to_owned(),
                Feature::Activity => "http://jabber.org/protocol/activity".to_owned(),
                Feature::Address => "http://jabber.org/protocol/address".to_owned(),
                Feature::Amp(amp) => match amp {
                    Some(a) => a.to_string(),
                    None => "http://jabber.org/protocol/amp".to_owned(),
                },
                Feature::Bytestreams(byte_streams) => match byte_streams {
                    Some(b) => b.to_string(),
                    None => "http://jabber.org/protocol/bytestreams".to_owned(),
                },
                Feature::Caps(caps_version) => caps_version.to_string(),
                Feature::ChatStates => "http://jabber.org/protocol/chatstates".to_owned(),
                Feature::Commands => "http://jabber.org/protocol/commands".to_owned(),
                Feature::Compress => "http://jabber.org/protocol/compress".to_owned(),
                Feature::Disco(disco) => disco.to_string(),
                Feature::FeatureNeg => "http://jabber.org/protocol/feature-neg".to_owned(),
                Feature::Geoloc => "http://jabber.org/protocol/geoloc".to_owned(),
                Feature::HttpAuth => "http://jabber.org/protocol/http-auth".to_owned(),
                Feature::HttpBind => "http://jabber.org/protocol/httpbind".to_owned(),
                Feature::IBB => "http://jabber.org/protocol/ibb".to_owned(),
                Feature::Mood => "http://jabber.org/protocol/mood".to_owned(),
                Feature::MUC(muc) => match muc {
                    Some(m) => m.to_string(),
                    None => "http://jabber.org/protocol/muc".to_owned(),
                },
                Feature::Offline => "http://jabber.org/protocol/offline".to_owned(),
                Feature::PubSub(pub_sub) => pub_sub.to_string(),
                Feature::RosterX => "http://jabber.org/protocol/rosterx".to_owned(),
                Feature::SIPub => "http://jabber.org/protocol/sipub".to_owned(),
                Feature::SOAP(soap) => match soap {
                    Some(s) => s.to_string(),
                    None => "http://jabber.org/protocol/soap".to_owned(),
                },
                Feature::WaitingList(waiting_list) => match waiting_list {
                    Some(w) => w.to_string(),
                    None => "http://jabber.org/protocol/waitinglist".to_owned(),
                },
                Feature::XHTMLIM => "http://jabber.org/protocol/xhtml-im".to_owned(),
                Feature::XDataLayout => "http://jabber.org/protocol/xdata-layout".to_owned(),
                Feature::XDataValidate => "http://jabber.org/protocol/xdata-validate".to_owned(),
                Feature::IPv6 => "ipv6".to_owned(),
                Feature::Client => "jabber:client".to_owned(),
                Feature::Component(component) => component.to_string(),
                Feature::Iq(iq) => iq.to_string(),
                Feature::Server => "jabber:server".to_owned(),
                Feature::X(x) => x.to_string(),
                Feature::MsgLog => "msglog".to_owned(),
                Feature::MsgOffline => "msgoffline".to_owned(),
                Feature::RosterDelimiter => "roster:delimiter".to_owned(),
                Feature::SSLC2S => "sslc2s".to_owned(),
                Feature::StringPrep => "stringprep".to_owned(),
                Feature::XMPPBind => "urn:ietf:params:xml:ns:xmpp-bind".to_owned(),
                Feature::XMPPE2E => "urn:ietf:params:xml:ns:xmpp-e2e".to_owned(),
                Feature::XMPPSASL(xmppsasl) => match xmppsasl {
                    Some(x) => x.to_string(),
                    None => "urn:ietf:params:xml:ns:xmpp-sasl".to_owned(),
                },
                Feature::XMPPSession => "urn:ietf:params:xml:ns:xmpp-session".to_owned(),
                Feature::XMPPStanzas => "urn:ietf:params:xml:ns:xmpp-stanzas".to_owned(),
                Feature::XMPPStreams => "urn:ietf:params:xml:ns:xmpp-streams".to_owned(),
                Feature::XMPPTLS(xmpptls) => match xmpptls {
                    Some(x) => x.to_string(),
                    None => "urn:ietf:params:xml:ns:xmpp-tls".to_owned(),
                },
                Feature::RFC3264 => "urn:ietf:rfc:3264".to_owned(),
                Feature::Archive(archive) => archive.to_string(),
                Feature::Avatar(avatar) => avatar.to_string(),
                Feature::Delay => "urn:xmpp:delay".to_owned(),
                Feature::Jingle(jingle) => jingle.to_string(),
                Feature::Ping => "urn:xmpp:ping".to_owned(),
                Feature::Receipts => "urn:xmpp:receipts".to_owned(),
                Feature::SSN => "urn:xmpp:ssn".to_owned(),
                Feature::Time => "urn:xmpp:time".to_owned(),
                Feature::XMLLang => "xmllang".to_owned(),
                Feature::VCardTemp => "vcard-temp".to_owned(),
                Feature::Styling(_) => "urn:xmpp:styling:0".to_owned(),
                Feature::SID(_) => "urn:xmpp:sid:0".to_owned(),
                Feature::Unknown(s) => s.to_owned(),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Amp {
        Errors,
        Action(AmpAction),
        Condition(AmpCondition),
    }

    impl ToString for Amp {
        fn to_string(&self) -> String {
            match self {
                Amp::Errors => "http://jabber.org/protocol/amp#errors".to_owned(),
                Amp::Action(amp_action) => amp_action.to_string(),
                Amp::Condition(amp_condition) => amp_condition.to_string(),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum AmpAction {
        Alert,
        Drop,
        Error,
        Notify,
    }

    impl ToString for AmpAction {
        fn to_string(&self) -> String {
            match self {
                AmpAction::Alert => "http://jabber.org/protocol/amp?action=alert",
                AmpAction::Drop => "http://jabber.org/protocol/amp?action=drop",
                AmpAction::Error => "http://jabber.org/protocol/amp?action=error",
                AmpAction::Notify => "http://jabber.org/protocol/amp?action=notify",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum AmpCondition {
        Deliver,
        ExpireAt,
        MatchResource,
    }

    impl ToString for AmpCondition {
        fn to_string(&self) -> String {
            match self {
                AmpCondition::Deliver => "http://jabber.org/protocol/amp?condition=deliver",
                AmpCondition::ExpireAt => "http://jabber.org/protocol/amp?condition=expire-at",
                AmpCondition::MatchResource => {
                    "http://jabber.org/protocol/amp?condition=match-resource"
                }
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum Bytestreams {
        UDP,
    }

    impl ToString for Bytestreams {
        fn to_string(&self) -> String {
            match self {
                Bytestreams::UDP => "http://jabber.org/protocol/bytestreams#udp",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum CapsVersion {
        One(Option<CapsOne>),
        Two(Option<CapsTwo>),
    }

    impl ToString for CapsVersion {
        fn to_string(&self) -> String {
            match self {
                CapsVersion::One(caps_one) => match caps_one {
                    Some(c) => c.to_string(),
                    None => "http://jabber.org/protocol/caps".to_owned(),
                },
                CapsVersion::Two(caps_two) => match caps_two {
                    Some(c) => c.to_string(),
                    None => "urn:xmpp:caps".to_owned(),
                },
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum CapsOne {
        Optimize,
    }

    impl ToString for CapsOne {
        fn to_string(&self) -> String {
            match self {
                CapsOne::Optimize => "http://jabber.org/protocol/caps#optimize",
            }
            .to_string()
        }
    }

    #[derive(Debug, Clone)]
    pub enum CapsTwo {
        Optimize,
    }

    impl ToString for CapsTwo {
        fn to_string(&self) -> String {
            match self {
                CapsTwo::Optimize => "urn:xmpp:caps:optimize",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum Disco {
        Info,
        Items,
    }

    impl ToString for Disco {
        fn to_string(&self) -> String {
            match self {
                Disco::Info => "http://jabber.org/protocol/disco#info",
                Disco::Items => "http://jabber.org/protocol/disco#items",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum MUC {
        Admin,
        Owner,
        Register,
        RoomConfig,
        RoomInfo,
        User,
        Hidden,
        MembersOnly,
        Moderated,
        NonAnonymous,
        Open,
        PasswordProtected,
        Persistent,
        Public,
        Rooms,
        SemiAnonymous,
        Temporary,
        Unmoderated,
        Unsecured,
    }

    impl ToString for MUC {
        fn to_string(&self) -> String {
            match self {
                MUC::Admin => "http://jabber.org/protocol/muc#admin",
                MUC::Owner => "http://jabber.org/protocol/muc#owner",
                MUC::Register => "http://jabber.org/protocol/muc#register",
                MUC::RoomConfig => "http://jabber.org/protocol/muc#roomconfig",
                MUC::RoomInfo => "http://jabber.org/protocol/muc#roominfo",
                MUC::User => "http://jabber.org/protocol/muc#user",
                MUC::Hidden => "muc_hidden",
                MUC::MembersOnly => "muc_membersonly",
                MUC::Moderated => "muc_moderated",
                MUC::NonAnonymous => "muc_nonanonymous",
                MUC::Open => "muc_open",
                MUC::PasswordProtected => "muc_passwordprotected",
                MUC::Persistent => "muc_persistent",
                MUC::Public => "muc_public",
                MUC::Rooms => "muc_rooms",
                MUC::SemiAnonymous => "muc_semianonymous",
                MUC::Temporary => "muc_temporary",
                MUC::Unmoderated => "muc_unmoderated",
                MUC::Unsecured => "muc_unsecured",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum PubSub {
        AccessAuthorize,
        AccessOpen,
        AccessPresence,
        AccessRoster,
        AccessWhitelist,
        AutoCreate,
        AutoSubscribe,
        Collections,
        ConfigNode,
        CreateAndConfigure,
        CreateNodes,
        DeleteAny,
        DeleteNodes,
        FilteredNotifications,
        GetPending,
        InstantNodes,
        ItemIDs,
        LastPublished,
        LeasedSubscription,
        ManageSubscription,
        MemberAffiliation,
        MetaData,
        ModifyAffiliations,
        MultiCollection,
        MultiSubscribe,
        OutcastAffiliation,
        PersistentItems,
        PresenceNotifications,
        PresenceSubscribe,
        Publish,
        PublishOptions,
        PublisherAffiliation,
        PurgeNodes,
        RetractItems,
        RetrieveAffiliations,
        RetrieveDefault,
        RetrieveItems,
        RetrieveSubscriptions,
        Subscribe,
        SubscriptionOptions,
        SubscriptionNotifications,
    }

    impl ToString for PubSub {
        fn to_string(&self) -> String {
            match self {
                PubSub::AccessAuthorize => "http://jabber.org/protocol/pubsub#access-authorize",
                PubSub::AccessOpen => "http://jabber.org/protocol/pubsub#access-open",
                PubSub::AccessPresence => "http://jabber.org/protocol/pubsub#access-presence",
                PubSub::AccessRoster => "http://jabber.org/protocol/pubsub#access-roster",
                PubSub::AccessWhitelist => "http://jabber.org/protocol/pubsub#access-whitelist",
                PubSub::AutoCreate => "http://jabber.org/protocol/pubsub#auto-create",
                PubSub::AutoSubscribe => "http://jabber.org/protocol/pubsub#auto-subscribe",
                PubSub::Collections => "http://jabber.org/protocol/pubsub#collections",
                PubSub::ConfigNode => "http://jabber.org/protocol/pubsub#config-node",
                PubSub::CreateAndConfigure => {
                    "http://jabber.org/protocol/pubsub#create-and-configure"
                }
                PubSub::CreateNodes => "http://jabber.org/protocol/pubsub#create-nodes",
                PubSub::DeleteAny => "http://jabber.org/protocol/pubsub#delete-any",
                PubSub::DeleteNodes => "http://jabber.org/protocol/pubsub#delete-nodes",
                PubSub::FilteredNotifications => {
                    "http://jabber.org/protocol/pubsub#filtered-notifications"
                }
                PubSub::GetPending => "http://jabber.org/protocol/pubsub#get-pending",
                PubSub::InstantNodes => "http://jabber.org/protocol/pubsub#instant-nodes",
                PubSub::ItemIDs => "http://jabber.org/protocol/pubsub#item-ids",
                PubSub::LastPublished => "http://jabber.org/protocol/pubsub#last-published",
                PubSub::LeasedSubscription => {
                    "http://jabber.org/protocol/pubsub#leased-subscription"
                }
                PubSub::ManageSubscription => {
                    "http://jabber.org/protocol/pubsub#manage-subscription"
                }
                PubSub::MemberAffiliation => "http://jabber.org/protocol/pubsub#member-affiliation",
                PubSub::MetaData => "http://jabber.org/protocol/pubsub#meta-data",
                PubSub::ModifyAffiliations => {
                    "http://jabber.org/protocol/pubsub#modify-affiliations"
                }
                PubSub::MultiCollection => "http://jabber.org/protocol/pubsub#multi-collection",
                PubSub::MultiSubscribe => "http://jabber.org/protocol/pubsub#multi-subscribe",
                PubSub::OutcastAffiliation => {
                    "http://jabber.org/protocol/pubsub#outcast-affiliation"
                }
                PubSub::PersistentItems => "http://jabber.org/protocol/pubsub#persistent-items",
                PubSub::PresenceNotifications => {
                    "http://jabber.org/protocol/pubsub#presence-notifications"
                }
                PubSub::PresenceSubscribe => "http://jabber.org/protocol/pubsub#presence-subscribe",
                PubSub::Publish => "http://jabber.org/protocol/pubsub#publish",
                PubSub::PublishOptions => "http://jabber.org/protocol/pubsub#publish-options",
                PubSub::PublisherAffiliation => {
                    "http://jabber.org/protocol/pubsub#publisher-affiliation"
                }
                PubSub::PurgeNodes => "http://jabber.org/protocol/pubsub#purge-nodes",
                PubSub::RetractItems => "http://jabber.org/protocol/pubsub#retract-items",
                PubSub::RetrieveAffiliations => {
                    "http://jabber.org/protocol/pubsub#retrieve-affiliations"
                }
                PubSub::RetrieveDefault => "http://jabber.org/protocol/pubsub#retrieve-default",
                PubSub::RetrieveItems => "http://jabber.org/protocol/pubsub#retrieve-items",
                PubSub::RetrieveSubscriptions => {
                    "http://jabber.org/protocol/pubsub#retrieve-subscriptions"
                }
                PubSub::Subscribe => "http://jabber.org/protocol/pubsub#subscribe",
                PubSub::SubscriptionOptions => {
                    "http://jabber.org/protocol/pubsub#subscription-options"
                }
                PubSub::SubscriptionNotifications => {
                    "http://jabber.org/protocol/pubsub#subscription-notifications"
                }
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum SOAP {
        Fault,
    }

    impl ToString for SOAP {
        fn to_string(&self) -> String {
            match self {
                SOAP::Fault => "http://jabber.org/protocol/soap#fault",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum WaitingList {
        Schemes(WaitingListSchemes),
    }

    impl ToString for WaitingList {
        fn to_string(&self) -> String {
            match self {
                WaitingList::Schemes(waiting_list_schemes) => waiting_list_schemes.to_string(),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum WaitingListSchemes {
        Mailto,
        Tel,
    }

    impl ToString for WaitingListSchemes {
        fn to_string(&self) -> String {
            match self {
                WaitingListSchemes::Mailto => {
                    "http://jabber.org/protocol/waitinglist/schemes/mailto"
                }
                WaitingListSchemes::Tel => "http://jabber.org/protocol/waitinglist/schemes/tel",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum Component {
        Accept,
        Connect,
    }

    impl ToString for Component {
        fn to_string(&self) -> String {
            match self {
                Component::Accept => "jabber:component:accept",
                Component::Connect => "jabber:component:connect",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum Iq {
        Auth,
        Gateway,
        Last,
        OOB,
        Privacy,
        Private,
        Register,
        Roster,
        RPC,
        Search,
        Version,
    }

    impl ToString for Iq {
        fn to_string(&self) -> String {
            match self {
                Iq::Auth => "jabber:iq:auth",
                Iq::Gateway => "jabber:iq:gateway",
                Iq::Last => "jabber:iq:last",
                Iq::OOB => "jabber:iq:oob",
                Iq::Privacy => "jabber:iq:privacy",
                Iq::Private => "jabber:iq:private",
                Iq::Register => "jabber:iq:register",
                Iq::Roster => "jabber:iq:roster",
                Iq::RPC => "jabber:iq:rpc",
                Iq::Search => "jabber:iq:search",
                Iq::Version => "jabber:iq:version",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum X {
        Data,
        Encrypted,
        OOB,
        Signed,
    }

    impl ToString for X {
        fn to_string(&self) -> String {
            match self {
                X::Data => "jabber:x:data",
                X::Encrypted => "jabber:x:encrypted",
                X::OOB => "jabber:x:oob",
                X::Signed => "jabber:x:signed",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum XMPPSASL {
        C2S,
        S2S,
    }

    impl ToString for XMPPSASL {
        fn to_string(&self) -> String {
            match self {
                XMPPSASL::C2S => "urn:ietf:params:xml:ns:xmpp-sasl#c2s",
                XMPPSASL::S2S => "urn:ietf:params:xml:ns:xmpp-sasl#s2s",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum XMPPTLS {
        C2S,
        S2S,
    }

    impl ToString for XMPPTLS {
        fn to_string(&self) -> String {
            match self {
                XMPPTLS::C2S => "urn:ietf:params:xml:ns:xmpp-tls#c2s",
                XMPPTLS::S2S => "urn:ietf:params:xml:ns:xmpp-tls#s2s",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum Archive {
        Auto,
        Manage,
        Manual,
        Pref,
    }

    impl ToString for Archive {
        fn to_string(&self) -> String {
            match self {
                Archive::Auto => "urn:xmpp:archive:auto",
                Archive::Manage => "urn:xmpp:archive:manage",
                Archive::Manual => "urn:xmpp:archive:manual",
                Archive::Pref => "urn:xmpp:archive:pref",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum Avatar {
        Data,
        Metadata,
    }

    impl ToString for Avatar {
        fn to_string(&self) -> String {
            match self {
                Avatar::Data => "urn:xmpp:avatar:data",
                Avatar::Metadata => "urn:xmpp:avatar:metadata",
            }
            .to_owned()
        }
    }

    #[derive(Debug, Clone)]
    pub enum Jingle {
        Apps(JingleApps),
    }

    impl ToString for Jingle {
        fn to_string(&self) -> String {
            match self {
                Jingle::Apps(jingle_apps) => jingle_apps.to_string(),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum JingleApps {
        RTP(JingleAppsRTP),
    }

    impl ToString for JingleApps {
        fn to_string(&self) -> String {
            match self {
                JingleApps::RTP(jingle_apps_rtp) => jingle_apps_rtp.to_string(),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum JingleAppsRTP {
        Audio,
        Video,
    }

    impl ToString for JingleAppsRTP {
        fn to_string(&self) -> String {
            match self {
                JingleAppsRTP::Audio => "urn:xmpp:jingle:apps:rtp:audio",
                JingleAppsRTP::Video => "urn:xmpp:jingle:apps:rtp:video",
            }
            .to_owned()
        }
    }
}

pub mod identity {
    use stanza::xep_0030::info;

    #[derive(Debug, Clone)]
    pub struct Identity {
        pub name: Option<String>,
        pub category: Category,
    }

    impl From<info::Identity> for Identity {
        fn from(value: info::Identity) -> Self {
            let category = Category::from_category_and_type(&value.category, &value.r#type);
            Self {
                name: value.name,
                category,
            }
        }
    }

    impl From<Identity> for info::Identity {
        fn from(value: Identity) -> Self {
            Self {
                category: value.category.to_string(),
                name: value.name,
                r#type: value.category.r#type(),
                lang: None,
            }
        }
    }

    // TODO: separate crate for disco registry

    /// categories taken from [XMPP Disco Categories](https://xmpp.org/registrar/disco-categories.html)
    #[derive(Debug, Clone)]
    pub enum Category {
        Account(Account),
        Auth(Auth),
        Authz(Authz),
        Automation(Automation),
        Client(Client),
        Collaboration(Collaboration),
        Component(Component),
        Conference(Conference),
        Directory(Directory),
        Gateway(Gateway),
        Headline(Headline),
        Hierarchy(Hierarchy),
        Proxy(Proxy),
        PubSub(PubSub),
        Server(Server),
        Store(Store),
        Other { category: String, r#type: String },
    }

    impl ToString for Category {
        fn to_string(&self) -> String {
            match self {
                Category::Account(_account) => "account",
                Category::Auth(_auth) => "auth",
                Category::Authz(_authz) => "authz",
                Category::Automation(_automation) => "automation",
                Category::Client(_client) => "client",
                Category::Collaboration(_collaboration) => "collaboration",
                Category::Component(_component) => "component",
                Category::Conference(_conference) => "conference",
                Category::Directory(_directory) => "directory",
                Category::Gateway(_gateway) => "gateway",
                Category::Headline(_headline) => "headline",
                Category::Hierarchy(_hierarchy) => "hierarchy",
                Category::Proxy(_proxy) => "proxy",
                Category::PubSub(_pub_sub) => "pubsub",
                Category::Server(_server) => "server",
                Category::Store(_store) => "store",
                Category::Other {
                    category,
                    r#type: _,
                } => category,
            }
            .to_owned()
        }
    }

    impl Category {
        pub fn from_category_and_type(category: &str, r#type: &str) -> Self {
            match category {
                "account" => Self::Account(r#type.into()),
                "auth" => Self::Auth(r#type.into()),
                "authz" => Self::Authz(r#type.into()),
                "automation" => Self::Automation(r#type.into()),
                "client" => Self::Client(r#type.into()),
                "collaboration" => Self::Collaboration(r#type.into()),
                "component" => Self::Component(r#type.into()),
                "conference" => Self::Conference(r#type.into()),
                "directory" => Self::Directory(r#type.into()),
                "gateway" => Self::Gateway(r#type.into()),
                "headline" => Self::Headline(r#type.into()),
                "hierarchy" => Self::Hierarchy(r#type.into()),
                "proxy" => Self::Proxy(r#type.into()),
                "pubsub" => Self::PubSub(r#type.into()),
                "server" => Self::Server(r#type.into()),
                "store" => Self::Store(r#type.into()),
                s => Self::Other {
                    category: s.to_owned(),
                    r#type: r#type.to_owned(),
                },
            }
        }

        pub fn r#type(&self) -> String {
            match self {
                Category::Account(account) => account.to_string(),
                Category::Auth(auth) => auth.to_string(),
                Category::Authz(authz) => authz.to_string(),
                Category::Automation(automation) => automation.to_string(),
                Category::Client(client) => client.to_string(),
                Category::Collaboration(collaboration) => collaboration.to_string(),
                Category::Component(component) => component.to_string(),
                Category::Conference(conference) => conference.to_string(),
                Category::Directory(directory) => directory.to_string(),
                Category::Gateway(gateway) => gateway.to_string(),
                Category::Headline(headline) => headline.to_string(),
                Category::Hierarchy(hierarchy) => hierarchy.to_string(),
                Category::Proxy(proxy) => proxy.to_string(),
                Category::PubSub(pub_sub) => pub_sub.to_string(),
                Category::Server(server) => server.to_string(),
                Category::Store(store) => store.to_string(),
                Category::Other {
                    category: _,
                    r#type,
                } => r#type.to_owned(),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Account {
        Admin,
        Anonymous,
        Registered,
        Other(String),
    }

    impl ToString for Account {
        fn to_string(&self) -> String {
            match self {
                Account::Admin => "admin",
                Account::Anonymous => "anonymous",
                Account::Registered => "registered",
                Account::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Account {
        fn from(value: &str) -> Self {
            match value {
                "admin" => Self::Admin,
                "anonymous" => Self::Anonymous,
                "registered" => Self::Registered,
                s => Self::Other(s.to_string()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Auth {
        Cert,
        Generic,
        LDAP,
        NTLM,
        PAM,
        Radius,
        Other(String),
    }

    impl ToString for Auth {
        fn to_string(&self) -> String {
            match self {
                Auth::Cert => "cert",
                Auth::Generic => "generic",
                Auth::LDAP => "ldap",
                Auth::NTLM => "ntlm",
                Auth::PAM => "pam",
                Auth::Radius => "radius",
                Auth::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Auth {
        fn from(value: &str) -> Self {
            match value {
                "cert" => Self::Cert,
                "generic" => Self::Generic,
                "ldap" => Self::LDAP,
                "ntlm" => Self::NTLM,
                "pam" => Self::PAM,
                "radius" => Self::Radius,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Authz {
        Ephemeral,
        Other(String),
    }

    impl ToString for Authz {
        fn to_string(&self) -> String {
            match self {
                Authz::Ephemeral => "ephemeral",
                Authz::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Authz {
        fn from(value: &str) -> Self {
            match value {
                "ephemeral" => Self::Ephemeral,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Automation {
        CommandList,
        CommandNode,
        RPC,
        SOAP,
        Translation,
        Other(String),
    }

    impl ToString for Automation {
        fn to_string(&self) -> String {
            match self {
                Automation::CommandList => "command-list",
                Automation::CommandNode => "command-node",
                Automation::RPC => "rpc",
                Automation::SOAP => "soap",
                Automation::Translation => "translation",
                Automation::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Automation {
        fn from(value: &str) -> Self {
            match value {
                "command-list" => Self::CommandList,
                "command-node" => Self::CommandNode,
                "rpc" => Self::RPC,
                "soap" => Self::SOAP,
                "translation" => Self::Translation,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Client {
        Bot,
        Console,
        Game,
        Handheld,
        PC,
        Phone,
        SMS,
        Tablet,
        Web,
        Other(String),
    }

    impl ToString for Client {
        fn to_string(&self) -> String {
            match self {
                Client::Bot => "bot",
                Client::Console => "console",
                Client::Game => "game",
                Client::Handheld => "handheld",
                Client::PC => "pc",
                Client::Phone => "phone",
                Client::SMS => "sms",
                Client::Tablet => "tablet",
                Client::Web => "web",
                Client::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Client {
        fn from(value: &str) -> Self {
            match value {
                "bot" => Self::Bot,
                "console" => Self::Console,
                "game" => Self::Game,
                "handheld" => Self::Handheld,
                "pc" => Self::PC,
                "phone" => Self::Phone,
                "sms" => Self::SMS,
                "tablet" => Self::Tablet,
                "web" => Self::Web,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Collaboration {
        Whiteboard,
        Other(String),
    }

    impl ToString for Collaboration {
        fn to_string(&self) -> String {
            match self {
                Collaboration::Whiteboard => "whiteboard",
                Collaboration::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Collaboration {
        fn from(value: &str) -> Self {
            match value {
                "whiteboard" => Self::Whiteboard,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Component {
        Archive,
        C2S,
        Generic,
        Load,
        Log,
        Presence,
        Router,
        S2S,
        SM,
        Stats,
        Other(String),
    }

    impl ToString for Component {
        fn to_string(&self) -> String {
            match self {
                Component::Archive => "archive",
                Component::C2S => "c2s",
                Component::Generic => "generic",
                Component::Load => "load",
                Component::Log => "log",
                Component::Presence => "presence",
                Component::Router => "router",
                Component::S2S => "s2s",
                Component::SM => "sm",
                Component::Stats => "stats",
                Component::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Component {
        fn from(value: &str) -> Self {
            match value {
                "archive" => Self::Archive,
                "c2s" => Self::C2S,
                "generic" => Self::Generic,
                "load" => Self::Load,
                "log" => Self::Log,
                "presence" => Self::Presence,
                "router" => Self::Router,
                "s2s" => Self::S2S,
                "sm" => Self::SM,
                "stats" => Self::Stats,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Conference {
        IRC,
        Text,
        Other(String),
    }

    impl ToString for Conference {
        fn to_string(&self) -> String {
            match self {
                Conference::IRC => "irc",
                Conference::Text => "text",
                Conference::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Conference {
        fn from(value: &str) -> Self {
            match value {
                "irc" => Self::IRC,
                "text" => Self::Text,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Directory {
        Chatroom,
        Group,
        User,
        WaitingList,
        Other(String),
    }

    impl ToString for Directory {
        fn to_string(&self) -> String {
            match self {
                Directory::Chatroom => "chatroom",
                Directory::Group => "group",
                Directory::User => "user",
                Directory::WaitingList => "waiting-list",
                Directory::Other(s) => s,
            }
            .to_string()
        }
    }

    impl From<&str> for Directory {
        fn from(value: &str) -> Self {
            match value {
                "chatroom" => Self::Chatroom,
                "group" => Self::Group,
                "user" => Self::User,
                "waiting-list" => Self::WaitingList,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Gateway {
        Aim,
        Discord,
        Facebook,
        GaduGadu,
        HttpWS,
        ICQ,
        IRC,
        LCS,
        Mattermost,
        MRIM,
        MSN,
        MySpaceIM,
        OCS,
        PSTN,
        QQ,
        Sametime,
        Signal,
        SIMPLE,
        Skype,
        SMS,
        SMTP,
        Steam,
        Telegram,
        Tlen,
        Xfire,
        XMPP,
        Yahoo,
        Other(String),
    }

    impl ToString for Gateway {
        fn to_string(&self) -> String {
            match self {
                Gateway::Aim => "aim",
                Gateway::Discord => "discord",
                Gateway::Facebook => "facebook",
                Gateway::GaduGadu => "gadu-gadu",
                Gateway::HttpWS => "http-ws",
                Gateway::ICQ => "icq",
                Gateway::IRC => "irc",
                Gateway::LCS => "lcs",
                Gateway::Mattermost => "mattermost",
                Gateway::MRIM => "mrim",
                Gateway::MSN => "msn",
                Gateway::MySpaceIM => "myspaceim",
                Gateway::OCS => "ocs",
                Gateway::PSTN => "pstn",
                Gateway::QQ => "qq",
                Gateway::Sametime => "sametime",
                Gateway::Signal => "signal",
                Gateway::SIMPLE => "simple",
                Gateway::Skype => "skype",
                Gateway::SMS => "sms",
                Gateway::SMTP => "smtp",
                Gateway::Steam => "steam",
                Gateway::Telegram => "telegram",
                Gateway::Tlen => "tlen",
                Gateway::Xfire => "xfire",
                Gateway::XMPP => "xmpp",
                Gateway::Yahoo => "yahoo",
                Gateway::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Gateway {
        fn from(value: &str) -> Self {
            match value {
                "aim" => Self::Aim,
                "discord" => Self::Discord,
                "facebook" => Self::Facebook,
                "gadu-gadu" => Self::GaduGadu,
                "http-ws" => Self::HttpWS,
                "icq" => Self::ICQ,
                "irc" => Self::IRC,
                "lcs" => Self::LCS,
                "mattermost" => Self::Mattermost,
                "mrim" => Self::MRIM,
                "msn" => Self::MSN,
                "myspaceim" => Self::MySpaceIM,
                "ocs" => Self::OCS,
                "pstn" => Self::PSTN,
                "qq" => Self::QQ,
                "sametime" => Self::Sametime,
                "signal" => Self::Signal,
                "simple" => Self::SIMPLE,
                "skype" => Self::Skype,
                "sms" => Self::SMS,
                "smtp" => Self::SMTP,
                "steam" => Self::Steam,
                "telegram" => Self::Telegram,
                "tlen" => Self::Tlen,
                "xfire" => Self::Xfire,
                "xmpp" => Self::XMPP,
                "yahoo" => Self::Yahoo,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Headline {
        NewMail,
        RSS,
        Weather,
        Other(String),
    }

    impl ToString for Headline {
        fn to_string(&self) -> String {
            match self {
                Headline::NewMail => "new-mail",
                Headline::RSS => "rss",
                Headline::Weather => "weather",
                Headline::Other(s) => s,
            }
            .to_string()
        }
    }

    impl From<&str> for Headline {
        fn from(value: &str) -> Self {
            match value {
                "new-mail" => Self::NewMail,
                "rss" => Self::RSS,
                "weather" => Self::Weather,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Hierarchy {
        Branch,
        Leaf,
        Other(String),
    }

    impl ToString for Hierarchy {
        fn to_string(&self) -> String {
            match self {
                Hierarchy::Branch => "branch",
                Hierarchy::Leaf => "leaf",
                Hierarchy::Other(s) => s,
            }
            .to_string()
        }
    }

    impl From<&str> for Hierarchy {
        fn from(value: &str) -> Self {
            match value {
                "branch" => Self::Branch,
                "leaf" => Self::Leaf,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Proxy {
        Bytestreams,
        Other(String),
    }

    impl ToString for Proxy {
        fn to_string(&self) -> String {
            match self {
                Proxy::Bytestreams => "bytestreams",
                Proxy::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for Proxy {
        fn from(value: &str) -> Self {
            match value {
                "bytestreams" => Self::Bytestreams,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum PubSub {
        Collection,
        Leaf,
        PEP,
        Service,
        // TODO: should there be other in each of these
        Other(String),
    }

    impl ToString for PubSub {
        fn to_string(&self) -> String {
            match self {
                PubSub::Collection => "collection",
                PubSub::Leaf => "leaf",
                PubSub::PEP => "pep",
                PubSub::Service => "service",
                PubSub::Other(s) => s,
            }
            .to_owned()
        }
    }

    impl From<&str> for PubSub {
        fn from(value: &str) -> Self {
            match value {
                "collection" => Self::Collection,
                "leaf" => Self::Leaf,
                "pep" => Self::PEP,
                "service" => Self::Service,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Server {
        IM,
        Other(String),
    }

    impl ToString for Server {
        fn to_string(&self) -> String {
            match self {
                Server::IM => "im",
                Server::Other(s) => s,
            }
            .to_string()
        }
    }

    impl From<&str> for Server {
        fn from(value: &str) -> Self {
            match value {
                "im" => Self::IM,
                s => Self::Other(s.to_owned()),
            }
        }
    }

    #[derive(Debug, Clone)]
    pub enum Store {
        Berkeley,
        File,
        Generic,
        LDAP,
        MySQL,
        Oracle,
        Postgres,
        Other(String),
    }

    impl ToString for Store {
        fn to_string(&self) -> String {
            match self {
                Store::Berkeley => "berkeley",
                Store::File => "file",
                Store::Generic => "generic",
                Store::LDAP => "ldap",
                Store::MySQL => "mysql",
                Store::Oracle => "oracle",
                Store::Postgres => "postgres",
                Store::Other(s) => s,
            }
            .to_string()
        }
    }

    impl From<&str> for Store {
        fn from(value: &str) -> Self {
            match value {
                "berkeley" => Self::Berkeley,
                "file" => Self::File,
                "generic" => Self::Generic,
                "ldap" => Self::LDAP,
                "mysql" => Self::MySQL,
                "oracle" => Self::Oracle,
                "postgres" => Self::Postgres,
                s => Self::Other(s.to_owned()),
            }
        }
    }
}