Initial commit.

main
Mauro D 2022-05-08 15:57:22 +00:00
parent 41eac1224c
commit 1680fdcd30
18 changed files with 715 additions and 0 deletions

24
Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "jmap-client"
description = "JMAP client library for Rust"
version = "0.1.0"
edition = "2018"
authors = [ "Stalwart Labs Ltd. <hello@stalw.art>"]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/stalwartlabs/jmap-client"
homepage = "https://github.com/stalwartlabs/jmap-client"
keywords = ["jmap", "email", "mime", "mail", "e-mail"]
categories = ["email"]
readme = "README.md"
[dependencies]
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"]}
reqwest = "0.11"
ece = "2.2"
#[dev-dependencies]
[profile.bench]
debug = true

27
src/blob/copy.rs Normal file
View File

@ -0,0 +1,27 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::core::set::SetError;
#[derive(Debug, Clone, Serialize)]
pub struct CopyBlobRequest {
#[serde(rename = "fromAccountId")]
from_account_id: String,
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "blobIds")]
blob_ids: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CopyBlobResponse<U> {
#[serde(rename = "fromAccountId")]
from_account_id: String,
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "copied")]
copied: Option<HashMap<String, String>>,
#[serde(rename = "notCopied")]
not_copied: Option<HashMap<String, SetError<U>>>,
}

1
src/blob/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod copy;

26
src/core/changes.rs Normal file
View File

@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
pub struct ChangesRequest {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "sinceState")]
since_state: String,
#[serde(rename = "maxChanges")]
max_changes: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ChangesResponse {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "oldState")]
old_state: String,
#[serde(rename = "newState")]
new_state: String,
#[serde(rename = "hasMoreChanges")]
has_more_changes: bool,
created: Vec<String>,
updated: Vec<String>,
destroyed: Vec<String>,
}

39
src/core/copy.rs Normal file
View File

@ -0,0 +1,39 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::set::SetError;
#[derive(Debug, Clone, Serialize)]
pub struct CopyRequest<T> {
#[serde(rename = "fromAccountId")]
from_account_id: String,
#[serde(rename = "ifFromInState")]
if_from_in_state: Option<String>,
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "ifInState")]
if_in_state: Option<String>,
#[serde(rename = "create")]
create: HashMap<String, T>,
#[serde(rename = "onSuccessDestroyOriginal")]
on_success_destroy_original: bool,
#[serde(rename = "destroyFromIfInState")]
destroy_from_if_in_state: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CopyResponse<T, U> {
#[serde(rename = "fromAccountId")]
from_account_id: String,
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "oldState")]
old_state: Option<String>,
#[serde(rename = "newState")]
new_state: String,
#[serde(rename = "created")]
created: Option<HashMap<String, T>>,
#[serde(rename = "notCreated")]
not_created: Option<HashMap<String, SetError<U>>>,
}

73
src/core/error.rs Normal file
View File

@ -0,0 +1,73 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct ProblemDetails {
#[serde(rename = "type")]
p_type: ProblemType,
status: Option<u32>,
title: Option<String>,
detail: Option<String>,
limit: Option<usize>,
}
#[derive(Debug, Deserialize)]
pub enum ProblemType {
#[serde(rename = "urn:ietf:params:jmap:error:unknownCapability")]
UnknownCapability,
#[serde(rename = "urn:ietf:params:jmap:error:notJSON")]
NotJSON,
#[serde(rename = "urn:ietf:params:jmap:error:notRequest")]
NotRequest,
#[serde(rename = "urn:ietf:params:jmap:error:limit")]
Limit,
}
#[derive(Debug, Deserialize)]
pub struct MethodError {
#[serde(rename = "type")]
p_type: MethodErrorType,
}
#[derive(Debug, Deserialize)]
pub enum MethodErrorType {
#[serde(rename = "serverUnavailable")]
ServerUnavailable,
#[serde(rename = "serverFail")]
ServerFail,
#[serde(rename = "serverPartialFail")]
ServerPartialFail,
#[serde(rename = "unknownMethod")]
UnknownMethod,
#[serde(rename = "invalidArguments")]
InvalidArguments,
#[serde(rename = "invalidResultReference")]
InvalidResultReference,
#[serde(rename = "forbidden")]
Forbidden,
#[serde(rename = "accountNotFound")]
AccountNotFound,
#[serde(rename = "accountNotSupportedByMethod")]
AccountNotSupportedByMethod,
#[serde(rename = "accountReadOnly")]
AccountReadOnly,
#[serde(rename = "requestTooLarge")]
RequestTooLarge,
#[serde(rename = "cannotCalculateChanges")]
CannotCalculateChanges,
#[serde(rename = "stateMismatch")]
StateMismatch,
#[serde(rename = "alreadyExists")]
AlreadyExists,
#[serde(rename = "fromAccountNotFound")]
FromAccountNotFound,
#[serde(rename = "fromAccountNotSupportedByMethod")]
FromAccountNotSupportedByMethod,
#[serde(rename = "anchorNotFound")]
AnchorNotFound,
#[serde(rename = "unsupportedSort")]
UnsupportedSort,
#[serde(rename = "unsupportedFilter")]
UnsupportedFilter,
#[serde(rename = "tooManyChanges")]
TooManyChanges,
}

19
src/core/get.rs Normal file
View File

@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
pub struct GetRequest<T> {
#[serde(rename = "accountId")]
account_id: String,
ids: Option<Vec<String>>,
properties: Option<Vec<T>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct GetResponse<T> {
#[serde(rename = "accountId")]
account_id: String,
state: String,
list: Vec<T>,
#[serde(rename = "notFound")]
not_found: Vec<String>,
}

10
src/core/mod.rs Normal file
View File

@ -0,0 +1,10 @@
pub mod changes;
pub mod copy;
pub mod error;
pub mod get;
pub mod query;
pub mod query_changes;
pub mod request;
pub mod response;
pub mod session;
pub mod set;

70
src/core/query.rs Normal file
View File

@ -0,0 +1,70 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
pub struct QueryRequest<T, U> {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "filter")]
filter: Option<Filter<T>>,
#[serde(rename = "sort")]
sort: Option<Vec<U>>,
#[serde(rename = "position")]
position: i32,
#[serde(rename = "anchor")]
anchor: Option<String>,
#[serde(rename = "anchorOffset")]
anchor_offset: i32,
#[serde(rename = "limit")]
limit: Option<usize>,
#[serde(rename = "calculateTotal")]
calculate_total: bool,
}
#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum Filter<T> {
FilterOperator(FilterOperator<T>),
FilterCondition(T),
}
#[derive(Debug, Clone, Serialize)]
pub struct FilterOperator<T> {
operator: Operator,
conditions: Vec<Filter<T>>,
}
#[derive(Debug, Clone, Serialize)]
pub enum Operator {
#[serde(rename = "AND")]
And,
#[serde(rename = "OR")]
Or,
#[serde(rename = "NOT")]
Not,
}
#[derive(Debug, Clone, Serialize)]
pub struct Comparator<T> {
property: T,
#[serde(rename = "isAscending")]
is_ascending: bool,
collation: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct QueryResponse {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "queryState")]
query_state: String,
#[serde(rename = "canCalculateChanges")]
can_calculate_changes: bool,
#[serde(rename = "position")]
position: i32,
#[serde(rename = "ids")]
ids: Vec<String>,
#[serde(rename = "total")]
total: Option<usize>,
#[serde(rename = "limit")]
limit: Option<usize>,
}

43
src/core/query_changes.rs Normal file
View File

@ -0,0 +1,43 @@
use serde::{Deserialize, Serialize};
use super::query::Filter;
#[derive(Debug, Clone, Serialize)]
pub struct QueryChangesRequest<T, U> {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "filter")]
filter: Option<Filter<T>>,
#[serde(rename = "sort")]
sort: Option<Vec<U>>,
#[serde(rename = "sinceQueryState")]
since_query_state: String,
#[serde(rename = "maxChanges")]
max_changes: Option<usize>,
#[serde(rename = "upToId")]
up_to_id: Option<String>,
#[serde(rename = "calculateTotal")]
calculate_total: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct QueryChangesResponse {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "oldQueryState")]
old_query_state: String,
#[serde(rename = "newQueryState")]
new_query_state: String,
#[serde(rename = "total")]
total: Option<usize>,
#[serde(rename = "removed")]
removed: Vec<String>,
#[serde(rename = "added")]
added: Vec<AddedItem>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AddedItem {
id: String,
index: usize,
}

26
src/core/request.rs Normal file
View File

@ -0,0 +1,26 @@
use std::collections::HashMap;
use serde::Serialize;
use crate::{Method, URI};
#[derive(Debug, Clone, Serialize)]
pub struct Request {
using: Vec<URI>,
#[serde(rename = "methodCalls")]
method_calls: Vec<(Method, Arguments, String)>,
#[serde(rename = "createdIds")]
#[serde(skip_serializing_if = "Option::is_none")]
created_ids: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ResultReference {
#[serde(rename = "resultOf")]
result_of: String,
name: String,
path: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct Arguments {}

18
src/core/response.rs Normal file
View File

@ -0,0 +1,18 @@
use std::collections::HashMap;
use serde::Deserialize;
use crate::Method;
#[derive(Debug, Clone, Deserialize)]
pub struct Request {
#[serde(rename = "methodResponses")]
method_calls: Vec<(Method, Result, String)>,
#[serde(rename = "createdIds")]
created_ids: Option<HashMap<String, String>>,
#[serde(rename = "sessionState")]
session_state: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Result {}

94
src/core/session.rs Normal file
View File

@ -0,0 +1,94 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct Session {
#[serde(rename = "capabilities")]
capabilities: HashMap<String, Capabilities>,
#[serde(rename = "accounts")]
accounts: HashMap<String, Account>,
#[serde(rename = "primaryAccounts")]
primary_accounts: HashMap<String, String>,
#[serde(rename = "username")]
username: String,
#[serde(rename = "apiUrl")]
api_url: String,
#[serde(rename = "downloadUrl")]
download_url: String,
#[serde(rename = "uploadUrl")]
upload_url: String,
#[serde(rename = "eventSourceUrl")]
event_source_url: String,
#[serde(rename = "state")]
state: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Account {
#[serde(rename = "name")]
name: String,
#[serde(rename = "isPersonal")]
is_personal: bool,
#[serde(rename = "isReadOnly")]
is_read_only: bool,
#[serde(rename = "accountCapabilities")]
account_capabilities: HashMap<String, Capabilities>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
enum Capabilities {
Core(CoreCapabilities),
Mail(MailCapabilities),
Submission(SubmissionCapabilities),
EmptyCapabilities(EmptyCapabilities),
Other(serde_json::Value),
}
#[derive(Debug, Clone, Deserialize)]
pub struct CoreCapabilities {
#[serde(rename = "maxSizeUpload")]
max_size_upload: usize,
#[serde(rename = "maxConcurrentUpload")]
max_concurrent_upload: usize,
#[serde(rename = "maxSizeRequest")]
max_size_request: usize,
#[serde(rename = "maxConcurrentRequests")]
max_concurrent_requests: usize,
#[serde(rename = "maxCallsInRequest")]
max_calls_in_request: usize,
#[serde(rename = "maxObjectsInGet")]
max_objects_in_get: usize,
#[serde(rename = "maxObjectsInSet")]
max_objects_in_set: usize,
#[serde(rename = "collationAlgorithms")]
collation_algorithms: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MailCapabilities {
#[serde(rename = "maxMailboxesPerEmail")]
max_mailboxes_per_email: Option<usize>,
#[serde(rename = "maxMailboxDepth")]
max_mailbox_depth: usize,
#[serde(rename = "maxSizeMailboxName")]
max_size_mailbox_name: usize,
#[serde(rename = "maxSizeAttachmentsPerEmail")]
max_size_attachments_per_email: usize,
#[serde(rename = "emailQuerySortOptions")]
email_query_sort_options: Vec<String>,
#[serde(rename = "mayCreateTopLevelMailbox")]
may_create_top_level_mailbox: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SubmissionCapabilities {
#[serde(rename = "maxDelayedSend")]
max_delayed_send: usize,
#[serde(rename = "submissionExtensions")]
submission_extensions: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct EmptyCapabilities {}

69
src/core/set.rs Normal file
View File

@ -0,0 +1,69 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::hash::Hash;
#[derive(Debug, Clone, Serialize)]
pub struct SetRequest<T, U>
where
U: Eq + Hash,
{
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "ifInState")]
if_in_state: Option<String>,
create: Option<HashMap<String, T>>,
update: Option<HashMap<String, HashMap<U, serde_json::Value>>>,
destroy: Option<Vec<String>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SetResponse<T, U> {
#[serde(rename = "accountId")]
account_id: String,
#[serde(rename = "oldState")]
old_state: Option<String>,
#[serde(rename = "newState")]
new_state: String,
#[serde(rename = "created")]
created: Option<HashMap<String, T>>,
#[serde(rename = "updated")]
updated: Option<HashMap<String, Option<T>>>,
#[serde(rename = "destroyed")]
destroyed: Option<Vec<String>>,
#[serde(rename = "notCreated")]
not_created: Option<HashMap<String, SetError<U>>>,
#[serde(rename = "notUpdated")]
not_updated: Option<HashMap<String, SetError<U>>>,
#[serde(rename = "notDestroyed")]
not_destroyed: Option<HashMap<String, SetError<U>>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SetError<U> {
#[serde(rename = "type")]
type_: SetErrorType,
description: Option<String>,
properties: Option<Vec<U>>,
}
#[derive(Debug, Clone, Deserialize)]
pub enum SetErrorType {
#[serde(rename = "forbidden")]
Forbidden,
#[serde(rename = "overQuota")]
OverQuota,
#[serde(rename = "tooLarge")]
TooLarge,
#[serde(rename = "rateLimit")]
RateLimit,
#[serde(rename = "notFound")]
NotFound,
#[serde(rename = "invalidPatch")]
InvalidPatch,
#[serde(rename = "willDestroy")]
WillDestroy,
#[serde(rename = "invalidProperties")]
InvalidProperties,
#[serde(rename = "singleton")]
Singleton,
}

114
src/lib.rs Normal file
View File

@ -0,0 +1,114 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
pub mod blob;
pub mod core;
pub mod push_subscription;
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub enum URI {
#[serde(rename = "urn:ietf:params:jmap:core")]
Core,
#[serde(rename = "urn:ietf:params:jmap:mail")]
Mail,
#[serde(rename = "urn:ietf:params:jmap:submission")]
Submission,
#[serde(rename = "urn:ietf:params:jmap:vacationresponse")]
VacationResponse,
#[serde(rename = "urn:ietf:params:jmap:contacts")]
Contacts,
#[serde(rename = "urn:ietf:params:jmap:calendars")]
Calendars,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
enum Method {
#[serde(rename = "Core/echo")]
Echo,
#[serde(rename = "Blob/copy")]
CopyBlob,
#[serde(rename = "PushSubscription/get")]
GetPushSubscription,
#[serde(rename = "PushSubscription/set")]
SetPushSubscription,
#[serde(rename = "Mailbox/get")]
GetMailbox,
#[serde(rename = "Mailbox/changes")]
ChangesMailbox,
#[serde(rename = "Mailbox/query")]
QueryMailbox,
#[serde(rename = "Mailbox/queryChanges")]
QueryChangesMailbox,
#[serde(rename = "Mailbox/set")]
SetMailbox,
#[serde(rename = "Thread/get")]
GetThread,
#[serde(rename = "Thread/changes")]
ChangesThread,
#[serde(rename = "Email/get")]
GetEmail,
#[serde(rename = "Email/changes")]
ChangesEmail,
#[serde(rename = "Email/query")]
QueryEmail,
#[serde(rename = "Email/queryChanges")]
QueryChangesEmail,
#[serde(rename = "Email/set")]
SetEmail,
#[serde(rename = "Email/copy")]
CopyEmail,
#[serde(rename = "Email/import")]
ImportEmail,
#[serde(rename = "Email/parse")]
ParseEmail,
#[serde(rename = "SearchSnippet/get")]
GetSearchSnippet,
#[serde(rename = "Identity/get")]
GetIdentity,
#[serde(rename = "Identity/changes")]
ChangesIdentity,
#[serde(rename = "Identity/set")]
SetIdentity,
#[serde(rename = "EmailSubmission/get")]
GetEmailSubmission,
#[serde(rename = "EmailSubmission/changes")]
ChangesEmailSubmission,
#[serde(rename = "EmailSubmission/query")]
QueryEmailSubmission,
#[serde(rename = "EmailSubmission/queryChanges")]
QueryChangesEmailSubmission,
#[serde(rename = "EmailSubmission/set")]
SetEmailSubmission,
#[serde(rename = "VacationResponse/get")]
GetVacationResponse,
#[serde(rename = "VacationResponse/set")]
SetVacationResponse,
#[serde(rename = "error")]
Error,
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Clone)]
pub enum Object {
Core,
Mailbox,
Thread,
Email,
SearchSnippet,
Identity,
EmailSubmission,
VacationResponse,
PushSubscription,
}
#[derive(Deserialize)]
pub enum StateChangeType {
StateChange,
}
#[derive(Deserialize)]
pub struct StateChange {
#[serde(rename(serialize = "@type"))]
pub type_: StateChangeType,
pub changed: HashMap<String, HashMap<Object, String>>,
}

View File

View File

@ -0,0 +1,62 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::Object;
pub mod get;
pub mod set;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PushSubscription {
#[serde(rename = "id")]
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(rename = "deviceClientId")]
#[serde(skip_serializing_if = "Option::is_none")]
device_client_id: Option<String>,
#[serde(rename = "url")]
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(rename = "keys")]
#[serde(skip_serializing_if = "Option::is_none")]
keys: Option<Keys>,
#[serde(rename = "verificationCode")]
#[serde(skip_serializing_if = "Option::is_none")]
verification_code: Option<String>,
#[serde(rename = "expires")]
#[serde(skip_serializing_if = "Option::is_none")]
expires: Option<DateTime<Utc>>,
#[serde(rename = "types")]
#[serde(skip_serializing_if = "Option::is_none")]
types: Option<Vec<Object>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PushSubscriptionProperty {
#[serde(rename = "id")]
Id,
#[serde(rename = "deviceClientId")]
DeviceClientId,
#[serde(rename = "url")]
Url,
#[serde(rename = "keys")]
Keys,
#[serde(rename = "verificationCode")]
VerificationCode,
#[serde(rename = "expires")]
Expires,
#[serde(rename = "types")]
Types,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Keys {
p256dh: String,
auth: String,
}

View File