Robin Jarry: 1 treewide: strip RFC comments 45 files changed, 117 insertions(+), 1697 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~rockorager/go-jmap-devel/patches/46374/mbox | git am -3Learn more about email & git
RFC text is subject to a specific license which does not allow modification of content. Remove all comments that are quoted from RFCs and replace them with links to the RFCs themselves. Signed-off-by: Robin Jarry <robin@jarry.cc> --- core/blob/copy.go | 12 +- core/push/subscription/get.go | 4 +- core/push/subscription/set.go | 2 + core/push/subscription/subscription.go | 26 +--- mail/email/changes.go | 17 +-- mail/email/copy.go | 39 +++--- mail/email/email.go | 142 +------------------- mail/email/filter.go | 63 +-------- mail/email/get.go | 67 +--------- mail/email/import.go | 33 +---- mail/email/parse.go | 58 +------- mail/email/query.go | 105 +-------------- mail/email/querychanges.go | 66 +--------- mail/email/set.go | 154 +--------------------- mail/email/sort.go | 41 +----- mail/emailsubmission/changes.go | 28 +--- mail/emailsubmission/emailsubmission.go | 34 +---- mail/emailsubmission/filter.go | 17 +-- mail/emailsubmission/get.go | 36 +---- mail/emailsubmission/query.go | 101 +------------- mail/emailsubmission/querychanges.go | 58 +------- mail/emailsubmission/set.go | 95 +------------ mail/emailsubmission/sort.go | 35 ----- mail/identity/changes.go | 28 +--- mail/identity/get.go | 15 +-- mail/identity/identity.go | 24 +--- mail/identity/set.go | 83 +----------- mail/mailbox/changes.go | 19 +-- mail/mailbox/filter.go | 19 +-- mail/mailbox/get.go | 13 +- mail/mailbox/mailbox.go | 31 +---- mail/mailbox/query.go | 26 +--- mail/mailbox/querychanges.go | 18 +-- mail/mailbox/set.go | 23 +--- mail/mdn/mdn.go | 35 +---- mail/mdn/parse.go | 9 +- mail/mdn/send.go | 11 +- mail/searchsnippet/get.go | 15 +-- mail/searchsnippet/searchsnippet.go | 26 +--- mail/thread/changes.go | 27 +--- mail/thread/get.go | 35 +---- mail/thread/thread.go | 13 +- mail/vacationresponse/get.go | 13 +- mail/vacationresponse/set.go | 83 +----------- mail/vacationresponse/vacationresponse.go | 15 +-- 45 files changed, 117 insertions(+), 1697 deletions(-) diff --git a/core/blob/copy.go b/core/blob/copy.go index 06216d7841c5..5a380ff205ff 100644 --- a/core/blob/copy.go +++ b/core/blob/copy.go @@ -5,15 +5,13 @@ import ( "git.sr.ht/~rockorager/go-jmap/core" ) -// Copy copies data between accounts +// Copy a binary blob from one account to another +// https://www.rfc-editor.org/rfc/rfc8620.html#section-6.3 type Copy struct { - // The ID of the account to copy blobs from FromAccount jmap.ID `json:"fromAccountId,omitempty"` - // The ID of the account to copy blobs to Account jmap.ID `json:"accountId,omitempty"` - // A list of IDs of blobs to copy IDs []jmap.ID `json:"blobIds,omitempty"` } @@ -22,18 +20,12 @@ func (m *Copy) Name() string { return "Blob/copy" } func (m *Copy) Requires() []jmap.URI { return []jmap.URI{core.URI} } type CopyResponse struct { - // The ID of the account blobs were copied from FromAccount jmap.ID `json:"fromAccountId,omitempty"` - // The ID of the account blobs were copied to Account jmap.ID `json:"accountId,omitempty"` - // A map of the blobId in the fromAccount to the ID of the blob in the - // account it was copied to. Map is null if no blobs were copied Copied map[jmap.ID]jmap.ID `json:"blobIds,omitempty"` - // A map of blobId to a SetError object for each blob that failed to be - // copied, or null if none. NotCopied map[jmap.ID]*jmap.SetError `json:"notCopied,omitempty"` } diff --git a/core/push/subscription/get.go b/core/push/subscription/get.go index 0f8696d8c7bc..84c0185bf59f 100644 --- a/core/push/subscription/get.go +++ b/core/push/subscription/get.go @@ -5,7 +5,8 @@ import ( "git.sr.ht/~rockorager/go-jmap/core" ) -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. +// Get push subscription details +// https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.1 type Get struct { IDs []jmap.ID `json:"ids,omitempty"` Properties []string `json:"properties,omitempty"` @@ -15,7 +16,6 @@ func (m *Get) Name() string { return "PushSubscription/get" } func (m *Get) Requires() []jmap.URI { return []jmap.URI{core.URI} } -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. type GetResponse struct { List []*PushSubscription `json:"list,omitempty"` NotFound []jmap.ID `json:"notFound,omitempty"` diff --git a/core/push/subscription/set.go b/core/push/subscription/set.go index 4da0be8815fe..02ad7763e497 100644 --- a/core/push/subscription/set.go +++ b/core/push/subscription/set.go @@ -5,6 +5,8 @@ import ( "git.sr.ht/~rockorager/go-jmap/core" ) +// Modify push subscription details +// https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2 type Set struct { Create map[jmap.ID]*PushSubscription `json:"create,omitempty"` Update map[jmap.ID]*jmap.Patch `json:"update,omitempty"` diff --git a/core/push/subscription/subscription.go b/core/push/subscription/subscription.go index bf7f16381870..5c8de6dedc99 100644 --- a/core/push/subscription/subscription.go +++ b/core/push/subscription/subscription.go @@ -11,43 +11,21 @@ func init() { jmap.RegisterMethod("PushSubscription/set", newSetResponse) } -// A PushSubscription object +// Server side push notification +// https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2 type PushSubscription struct { - // The ID of the push subscription - // - // immutable;server-set ID jmap.ID `json:"id,omitempty"` - // An ID that uniquely identifies the client + device the subscription - // is running on - // - // immutable DeviceClientID string `json:"deviceClientId,omitempty"` - // An absolute URL where the JMAP server will POST the data for the push - // message. This must start with "https://" - // - // immutable URL string `json:"url,omitempty"` - // Client-generated encryption keys. If specified, the server will - // encrypt the push data Keys *Key `json:"keys,omitempty"` - // This must be null or omitted when the subscription is created. The - // JMAP server will generate a code and send it in a push message. The - // client must then update this field with that code VerificationCode string `json:"verificationCode,omitempty"` - // The time this subscription expires, if specified. If not specified, - // the subscription does not expire, however the server may specify a - // time - // - // Must be in UTC Expires *time.Time `json:"expires,omitempty"` - // A list of type changes the client is subscribing to, using the same - // keys as a TypeState object Types []string `json:"types,omitempty"` } diff --git a/mail/email/changes.go b/mail/email/changes.go index 1cefbd63064a..894b21c0b430 100644 --- a/mail/email/changes.go +++ b/mail/email/changes.go @@ -5,15 +5,13 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard "/changes" method as described in [RFC8620], Section 5.2. +// Get changes to emails on the whole account since a given state +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.3 type Changes struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The current state of the client SinceState string `json:"sinceState,omitempty"` - // The maximum number of ids to return in the response MaxChanges uint64 `json:"maxChanges,omitempty"` } @@ -21,22 +19,19 @@ func (m *Changes) Name() string { return "Email/changes" } func (m *Changes) Requires() []jmap.URI { return []jmap.URI{mail.URI} } -// This is a standard "/changes" method as described in [RFC8620], Section 5.2. type ChangesResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // This is the sinceState argument echoed back OldState string `json:"oldState,omitempty"` - // The state the client will be in after applying the Changes NewState string `json:"newState,omitempty"` - // If true, not all changes were returned in this response HasMoreChanges bool `json:"hasMoreChanges,omitempty"` - Created []jmap.ID `json:"created,omitempty"` - Updated []jmap.ID `json:"updated,omitempty"` + Created []jmap.ID `json:"created,omitempty"` + + Updated []jmap.ID `json:"updated,omitempty"` + Destroyed []jmap.ID `json:"destroyed,omitempty"` } diff --git a/mail/email/copy.go b/mail/email/copy.go index 9421a48cc7c3..6ca7396f5a51 100644 --- a/mail/email/copy.go +++ b/mail/email/copy.go @@ -5,19 +5,22 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard "/copy" method as described in [RFC8620], Section 5.4, +// Copy messages from one account to another +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.7 type Copy struct { - // The id of the account to copy records from. - FromAccount jmap.ID `json:"fromAccountId,omitempty"` - IfFromInState string `json:"ifFromInState,omitempty"` + FromAccount jmap.ID `json:"fromAccountId,omitempty"` - // The id of the account to copy records to. This MUST be different to - // the fromAccountId. - Account jmap.ID `json:"accountId,omitempty"` - IfInState string `json:"ifInState,omitempty"` - Create map[jmap.ID]*Email `json:"create,omitempty"` - OnSuccessDestroyOriginal bool `json:"onSuccessDestroyOriginal,omitempty"` - DestroyFromIfInState string `json:"destroyFromIfInState,omitempty"` + IfFromInState string `json:"ifFromInState,omitempty"` + + Account jmap.ID `json:"accountId,omitempty"` + + IfInState string `json:"ifInState,omitempty"` + + Create map[jmap.ID]*Email `json:"create,omitempty"` + + OnSuccessDestroyOriginal bool `json:"onSuccessDestroyOriginal,omitempty"` + + DestroyFromIfInState string `json:"destroyFromIfInState,omitempty"` } func (m *Copy) Name() string { return "Email/copy" } @@ -25,14 +28,16 @@ func (m *Copy) Name() string { return "Email/copy" } func (m *Copy) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type CopyResponse struct { - // The id of the account records were copied from. FromAccount jmap.ID `json:"fromAccountId,omitempty"` - // The id of the account records were copied to. - Account jmap.ID `json:"accountId,omitempty"` - OldState string `json:"oldState,omitempty"` - NewState string `json:"newState,omitempty"` - Created map[jmap.ID]*Email `json:"created,omitempty"` + Account jmap.ID `json:"accountId,omitempty"` + + OldState string `json:"oldState,omitempty"` + + NewState string `json:"newState,omitempty"` + + Created map[jmap.ID]*Email `json:"created,omitempty"` + NotCreated map[jmap.ID]*jmap.SetError `json:"notCreated,omitempty"` } diff --git a/mail/email/email.go b/mail/email/email.go index b8735655456b..ca731e562531 100644 --- a/mail/email/email.go +++ b/mail/email/email.go @@ -19,246 +19,112 @@ func init() { jmap.RegisterMethod("Email/parse", newParseResponse) } +// Representation of an RFC5322 message +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4 type Email struct { - // The ID of the Email. Note: this is _not_ the Message-ID - // - // immutable;server-set ID jmap.ID `json:"id,omitempty"` - // The ID of the raw RFC5322 message - // - // immutable;server-set BlobID jmap.ID `json:"blobId,omitempty"` - // The id of the Thread to which this Email belongs. - // - // immutable;server-set ThreadID jmap.ID `json:"threadId,omitempty"` - // The set of Mailbox ids this Email belongs to. An Email in the mail - // store MUST belong to one or more Mailboxes at all times (until it - // is destroyed). MailboxIDs map[jmap.ID]bool `json:"mailboxIds,omitempty"` - // A set of keywords that apply to the Email. Each key must have an - // associated value of "true" Keywords map[string]bool `json:"keywords,omitempty"` - // The size, in bytes, of the message - // - // immutable;server-set Size uint64 `json:"size,omitempty"` - // The date the Email was received. Equivalent to INTERNAL_DATE in IMAP - // - // immutable ReceivedAt *time.Time `json:"receivedAt,omitempty"` - // This is a list of all header fields, in the same order they appear in - // the message. - // - // immutable Headers []*Header `json:"headers,omitempty"` - // The Message-ID of the email. For conforming messages, this will be - // len() == 1 - // - // immutable MessageID []string `json:"messageId,omitempty"` - // immutable InReplyTo []string `json:"inReplyTo,omitempty"` - // immutable References []string `json:"references,omitempty"` - // immutable Sender []*mail.Address `json:"sender,omitempty"` - // immutable From []*mail.Address `json:"from,omitempty"` - // immutable To []*mail.Address `json:"to,omitempty"` - // immutable CC []*mail.Address `json:"cc,omitempty"` - // immutable BCC []*mail.Address `json:"bcc,omitempty"` - // immutable ReplyTo []*mail.Address `json:"replyTo,omitempty"` - // immutable Subject string `json:"subject,omitempty"` - // SentAt is the Date header value - // - // immutable SentAt *time.Time `json:"sentAt,omitempty"` - // This is the full MIME structure of the message body, without - // recursing into message/rfc822 or message/global parts. - // - // immutable BodyStructure *BodyPart `json:"bodyStructure,omitempty"` - // This is a map of partId to an EmailBodyValue object for none, some, - // or all text/* parts. Which parts are included and whether the value - // is truncated is determined by various arguments to Email/get and - // Email/parse. - // - // immutable BodyValues map[string]*BodyValue `json:"bodyValues,omitempty"` - // A list of text/plain, text/html, image/*, audio/*, and/or video/* - // parts to display (sequentially) as the message body, with a - // preference for text/plain when alternative versions are available. - // - // immutable TextBody []*BodyPart `json:"textBody,omitempty"` - // A list of text/plain, text/html, image/*, audio/*, and/or video/* - // parts to display (sequentially) as the message body, with a - // preference for text/html when alternative versions are available. - // - // immutable HTMLBody []*BodyPart `json:"htmlBody,omitempty"` - // A list, traversing depth-first, of all parts in bodyStructure that - // satisfy either of the following conditions: - // - // not of type multipart/* and not included in textBody or htmlBody - // - // of type image/*, audio/*, or video/* and not in both textBody - // and htmlBody - // - // immutable Attachments []*BodyPart `json:"attachments,omitempty"` - // immutable;server-set HasAttachment bool `json:"hasAttachment,omitempty"` - // A plaintext fragment of the message body. // This MUST NOT be more - // than 256 characters in length. - // - // immutable;server-set Preview string `json:"preview,omitempty"` - // If empty, there is no S/MIME signature. Otherwise will be one of the - // following strings - // - "unknown" - Can be returned for OpenPGP signed messages - // - "signed" - S/MIME signed but not yet verified - // - "signed/verified" - Signed and verified per RFC8551 and RFC8550 - // - "signed/failed" - // - "encrypted+signed/verified" - // - "encrypted+signed/failed" - // - // server-set SMIMEStatus string `json:"smimeStatus,omitempty"` - // If empty, there is no S/MIME signature. Otherwise will be one of the - // following strings, and represents the status at time of delivery - // - "unknown" - Can be returned for OpenPGP signed messages - // - "signed" - S/MIME signed but not yet verified - // - "signed/verified" - Signed and verified per RFC8551 and RFC8550 - // - "signed/failed" - // - "encrypted+signed/verified" - // - "encrypted+signed/failed" - // - // server-set SMIMEStatusAtDelivery string `json:"smimeStatusAtDelivery,omitempty"` - // If empty, no errors or no signature. Otherwise, this will contain any - // errors during verification of SMIME properties - // - // server-set SMIMEErrors []string `json:"smimeErrors,omitempty"` - // If empty, no signature or not verified. Otherwise, this is the time - // the signature was most recently verified - // - // server-set SMIMEVerifiedAt *time.Time `json:"smimeVerifiedAt,omitempty"` } type AddressGroup struct { - // The display-name of the group - Name string `json:"name,omitempty"` + Name string `json:"name,omitempty"` + Addresses []*mail.Address `json:"addresses,omitempty"` } type Header struct { - // The header field name, with the same capitalization that it has in - // the message. Name string `json:"name,omitempty"` - // The header field value in Raw form. Value string `json:"value,omitempty"` } type BodyPart struct { - // Identifies this part uniquely within the Email. This is scoped to - // the emailId and has no meaning outside of the JMAP Email object - // - // Multipart messages do not have a PartID PartID string `json:"partId,omitempty"` - // The Blob ID representing this Part BlobID jmap.ID `json:"blobId,omitempty"` - // The number of bytes the user would download Size uint64 `json:"size,omitempty"` - // This is a list of all header fields in the part, in the order they - // appear in the message. The values are in Raw form. Headers []*Header `json:"headers,omitempty"` - // The filename associated with this Part, if given Name string `json:"name,omitempty"` - // The value of the Content-Type header field of the part, if present; - // otherwise, the implicit type as per the MIME standard (text/plain or - // message/rfc822 if inside a multipart/digest) Type string `json:"type,omitempty"` - // The value of the charset parameter of the Content-Type header - // field, if present, or null if the header field is present but not - // of type text/*. If there is no Content-Type header field, or it - // exists and is of type text/* but has no charset parameter, this is - // the implicit charset as per the MIME standard: us-ascii. Charset string `json:"charset,omitempty"` - // The value of the Content-Disposition header field of the part, if - // present; otherwise, it’s null. CFWS is removed and any parameters - // are stripped. Disposition string `json:"disposition,omitempty"` - // The value of the Content-Id header field of the part, if present; - // otherwise it’s null. CID string `json:"cid,omitempty"` - // The list of language tags, as defined in RFC3282, in the - // Content-Language header field of the part, if present. Language []string `json:"language,omitempty"` - // The URI, as defined in RFC2557, in the Content-Location header field - // of the part, if present. Location string `json:"location,omitempty"` - // If the type is multipart/*, this contains the body parts of each - // child. SubParts []*BodyPart `json:"subParts,omitempty"` } type BodyValue struct { - // The value of the BodyValue Value string `json:"value,omitempty"` - // True if there was an encoding problem IsEncodingProblem bool `json:"isEncodingProblem,omitempty"` - // This is true if the value has been truncated IsTruncated bool `json:"isTruncated"` } diff --git a/mail/email/filter.go b/mail/email/filter.go index 288985d30215..5c6dc146a9b8 100644 --- a/mail/email/filter.go +++ b/mail/email/filter.go @@ -12,113 +12,60 @@ type Filter interface { } type FilterOperator struct { - Operator jmap.Operator `json:"operator,omitempty"` - Conditions []Filter `json:"conditions,omitempty"` + Operator jmap.Operator `json:"operator,omitempty"` + + Conditions []Filter `json:"conditions,omitempty"` } func (fo *FilterOperator) implementsFilter() {} -// EmailFilterCondition is an interface that represents FilterCondition -// objects. A filter condition object can be either a named struct, ie -// EmailFilterConditionName, or an EmailFilter itself. EmailFilters can -// be used to create complex filtering +// Email query condition that can be compounded with FilterOperator +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.4.1 type FilterCondition struct { - // A Mailbox id. An Email must be in this Mailbox to match the condition. InMailbox jmap.ID `json:"inMailbox,omitempty"` - // A list of Mailbox ids. An Email must be in at least one Mailbox not in this - // list to match the condition. This is to allow messages solely in trash/spam - // to be easily excluded from a search. InMailboxOtherThan []jmap.ID `json:"inMailboxOtherThan,omitempty"` - // The "receivedAt" date-time of the Email must be before this date- time to - // match the condition. Before *time.Time `json:"before,omitempty"` - // The "receivedAt" date-time of the Email must be the same or after this - // date-time to match the condition. After *time.Time `json:"after,omitempty"` - // The "size" property of the Email must be equal to or greater than this - // number to match the condition. MinSize uint64 `json:"minSize,omitempty"` - // The "size" property of the Email must be less than this number to match the - // condition. MaxSize uint64 `json:"maxSize,omitempty"` - // All Emails (including this one) in the same Thread as this Email must have - // the given keyword to match the condition. AllInThreadHaveKeyword string `json:"allInThreadHaveKeyword,omitempty"` - // At least one Email (possibly this one) in the same Thread as this Email must - // have the given keyword to match the condition. SomeInThreadHaveKeyword string `json:"someInThreadHaveKeyword,omitempty"` - // All Emails (including this one) in the same Thread as this Email must *not* - // have the given keyword to match the condition. NoneInThreadHaveKeyword string `json:"noneInThreadHaveKeyword,omitempty"` - // This Email must have the given keyword to match the condition. HasKeyword string `json:"hasKeyword,omitempty"` - // This Email must not have the given keyword to match the condition. NotKeyword string `json:"notKeyword,omitempty"` - // The "hasAttachment" property of the Email must be identical to the value - // given to match the condition. HasAttachment bool `json:"hasAttachment,omitempty"` - // Looks for the text in Emails. The server MUST look up text in the From, To, - // Cc, Bcc, and Subject header fields of the message and SHOULD look inside any - // "text/*" or other body parts that may be converted to text by the server. - // The server MAY extend the search to any additional textual property. Text string `json:"text,omitempty"` - // Looks for the text in the From header field of the message. From string `json:"from,omitempty"` - // Looks for the text in the To header field of the message. To string `json:"to,omitempty"` - // Looks for the text in the Cc header field of the message. Cc string `json:"cc,omitempty"` - // Looks for the text in the Bcc header field of the message. Bcc string `json:"bcc,omitempty"` - // Looks for the text in the Subject header field of the message. Subject string `json:"subject,omitempty"` - // Looks for the text in one of the body parts of the message. The server MAY - // exclude MIME body parts with content media types other than "text/*" and - // "message/*" from consideration in search matching. Care should be taken to - // match based on the text content actually presented to an end user by viewers - // for that media type or otherwise identified as appropriate for search - // indexing. Matching document metadata uninteresting to an end user (e.g., - // markup tag and attribute names) is undesirable. Body string `json:"body,omitempty"` - // The array MUST contain either one or two elements. The first element is the - // name of the header field to match against. The second (optional) element is - // the text to look for in the header field value. If not supplied, the - // message matches simply if it has a header field of the given name. Header []string `json:"header,omitempty"` - // When true, only messages where smimeStatus is not null match - // - // Requires server to support urn:ietf:jmap:smimeverify HasSMIME bool `json:"hasSmime,omitempty"` - // When true, only messages with successfully verified SMIME match - // - // Requires server to support urn:ietf:jmap:smimeverify HasVerifiedSMIME bool `json:"hasVerifiedSmime,omitempty"` - // When true, only messages with successfully verified SMIME at the time - // of delivery match - // - // Requires server to support urn:ietf:jmap:smimeverify HasVerifiedSMIMEAtDelivery bool `json:"hasVerifiedSmimeAtDelivery,omitempty"` } diff --git a/mail/email/get.go b/mail/email/get.go index 41418e22e1ae..9aaad53057d5 100644 --- a/mail/email/get.go +++ b/mail/email/get.go @@ -5,68 +5,27 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard get request -// -// If the standard "properties" argument is omitted or null, the following -// default MUST be used instead of "all" properties: -// -// [ "id", "blobId", "threadId", "mailboxIds", "keywords", "size", -// "receivedAt", "messageId", "inReplyTo", "references", "sender", "from", -// "to", "cc", "bcc", "replyTo", "subject", "sentAt", "hasAttachment", -// "preview", "bodyValues", "textBody", "htmlBody", "attachments" ] +// Get email details +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.2 type Get struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The ids of the Foo objects to return. If null, then all records of - // the data type are returned, if this is supported for that data type - // and the number of records does not exceed the maxObjectsInGet limit. IDs []jmap.ID `json:"ids,omitempty"` - // If supplied, only the properties listed in the array are returned - // for each Foo object. If null, all properties of the object are - // returned. The id property of the object is always returned, even if - // not explicitly requested. If an invalid property is requested, the - // call MUST be rejected with an invalidArguments error. Properties []string `json:"properties,omitempty"` - // A list of properties to fetch for each EmailBodyPart returned. If - // omitted, this defaults to: - // - // [ "partId", "blobId", "size", "name", "type", "charset", - // "disposition", "cid", "language", "location" ] BodyProperties []string `json:"bodyProperties,omitempty"` - // If true, the "bodyValues" property includes any "text/*" part in - // the "textBody" property. FetchTextBodyValues bool `json:"fetchTextBodyValues,omitempty"` - // If true, the "bodyValues" property includes any "text/*" part in - // the "htmlBody" property. FetchHTMLBodyValues bool `json:"fetchHTMLBodyValues,omitempty"` - // If true, the "bodyValues" property includes any "text/*" part in - // the "bodyStructure" property. FetchAllBodyValues bool `json:"fetchAllBodyValues,omitempty"` - // If greater than zero, the "value" property of any EmailBodyValue - // object returned in "bodyValues" MUST be truncated if necessary so - // it does not exceed this number of octets in size. If 0 (the - // default), no truncation occurs. - // - // The server MUST ensure the truncation results in valid UTF-8 and - // does not occur mid-codepoint. If the part is of type "text/html", - // the server SHOULD NOT truncate inside an HTML tag, e.g., in the - // middle of "<a href="https://example.com">". There is no - // requirement for the truncated form to be a balanced tree or valid - // HTML (indeed, the original source may well be neither of these - // things). MaxBodyValueBytes uint64 `json:"maxBodyValueBytes,omitempty"` - // Use IDs from a previous call ReferenceIDs *jmap.ResultReference `json:"#ids,omitempty"` - // Use IDs from a previous call ReferenceProperties *jmap.ResultReference `json:"#properties,omitempty"` } @@ -74,35 +33,13 @@ func (m *Get) Name() string { return "Email/get" } func (m *Get) Requires() []jmap.URI { return []jmap.URI{mail.URI} } -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. type GetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // A (preferably short) string representing the state on the server for - // all the data of this type in the account (not just the objects - // returned in this call). If the data changes, this string MUST - // change. If the Foo data is unchanged, servers SHOULD return the same - // state string on subsequent requests for this data type. - // - // When a client receives a response with a different state string to a - // previous call, it MUST either throw away all currently cached - // objects for the type or call Foo/changes to get the exact changes. State string `json:"state,omitempty"` - // An array of the Foo objects requested. This is the empty array - // if no objects were found or if the ids argument passed in was also - // an empty array. The results MAY be in a different order to the ids - // in the request arguments. If an identical id is included more than - // once in the request, the server MUST only include it once in either - // the list or the notFound argument of the response. - // - // Each specification must define it's own List property List []*Email `json:"list,omitempty"` - // This array contains the ids passed to the method for records that do - // not exist. The array is empty if all requested ids were found or if - // the ids argument passed in was either null or an empty array. NotFound []jmap.ID `json:"notFound,omitempty"` } diff --git a/mail/email/import.go b/mail/email/import.go index eba8ebfd811f..8186a19cc46c 100644 --- a/mail/email/import.go +++ b/mail/email/import.go @@ -7,22 +7,13 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// The "Email/import" method adds messages [RFC5322] to the set of Emails in an -// account. The server MUST support messages with Email Address -// Internationalization (EAI) headers [RFC6532]. The messages must first be -// uploaded as blobs using the standard upload mechanism. +// Import email from binary blobs +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.8 type Import struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // This is a state string as returned by the Foo/get method - // (representing the state of all objects of this type in the account). - // If supplied, the string must match the current state; otherwise, the - // method will be aborted and a stateMismatch error returned. If null, - // any changes will be applied to the current state. IfInState string `json:"ifInState,omitempty"` - // A map of creation id (client specified) to EmailImport objects. Emails map[string]*EmailImport `json:"emails,omitempty"` } @@ -31,44 +22,24 @@ func (m *Import) Name() string { return "Email/import" } func (m *Import) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type EmailImport struct { - // The id of the blob containing the raw message [RFC5322]. BlobID jmap.ID `json:"blobId,omitempty"` - // The ids of the Mailboxes to assign this Email to. At least one - // Mailbox MUST be given. MailboxIDs map[jmap.ID]bool `json:"mailboxIds,omitempty"` - // The keywords to apply to the Email. Keywords map[string]bool `json:"keywords,omitempty"` - // The "receivedAt" date to set on the Email. The value must be in UTC ReceivedAt *time.Time `json:"receivedAt,omitempty"` } type ImportResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // The state string that would have been returned by Foo/get before - // making the requested changes, or null if the server doesn’t know - // what the previous state string was. OldState string `json:"oldState,omitempty"` - // The state string that will now be returned by Foo/get. NewState string `json:"newState,omitempty"` - // A map of the creation id to an object containing any properties of - // the created Foo object that were not sent by the client. This - // includes all server-set properties (such as the id in most object - // types) and any properties that were omitted by the client and thus - // set to a default by the server. - // - // This argument is null if no Foo objects were successfully created. Created map[jmap.ID]*Email `json:"created,omitempty"` - // A map of the creation id to a SetError object for each Email that - // failed to be created, or null if all successful. The possible - // errors are defined above. NotCreated map[jmap.ID]*jmap.SetError `json:"notCreated,omitempty"` } diff --git a/mail/email/parse.go b/mail/email/parse.go index d98f1fda195a..66da9ba6747a 100644 --- a/mail/email/parse.go +++ b/mail/email/parse.go @@ -5,71 +5,23 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This method allows you to parse blobs as messages [RFC5322] to get -// Email objects. The server MUST support messages with EAI headers -// [RFC6532]. This can be used to parse and display attached messages -// without having to import them as top-level Email objects in the mail -// store in their own right. -// -// The following metadata properties on the Email objects will be null -// if requested: -// -// o id -// -// o mailboxIds -// -// o keywords -// -// o receivedAt -// -// The "threadId" property of the Email MAY be present if the server can -// calculate which Thread the Email would be assigned to were it to be -// imported. Otherwise, this too is null if fetched. +// Parse binary blobs as RFC5322 messages +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.9 type Parse struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The ids of the blobs to parse. BlobIDs []jmap.ID `json:"blobIds,omitempty"` - // If supplied, only the properties listed in the array are returned - // for each Foo object. If null, all properties of the object are - // returned. The id property of the object is always returned, even if - // not explicitly requested. If an invalid property is requested, the - // call MUST be rejected with an invalidArguments error. Properties []string `json:"properties,omitempty"` - // A list of properties to fetch for each EmailBodyPart returned. If - // omitted, this defaults to: - // - // [ "partId", "blobId", "size", "name", "type", "charset", - // "disposition", "cid", "language", "location" ] BodyProperties []string `json:"bodyProperties,omitempty"` - // If true, the "bodyValues" property includes any "text/*" part in - // the "textBody" property. FetchTextBodyValues bool `json:"fetchTextBodyValues,omitempty"` - // If true, the "bodyValues" property includes any "text/*" part in - // the "htmlBody" property. FetchHTMLBodyValues bool `json:"fetchHTMLBodyValues,omitempty"` - // If true, the "bodyValues" property includes any "text/*" part in - // the "bodyStructure" property. FetchAllBodyValues bool `json:"fetchAllBodyValues,omitempty"` - // If greater than zero, the "value" property of any EmailBodyValue - // object returned in "bodyValues" MUST be truncated if necessary so - // it does not exceed this number of octets in size. If 0 (the - // default), no truncation occurs. - // - // The server MUST ensure the truncation results in valid UTF-8 and - // does not occur mid-codepoint. If the part is of type "text/html", - // the server SHOULD NOT truncate inside an HTML tag, e.g., in the - // middle of "<a href="https://example.com">". There is no - // requirement for the truncated form to be a balanced tree or valid - // HTML (indeed, the original source may well be neither of these - // things). MaxBodyValueBytes uint64 `json:"maxBodyValueBytes,omitempty"` } @@ -78,18 +30,12 @@ func (m *Parse) Name() string { return "Email/parse" } func (m *Parse) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type ParseResponse struct { - // The id of the account used for the call Account jmap.ID `json:"accountId,omitempty"` - // A map of blob id to parsed Email representation for each - // successfully parsed blob, or null if none. Parsed map[jmap.ID]*Email `json:"parsed,omitempty"` - // A list of ids given that corresponded to blobs that could not be - // parsed as Emails, or null if none. NotParsable []jmap.ID `json:"notParsable,omitempty"` - // A list of blob ids given that could not be found, or null if none. NotFound []jmap.ID `json:"notFound,omitempty"` } diff --git a/mail/email/query.go b/mail/email/query.go index aeaa98e6884e..f6255d987e3f 100644 --- a/mail/email/query.go +++ b/mail/email/query.go @@ -5,88 +5,25 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard "/query" method as described in [RFC8620], Section 5.5 -// but with the following additional request arguments: +// Get list of email IDs based on filter and sort criteria +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.4 type Query struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // Determines the set of Foos returned in the results. If null, all - // objects in the account of this type are included in the results. - // - // Each implementation must implement it's own Filter Filter Filter `json:"filter,omitempty"` - // Lists the names of properties to compare between two Foo records, - // and how to compare them, to determine which comes first in the sort. - // If two Foo records have an identical value for the first comparator, - // the next comparator will be considered, and so on. If all - // comparators are the same (this includes the case where an empty - // array or null is given as the sort argument), the sort order is - // server dependent, but it MUST be stable between calls to Foo/query. - // - // Each implementation must define it's own Sort property. The - // SortComparator object can be used as a basis Sort []*SortComparator `json:"sort,omitempty"` - // The zero-based index of the first id in the full list of results to - // return. - // - // If a negative value is given, it is an offset from the end of the - // list. Specifically, the negative value MUST be added to the total - // number of results given the filter, and if still negative, it’s - // clamped to 0. This is now the zero-based index of the first id to - // return. - // - // If the index is greater than or equal to the total number of objects - // in the results list, then the ids array in the response will be - // empty, but this is not an error. Position int64 `json:"position,omitempty"` - // A Foo id. If supplied, the position argument is ignored. The index - // of this id in the results will be used in combination with the - // anchorOffset argument to determine the index of the first result to - // return (see below for more details). - // - // If an anchor argument is given, the anchor is looked for in the - // results after filtering and sorting. If found, the anchorOffset is - // then added to its index. If the resulting index is now negative, it - // is clamped to 0. This index is now used exactly as though it were - // supplied as the position argument. If the anchor is not found, the - // call is rejected with an anchorNotFound error. - // - // If an anchor is specified, any position argument supplied by the - // client MUST be ignored. If no anchor is supplied, any anchorOffset - // argument MUST be ignored. - // - // A client can use anchor instead of position to find the index of an - // id within a large set of results. Anchor jmap.ID `json:"anchor,omitempty"` - // The index of the first result to return relative to the index of the - // anchor, if an anchor is given. This MAY be negative. For example, -1 - // means the Foo immediately preceding the anchor is the first result - // in the list returned (see below for more details). AnchorOffset int64 `json:"anchorOffset,omitempty"` - // The maximum number of results to return. If null, no limit presumed. - // The server MAY choose to enforce a maximum limit argument. In this - // case, if a greater value is given (or if it is null), the limit is - // clamped to the maximum; the new limit is returned with the response - // so the client is aware. If a negative value is given, the call MUST - // be rejected with an invalidArguments error. Limit uint64 `json:"limit,omitempty"` - // Does the client wish to know the total number of results in the - // query? This may be slow and expensive for servers to calculate, - // particularly with complex filters, so clients should take care to - // only request the total when needed. CalculateTotal bool `json:"calculateTotal,omitempty"` - // If true, Emails in the same Thread as a previous Email in the list - // (given the filter and sort order) will be removed from the list. - // This means only one Email at most will be included in the list for - // any given Thread. CollapseThreads bool `json:"collapseThreads,omitempty"` } @@ -95,56 +32,18 @@ func (m *Query) Name() string { return "Email/query" } func (m *Query) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type QueryResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // A string encoding the current state of the query on the server. This - // string MUST change if the results of the query (i.e., the matching - // ids and their sort order) have changed. The queryState string MAY - // change if something has changed on the server, which means the - // results may have changed but the server doesn’t know for sure. - // - // The queryState string only represents the ordered list of ids that - // match the particular query (including its sort/filter). There is no - // requirement for it to change if a property on an object matching the - // query changes but the query results are unaffected (indeed, it is - // more efficient if the queryState string does not change in this - // case). The queryState string only has meaning when compared to - // future responses to a query with the same type/sort/filter or when - // used with /queryChanges to fetch changes. - // - // Should a client receive back a response with a different queryState - // string to a previous call, it MUST either throw away the currently - // cached query and fetch it again (note, this does not require - // fetching the records again, just the list of ids) or call - // Foo/queryChanges to get the difference. QueryState string `json:"queryState,omitempty"` - // This is true if the server supports calling Foo/queryChanges with - // these filter/sort parameters. Note, this does not guarantee that the - // Foo/queryChanges call will succeed, as it may only be possible for a - // limited time afterwards due to server internal implementation - // details. CanCalculateChanges bool `json:"canCalculateChanges,omitempty"` - // The zero-based index of the first result in the ids array within the - // complete list of query results. Position uint64 `json:"position,omitempty"` - // The list of ids for each Foo in the query results, starting at the - // index given by the position argument of this response and continuing - // until it hits the end of the results or reaches the limit number of - // ids. If position is >= total, this MUST be the empty list. IDs []jmap.ID `json:"ids,omitempty"` - // The total number of Foos in the results (given the filter). This - // argument MUST be omitted if the calculateTotal request argument is - // not true. Total uint64 `json:"total,omitempty"` - // The limit enforced by the server on the maximum number of results to - // return. This is only returned if the server set a limit or used a - // different limit than that given in the request. Limit uint64 `json:"limit,omitempty"` } diff --git a/mail/email/querychanges.go b/mail/email/querychanges.go index 219fbe60290a..aa11c723320c 100644 --- a/mail/email/querychanges.go +++ b/mail/email/querychanges.go @@ -5,51 +5,23 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard "/queryChanges" method as described in [RFC8620], Section -// 5.6 with the following additional request argument: collapseThreads +// Get changes to an email query since a given state +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.5 type QueryChanges struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The filter argument that was used with Foo/query. - // - // Each implementation must supply it's own Filter property Filter Filter `json:"filter,omitempty"` - // The sort argument that was used with Foo/query. - // - // Each implementation must supply it's own Sort property Sort []*SortComparator `json:"sort,omitempty"` - // The current state of the query in the client. This is the string - // that was returned as the queryState argument in the Foo/query - // response with the same sort/filter. The server will return the - // changes made to the query since this state. SinceQueryState string `json:"sinceQueryState,omitempty"` - // The maximum number of changes to return in the response. See error - // descriptions below for more details. MaxChanges uint64 `json:"maxChanges,omitempty"` - // The last (highest-index) id the client currently has cached from the - // query results. When there are a large number of results, in a common - // case, the client may have only downloaded and cached a small subset - // from the beginning of the results. If the sort and filter are both - // only on immutable properties, this allows the server to omit changes - // after this point in the results, which can significantly increase - // efficiency. If they are not immutable, this argument is ignored. UpToID jmap.ID `json:"upToId,omitempty"` - // Does the client wish to know the total number of results now in the - // query? This may be slow and expensive for servers to calculate, - // particularly with complex filters, so clients should take care to - // only request the total when needed. CalculateTotal bool `json:"calculateTotal,omitempty"` - // If true, Emails in the same Thread as a previous Email in the list - // (given the filter and sort order) will be removed from the list. - // This means only one Email at most will be included in the list for - // any given Thread. CollapseThreads bool `json:"collapseThreads,omitempty"` } @@ -57,49 +29,15 @@ func (m *QueryChanges) Name() string { return "Email/queryChanges" } func (m *QueryChanges) Requires() []jmap.URI { return []jmap.URI{mail.URI} } -// This is a standard "/queryChanges" method as described in [RFC8620], Section -// 5.6 type QueryChangesResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // This is the sinceQueryState argument echoed back; that is, the state - // from which the server is returning changes. OldQueryState string `json:"oldQueryState,omitempty"` - // This is the state the query will be in after applying the set of - // changes to the old state. NewQueryState string `json:"newQueryState,omitempty"` - // The id for every Foo that was in the query results in the old state - // and that is not in the results in the new state. - // - // If the server cannot calculate this exactly, the server MAY return - // the ids of extra Foos in addition that may have been in the old - // results but are not in the new results. - // - // If the sort and filter are both only on immutable properties and an - // upToId is supplied and exists in the results, any ids that were - // removed but have a higher index than upToId SHOULD be omitted. - // - // If the filter or sort includes a mutable property, the server MUST - // include all Foos in the current results for which this property may - // have changed. The position of these may have moved in the results, - // so must be reinserted by the client to ensure its query cache is - // correct. Removed []jmap.ID `json:"removed,omitempty"` - // The id and index in the query results (in the new state) for every - // Foo that has been added to the results since the old state AND every - // Foo in the current results that was included in the removed array - // (due to a filter or sort based upon a mutable property). - // - // If the sort and filter are both only on immutable properties and an - // upToId is supplied and exists in the results, any ids that were - // added but have a higher index than upToId SHOULD be omitted. - // - // The array MUST be sorted in order of index, with the lowest index - // first. Added []jmap.AddedItem `json:"added,omitempty"` } diff --git a/mail/email/set.go b/mail/email/set.go index c046527c962b..83e4296da7b7 100644 --- a/mail/email/set.go +++ b/mail/email/set.go @@ -5,141 +5,17 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard "/set" method as described in [RFC8620], Section 5.3. The -// "Email/set" method encompasses: -// -// o Creating a draft -// -// o Changing the keywords of an Email (e.g., unread/flagged status) -// -// o Adding/removing an Email to/from Mailboxes (moving a message) -// -// o Deleting Emails -// -// The format of the "keywords"/"mailboxIds" properties means that when -// updating an Email, you can either replace the entire set of keywords/ -// Mailboxes (by setting the full value of the property) or add/remove -// individual ones using the JMAP patch syntax (see [RFC8620], -// Section 5.3 for the specification and Section 5.7 for an example). -// -// Due to the format of the Email object, when creating an Email, there -// are a number of ways to specify the same information. To ensure that -// the message [RFC5322] to create is unambiguous, the following -// constraints apply to Email objects submitted for creation: -// -// - The "headers" property MUST NOT be given on either the top-level -// Email or an EmailBodyPart -- the client must set each header field -// as an individual property. -// -// o There MUST NOT be two properties that represent the same header -// -// field (e.g., "header:from" and "from") within the Email or -// particular EmailBodyPart. -// -// o Header fields MUST NOT be specified in parsed forms that are -// -// forbidden for that particular field. -// -// o Header fields beginning with "Content-" MUST NOT be specified on -// -// the Email object, only on EmailBodyPart objects. -// -// o If a "bodyStructure" property is given, there MUST NOT be -// -// "textBody", "htmlBody", or "attachments" properties. -// -// o If given, the "bodyStructure" EmailBodyPart MUST NOT contain a -// -// property representing a header field that is already defined on -// the top-level Email object. -// -// o If given, textBody MUST contain exactly one body part and it MUST -// -// be of type "text/plain". -// -// o If given, htmlBody MUST contain exactly one body part and it MUST -// -// be of type "text/html". -// -// - Within an EmailBodyPart: -// -// - The client may specify a partId OR a blobId, but not both. If -// a partId is given, this partId MUST be present in the -// "bodyValues" property. -// -// - The "charset" property MUST be omitted if a partId is given -// (the part's content is included in bodyValues, and the server -// may choose any appropriate encoding). -// -// - The "size" property MUST be omitted if a partId is given. If a -// blobId is given, it may be included but is ignored by the -// server (the size is actually calculated from the blob content -// itself). -// -// - A Content-Transfer-Encoding header field MUST NOT be given. +// Create, delete or update emails +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.6 type Set struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // This is a state string as returned by the Foo/get method - // (representing the state of all objects of this type in the account). - // If supplied, the string must match the current state; otherwise, the - // method will be aborted and a stateMismatch error returned. If null, - // any changes will be applied to the current state. IfInState string `json:"ifInState,omitempty"` - // A map of a creation id (a temporary id set by the client) to Foo - // objects, or null if no objects are to be created. - // - // The Foo object type definition may define default values for - // properties. Any such property may be omitted by the client. - // - // The client MUST omit any properties that may only be set by the - // server (for example, the id property on most object types). Create map[jmap.ID]*Email `json:"create,omitempty"` - // A map of an id to a Patch object to apply to the current Foo object - // with that id, or null if no objects are to be updated. - // - // A PatchObject is of type String[*] and represents an unordered set - // of patches. The keys are a path in JSON Pointer Format [@!RFC6901], - // with an implicit leading “/” (i.e., prefix each key with “/” before - // applying the JSON Pointer evaluation algorithm). - // - // All paths MUST also conform to the following restrictions; if there - // is any violation, the update MUST be rejected with an invalidPatch - // error: - // - // The pointer MUST NOT reference inside an array (i.e., you MUST - // NOT insert/delete from an array; the array MUST be replaced in - // its entirety instead). All parts prior to the last (i.e., the - // value after the final slash) MUST already exist on the object - // being patched. There MUST NOT be two patches in the PatchObject - // where the pointer of one is the prefix of the pointer of the - // other, e.g., “alerts/1/offset” and “alerts”. - // - // The value associated with each pointer determines how to apply that - // patch: - // - // If null, set to the default value if specified for this - // property; otherwise, remove the property from the patched - // object. If the key is not present in the parent, this a no-op. - // Anything else: The value to set for this property (this may be a - // replacement or addition to the object being patched). - // - // Any server-set properties MAY be included in the patch if their - // value is identical to the current server value (before applying the - // patches to the object). Otherwise, the update MUST be rejected with - // an invalidProperties SetError. - // - // This patch definition is designed such that an entire Foo object is - // also a valid PatchObject. The client may choose to optimise network - // usage by just sending the diff or may send the whole object; the - // server processes it the same either way. Update map[jmap.ID]jmap.Patch `json:"update,omitempty"` - // A list of ids for Foo objects to permanently delete, or null if no - // objects are to be destroyed. Destroy []jmap.ID `json:"destroy,omitempty"` } @@ -148,48 +24,22 @@ func (m *Set) Name() string { return "Email/set" } func (m *Set) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type SetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // The state string that would have been returned by Foo/get before - // making the requested changes, or null if the server doesn’t know - // what the previous state string was. OldState string `json:"oldState,omitempty"` - // The state string that will now be returned by Foo/get. NewState string `json:"newState,omitempty"` - // A map of the creation id to an object containing any properties of - // the created Foo object that were not sent by the client. This - // includes all server-set properties (such as the id in most object - // types) and any properties that were omitted by the client and thus - // set to a default by the server. - // - // This argument is null if no Foo objects were successfully created. Created map[jmap.ID]*Email `json:"created,omitempty"` - // The keys in this map are the ids of all Foos that were successfully - // updated. - // - // The value for each id is a Foo object containing any property that - // changed in a way not explicitly requested by the PatchObject sent to - // the server, or null if none. This lets the client know of any - // changes to server-set or computed properties. - // - // This argument is null if no Foo objects were successfully updated. Updated map[jmap.ID]*Email `json:"updated,omitempty"` - // An array of ids for records that have been destroyed since the old - // state. Destroyed []jmap.ID `json:"destroyed,omitempty"` - // A map of ID to a SetError for each record that failed to be created NotCreated map[jmap.ID]*jmap.SetError `json:"notCreated,omitempty"` - // A map of ID to a SetError for each record that failed to be updated NotUpdated map[jmap.ID]*jmap.SetError `json:"notUpdated,omitempty"` - // A map of ID to a SetError for each record that failed to be destroyed NotDestroyed map[jmap.ID]*jmap.SetError `json:"notDestroyed,omitempty"` } diff --git a/mail/email/sort.go b/mail/email/sort.go index 4c9bceebdc9b..f28e396aab96 100644 --- a/mail/email/sort.go +++ b/mail/email/sort.go @@ -2,51 +2,14 @@ package email import "git.sr.ht/~rockorager/go-jmap" +// Email sort criteria +// https://www.rfc-editor.org/rfc/rfc8621.html#section-4.4.2 type SortComparator struct { - // The name of the property on the Foo objects to compare. Servers MUST - // support sorting by the following properties: - // - receivedAt - // - // Additional supported properties are reported in the mail capability - // object Property string `json:"property,omitempty"` - // When specifying a "hasKeyword", "allInThreadHaveKeyword", or - // "someInThreadHaveKeyword" sort, the Comparator object MUST also have - // a "keyword" property. Keyword string `json:"keyword,omitempty"` - // If true, sort in ascending order. If false, reverse the comparator’s - // results to sort in descending order. IsAscending bool `json:"isAscending,omitempty"` - // The identifier, as registered in the collation registry defined in - // [@!RFC4790], for the algorithm to use when comparing the order of - // strings. The algorithms the server supports are advertised in the - // capabilities object returned with the Session object (see Section - // 2). - // - // If omitted, the default algorithm is server-dependent, but: - // - // It MUST be unicode-aware. It MAY be selected based on an - // Accept-Language header in the request (as defined in - // [@!RFC7231], Section 5.3.5), or out-of-band information about - // the user’s language/locale. It SHOULD be case insensitive where - // such a concept makes sense for a language/locale. Where the - // user’s language is unknown, it is RECOMMENDED to follow the - // advice in Section 5.2.3 of [@!RFC8264]. - // - // The “i;unicode-casemap” collation [@!RFC5051] and the Unicode - // Collation Algorithm (http://www.unicode.org/reports/tr10/) are two - // examples that fulfil these criterion and provide reasonable - // behaviour for a large number of languages. - // - // When the property being compared is not a string, the collation - // property is ignored, and the following comparison rules apply based - // on the type. In ascending order: - // - // Boolean: false comes before true. Number: A lower number comes - // before a higher number. Date/UTCDate: The earlier date comes - // first. Collation jmap.CollationAlgo `json:"collation,omitempty"` } diff --git a/mail/emailsubmission/changes.go b/mail/emailsubmission/changes.go index 4d9e72a308fb..f8aec24ccaf5 100644 --- a/mail/emailsubmission/changes.go +++ b/mail/emailsubmission/changes.go @@ -5,22 +5,13 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard “/changes” method as described in [@!RFC8620], Section 5.2. +// Get email submission changes for the whole account +// https://www.rfc-editor.org/rfc/rfc8621.html#section-7.2 type Changes struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The current state of the client. This is the string that was - // returned as the state argument in the Foo/get response. The server - // will return the changes that have occurred since this state. SinceState string `json:"sinceState,omitempty"` - // The maximum number of ids to return in the response. The server MAY - // choose to return fewer than this value but MUST NOT return more. If - // not given by the client, the server may choose how many to return. - // If supplied by the client, the value MUST be a positive integer - // greater than 0. If a value outside of this range is given, the - // server MUST reject the call with an invalidArguments error. MaxChanges uint64 `json:"maxChanges,omitempty"` } @@ -28,34 +19,19 @@ func (m *Changes) Name() string { return "EmailSubmission/changes" } func (m *Changes) Requires() []jmap.URI { return []jmap.URI{URI, mail.URI} } -// This is a standard “/changes” method as described in [@!RFC8620], Section 5.2. type ChangesResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // This is the sinceState argument echoed back; it’s the state from - // which the server is returning changes. OldState string `json:"oldState,omitempty"` - // This is the state the client will be in after applying the set of - // changes to the old state. NewState string `json:"newState,omitempty"` - // If true, the client may call Foo/changes again with the newState - // returned to get further updates. If false, newState is the current - // server state. HasMoreChanges bool `json:"hasMoreChanges,omitempty"` - // An array of ids for records that have been created since the old - // state. Created []jmap.ID `json:"created,omitempty"` - // An array of ids for records that have been updated since the old - // state. Updated []jmap.ID `json:"updated,omitempty"` - // An array of ids for records that have been destroyed since the old - // state. Destroyed []jmap.ID `json:"destroyed,omitempty"` } diff --git a/mail/emailsubmission/emailsubmission.go b/mail/emailsubmission/emailsubmission.go index 949cc3b48ee4..e679c7957334 100644 --- a/mail/emailsubmission/emailsubmission.go +++ b/mail/emailsubmission/emailsubmission.go @@ -35,57 +35,27 @@ func (m *Capability) URI() jmap.URI { return URI } func (m *Capability) New() jmap.Capability { return &Capability{} } +// Submission of an Email for delivery to one or more recipients. +// https://www.rfc-editor.org/rfc/rfc8621.html#section-7 type EmailSubmission struct { - // The ID of the [EmailSubmission] - // - // immutable;server-set ID jmap.ID `json:"id,omitempty"` - // The ID of the Identity to associate with this submission - // - // immutable IdentityID jmap.ID `json:"identityId,omitempty"` - // The ID of the Email to send - // - // immutable EmailID jmap.ID `json:"emailId,omitempty"` - // The Thread ID of the Email to send - // - // immutable;server-set ThreadID jmap.ID `json:"threadId,omitempty"` - // The Envelope used for SMTP - // - // immutable Envelope *Envelope `json:"envelope,omitempty"` - // The date the submission was/will be released for delivery - // - // immutable;server-set SendAt *time.Time `json:"sendAt,omitempty"` - // A status indicating if the send can be undone. One of: - // - "pending": it may be possible to cancel - // - "final": the message has been sent - // - "canceled": the submission was canceled - // - // If this is "pending", a client can attempt to cancel by issuing a set - // method with this set to canceled UndoStatus string `json:"undoStatus,omitempty"` - // The delivery status for each recipient DeliveryStatus map[string]*DeliveryStatus `json:"deliveryStatus,omitempty"` - // A list of blob IDs for DSNs received for this submission - // - // server-set DSNBlobIDs []jmap.ID `json:"dsnBlobIds,omitempty"` - // A list of blob IDs for MDNs received for this submission - // - // server-set MDNBlobIDs []jmap.ID `json:"mdnBlobIds,omitempty"` } diff --git a/mail/emailsubmission/filter.go b/mail/emailsubmission/filter.go index 9c35866fa829..d2b0fb75f00d 100644 --- a/mail/emailsubmission/filter.go +++ b/mail/emailsubmission/filter.go @@ -11,40 +11,27 @@ type Filter interface { implementsFilter() } -// Determines the set of EmailSubmissions returned in the results. If null, all -// objects in the account of this type are included in the results. type FilterOperator struct { - // This MUST be one of the following strings: “AND” / “OR” / “NOT” Operator jmap.Operator `json:"operator,omitempty"` - // The conditions to evaluate against each record. Conditions []Filter `json:"conditions,omitempty"` } func (fo *FilterOperator) implementsFilter() {} -// FilterCondition is an interface that represents FilterCondition -// objects. A filter condition object can be either a named struct, ie -// MailboxFilterConditionName, or a MailboxFilter itself. MailboxFilters can -// be used to create complex filtering ie return mailboxes which are subscribed -// and NOT named Inbox +// Email submission filter criteria +// https://www.rfc-editor.org/rfc/rfc8621.html#section-7.3 type FilterCondition struct { - // identityIds field must be in this list to match IdentityIDs []jmap.ID `json:"identityIds,omitempty"` - // emailId field must be in this list to match EmailIDs []jmap.ID `json:"emailIds,omitempty"` - // threadId field must be in this list to match ThreadIDs []jmap.ID `json:"threadIds,omitempty"` - // The undoStatus property must exactly match this to match UndoStatus string `json:"undoStatus,omitempty"` - // UTC. The sendAt property must be before this time to match Before *time.Time `json:"before,omitempty"` - // UTC. The sendAt property must be after this time to match After *time.Time `json:"after,omitempty"` } diff --git a/mail/emailsubmission/get.go b/mail/emailsubmission/get.go index 9f20e4e06551..d39be6eec7d6 100644 --- a/mail/emailsubmission/get.go +++ b/mail/emailsubmission/get.go @@ -5,27 +5,17 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. +// Get email submission details +// https://www.rfc-editor.org/rfc/rfc8621.html#section-7.1 type Get struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The ids of the Foo objects to return. If null, then all records of - // the data type are returned, if this is supported for that data type - // and the number of records does not exceed the maxObjectsInGet limit. IDs []jmap.ID `json:"ids,omitempty"` - // If supplied, only the properties listed in the array are returned - // for each Foo object. If null, all properties of the object are - // returned. The id property of the object is always returned, even if - // not explicitly requested. If an invalid property is requested, the - // call MUST be rejected with an invalidArguments error. Properties []string `json:"properties,omitempty"` - // Use IDs from a previous call ReferenceIDs *jmap.ResultReference `json:"#ids,omitempty"` - // Use Properties from a previous call ReferenceProperties *jmap.ResultReference `json:"#properties,omitempty"` } @@ -33,35 +23,13 @@ func (m *Get) Name() string { return "EmailSubmission/get" } func (m *Get) Requires() []jmap.URI { return []jmap.URI{URI, mail.URI} } -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. type GetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // A (preferably short) string representing the state on the server for - // all the data of this type in the account (not just the objects - // returned in this call). If the data changes, this string MUST - // change. If the Foo data is unchanged, servers SHOULD return the same - // state string on subsequent requests for this data type. - // - // When a client receives a response with a different state string to a - // previous call, it MUST either throw away all currently cached - // objects for the type or call Foo/changes to get the exact changes. State string `json:"state,omitempty"` - // An array of the Foo objects requested. This is the empty array - // if no objects were found or if the ids argument passed in was also - // an empty array. The results MAY be in a different order to the ids - // in the request arguments. If an identical id is included more than - // once in the request, the server MUST only include it once in either - // the list or the notFound argument of the response. - // - // Each specification must define it's own List property List []*EmailSubmission `json:"list,omitempty"` - // This array contains the ids passed to the method for records that do - // not exist. The array is empty if all requested ids were found or if - // the ids argument passed in was either null or an empty array. NotFound []jmap.ID `json:"notFound,omitempty"` } diff --git a/mail/emailsubmission/query.go b/mail/emailsubmission/query.go index ad69383524c9..120bd0b91a45 100644 --- a/mail/emailsubmission/query.go +++ b/mail/emailsubmission/query.go @@ -5,82 +5,23 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard “/query” method as described in [@!RFC8620], Section 5.5, -// but with the following additional request argument: sortAsTree, filterAsTree +// List email submission IDs based on filter and sort criteria +// https://www.rfc-editor.org/rfc/rfc8621.html#section-7.3 type Query struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // Determines the set of Foos returned in the results. If null, all - // objects in the account of this type are included in the results. - // - // Each implementation must implement it's own Filter Filter Filter `json:"filter,omitempty"` - // Lists the names of properties to compare between two Foo records, - // and how to compare them, to determine which comes first in the sort. - // If two Foo records have an identical value for the first comparator, - // the next comparator will be considered, and so on. If all - // comparators are the same (this includes the case where an empty - // array or null is given as the sort argument), the sort order is - // server dependent, but it MUST be stable between calls to Foo/query. - // - // Each implementation must define it's own Sort property. The - // SortComparator object can be used as a basis Sort []*SortComparator `json:"sort,omitempty"` - // The zero-based index of the first id in the full list of results to - // return. - // - // If a negative value is given, it is an offset from the end of the - // list. Specifically, the negative value MUST be added to the total - // number of results given the filter, and if still negative, it’s - // clamped to 0. This is now the zero-based index of the first id to - // return. - // - // If the index is greater than or equal to the total number of objects - // in the results list, then the ids array in the response will be - // empty, but this is not an error. Position int64 `json:"position,omitempty"` - // A Foo id. If supplied, the position argument is ignored. The index - // of this id in the results will be used in combination with the - // anchorOffset argument to determine the index of the first result to - // return (see below for more details). - // - // If an anchor argument is given, the anchor is looked for in the - // results after filtering and sorting. If found, the anchorOffset is - // then added to its index. If the resulting index is now negative, it - // is clamped to 0. This index is now used exactly as though it were - // supplied as the position argument. If the anchor is not found, the - // call is rejected with an anchorNotFound error. - // - // If an anchor is specified, any position argument supplied by the - // client MUST be ignored. If no anchor is supplied, any anchorOffset - // argument MUST be ignored. - // - // A client can use anchor instead of position to find the index of an - // id within a large set of results. Anchor jmap.ID `json:"anchor,omitempty"` - // The index of the first result to return relative to the index of the - // anchor, if an anchor is given. This MAY be negative. For example, -1 - // means the Foo immediately preceding the anchor is the first result - // in the list returned (see below for more details). AnchorOffset int64 `json:"anchorOffset,omitempty"` - // The maximum number of results to return. If null, no limit presumed. - // The server MAY choose to enforce a maximum limit argument. In this - // case, if a greater value is given (or if it is null), the limit is - // clamped to the maximum; the new limit is returned with the response - // so the client is aware. If a negative value is given, the call MUST - // be rejected with an invalidArguments error. Limit uint64 `json:"limit,omitempty"` - // Does the client wish to know the total number of results in the - // query? This may be slow and expensive for servers to calculate, - // particularly with complex filters, so clients should take care to - // only request the total when needed. CalculateTotal bool `json:"calculateTotal,omitempty"` } @@ -89,56 +30,18 @@ func (m *Query) Name() string { return "EmailSubmission/query" } func (m *Query) Requires() []jmap.URI { return []jmap.URI{URI, mail.URI} } type QueryResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // A string encoding the current state of the query on the server. This - // string MUST change if the results of the query (i.e., the matching - // ids and their sort order) have changed. The queryState string MAY - // change if something has changed on the server, which means the - // results may have changed but the server doesn’t know for sure. - // - // The queryState string only represents the ordered list of ids that - // match the particular query (including its sort/filter). There is no - // requirement for it to change if a property on an object matching the - // query changes but the query results are unaffected (indeed, it is - // more efficient if the queryState string does not change in this - // case). The queryState string only has meaning when compared to - // future responses to a query with the same type/sort/filter or when - // used with /queryChanges to fetch changes. - // - // Should a client receive back a response with a different queryState - // string to a previous call, it MUST either throw away the currently - // cached query and fetch it again (note, this does not require - // fetching the records again, just the list of ids) or call - // Foo/queryChanges to get the difference. QueryState string `json:"queryState,omitempty"` - // This is true if the server supports calling Foo/queryChanges with - // these filter/sort parameters. Note, this does not guarantee that the - // Foo/queryChanges call will succeed, as it may only be possible for a - // limited time afterwards due to server internal implementation - // details. CanCalculateChanges bool `json:"canCalculateChanges,omitempty"` - // The zero-based index of the first result in the ids array within the - // complete list of query results. Position uint64 `json:"position,omitempty"` - // The list of ids for each Foo in the query results, starting at the - // index given by the position argument of this response and continuing - // until it hits the end of the results or reaches the limit number of - // ids. If position is >= total, this MUST be the empty list. IDs []jmap.ID `json:"ids,omitempty"` - // The total number of Foos in the results (given the filter). This - // argument MUST be omitted if the calculateTotal request argument is - // not true. Total int64 `json:"total,omitempty"` - // The limit enforced by the server on the maximum number of results to - // return. This is only returned if the server set a limit or used a - // different limit than that given in the request. Limit uint64 `json:"limit,omitempty"` } diff --git a/mail/emailsubmission/querychanges.go b/mail/emailsubmission/querychanges.go index 1d37662f4492..8dfb6a510e39 100644 --- a/mail/emailsubmission/querychanges.go +++ b/mail/emailsubmission/querychanges.go @@ -5,43 +5,21 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) +// Get changes over an email submission query +// https://www.rfc-editor.org/rfc/rfc8621.html#section-7.4 type QueryChanges struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The filter argument that was used with Foo/query. - // - // Each implementation must supply it's own Filter property Filter Filter `json:"filter,omitempty"` - // The sort argument that was used with Foo/query. - // - // Each implementation must supply it's own Sort property Sort []*SortComparator `json:"sort,omitempty"` - // The current state of the query in the client. This is the string - // that was returned as the queryState argument in the Foo/query - // response with the same sort/filter. The server will return the - // changes made to the query since this state. SinceQueryState string `json:"sinceQueryState,omitempty"` - // The maximum number of changes to return in the response. See error - // descriptions below for more details. MaxChanges uint64 `json:"maxChanges,omitempty"` - // The last (highest-index) id the client currently has cached from the - // query results. When there are a large number of results, in a common - // case, the client may have only downloaded and cached a small subset - // from the beginning of the results. If the sort and filter are both - // only on immutable properties, this allows the server to omit changes - // after this point in the results, which can significantly increase - // efficiency. If they are not immutable, this argument is ignored. UpToID jmap.ID `json:"upToId,omitempty"` - // Does the client wish to know the total number of results now in the - // query? This may be slow and expensive for servers to calculate, - // particularly with complex filters, so clients should take care to - // only request the total when needed. CalculateTotal bool `json:"calculateTotal,omitempty"` } @@ -50,46 +28,14 @@ func (m *QueryChanges) Name() string { return "EmailSubmission/queryChanges" } func (m *QueryChanges) Requires() []jmap.URI { return []jmap.URI{URI, mail.URI} } type QueryChangesResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // This is the sinceQueryState argument echoed back; that is, the state - // from which the server is returning changes. OldQueryState string `json:"oldQueryState,omitempty"` - // This is the state the query will be in after applying the set of - // changes to the old state. NewQueryState string `json:"newQueryState,omitempty"` - // The id for every Foo that was in the query results in the old state - // and that is not in the results in the new state. - // - // If the server cannot calculate this exactly, the server MAY return - // the ids of extra Foos in addition that may have been in the old - // results but are not in the new results. - // - // If the sort and filter are both only on immutable properties and an - // upToId is supplied and exists in the results, any ids that were - // removed but have a higher index than upToId SHOULD be omitted. - // - // If the filter or sort includes a mutable property, the server MUST - // include all Foos in the current results for which this property may - // have changed. The position of these may have moved in the results, - // so must be reinserted by the client to ensure its query cache is - // correct. Removed []jmap.ID `json:"removed,omitempty"` - // The id and index in the query results (in the new state) for every - // Foo that has been added to the results since the old state AND every - // Foo in the current results that was included in the removed array - // (due to a filter or sort based upon a mutable property). - // - // If the sort and filter are both only on immutable properties and an - // upToId is supplied and exists in the results, any ids that were - // added but have a higher index than upToId SHOULD be omitted. - // - // The array MUST be sorted in order of index, with the lowest index - // first. Added []*jmap.AddedItem `json:"added,omitempty"` } diff --git a/mail/emailsubmission/set.go b/mail/emailsubmission/set.go index ac47319e265c..fb0404527238 100644 --- a/mail/emailsubmission/set.go +++ b/mail/emailsubmission/set.go @@ -5,86 +5,21 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard “/set” method as described in [@!RFC8620], Section 5.3, -// but with the following additional request argument: onDestroyRemoveEmails +// Create, delete or modify an email submission +// https://www.rfc-editor.org/rfc/rfc8621.html#section-7.5 type Set struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // This is a state string as returned by the Foo/get method - // (representing the state of all objects of this type in the account). - // If supplied, the string must match the current state; otherwise, the - // method will be aborted and a stateMismatch error returned. If null, - // any changes will be applied to the current state. IfInState string `json:"ifInState,omitempty"` - // A map of a creation id (a temporary id set by the client) to Foo - // objects, or null if no objects are to be created. - // - // The Foo object type definition may define default values for - // properties. Any such property may be omitted by the client. - // - // The client MUST omit any properties that may only be set by the - // server (for example, the id property on most object types). Create map[jmap.ID]*EmailSubmission `json:"create,omitempty"` - // A map of an id to a Patch object to apply to the current Foo object - // with that id, or null if no objects are to be updated. - // - // A PatchObject is of type String[*] and represents an unordered set - // of patches. The keys are a path in JSON Pointer Format [@!RFC6901], - // with an implicit leading “/” (i.e., prefix each key with “/” before - // applying the JSON Pointer evaluation algorithm). - // - // All paths MUST also conform to the following restrictions; if there - // is any violation, the update MUST be rejected with an invalidPatch - // error: - // - // The pointer MUST NOT reference inside an array (i.e., you MUST - // NOT insert/delete from an array; the array MUST be replaced in - // its entirety instead). All parts prior to the last (i.e., the - // value after the final slash) MUST already exist on the object - // being patched. There MUST NOT be two patches in the PatchObject - // where the pointer of one is the prefix of the pointer of the - // other, e.g., “alerts/1/offset” and “alerts”. - // - // The value associated with each pointer determines how to apply that - // patch: - // - // If null, set to the default value if specified for this - // property; otherwise, remove the property from the patched - // object. If the key is not present in the parent, this a no-op. - // Anything else: The value to set for this property (this may be a - // replacement or addition to the object being patched). - // - // Any server-set properties MAY be included in the patch if their - // value is identical to the current server value (before applying the - // patches to the object). Otherwise, the update MUST be rejected with - // an invalidProperties SetError. - // - // This patch definition is designed such that an entire Foo object is - // also a valid PatchObject. The client may choose to optimise network - // usage by just sending the diff or may send the whole object; the - // server processes it the same either way. Update map[jmap.ID]jmap.Patch `json:"update,omitempty"` - // A list of ids for Foo objects to permanently delete, or null if no - // objects are to be destroyed. Destroy []jmap.ID `json:"destroy,omitempty"` - // A map of EmailSubmission id to an object containing properties to - // update on the Email object referenced by the EmailSubmission if the - // create/update/destroy succeeds. (For references to EmailSubmissions - // created in the same “/set” invocation, this is equivalent to a - // creation-reference, so the id will be the creation id prefixed with - // a #.) OnSuccessUpdateEmail map[jmap.ID]jmap.Patch `json:"onSuccessUpdateEmail,omitempty"` - // A list of EmailSubmission ids for which the Email with the - // corresponding emailId should be destroyed if the - // create/update/destroy succeeds. (For references to EmailSubmission - // creations, this is equivalent to a creation-reference so the id will - // be the creation id prefixed with a #.) OnSuccessDestroyEmail []jmap.ID `json:"onSuccessDestroyEmail,omitempty"` } @@ -93,48 +28,22 @@ func (m *Set) Name() string { return "EmailSubmission/set" } func (m *Set) Requires() []jmap.URI { return []jmap.URI{URI, mail.URI} } type SetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // The state string that would have been returned by Foo/get before - // making the requested changes, or null if the server doesn’t know - // what the previous state string was. OldState string `json:"oldState,omitempty"` - // The state string that will now be returned by Foo/get. NewState string `json:"newState,omitempty"` - // A map of the creation id to an object containing any properties of - // the created Foo object that were not sent by the client. This - // includes all server-set properties (such as the id in most object - // types) and any properties that were omitted by the client and thus - // set to a default by the server. - // - // This argument is null if no Foo objects were successfully created. Created map[jmap.ID]*EmailSubmission `json:"created,omitempty"` - // The keys in this map are the ids of all Foos that were successfully - // updated. - // - // The value for each id is a Foo object containing any property that - // changed in a way not explicitly requested by the PatchObject sent to - // the server, or null if none. This lets the client know of any - // changes to server-set or computed properties. - // - // This argument is null if no Foo objects were successfully updated. Updated map[jmap.ID]*EmailSubmission `json:"updated,omitempty"` - // An array of ids for records that have been destroyed since the old - // state. Destroyed []jmap.ID `json:"destroyed,omitempty"` - // A map of ID to a SetError for each record that failed to be created NotCreated map[jmap.ID]*jmap.SetError `json:"notCreated,omitempty"` - // A map of ID to a SetError for each record that failed to be updated NotUpdated map[jmap.ID]*jmap.SetError `json:"notUpdated,omitempty"` - // A map of ID to a SetError for each record that failed to be destroyed NotDestroyed map[jmap.ID]*jmap.SetError `json:"notDestroyed,omitempty"` } diff --git a/mail/emailsubmission/sort.go b/mail/emailsubmission/sort.go index 7dbb0262f7a3..04d617484ccc 100644 --- a/mail/emailsubmission/sort.go +++ b/mail/emailsubmission/sort.go @@ -3,44 +3,9 @@ package emailsubmission import "git.sr.ht/~rockorager/go-jmap" type SortComparator struct { - // The name of the property on the Foo objects to compare. Servers MUST - // support sorting by the following properties: - // - emailId - // - threadId - // - sentAt Property string `json:"property,omitempty"` - // If true, sort in ascending order. If false, reverse the comparator’s - // results to sort in descending order. IsAscending bool `json:"isAscending,omitempty"` - // The identifier, as registered in the collation registry defined in - // [@!RFC4790], for the algorithm to use when comparing the order of - // strings. The algorithms the server supports are advertised in the - // capabilities object returned with the Session object (see Section - // 2). - // - // If omitted, the default algorithm is server-dependent, but: - // - // It MUST be unicode-aware. It MAY be selected based on an - // Accept-Language header in the request (as defined in - // [@!RFC7231], Section 5.3.5), or out-of-band information about - // the user’s language/locale. It SHOULD be case insensitive where - // such a concept makes sense for a language/locale. Where the - // user’s language is unknown, it is RECOMMENDED to follow the - // advice in Section 5.2.3 of [@!RFC8264]. - // - // The “i;unicode-casemap” collation [@!RFC5051] and the Unicode - // Collation Algorithm (http://www.unicode.org/reports/tr10/) are two - // examples that fulfil these criterion and provide reasonable - // behaviour for a large number of languages. - // - // When the property being compared is not a string, the collation - // property is ignored, and the following comparison rules apply based - // on the type. In ascending order: - // - // Boolean: false comes before true. Number: A lower number comes - // before a higher number. Date/UTCDate: The earlier date comes - // first. Collation jmap.CollationAlgo `json:"collation,omitempty"` } diff --git a/mail/identity/changes.go b/mail/identity/changes.go index 20da80bf8967..18351e32912b 100644 --- a/mail/identity/changes.go +++ b/mail/identity/changes.go @@ -5,22 +5,13 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail/emailsubmission" ) -// An Identity/changes method call +// Get identity changes +// https://www.rfc-editor.org/rfc/rfc8621.html#section-6.2 type Changes struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The current state of the client. This is the string that was - // returned as the state argument in the Foo/get response. The server - // will return the changes that have occurred since this state. SinceState string `json:"sinceState,omitempty"` - // The maximum number of ids to return in the response. The server MAY - // choose to return fewer than this value but MUST NOT return more. If - // not given by the client, the server may choose how many to return. - // If supplied by the client, the value MUST be a positive integer - // greater than 0. If a value outside of this range is given, the - // server MUST reject the call with an invalidArguments error. MaxChanges uint64 `json:"maxChanges,omitempty"` } @@ -28,34 +19,19 @@ func (m *Changes) Name() string { return "Identity/changes" } func (m *Changes) Requires() []jmap.URI { return []jmap.URI{emailsubmission.URI} } -// An Identity/changes response type ChangesResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // This is the sinceState argument echoed back; it’s the state from - // which the server is returning changes. OldState string `json:"oldState,omitempty"` - // This is the state the client will be in after applying the set of - // changes to the old state. NewState string `json:"newState,omitempty"` - // If true, the client may call Foo/changes again with the newState - // returned to get further updates. If false, newState is the current - // server state. HasMoreChanges bool `json:"hasMoreChanges,omitempty"` - // An array of ids for records that have been created since the old - // state. Created []jmap.ID `json:"created,omitempty"` - // An array of ids for records that have been updated since the old - // state. Updated []jmap.ID `json:"updated,omitempty"` - // An array of ids for records that have been destroyed since the old - // state. Destroyed []jmap.ID `json:"destroyed,omitempty"` } diff --git a/mail/identity/get.go b/mail/identity/get.go index 2e9c5bbffc4c..89c6c362a0af 100644 --- a/mail/identity/get.go +++ b/mail/identity/get.go @@ -5,22 +5,17 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail/emailsubmission" ) -// An Identity/get request +// Get details identity details +// https://www.rfc-editor.org/rfc/rfc8621.html#section-6.1 type Get struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The IDs of Identity objects to return. Leave blank to return all, - // subject to the MaxObjectsInGet limit of the server IDs []jmap.ID `json:"ids,omitempty"` - // Only the supplied properties will be returned Properties []string `json:"properties,omitempty"` - // Use IDs from a previous call ReferenceIDs *jmap.ResultReference `json:"#ids,omitempty"` - // Use Properties from a previous call ReferenceProperties *jmap.ResultReference `json:"#properties,omitempty"` } @@ -28,19 +23,13 @@ func (m *Get) Name() string { return "Identity/get" } func (m *Get) Requires() []jmap.URI { return []jmap.URI{emailsubmission.URI} } -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. type GetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // State for all Identity objects on the server for this account State string `json:"state,omitempty"` - // The Identity objects requested List []*Identity `json:"list,omitempty"` - // Slice of objects not found. Only present if specific IDs were - // requested NotFound []jmap.ID `json:"notFound,omitempty"` } diff --git a/mail/identity/identity.go b/mail/identity/identity.go index edd243513d79..fb29151ede35 100644 --- a/mail/identity/identity.go +++ b/mail/identity/identity.go @@ -11,42 +11,22 @@ func init() { jmap.RegisterMethod("Identity/set", newSetResponse) } +// Information about an email address or domain the user may send from +// https://www.rfc-editor.org/rfc/rfc8621.html#section-6 type Identity struct { - // The ID of the Identity - // - // immutable;server-set ID jmap.ID `json:"id,omitempty"` - // The "From" name the client SHOULD use when creating a new Email from - // this identity Name string `json:"name,omitempty"` - // The "From" email address the client MUST use when creating a new - // Email from this Identity. If the mailbox part of the address (the - // section before the "@") is the single character "*", then the client - // may use any valid address ending in that domain - // - // immutable Email string `json:"email,omitempty"` - // The Reply-To value the client SHOULD set when creating a new Email - // from this identity ReplyTo []*mail.Address `json:"replyTo,omitempty"` - // The Bcc value the client SHOULD set when creating a new Email from - // this Identity Bcc []*mail.Address `json:"bcc,omitempty"` - // A signature the client SHOULD insert into new plaintext messages that - // will be sent from this identity TextSignature string `json:"textSignature,omitempty"` - // A signature the client SHOULD insert into new html messages that - // will be sent from this identity HTMLSignature string `json:"htmlSignature,omitempty"` - // If the user is allowed to delete this identity - // - // server-set MayDelete bool `json:"mayDelete,omitempty"` } diff --git a/mail/identity/set.go b/mail/identity/set.go index 9c83cad3fbe7..815f8072f1d1 100644 --- a/mail/identity/set.go +++ b/mail/identity/set.go @@ -5,70 +5,17 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail/emailsubmission" ) -// An Identity/set method call +// Modify identities +// https://www.rfc-editor.org/rfc/rfc8621.html#section-6.3 type Set struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // This is a state string as returned by the Foo/get method - // (representing the state of all objects of this type in the account). - // If supplied, the string must match the current state; otherwise, the - // method will be aborted and a stateMismatch error returned. If null, - // any changes will be applied to the current state. IfInState string `json:"ifInState,omitempty"` - // A map of a creation id (a temporary id set by the client) to Foo - // objects, or null if no objects are to be created. - // - // The Foo object type definition may define default values for - // properties. Any such property may be omitted by the client. - // - // The client MUST omit any properties that may only be set by the - // server (for example, the id property on most object types). Create map[jmap.ID]*Identity `json:"create,omitempty"` - // A map of an id to a Patch object to apply to the current Foo object - // with that id, or null if no objects are to be updated. - // - // A PatchObject is of type String[*] and represents an unordered set - // of patches. The keys are a path in JSON Pointer Format [@!RFC6901], - // with an implicit leading “/” (i.e., prefix each key with “/” before - // applying the JSON Pointer evaluation algorithm). - // - // All paths MUST also conform to the following restrictions; if there - // is any violation, the update MUST be rejected with an invalidPatch - // error: - // - // The pointer MUST NOT reference inside an array (i.e., you MUST - // NOT insert/delete from an array; the array MUST be replaced in - // its entirety instead). All parts prior to the last (i.e., the - // value after the final slash) MUST already exist on the object - // being patched. There MUST NOT be two patches in the PatchObject - // where the pointer of one is the prefix of the pointer of the - // other, e.g., “alerts/1/offset” and “alerts”. - // - // The value associated with each pointer determines how to apply that - // patch: - // - // If null, set to the default value if specified for this - // property; otherwise, remove the property from the patched - // object. If the key is not present in the parent, this a no-op. - // Anything else: The value to set for this property (this may be a - // replacement or addition to the object being patched). - // - // Any server-set properties MAY be included in the patch if their - // value is identical to the current server value (before applying the - // patches to the object). Otherwise, the update MUST be rejected with - // an invalidProperties SetError. - // - // This patch definition is designed such that an entire Foo object is - // also a valid PatchObject. The client may choose to optimise network - // usage by just sending the diff or may send the whole object; the - // server processes it the same either way. Update map[jmap.ID]jmap.Patch `json:"update,omitempty"` - // A list of ids for Foo objects to permanently delete, or null if no - // objects are to be destroyed. Destroy []jmap.ID `json:"destroy,omitempty"` } @@ -77,48 +24,22 @@ func (m *Set) Name() string { return "Identity/set" } func (m *Set) Requires() []jmap.URI { return []jmap.URI{emailsubmission.URI} } type SetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // The state string that would have been returned by Foo/get before - // making the requested changes, or null if the server doesn’t know - // what the previous state string was. OldState string `json:"oldState,omitempty"` - // The state string that will now be returned by Foo/get. NewState string `json:"newState,omitempty"` - // A map of the creation id to an object containing any properties of - // the created Foo object that were not sent by the client. This - // includes all server-set properties (such as the id in most object - // types) and any properties that were omitted by the client and thus - // set to a default by the server. - // - // This argument is null if no Foo objects were successfully created. Created map[jmap.ID]*Identity `json:"created,omitempty"` - // The keys in this map are the ids of all Foos that were successfully - // updated. - // - // The value for each id is a Foo object containing any property that - // changed in a way not explicitly requested by the PatchObject sent to - // the server, or null if none. This lets the client know of any - // changes to server-set or computed properties. - // - // This argument is null if no Foo objects were successfully updated. Updated map[jmap.ID]*Identity `json:"updated,omitempty"` - // An array of ids for records that have been destroyed since the old - // state. Destroyed []jmap.ID `json:"destroyed,omitempty"` - // A map of ID to a SetError for each record that failed to be created NotCreated map[jmap.ID]*jmap.SetError `json:"notCreated,omitempty"` - // A map of ID to a SetError for each record that failed to be updated NotUpdated map[jmap.ID]*jmap.SetError `json:"notUpdated,omitempty"` - // A map of ID to a SetError for each record that failed to be destroyed NotDestroyed map[jmap.ID]*jmap.SetError `json:"notDestroyed,omitempty"` } diff --git a/mail/mailbox/changes.go b/mail/mailbox/changes.go index 58a490a54d05..df00f5b89ac4 100644 --- a/mail/mailbox/changes.go +++ b/mail/mailbox/changes.go @@ -5,14 +5,13 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard /changes method as described in RFC8620, Section 5.2. +// Get mailbox changes for the whole account +// https://www.rfc-editor.org/rfc/rfc8621.html#section-2.2 type Changes struct { Account jmap.ID `json:"accountId,omitempty"` - // The current state of the client. SinceState string `json:"sinceState,omitempty"` - // The maximum number of ids to return in the response. MaxChanges uint64 `json:"maxChanges,omitempty"` } @@ -20,35 +19,21 @@ func (m *Changes) Name() string { return "Mailbox/changes" } func (m *Changes) Requires() []jmap.URI { return []jmap.URI{mail.URI} } -// This is a standard /changes method as described in RFC8620, Section 5.2 -// but with one extra argument to the response: updatedProperties type ChangesResponse struct { Account jmap.ID `json:"accountId,omitempty"` - // This is the SinceState argument echoed back OldState string `json:"oldState,omitempty"` - // This is the state the client will be in after applying the set of - // changes to the old state. NewState string `json:"newState,omitempty"` - // If true, the client may call /changes again with the NewState - // returned to get further updates. If false, NewState is the current - // server state. HasMoreChanges bool `json:"hasMoreChanges,omitempty"` - // New mailbox IDs. Created []jmap.ID `json:"created,omitempty"` - // Updated mailbox IDs. Updated []jmap.ID `json:"updated,omitempty"` - // Deleted mailbox IDs. Destroyed []jmap.ID `json:"destroyed,omitempty"` - // If only the “totalEmails”, “unreadEmails”, “totalThreads”, and/or - // “unreadThreads” Mailbox properties have changed since the old state, - // this will be the list of properties that may have changed. UpdatedProperties []string `json:"updatedProperties,omitempty"` } diff --git a/mail/mailbox/filter.go b/mail/mailbox/filter.go index 777a68521c0c..34f3e5d60ebb 100644 --- a/mail/mailbox/filter.go +++ b/mail/mailbox/filter.go @@ -2,36 +2,29 @@ package mailbox import "git.sr.ht/~rockorager/go-jmap" -// Filter argument for a /query operation (see RFC8620, section 5.5) type Filter interface { implementsFilter() } -// FilterOperator can be used to create complex filtering (e.g.: return -// mailboxes which are subscribed and NOT named Inbox) type FilterOperator struct { - // jmap.OperatorOR, jmap.OperatorAND or jmap.OperatorNOT Operator jmap.Operator `json:"operator,omitempty"` - // List of nested FilterOperator or FilterCondition. Conditions []Filter `json:"conditions,omitempty"` } func (fo *FilterOperator) implementsFilter() {} -// See RFC8621, section 4.4.1. +// Filter criteria for mailbox queries +// https://www.rfc-editor.org/rfc/rfc8621.html#section-2.3 type FilterCondition struct { - // The Mailbox parentId property must match the given value exactly. ParentID jmap.ID `json:"parentId,omitempty"` - // The Mailbox name property contains the given string. + Name string `json:"name,omitempty"` - // The Mailbox role property must match the given value exactly. + Role Role `json:"role,omitempty"` - // If true, a Mailbox matches if it has any non-null value for its role - // property. + HasAnyRole bool `json:"hasAnyRole,omitempty"` - // The isSubscribed property of the Mailbox must be identical to the - // value given to match the condition. + IsSubscribed bool `json:"isSubscribed,omitempty"` } diff --git a/mail/mailbox/get.go b/mail/mailbox/get.go index 8f8c4a0f620a..7e52817f464a 100644 --- a/mail/mailbox/get.go +++ b/mail/mailbox/get.go @@ -5,22 +5,17 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// Objects of type Mailbox are fetched via a call to Mailbox/get The ids -// argument may be null to fetch all at once. +// Get mailbox details +// https://www.rfc-editor.org/rfc/rfc8621.html#section-2.1 type Get struct { Account jmap.ID `json:"accountId,omitempty"` - // The ids of the Mailbox objects to return. IDs []jmap.ID `json:"ids,omitempty"` - // If supplied, only the properties listed in the array are returned - // for each Mailbox object. Properties []string `json:"properties,omitempty"` - // Use IDs from a previous call ReferenceIDs *jmap.ResultReference `json:"#ids,omitempty"` - // Use Properties from a previous call ReferenceProperties *jmap.ResultReference `json:"#properties,omitempty"` } @@ -33,14 +28,10 @@ func (m *Get) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type GetResponse struct { Account jmap.ID `json:"accountId,omitempty"` - // A string representing the state on the server for all the data of - // this type in the account. State string `json:"state,omitempty"` - // List of the Mailbox objects requested. List []*Mailbox `json:"list,omitempty"` - // List of Mailbox IDs that do not exist. NotFound []string `json:"notFound,omitempty"` } diff --git a/mail/mailbox/mailbox.go b/mail/mailbox/mailbox.go index a142e3adafd3..417b644b7645 100644 --- a/mail/mailbox/mailbox.go +++ b/mail/mailbox/mailbox.go @@ -10,55 +10,30 @@ func init() { jmap.RegisterMethod("Mailbox/set", newSetResponse) } -// A Mailbox represents a named set of Emails. This is the primary mechanism -// for organising Emails within an account. It is analogous to a folder or a -// label in other systems. -// -// See RFC8621, section 2. +// Named set of Email objects. Can be viewed as a folder or a label. +// An email must be part of at least one Mailbox. +// https://www.rfc-editor.org/rfc/rfc8621.html#section-2 type Mailbox struct { - // The id of the Mailbox. ID jmap.ID `json:"id,omitempty"` - // User-visible name for the Mailbox, e.g., “Inbox”. Name string `json:"name,omitempty"` - // The Mailbox id for the parent of this Mailbox, or null if this - // Mailbox is at the top level. ParentID jmap.ID `json:"parentId,omitempty"` - // Identifies Mailboxes that have a particular common purpose (e.g., - // the “inbox”), regardless of the name property (which may be - // localised). Role Role `json:"role,omitempty"` - // Defines the sort order of Mailboxes when presented in the client’s - // UI, so it is consistent between devices. - // - // A Mailbox with a lower order should be displayed before a Mailbox - // with a higher order (that has the same parent) in any Mailbox - // listing in the client’s UI. SortOrder uint64 `json:"sortOrder,omitempty"` - // The number of Emails in this Mailbox. TotalEmails uint64 `json:"totalEmails,omitempty"` - // The number of Emails in this Mailbox that have neither the $seen - // keyword nor the $draft keyword. UnreadEmails uint64 `json:"unreadEmails,omitempty"` - // The number of Threads where at least one Email in the Thread is in - // this Mailbox. TotalThreads uint64 `json:"totalThreads,omitempty"` - // An indication of the number of “unread” Threads in the Mailbox. UnreadThreads uint64 `json:"unreadThreads,omitempty"` - // The set of rights (Access Control Lists (ACLs)) the user has in - // relation to this Mailbox. Rights *Rights `json:"myRights,omitempty"` - // true if the user indicated they wish to see this Mailbox in their - // client. IsSubscribed bool `json:"isSubscribed,omitempty"` } diff --git a/mail/mailbox/query.go b/mail/mailbox/query.go index 5f1de013808b..ce529329b3f9 100644 --- a/mail/mailbox/query.go +++ b/mail/mailbox/query.go @@ -5,41 +5,27 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard /query method as described in RFC8620, Section 5.5, -// but with the following additional request argument: sortAsTree, filterAsTree +// Get a list of mailbox IDs based on filter and sort criteria +// https://www.rfc-editor.org/rfc/rfc8621.html#section-2.3 type Query struct { Account jmap.ID `json:"accountId,omitempty"` - // Determines the set of emails returned in the results. Filter Filter `json:"filter,omitempty"` - // Lists the names of properties to compare between two Email records, Sort []*SortComparator `json:"sort,omitempty"` - // The zero-based index of the first id in the full list of results to - // return. Position int64 `json:"position,omitempty"` - // An Email id to use along with AnchorOffset. Anchor jmap.ID `json:"anchor,omitempty"` - // The index of the first result to return relative to the index of the - // anchor, if an anchor is given. AnchorOffset int64 `json:"anchorOffset,omitempty"` - // The maximum number of results to return. Limit uint64 `json:"limit,omitempty"` - // Does the client wish to know the total number of results in the - // query? CalculateTotal bool `json:"calculateTotal,omitempty"` - // Sort mailboxes according to their tree structure first, and then - // according to the Sort argument. SortAsTree bool `json:"sortAsTree,omitempty"` - // If true, a Mailbox is only included in the query if all its - // ancestors are also included in the query according to the filter. FilterAsTree bool `json:"filterAsTree,omitempty"` } @@ -50,24 +36,16 @@ func (m *Query) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type QueryResponse struct { Account jmap.ID `json:"accountId,omitempty"` - // A string encoding the current state of the query on the server. QueryState string `json:"queryState,omitempty"` - // This is true if the server supports calling Mailbox/queryChanges CanCalculateChanges bool `json:"canCalculateChanges,omitempty"` - // The zero-based index of the first result in the ids array within the - // complete list of query results. Position uint64 `json:"position,omitempty"` - // The list of ids for each Mailbox in the query results IDs []jmap.ID `json:"ids,omitempty"` - // The total number of Mailboxes in the results (given the filter). Total int64 `json:"total,omitempty"` - // The limit enforced by the server on the maximum number of results to - // return. Limit uint64 `json:"limit,omitempty"` } diff --git a/mail/mailbox/querychanges.go b/mail/mailbox/querychanges.go index ae3f57b3844f..50b89e6b86bd 100644 --- a/mail/mailbox/querychanges.go +++ b/mail/mailbox/querychanges.go @@ -5,30 +5,21 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) +// Get changes on a mailbox query +// https://www.rfc-editor.org/rfc/rfc8621.html#section-2.4 type QueryChanges struct { Account jmap.ID `json:"accountId,omitempty"` - // The filter argument that was used with Mailbox/query. Filter Filter `json:"filter,omitempty"` - // The sort argument that was used with Mailbox/query. Sort []*SortComparator `json:"sort,omitempty"` - // The current state of the query in the client. This is the string - // that was returned as the queryState argument in the Mailbox/query - // response with the same sort/filter. The server will return the - // changes made to the query since this state. SinceQueryState string `json:"sinceQueryState,omitempty"` - // The maximum number of changes to return in the response. MaxChanges uint64 `json:"maxChanges,omitempty"` - // The last (highest-index) id the client currently has cached from the - // query results. UpToID jmap.ID `json:"upToId,omitempty"` - // Does the client wish to know the total number of results now in the - // query? CalculateTotal bool `json:"calculateTotal,omitempty"` } @@ -39,17 +30,12 @@ func (m *QueryChanges) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type QueryChangesResponse struct { Account jmap.ID `json:"accountId,omitempty"` - // This is the SinceQueryState argument echoed back OldQueryState string `json:"oldQueryState,omitempty"` - // This is the state the query will be in after applying the set of - // changes to the old state. NewQueryState string `json:"newQueryState,omitempty"` - // Deleted Mailbox IDs Removed []jmap.ID `json:"removed,omitempty"` - // Added Mailbox IDs Added []*jmap.AddedItem `json:"added,omitempty"` } diff --git a/mail/mailbox/set.go b/mail/mailbox/set.go index 5b380fbd5870..9347d617aaf3 100644 --- a/mail/mailbox/set.go +++ b/mail/mailbox/set.go @@ -5,29 +5,19 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard /set method as described in RFC8620, Section 5.3, +// Create, delete & modify mailboxes +// https://www.rfc-editor.org/rfc/rfc8621.html#section-2.5 type Set struct { Account jmap.ID `json:"accountId,omitempty"` - // This is a state string as returned by the Mailbox/get method IfInState string `json:"ifInState,omitempty"` - // A map of a creation id (a temporary id set by the client) to Mailbox - // objects, or null if no objects are to be created. Create map[jmap.ID]*Mailbox `json:"create,omitempty"` - // A map of an id to a Patch object to apply to the current Mailbox - // object with that id, or null if no objects are to be updated. Update map[jmap.ID]jmap.Patch `json:"update,omitempty"` - // A list of ids for Mailbox objects to permanently delete Destroy []jmap.ID `json:"destroy,omitempty"` - // If false, any attempt to destroy a Mailbox that still has Emails in - // it will be rejected with a mailboxHasEmail SetError. If true, any - // Emails that were in the Mailbox will be removed from it, and if in - // no other Mailboxes, they will be destroyed when the Mailbox is - // destroyed. OnDestroyRemoveEmails bool `json:"onDestroyRemoveEmails,omitempty"` } @@ -38,29 +28,20 @@ func (m *Set) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type SetResponse struct { Account jmap.ID `json:"accountId,omitempty"` - // The state string that would have been returned by Mailbox/get before - // making the requested changes OldState string `json:"oldState,omitempty"` - // The state string that will now be returned by Mailbox/get. NewState string `json:"newState,omitempty"` - // Created mailboxes Created map[jmap.ID]*Mailbox `json:"created,omitempty"` - // Updated mailboxes Updated map[jmap.ID]*Mailbox `json:"updated,omitempty"` - // Deleted mailbox ids Destroyed []jmap.ID `json:"destroyed,omitempty"` - // A map of ID to a SetError for each record that failed to be created NotCreated map[jmap.ID]*jmap.SetError `json:"notCreated,omitempty"` - // A map of ID to a SetError for each record that failed to be updated NotUpdated map[jmap.ID]*jmap.SetError `json:"notUpdated,omitempty"` - // A map of ID to a SetError for each record that failed to be destroyed NotDestroyed map[jmap.ID]*jmap.SetError `json:"notDestroyed,omitempty"` } diff --git a/mail/mdn/mdn.go b/mail/mdn/mdn.go index 33d3af6d4e61..1335b19a4e91 100644 --- a/mail/mdn/mdn.go +++ b/mail/mdn/mdn.go @@ -1,10 +1,3 @@ -// Package mdn is an implementation of RFC 9007: Handling Message Disposition -// Notification with the JSON Meta Application Protocol (JMAP). In plain terms, -// it handles read receipts of emails. -// -// Documentation strings for most of the protocol objects are taken from (or -// based on) contents of RFC 9007 and is subject to the IETF Trust Provisions. -// See https://trustee.ietf.org/license-info for details. package mdn import "git.sr.ht/~rockorager/go-jmap" @@ -25,56 +18,30 @@ func (m *Capability) URI() jmap.URI { return URI } func (m *Capability) New() jmap.Capability { return &Capability{} } // A Message Delivery Notification (MDN) object +// https://www.rfc-editor.org/rfc/rfc9007.html#section-2 type MDN struct { - // The Email ID of the received message to which this MDN is related ForEmailID jmap.ID `json:"forEmailId,omitempty"` - // The Subject of the MDN Subject string `json:"subject,omitempty"` - // The human-readable part of the MDN, as plain text TextBody string `json:"textBody,omitempty"` - // If true, the content of the original message will appear in the third - // component of the multipart/report generated for the MDN IncludeOriginalmessage bool `json:"includeOriginalMessage,omitempty"` - // The name of the Mail User Agent (MUA) creating this MDN ReportingUA string `json:"reportinUA,omitempty"` - // The object containing the diverse MDN disposition options Disposition *Disposition `json:"disposition,omitempty"` - // The name of the gateway or MTA that translated a foreign - // (non-internet) MDN into this MDN - // - // server-set MDNGateway string `json:"mdnGateway,omitempty"` - // The original recipient address specified by the sender of the message - // which the MDN is for - // - // server-set OriginalRecipient string `json:"originalRecipient,omitempty"` - // The recipient for which the MDN is issued - // - // server-set FinalRecipient string `json:"finalRecipient,omitempty"` - // The "Message-ID" header field of the message this MDN is for - // - // server-set OriginalMessageID string `json:"originalMessageId,omitempty"` - // Additional information in the form of text messages when the "error" - // disposition modifier appears - // - // server-set Error []string `json:"error,omitempty"` - // The object where keys are extension-field names and values are - // extension-field values ExtensionFields map[string]string `json:"extensionFields,omitempty"` } diff --git a/mail/mdn/parse.go b/mail/mdn/parse.go index 4e978676204d..ac5b35fc6a4c 100644 --- a/mail/mdn/parse.go +++ b/mail/mdn/parse.go @@ -4,12 +4,11 @@ import ( "git.sr.ht/~rockorager/go-jmap" ) -// Sends an RFC5322 message from an MDN object +// Parse blobs as messages in the style of RFC5322 to get MDN objects +// https://www.rfc-editor.org/rfc/rfc9007.html#section-2.2 type Parse struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The IDs of blobs to parse as MDNs BlobIDs []jmap.ID `json:"blobIds,omitempty"` } @@ -18,16 +17,12 @@ func (m *Parse) Name() string { return "MDN/parse" } func (m *Parse) Requires() []jmap.URI { return []jmap.URI{URI} } type ParseResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // A map of the blob ID to the MDN resulting from the parse Parsed map[jmap.ID]*MDN `json:"parsed,omitempty"` - // A list blob IDs that could not be parsed as MDNs NotParsable []jmap.ID `json:"notParsable,omitempty"` - // A list of blob IDs that couldn't be found NotFound []jmap.ID `json:"notFound,omitempty"` } diff --git a/mail/mdn/send.go b/mail/mdn/send.go index da8e4c45c0ce..a3d14be38981 100644 --- a/mail/mdn/send.go +++ b/mail/mdn/send.go @@ -6,19 +6,14 @@ import ( ) // Sends an RFC5322 message from an MDN object +// https://www.rfc-editor.org/rfc/rfc9007.html#section-2.1 type Send struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The ID of the Identity to associate with these MDNs IdentityID jmap.ID `json:"identityId,omitempty"` - // A map of client-specified creation ID to MDN object Send map[jmap.ID]*MDN `json:"send,omitempty"` - // A map of the ID to a patch of update the Email object referenced by - // MDN/send, if the sending succeeds. The ID will always be a backward - // reference to the creation ids OnSuccessUpdateEmail map[jmap.ID]*jmap.Patch `json:"onSuccessUpdateEmail,omitempty"` } @@ -27,14 +22,10 @@ func (m *Send) Name() string { return "MDN/send" } func (m *Send) Requires() []jmap.URI { return []jmap.URI{mail.URI, URI} } type SendResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // A map of the creation ID to an MDN containing any properties that - // were not set by the client Sent map[jmap.ID]*MDN `json:"sent,omitempty"` - // A map of creation ID to a SetError for each MDN not sent NotSent map[jmap.ID]*jmap.SetError `json:"notSent,omitempty"` } diff --git a/mail/searchsnippet/get.go b/mail/searchsnippet/get.go index 32cdaf173eea..6d6ac8d3492e 100644 --- a/mail/searchsnippet/get.go +++ b/mail/searchsnippet/get.go @@ -5,20 +5,15 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) +// Get search snippet details +// https://www.rfc-editor.org/rfc/rfc8621.html#section-5.1 type Get struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // Determines the set of Foos returned in the results. If null, all - // objects in the account of this type are included in the results. - // - // Each implementation must implement it's own Filter Filter interface{} `json:"filter,omitempty"` - // The ids of the Emails to fetch snippets for. EmailIDs []jmap.ID `json:"emailIds,omitempty"` - // Use IDs from a previous call ReferenceIDs *jmap.ResultReference `json:"#emailIds,omitempty"` } @@ -27,16 +22,10 @@ func (m *Get) Name() string { return "Mailbox/get" } func (m *Get) Requires() []jmap.URI { return []jmap.URI{mail.URI} } type GetResponse struct { - // The id of the account used for the call Account jmap.ID `json:"accountId,omitempty"` - // An array of SearchSnippet objects for the requested Email ids. - // This may not be in the same order as the ids that were in the - // request. List []*SearchSnippet `json:"list,omitempty"` - // An array of Email ids requested that could not be found, or null - // if all ids were found. NotFound []jmap.ID `json:"notFound,omitempty"` } diff --git a/mail/searchsnippet/searchsnippet.go b/mail/searchsnippet/searchsnippet.go index 7053cefc99d7..317da6e06447 100644 --- a/mail/searchsnippet/searchsnippet.go +++ b/mail/searchsnippet/searchsnippet.go @@ -6,34 +6,12 @@ func init() { jmap.RegisterMethod("SearchSnippet/get", newGetResponse) } -// When doing a search on a "String" property, the client may wish to -// show the relevant section of the body that matches the search as a -// preview and to highlight any matching terms in both this and the -// subject of the Email. Search snippets represent this data. +// Search preview snippet +// https://www.rfc-editor.org/rfc/rfc8621.html#section-5 type SearchSnippet struct { - // The Email id the snippet applies to. Email jmap.ID `json:"emailId,omitempty"` - // If text from the filter matches the subject, this is the subject - // of the Email with the following transformations: - // - // 1. Any instance of the following three characters MUST be - // replaced by an appropriate HTML entity: & (ampersand), < - // (less-than sign), and > (greater-than sign) [HTML]. Other - // characters MAY also be replaced with an HTML entity form. - // - // 2. The matching words/phrases from the filter are wrapped in HTML - // "<mark></mark>" tags. - // - // If the subject does not match text from the filter, this property - // is null. Subject string `json:"subject,omitempty"` - // If text from the filter matches the plaintext or HTML body, this is - // the relevant section of the body (converted to plaintext if - // originally HTML), with the same transformations as the "subject" - // property. It MUST NOT be bigger than 255 octets in size. If the - // body does not contain a match for the text from the filter, this - // property is null. Preview string `json:"preview,omitempty"` } diff --git a/mail/thread/changes.go b/mail/thread/changes.go index b383d01b058d..ff38836b5418 100644 --- a/mail/thread/changes.go +++ b/mail/thread/changes.go @@ -5,22 +5,12 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard “/changes” method as described in [@!RFC8620], Section 5.2. +// See RFC8621, Section 3.2. type Changes struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The current state of the client. This is the string that was - // returned as the state argument in the Foo/get response. The server - // will return the changes that have occurred since this state. SinceState string `json:"sinceState,omitempty"` - // The maximum number of ids to return in the response. The server MAY - // choose to return fewer than this value but MUST NOT return more. If - // not given by the client, the server may choose how many to return. - // If supplied by the client, the value MUST be a positive integer - // greater than 0. If a value outside of this range is given, the - // server MUST reject the call with an invalidArguments error. MaxChanges uint64 `json:"maxChanges,omitempty"` } @@ -28,34 +18,19 @@ func (m *Changes) Name() string { return "Thread/changes" } func (m *Changes) Requires() []jmap.URI { return []jmap.URI{mail.URI} } -// This is a standard “/changes” method as described in [@!RFC8620], Section 5.2. type ChangesResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // This is the sinceState argument echoed back; it’s the state from - // which the server is returning changes. OldState string `json:"oldState,omitempty"` - // This is the state the client will be in after applying the set of - // changes to the old state. NewState string `json:"newState,omitempty"` - // If true, the client may call Foo/changes again with the newState - // returned to get further updates. If false, newState is the current - // server state. HasMoreChanges bool `json:"hasMoreChanges,omitempty"` - // An array of ids for records that have been created since the old - // state. Created []jmap.ID `json:"created,omitempty"` - // An array of ids for records that have been updated since the old - // state. Updated []jmap.ID `json:"updated,omitempty"` - // An array of ids for records that have been destroyed since the old - // state. Destroyed []jmap.ID `json:"destroyed,omitempty"` } diff --git a/mail/thread/get.go b/mail/thread/get.go index c0c534c92e63..51c9d11a404e 100644 --- a/mail/thread/get.go +++ b/mail/thread/get.go @@ -5,27 +5,16 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. +// See RFC8621, Section 3.1. type Get struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The ids of the Foo objects to return. If null, then all records of - // the data type are returned, if this is supported for that data type - // and the number of records does not exceed the maxObjectsInGet limit. IDs []jmap.ID `json:"ids,omitempty"` - // If supplied, only the properties listed in the array are returned - // for each Foo object. If null, all properties of the object are - // returned. The id property of the object is always returned, even if - // not explicitly requested. If an invalid property is requested, the - // call MUST be rejected with an invalidArguments error. Properties []string `json:"properties,omitempty"` - // Use IDs from a previous call ReferenceIDs *jmap.ResultReference `json:"#ids,omitempty"` - // Use Properties from a previous call ReferenceProperties *jmap.ResultReference `json:"#properties,omitempty"` } @@ -33,35 +22,13 @@ func (m *Get) Name() string { return "Thread/get" } func (m *Get) Requires() []jmap.URI { return []jmap.URI{mail.URI} } -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. type GetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // A (preferably short) string representing the state on the server for - // all the data of this type in the account (not just the objects - // returned in this call). If the data changes, this string MUST - // change. If the Foo data is unchanged, servers SHOULD return the same - // state string on subsequent requests for this data type. - // - // When a client receives a response with a different state string to a - // previous call, it MUST either throw away all currently cached - // objects for the type or call Foo/changes to get the exact changes. State string `json:"state,omitempty"` - // An array of the Foo objects requested. This is the empty array - // if no objects were found or if the ids argument passed in was also - // an empty array. The results MAY be in a different order to the ids - // in the request arguments. If an identical id is included more than - // once in the request, the server MUST only include it once in either - // the list or the notFound argument of the response. - // - // Each specification must define it's own List property List []*Thread `json:"list,omitempty"` - // This array contains the ids passed to the method for records that do - // not exist. The array is empty if all requested ids were found or if - // the ids argument passed in was either null or an empty array. NotFound []jmap.ID `json:"notFound,omitempty"` } diff --git a/mail/thread/thread.go b/mail/thread/thread.go index 324eda46fb25..42614399b8ac 100644 --- a/mail/thread/thread.go +++ b/mail/thread/thread.go @@ -7,20 +7,9 @@ func init() { jmap.RegisterMethod("Thread/changes", newChangesResponse) } -// Replies are grouped together with the original message to form a Thread. In -// JMAP, a Thread is simply a flat list of Emails, ordered by date. Every Email -// MUST belong to a Thread, even if it is the only Email in the Thread. +// See RFC8621, Section 3. type Thread struct { - // The ID of the thread - // - // immutable;server-set ID jmap.ID `json:"id,omitempty"` - // The ids of the Emails in the Thread, sorted by the receivedAt date - // of the Email, oldest first. If two Emails have an identical date, - // the sort is server dependent but MUST be stable (sorting by id is - // recommended). - // - // server-set EmailIDs []jmap.ID `json:"emailIds,omitempty"` } diff --git a/mail/vacationresponse/get.go b/mail/vacationresponse/get.go index fe19ffd02be7..fe090ba10404 100644 --- a/mail/vacationresponse/get.go +++ b/mail/vacationresponse/get.go @@ -5,16 +5,13 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// An Identity/get request +// Get vacation response details +// https://www.rfc-editor.org/rfc/rfc8621.html#section-8.1 type Get struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // The IDs of Identity objects to return. Leave blank to return all, - // subject to the MaxObjectsInGet limit of the server IDs []jmap.ID `json:"ids,omitempty"` - // Only the supplied properties will be returned Properties []string `json:"properties,omitempty"` } @@ -22,19 +19,13 @@ func (m *Get) Name() string { return "VacationResponse/get" } func (m *Get) Requires() []jmap.URI { return []jmap.URI{mail.URI, URI} } -// This is a standard “/get” method as described in [@!RFC8620], Section 5.1. type GetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // State for all Identity objects on the server for this account State string `json:"state,omitempty"` - // The Identity objects requested List []*VacationResponse `json:"list,omitempty"` - // Slice of objects not found. Only present if specific IDs were - // requested NotFound []jmap.ID `json:"notFound,omitempty"` } diff --git a/mail/vacationresponse/set.go b/mail/vacationresponse/set.go index ecf540f38d51..37c054293990 100644 --- a/mail/vacationresponse/set.go +++ b/mail/vacationresponse/set.go @@ -5,70 +5,17 @@ import ( "git.sr.ht/~rockorager/go-jmap/mail" ) -// An Identity/set method call +// Create, update & modify vacation responses +// https://www.rfc-editor.org/rfc/rfc8621.html#section-8.2 type Set struct { - // The id of the account to use. Account jmap.ID `json:"accountId,omitempty"` - // This is a state string as returned by the Foo/get method - // (representing the state of all objects of this type in the account). - // If supplied, the string must match the current state; otherwise, the - // method will be aborted and a stateMismatch error returned. If null, - // any changes will be applied to the current state. IfInState string `json:"ifInState,omitempty"` - // A map of a creation id (a temporary id set by the client) to Foo - // objects, or null if no objects are to be created. - // - // The Foo object type definition may define default values for - // properties. Any such property may be omitted by the client. - // - // The client MUST omit any properties that may only be set by the - // server (for example, the id property on most object types). Create map[jmap.ID]*VacationResponse `json:"create,omitempty"` - // A map of an id to a Patch object to apply to the current Foo object - // with that id, or null if no objects are to be updated. - // - // A PatchObject is of type String[*] and represents an unordered set - // of patches. The keys are a path in JSON Pointer Format [@!RFC6901], - // with an implicit leading “/” (i.e., prefix each key with “/” before - // applying the JSON Pointer evaluation algorithm). - // - // All paths MUST also conform to the following restrictions; if there - // is any violation, the update MUST be rejected with an invalidPatch - // error: - // - // The pointer MUST NOT reference inside an array (i.e., you MUST - // NOT insert/delete from an array; the array MUST be replaced in - // its entirety instead). All parts prior to the last (i.e., the - // value after the final slash) MUST already exist on the object - // being patched. There MUST NOT be two patches in the PatchObject - // where the pointer of one is the prefix of the pointer of the - // other, e.g., “alerts/1/offset” and “alerts”. - // - // The value associated with each pointer determines how to apply that - // patch: - // - // If null, set to the default value if specified for this - // property; otherwise, remove the property from the patched - // object. If the key is not present in the parent, this a no-op. - // Anything else: The value to set for this property (this may be a - // replacement or addition to the object being patched). - // - // Any server-set properties MAY be included in the patch if their - // value is identical to the current server value (before applying the - // patches to the object). Otherwise, the update MUST be rejected with - // an invalidProperties SetError. - // - // This patch definition is designed such that an entire Foo object is - // also a valid PatchObject. The client may choose to optimise network - // usage by just sending the diff or may send the whole object; the - // server processes it the same either way. Update map[jmap.ID]jmap.Patch `json:"update,omitempty"` - // A list of ids for Foo objects to permanently delete, or null if no - // objects are to be destroyed. Destroy []jmap.ID `json:"destroy,omitempty"` } @@ -77,48 +24,22 @@ func (m *Set) Name() string { return "VacationResponse/set" } func (m *Set) Requires() []jmap.URI { return []jmap.URI{mail.URI, URI} } type SetResponse struct { - // The id of the account used for the call. Account jmap.ID `json:"accountId,omitempty"` - // The state string that would have been returned by Foo/get before - // making the requested changes, or null if the server doesn’t know - // what the previous state string was. OldState string `json:"oldState,omitempty"` - // The state string that will now be returned by Foo/get. NewState string `json:"newState,omitempty"` - // A map of the creation id to an object containing any properties of - // the created Foo object that were not sent by the client. This - // includes all server-set properties (such as the id in most object - // types) and any properties that were omitted by the client and thus - // set to a default by the server. - // - // This argument is null if no Foo objects were successfully created. Created map[jmap.ID]*VacationResponse `json:"created,omitempty"` - // The keys in this map are the ids of all Foos that were successfully - // updated. - // - // The value for each id is a Foo object containing any property that - // changed in a way not explicitly requested by the PatchObject sent to - // the server, or null if none. This lets the client know of any - // changes to server-set or computed properties. - // - // This argument is null if no Foo objects were successfully updated. Updated map[jmap.ID]*VacationResponse `json:"updated,omitempty"` - // An array of ids for records that have been destroyed since the old - // state. Destroyed []jmap.ID `json:"destroyed,omitempty"` - // A map of ID to a SetError for each record that failed to be created NotCreated map[jmap.ID]*jmap.SetError `json:"notCreated,omitempty"` - // A map of ID to a SetError for each record that failed to be updated NotUpdated map[jmap.ID]*jmap.SetError `json:"notUpdated,omitempty"` - // A map of ID to a SetError for each record that failed to be destroyed NotDestroyed map[jmap.ID]*jmap.SetError `json:"notDestroyed,omitempty"` } diff --git a/mail/vacationresponse/vacationresponse.go b/mail/vacationresponse/vacationresponse.go index 2967218ce8fe..73798b1a48e8 100644 --- a/mail/vacationresponse/vacationresponse.go +++ b/mail/vacationresponse/vacationresponse.go @@ -22,32 +22,21 @@ func (m *Capability) URI() jmap.URI { return URI } func (m *Capability) New() jmap.Capability { return &Capability{} } +// Automatic reply when a message is delivered to the mail store +// https://www.rfc-editor.org/rfc/rfc8621.html#section-8 type VacationResponse struct { - // The ID of the object. There is only ever one VacationResponse object, - // and it's ID is constant: "singleton" - // - // immutable;server-set;constant ID string `json:"id,omitempty"` - // If the response is enabled IsEnabled bool `json:"isEnabled,omitempty"` - // If IsEnabled is true, the response is active for messages received - // after this time. Must be UTC FromDate *time.Time `json:"fromDate,omitempty"` - // If IsEnabled is true, the response is active for messages received - // before this time. Must be UTC ToDate *time.Time `json:"toDate,omitempty"` - // The subject for the response. If null, the server MAY set a suitable - // subject Subject *string `json:"subject,omitempty"` - // The plaintext body to send in the response TextBody *string `json:"textBody,omitempty"` - // The HTML body to send in the response HTMLBody *string `json:"htmlBody,omitempty"` } -- 2.41.0