/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * * Modified by Coffey Labs in 2026 for INBUXA. */ use crate::request::capability::Capability; use registry::{ schema::prelude::{OBJ_SINGLETON, ObjectType}, types::EnumImpl, }; use std::{borrow::Cow, fmt::Display}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MethodName { pub obj: MethodObject, pub fnc: MethodFunction, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MethodObject { Email, Mailbox, Core, Blob, PushSubscription, Thread, SearchSnippet, Identity, EmailSubmission, VacationResponse, SieveScript, Principal, Quota, Calendar, CalendarEvent, CalendarEventNotification, AddressBook, ContactCard, FileNode, ParticipantIdentity, ShareNotification, Registry(ObjectType), // inbuxa: Fastmail's MaskedEmail MaskedEmail, // inbuxa: deleted accounts (UD-17) DeletedAccount, // inbuxa: AI call limits AiLimits, } impl MethodObject { pub fn capability(&self) -> Capability { match self { MethodObject::Email | MethodObject::Mailbox | MethodObject::Thread | MethodObject::SearchSnippet => Capability::Mail, MethodObject::Core | MethodObject::PushSubscription => Capability::Core, MethodObject::Blob => Capability::Blob, MethodObject::Identity | MethodObject::EmailSubmission => Capability::Submission, MethodObject::VacationResponse => Capability::VacationResponse, MethodObject::SieveScript => Capability::Sieve, MethodObject::Principal | MethodObject::ShareNotification => Capability::Principals, MethodObject::Quota => Capability::Quota, MethodObject::Calendar | MethodObject::CalendarEvent | MethodObject::CalendarEventNotification | MethodObject::ParticipantIdentity => Capability::Calendars, MethodObject::AddressBook | MethodObject::ContactCard => Capability::Contacts, MethodObject::FileNode => Capability::FileNode, MethodObject::Registry(_) => Capability::Stalwart, MethodObject::MaskedEmail => Capability::FastmailMaskedEmail, MethodObject::DeletedAccount => Capability::Inbuxa, MethodObject::AiLimits => Capability::Inbuxa, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MethodFunction { Get, Set, Changes, Query, QueryChanges, Copy, Import, Parse, Validate, Lookup, Upload, Echo, GetAvailability, } impl Display for MethodName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str().as_ref()) } } impl MethodName { pub fn new(obj: MethodObject, fnc: MethodFunction) -> Self { Self { obj, fnc } } pub fn error() -> Self { Self { obj: MethodObject::Thread, fnc: MethodFunction::Echo, } } pub fn as_str(&self) -> Cow<'static, str> { match (self.fnc, self.obj) { (MethodFunction::Get, MethodObject::PushSubscription) => "PushSubscription/get", (MethodFunction::Set, MethodObject::PushSubscription) => "PushSubscription/set", (MethodFunction::Get, MethodObject::Mailbox) => "Mailbox/get", (MethodFunction::Changes, MethodObject::Mailbox) => "Mailbox/changes", (MethodFunction::Query, MethodObject::Mailbox) => "Mailbox/query", (MethodFunction::QueryChanges, MethodObject::Mailbox) => "Mailbox/queryChanges", (MethodFunction::Set, MethodObject::Mailbox) => "Mailbox/set", (MethodFunction::Get, MethodObject::Thread) => "Thread/get", (MethodFunction::Changes, MethodObject::Thread) => "Thread/changes", (MethodFunction::Get, MethodObject::Email) => "Email/get", (MethodFunction::Changes, MethodObject::Email) => "Email/changes", (MethodFunction::Query, MethodObject::Email) => "Email/query", (MethodFunction::QueryChanges, MethodObject::Email) => "Email/queryChanges", (MethodFunction::Set, MethodObject::Email) => "Email/set", (MethodFunction::Copy, MethodObject::Email) => "Email/copy", (MethodFunction::Import, MethodObject::Email) => "Email/import", (MethodFunction::Parse, MethodObject::Email) => "Email/parse", (MethodFunction::Get, MethodObject::SearchSnippet) => "SearchSnippet/get", (MethodFunction::Get, MethodObject::Identity) => "Identity/get", (MethodFunction::Changes, MethodObject::Identity) => "Identity/changes", (MethodFunction::Set, MethodObject::Identity) => "Identity/set", (MethodFunction::Get, MethodObject::EmailSubmission) => "EmailSubmission/get", (MethodFunction::Changes, MethodObject::EmailSubmission) => "EmailSubmission/changes", (MethodFunction::Query, MethodObject::EmailSubmission) => "EmailSubmission/query", (MethodFunction::QueryChanges, MethodObject::EmailSubmission) => { "EmailSubmission/queryChanges" } (MethodFunction::Set, MethodObject::EmailSubmission) => "EmailSubmission/set", (MethodFunction::Get, MethodObject::VacationResponse) => "VacationResponse/get", (MethodFunction::Set, MethodObject::VacationResponse) => "VacationResponse/set", (MethodFunction::Get, MethodObject::SieveScript) => "SieveScript/get", (MethodFunction::Set, MethodObject::SieveScript) => "SieveScript/set", (MethodFunction::Query, MethodObject::SieveScript) => "SieveScript/query", (MethodFunction::Validate, MethodObject::SieveScript) => "SieveScript/validate", (MethodFunction::Get, MethodObject::Principal) => "Principal/get", (MethodFunction::Set, MethodObject::Principal) => "Principal/set", (MethodFunction::Query, MethodObject::Principal) => "Principal/query", (MethodFunction::Changes, MethodObject::Principal) => "Principal/changes", (MethodFunction::QueryChanges, MethodObject::Principal) => "Principal/queryChanges", (MethodFunction::GetAvailability, MethodObject::Principal) => { "Principal/getAvailability" } (MethodFunction::Get, MethodObject::Quota) => "Quota/get", (MethodFunction::Changes, MethodObject::Quota) => "Quota/changes", (MethodFunction::Query, MethodObject::Quota) => "Quota/query", (MethodFunction::QueryChanges, MethodObject::Quota) => "Quota/queryChanges", (MethodFunction::Get, MethodObject::Blob) => "Blob/get", (MethodFunction::Copy, MethodObject::Blob) => "Blob/copy", (MethodFunction::Lookup, MethodObject::Blob) => "Blob/lookup", (MethodFunction::Upload, MethodObject::Blob) => "Blob/upload", (MethodFunction::Get, MethodObject::AddressBook) => "AddressBook/get", (MethodFunction::Changes, MethodObject::AddressBook) => "AddressBook/changes", (MethodFunction::Set, MethodObject::AddressBook) => "AddressBook/set", (MethodFunction::Query, MethodObject::AddressBook) => "AddressBook/query", (MethodFunction::Get, MethodObject::ContactCard) => "ContactCard/get", (MethodFunction::Changes, MethodObject::ContactCard) => "ContactCard/changes", (MethodFunction::Query, MethodObject::ContactCard) => "ContactCard/query", (MethodFunction::QueryChanges, MethodObject::ContactCard) => "ContactCard/queryChanges", (MethodFunction::Set, MethodObject::ContactCard) => "ContactCard/set", (MethodFunction::Copy, MethodObject::ContactCard) => "ContactCard/copy", (MethodFunction::Parse, MethodObject::ContactCard) => "ContactCard/parse", (MethodFunction::Get, MethodObject::FileNode) => "FileNode/get", (MethodFunction::Changes, MethodObject::FileNode) => "FileNode/changes", (MethodFunction::Query, MethodObject::FileNode) => "FileNode/query", (MethodFunction::QueryChanges, MethodObject::FileNode) => "FileNode/queryChanges", (MethodFunction::Set, MethodObject::FileNode) => "FileNode/set", (MethodFunction::Copy, MethodObject::FileNode) => "FileNode/copy", (MethodFunction::Get, MethodObject::ShareNotification) => "ShareNotification/get", (MethodFunction::Changes, MethodObject::ShareNotification) => { "ShareNotification/changes" } (MethodFunction::Query, MethodObject::ShareNotification) => "ShareNotification/query", (MethodFunction::QueryChanges, MethodObject::ShareNotification) => { "ShareNotification/queryChanges" } (MethodFunction::Set, MethodObject::ShareNotification) => "ShareNotification/set", (MethodFunction::Get, MethodObject::Calendar) => "Calendar/get", (MethodFunction::Changes, MethodObject::Calendar) => "Calendar/changes", (MethodFunction::Set, MethodObject::Calendar) => "Calendar/set", (MethodFunction::Query, MethodObject::Calendar) => "Calendar/query", (MethodFunction::Get, MethodObject::CalendarEvent) => "CalendarEvent/get", (MethodFunction::Changes, MethodObject::CalendarEvent) => "CalendarEvent/changes", (MethodFunction::Query, MethodObject::CalendarEvent) => "CalendarEvent/query", (MethodFunction::QueryChanges, MethodObject::CalendarEvent) => { "CalendarEvent/queryChanges" } (MethodFunction::Set, MethodObject::CalendarEvent) => "CalendarEvent/set", (MethodFunction::Copy, MethodObject::CalendarEvent) => "CalendarEvent/copy", (MethodFunction::Parse, MethodObject::CalendarEvent) => "CalendarEvent/parse", (MethodFunction::Get, MethodObject::CalendarEventNotification) => { "CalendarEventNotification/get" } (MethodFunction::Changes, MethodObject::CalendarEventNotification) => { "CalendarEventNotification/changes" } (MethodFunction::Query, MethodObject::CalendarEventNotification) => { "CalendarEventNotification/query" } (MethodFunction::QueryChanges, MethodObject::CalendarEventNotification) => { "CalendarEventNotification/queryChanges" } (MethodFunction::Set, MethodObject::CalendarEventNotification) => { "CalendarEventNotification/set" } (MethodFunction::Get, MethodObject::ParticipantIdentity) => "ParticipantIdentity/get", (MethodFunction::Changes, MethodObject::ParticipantIdentity) => { "ParticipantIdentity/changes" } (MethodFunction::Set, MethodObject::ParticipantIdentity) => "ParticipantIdentity/set", (MethodFunction::Echo, MethodObject::Core) => "Core/echo", (MethodFunction::Get, MethodObject::MaskedEmail) => "MaskedEmail/get", (MethodFunction::Set, MethodObject::MaskedEmail) => "MaskedEmail/set", (MethodFunction::Get, MethodObject::DeletedAccount) => "inbuxa:DeletedAccount/get", (MethodFunction::Set, MethodObject::DeletedAccount) => "inbuxa:DeletedAccount/set", (MethodFunction::Get, MethodObject::AiLimits) => "inbuxa:AiLimits/get", (MethodFunction::Set, MethodObject::AiLimits) => "inbuxa:AiLimits/set", (method, MethodObject::Registry(obj)) => { return Cow::Owned(format!("x:{}/{}", obj.as_str(), method.as_str())); } _ => "error", } .into() } pub fn parse(s: &str) -> Option { hashify::tiny_map!(s.as_bytes(), "PushSubscription/get" => (MethodObject::PushSubscription, MethodFunction::Get), "PushSubscription/set" => (MethodObject::PushSubscription, MethodFunction::Set), "Mailbox/get" => (MethodObject::Mailbox, MethodFunction::Get), "Mailbox/changes" => (MethodObject::Mailbox, MethodFunction::Changes), "Mailbox/query" => (MethodObject::Mailbox, MethodFunction::Query), "Mailbox/queryChanges" => (MethodObject::Mailbox, MethodFunction::QueryChanges), "Mailbox/set" => (MethodObject::Mailbox, MethodFunction::Set), "Thread/get" => (MethodObject::Thread, MethodFunction::Get), "Thread/changes" => (MethodObject::Thread, MethodFunction::Changes), "Email/get" => (MethodObject::Email, MethodFunction::Get), "Email/changes" => (MethodObject::Email, MethodFunction::Changes), "Email/query" => (MethodObject::Email, MethodFunction::Query), "Email/queryChanges" => (MethodObject::Email, MethodFunction::QueryChanges), "Email/set" => (MethodObject::Email, MethodFunction::Set), "Email/copy" => (MethodObject::Email, MethodFunction::Copy), "Email/import" => (MethodObject::Email, MethodFunction::Import), "Email/parse" => (MethodObject::Email, MethodFunction::Parse), "SearchSnippet/get" => (MethodObject::SearchSnippet, MethodFunction::Get), "Identity/get" => (MethodObject::Identity, MethodFunction::Get), "Identity/changes" => (MethodObject::Identity, MethodFunction::Changes), "Identity/set" => (MethodObject::Identity, MethodFunction::Set), "EmailSubmission/get" => (MethodObject::EmailSubmission, MethodFunction::Get), "EmailSubmission/changes" => (MethodObject::EmailSubmission, MethodFunction::Changes), "EmailSubmission/query" => (MethodObject::EmailSubmission, MethodFunction::Query), "EmailSubmission/queryChanges" => (MethodObject::EmailSubmission, MethodFunction::QueryChanges), "EmailSubmission/set" => (MethodObject::EmailSubmission, MethodFunction::Set), "VacationResponse/get" => (MethodObject::VacationResponse, MethodFunction::Get), "VacationResponse/set" => (MethodObject::VacationResponse, MethodFunction::Set), "SieveScript/get" => (MethodObject::SieveScript, MethodFunction::Get), "SieveScript/set" => (MethodObject::SieveScript, MethodFunction::Set), "SieveScript/query" => (MethodObject::SieveScript, MethodFunction::Query), "SieveScript/validate" => (MethodObject::SieveScript, MethodFunction::Validate), "Principal/get" => (MethodObject::Principal, MethodFunction::Get), "Principal/set" => (MethodObject::Principal, MethodFunction::Set), "Principal/query" => (MethodObject::Principal, MethodFunction::Query), "Principal/changes" => (MethodObject::Principal, MethodFunction::Changes), "Principal/queryChanges" => (MethodObject::Principal, MethodFunction::QueryChanges), "Principal/getAvailability" => (MethodObject::Principal, MethodFunction::GetAvailability), "Quota/get" => (MethodObject::Quota, MethodFunction::Get), "Quota/changes" => (MethodObject::Quota, MethodFunction::Changes), "Quota/query" => (MethodObject::Quota, MethodFunction::Query), "Quota/queryChanges" => (MethodObject::Quota, MethodFunction::QueryChanges), "Blob/get" => (MethodObject::Blob, MethodFunction::Get), "Blob/copy" => (MethodObject::Blob, MethodFunction::Copy), "Blob/lookup" => (MethodObject::Blob, MethodFunction::Lookup), "Blob/upload" => (MethodObject::Blob, MethodFunction::Upload), "AddressBook/get" => (MethodObject::AddressBook, MethodFunction::Get), "AddressBook/changes" => (MethodObject::AddressBook, MethodFunction::Changes), "AddressBook/set" => (MethodObject::AddressBook, MethodFunction::Set), "AddressBook/query" => (MethodObject::AddressBook, MethodFunction::Query), "ContactCard/get" => (MethodObject::ContactCard, MethodFunction::Get), "ContactCard/changes" => (MethodObject::ContactCard, MethodFunction::Changes), "ContactCard/query" => (MethodObject::ContactCard, MethodFunction::Query), "ContactCard/queryChanges" => (MethodObject::ContactCard, MethodFunction::QueryChanges), "ContactCard/set" => (MethodObject::ContactCard, MethodFunction::Set), "ContactCard/copy" => (MethodObject::ContactCard, MethodFunction::Copy), "ContactCard/parse" => (MethodObject::ContactCard, MethodFunction::Parse), "FileNode/get" => (MethodObject::FileNode, MethodFunction::Get), "FileNode/changes" => (MethodObject::FileNode, MethodFunction::Changes), "FileNode/query" => (MethodObject::FileNode, MethodFunction::Query), "FileNode/queryChanges" => (MethodObject::FileNode, MethodFunction::QueryChanges), "FileNode/set" => (MethodObject::FileNode, MethodFunction::Set), "FileNode/copy" => (MethodObject::FileNode, MethodFunction::Copy), "ShareNotification/get" => (MethodObject::ShareNotification, MethodFunction::Get), "ShareNotification/changes" => (MethodObject::ShareNotification, MethodFunction::Changes), "ShareNotification/set" => (MethodObject::ShareNotification, MethodFunction::Set), "ShareNotification/query" => (MethodObject::ShareNotification, MethodFunction::Query), "ShareNotification/queryChanges" => (MethodObject::ShareNotification, MethodFunction::QueryChanges), "Calendar/get" => (MethodObject::Calendar, MethodFunction::Get), "Calendar/changes" => (MethodObject::Calendar, MethodFunction::Changes), "Calendar/set" => (MethodObject::Calendar, MethodFunction::Set), "Calendar/query" => (MethodObject::Calendar, MethodFunction::Query), "CalendarEvent/get" => (MethodObject::CalendarEvent, MethodFunction::Get), "CalendarEvent/changes" => (MethodObject::CalendarEvent, MethodFunction::Changes), "CalendarEvent/query" => (MethodObject::CalendarEvent, MethodFunction::Query), "CalendarEvent/queryChanges" => (MethodObject::CalendarEvent, MethodFunction::QueryChanges), "CalendarEvent/set" => (MethodObject::CalendarEvent, MethodFunction::Set), "CalendarEvent/copy" => (MethodObject::CalendarEvent, MethodFunction::Copy), "CalendarEvent/parse" => (MethodObject::CalendarEvent, MethodFunction::Parse), "CalendarEventNotification/get" => (MethodObject::CalendarEventNotification, MethodFunction::Get), "CalendarEventNotification/changes" => (MethodObject::CalendarEventNotification, MethodFunction::Changes), "CalendarEventNotification/set" => (MethodObject::CalendarEventNotification, MethodFunction::Set), "CalendarEventNotification/query" => (MethodObject::CalendarEventNotification, MethodFunction::Query), "CalendarEventNotification/queryChanges" => (MethodObject::CalendarEventNotification, MethodFunction::QueryChanges), "ParticipantIdentity/get" => (MethodObject::ParticipantIdentity, MethodFunction::Get), "ParticipantIdentity/changes" => (MethodObject::ParticipantIdentity, MethodFunction::Changes), "ParticipantIdentity/set" => (MethodObject::ParticipantIdentity, MethodFunction::Set), "Core/echo" => (MethodObject::Core, MethodFunction::Echo), "MaskedEmail/get" => (MethodObject::MaskedEmail, MethodFunction::Get), "MaskedEmail/set" => (MethodObject::MaskedEmail, MethodFunction::Set), "inbuxa:DeletedAccount/get" => (MethodObject::DeletedAccount, MethodFunction::Get), "inbuxa:DeletedAccount/set" => (MethodObject::DeletedAccount, MethodFunction::Set), "inbuxa:AiLimits/get" => (MethodObject::AiLimits, MethodFunction::Get), "inbuxa:AiLimits/set" => (MethodObject::AiLimits, MethodFunction::Set), ).or_else(|| { let (obj, fnc) = s.strip_prefix("x:")?.split_once('/')?; let obj = ObjectType::parse(obj)?; let fnc = hashify::tiny_map!(fnc.as_bytes(), "get" => MethodFunction::Get, "set" => MethodFunction::Set, "query" => MethodFunction::Query, "changes" => MethodFunction::Changes, )?; // inbuxa: only masked email and undelete have /changes (fork additions) if fnc == MethodFunction::Changes && !matches!(obj, ObjectType::MaskedEmail | ObjectType::ArchivedItem) { return None; } if obj.flags() & OBJ_SINGLETON == 0 || fnc != MethodFunction::Query { (MethodObject::Registry(obj), fnc).into() } else { None } }).map(|(obj, fnc)| MethodName { obj, fnc }) } } impl Display for MethodObject { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { MethodObject::Blob => "Blob", MethodObject::EmailSubmission => "EmailSubmission", MethodObject::SearchSnippet => "SearchSnippet", MethodObject::Identity => "Identity", MethodObject::VacationResponse => "VacationResponse", MethodObject::PushSubscription => "PushSubscription", MethodObject::SieveScript => "SieveScript", MethodObject::Principal => "Principal", MethodObject::Core => "Core", MethodObject::Mailbox => "Mailbox", MethodObject::Thread => "Thread", MethodObject::Email => "Email", MethodObject::Quota => "Quota", MethodObject::AddressBook => "AddressBook", MethodObject::ContactCard => "ContactCard", MethodObject::FileNode => "FileNode", MethodObject::ParticipantIdentity => "ParticipantIdentity", MethodObject::Calendar => "Calendar", MethodObject::CalendarEvent => "CalendarEvent", MethodObject::CalendarEventNotification => "CalendarEventNotification", MethodObject::ShareNotification => "ShareNotification", MethodObject::MaskedEmail => "MaskedEmail", MethodObject::DeletedAccount => "inbuxa:DeletedAccount", MethodObject::AiLimits => "inbuxa:AiLimits", MethodObject::Registry(obj) => { f.write_str("x:")?; return f.write_str(obj.as_str()); } }) } } impl MethodFunction { pub fn as_str(&self) -> &'static str { match self { MethodFunction::Get => "get", MethodFunction::Set => "set", MethodFunction::Changes => "changes", MethodFunction::Query => "query", MethodFunction::QueryChanges => "queryChanges", MethodFunction::Copy => "copy", MethodFunction::Import => "import", MethodFunction::Parse => "parse", MethodFunction::Validate => "validate", MethodFunction::Lookup => "lookup", MethodFunction::Upload => "upload", MethodFunction::Echo => "echo", MethodFunction::GetAvailability => "getAvailability", } } } impl MethodObject { pub fn unwrap_registry(self) -> ObjectType { match self { MethodObject::Registry(obj) => obj, _ => panic!("Not a registry method object"), } } } impl<'de> serde::Deserialize<'de> for MethodName { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let value = >::deserialize(deserializer)?; MethodName::parse(value.as_ref()) .ok_or_else(|| serde::de::Error::custom(format!("Invalid method name: {:?}", value))) } } impl serde::Serialize for MethodName { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(self.as_str().as_ref()) } }