aboutsummaryrefslogblamecommitdiffstats
path: root/stanza/src/xep_0060/errors.rs
blob: e6817ac458555ce9ff15e75fc33d4d3375caf5de (plain) (tree)
1
2
3
4
5
6
7
8
9
10







                                        

                                                                          



















































                                                                   



























































                                                                                                 





























































































































































































































                                                                                                    
use std::{fmt::Display, str::FromStr};

use peanuts::{
    element::{FromElement, IntoElement},
    DeserializeError, Element,
};
use thiserror::Error;

use crate::{client::error::ErrorType, stanza_error::Error as StanzaError};

pub const XMLNS: &str = "http://jabber.org/protocol/pubsub#errors";

#[derive(Error, Clone, Debug)]
pub enum Error {
    #[error("closed node")]
    ClosedNode,
    #[error("configuration required")]
    ConfigurationRequired,
    #[error("invalid jid")]
    InvalidJID,
    #[error("invalid options")]
    InvalidOptions,
    #[error("invalid payload")]
    InvalidPayload,
    #[error("invalid subscription id")]
    InvalidSubID,
    #[error("item forbidden")]
    ItemForbidden,
    #[error("item required")]
    ItemRequired,
    #[error("jid required")]
    JIDRequired,
    #[error("max items exceeded")]
    MaxItemsExceeded,
    #[error("max nodes exceeded")]
    MaxNodesExceeded,
    #[error("node id required")]
    NodeIDRequired,
    #[error("not in roster group")]
    NotInRosterGroup,
    #[error("not subscribed")]
    NotSubscribed,
    #[error("payload too big")]
    PayloadTooBig,
    #[error("payload required")]
    PayloadRequired,
    #[error("pending subscription")]
    PendingSubscription,
    #[error("precondition not met")]
    PreconditionNotMet,
    #[error("presence subscription required")]
    PresenceSubscriptionRequired,
    #[error("subscription id required")]
    SubIDRequired,
    #[error("too many subscriptions")]
    TooManySubscriptions,
    #[error("unsupported feature: {0}")]
    Unsupported(Feature),
    #[error("unsupported access mode")]
    UnsupportedAccessModel,
}

impl Error {
    pub fn r#type(&self) -> ErrorType {
        match self {
            Error::ClosedNode => ErrorType::Cancel,
            Error::ConfigurationRequired => ErrorType::Modify,
            Error::InvalidJID => ErrorType::Modify,
            Error::InvalidOptions => ErrorType::Modify,
            Error::InvalidPayload => ErrorType::Modify,
            Error::InvalidSubID => ErrorType::Modify,
            Error::ItemForbidden => ErrorType::Modify,
            Error::ItemRequired => ErrorType::Modify,
            Error::JIDRequired => ErrorType::Modify,
            Error::MaxItemsExceeded => ErrorType::Cancel,
            Error::MaxNodesExceeded => ErrorType::Cancel,
            Error::NodeIDRequired => ErrorType::Modify,
            Error::NotInRosterGroup => ErrorType::Auth,
            Error::NotSubscribed => ErrorType::Cancel,
            Error::PayloadTooBig => ErrorType::Modify,
            Error::PayloadRequired => ErrorType::Modify,
            Error::PendingSubscription => ErrorType::Auth,
            Error::PreconditionNotMet => ErrorType::Cancel,
            Error::PresenceSubscriptionRequired => ErrorType::Auth,
            Error::SubIDRequired => ErrorType::Modify,
            Error::TooManySubscriptions => ErrorType::Wait,
            Error::Unsupported(_feature) => ErrorType::Cancel,
            Error::UnsupportedAccessModel => ErrorType::Modify,
        }
    }

    pub fn stanza_errors(self) -> Vec<StanzaError> {
        match self {
            Error::ClosedNode => vec![StanzaError::NotAllowed, self.into()],
            Error::ConfigurationRequired => vec![StanzaError::NotAcceptable, self.into()],
            Error::InvalidJID => vec![StanzaError::BadRequest, self.into()],
            Error::InvalidOptions => vec![StanzaError::BadRequest, self.into()],
            Error::InvalidPayload => vec![StanzaError::BadRequest, self.into()],
            Error::InvalidSubID => vec![StanzaError::NotAcceptable, self.into()],
            Error::ItemForbidden => vec![StanzaError::BadRequest, self.into()],
            Error::ItemRequired => vec![StanzaError::BadRequest, self.into()],
            Error::JIDRequired => vec![StanzaError::BadRequest, self.into()],
            Error::MaxItemsExceeded => vec![StanzaError::NotAllowed, self.into()],
            Error::MaxNodesExceeded => vec![StanzaError::NotAllowed, self.into()],
            Error::NodeIDRequired => vec![StanzaError::BadRequest, self.into()],
            Error::NotInRosterGroup => vec![StanzaError::NotAuthorized, self.into()],
            Error::NotSubscribed => vec![StanzaError::UnexpectedRequest, self.into()],
            Error::PayloadTooBig => vec![StanzaError::NotAcceptable, self.into()],
            Error::PayloadRequired => vec![StanzaError::BadRequest, self.into()],
            Error::PendingSubscription => vec![StanzaError::NotAuthorized, self.into()],
            Error::PreconditionNotMet => vec![StanzaError::Conflict, self.into()],
            Error::PresenceSubscriptionRequired => vec![StanzaError::NotAuthorized, self.into()],
            Error::SubIDRequired => vec![StanzaError::BadRequest, self.into()],
            Error::TooManySubscriptions => vec![StanzaError::PolicyViolation, self.into()],
            Error::Unsupported(ref _feature) => {
                vec![StanzaError::FeatureNotImplemented, self.into()]
            }
            Error::UnsupportedAccessModel => vec![StanzaError::NotAcceptable, self.into()],
        }
    }
}

impl FromElement for Error {
    fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
        match element.identify() {
            (Some(XMLNS), "closed-node") => Ok(Self::ClosedNode),
            (Some(XMLNS), "configuration-required") => Ok(Self::ConfigurationRequired),
            (Some(XMLNS), "invalid-jid") => Ok(Self::InvalidJID),
            (Some(XMLNS), "invalid-options") => Ok(Self::InvalidOptions),
            (Some(XMLNS), "invalid-payload") => Ok(Self::InvalidPayload),
            (Some(XMLNS), "invalid-subid") => Ok(Self::InvalidSubID),
            (Some(XMLNS), "item-forbidden") => Ok(Self::ItemForbidden),
            (Some(XMLNS), "item-required") => Ok(Self::ItemRequired),
            (Some(XMLNS), "jid-required") => Ok(Self::JIDRequired),
            (Some(XMLNS), "max-items-exceeded") => Ok(Self::MaxItemsExceeded),
            (Some(XMLNS), "max-nodes-exceeded") => Ok(Self::MaxNodesExceeded),
            (Some(XMLNS), "nodeid-required") => Ok(Self::NodeIDRequired),
            (Some(XMLNS), "not-in-roster-group") => Ok(Self::NotInRosterGroup),
            (Some(XMLNS), "not-subscribed") => Ok(Self::NotSubscribed),
            (Some(XMLNS), "payload-too-big") => Ok(Self::PayloadTooBig),
            (Some(XMLNS), "payload-required") => Ok(Self::PayloadRequired),
            (Some(XMLNS), "pending-subscription") => Ok(Self::PendingSubscription),
            (Some(XMLNS), "precondition-not-met") => Ok(Self::PreconditionNotMet),
            (Some(XMLNS), "presence-subscription-required") => {
                Ok(Self::PresenceSubscriptionRequired)
            }
            (Some(XMLNS), "subid-required") => Ok(Self::SubIDRequired),
            (Some(XMLNS), "too-many-subscription") => Ok(Self::TooManySubscriptions),
            (Some(XMLNS), "unsupported") => Ok(Self::Unsupported(element.attribute("feature")?)),
            (Some(XMLNS), "unsupported-access-model") => Ok(Self::UnsupportedAccessModel),
            _ => return Err(peanuts::DeserializeError::UnexpectedElement(element)),
        }
    }
}

impl IntoElement for Error {
    fn builder(&self) -> peanuts::element::ElementBuilder {
        match self {
            Error::ClosedNode => Element::builder("closed-node", Some(XMLNS)),
            Error::ConfigurationRequired => Element::builder("configuration-required", Some(XMLNS)),
            Error::InvalidJID => Element::builder("invalid-jid", Some(XMLNS)),
            Error::InvalidOptions => Element::builder("invalid-options", Some(XMLNS)),
            Error::InvalidPayload => Element::builder("invalid-payload", Some(XMLNS)),
            Error::InvalidSubID => Element::builder("invalid-subid", Some(XMLNS)),
            Error::ItemForbidden => Element::builder("item-forbidden", Some(XMLNS)),
            Error::ItemRequired => Element::builder("item-required", Some(XMLNS)),
            Error::JIDRequired => Element::builder("jid-required", Some(XMLNS)),
            Error::MaxItemsExceeded => Element::builder("max-items-exceeded", Some(XMLNS)),
            Error::MaxNodesExceeded => Element::builder("max-nodes-exceeded", Some(XMLNS)),
            Error::NodeIDRequired => Element::builder("node-id-required", Some(XMLNS)),
            Error::NotInRosterGroup => Element::builder("not-in-roster-group", Some(XMLNS)),
            Error::NotSubscribed => Element::builder("not-subscribed", Some(XMLNS)),
            Error::PayloadTooBig => Element::builder("payload-too-big", Some(XMLNS)),
            Error::PayloadRequired => Element::builder("payload-required", Some(XMLNS)),
            Error::PendingSubscription => Element::builder("pending-subscription", Some(XMLNS)),
            Error::PreconditionNotMet => Element::builder("precondition-not-met", Some(XMLNS)),
            Error::PresenceSubscriptionRequired => {
                Element::builder("presence-subscription-required", Some(XMLNS))
            }
            Error::SubIDRequired => Element::builder("subid-required", Some(XMLNS)),
            Error::TooManySubscriptions => Element::builder("too-many-subscriptions", Some(XMLNS)),
            Error::Unsupported(feature) => {
                Element::builder("unsupported", Some(XMLNS)).push_attribute("feature", feature)
            }
            Error::UnsupportedAccessModel => {
                Element::builder("unsupported-access-model", Some(XMLNS))
            }
        }
    }
}

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

impl Display for Feature {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str = match self {
            Feature::AccessAuthorize => "access-authorize",
            Feature::AccessOpen => "access-open",
            Feature::AccessPresence => "access-presence",
            Feature::AccessRoster => "access-roster",
            Feature::AccessWhitelist => "access-whitelist",
            Feature::AutoCreate => "auto-create",
            Feature::AutoSubscribe => "auto-subscribe",
            Feature::Collections => "collections",
            Feature::ConfigNode => "config-node",
            Feature::CreateAndConfigure => "create-and-configure",
            Feature::CreateNodes => "create-nodes",
            Feature::DeleteItems => "delete-items",
            Feature::DeleteNodes => "delete-nodes",
            Feature::FilteredNotifications => "filtered-notifications",
            Feature::GetPending => "get-pending",
            Feature::InstantNodes => "instant-nodes",
            Feature::ItemIDs => "item-ids",
            Feature::LastPublished => "last-published",
            Feature::LeasedSubscription => "leased-subscription",
            Feature::ManageSubscriptions => "manage-subscriptions",
            Feature::MemberAffiliation => "member-affiliation",
            Feature::MetaData => "meta-data",
            Feature::ModifyAffiliations => "modify-affiliations",
            Feature::MultiCollection => "multi-collection",
            Feature::MultiItems => "multi-items",
            Feature::MultiSubscribe => "multi-subscribe",
            Feature::OutcastAffiliation => "outcast-affiliation",
            Feature::PersistentItems => "persistent-items",
            Feature::PresenceNotifications => "presence-notifications",
            Feature::PresenceSubscribe => "presence-subscribe",
            Feature::Publish => "publish",
            Feature::PublishOptions => "publish-options",
            Feature::PublishOnlyAffiliation => "publish-only-affiliation",
            Feature::PublisherAffiliation => "publisher-affiliation",
            Feature::PurgeNodes => "purge-nodes",
            Feature::RetractItems => "retract-items",
            Feature::RetrieveAffiliations => "retrieve-affiliations",
            Feature::RetrieveDefault => "retrieve-default",
            Feature::RetrieveDefaultSub => "retrieve-default-sub",
            Feature::RetrieveItems => "retrieve-items",
            Feature::RetrieveSubscriptions => "retrieve-subscriptions",
            Feature::Subscribe => "subscribe",
            Feature::SubscriptionOptions => "subscription-options",
            Feature::SubscriptionNotifications => "subscription-notifications",
        };
        f.write_str(str)
    }
}

impl FromStr for Feature {
    type Err = DeserializeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "access-authorize" => Feature::AccessAuthorize,
            "access-open" => Feature::AccessOpen,
            "access-presence" => Feature::AccessPresence,
            "access-roster" => Feature::AccessRoster,
            "access-whitelist" => Feature::AccessWhitelist,
            "auto-create" => Feature::AutoCreate,
            "auto-subscribe" => Feature::AutoSubscribe,
            "collections" => Feature::Collections,
            "config-node" => Feature::ConfigNode,
            "create-and-configure" => Feature::CreateAndConfigure,
            "create-nodes" => Feature::CreateNodes,
            "delete-items" => Feature::DeleteItems,
            "delete-nodes" => Feature::DeleteNodes,
            "filtered-notifications" => Feature::FilteredNotifications,
            "get-pending" => Feature::GetPending,
            "instant-nodes" => Feature::InstantNodes,
            "item-ids" => Feature::ItemIDs,
            "last-published" => Feature::LastPublished,
            "leased-subscription" => Feature::LeasedSubscription,
            "manage-subscriptions" => Feature::ManageSubscriptions,
            "member-affiliation" => Feature::MemberAffiliation,
            "meta-data" => Feature::MetaData,
            "modify-affiliations" => Feature::ModifyAffiliations,
            "multi-collection" => Feature::MultiCollection,
            "multi-items" => Feature::MultiItems,
            "multi-subscribe" => Feature::MultiSubscribe,
            "outcast-affiliation" => Feature::OutcastAffiliation,
            "persistent-items" => Feature::PersistentItems,
            "presence-notifications" => Feature::PresenceNotifications,
            "presence-subscribe" => Feature::PresenceSubscribe,
            "publish" => Feature::Publish,
            "publish-options" => Feature::PublishOptions,
            "publish-only-affiliation" => Feature::PublishOnlyAffiliation,
            "publisher-affiliation" => Feature::PublisherAffiliation,
            "purge-nodes" => Feature::PurgeNodes,
            "retract-items" => Feature::RetractItems,
            "retrieve-affiliations" => Feature::RetrieveAffiliations,
            "retrieve-default" => Feature::RetrieveDefault,
            "retrieve-default-sub" => Feature::RetrieveDefaultSub,
            "retrieve-items" => Feature::RetrieveItems,
            "retrieve-subscriptions" => Feature::RetrieveSubscriptions,
            "subscribe" => Feature::Subscribe,
            "subscription-options" => Feature::SubscriptionOptions,
            "subscription-notifications" => Feature::SubscriptionNotifications,
            s => return Err(DeserializeError::FromStr(s.to_owned())),
        })
    }
}