~rockorager/go-jmap

b781340c11039bb2284d4eaa36ab40933efe4234 — Tim Culverhouse 8 months ago 139a261
jmap: reword comments

RFC text cannot be quoted and modified, per copyright. Reword comments
that come from RFCs

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
M account.go => account.go +2 -12
@@ 2,9 2,7 @@ package jmap

import "encoding/json"

// An account is a collection of data. A single account may contain an
// arbitrary set of data types, for example a collection of mail, contacts and
// calendars.
// An account is a collection of data the authenticated user has access to
//
// See RFC 8620 section 1.6.2 for details.
type Account struct {


@@ 15,20 13,12 @@ type Account struct {
	// account, e.g. the email address representing the owner of the account.
	Name string `json:"name"`

	// This is true if the account belongs to the authenticated user, rather
	// than a group account or a personal account of another user that has been
	// shared with them.
	// True if this account belongs to the authenticated user
	IsPersonal bool `json:"isPersonal"`

	// This is true if the entire account is read-only.
	IsReadOnly bool `json:"isReadOnly"`

	// The set of capability URIs for the methods supported in this account.
	// Each key is a URI for a capability that has methods you can use with
	// this account. The value for each of these keys is an object with further
	// information about the account’s permissions and restrictions with
	// respect to this capability, as defined in the capability’s
	// specification.
	Capabilities map[URI]Capability `json:"-"`

	// The raw JSON of accountCapabilities

M core/blob/copy.go => core/blob/copy.go +1 -2
@@ 5,8 5,7 @@ import (
	"git.sr.ht/~rockorager/go-jmap/core"
)

// Binary data may be copied between two different accounts using the Blob/copy
// method rather than having to download and then reupload on the client.
// Copy copies data between accounts
type Copy struct {
	// The ID of the account to copy blobs from
	FromAccount jmap.ID `json:"fromAccountId,omitempty"`

M core/core.go => core/core.go +7 -27
@@ 10,39 10,19 @@ func init() {
}

type Core struct {
	// The maximum file size, in octets, that the server will accept for a
	// single file upload (for any purpose).
	MaxSizeUpload uint64 `json:"maxSizeUpload"`

	// The maximum number of concurrent requests the server will accept to the
	// upload endpoint.
	// The maximum file size, in bytes
	MaxSizeUpload       uint64 `json:"maxSizeUpload"`
	MaxConcurrentUpload uint64 `json:"maxConcurrentUpload"`

	// The maximum size, in octets, that the server will accept for a single
	// request to the API endpoint.
	MaxSizeRequest uint64 `json:"maxSizeRequest"`

	// The maximum number of concurrent requests the server will accept to the
	// API endpoint.
	// The maximum size, in bytes, that the server will accept for a request
	MaxSizeRequest        uint64 `json:"maxSizeRequest"`
	MaxConcurrentRequests uint64 `json:"maxConcurrentRequests"`

	// The maximum number of method calls the server will accept in a single
	// request to the API endpoint.
	MaxCallsInRequest uint64 `json:"maxCallsInRequest"`
	MaxCallsInRequest     uint64 `json:"maxCallsInRequest"`

	// The maximum number of objects that the client may request in a single
	// /get type method call.
	MaxObjectsInGet uint64 `json:"maxObjectsInGet"`

	// The maximum number of objects the client may send to create, update or
	// destroy in a single /set type method call. This is the combined total, e.g.
	// if the maximum is 10 you could not create 7 objects and destroy 6, as this
	// would be 13 actions, which exceeds the limit.
	MaxObjectsInSet uint64 `json:"maxObjectsInSet"`

	// A list of identifiers for algorithms registered in the collation
	// registry defined in RFC 4790 that the server supports for sorting
	// when querying records.
	MaxObjectsInGet     uint64               `json:"maxObjectsInGet"`
	MaxObjectsInSet     uint64               `json:"maxObjectsInSet"`
	CollationAlgorithms []jmap.CollationAlgo `json:"collationAlgorithms"`
}


M core/push/subscription/get.go => core/push/subscription/get.go +5 -27
@@ 5,20 5,10 @@ import (
	"git.sr.ht/~rockorager/go-jmap/core"
)

// Get the active Push Subscriptions that were created with the same
// authentication credentials used to make the call
// This is a standard “/get” method as described in [@!RFC8620], Section 5.1.
type Get struct {
	// 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"`
	IDs        []jmap.ID `json:"ids,omitempty"`
	Properties []string  `json:"properties,omitempty"`
}

func (m *Get) Name() string { return "PushSubscription/get" }


@@ 27,20 17,8 @@ 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 {
	// 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 []*PushSubscription `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"`
	List     []*PushSubscription `json:"list,omitempty"`
	NotFound []jmap.ID           `json:"notFound,omitempty"`
}

func newGetResponse() jmap.MethodResponse { return &GetResponse{} }

M core/push/subscription/set.go => core/push/subscription/set.go +9 -85
@@ 6,59 6,9 @@ import (
)

type Set struct {
	// 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]*PushSubscription `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"`
	Create  map[jmap.ID]*PushSubscription `json:"create,omitempty"`
	Update  map[jmap.ID]*jmap.Patch       `json:"update,omitempty"`
	Destroy []jmap.ID                     `json:"destroy,omitempty"`
}

func (m *Set) Name() string { return "PushSubscription/set" }


@@ 66,38 16,12 @@ func (m *Set) Name() string { return "PushSubscription/set" }
func (m *Set) Requires() []jmap.URI { return []jmap.URI{core.URI} }

type SetResponse struct {
	// 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]*PushSubscription `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]*PushSubscription `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"`
	Created      map[jmap.ID]*PushSubscription `json:"created,omitempty"`
	Updated      map[jmap.ID]*PushSubscription `json:"updated,omitempty"`
	Destroyed    []jmap.ID                     `json:"destroyed,omitempty"`
	NotCreated   map[jmap.ID]*jmap.SetError    `json:"notCreated,omitempty"`
	NotUpdated   map[jmap.ID]*jmap.SetError    `json:"notUpdated,omitempty"`
	NotDestroyed map[jmap.ID]*jmap.SetError    `json:"notDestroyed,omitempty"`
}

func newSetResponse() jmap.MethodResponse { return &SetResponse{} }

M core/push/subscription/verification.go => core/push/subscription/verification.go +1 -3
@@ 1,9 1,7 @@
package subscription

// A PushVerification object is sent by the server to the created Subscriptions'
// URL. This object contains the ID of the subscription and the Verification
// code required. The Client must update the PushSubscription using a Set method
// with the code.
// URL.
type Verification struct {
	// The MUST be "PushVerification"
	Type string `json:"@type,omitempty"`

M jmap.go => jmap.go +5 -73
@@ 1,9 1,5 @@
// Package jmap implements JMAP Core protocol
// as defined in RFC 8620 published on July 2019.
//
// Documentation strings for most of the protocol objects are taken from (or
// based on) contents of RFC 8620 and is subject to the IETF Trust Provisions.
// See https://trustee.ietf.org/license-info for details.
package jmap

import (


@@ 19,9 15,7 @@ func init() {
// URI is an identifier of a capability, eg "urn:ietf:params:jmap:core"
type URI string

// ID is a unique identifier assigned by the server. The character set must
// contain only ASCII alphanumerics, hyphen, or underscore and the ID must be
// between 1 and 255 octets long.
// ID is a unique identifier assigned by the server
type ID string

var idRegexp = regexp.MustCompile(`^[A-Za-z0-9\-_]+$`)


@@ 33,25 27,11 @@ func (id ID) MarshalJSON() ([]byte, error) {
	if len(string(id)) > 255 {
		return nil, fmt.Errorf("invalid ID: too long")
	}
	// if !idRegexp.MatchString(string(id)) {
	// 	return nil, fmt.Errorf("invalid ID: invalid characters")
	// }
	return json.Marshal(string(id))
}

// Patch represents a patch which can be used in a set.Update call.
// 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 Patch where the pointer of one is the prefix of
//	the pointer of the other, e.g., “alerts/1/offset” and “alerts”.
//
// The keys are a JSON pointer path, and the value is the value to set the path
// to
// Patch is a JMAP patch object which can be used in set.Update calls. The keys
// are json pointer paths, and the value is the value to set the path to.
type Patch map[string]interface{}

// Operator is used when constructing FilterOperator. It MUST be "AND", "OR", or


@@ 69,34 49,13 @@ const (
	OperatorNOT Operator = "NOT"
)

// 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.
// AddedItem is an item that has been added to the results of a Query
type AddedItem struct {
	ID    ID     `json:"id"`
	Index uint64 `json:"index"`
}

// To allow clients to make more efficient use of the network and avoid round
// trips, an argument to one method can be taken from the result of a previous
// method call in the same request.
//
// To do this, the client prefixes the argument name with # (an octothorpe). The
// value is a ResultReference object as described below. When processing a
// method call, the server MUST first check the arguments object for any names
// beginning with #. If found, the result reference should be resolved and the
// value used as the “real” argument. The method is then processed as normal. If
// any result reference fails to resolve, the whole method MUST be rejected with
// an invalidResultReference error. If an arguments object contains the same
// argument name in normal and referenced form (e.g., foo and #foo), the method
// MUST return an invalidArguments error.
// ResultReference is a reference to a previous Invocations' result
type ResultReference struct {
	// The method call id (see Section 3.1.1) of a previous method call in
	// the current request.


@@ 115,39 74,12 @@ type ResultReference struct {
type CollationAlgo string

const (
	// The ASCIINumeric collation is a simple collation intended for use
	// with arbitrary sized unsigned decimal integer numbers stored as octet
	// strings. US-ASCII digits (0x30 to 0x39) represent digits of the numbers.
	// Before converting from string to integer, the input string is truncated
	// at the first non-digit character. All input is valid; strings which do
	// not start with a digit represent positive infinity.
	//
	// Defined in RFC 4790.
	ASCIINumeric CollationAlgo = "i;ascii-numeric"

	// The ASCIICasemap collation is a simple collation which operates on
	// octet strings and treats US-ASCII letters case-insensitively. It provides
	// equality, substring and ordering operations. All input is valid. Note that
	// letters outside ASCII are not treated case- insensitively.
	//
	// Defined in RFC 4790.
	ASCIICasemap = "i;ascii-casemap"

	// The "i;unicode-casemap" collation is a simple collation which is
	// case-insensitive in its treatment of characters. It provides equality,
	// substring, and ordering operations. The validity test operation returns "valid"
	// for any input.
	//
	// This collation allows strings in arbitrary (and mixed) character sets,
	// as long as the character set for each string is identified and it is
	// possible to convert the string to Unicode. Strings which have an
	// unidentified character set and/or cannot be converted to Unicode are not
	// rejected, but are treated as binary.
	//
	// Defined in RFC 5051.
	UnicodeCasemap = "i;unicode-casemap"

	// Octet collation is left out intentionally: "Protocols that want to make
	// this collation available have to do so by explicitly allowing it. If not
	// explicitly allowed, it MUST NOT be used."
)

M mail/email/changes.go => mail/email/changes.go +7 -32
@@ 6,24 6,14 @@ import (
)

// This is a standard "/changes" method as described in [RFC8620], Section 5.2.
// If generating intermediate states for a large set of changes, it is
// recommended that newer changes be returned first, as these are generally of
// more interest to users.
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.
	// The current state of the client
	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.
	// The maximum number of ids to return in the response
	MaxChanges uint64 `json:"maxChanges,omitempty"`
}



@@ 32,36 22,21 @@ 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.
// If generating intermediate states for a large set of changes, it is
// recommended that newer changes be returned first, as these are generally of
// more interest to users.
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.
	// 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.
	// The state the client will be in after applying the Changes
	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.
	// If true, not all changes were returned in this response
	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.
	Created   []jmap.ID `json:"created,omitempty"`
	Updated   []jmap.ID `json:"updated,omitempty"`
	Destroyed []jmap.ID `json:"destroyed,omitempty"`
}


M mail/email/copy.go => mail/email/copy.go +11 -61
@@ 6,50 6,18 @@ import (
)

// This is a standard "/copy" method as described in [RFC8620], Section 5.4,
// except only the "mailboxIds", "keywords", and "receivedAt" properties may be
// set during the copy.  This method cannot modify the message represented by
// the Email.
type Copy struct {
	// The id of the account to copy records from.
	FromAccount jmap.ID `json:"fromAccountId,omitempty"`

	// This is a state string as returned by the Foo/get method. If
	// supplied, the string must match the current state of the account
	// referenced by the fromAccountId when reading the data to be copied;
	// otherwise, the method will be aborted and a stateMismatch error
	// returned. If null, the data will be read from the current state.
	IfFromInState string `json:"ifFromInState,omitempty"`
	FromAccount   jmap.ID `json:"fromAccountId,omitempty"`
	IfFromInState string  `json:"ifFromInState,omitempty"`

	// The id of the account to copy records to. This MUST be different to
	// the fromAccountId.
	Account jmap.ID `json:"accountId,omitempty"`

	// This is a state string as returned by the Foo/get method. If
	// supplied, the string must match the current state of the account
	// referenced by the accountId; 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 the creation id to a Foo object. The Foo object MUST
	// contain an id property, which is the id (in the fromAccount) of the
	// record to be copied. When creating the copy, any other properties
	// included are used instead of the current value for that property on
	// the original.
	Create map[jmap.ID]*Email `json:"create,omitempty"`

	// If true, an attempt will be made to destroy the original records
	// that were successfully copied: after emitting the Foo/copy response,
	// but before processing the next method, the server MUST make a single
	// call to Foo/set to destroy the original of each successfully copied
	// record; the output of this is added to the responses as normal, to
	// be returned to the client.
	OnSuccessDestroyOriginal bool `json:"onSuccessDestroyOriginal,omitempty"`

	// This argument is passed on as the ifInState argument to the implicit
	// Foo/set call, if made at the end of this request to destroy the
	// originals that were successfully copied.
	DestroyFromIfInState string `json:"destroyFromIfInState,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" }


@@ 61,28 29,10 @@ type CopyResponse struct {
	FromAccount jmap.ID `json:"fromAccountId,omitempty"`

	// The id of the account records were copied to.
	Account jmap.ID `json:"accountId,omitempty"`

	// The state string that would have been returned by Foo/get on the
	// account records that were copied to 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 on the account
	// records were copied to.
	NewState string `json:"newState,omitempty"`

	// A map of the creation id to an object containing any properties of
	// the copied Foo object that are set by the server (such as the id in
	// most object types; note, the id is likely to be different to the id
	// of the object in the account it was copied from).
	//
	// This argument is null if no Foo objects were successfully copied.
	Created map[jmap.ID]*Email `json:"created,omitempty"`

	// A map of the creation id to a SetError object for each record that
	// failed to be copied, or null if none.
	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"`
}


M mail/email/email.go => mail/email/email.go +29 -126
@@ 40,92 40,66 @@ type Email struct {
	// is destroyed).
	MailboxIDs map[jmap.ID]bool `json:"mailboxIds,omitempty"`

	// A set of keywords that apply to the Email. The set is represented as
	// an object, with the keys being the keywords. The value for each key
	// in the object MUST be true.
	// 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 octets, of the raw data for the message [@!RFC5322] (as
	// referenced by the blobId, i.e., the number of octets in the file the
	// user would download).
	// The size, in bytes, of the message
	//
	// immutable;server-set
	Size uint64 `json:"size,omitempty"`

	// The date the Email was received by the message store. This is the
	// internal date in IMAP [@?RFC3501].
	// 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 [@!RFC5322], in the same order
	// they appear in the message.
	// This is a list of all header fields, in the same order they appear in
	// the message.
	//
	// immutable
	Headers []*Header `json:"headers,omitempty"`

	// The value is identical to the value of
	// header:Message-ID:asMessageIds. For messages conforming to RFC 5322
	// this will be an array with a single entry.
	// The Message-ID of the email. For conforming messages, this will be
	// len() == 1
	//
	// immutable
	MessageID []string `json:"messageId,omitempty"`

	// The value is identical to the value of
	// header:In-Reply-To:asMessageIds.
	//
	// immutable
	InReplyTo []string `json:"inReplyTo,omitempty"`

	// The value is identical to the value of
	// header:References:asMessageIds.mailAccount
	//
	// immutable
	References []string `json:"references,omitempty"`

	// The value is identical to the value of header:Sender:asAddresses.
	//
	// immutable
	Sender []*mail.Address `json:"sender,omitempty"`

	// The value is identical to the value of header:From:asAddresses.
	//
	// immutable
	From []*mail.Address `json:"from,omitempty"`

	// The value is identical to the value of header:To:asAddresses.
	//
	// immutable
	To []*mail.Address `json:"to,omitempty"`

	// The value is identical to the value of header:Cc:asAddresses.
	//
	// immutable
	CC []*mail.Address `json:"cc,omitempty"`

	// The value is identical to the value of header:Bcc:asAddresses.
	//
	// immutable
	BCC []*mail.Address `json:"bcc,omitempty"`

	// The value is identical to the value of header:Reply-To:asAddresses.
	//
	// immutable
	ReplyTo []*mail.Address `json:"replyTo,omitempty"`

	// The value is identical to the value of header:Subject:asText.
	//
	// immutable
	Subject string `json:"subject,omitempty"`

	// The value is identical to the value of header:Date:asDate.
	// 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. Note that
	// EmailBodyParts may have subParts if they are of type multipart/*.
	// recursing into message/rfc822 or message/global parts.
	//
	// immutable
	BodyStructure *BodyPart `json:"bodyStructure,omitempty"`


@@ 160,46 134,14 @@ type Email struct {
	//     of type image/*, audio/*, or video/* and not in both textBody
	//     and htmlBody
	//
	// None of these parts include subParts, including message/* types.
	// Attached messages may be fetched using the Email/parse method and
	// the blobId.
	//
	// Note that a text/html body part HTML may reference image parts in
	// attachments by using cid: links to reference the Content-Id, as
	// defined in [@!RFC2392], or by referencing the Content-Location.
	//
	// immutable
	Attachments []*BodyPart `json:"attachments,omitempty"`

	// This is true if there are one or more parts in the message that a
	// client UI should offer as downloadable. A server SHOULD set
	// hasAttachment to true if the attachments list contains at least one
	// item that does not have Content-Disposition: inline. The server MAY
	// ignore parts in this list that are processed automatically in some
	// way or are referenced as embedded images in one of the text/html
	// parts of the message.
	//
	// The server MAY set hasAttachment based on implementation-defined or
	// site-configurable heuristics.
	//
	// immutable;server-set
	HasAttachment bool `json:"hasAttachment,omitempty"`

	// A plaintext fragment of the message body. This is intended to be
	// shown as a preview line when listing messages in the mail store and
	// may be truncated when shown. The server may choose which part of the
	// message to include in the preview; skipping quoted sections and
	// salutations and collapsing white space can result in a more useful
	// preview.
	//
	// This MUST NOT be more than 256 characters in length.
	//
	// As this is derived from the message content by the server, and the
	// algorithm for doing so could change over time, fetching this for an
	// Email a second time MAY return a different result. However, the
	// previous value is not considered incorrect, and the change SHOULD
	// NOT cause the Email object to be considered as changed by the
	// server.
	// 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"`


@@ 242,63 184,43 @@ type Email struct {
}

type AddressGroup struct {
	// The display-name of the group [@!RFC5322], or null if the addresses
	// are not part of a group. If this is a quoted-string, it is processed
	// the same as the name in the EmailAddress type.
	Name string `json:"name,omitempty"`

	// The mailbox values that belong to this group, represented as
	// EmailAddress objects.
	// The display-name of the group
	Name      string          `json:"name,omitempty"`
	Addresses []*mail.Address `json:"addresses,omitempty"`
}

type Header struct {
	// The header field name as defined in [@!RFC5322], with the same
	// capitalization that it has in the message.
	// The header field name, with the same capitalization that it has in
	// the message.
	Name string `json:"name,omitempty"`

	// The header field value as defined in [@!RFC5322], in Raw form.
	// The header field value in Raw form.
	Value string `json:"value,omitempty"`
}

// These properties are derived from the message body [@!RFC5322] and its MIME
// entities [@RFC2045].
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
	// representation. This is null if, and only if, the part is of type
	// multipart/*.
	//
	// Multipart messages do not have a PartID
	PartID string `json:"partId,omitempty"`

	// The id representing the raw octets of the contents of the part,
	// after decoding any known Content-Transfer-Encoding (as defined in
	// [@!RFC2045]), or null if, and only if, the part is of type
	// multipart/*. Note that two parts may be transfer-encoded differently
	// but have the same blob id if their decoded octets are identical and
	// the server is using a secure hash of the data for the blob id. If
	// the transfer encoding is unknown, it is treated as though it had no
	// transfer encoding.
	// The Blob ID representing this Part
	BlobID jmap.ID `json:"blobId,omitempty"`

	// The size, in octets, of the raw data after content transfer decoding
	// (as referenced by the blobId, i.e., the number of octets in the file
	// the user would download).
	// 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"`

	// This is the decoded filename parameter of the Content-Disposition
	// header field per [@!RFC2231], or (for compatibility with existing
	// systems) if not present, then it’s the decoded name parameter of the
	// Content-Type header field per [@!RFC2047].
	// 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). CFWS is removed and
	// any parameters are stripped.
	// message/rfc822 if inside a multipart/digest)
	Type string `json:"type,omitempty"`

	// The value of the charset parameter of the Content-Type header


@@ 314,18 236,15 @@ type BodyPart struct {
	Disposition string `json:"disposition,omitempty"`

	// The value of the Content-Id header field of the part, if present;
	// otherwise it’s null. CFWS and surrounding angle brackets (<>) are
	// removed. This may be used to reference the content from within a
	// text/html body part HTML using the cid: protocol, as defined in
	// [@!RFC2392].
	// otherwise it’s null.
	CID string `json:"cid,omitempty"`

	// The list of language tags, as defined in [@!RFC3282], in the
	// 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.
	// 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


@@ 333,27 252,11 @@ type BodyPart struct {
	SubParts []*BodyPart `json:"subParts,omitempty"`
}

// This is a map of partId to an BodyValue 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.
type BodyValue struct {
	// The value of the body part after decoding Content-Transfer-Encoding
	// and the Content-Type charset, if both known to the server, and with
	// any CRLF replaced with a single LF. The server MAY use heuristics to
	// determine the charset to use for decoding if the charset is unknown,
	// no charset is given, or it believes the charset given is incorrect.
	// Decoding is best effort; the server SHOULD insert the unicode
	// replacement character (U+FFFD) and continue when a malformed section
	// is encountered.
	//
	// Note that due to the charset decoding and line ending normalisation,
	// the length of this string will probably not be exactly the same as
	// the size property on the corresponding EmailBodyPart.
	// The value of the BodyValue
	Value string `json:"value,omitempty"`

	// This is true if malformed sections were found while decoding the
	// charset, or the charset was unknown, or the
	// content-transfer-encoding was unknown.
	// True if there was an encoding problem
	IsEncodingProblem bool `json:"isEncodingProblem,omitempty"`

	// This is true if the value has been truncated

M mail/email/filter.go => mail/email/filter.go +2 -7
@@ 11,14 11,9 @@ type Filter interface {
	implementsFilter()
}

// Determines the set of Emails 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"`
	Operator   jmap.Operator `json:"operator,omitempty"`
	Conditions []Filter      `json:"conditions,omitempty"`
}

func (fo *FilterOperator) implementsFilter() {}

M mail/mail.go => mail/mail.go +5 -63
@@ 1,9 1,5 @@
// Package mail is an implementation of JSON Metal Application Protocol (JMAP)
// for MAIL (RFC 8621)
//
// Documentation strings for most of the protocol objects are taken from (or
// based on) contents of RFC 8621 and is subject to the IETF Trust Provisions.
// See https://trustee.ietf.org/license-info for details.
package mail

import (


@@ 46,56 42,14 @@ func init() {
}

type Mail struct {
	// The maximum number of Mailboxes (see Section 2) that can be can
	// assigned to a single Email object (see Section 4). This MUST be an
	// integer >= 1, or null for no limit (or rather, the limit is always
	// the number of Mailboxes in the account).
	MaxMailboxesPerEmail uint64 `json:"maxMailboxesPerEmail"`

	// The maximum depth of the Mailbox hierarchy (i.e., one more than the
	// maximum number of ancestors a Mailbox may have), or null for no
	// limit.
	MaxMailboxDepth uint64 `json:"maxMailboxDepth"`

	// The maximum length, in (UTF-8) octets, allowed for the name of a
	// Mailbox. This MUST be at least 100, although it is recommended
	// servers allow more.
	MaxSizeMailboxName uint64 `json:"maxSizeMailboxName"`

	// The maximum total size of attachments, in octets, allowed for a
	// single Email object. A server MAY still reject the import or
	// creation of an Email with a lower attachment size total (for
	// example, if the body includes several megabytes of text, causing the
	// size of the encoded MIME structure to be over some server-defined
	// limit).
	//
	// Note that this limit is for the sum of unencoded attachment sizes.
	// Users are generally not knowledgeable about encoding overhead, etc.,
	// nor should they need to be, so marketing and help materials normally
	// tell them the “max size attachments”. This is the unencoded size
	// they see on their hard drive, so this capability matches that and
	// allows the client to consistently enforce what the user understands
	// as the limit.
	//
	// The server may separately have a limit for the total size of the
	// message [@!RFC5322], created by combining the attachments (often
	// base64 encoded) with the message headers and bodies. For example,
	// suppose the server advertises maxSizeAttachmentsPerEmail: 50000000
	// (50 MB). The enforced server limit may be for a message size of
	// 70000000 octets. Even with base64 encoding and a 2 MB HTML body, 50
	// MB attachments would fit under this limit.
	MaxMailboxesPerEmail       uint64 `json:"maxMailboxesPerEmail"`
	MaxMailboxDepth            uint64 `json:"maxMailboxDepth"`
	MaxSizeMailboxName         uint64 `json:"maxSizeMailboxName"`
	MaxSizeAttachmentsPerEmail uint64 `json:"maxSizeAttachmentsPerEmail"`

	// A list of all the values the server supports for the “property”
	// field of the Comparator object in an Email/query sort (see Section
	// 4.4.2). This MAY include properties the client does not recognise
	// (for example, custom properties specified in a vendor extension).
	// Clients MUST ignore any unknown properties in the list.
	// A list of all values the server supports for sorting
	EmailQuerySortOptions []string `json:"emailQuerySortOptions"`

	// If true, the user may create a Mailbox (see Section 2) in this
	// account with a null parentId. (Permission for creating a child of an
	// existing Mailbox is given by the myRights property on that Mailbox.)
	MayCreateTopLevelMailbox bool `json:"mayCreateTopLevelMailbox"`
}



@@ 105,19 59,7 @@ func (m *Mail) New() jmap.Capability { return &Mail{} }

// An Email address
type Address struct {
	// The display-name of the mailbox [@!RFC5322]. If this is a
	// quoted-string:
	//
	//     The surrounding DQUOTE characters are removed. Any quoted-pair
	//     is decoded. White space is unfolded, and then any leading and
	//     trailing white space is removed.
	//
	// If there is no display-name but there is a comment immediately
	// following the addr-spec, the value of this SHOULD be used instead.
	// Otherwise, this property is null.
	Name string `json:"name,omitempty"`

	// The addr-spec of the mailbox [@!RFC5322].
	Name  string `json:"name,omitempty"`
	Email string `json:"email,omitempty"`
}


M response.go => response.go +4 -8
@@ 1,17 1,13 @@
package jmap

type Response struct {
	// An array of responses, in the same format as the Calls on the
	// Request object. The output of the methods will be added to the
	// methodResponses array in the same order as the methods are processed.
	// Responses are the responses to the request, in the same order that
	// the request was made
	Responses []*Invocation `json:"methodResponses"`

	// A map of (client-specified) creation id to the id the server assigned
	// when a record was successfully created.
	// A map of client-specified ID to server-assigned ID
	CreatedIDs map[ID]ID `json:"createdIds,omitempty"`

	// The current value of the “state” string on the JMAP Session object, as
	// described in section 2. Clients may use this to detect if this object
	// has changed and needs to be refetched.
	// SessionState is the current state of the Session
	SessionState string `json:"sessionState"`
}

M session.go => session.go +8 -25
@@ 5,50 5,33 @@ import (
)

type Session struct {
	// An object specifying the capabilities of this server. Each key is a URI
	// for a capability supported by the server. The value for each of these
	// keys is an object with further information about the server’s
	// capabilities in relation to that capability.
	// Capabilities specifies the capabililities the server has.
	Capabilities map[URI]Capability `json:"-"`

	RawCapabilities map[URI]json.RawMessage `json:"capabilities"`

	// A map of account id to Account object for each account the user has
	// access to.
	Accounts map[ID]Account `json:"accounts"`

	// A map of capability URIs (as found in Capabilities) to the
	// account id to be considered the user’s main or default account for data
	// pertaining to that capability.
	// PrimaryAccounts maps a Capability to the primary account associated
	// with it
	PrimaryAccounts map[URI]ID `json:"primaryAccounts"`

	// The username associated with the given credentials, or the empty string
	// if none.
	// The username associated with the given credentials
	Username string `json:"username"`

	// The URL to use for JMAP API requests.
	APIURL string `json:"apiUrl"`

	// The URL endpoint to use when downloading files, in RFC 6570 URI
	// Template (level 1) format.
	// The URL endpoint to use when downloading files
	DownloadURL string `json:"downloadUrl"`

	// The URL endpoint to use when uploading files, in RFC 6570 URI
	// Template (level 1) format.
	// The URL endpoint to use when uploading files
	UploadURL string `json:"uploadUrl"`

	// The URL to connect to for push events, as described in section 7.3, in
	// RFC 6570 URI Template (level 1) format.
	// The URL to connect to for push events
	EventSourceURL string `json:"eventSourceUrl"`

	// A string representing the state of this object on the server. If the
	// value of any other property on the session object changes, this string
	// will change.
	//
	// The current value is also returned on the API Response object, allowing
	// clients to quickly determine if the session information has changed
	// (e.g. an account has been added or removed) and so they need to refetch
	// the object.
	// A string representing the state of this object on the server
	State string `json:"state"`
}


M statechange.go => statechange.go +2 -0
@@ 3,6 3,8 @@ package jmap
// An EventType is the name of a Type provided by a capability which may be
// subscribed to using a PushSubscription or an EventSource connection. Each
// specification may define their own types and events
//
// EventType is the type of object the Event is for ("Mailbox", "Email")
type EventType string

// Subscribe to all events