groupware: refactor responses to a jmap.Response object

* in the JMAP API as well as in several places in the Groupware
   framework, use a single jmap.Response[T] object to return the
   payload, the language, the session state and the etag/state instead
   of individual multi-valued return values
This commit is contained in:
Pascal Bleser
2026-04-29 14:58:26 +02:00
parent ac52a4f967
commit 0cbf4b7287
42 changed files with 827 additions and 719 deletions

View File

@@ -62,3 +62,57 @@ const (
logBlobId = "blob-id"
logSinceState = "since-state"
)
type ResultMetadata interface {
GetSessionState() SessionState
GetState() State
GetLanguage() Language
}
type Result[T any] struct {
Payload T
SessionState SessionState
State State
Language Language
}
func RefineResult[A, B any](a Result[A], refiner func(A) B) Result[B] {
return newResult(
refiner(a.Payload),
a.SessionState,
a.State,
a.Language,
)
}
func (r Result[T]) GetSessionState() SessionState {
return r.SessionState
}
func (r Result[T]) GetState() State {
return r.State
}
func (r Result[T]) GetLanguage() Language {
return r.Language
}
func newResult[T any](result T, sessionState SessionState, state State, language Language) Result[T] {
return Result[T]{
Payload: result,
SessionState: sessionState,
State: state,
Language: language,
}
}
func newPartialResult[T any](sessionState SessionState, language Language) Result[T] {
return Result[T]{
SessionState: sessionState,
Language: language,
}
}
func ZeroResult[T any]() Result[T] {
return Result[T]{}
}

View File

@@ -2,7 +2,7 @@ package jmap
var NS_ADDRESSBOOKS = ns(JmapContacts)
func (j *Client) GetAddressbooks(accountId string, ids []string, ctx Context) (AddressBookGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetAddressbooks(accountId string, ids []string, ctx Context) (Result[AddressBookGetResponse], Error) {
return get(j, "GetAddressbooks", MailboxType,
func(accountId string, ids []string) AddressBookGetCommand {
return AddressBookGetCommand{AccountId: accountId, Ids: ids}
@@ -27,7 +27,7 @@ func (c AddressBookChanges) GetDestroyed() []string { return c.Destroyed }
// Retrieve Address Book changes since a given state.
// @apidoc addressbook,changes
func (j *Client) GetAddressbookChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (AddressBookChanges, SessionState, State, Language, Error) {
func (j *Client) GetAddressbookChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (Result[AddressBookChanges], Error) {
return changesA(j, "GetAddressbookChanges", MailboxType,
func() AddressBookChangesCommand {
return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
@@ -58,7 +58,7 @@ func (j *Client) GetAddressbookChanges(accountId string, sinceState State, maxCh
)
}
func (j *Client) CreateAddressBook(accountId string, addressbook AddressBookChange, ctx Context) (*AddressBook, SessionState, State, Language, Error) {
func (j *Client) CreateAddressBook(accountId string, addressbook AddressBookChange, ctx Context) (Result[*AddressBook], Error) {
return create(j, "CreateAddressBook", MailboxType,
func(accountId string, create map[string]AddressBookChange) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Create: create}
@@ -77,7 +77,7 @@ func (j *Client) CreateAddressBook(accountId string, addressbook AddressBookChan
)
}
func (j *Client) DeleteAddressBook(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
func (j *Client) DeleteAddressBook(accountId string, destroyIds []string, ctx Context) (Result[map[string]SetError], Error) {
return destroy(j, "DeleteAddressBook", MailboxType,
func(accountId string, destroy []string) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Destroy: destroy}
@@ -88,7 +88,7 @@ func (j *Client) DeleteAddressBook(accountId string, destroyIds []string, ctx Co
)
}
func (j *Client) UpdateAddressBook(accountId string, id string, changes AddressBookChange, ctx Context) (AddressBook, SessionState, State, Language, Error) {
func (j *Client) UpdateAddressBook(accountId string, id string, changes AddressBookChange, ctx Context) (Result[AddressBook], Error) {
return update(j, "UpdateAddressBook", MailboxType,
func(update map[string]PatchObject) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Update: update}

View File

@@ -10,7 +10,7 @@ import (
var NS_BLOB = ns(JmapBlob)
func (j *Client) GetBlobMetadata(accountId string, ids []string, ctx Context) (BlobGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetBlobMetadata(accountId string, ids []string, ctx Context) (Result[BlobGetResponse], Error) {
get := BlobGetCommand{
AccountId: accountId,
Ids: ids,
@@ -21,7 +21,7 @@ func (j *Client) GetBlobMetadata(accountId string, ids []string, ctx Context) (B
invocation(get, "0"),
)
if jerr != nil {
return bail[BlobGetResponse](jerr)
return ZeroResult[BlobGetResponse](), jerr
}
return command(j, ctx, cmd, func(body *Response) (BlobGetResponse, State, Error) {
@@ -62,7 +62,7 @@ func (j *Client) DownloadBlobStream(accountId string, blobId string, name string
return j.blob.DownloadBinary(downloadUrl, ctx.Session.DownloadEndpoint, ctx)
}
func (j *Client) UploadBlob(accountId string, data []byte, contentType string, ctx Context) (UploadedBlobWithHash, SessionState, State, Language, Error) {
func (j *Client) UploadBlob(accountId string, data []byte, contentType string, ctx Context) (Result[UploadedBlobWithHash], Error) {
encoded := base64.StdEncoding.EncodeToString(data)
upload := BlobUploadCommand{
@@ -92,7 +92,7 @@ func (j *Client) UploadBlob(accountId string, data []byte, contentType string, c
invocation(getHash, "1"),
)
if jerr != nil {
return UploadedBlobWithHash{}, "", "", "", jerr
return ZeroResult[UploadedBlobWithHash](), jerr
}
return command(j, ctx, cmd, func(body *Response) (UploadedBlobWithHash, State, Error) {

View File

@@ -11,7 +11,7 @@ type AccountBootstrapResult struct {
var NS_MAIL_QUOTA = ns(JmapMail, JmapQuota)
func (j *Client) GetBootstrap(accountIds []string, ctx Context) (map[string]AccountBootstrapResult, SessionState, State, Language, Error) { //NOSONAR
func (j *Client) GetBootstrap(accountIds []string, ctx Context) (Result[map[string]AccountBootstrapResult], Error) { //NOSONAR
uniqueAccountIds := structs.Uniq(accountIds)
logger := j.logger("GetBootstrap", ctx)
@@ -25,7 +25,7 @@ func (j *Client) GetBootstrap(accountIds []string, ctx Context) (map[string]Acco
cmd, err := j.request(ctx, NS_MAIL_QUOTA, calls...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]AccountBootstrapResult](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string]AccountBootstrapResult, State, Error) {
identityPerAccount := map[string][]Identity{}

View File

@@ -2,7 +2,7 @@ package jmap
var NS_CALENDARS = ns(JmapCalendars)
func (j *Client) ParseICalendarBlob(accountId string, blobIds []string, ctx Context) (CalendarEventParseResponse, SessionState, State, Language, Error) {
func (j *Client) ParseICalendarBlob(accountId string, blobIds []string, ctx Context) (Result[CalendarEventParseResponse], Error) {
logger := j.logger("ParseICalendarBlob", ctx)
parse := CalendarEventParseCommand{AccountId: accountId, BlobIds: blobIds}
@@ -10,7 +10,7 @@ func (j *Client) ParseICalendarBlob(accountId string, blobIds []string, ctx Cont
invocation(parse, "0"),
)
if err != nil {
return CalendarEventParseResponse{}, "", "", "", err
return ZeroResult[CalendarEventParseResponse](), err
}
return command(j, ctx, cmd, func(body *Response) (CalendarEventParseResponse, State, Error) {
@@ -23,7 +23,7 @@ func (j *Client) ParseICalendarBlob(accountId string, blobIds []string, ctx Cont
})
}
func (j *Client) GetCalendars(accountId string, ids []string, ctx Context) (CalendarGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetCalendars(accountId string, ids []string, ctx Context) (Result[CalendarGetResponse], Error) {
return get(j, "GetCalendars", CalendarType,
func(accountId string, ids []string) CalendarGetCommand {
return CalendarGetCommand{AccountId: accountId, Ids: ids}
@@ -48,7 +48,7 @@ func (c CalendarChanges) GetDestroyed() []string { return c.Destroyed }
// Retrieve Calendar changes since a given state.
// @apidoc calendar,changes
func (j *Client) GetCalendarChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (CalendarChanges, SessionState, State, Language, Error) {
func (j *Client) GetCalendarChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (Result[CalendarChanges], Error) {
return changes(j, "GetCalendarChanges", CalendarType,
func() CalendarChangesCommand {
return CalendarChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
@@ -79,7 +79,7 @@ func (j *Client) GetCalendarChanges(accountId string, sinceState State, maxChang
)
}
func (j *Client) CreateCalendar(accountId string, calendar CalendarChange, ctx Context) (*Calendar, SessionState, State, Language, Error) {
func (j *Client) CreateCalendar(accountId string, calendar CalendarChange, ctx Context) (Result[*Calendar], Error) {
return create(j, "CreateCalendar", CalendarEventType,
func(accountId string, create map[string]CalendarChange) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Create: create}
@@ -98,7 +98,7 @@ func (j *Client) CreateCalendar(accountId string, calendar CalendarChange, ctx C
)
}
func (j *Client) DeleteCalendar(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
func (j *Client) DeleteCalendar(accountId string, destroyIds []string, ctx Context) (Result[map[string]SetError], Error) {
return destroy(j, "DeleteCalendar", CalendarEventType,
func(accountId string, destroy []string) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Destroy: destroy}
@@ -109,7 +109,7 @@ func (j *Client) DeleteCalendar(accountId string, destroyIds []string, ctx Conte
)
}
func (j *Client) UpdateCalendar(accountId string, id string, changes CalendarChange, ctx Context) (Calendar, SessionState, State, Language, Error) {
func (j *Client) UpdateCalendar(accountId string, id string, changes CalendarChange, ctx Context) (Result[Calendar], Error) {
return update(j, "UpdateCalendar", CalendarEventType,
func(update map[string]PatchObject) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Update: update}

View File

@@ -74,7 +74,7 @@ func (s StateMap) MarshalZerologObject(e *zerolog.Event) {
// Retrieve the changes in any type of objects at once since a given State.
// @api:tags changes
func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint, ctx Context) (ObjectChanges, SessionState, State, Language, Error) { //NOSONAR
func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint, ctx Context) (Result[ObjectChanges], Error) { //NOSONAR
logger := log.From(j.logger("GetChanges", ctx).With().Object("state", stateMap).Uint("maxChanges", maxChanges))
ctx = ctx.WithLogger(logger)
@@ -107,7 +107,7 @@ func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint
cmd, err := j.request(ctx, NS_CHANGES, methodCalls...)
if err != nil {
return ObjectChanges{}, "", "", "", err
return ZeroResult[ObjectChanges](), err
}
return command(j, ctx, cmd, func(body *Response) (ObjectChanges, State, Error) {

View File

@@ -6,7 +6,7 @@ var NS_CONTACTS = ns(JmapContacts)
var DEFAULT_CONTACT_CARD_VERSION = jscontact.JSContactVersion_1_0
func (j *Client) GetContactCards(accountId string, contactIds []string, ctx Context) (ContactCardGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetContactCards(accountId string, contactIds []string, ctx Context) (Result[ContactCardGetResponse], Error) {
return get(j, "GetContactCards", ContactCardType,
func(accountId string, ids []string) ContactCardGetCommand {
return ContactCardGetCommand{AccountId: accountId, Ids: contactIds}
@@ -31,7 +31,7 @@ func (c ContactCardChanges) GetDestroyed() []string { return c.Destroyed }
// Retrieve the changes in Contact Cards since a given State.
// @api:tags contact,changes
func (j *Client) GetContactCardChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (ContactCardChanges, SessionState, State, Language, Error) {
func (j *Client) GetContactCardChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (Result[ContactCardChanges], Error) {
return changes(j, "GetContactCardChanges", ContactCardType,
func() ContactCardChangesCommand {
return ContactCardChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
@@ -80,7 +80,7 @@ func (r *ContactCardSearchResults) SetPosition(position *uint) { r.Position = po
func (j *Client) QueryContactCards(accountIds []string, //NOSONAR
filter ContactCardFilterElement, sortBy []ContactCardComparator,
position int, anchor string, anchorOffset *int, limit *uint, calculateTotal bool,
ctx Context) (map[string]*ContactCardSearchResults, SessionState, State, Language, Error) {
ctx Context) (Result[map[string]*ContactCardSearchResults], Error) {
return queryN(j, "QueryContactCards", ContactCardType,
[]ContactCardComparator{{Property: ContactCardPropertyUpdated, IsAscending: false}},
func(accountId string, filter ContactCardFilterElement, sortBy []ContactCardComparator, position int, anchor string, anchorOffset *int, limit *uint) ContactCardQueryCommand {
@@ -104,7 +104,7 @@ func (j *Client) QueryContactCards(accountIds []string, //NOSONAR
}
// @api:example create
func (j *Client) CreateContactCard(accountId string, contact ContactCardChange, ctx Context) (*ContactCard, SessionState, State, Language, Error) {
func (j *Client) CreateContactCard(accountId string, contact ContactCardChange, ctx Context) (Result[*ContactCard], Error) {
if contact.Version == nil {
contact.Version = &DEFAULT_CONTACT_CARD_VERSION
}
@@ -126,7 +126,7 @@ func (j *Client) CreateContactCard(accountId string, contact ContactCardChange,
)
}
func (j *Client) DeleteContactCard(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
func (j *Client) DeleteContactCard(accountId string, destroyIds []string, ctx Context) (Result[map[string]SetError], Error) {
return destroy(j, "DeleteContactCard", ContactCardType,
func(accountId string, destroy []string) ContactCardSetCommand {
return ContactCardSetCommand{AccountId: accountId, Destroy: destroy}
@@ -138,7 +138,7 @@ func (j *Client) DeleteContactCard(accountId string, destroyIds []string, ctx Co
}
// @api:example update
func (j *Client) UpdateContactCard(accountId string, id string, changes ContactCardChange, ctx Context) (ContactCard, SessionState, State, Language, Error) {
func (j *Client) UpdateContactCard(accountId string, id string, changes ContactCardChange, ctx Context) (Result[ContactCard], Error) {
return update(j, "UpdateContactCard", ContactCardType,
func(update map[string]PatchObject) ContactCardSetCommand {
return ContactCardSetCommand{AccountId: accountId, Update: update}

View File

@@ -13,15 +13,10 @@ import (
var NS_MAIL = ns(JmapMail)
var NS_MAIL_SUBMISSION = ns(JmapMail, JmapSubmission)
type getEmailsResult struct {
emails []Email
notFound []string
}
// Retrieve specific Emails by their id.
func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR
fetchBodies bool, maxBodyValueBytes uint, markAsSeen bool, withThreads bool,
ctx Context) ([]Email, []string, SessionState, State, Language, Error) {
ctx Context) (Result[EmailGetResponse], Error) {
logger := j.logger("GetEmails", ctx)
ctx = ctx.WithLogger(logger)
@@ -56,46 +51,45 @@ func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR
cmd, err := j.request(ctx, NS_MAIL, methodCalls...)
if err != nil {
return nil, nil, "", "", "", err
return ZeroResult[EmailGetResponse](), err
}
result, sessionState, state, language, gwerr := command(j, ctx, cmd, func(body *Response) (getEmailsResult, State, Error) {
return command(j, ctx, cmd, func(body *Response) (EmailGetResponse, State, Error) {
if markAsSeen {
var markResponse EmailSetResponse
err = retrieveSet(ctx, body, markEmails, "0", &markResponse)
if err != nil {
return getEmailsResult{}, "", err
return EmailGetResponse{}, "", err
}
for _, seterr := range markResponse.NotUpdated {
// TODO we don't have a way to compose multiple set errors yet
return getEmailsResult{}, "", setErrorError(seterr, EmailType)
return EmailGetResponse{}, "", setErrorError(seterr, EmailType)
}
}
var response EmailGetResponse
err = retrieveGet(ctx, body, getEmails, "1", &response)
if err != nil {
return getEmailsResult{}, "", err
return EmailGetResponse{}, "", err
}
if withThreads {
var threads ThreadGetResponse
err = retrieveGet(ctx, body, getThreads, "2", &threads)
if err != nil {
return getEmailsResult{}, "", err
return EmailGetResponse{}, "", err
}
setThreadSize(&threads, response.List)
}
return getEmailsResult{emails: response.List, notFound: response.NotFound}, response.State, nil
return response, response.State, nil
})
return result.emails, result.notFound, sessionState, state, language, gwerr
}
func (j *Client) GetEmailBlobId(accountId string, id string, ctx Context) (string, SessionState, State, Language, Error) {
func (j *Client) GetEmailBlobId(accountId string, id string, ctx Context) (Result[string], Error) {
logger := j.logger("GetEmailBlobId", ctx)
ctx = ctx.WithLogger(logger)
get := EmailGetCommand{AccountId: accountId, Ids: []string{id}, FetchAllBodyValues: false, Properties: []string{"blobId"}}
cmd, err := j.request(ctx, NS_MAIL, invocation(get, "0"))
if err != nil {
return "", "", "", "", err
return ZeroResult[string](), err
}
return command(j, ctx, cmd, func(body *Response) (string, State, Error) {
var response EmailGetResponse
@@ -127,7 +121,7 @@ func (r *EmailSearchResults) SetPosition(position *uint) { r.Posi
// Retrieve all the Emails in a given Mailbox by its id.
func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOSONAR
position int, anchor string, anchorOffset *int, limit *uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool,
ctx Context) (*EmailSearchResults, SessionState, State, Language, Error) {
ctx Context) (Result[*EmailSearchResults], Error) {
logger := j.loggerParams("GetAllEmailsInMailbox", ctx, func(z zerolog.Context) zerolog.Context {
l := z.Bool(logFetchBodies, fetchBodies).Int(logPosition, position)
if limit != nil {
@@ -178,7 +172,7 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOS
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[*EmailSearchResults](), err
}
return command(j, ctx, cmd, func(body *Response) (*EmailSearchResults, State, Error) {
@@ -228,7 +222,7 @@ func (c EmailChanges) GetDestroyed() []string { return c.Destroyed }
// @api:tags email,changes
func (j *Client) GetEmailChanges(accountId string,
sinceState State, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint,
ctx Context) (EmailChanges, SessionState, State, Language, Error) { //NOSONAR
ctx Context) (Result[EmailChanges], Error) { //NOSONAR
logger := j.loggerParams("GetEmailChanges", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, string(sinceState))
})
@@ -265,7 +259,7 @@ func (j *Client) GetEmailChanges(accountId string,
invocation(getUpdated, "2"),
)
if err != nil {
return EmailChanges{}, "", "", "", err
return ZeroResult[EmailChanges](), err
}
return command(j, ctx, cmd, func(body *Response) (EmailChanges, State, Error) {
@@ -310,7 +304,7 @@ type EmailSnippetSearchResults SearchResultsTemplate[SearchSnippetWithMeta]
func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
filter EmailFilterElement, position int, anchor string, anchorOffset *int, limit *uint,
ctx Context) (map[string]EmailSnippetSearchResults, SessionState, State, Language, Error) {
ctx Context) (Result[map[string]EmailSnippetSearchResults], Error) {
logger := j.loggerParams("QueryEmailSnippets", ctx, func(z zerolog.Context) zerolog.Context {
l := z.Int(logPosition, position)
if limit != nil {
@@ -364,7 +358,7 @@ func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]EmailSnippetSearchResults](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string]EmailSnippetSearchResults, State, Error) {
@@ -434,7 +428,7 @@ type EmailQueryResult struct {
func (j *Client) QueryEmails(accountIds []string,
filter EmailFilterElement, position int, limit uint, fetchBodies bool, maxBodyValueBytes uint,
ctx Context) (map[string]EmailQueryResult, SessionState, State, Language, Error) { //NOSONAR
ctx Context) (Result[map[string]EmailQueryResult], Error) { //NOSONAR
logger := j.loggerParams("QueryEmails", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies)
})
@@ -474,7 +468,7 @@ func (j *Client) QueryEmails(accountIds []string,
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]EmailQueryResult](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string]EmailQueryResult, State, Error) {
@@ -519,7 +513,7 @@ type EmailQueryWithSnippetsResult struct {
func (j *Client) QueryEmailsWithSnippets(accountIds []string, //NOSONAR
filter EmailFilterElement, position int, anchor string, anchorOffset *int, limit *uint, collapseThreads bool, calculateTotal bool, fetchBodies bool, maxBodyValueBytes uint,
ctx Context) (map[string]EmailQueryWithSnippetsResult, SessionState, State, Language, Error) {
ctx Context) (Result[map[string]EmailQueryWithSnippetsResult], Error) {
logger := j.loggerParams("QueryEmailsWithSnippets", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies)
})
@@ -567,7 +561,7 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, //NOSONAR
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]EmailQueryWithSnippetsResult](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string]EmailQueryWithSnippetsResult, State, Error) {
@@ -631,7 +625,7 @@ type UploadedEmail struct {
Sha512 string `json:"sha:512"`
}
func (j *Client) ImportEmail(accountId string, data []byte, ctx Context) (UploadedEmail, SessionState, State, Language, Error) {
func (j *Client) ImportEmail(accountId string, data []byte, ctx Context) (Result[UploadedEmail], Error) {
encoded := base64.StdEncoding.EncodeToString(data)
upload := BlobUploadCommand{
@@ -661,7 +655,7 @@ func (j *Client) ImportEmail(accountId string, data []byte, ctx Context) (Upload
invocation(getHash, "1"),
)
if err != nil {
return UploadedEmail{}, "", "", "", err
return ZeroResult[UploadedEmail](), err
}
return command(j, ctx, cmd, func(body *Response) (UploadedEmail, State, Error) {
@@ -704,7 +698,7 @@ func (j *Client) ImportEmail(accountId string, data []byte, ctx Context) (Upload
}
func (j *Client) CreateEmail(accountId string, email EmailChange, replaceId string, ctx Context) (*Email, SessionState, State, Language, Error) {
func (j *Client) CreateEmail(accountId string, email EmailChange, replaceId string, ctx Context) (Result[*Email], Error) {
set := EmailSetCommand{
AccountId: accountId,
Create: map[string]EmailChange{
@@ -719,7 +713,7 @@ func (j *Client) CreateEmail(accountId string, email EmailChange, replaceId stri
invocation(set, "0"),
)
if err != nil {
return nil, "", "", "", err
return ZeroResult[*Email](), err
}
return command(j, ctx, cmd, func(body *Response) (*Email, State, Error) {
@@ -759,14 +753,14 @@ func (j *Client) CreateEmail(accountId string, email EmailChange, replaceId stri
// To create drafts, use the CreateEmail function instead.
//
// To delete mails, use the DeleteEmails function instead.
func (j *Client) UpdateEmails(accountId string, updates map[string]PatchObject, ctx Context) (map[string]*Email, SessionState, State, Language, Error) {
func (j *Client) UpdateEmails(accountId string, updates map[string]PatchObject, ctx Context) (Result[map[string]*Email], Error) {
set := EmailSetCommand{
AccountId: accountId,
Update: updates,
}
cmd, err := j.request(ctx, NS_MAIL, invocation(set, "0"))
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]*Email](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string]*Email, State, Error) {
@@ -785,7 +779,7 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]PatchObject,
})
}
func (j *Client) UpdateEmail(accountId string, id string, changes EmailChange, ctx Context) (Email, SessionState, State, Language, Error) {
func (j *Client) UpdateEmail(accountId string, id string, changes EmailChange, ctx Context) (Result[Email], Error) {
return update(j, "UpdateEmail", EmailType,
func(update map[string]PatchObject) EmailSetCommand {
return EmailSetCommand{AccountId: accountId, Update: update}
@@ -800,7 +794,7 @@ func (j *Client) UpdateEmail(accountId string, id string, changes EmailChange, c
)
}
func (j *Client) DeleteEmails(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
func (j *Client) DeleteEmails(accountId string, destroyIds []string, ctx Context) (Result[map[string]SetError], Error) {
return destroy(j, "DeleteEmails", EmailType,
func(accountId string, destroy []string) EmailSetCommand {
return EmailSetCommand{AccountId: accountId, Destroy: destroy}
@@ -841,7 +835,7 @@ type MoveMail struct {
}
func (j *Client) SubmitEmail(accountId string, identityId string, emailId string, move *MoveMail, //NOSONAR
ctx Context) (EmailSubmission, SessionState, State, Language, Error) {
ctx Context) (Result[EmailSubmission], Error) {
logger := j.logger("SubmitEmail", ctx)
ctx = ctx.WithLogger(logger)
@@ -880,7 +874,7 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
invocation(get, "1"),
)
if err != nil {
return EmailSubmission{}, "", "", "", err
return ZeroResult[EmailSubmission](), err
}
return command(j, ctx, cmd, func(body *Response) (EmailSubmission, State, Error) {
@@ -928,12 +922,7 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
})
}
type emailSubmissionResult struct {
submissions map[string]EmailSubmission
notFound []string
}
func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []string, ctx Context) (map[string]EmailSubmission, []string, SessionState, State, Language, Error) {
func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []string, ctx Context) (Result[EmailSubmissionGetResponse], Error) {
logger := j.logger("GetEmailSubmissionStatus", ctx)
ctx = ctx.WithLogger(logger)
@@ -943,28 +932,22 @@ func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []stri
}
cmd, err := j.request(ctx, NS_MAIL_SUBMISSION, invocation(get, "0"))
if err != nil {
return nil, nil, "", "", "", err
return ZeroResult[EmailSubmissionGetResponse](), err
}
result, sessionState, state, lang, err := command(j, ctx, cmd, func(body *Response) (emailSubmissionResult, State, Error) {
return command(j, ctx, cmd, func(body *Response) (EmailSubmissionGetResponse, State, Error) {
var response EmailSubmissionGetResponse
err = retrieveGet(ctx, body, get, "0", &response)
if err != nil {
return emailSubmissionResult{}, "", err
return EmailSubmissionGetResponse{}, "", err
}
m := make(map[string]EmailSubmission, len(response.List))
for _, s := range response.List {
m[s.Id] = s
}
return emailSubmissionResult{submissions: m, notFound: response.NotFound}, response.State, nil
return response, response.State, nil
})
return result.submissions, result.notFound, sessionState, state, lang, err
}
func (j *Client) EmailsInThread(accountId string, threadId string,
fetchBodies bool, maxBodyValueBytes uint,
ctx Context) ([]Email, SessionState, State, Language, Error) { //NOSONAR
ctx Context) (Result[[]Email], Error) { //NOSONAR
logger := j.loggerParams("EmailsInThread", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str("threadId", log.SafeString(threadId))
})
@@ -991,7 +974,7 @@ func (j *Client) EmailsInThread(accountId string, threadId string,
invocation(get, "1"),
)
if err != nil {
return nil, "", "", "", err
return ZeroResult[[]Email](), err
}
return command(j, ctx, cmd, func(body *Response) ([]Email, State, Error) {
@@ -1033,7 +1016,7 @@ var EmailSummaryProperties = []string{
func (j *Client) QueryEmailSummaries(accountIds []string, //NOSONAR
filter EmailFilterElement, position int, anchor string, anchorOffset *int, limit *uint, withThreads bool, calculateTotal bool,
ctx Context) (map[string]EmailsSummary, SessionState, State, Language, Error) {
ctx Context) (Result[map[string]EmailsSummary], Error) {
logger := j.logger("QueryEmailSummaries", ctx)
ctx = ctx.WithLogger(logger)
@@ -1080,7 +1063,7 @@ func (j *Client) QueryEmailSummaries(accountIds []string, //NOSONAR
}
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]EmailsSummary](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string]EmailsSummary, State, Error) {
@@ -1126,7 +1109,7 @@ type EmailSubmissionChanges = ChangesTemplate[EmailSubmission]
// Retrieve the changes in Email Submissions since a given State.
// @api:tags email,changes
func (j *Client) GetEmailSubmissionChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (EmailSubmissionChanges, SessionState, State, Language, Error) {
ctx Context) (Result[EmailSubmissionChanges], Error) {
return changes(j, "GetEmailSubmissionChanges", EmailSubmissionType,
func() EmailSubmissionChangesCommand {
return EmailSubmissionChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}

View File

@@ -17,7 +17,7 @@ func (r *CalendarEventSearchResults) RemoveResults() { r.Results = n
func (r *CalendarEventSearchResults) SetLimit(limit *uint) { r.Limit = limit }
func (r *CalendarEventSearchResults) SetPosition(position *uint) { r.Position = position }
func (j *Client) GetCalendarEvents(accountId string, eventIds []string, ctx Context) (CalendarEventGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetCalendarEvents(accountId string, eventIds []string, ctx Context) (Result[CalendarEventGetResponse], Error) {
return get(j, "GetCalendarEvents", CalendarEventType,
func(accountId string, ids []string) CalendarEventGetCommand {
return CalendarEventGetCommand{AccountId: accountId, Ids: eventIds}
@@ -32,7 +32,7 @@ func (j *Client) GetCalendarEvents(accountId string, eventIds []string, ctx Cont
func (j *Client) QueryCalendarEvents(accountIds []string, //NOSONAR
filter CalendarEventFilterElement, sortBy []CalendarEventComparator,
position int, anchor string, anchorOffset *int, limit *uint, calculateTotal bool,
ctx Context) (map[string]*CalendarEventSearchResults, SessionState, State, Language, Error) {
ctx Context) (Result[map[string]*CalendarEventSearchResults], Error) {
return queryN(j, "QueryCalendarEvents", CalendarEventType,
[]CalendarEventComparator{{Property: CalendarEventPropertyStart, IsAscending: false}},
func(accountId string, filter CalendarEventFilterElement, sortBy []CalendarEventComparator, position int, anchor string, anchorOffset *int, limit *uint) CalendarEventQueryCommand {
@@ -69,7 +69,7 @@ func (c CalendarEventChanges) GetDestroyed() []string { return c.Destroyed
// Retrieve the changes in Calendar Events since a given State.
// @api:tags event,changes
func (j *Client) GetCalendarEventChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (CalendarEventChanges, SessionState, State, Language, Error) {
ctx Context) (Result[CalendarEventChanges], Error) {
return changes(j, "GetCalendarEventChanges", CalendarEventType,
func() CalendarEventChangesCommand {
return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
@@ -100,7 +100,7 @@ func (j *Client) GetCalendarEventChanges(accountId string, sinceState State, max
)
}
func (j *Client) CreateCalendarEvent(accountId string, event CalendarEventChange, ctx Context) (*CalendarEvent, SessionState, State, Language, Error) {
func (j *Client) CreateCalendarEvent(accountId string, event CalendarEventChange, ctx Context) (Result[*CalendarEvent], Error) {
return create(j, "CreateCalendarEvent", CalendarEventType,
func(accountId string, create map[string]CalendarEventChange) CalendarEventSetCommand {
return CalendarEventSetCommand{AccountId: accountId, Create: create}
@@ -119,7 +119,7 @@ func (j *Client) CreateCalendarEvent(accountId string, event CalendarEventChange
)
}
func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, ctx Context) (Result[map[string]SetError], Error) {
return destroy(j, "DeleteCalendarEvent", CalendarEventType,
func(accountId string, destroy []string) CalendarEventSetCommand {
return CalendarEventSetCommand{AccountId: accountId, Destroy: destroy}
@@ -130,7 +130,7 @@ func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, ctx
)
}
func (j *Client) UpdateCalendarEvent(accountId string, id string, changes CalendarEventChange, ctx Context) (CalendarEvent, SessionState, State, Language, Error) {
func (j *Client) UpdateCalendarEvent(accountId string, id string, changes CalendarEventChange, ctx Context) (Result[CalendarEvent], Error) {
return update(j, "UpdateCalendarEvent", CalendarEventType,
func(update map[string]PatchObject) CalendarEventSetCommand {
return CalendarEventSetCommand{AccountId: accountId, Update: update}

View File

@@ -8,7 +8,7 @@ import (
var NS_IDENTITY = ns(JmapMail)
func (j *Client) GetIdentities(accountId string, identityIds []string, ctx Context) (IdentityGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetIdentities(accountId string, identityIds []string, ctx Context) (Result[IdentityGetResponse], Error) {
return get(j, "GetIdentities", IdentityType,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId, Ids: ids}
@@ -20,7 +20,7 @@ func (j *Client) GetIdentities(accountId string, identityIds []string, ctx Conte
)
}
func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, ctx Context) (map[string][]Identity, SessionState, State, Language, Error) {
func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, ctx Context) (Result[map[string][]Identity], Error) {
return getN(j, "GetIdentitiesForAllAccounts", IdentityType,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
@@ -39,7 +39,7 @@ type IdentitiesAndMailboxesGetResponse struct {
Mailboxes []Mailbox `json:"mailboxes"`
}
func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, ctx Context) (IdentitiesAndMailboxesGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, ctx Context) (Result[IdentitiesAndMailboxesGetResponse], Error) {
uniqueAccountIds := structs.Uniq(accountIds)
logger := j.logger("GetIdentitiesAndMailboxes", ctx)
@@ -53,7 +53,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
cmd, err := j.request(ctx, NS_IDENTITY, calls...)
if err != nil {
return IdentitiesAndMailboxesGetResponse{}, "", "", "", err
return ZeroResult[IdentitiesAndMailboxesGetResponse](), err
}
return command(j, ctx, cmd, func(body *Response) (IdentitiesAndMailboxesGetResponse, State, Error) {
identities := make(map[string][]Identity, len(uniqueAccountIds))
@@ -85,7 +85,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
})
}
func (j *Client) CreateIdentity(accountId string, identity IdentityChange, ctx Context) (*Identity, SessionState, State, Language, Error) {
func (j *Client) CreateIdentity(accountId string, identity IdentityChange, ctx Context) (Result[*Identity], Error) {
return create(j, "CreateIdentity", IdentityType,
func(accountId string, create map[string]IdentityChange) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Create: create}
@@ -104,7 +104,7 @@ func (j *Client) CreateIdentity(accountId string, identity IdentityChange, ctx C
)
}
func (j *Client) UpdateIdentity(accountId string, id string, changes IdentityChange, ctx Context) (Identity, SessionState, State, Language, Error) {
func (j *Client) UpdateIdentity(accountId string, id string, changes IdentityChange, ctx Context) (Result[Identity], Error) {
return update(j, "UpdateIdentity", IdentityType,
func(update map[string]PatchObject) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Update: update}
@@ -119,7 +119,7 @@ func (j *Client) UpdateIdentity(accountId string, id string, changes IdentityCha
)
}
func (j *Client) DeleteIdentity(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
func (j *Client) DeleteIdentity(accountId string, destroyIds []string, ctx Context) (Result[map[string]SetError], Error) {
return destroy(j, "DeleteIdentity", IdentityType,
func(accountId string, destroy []string) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Destroy: destroyIds}
@@ -144,7 +144,7 @@ func (c IdentityChanges) GetDestroyed() []string { return c.Destroyed }
// Retrieve the changes in Email Identities since a given State.
// @api:tags email,changes
func (j *Client) GetIdentityChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (IdentityChanges, SessionState, State, Language, Error) {
ctx Context) (Result[IdentityChanges], Error) {
return changes(j, "GetIdentityChanges", IdentityType,
func() IdentityChangesCommand {
return IdentityChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}

View File

@@ -8,7 +8,7 @@ import (
var NS_MAILBOX = ns(JmapMail)
func (j *Client) GetMailbox(accountId string, ids []string, ctx Context) (MailboxGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetMailbox(accountId string, ids []string, ctx Context) (Result[MailboxGetResponse], Error) {
return get(j, "GetMailbox", MailboxType,
func(accountId string, ids []string) MailboxGetCommand {
return MailboxGetCommand{AccountId: accountId, Ids: ids}
@@ -20,7 +20,7 @@ func (j *Client) GetMailbox(accountId string, ids []string, ctx Context) (Mailbo
)
}
func (j *Client) GetAllMailboxes(accountIds []string, ctx Context) (map[string][]Mailbox, SessionState, State, Language, Error) {
func (j *Client) GetAllMailboxes(accountIds []string, ctx Context) (Result[map[string][]Mailbox], Error) {
return getAN(j, "GetAllMailboxes", MailboxType,
func(accountId string, ids []string) MailboxGetCommand {
return MailboxGetCommand{AccountId: accountId}
@@ -32,7 +32,7 @@ func (j *Client) GetAllMailboxes(accountIds []string, ctx Context) (map[string][
)
}
func (j *Client) SearchMailboxes(accountIds []string, filter MailboxFilterElement, ctx Context) (map[string][]Mailbox, SessionState, State, Language, Error) {
func (j *Client) SearchMailboxes(accountIds []string, filter MailboxFilterElement, ctx Context) (Result[map[string][]Mailbox], Error) {
logger := j.logger("SearchMailboxes", ctx)
ctx = ctx.WithLogger(logger)
@@ -52,7 +52,7 @@ func (j *Client) SearchMailboxes(accountIds []string, filter MailboxFilterElemen
}
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string][]Mailbox](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string][]Mailbox, State, Error) {
@@ -72,7 +72,7 @@ func (j *Client) SearchMailboxes(accountIds []string, filter MailboxFilterElemen
})
}
func (j *Client) SearchMailboxIdsPerRole(accountIds []string, roles []string, ctx Context) (map[string]map[string]string, SessionState, State, Language, Error) { //NOSONAR
func (j *Client) SearchMailboxIdsPerRole(accountIds []string, roles []string, ctx Context) (Result[map[string]map[string]string], Error) { //NOSONAR
logger := j.logger("SearchMailboxIdsPerRole", ctx)
ctx = ctx.WithLogger(logger)
@@ -86,7 +86,7 @@ func (j *Client) SearchMailboxIdsPerRole(accountIds []string, roles []string, ct
}
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]map[string]string](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string]map[string]string, State, Error) {
@@ -138,7 +138,7 @@ func newMailboxChanges(oldState, newState State, hasMoreChanges bool, created, u
// Retrieve Mailbox changes since a given state.
// @apidoc mailboxes,changes
func (j *Client) GetMailboxChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (MailboxChanges, SessionState, State, Language, Error) {
ctx Context) (Result[MailboxChanges], Error) {
return changesA(j, "GetMailboxChanges", MailboxType,
func() MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
@@ -164,7 +164,7 @@ func (j *Client) GetMailboxChanges(accountId string, sinceState State, maxChange
// @api:tags email,changes
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, //NOSONAR
sinceStateMap map[string]State, maxChanges uint,
ctx Context) (map[string]MailboxChanges, SessionState, State, Language, Error) {
ctx Context) (Result[map[string]MailboxChanges], Error) {
return changesN(j, "GetMailboxChangesForMultipleAccounts", MailboxType,
accountIds, sinceStateMap,
func(accountId string, state State) MailboxChangesCommand {
@@ -182,7 +182,7 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, //NOS
)
}
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, ctx Context) (map[string]*[]string, SessionState, State, Language, Error) {
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, ctx Context) (Result[map[string]*[]string], Error) {
return queryN(j, "GetMailboxRolesForMultipleAccounts", MailboxType,
[]MailboxComparator{{Property: MailboxPropertySortOrder, IsAscending: true}},
func(accountId string, filter MailboxFilterCondition, sortBy []MailboxComparator, _ int, _ string, _ *int, _ *uint) MailboxQueryCommand {
@@ -201,14 +201,14 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, ctx Con
)
}
func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, ctx Context) (map[string]string, SessionState, State, Language, Error) {
func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, ctx Context) (Result[map[string]string], Error) {
logger := j.logger("GetInboxNameForMultipleAccounts", ctx)
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
n := len(uniqueAccountIds)
if n < 1 {
return nil, "", "", "", nil
return ZeroResult[map[string]string](), nil
}
invocations := make([]Invocation, n*2)
@@ -223,7 +223,7 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, ctx Contex
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]string](), err
}
return command(j, ctx, cmd, func(body *Response) (map[string]string, State, Error) {
@@ -252,7 +252,7 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, ctx Contex
}
func (j *Client) UpdateMailbox(accountId string, mailboxId string, change MailboxChange, //NOSONAR
ctx Context) (Mailbox, SessionState, State, Language, Error) {
ctx Context) (Result[Mailbox], Error) {
return update(j, "UpdateMailbox", MailboxType,
func(update map[string]PatchObject) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Update: update}
@@ -267,7 +267,7 @@ func (j *Client) UpdateMailbox(accountId string, mailboxId string, change Mailbo
)
}
func (j *Client) CreateMailbox(accountId string, mailbox MailboxChange, ctx Context) (*Mailbox, SessionState, State, Language, Error) {
func (j *Client) CreateMailbox(accountId string, mailbox MailboxChange, ctx Context) (Result[*Mailbox], Error) {
return create(j, "CreateMailbox", MailboxType,
func(accountId string, create map[string]MailboxChange) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Create: create}
@@ -286,7 +286,7 @@ func (j *Client) CreateMailbox(accountId string, mailbox MailboxChange, ctx Cont
)
}
func (j *Client) DeleteMailboxes(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
func (j *Client) DeleteMailboxes(accountId string, destroyIds []string, ctx Context) (Result[map[string]SetError], Error) {
return destroy(j, "DeleteMailboxes", MailboxType,
func(accountId string, destroy []string) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Destroy: destroyIds}

View File

@@ -27,7 +27,7 @@ func (j *Client) GetObjects(accountId string, //NOSONAR
quotaIds []string, identityIds []string,
emailSubmissionIds []string,
ctx Context,
) (Objects, SessionState, State, Language, Error) {
) (Result[Objects], Error) {
l := j.logger("GetObjects", ctx).With()
if len(mailboxIds) > 0 {
l = l.Array("mailboxIds", log.SafeStringArray(mailboxIds))
@@ -90,7 +90,7 @@ func (j *Client) GetObjects(accountId string, //NOSONAR
cmd, err := j.request(ctx, NS_OBJECTS, methodCalls...)
if err != nil {
return Objects{}, "", "", "", err
return ZeroResult[Objects](), err
}
return command(j, ctx, cmd, func(body *Response) (Objects, State, Error) {

View File

@@ -2,7 +2,7 @@ package jmap
var NS_PRINCIPALS = ns(JmapPrincipals)
func (j *Client) GetPrincipals(accountId string, ids []string, ctx Context) (PrincipalGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetPrincipals(accountId string, ids []string, ctx Context) (Result[PrincipalGetResponse], Error) {
return get(j, "GetPrincipals", PrincipalType,
func(accountId string, ids []string) PrincipalGetCommand {
return PrincipalGetCommand{AccountId: accountId, Ids: ids}
@@ -32,7 +32,7 @@ func (r *PrincipalSearchResults) SetPosition(position *uint) { r.Position = posi
func (j *Client) QueryPrincipals(accountId string, //NOSONAR
filter PrincipalFilterElement, sortBy []PrincipalComparator,
position int, anchor string, anchorOffset *int, limit *uint, calculateTotal bool,
ctx Context) (*PrincipalSearchResults, SessionState, State, Language, Error) {
ctx Context) (Result[*PrincipalSearchResults], Error) {
return query(j, "QueryPrincipals", PrincipalType,
[]PrincipalComparator{{Property: PrincipalPropertyName, IsAscending: true}},
func(filter PrincipalFilterElement, sortBy []PrincipalComparator, position int, anchor string, anchorOffset *int, limit *uint) PrincipalQueryCommand {

View File

@@ -2,7 +2,7 @@ package jmap
var NS_QUOTA = ns(JmapQuota)
func (j *Client) GetQuotas(accountIds []string, ctx Context) (map[string]QuotaGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetQuotas(accountIds []string, ctx Context) (Result[map[string]QuotaGetResponse], Error) {
return getN(j, "GetQuotas", QuotaType,
func(accountId string, ids []string) QuotaGetCommand {
return QuotaGetCommand{AccountId: accountId}
@@ -29,7 +29,7 @@ func (c QuotaChanges) GetDestroyed() []string { return c.Destroyed }
// Retrieve the changes in Quotas since a given State.
// @api:tags quota,changes
func (j *Client) GetQuotaChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (QuotaChanges, SessionState, State, Language, Error) {
ctx Context) (Result[QuotaChanges], Error) {
return changesA(j, "GetQuotaChanges", QuotaType,
func() QuotaChangesCommand {
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
@@ -61,7 +61,7 @@ func (j *Client) GetQuotaChanges(accountId string, sinceState State, maxChanges
}
func (j *Client) GetQuotaUsageChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (QuotaChanges, SessionState, State, Language, Error) {
ctx Context) (Result[QuotaChanges], Error) {
return updates(j, "GetQuotaUsageChanges", QuotaType,
func() QuotaChangesCommand {
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}

View File

@@ -11,7 +11,7 @@ const (
vacationResponseId = "singleton"
)
func (j *Client) GetVacationResponse(accountId string, ctx Context) (VacationResponseGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetVacationResponse(accountId string, ctx Context) (Result[VacationResponseGetResponse], Error) {
return get(j, "GetVacationResponse", VacationResponseType,
func(accountId string, ids []string) VacationResponseGetCommand {
return VacationResponseGetCommand{AccountId: accountId}
@@ -65,7 +65,7 @@ func (c VacationResponseChanges) GetUpdated() []VacationResponse { return c.Upda
func (c VacationResponseChanges) GetDestroyed() []string { return c.Destroyed }
func (j *Client) SetVacationResponse(accountId string, vacation VacationResponseChange,
ctx Context) (VacationResponse, SessionState, State, Language, Error) {
ctx Context) (Result[VacationResponse], Error) {
logger := j.logger("SetVacationResponse", ctx)
ctx = ctx.WithLogger(logger)
@@ -92,7 +92,7 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
invocation(get, "1"),
)
if err != nil {
return VacationResponse{}, "", "", "", err
return ZeroResult[VacationResponse](), err
}
return command(j, ctx, cmd, func(body *Response) (VacationResponse, State, Error) {
var setResponse VacationResponseSetResponse

View File

@@ -38,8 +38,9 @@ const (
JmapErrorWssFailedToRetrieveSession
JmapErrorSocketPushUnsupported
JmapErrorMissingCreatedObject
JmapInvalidObjectState
JmapPatchObjectSerialization
JmapErrorInvalidObjectState
JmapErrorPatchObjectSerialization
JmapErrorInvalidProperties
)
var (
@@ -104,5 +105,10 @@ func setErrorError(err SetError, objectType ObjectType) Error {
} else {
e = fmt.Errorf("failed to modify %s due to %s error: %s", objectType, err.Type, err.Description)
}
return JmapError{code: JmapErrorSetError, err: e}
code := JmapErrorSetError
switch err.Type {
case SetErrorTypeInvalidProperties:
code = JmapErrorInvalidProperties
}
return JmapError{code: code, err: e}
}

View File

@@ -1157,9 +1157,9 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
acc func(session *Session) string,
obj func(RESP) []OBJ,
id func(OBJ) string,
get func(s *StalwartTest, accountId string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error),
update func(s *StalwartTest, accountId string, id string, change CHANGE, ctx Context) (OBJ, SessionState, State, Language, Error),
destroy func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error),
get func(s *StalwartTest, accountId string, ids []string, ctx Context) (Result[RESP], Error),
update func(s *StalwartTest, accountId string, id string, change CHANGE, ctx Context) (Result[OBJ], Error),
destroy func(s *StalwartTest, accountId string, ids []string, ctx Context) (Result[map[string]SetError], Error),
fill func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, _ User, principalIds []string) (BOXES, []OBJ, SessionState, State, error),
change func(OBJ) CHANGE,
checkChanged func(t *testing.T, orig OBJ, change CHANGE, changed OBJ),
@@ -1179,10 +1179,10 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
// we first need to retrieve the list of all the Principals in order to be able to use and test sharing
principalIds := []string{}
{
principals, _, _, _, err := s.client.GetPrincipals(accountId, []string{}, ctx)
result, err := s.client.GetPrincipals(accountId, []string{}, ctx)
require.NoError(err)
require.NotEmpty(principals.List)
principalIds = structs.Map(principals.List, func(p Principal) string { return p.Id })
require.NotEmpty(result.Payload.List)
principalIds = structs.Map(result.Payload.List, func(p Principal) string { return p.Id })
}
ss := EmptySessionState
@@ -1192,37 +1192,43 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
// from the tests below
preExistingIds := []string{}
{
resp, sessionState, state, _, err := get(s, accountId, []string{}, ctx)
result, err := get(s, accountId, []string{}, ctx)
require.NoError(err)
require.Empty(resp.GetNotFound())
objs := obj(resp)
require.Empty(result.Payload.GetNotFound())
objs := obj(result.Payload)
preExistingIds = structs.Map(objs, id)
ss = sessionState
as = state
ss = result.GetSessionState()
as = result.GetState()
}
// we are going to create a random amount of objects
num := uint(5 + rand.Intn(30))
{
boxes, all, sessionState, state, err := fill(s, t, accountId, num, ctx, user, principalIds)
require.NoError(err)
require.Len(all, int(num))
ss = sessionState
as = state
var all []OBJ
var boxes BOXES
{
b, a, sessionState, state, err := fill(s, t, accountId, num, ctx, user, principalIds)
require.NoError(err)
require.Len(a, int(num))
ss = sessionState
as = state
boxes = b
all = a
}
{
// lets retrieve all the existing objects by passing an empty ID slice
resp, sessionState, state, _, err := get(s, accountId, []string{}, ctx)
result, err := get(s, accountId, []string{}, ctx)
require.NoError(err)
require.Empty(resp.GetNotFound())
objs := obj(resp)
require.Empty(result.Payload.GetNotFound())
objs := obj(result.Payload)
// lets skip the objects that already exist since we did not create those
found := structs.Filter(objs, func(a OBJ) bool { return !slices.Contains(preExistingIds, id(a)) })
require.Len(found, int(num))
m := structs.Index(found, id)
require.Len(m, int(num))
require.Equal(sessionState, ss)
require.Equal(state, as)
require.Equal(result.GetSessionState(), ss)
require.Equal(result.GetState(), as)
for _, a := range all {
i := id(a)
@@ -1232,35 +1238,35 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
require.Equal(a, found)
}
ss = sessionState
as = state
ss = result.GetSessionState()
as = result.GetState()
}
// lets retrieve every object we created by its ID
for _, a := range all {
i := id(a)
resp, sessionState, state, _, err := get(s, accountId, []string{i}, ctx)
result, err := get(s, accountId, []string{i}, ctx)
require.NoError(err)
require.Empty(resp.GetNotFound())
objs := obj(resp)
require.Empty(result.Payload.GetNotFound())
objs := obj(result.Payload)
require.Len(objs, 1)
require.Equal(sessionState, ss)
require.Equal(state, as)
require.Equal(result.GetSessionState(), ss)
require.Equal(result.GetState(), as)
require.Equal(objs[0], a)
}
// let's retrieve them all by their IDs, but this time all at once
{
ids := structs.Map(all, id)
resp, sessionState, state, _, err := get(s, accountId, ids, ctx)
result, err := get(s, accountId, ids, ctx)
require.NoError(err)
require.Empty(resp.GetNotFound())
objs := obj(resp)
require.Empty(result.Payload.GetNotFound())
objs := obj(result.Payload)
require.Len(objs, len(all))
require.Equal(sessionState, ss)
require.Equal(state, as)
require.Equal(result.GetSessionState(), ss)
require.Equal(result.GetState(), as)
allById := structs.Index(all, id)
for _, r := range resp.GetList() {
for _, r := range result.Payload.GetList() {
a, ok := allById[r.GetId()]
require.True(ok, "failed to find object that was retrieved in mass ID request in the list of objects that were created")
require.Equal(a, r)
@@ -1271,22 +1277,22 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
for _, a := range all {
i := id(a)
ch := change(a)
changed, sessionState, state, _, err := update(s, accountId, i, ch, ctx)
result, err := update(s, accountId, i, ch, ctx)
require.NoError(err)
require.NotEqual(a, changed)
require.Equal(sessionState, ss)
require.NotEqual(state, as)
checkChanged(t, a, ch, changed)
require.NotEqual(a, result.Payload)
require.Equal(result.GetSessionState(), ss)
require.NotEqual(result.GetState(), as)
checkChanged(t, a, ch, result.Payload)
}
// now lets delete each object that we created, all at once
ids := structs.Map(all, id)
{
errMap, sessionState, state, _, err := destroy(s, accountId, ids, ctx)
result, err := destroy(s, accountId, ids, ctx)
require.NoError(err)
require.Empty(errMap)
require.Equal(sessionState, ss)
require.NotEqual(state, as)
require.Empty(result.Payload)
require.Equal(result.GetSessionState(), ss)
require.NotEqual(result.GetState(), as)
}
allBoxesAreTicked(t, boxes)

View File

@@ -45,13 +45,13 @@ func TestAddressBooks(t *testing.T) {
func(session *Session) string { return session.PrimaryAccounts.Contacts },
list,
getid,
func(s *StalwartTest, accountId string, ids []string, ctx Context) (AddressBookGetResponse, SessionState, State, Language, Error) {
func(s *StalwartTest, accountId string, ids []string, ctx Context) (Result[AddressBookGetResponse], Error) {
return s.client.GetAddressbooks(accountId, ids, ctx)
},
func(s *StalwartTest, accountId string, id string, change AddressBookChange, ctx Context) (AddressBook, SessionState, State, Language, Error) { //NOSONAR
func(s *StalwartTest, accountId string, id string, change AddressBookChange, ctx Context) (Result[AddressBook], Error) { //NOSONAR
return s.client.UpdateAddressBook(accountId, id, change, ctx)
},
func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR
func(s *StalwartTest, accountId string, ids []string, ctx Context) (Result[map[string]SetError], Error) { //NOSONAR
return s.client.DeleteAddressBook(accountId, ids, ctx)
},
func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, user User, principalIds []string) (AddressBookBoxes, []AddressBook, SessionState, State, error) {
@@ -100,19 +100,29 @@ func TestContacts(t *testing.T) {
{Property: ContactCardPropertyCreated, IsAscending: true},
}
contactsByAccount, ss, os, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
require.NoError(err)
var results *ContactCardSearchResults
ss := EmptySessionState
os := EmptyState
{
result, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
require.NoError(err)
require.Len(contactsByAccount, 1)
require.Contains(contactsByAccount, accountId)
results := contactsByAccount[accountId]
require.Len(results.Results, int(count))
require.Nil(results.Limit)
require.NotNil(results.Position)
require.Equal(uint(0), *results.Position)
require.NotNil(results.Total)
require.Equal(count, *results.Total)
require.Equal(ChangeCalculation(true), results.CanCalculateChanges)
require.Len(result.Payload, 1)
require.Contains(result.Payload, accountId)
results = result.Payload[accountId]
require.Len(results.Results, int(count))
require.Nil(results.Limit)
require.NotNil(results.Position)
require.Equal(uint(0), *results.Position)
require.NotNil(results.Total)
require.Equal(count, *results.Total)
require.Equal(ChangeCalculation(true), results.CanCalculateChanges)
ss = result.GetSessionState()
require.NotEmpty(ss)
os = result.GetState()
require.NotEmpty(os)
}
for _, actual := range results.Results {
expected, ok := expectedContactCardsById[actual.Id]
@@ -123,11 +133,11 @@ func TestContacts(t *testing.T) {
// retrieve all objects at once
{
ids := structs.Map(results.Results, func(c ContactCard) string { return c.Id })
fetched, _, _, _, err := s.client.GetContactCards(accountId, ids, ctx)
result, err := s.client.GetContactCards(accountId, ids, ctx)
require.NoError(err)
require.Empty(fetched.NotFound)
require.Len(fetched.List, len(ids))
byId := structs.Index(fetched.List, func(r ContactCard) string { return r.Id })
require.Empty(result.Payload.NotFound)
require.Len(result.Payload.List, len(ids))
byId := structs.Index(result.Payload.List, func(r ContactCard) string { return r.Id })
for _, actual := range results.Results {
expected, ok := byId[actual.Id]
require.True(ok, "failed to find created contact by its id")
@@ -137,10 +147,10 @@ func TestContacts(t *testing.T) {
// retrieve each object one by one
for _, actual := range results.Results {
fetched, _, _, _, err := s.client.GetContactCards(accountId, []string{actual.Id}, ctx)
result, err := s.client.GetContactCards(accountId, []string{actual.Id}, ctx)
require.NoError(err)
require.Len(fetched.List, 1)
matchContact(t, fetched.List[0], actual)
require.Len(result.Payload.List, 1)
matchContact(t, result.Payload.List[0], actual)
}
{
@@ -151,11 +161,11 @@ func TestContacts(t *testing.T) {
for i := range slices {
position := int(i * limit)
page := min(remainder, limit)
m, sessionState, _, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, position, "", nil, &limit, true, ctx)
result, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, position, "", nil, &limit, true, ctx)
require.NoError(err)
require.Len(m, 1)
require.Contains(m, accountId)
results := m[accountId]
require.Len(result.Payload, 1)
require.Contains(result.Payload, accountId)
results := result.Payload[accountId]
require.Equal(len(results.Results), int(page))
require.NotNil(results.Limit)
require.Equal(limit, *results.Limit)
@@ -166,7 +176,7 @@ func TestContacts(t *testing.T) {
require.Equal(count, *results.Total)
remainder -= uint(len(results.Results))
require.Equal(ss, sessionState)
require.Equal(ss, result.GetSessionState())
}
}
@@ -176,12 +186,12 @@ func TestContacts(t *testing.T) {
offset := 0
i := 0
for chunk := range slices.Chunk(results.Results, chunkSize) {
m, sessionState, _, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, anchor, &offset, uintPtr(chunkSize), true, ctx)
require.Equal(ss, sessionState)
result, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, anchor, &offset, uintPtr(chunkSize), true, ctx)
require.Equal(ss, result.GetSessionState())
require.NoError(err)
require.Len(m, 1)
require.Contains(m, accountId)
results := m[accountId]
require.Len(result.Payload, 1)
require.Contains(result.Payload, accountId)
results := result.Payload[accountId]
l := len(results.Results)
require.LessOrEqual(l, chunkSize)
require.NotZero(l)
@@ -206,35 +216,35 @@ func TestContacts(t *testing.T) {
Language: ptr("xyz"),
Updated: ptr(now),
}
changed, sessionState, state, _, err := s.client.UpdateContactCard(accountId, event.Id, change, ctx)
result, err := s.client.UpdateContactCard(accountId, event.Id, change, ctx)
require.NoError(err)
require.Equal("xyz", changed.Language)
require.Equal(now, changed.Updated)
require.Equal(ss, sessionState)
require.NotEqual(os, state)
os = state
require.Equal("xyz", result.Payload.Language)
require.Equal(now, result.Payload.Updated)
require.Equal(ss, result.GetSessionState())
require.NotEqual(os, result.GetState())
os = result.GetState()
}
}
{
ids := structs.Map(slices.Collect(maps.Values(expectedContactCardsById)), func(e ContactCard) string { return e.Id })
errMap, sessionState, state, _, err := s.client.DeleteContactCard(accountId, ids, ctx)
result, err := s.client.DeleteContactCard(accountId, ids, ctx)
require.NoError(err)
require.Empty(errMap)
require.Empty(result.Payload)
require.Equal(ss, sessionState)
require.NotEqual(os, state)
os = state
require.Equal(ss, result.GetSessionState())
require.NotEqual(os, result.GetState())
os = result.GetState()
}
{
shouldBeEmpty, sessionState, state, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
result, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
require.NoError(err)
require.Contains(shouldBeEmpty, accountId)
resp := shouldBeEmpty[accountId]
require.Contains(result.Payload, accountId)
resp := result.Payload[accountId]
require.Empty(resp.Results)
require.NotNil(resp.Total)
require.Equal(uint(0), *resp.Total)
require.Equal(ss, sessionState)
require.Equal(os, state)
require.Equal(ss, result.GetSessionState())
require.Equal(os, result.GetState())
}
exceptions := []string{}
@@ -319,24 +329,24 @@ func (s *StalwartTest) fillAddressBook( //NOSONAR
abook.ShareWith = m
}
a, sessionState, state, _, err := s.client.CreateAddressBook(accountId, abook, ctx)
result, err := s.client.CreateAddressBook(accountId, abook, ctx)
if err != nil {
return boxes, created, ss, as, err
}
require.NotEmpty(sessionState)
require.NotEmpty(state)
require.NotEmpty(result.GetSessionState())
require.NotEmpty(result.GetState())
if ss != EmptySessionState {
require.Equal(ss, sessionState)
require.Equal(ss, result.GetSessionState())
}
if as != EmptyState {
require.NotEqual(as, state)
require.NotEqual(as, result.GetState())
}
require.NotNil(a)
created = append(created, *a)
ss = sessionState
as = state
require.NotNil(result.Payload)
created = append(created, *result.Payload)
ss = result.GetSessionState()
as = result.GetState()
printer(fmt.Sprintf("📔 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, a.Id))
printer(fmt.Sprintf("📔 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, result.Payload.Id))
}
return boxes, created, ss, as, nil
}
@@ -644,13 +654,13 @@ func (s *StalwartTest) fillContacts( //NOSONAR
return "", "", nil, boxes, err
}
created, _, _, _, err := s.client.CreateContactCard(accountId, card, ctx)
result, err := s.client.CreateContactCard(accountId, card, ctx)
if err != nil {
return accountId, addressbookId, filled, boxes, err
}
require.NotNil(created)
filled[created.Id] = *created
printer(fmt.Sprintf("🧑🏻 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, created.Id))
require.NotNil(result.Payload)
filled[result.Payload.Id] = *result.Payload
printer(fmt.Sprintf("🧑🏻 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, result.Payload.Id))
}
return accountId, addressbookId, filled, boxes, nil
}

View File

@@ -35,13 +35,13 @@ func TestCalendars(t *testing.T) { //NOSONAR
func(session *Session) string { return session.PrimaryAccounts.Calendars },
func(resp CalendarGetResponse) []Calendar { return resp.List },
func(obj Calendar) string { return obj.Id },
func(s *StalwartTest, accountId string, ids []string, ctx Context) (CalendarGetResponse, SessionState, State, Language, Error) {
func(s *StalwartTest, accountId string, ids []string, ctx Context) (Result[CalendarGetResponse], Error) {
return s.client.GetCalendars(accountId, ids, ctx)
},
func(s *StalwartTest, accountId string, id string, change CalendarChange, ctx Context) (Calendar, SessionState, State, Language, Error) { //NOSONAR
func(s *StalwartTest, accountId string, id string, change CalendarChange, ctx Context) (Result[Calendar], Error) { //NOSONAR
return s.client.UpdateCalendar(accountId, id, change, ctx)
},
func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR
func(s *StalwartTest, accountId string, ids []string, ctx Context) (Result[map[string]SetError], Error) { //NOSONAR
return s.client.DeleteCalendar(accountId, ids, ctx)
},
func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, user User, principalIds []string) (CalendarBoxes, []Calendar, SessionState, State, error) {
@@ -94,12 +94,12 @@ func TestEvents(t *testing.T) {
os := EmptyState
var results *CalendarEventSearchResults
{
resultsByAccount, sessionState, state, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
result, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
require.NoError(err)
require.Len(resultsByAccount, 1)
require.Contains(resultsByAccount, accountId)
results = resultsByAccount[accountId]
require.Len(result.Payload, 1)
require.Contains(result.Payload, accountId)
results = result.Payload[accountId]
require.NotNil(results)
require.Len(results.Results, int(count))
require.Nil(results.Limit)
@@ -115,8 +115,8 @@ func TestEvents(t *testing.T) {
matchEvent(t, actual, expected)
}
ss = sessionState
os = state
ss = result.GetSessionState()
os = result.GetState()
}
{
@@ -127,11 +127,11 @@ func TestEvents(t *testing.T) {
for i := range slices {
position := int(i * limit)
page := min(remainder, limit)
m, sessionState, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, position, "", nil, &limit, true, ctx)
result, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, position, "", nil, &limit, true, ctx)
require.NoError(err)
require.Len(m, 1)
require.Contains(m, accountId)
results := m[accountId]
require.Len(result.Payload, 1)
require.Contains(result.Payload, accountId)
results := result.Payload[accountId]
require.Equal(len(results.Results), int(page))
require.NotNil(results.Limit)
require.Equal(limit, *results.Limit)
@@ -142,7 +142,7 @@ func TestEvents(t *testing.T) {
require.Equal(count, *results.Total)
remainder -= uint(len(results.Results))
require.Equal(ss, sessionState)
require.Equal(ss, result.GetSessionState())
}
}
@@ -152,12 +152,12 @@ func TestEvents(t *testing.T) {
offset := 0
i := 0
for chunk := range slices.Chunk(results.Results, chunkSize) {
m, sessionState, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, anchor, &offset, uintPtr(chunkSize), true, ctx)
require.Equal(ss, sessionState)
result, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, anchor, &offset, uintPtr(chunkSize), true, ctx)
require.Equal(ss, result.GetSessionState())
require.NoError(err)
require.Len(m, 1)
require.Contains(m, accountId)
results := m[accountId]
require.Len(result.Payload, 1)
require.Contains(result.Payload, accountId)
results := result.Payload[accountId]
l := len(results.Results)
require.LessOrEqual(l, chunkSize)
require.NotZero(l)
@@ -185,37 +185,37 @@ func TestEvents(t *testing.T) {
},
},
}
changed, sessionState, state, _, err := s.client.UpdateCalendarEvent(accountId, event.Id, change, ctx)
result, err := s.client.UpdateCalendarEvent(accountId, event.Id, change, ctx)
require.NoError(err)
require.Equal(jscalendar.StatusCancelled, changed.Status)
require.Equal(uint(99), changed.Sequence)
require.Equal(true, changed.ShowWithoutTime)
require.Equal(ss, sessionState)
require.NotEqual(os, state)
os = state
require.Equal(jscalendar.StatusCancelled, result.Payload.Status)
require.Equal(uint(99), result.Payload.Sequence)
require.Equal(true, result.Payload.ShowWithoutTime)
require.Equal(ss, result.GetSessionState())
require.NotEqual(os, result.GetState())
os = result.GetState()
}
{
ids := structs.Map(slices.Collect(maps.Values(expectedEventsById)), func(e CalendarEvent) string { return e.Id })
errMap, sessionState, state, _, err := s.client.DeleteCalendarEvent(accountId, ids, ctx)
result, err := s.client.DeleteCalendarEvent(accountId, ids, ctx)
require.NoError(err)
require.Empty(errMap)
require.Empty(result.Payload)
require.Equal(ss, sessionState)
require.NotEqual(os, state)
os = state
require.Equal(ss, result.GetSessionState())
require.NotEqual(os, result.GetState())
os = result.GetState()
}
{
shouldBeEmpty, sessionState, state, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
result, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
require.NoError(err)
require.Contains(shouldBeEmpty, accountId)
resp := shouldBeEmpty[accountId]
require.Contains(result.Payload, accountId)
resp := result.Payload[accountId]
require.Empty(resp.Results)
require.NotNil(resp.Total)
require.Equal(uint(0), *resp.Total)
require.Equal(ss, sessionState)
require.Equal(os, state)
require.Equal(ss, result.GetSessionState())
require.Equal(os, result.GetState())
}
exceptions := []string{}
@@ -347,24 +347,24 @@ func (s *StalwartTest) fillCalendar( //NOSONAR
cal.ShareWith = m
}
a, sessionState, state, _, err := s.client.CreateCalendar(accountId, cal, ctx)
result, err := s.client.CreateCalendar(accountId, cal, ctx)
if err != nil {
return boxes, created, ss, as, err
}
require.NotEmpty(sessionState)
require.NotEmpty(state)
require.NotEmpty(result.GetSessionState())
require.NotEmpty(result.GetState())
if ss != EmptySessionState {
require.Equal(ss, sessionState)
require.Equal(ss, result.GetSessionState())
}
if as != EmptyState {
require.NotEqual(as, state)
require.NotEqual(as, result.GetState())
}
require.NotNil(a)
created = append(created, *a)
ss = sessionState
as = state
require.NotNil(result.Payload)
created = append(created, *result.Payload)
ss = result.GetSessionState()
as = result.GetState()
printer(fmt.Sprintf("📅 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, a.Id))
printer(fmt.Sprintf("📅 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, result.Payload.Id))
}
return boxes, created, ss, as, nil
}
@@ -571,12 +571,12 @@ func (s *StalwartTest) fillEvents( //NOSONAR
obj.RecurrenceRule = &rr
}
created, _, _, _, err := s.client.CreateCalendarEvent(accountId, obj, ctx)
result, err := s.client.CreateCalendarEvent(accountId, obj, ctx)
if err != nil {
return accountId, calendarId, nil, boxes, err
}
filled[created.Id] = *created
filled[result.Payload.Id] = *result.Payload
printer(fmt.Sprintf("📅 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, uid))
}

View File

@@ -56,21 +56,21 @@ func TestEmails(t *testing.T) {
{
{
resp, sessionState, _, _, err := s.client.GetIdentities(accountId, []string{}, ctx)
result, err := s.client.GetIdentities(accountId, []string{}, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Len(resp.List, 1)
require.Equal(user.email, resp.List[0].Email)
require.Equal(user.description, resp.List[0].Name)
require.Equal(session.State, result.GetSessionState())
require.Len(result.Payload.List, 1)
require.Equal(user.email, result.Payload.List[0].Email)
require.Equal(user.description, result.Payload.List[0].Name)
}
{
respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
result, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Len(respByAccountId, 1)
require.Contains(respByAccountId, accountId)
resp := respByAccountId[accountId]
require.Equal(session.State, result.GetSessionState())
require.Len(result.Payload, 1)
require.Contains(result.Payload, accountId)
resp := result.Payload[accountId]
mailboxesUnreadByRole := map[string]int{}
for _, m := range resp {
if m.Role != "" {
@@ -81,12 +81,12 @@ func TestEmails(t *testing.T) {
}
{
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, "", nil, nil, true, false, 0, true, ctx)
result, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, "", nil, nil, true, false, 0, true, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Equal(session.State, result.GetSessionState())
require.Equalf(threads, len(resp.Results), "the number of collapsed emails in the inbox is expected to be %v, but is actually %v", threads, len(resp.Results))
for _, e := range resp.Results {
require.Equalf(threads, len(result.Payload.Results), "the number of collapsed emails in the inbox is expected to be %v, but is actually %v", threads, len(result.Payload.Results))
for _, e := range result.Payload.Results {
require.Len(e.MessageId, 1)
expectation, ok := mailsByMessageId[e.MessageId[0]]
require.True(ok)
@@ -95,12 +95,12 @@ func TestEmails(t *testing.T) {
}
{
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, "", nil, nil, false, false, 0, true, ctx)
result, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, "", nil, nil, false, false, 0, true, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Equal(session.State, result.GetSessionState())
require.Equalf(count, len(resp.Results), "the number of emails in the inbox is expected to be %v, but is actually %v", count, len(resp.Results))
for _, e := range resp.Results {
require.Equalf(count, len(result.Payload.Results), "the number of emails in the inbox is expected to be %v, but is actually %v", count, len(result.Payload.Results))
for _, e := range result.Payload.Results {
require.Len(e.MessageId, 1)
expectation, ok := mailsByMessageId[e.MessageId[0]]
require.True(ok)
@@ -144,9 +144,9 @@ func TestSendingEmails(t *testing.T) {
var mailboxPerRole map[string]Mailbox
{
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
result, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
require.NoError(err)
mailboxPerRole = structs.Index(mailboxes[accountId], func(m Mailbox) string { return m.Role })
mailboxPerRole = structs.Index(result.Payload[accountId], func(m Mailbox) string { return m.Role })
require.Contains(mailboxPerRole, JmapMailboxRoleInbox)
require.Contains(mailboxPerRole, JmapMailboxRoleDrafts)
require.Contains(mailboxPerRole, JmapMailboxRoleSent)
@@ -154,10 +154,10 @@ func TestSendingEmails(t *testing.T) {
}
{
roles := []string{JmapMailboxRoleDrafts, JmapMailboxRoleSent, JmapMailboxRoleInbox}
m, _, _, _, err := s.client.SearchMailboxIdsPerRole([]string{accountId}, roles, ctx)
result, err := s.client.SearchMailboxIdsPerRole([]string{accountId}, roles, ctx)
require.NoError(err)
require.Contains(m, accountId)
a := m[accountId]
require.Contains(result.Payload, accountId)
a := result.Payload[accountId]
for _, role := range roles {
require.Contains(a, role)
}
@@ -174,9 +174,9 @@ func TestSendingEmails(t *testing.T) {
Logger: ctx.Logger,
AcceptLanguage: ctx.AcceptLanguage,
}
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{u.accountId}, uctx)
result, err := s.client.GetAllMailboxes([]string{u.accountId}, uctx)
require.NoError(err)
for _, mailbox := range mailboxes[u.accountId] {
for _, mailbox := range result.Payload[u.accountId] {
require.Equal(0, mailbox.TotalEmails)
}
}
@@ -188,10 +188,10 @@ func TestSendingEmails(t *testing.T) {
{
var identity Identity
{
resp, _, _, _, err := s.client.GetIdentities(accountId, []string{}, ctx)
result, err := s.client.GetIdentities(accountId, []string{}, ctx)
require.NoError(err)
require.NotEmpty(resp.List)
identity = resp.List[0]
require.NotEmpty(result.Payload.List)
identity = result.Payload.List[0]
}
create := EmailChange{
@@ -199,16 +199,20 @@ func TestSendingEmails(t *testing.T) {
Subject: subject,
MailboxIds: toBoolMapS(mailboxPerRole[JmapMailboxRoleDrafts].Id),
}
created, _, _, _, err := s.client.CreateEmail(accountId, create, "", ctx)
require.NoError(err)
require.NotEmpty(created.Id)
var created *Email
{
result, err := s.client.CreateEmail(accountId, create, "", ctx)
require.NoError(err)
created = result.Payload
require.NotEmpty(created.Id)
}
{
emails, notFound, _, _, _, err := s.client.GetEmails(accountId, []string{created.Id}, true, 0, false, false, ctx)
result, err := s.client.GetEmails(accountId, []string{created.Id}, true, 0, false, false, ctx)
require.NoError(err)
require.Len(emails, 1)
require.Empty(notFound)
email := emails[0]
require.Len(result.Payload.List, 1)
require.Empty(result.Payload.NotFound)
email := result.Payload.List[0]
require.Equal(created.Id, email.Id)
require.Len(email.MailboxIds, 1)
require.Contains(email.MailboxIds, mailboxPerRole[JmapMailboxRoleDrafts].Id)
@@ -223,22 +227,27 @@ func TestSendingEmails(t *testing.T) {
Subject: subject,
MailboxIds: toBoolMapS(mailboxPerRole[JmapMailboxRoleDrafts].Id),
}
updated, _, _, _, err := s.client.CreateEmail(accountId, update, created.Id, ctx)
require.NoError(err)
require.NotEmpty(updated.Id)
require.NotEqual(created.Id, updated.Id)
var updated *Email
{
result, err := s.client.CreateEmail(accountId, update, created.Id, ctx)
require.NoError(err)
updated = result.Payload
require.NotNil(updated)
require.NotEmpty(updated.Id)
require.NotEqual(created.Id, updated.Id)
}
var updatedMailboxId string
{
emails, notFound, _, _, _, err := s.client.GetEmails(accountId, []string{created.Id, updated.Id}, true, 0, false, false, ctx)
result, err := s.client.GetEmails(accountId, []string{created.Id, updated.Id}, true, 0, false, false, ctx)
require.NoError(err)
require.Len(emails, 1)
require.Len(notFound, 1)
email := emails[0]
require.Len(result.Payload.List, 1)
require.Len(result.Payload.NotFound, 1)
email := result.Payload.List[0]
require.Equal(updated.Id, email.Id)
require.Len(email.MailboxIds, 1)
require.Contains(email.MailboxIds, mailboxPerRole[JmapMailboxRoleDrafts].Id)
require.Equal(notFound[0], created.Id)
require.Equal(result.Payload.NotFound[0], created.Id)
var ok bool
updatedMailboxId, ok = firstKey(email.MailboxIds)
require.True(ok)
@@ -249,24 +258,28 @@ func TestSendingEmails(t *testing.T) {
ToMailboxId: mailboxPerRole[JmapMailboxRoleSent].Id,
}
sub, _, _, _, err := s.client.SubmitEmail(accountId, identity.Id, updated.Id, &move, ctx)
require.NoError(err)
require.NotEmpty(sub.Id)
require.NotEmpty(sub.ThreadId)
require.Equal(updated.Id, sub.EmailId)
require.Equal(identity.Id, sub.IdentityId)
require.Equal(sub.UndoStatus, UndoStatusPending) // this *might* be fragile: if the server is fast enough, would we get "final" here?
require.Empty(sub.DsnBlobIds)
require.Empty(sub.MdnBlobIds)
require.Equal(from.email, sub.Envelope.MailFrom.Email)
require.Nil(sub.Envelope.MailFrom.Parameters)
require.Len(sub.Envelope.RcptTo, 2)
require.Contains(sub.Envelope.RcptTo, Address{Email: to.email})
require.Contains(sub.Envelope.RcptTo, Address{Email: cc.email})
require.NotZero(sub.SendAt)
require.Len(sub.DeliveryStatus, 2)
require.Contains(sub.DeliveryStatus, to.email)
require.Contains(sub.DeliveryStatus, cc.email)
var sub EmailSubmission
{
result, err := s.client.SubmitEmail(accountId, identity.Id, updated.Id, &move, ctx)
require.NoError(err)
sub = result.Payload
require.NotEmpty(sub.Id)
require.NotEmpty(sub.ThreadId)
require.Equal(updated.Id, sub.EmailId)
require.Equal(identity.Id, sub.IdentityId)
require.Equal(sub.UndoStatus, UndoStatusPending) // this *might* be fragile: if the server is fast enough, would we get "final" here?
require.Empty(sub.DsnBlobIds)
require.Empty(sub.MdnBlobIds)
require.Equal(from.email, sub.Envelope.MailFrom.Email)
require.Nil(sub.Envelope.MailFrom.Parameters)
require.Len(sub.Envelope.RcptTo, 2)
require.Contains(sub.Envelope.RcptTo, Address{Email: to.email})
require.Contains(sub.Envelope.RcptTo, Address{Email: cc.email})
require.NotZero(sub.SendAt)
require.Len(sub.DeliveryStatus, 2)
require.Contains(sub.DeliveryStatus, to.email)
require.Contains(sub.DeliveryStatus, cc.email)
}
a := 0
maxAttempts := 3
@@ -280,10 +293,12 @@ func TestSendingEmails(t *testing.T) {
}
time.Sleep(1 * time.Second)
subs, notFound, _, _, _, err := s.client.GetEmailSubmissionStatus(accountId, []string{sub.Id}, ctx)
result, err := s.client.GetEmailSubmissionStatus(accountId, []string{sub.Id}, ctx)
require.NoError(err)
require.Empty(notFound)
require.Contains(subs, sub.Id)
require.Empty(result.Payload.NotFound)
submittedIds := structs.Map(result.Payload.List, func(s EmailSubmission) string { return s.Id })
require.Contains(submittedIds, sub.Id)
subs := structs.Index(result.Payload.List, func(s EmailSubmission) string { return s.Id })
delivery = subs[sub.Id].DeliveryStatus[to.email].Delivered
}
@@ -300,22 +315,24 @@ func TestSendingEmails(t *testing.T) {
Logger: ctx.Logger,
AcceptLanguage: ctx.AcceptLanguage,
}
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{r.accountId}, rctx)
require.NoError(err)
inboxId := ""
for _, mailbox := range mailboxes[r.accountId] {
if mailbox.Role == JmapMailboxRoleInbox {
inboxId = mailbox.Id
require.Equal(1, mailbox.TotalEmails)
{
result, err := s.client.GetAllMailboxes([]string{r.accountId}, rctx)
require.NoError(err)
for _, mailbox := range result.Payload[r.accountId] {
if mailbox.Role == JmapMailboxRoleInbox {
inboxId = mailbox.Id
require.Equal(1, mailbox.TotalEmails)
}
}
require.NotEmpty(inboxId, "failed to find the Mailbox with the 'inbox' role for %v", r.user.name)
}
require.NotEmpty(inboxId, "failed to find the Mailbox with the 'inbox' role for %v", r.user.name)
emails, _, _, _, err := s.client.QueryEmails([]string{r.accountId}, EmailFilterCondition{InMailbox: inboxId}, 0, 0, true, 0, rctx)
result, err := s.client.QueryEmails([]string{r.accountId}, EmailFilterCondition{InMailbox: inboxId}, 0, 0, true, 0, rctx)
require.NoError(err)
require.Contains(emails, r.accountId)
require.Len(emails[r.accountId].Emails, 1)
received := emails[r.accountId].Emails[0]
require.Contains(result.Payload, r.accountId)
require.Len(result.Payload[r.accountId].Emails, 1)
received := result.Payload[r.accountId].Emails[0]
require.Len(received.From, 1)
require.Equal(from.email, received.From[0].Email)
require.Equal(fromName, received.From[0].Name)
@@ -370,12 +387,12 @@ func matchEmail(t *testing.T, actual Email, expected filledMail, hasBodies bool)
func (s *StalwartTest) findInbox(t *testing.T, accountId string, ctx Context) (string, string) {
require := require.New(t)
respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
result, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
require.NoError(err)
require.Equal(ctx.Session.State, sessionState)
require.Len(respByAccountId, 1)
require.Contains(respByAccountId, accountId)
resp := respByAccountId[accountId]
require.Equal(ctx.Session.State, result.GetSessionState())
require.Len(result.Payload, 1)
require.Contains(result.Payload, accountId)
resp := result.Payload[accountId]
mailboxesNameByRole := map[string]string{}
mailboxesUnreadByRole := map[string]int{}

View File

@@ -91,28 +91,28 @@ func TestWs(t *testing.T) {
var initialState State
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, EmptyState, true, 0, 0, ctx)
result, err := s.client.GetEmailChanges(mailAccountId, EmptyState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEmpty(state)
require.Equal(session.State, result.GetSessionState())
require.NotEmpty(result.GetState())
//fmt.Printf("\x1b[45;1;4mChanges [%s]:\x1b[0m\n", state)
//for _, c := range changes.Created { fmt.Printf("%s %s\n", c.Id, c.Subject) }
initialState = state
require.Empty(changes.Created)
require.Empty(changes.Destroyed)
require.Empty(changes.Updated)
initialState = result.GetState()
require.Empty(result.Payload.Created)
require.Empty(result.Payload.Destroyed)
require.Empty(result.Payload.Updated)
}
require.NotEmpty(initialState)
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, initialState, true, 0, 0, ctx)
result, err := s.client.GetEmailChanges(mailAccountId, initialState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Equal(initialState, state)
require.Equal(initialState, changes.NewState)
require.Empty(changes.Created)
require.Empty(changes.Destroyed)
require.Empty(changes.Updated)
require.Equal(session.State, result.GetSessionState())
require.Equal(initialState, result.GetState())
require.Equal(initialState, result.Payload.NewState)
require.Empty(result.Payload.Created)
require.Empty(result.Payload.Destroyed)
require.Empty(result.Payload.Updated)
}
wsc, err := s.client.EnablePushNotifications(cotx, initialState, func() (*Session, error) { return session, nil })
@@ -148,18 +148,18 @@ func TestWs(t *testing.T) {
}
var lastState State
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, initialState, true, 0, 0, ctx)
result, err := s.client.GetEmailChanges(mailAccountId, initialState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(initialState, state)
require.NotEqual(initialState, changes.NewState)
require.Equal(state, changes.NewState)
require.Len(changes.Created, 1)
require.Empty(changes.Destroyed)
require.Empty(changes.Updated)
lastState = state
require.Equal(session.State, result.GetSessionState())
require.NotEqual(initialState, result.GetState())
require.NotEqual(initialState, result.Payload.NewState)
require.Equal(result.GetState(), result.Payload.NewState)
require.Len(result.Payload.Created, 1)
require.Empty(result.Payload.Destroyed)
require.Empty(result.Payload.Updated)
lastState = result.GetState()
emailIds = append(emailIds, structs.Map(changes.Created, func(e Email) string { return e.Id })...)
emailIds = append(emailIds, structs.Map(result.Payload.Created, func(e Email) string { return e.Id })...)
}
{
@@ -182,18 +182,18 @@ func TestWs(t *testing.T) {
l.m.Unlock()
}
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, lastState, true, 0, 0, ctx)
result, err := s.client.GetEmailChanges(mailAccountId, lastState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(lastState, state)
require.NotEqual(lastState, changes.NewState)
require.Equal(state, changes.NewState)
require.Len(changes.Created, 1)
require.Empty(changes.Destroyed)
require.Empty(changes.Updated)
lastState = state
require.Equal(session.State, result.GetSessionState())
require.NotEqual(lastState, result.GetState())
require.NotEqual(lastState, result.Payload.NewState)
require.Equal(result.GetState(), result.Payload.NewState)
require.Len(result.Payload.Created, 1)
require.Empty(result.Payload.Destroyed)
require.Empty(result.Payload.Updated)
lastState = result.GetState()
emailIds = append(emailIds, structs.Map(changes.Created, func(e Email) string { return e.Id })...)
emailIds = append(emailIds, structs.Map(result.Payload.Created, func(e Email) string { return e.Id })...)
}
{
@@ -216,25 +216,25 @@ func TestWs(t *testing.T) {
l.m.Unlock()
}
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, lastState, true, 0, 0, ctx)
result, err := s.client.GetEmailChanges(mailAccountId, lastState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(lastState, state)
require.NotEqual(lastState, changes.NewState)
require.Equal(state, changes.NewState)
require.Empty(changes.Created)
require.Len(changes.Destroyed, 2)
require.Equal(session.State, result.GetSessionState())
require.NotEqual(lastState, result.GetState())
require.NotEqual(lastState, result.Payload.NewState)
require.Equal(result.GetState(), result.Payload.NewState)
require.Empty(result.Payload.Created)
require.Len(result.Payload.Destroyed, 2)
{
a := make([]string, len(emailIds))
copy(a, emailIds)
slices.Sort(emailIds)
b := make([]string, len(changes.Destroyed))
copy(b, changes.Destroyed)
b := make([]string, len(result.Payload.Destroyed))
copy(b, result.Payload.Destroyed)
slices.Sort(b)
require.EqualValues(a, b)
}
require.Empty(changes.Updated)
lastState = state
require.Empty(result.Payload.Updated)
lastState = result.GetState()
}
err = wsc.DisableNotifications()

View File

@@ -55,6 +55,12 @@ type Session struct {
SessionResponse
}
var _ ResultMetadata = Session{}
func (s Session) GetSessionState() SessionState { return s.State }
func (s Session) GetState() State { return EmptyState }
func (s Session) GetLanguage() Language { return NoLanguage }
var (
invalidSessionResponseErrorMissingUsername = jmapError(errors.New("JMAP session response does not provide a username"), JmapErrorInvalidSessionResponse)
invalidSessionResponseErrorMissingApiUrl = jmapError(errors.New("JMAP session response does not provide an API URL"), JmapErrorInvalidSessionResponse)

View File

@@ -13,21 +13,20 @@ func get[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSON
getCommandFactory func(string, []string) GETREQ,
_ GETRESP,
mapper func(GETRESP) RESP,
accountId string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) {
accountId string, ids []string, ctx Context) (Result[RESP], Error) {
ctx = ctx.WithLogger(client.logger(name, ctx))
var zero RESP
get := getCommandFactory(accountId, ids)
cmd, err := client.request(ctx, objType.Namespaces, invocation(get, "0"))
if err != nil {
return zero, "", "", "", err
return ZeroResult[RESP](), err
}
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var response GETRESP
err = retrieveGet(ctx, body, get, "0", &response)
if err != nil {
var zero RESP
return zero, "", err
}
@@ -40,7 +39,7 @@ func getAN[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOS
getCommandFactory func(string, []string) GETREQ,
resp GETRESP,
respMapper func(map[string][]T) RESP,
accountIds []string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) {
accountIds []string, ids []string, ctx Context) (Result[RESP], Error) {
return getN(client, name, objType, getCommandFactory, resp,
func(r GETRESP) []T { return r.GetList() },
respMapper,
@@ -55,12 +54,10 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an
_ GETRESP,
itemMapper func(GETRESP) ITEM,
respMapper func(map[string]ITEM) RESP,
accountIds []string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) {
accountIds []string, ids []string, ctx Context) (Result[RESP], Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
var zero RESP
uniqueAccountIds := structs.Uniq(accountIds)
invocations := make([]Invocation, len(uniqueAccountIds))
@@ -73,7 +70,7 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an
cmd, err := client.request(ctx, objType.Namespaces, invocations...)
if err != nil {
return zero, "", "", "", err
return ZeroResult[RESP](), err
}
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
@@ -83,6 +80,7 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an
var resp GETRESP
err = retrieveResponseMatchParameters(ctx, body, c, mcid(accountId, "0"), &resp)
if err != nil {
var zero RESP
return zero, "", err
}
responses[accountId] = resp
@@ -99,7 +97,7 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
createdMapper func(SETRESP) map[string]*T,
listMapper func(GETRESP) []T,
accountId string, create C,
ctx Context) (*T, SessionState, State, Language, Error) {
ctx Context) (Result[*T], Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
@@ -111,7 +109,7 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
invocation(get, "1"),
)
if err != nil {
return nil, "", "", "", err
return ZeroResult[*T](), err
}
return command(client, ctx, cmd, func(body *Response) (*T, State, Error) {
@@ -155,7 +153,7 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
func destroy[T Foo, REQ SetCommand[T], RESP SetResponse[T]](client *Client, name string, objType ObjectType, //NOSONAR
setCommandFactory func(string, []string) REQ, _ RESP,
accountId string, destroy []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
accountId string, destroy []string, ctx Context) (Result[map[string]SetError], Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
@@ -164,7 +162,7 @@ func destroy[T Foo, REQ SetCommand[T], RESP SetResponse[T]](client *Client, name
invocation(set, "0"),
)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]SetError](), err
}
return command(client, ctx, cmd, func(body *Response) (map[string]SetError, State, Error) {
@@ -184,8 +182,7 @@ func changesA[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
_ GETRESP,
getCommandFactory func(string, string) GETREQ,
respMapper func(State, State, bool, []T, []T, []string) RESP,
ctx Context) (RESP, SessionState, State, Language, Error) {
ctx Context) (Result[RESP], Error) {
return changes(client, name, objType, changesCommandFactory, changesResp, getCommandFactory,
func(r GETRESP) []T { return r.GetList() },
respMapper,
@@ -200,9 +197,8 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
getCommandFactory func(string, string) GETREQ,
getMapper func(GETRESP) []ITEM,
respMapper func(State, State, bool, []ITEM, []ITEM, []string) RESP,
ctx Context) (RESP, SessionState, State, Language, Error) {
ctx Context) (Result[RESP], Error) {
logger := client.logger(name, ctx)
var zero RESP
changes := changesCommandFactory()
getCreated := getCommandFactory("/created", "0") //NOSONAR
@@ -214,13 +210,14 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
invocation(getUpdated, "2"),
)
if err != nil {
return zero, "", "", "", err
return ZeroResult[RESP](), err
}
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var changesResponse CHANGESRESP
err = retrieveChanges(ctx, body, changes, "0", &changesResponse)
if err != nil {
var zero RESP
return zero, "", err
}
@@ -228,6 +225,7 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
err = retrieveGet(ctx, body, getCreated, "1", &createdResponse)
if err != nil {
logger.Error().Err(err).Send()
var zero RESP
return zero, "", err
}
@@ -235,6 +233,7 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
err = retrieveGet(ctx, body, getUpdated, "2", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
var zero RESP
return zero, "", err
}
@@ -257,7 +256,7 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
changesItemMapper func(State, State, bool, []ITEM, []ITEM, []string) CHANGESITEM,
respMapper func(map[string]CHANGESITEM) RESP,
stateMapper func(GETRESP) State,
ctx Context) (RESP, SessionState, State, Language, Error) {
ctx Context) (Result[RESP], Error) {
logger := client.loggerParams(name, ctx, func(z zerolog.Context) zerolog.Context {
sinceStateLogDict := zerolog.Dict()
for k, v := range sinceStateMap {
@@ -266,12 +265,10 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
return z.Dict(logSinceState, sinceStateLogDict)
})
var zero RESP
uniqueAccountIds := structs.Uniq(accountIds)
n := len(uniqueAccountIds)
if n < 1 {
return zero, "", "", "", nil
return ZeroResult[RESP](), nil
}
invocations := make([]Invocation, n*3)
@@ -302,7 +299,7 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
cmd, err := client.request(ctx, objType.Namespaces, invocations...)
if err != nil {
return zero, "", "", "", err
return ZeroResult[RESP](), err
}
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
@@ -312,18 +309,21 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
var changesResponse CHANGESRESP
err = retrieveChanges(ctx, body, ch, mcid(accountId, "0"), &changesResponse)
if err != nil {
var zero RESP
return zero, "", err
}
var createdResponse GETRESP
err = retrieveGet(ctx, body, gc, mcid(accountId, "1"), &createdResponse)
if err != nil {
var zero RESP
return zero, "", err
}
var updatedResponse GETRESP
err = retrieveGet(ctx, body, gu, mcid(accountId, "2"), &updatedResponse)
if err != nil {
var zero RESP
return zero, "", err
}
@@ -343,10 +343,9 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
getCommandFactory func(string, string) GETREQ,
getMapper func(GETRESP) []ITEM,
respMapper func(State, State, bool, []ITEM) RESP,
ctx Context) (RESP, SessionState, State, Language, Error) {
ctx Context) (Result[RESP], Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
var zero RESP
changes := changesCommandFactory()
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
@@ -355,13 +354,14 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
invocation(getUpdated, "1"),
)
if err != nil {
return zero, "", "", "", err
return ZeroResult[RESP](), err
}
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var changesResponse CHANGESRESP
err = retrieveChanges(ctx, body, changes, "0", &changesResponse)
if err != nil {
var zero RESP
return zero, "", err
}
@@ -369,6 +369,7 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
err = retrieveGet(ctx, body, getUpdated, "1", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
var zero RESP
return zero, "", err
}
@@ -386,41 +387,42 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an
notUpdatedExtractor func(SETRESP) map[string]SetError,
objExtractor func(GETRESP) RESP,
id string, changes CHANGES,
ctx Context) (RESP, SessionState, State, Language, Error) {
ctx Context) (Result[RESP], Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
var zero RESP
var update SET
{
patch, err := changes.AsPatch()
if err != nil {
return zero, "", "", "", jmapError(err, JmapPatchObjectSerialization)
return ZeroResult[RESP](), jmapError(err, JmapErrorPatchObjectSerialization)
}
update = setCommandFactory(map[string]PatchObject{id: patch})
}
get := getCommandFactory(id)
cmd, err := client.request(ctx, objType.Namespaces, invocation(update, "0"), invocation(get, "1"))
if err != nil {
return zero, "", "", "", err
return ZeroResult[RESP](), err
}
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var setResponse SETRESP
err = retrieveSet(ctx, body, update, "0", &setResponse)
if err != nil {
var zero RESP
return zero, setResponse.GetNewState(), err
}
nc := notUpdatedExtractor(setResponse)
setErr, notok := nc[id]
if notok {
logger.Error().Msgf("%T.NotUpdated returned an error %v", setResponse, setErr)
var zero RESP
return zero, "", setErrorError(setErr, update.GetObjectType())
}
var getResponse GETRESP
err = retrieveGet(ctx, body, get, "1", &getResponse)
if err != nil {
var zero RESP
return zero, setResponse.GetNewState(), err
}
return objExtractor(getResponse), setResponse.GetNewState(), nil
@@ -434,7 +436,7 @@ func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T]
getCommandFactory func(cmd Command, path string, rof string) GET,
respMapper func(query QUERYRESP, get GETRESP) *RESP,
filter FILTER, sortBy []SORT, position int, anchor string, anchorOffset *int, limit *uint,
ctx Context) (*RESP, SessionState, State, Language, Error) {
ctx Context) (Result[*RESP], Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
@@ -448,7 +450,7 @@ func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T]
cmd, err := client.request(ctx, objType.Namespaces, invocation(query, "0"), invocation(get, "1"))
if err != nil {
return nil, "", "", "", err
return ZeroResult[*RESP](), err
}
return command(client, ctx, cmd, func(body *Response) (*RESP, State, Error) {
@@ -474,7 +476,7 @@ func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T
respMapper func(query QUERYRESP, get GETRESP) *RESP,
accountIds []string,
filter FILTER, sortBy []SORT, position int, anchor string, anchorOffset *int, limit *uint,
ctx Context) (map[string]*RESP, SessionState, State, Language, Error) {
ctx Context) (Result[map[string]*RESP], Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
@@ -498,7 +500,7 @@ func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T
cmd, err := client.request(ctx, objType.Namespaces, invocations...)
if err != nil {
return nil, "", "", "", err
return ZeroResult[map[string]*RESP](), err
}
return command(client, ctx, cmd, func(body *Response) (map[string]*RESP, State, Error) {

View File

@@ -50,11 +50,6 @@ func mcid(accountId string, tag string) string {
return accountId + ":" + tag
}
func bail[R JmapResponse[T], T Foo](err Error) (R, SessionState, State, Language, Error) {
var zero R
return zero, EmptySessionState, EmptyState, NoLanguage, err
}
type Cmdr interface {
ApiSupplier
Hooks
@@ -63,22 +58,22 @@ type Cmdr interface {
func command[T any](client Cmdr, //NOSONAR
ctx Context,
request Request,
mapper func(body *Response) (T, State, Error)) (T, SessionState, State, Language, Error) {
mapper func(body *Response) (T, State, Error)) (Result[T], Error) {
logger := ctx.Logger
responseBody, language, jmapErr := client.Api().Command(request, ctx)
if jmapErr != nil {
var zero T
return zero, "", "", language, jmapErr
var zero Result[T]
return zero, jmapErr
}
var response Response
err := json.Unmarshal(responseBody, &response)
if err != nil {
logger.Error().Err(err).Msgf("failed to deserialize body JSON payload into a %T", response)
var zero T
return zero, "", "", language, jmapError(err, JmapErrorDecodingResponseBody)
var zero Result[T]
return zero, jmapError(err, JmapErrorDecodingResponseBody)
}
if response.SessionState != ctx.Session.State {
@@ -102,7 +97,7 @@ func command[T any](client Cmdr, //NOSONAR
case MethodLevelErrorInvalidArguments:
code = JmapErrorInvalidArguments
if strings.HasPrefix(errorParameters.Description, "invalid JMAP State") {
code = JmapInvalidObjectState
code = JmapErrorInvalidObjectState
}
case MethodLevelErrorInvalidResultReference:
code = JmapErrorInvalidResultReference
@@ -126,22 +121,20 @@ func command[T any](client Cmdr, //NOSONAR
msg := fmt.Sprintf("found method level error in response '%v', type: '%v', description: '%v'", mr.Tag, errorParameters.Type, errorParameters.Description)
err = errors.New(msg)
logger.Warn().Int("code", code).Str("type", errorParameters.Type).Msg(msg)
var zero T
return zero, response.SessionState, "", language, jmapResponseError(code, err, errorParameters.Type, errorParameters.Description)
return newPartialResult[T](response.SessionState, language), jmapResponseError(code, err, errorParameters.Type, errorParameters.Description)
} else {
code := JmapErrorUnspecifiedType
msg := fmt.Sprintf("found method level error in response '%v'", mr.Tag)
err := errors.New(msg)
logger.Warn().Int("code", code).Msg(msg)
var zero T
return zero, response.SessionState, "", language, jmapResponseError(code, err, errorParameters.Type, errorParameters.Description)
return newPartialResult[T](response.SessionState, language), jmapResponseError(code, err, errorParameters.Type, errorParameters.Description)
}
}
}
result, state, jerr := mapper(&response)
sessionState := response.SessionState
return result, sessionState, state, language, jerr
return newResult(result, sessionState, state, language), jerr
}
func mapstructStringToTimeHook() mapstructure.DecodeHookFunc {

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"@redocly/cli": "^2.30.1",
"@redocly/cli": "^2.30.2",
"@types/js-yaml": "^4.0.9",
"cheerio": "^1.2.0",
"js-yaml": "^4.1.1",

View File

@@ -17,7 +17,7 @@ func (g *Groupware) GetAccountById(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
var body jmap.Account = account
return req.respond(accountId, body, req.session.State, AccountResponseObjectType, jmap.EmptyState, jmap.NoLanguage)
return req.respond(accountId, body, AccountResponseObjectType, req.session)
})
}
@@ -36,7 +36,7 @@ func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
// sort on accountId to have a stable order that remains the same with every query
slices.SortFunc(list, func(a, b AccountWithId) int { return strings.Compare(a.AccountId, b.AccountId) })
var RBODY []AccountWithId = list
return req.respondN(structs.Map(list, func(a AccountWithId) string { return a.AccountId }), RBODY, req.session.State, AccountResponseObjectType, jmap.EmptyState, jmap.NoLanguage)
return req.respondN(structs.Map(list, func(a AccountWithId) string { return a.AccountId }), RBODY, AccountResponseObjectType, req.session)
})
}
@@ -44,14 +44,14 @@ func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
allAccountIds := req.AllAccountIds()
resp, sessionState, state, lang, err := g.jmap.GetIdentitiesForAllAccounts(allAccountIds, req.ctx)
resp, err := g.jmap.GetIdentitiesForAllAccounts(allAccountIds, req.ctx)
if err != nil {
return req.jmapErrorN(allAccountIds, err, sessionState, lang)
return req.jmapErrorN(allAccountIds, err, resp)
}
list := make([]AccountWithIdAndIdentities, len(req.session.Accounts))
i := 0
for accountId, account := range req.session.Accounts {
identities, ok := resp[accountId]
identities, ok := resp.Payload[accountId]
if !ok {
identities = []jmap.Identity{}
}
@@ -66,7 +66,7 @@ func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *htt
// sort on accountId to have a stable order that remains the same with every query
slices.SortFunc(list, func(a, b AccountWithIdAndIdentities) int { return strings.Compare(a.AccountId, b.AccountId) })
var RBODY []AccountWithIdAndIdentities = list
return req.respondN(structs.Map(list, func(a AccountWithIdAndIdentities) string { return a.AccountId }), RBODY, sessionState, AccountResponseObjectType, state, lang)
return req.respondN(structs.Map(list, func(a AccountWithIdAndIdentities) string { return a.AccountId }), RBODY, AccountResponseObjectType, resp)
})
}

View File

@@ -36,9 +36,9 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
ctx := req.ctx.WithLogger(logger)
resp, lang, jerr := g.jmap.UploadBlobStream(accountId, contentType, body, ctx)
resp, _, jerr := g.jmap.UploadBlobStream(accountId, contentType, body, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, req.session.State, lang)
return req.jmapError(accountId, jerr, req.session)
}
return req.respondWithoutStatus(accountId, resp)

View File

@@ -78,12 +78,12 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetChanges(accountId, sinceState, maxChanges, ctx)
result, jerr := g.jmap.GetChanges(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
var body jmap.ObjectChanges = changes
var body jmap.ObjectChanges = result.Payload
return req.respond(accountId, body, sessionState, "", state, lang)
return req.respond(accountId, body, UnspecifiedResponseObjectType, result)
})
}

View File

@@ -24,7 +24,7 @@ import (
// Get the changes tp Emails since a certain State.
// @api:tags email,changes
func (g *Groupware) GetEmailChanges(w http.ResponseWriter, r *http.Request) {
changes(Email, w, r, g, func(accountId string, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.EmailChanges, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
changes(Email, w, r, g, func(accountId string, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[jmap.EmailChanges], jmap.Error) {
return g.jmap.GetEmailChanges(accountId, sinceState, true, g.config.maxBodyValueBytes, maxChanges, ctx)
})
}
@@ -42,25 +42,26 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
fetchBodies := false
withThreads := true
query(Email, w, r, g, g.defaults.emailLimit,
func(req Request, accountId, containerId string, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (*jmap.EmailSearchResults, jmap.SessionState, jmap.State, jmap.Language, *Error) { //NOSONAR
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, containerId, position, anchor, anchorOffset, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads, ctx)
func(req Request, accountId, containerId string, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (jmap.Result[*jmap.EmailSearchResults], *Error) { //NOSONAR
result, jerr := g.jmap.GetAllEmailsInMailbox(accountId, containerId, position, anchor, anchorOffset, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads, ctx)
if jerr != nil {
return emails, sessionState, state, lang, req.apiErrorFromJmap(req.observeJmapError(jerr))
return jmap.ZeroResult[*jmap.EmailSearchResults](), req.apiErrorFromJmap(req.observeJmapError(jerr))
}
sanitized, err := req.sanitizeEmails(emails.Results)
sanitized, err := req.sanitizeEmails(result.Payload.Results)
if err != nil {
return emails, sessionState, state, lang, err
return jmap.ZeroResult[*jmap.EmailSearchResults](), err
}
safe := &jmap.EmailSearchResults{
Results: sanitized,
Total: emails.Total,
Limit: emails.Limit,
Position: emails.Position,
CanCalculateChanges: emails.CanCalculateChanges,
}
return safe, sessionState, state, lang, nil
return jmap.RefineResult(result, func(orig *jmap.EmailSearchResults) *jmap.EmailSearchResults {
return &jmap.EmailSearchResults{
Results: sanitized,
Total: orig.Total,
Limit: orig.Limit,
Position: orig.Position,
CanCalculateChanges: orig.CanCalculateChanges,
}
}), nil
},
)
}
@@ -93,20 +94,20 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
logger := log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)).Str("id", log.SafeString(id)).Str("accept", log.SafeString(accept)))
ctx := req.ctx.WithLogger(logger)
blobId, _, _, _, jerr := g.jmap.GetEmailBlobId(accountId, id, ctx)
result, jerr := g.jmap.GetEmailBlobId(accountId, id, ctx)
if jerr != nil {
return req.apiErrorFromJmap(req.observeJmapError(jerr))
}
if blobId == "" {
if result.Payload == "" {
return nil
} else {
name := blobId + ".eml"
name := result.Payload + ".eml"
typ := accept
accountId, gwerr := req.GetAccountIdForBlob()
if gwerr != nil {
return gwerr
}
return req.serveBlob(blobId, name, typ, ctx, accountId, w)
return req.serveBlob(result.Payload, name, typ, ctx, accountId, w)
}
})
} else {
@@ -137,34 +138,34 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
if len(ids) == 1 {
logger := log.From(l.Str(UriParamEmailId, log.SafeString(id)))
ctx := req.ctx.WithLogger(logger)
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, ids, true, g.config.maxBodyValueBytes, markAsSeen, true, ctx)
result, jerr := g.jmap.GetEmails(accountId, ids, true, g.config.maxBodyValueBytes, markAsSeen, true, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if len(emails) < 1 {
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
if len(result.Payload.List) < 1 {
return req.notFound(accountId, EmailResponseObjectType, result)
} else {
sanitized, err := req.sanitizeEmail(emails[0])
sanitized, err := req.sanitizeEmail(result.Payload.List[0])
if err != nil {
return req.error(accountId, err)
}
return req.respond(accountId, sanitized, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, sanitized, EmailResponseObjectType, result)
}
} else {
logger := log.From(l.Array(UriParamEmailId, log.SafeStringArray(ids)))
ctx := req.ctx.WithLogger(logger)
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, ids, true, g.config.maxBodyValueBytes, markAsSeen, false, ctx)
result, jerr := g.jmap.GetEmails(accountId, ids, true, g.config.maxBodyValueBytes, markAsSeen, false, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if len(emails) < 1 {
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
if len(result.Payload.List) < 1 {
return req.notFound(accountId, EmailResponseObjectType, result)
} else {
sanitized, err := req.sanitizeEmails(emails)
sanitized, err := req.sanitizeEmails(result.Payload.List)
if err != nil {
return req.error(accountId, err)
}
return req.respond(accountId, sanitized, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, sanitized, EmailResponseObjectType, result)
}
}
})
@@ -211,19 +212,19 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, single(id), false, 0, false, false, ctx)
result, jerr := g.jmap.GetEmails(accountId, single(id), false, 0, false, false, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if len(emails) < 1 {
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
if len(result.Payload.List) < 1 {
return req.notFound(accountId, EmailResponseObjectType, result)
}
email, err := req.sanitizeEmail(emails[0])
email, err := req.sanitizeEmail(result.Payload.List[0])
if err != nil {
return req.error(accountId, err)
}
var body []jmap.EmailBodyPart = email.Attachments
return req.respond(accountId, body, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, body, EmailResponseObjectType, result)
})
} else {
g.stream(w, r, func(req Request, w http.ResponseWriter) *Error {
@@ -248,15 +249,15 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
l = contextAppender(l)
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
emails, _, _, _, lang, jerr := g.jmap.GetEmails(mailAccountId, single(id), false, 0, false, false, ctx)
result, jerr := g.jmap.GetEmails(mailAccountId, single(id), false, 0, false, false, ctx)
if jerr != nil {
return req.apiErrorFromJmap(req.observeJmapError(jerr))
}
if len(emails) < 1 {
if len(result.Payload.List) < 1 {
return nil
}
email, err := req.sanitizeEmail(emails[0])
email, err := req.sanitizeEmail(result.Payload.List[0])
if err != nil {
return err
}
@@ -333,12 +334,12 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, since, true, g.config.maxBodyValueBytes, maxChanges, ctx)
result, jerr := g.jmap.GetEmailChanges(accountId, since, true, g.config.maxBodyValueBytes, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
return req.respond(accountId, changes, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, result.Payload, EmailResponseObjectType, result)
})
}
@@ -583,12 +584,12 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
jmaplimit = UintPtrOne
}
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, position, anchor, anchorOffset, jmaplimit, collapseThreads, calculateTotal, fetchBodies, g.config.maxBodyValueBytes, ctx)
result, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, position, anchor, anchorOffset, jmaplimit, collapseThreads, calculateTotal, fetchBodies, g.config.maxBodyValueBytes, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if results, ok := resultsByAccount[accountId]; ok {
if results, ok := result.Payload[accountId]; ok {
var flattened []EmailWithSnippets
if limit != nil && *limit == 0 {
flattened = nil
@@ -628,9 +629,9 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
Total: ptrIf(results.Total, calculateTotal),
Position: results.Position,
Limit: rlimit,
}, sessionState, EmailResponseObjectType, state, lang)
}, EmailResponseObjectType, result)
} else {
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
return req.notFound(accountId, EmailResponseObjectType, result)
}
})
}
@@ -657,14 +658,14 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
}
if makesSnippets {
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, position, anchor, anchorOffset, jmaplimit, ctx)
result, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, position, anchor, anchorOffset, jmaplimit, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
return req.jmapErrorN(allAccountIds, jerr, result)
}
var totalOverAllAccounts uint = 0
total := 0
for _, results := range resultsByAccountId {
for _, results := range result.Payload {
if results.Total != nil {
totalOverAllAccounts += *results.Total
}
@@ -678,7 +679,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
flattened = make([]Snippet, total)
{
i := 0
for accountId, results := range resultsByAccountId {
for accountId, results := range result.Payload {
for _, result := range results.Results {
flattened[i] = Snippet{
AccountId: accountId,
@@ -699,19 +700,19 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
Limit: limit,
}
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
return req.respondN(allAccountIds, body, EmailResponseObjectType, result)
} else {
withThreads := true
calculateTotal := true
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, position, anchor, anchorOffset, jmaplimit, withThreads, calculateTotal, ctx)
result, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, position, anchor, anchorOffset, jmaplimit, withThreads, calculateTotal, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
return req.jmapErrorN(allAccountIds, jerr, result)
}
var totalAcrossAllAccounts uint = 0
total := 0
for _, results := range resultsByAccountId {
for _, results := range result.Payload {
totalAcrossAllAccounts += results.Total
total += len(results.Emails)
}
@@ -723,7 +724,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
flattened = make([]jmap.Email, total)
{
i := 0
for accountId, results := range resultsByAccountId {
for accountId, results := range result.Payload {
for _, result := range results.Emails {
result.AccountId = accountId
flattened[i] = result
@@ -743,7 +744,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
Limit: limit,
}
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
return req.respondN(allAccountIds, body, EmailResponseObjectType, result)
}
})
}
@@ -754,12 +755,12 @@ var draftEmailAutoMailboxRolePrecedence = []string{
}
func findDraftsMailboxId(j *jmap.Client, accountId string, req Request, ctx jmap.Context) (string, Response) {
mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), draftEmailAutoMailboxRolePrecedence, ctx)
result, jerr := j.SearchMailboxIdsPerRole(single(accountId), draftEmailAutoMailboxRolePrecedence, ctx)
if jerr != nil {
return "", req.jmapError(accountId, jerr, sessionState, lang)
return "", req.jmapError(accountId, jerr, result)
} else {
for _, role := range draftEmailAutoMailboxRolePrecedence {
if mailboxId, ok := mailboxIdsPerAccountIds[accountId][role]; ok {
if mailboxId, ok := result.Payload[accountId][role]; ok {
return mailboxId, Response{}
}
}
@@ -777,13 +778,13 @@ var sentEmailAutoMailboxRolePrecedence = []string{
var draftAndSentMailboxRoles = structs.Uniq(structs.Concat(draftEmailAutoMailboxRolePrecedence, sentEmailAutoMailboxRolePrecedence))
func findSentMailboxId(j *jmap.Client, accountId string, req Request, ctx jmap.Context) (string, string, Response) { //NOSONAR
mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), draftAndSentMailboxRoles, ctx)
result, jerr := j.SearchMailboxIdsPerRole(single(accountId), draftAndSentMailboxRoles, ctx)
if jerr != nil {
return "", "", req.jmapError(accountId, jerr, sessionState, lang)
return "", "", req.jmapError(accountId, jerr, result)
} else {
sentMailboxId := ""
for _, role := range sentEmailAutoMailboxRolePrecedence {
if mailboxId, ok := mailboxIdsPerAccountIds[accountId][role]; ok {
if mailboxId, ok := result.Payload[accountId][role]; ok {
sentMailboxId = mailboxId
break
}
@@ -793,7 +794,7 @@ func findSentMailboxId(j *jmap.Client, accountId string, req Request, ctx jmap.C
}
draftsMailboxId := ""
for _, role := range draftEmailAutoMailboxRolePrecedence {
if mailboxId, ok := mailboxIdsPerAccountIds[accountId][role]; ok {
if mailboxId, ok := result.Payload[accountId][role]; ok {
draftsMailboxId = mailboxId
break
}
@@ -810,7 +811,10 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
func(r Request, accountId string, body *jmap.EmailChange, ctx jmap.Context) (bool, Response) {
if len(body.MailboxIds) < 1 {
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, r, ctx)
if mailboxId != "" {
if mailboxId != "" && body != nil {
if body.MailboxIds == nil {
body.MailboxIds = map[string]bool{}
}
body.MailboxIds[mailboxId] = true
} else {
return false, resp
@@ -818,7 +822,7 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
}
return true, Response{}
},
func(accountId string, body jmap.EmailChange, ctx jmap.Context) (*jmap.Email, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
func(accountId string, body jmap.EmailChange, ctx jmap.Context) (jmap.Result[*jmap.Email], jmap.Error) {
return g.jmap.CreateEmail(accountId, body, "", ctx)
},
)
@@ -844,7 +848,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
return true, Response{}
},
func(accountId string, body jmap.EmailChange, ctx jmap.Context) (*jmap.Email, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
func(accountId string, body jmap.EmailChange, ctx jmap.Context) (jmap.Result[*jmap.Email], jmap.Error) {
ctx = ctx.WithLogger(log.From(ctx.Logger.With().Str("replaceId", replaceId)))
return g.jmap.CreateEmail(accountId, body, replaceId, ctx)
},
@@ -907,22 +911,22 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
emailId: patch,
}
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
result, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if result == nil {
if result.Payload == nil {
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response", //NOSONAR
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint"))) //NOSONAR
}
updatedEmail, ok := result[emailId]
updatedEmail, ok := result.Payload[emailId]
if !ok {
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID", //NOSONAR
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint"))) //NOSONAR
}
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, updatedEmail, EmailResponseObjectType, result)
})
}
@@ -966,25 +970,25 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
emailId: patch,
}
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
result, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if result == nil {
if result.Payload == nil {
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
}
updatedEmail, ok := result[emailId]
updatedEmail, ok := result.Payload[emailId]
if !ok {
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
}
if updatedEmail == nil {
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
return req.noContent(accountId, EmailResponseObjectType, result)
} else {
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, updatedEmail, EmailResponseObjectType, result)
}
})
}
@@ -1029,25 +1033,25 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
emailId: patch,
}
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
result, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if result == nil {
if result.Payload == nil {
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
}
updatedEmail, ok := result[emailId]
updatedEmail, ok := result.Payload[emailId]
if !ok {
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
}
if updatedEmail == nil {
return req.noContent(accountId, sessionState, EmailResponseObjectType, state)
return req.noContent(accountId, EmailResponseObjectType, result)
} else {
return req.respond(accountId, updatedEmail, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, updatedEmail, EmailResponseObjectType, result)
}
})
}
@@ -1116,12 +1120,12 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { //NOSONA
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
resp, sessionState, state, lang, jerr := g.jmap.SubmitEmail(accountId, identityId, emailId, move, ctx)
result, jerr := g.jmap.SubmitEmail(accountId, identityId, emailId, move, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
return req.respond(accountId, resp, sessionState, EmailResponseObjectType, state, lang)
return req.respond(accountId, result.Payload, EmailResponseObjectType, result)
})
}
@@ -1221,21 +1225,21 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
reqId := req.GetRequestId()
getEmailsBefore := time.Now()
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, single(id), true, g.config.maxBodyValueBytes, false, false, ctx)
result, jerr := g.jmap.GetEmails(accountId, single(id), true, g.config.maxBodyValueBytes, false, false, ctx)
getEmailsDuration := time.Since(getEmailsBefore)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if len(emails) < 1 {
if len(result.Payload.List) < 1 {
req.observe(g.metrics.EmailByIdDuration.WithLabelValues(req.session.JmapEndpoint, metrics.Values.Result.NotFound), getEmailsDuration.Seconds())
logger.Trace().Msg("failed to find any emails matching id") // the id is already in the log field
return req.notFound(accountId, sessionState, EmailResponseObjectType, state)
return req.notFound(accountId, EmailResponseObjectType, result)
} else {
req.observe(g.metrics.EmailByIdDuration.WithLabelValues(req.session.JmapEndpoint, metrics.Values.Result.Found), getEmailsDuration.Seconds())
}
email := emails[0]
email := result.Payload.List[0]
beacon := email.ReceivedAt // TODO configurable: either relative to when the email was received, or relative to now
//beacon := time.Now()
@@ -1247,8 +1251,8 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
g.job(logger, RelationTypeSameSender, func(jobId uint64, l *log.Logger) {
before := time.Now()
ctx = ctx.WithLogger(logger).WithContext(bgctx)
resultsByAccountId, _, _, lang, jerr := g.jmap.QueryEmails(single(accountId), filter, 0, limit, false, g.config.maxBodyValueBytes, ctx)
if results, ok := resultsByAccountId[accountId]; ok {
results, jerr := g.jmap.QueryEmails(single(accountId), filter, 0, limit, false, g.config.maxBodyValueBytes, ctx)
if results, ok := results.Payload[accountId]; ok {
duration := time.Since(before)
if jerr != nil {
_ = req.observeJmapError(jerr)
@@ -1259,7 +1263,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
if err == nil {
l.Trace().Msgf("'%v' found %v other emails", RelationTypeSameSender, len(related))
if len(related) > 0 {
req.push(RelationEntityEmail, AboutEmailsEvent{Id: reqId, Emails: related, Source: RelationTypeSameSender, Language: lang})
req.push(RelationEntityEmail, AboutEmailsEvent{Id: reqId, Emails: related, Source: RelationTypeSameSender, Language: result.GetLanguage()})
}
}
}
@@ -1269,18 +1273,18 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
g.job(logger, RelationTypeSameThread, func(jobId uint64, l *log.Logger) {
before := time.Now()
ctx = ctx.WithLogger(logger).WithContext(bgctx)
emails, _, _, lang, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, false, g.config.maxBodyValueBytes, ctx)
results, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, false, g.config.maxBodyValueBytes, ctx)
duration := time.Since(before)
if jerr != nil {
_ = req.observeJmapError(jerr)
l.Error().Err(jerr).Msgf("failed to list %v emails", RelationTypeSameThread)
} else {
req.observe(g.metrics.EmailSameThreadDuration.WithLabelValues(req.session.JmapEndpoint), duration.Seconds())
related, err := req.sanitizeEmails(filterEmails(emails, email))
related, err := req.sanitizeEmails(filterEmails(results.Payload, email))
if err == nil {
l.Trace().Msgf("'%v' found %v other emails", RelationTypeSameThread, len(related))
if len(related) > 0 {
req.push(RelationEntityEmail, AboutEmailsEvent{Id: reqId, Emails: related, Source: RelationTypeSameThread, Language: lang})
req.push(RelationEntityEmail, AboutEmailsEvent{Id: reqId, Emails: related, Source: RelationTypeSameThread, Language: result.GetLanguage()})
}
}
}
@@ -1293,7 +1297,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
return req.respond(accountId, AboutEmailResponse{
Email: sanitized,
RequestId: reqId,
}, sessionState, EmailResponseObjectType, state, lang)
}, EmailResponseObjectType, result)
})
}
@@ -1562,19 +1566,19 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
calculateTotal := true
withThreads := true
emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, position, anchor, anchorOffset, &limit, withThreads, calculateTotal, ctx)
result, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, position, anchor, anchorOffset, &limit, withThreads, calculateTotal, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
return req.jmapErrorN(allAccountIds, jerr, result)
}
// sort in memory to respect the overall limit
total := uint(0)
for _, emails := range emailsSummariesByAccount {
for _, emails := range result.Payload {
total += uint(max(len(emails.Emails), 0))
}
all := make([]emailWithAccountId, total)
i := uint(0)
for accountId, emails := range emailsSummariesByAccount {
for accountId, emails := range result.Payload {
for _, email := range emails.Emails {
all[i] = emailWithAccountId{accountId: accountId, email: email}
i++
@@ -1597,7 +1601,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
body.Total = &total
}
return req.respondN(allAccountIds, body, sessionState, EmailResponseObjectType, state, lang)
return req.respondN(allAccountIds, body, EmailResponseObjectType, result)
})
}

View File

@@ -72,11 +72,15 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
}
func curryMapQuery[SRES jmap.SearchResults[T], T jmap.Foo, FILTER any, COMP any](
f func(accountIds []string, filter FILTER, sortBy []COMP, position int, anchor string, anchorOffset *int, limit *uint, calculateTotal bool, ctx jmap.Context) (map[string]SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
) func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
return func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (SRES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) { //NOSONAR
m, sessionState, state, lang, err := f(single(accountId), filter, sortBy, position, anchor, anchorOffset, limit, true, ctx)
return m[accountId], sessionState, state, lang, err
f func(accountIds []string, filter FILTER, sortBy []COMP, position int, anchor string, anchorOffset *int, limit *uint, calculateTotal bool, ctx jmap.Context) (jmap.Result[map[string]SRES], jmap.Error),
) func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (jmap.Result[SRES], jmap.Error) {
return func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (jmap.Result[SRES], jmap.Error) { //NOSONAR
result, err := f(single(accountId), filter, sortBy, position, anchor, anchorOffset, limit, true, ctx)
if err != nil {
return jmap.ZeroResult[SRES](), err
} else {
return jmap.RefineResult(result, func(m map[string]SRES) SRES { return m[accountId] }), err
}
}
}
@@ -130,10 +134,10 @@ func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
l := req.logger.With().Array(UriParamBlobId, log.SafeStringArray(blobIds))
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, blobIds, ctx)
result, jerr := g.jmap.ParseICalendarBlob(accountId, blobIds, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
return req.respond(accountId, resp, sessionState, EventResponseObjectType, state, lang)
return req.respond(accountId, result.Payload, EventResponseObjectType, result)
})
}

View File

@@ -148,19 +148,19 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountIds := req.AllAccountIds()
boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.ctx)
result, err := g.jmap.GetBootstrap(accountIds, req.ctx)
if err != nil {
return req.jmapErrorN(accountIds, err, sessionState, lang)
return req.jmapErrorN(accountIds, err, result)
}
var body IndexResponse = IndexResponse{
Version: Version,
Capabilities: Capabilities,
Limits: buildIndexLimits(req.session),
Accounts: buildIndexAccounts(req.session, boot),
Accounts: buildIndexAccounts(req.session, result.Payload),
PrimaryAccounts: buildIndexPrimaryAccounts(req.session),
}
return req.respondN(accountIds, body, sessionState, IndexResponseObjectType, state, lang)
return req.respondN(accountIds, body, IndexResponseObjectType, result)
})
}

View File

@@ -75,25 +75,25 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS
ctx := req.ctx.WithLogger(logger)
if hasCriteria {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(single(accountId), filter, ctx)
result, err := g.jmap.SearchMailboxes(single(accountId), filter, ctx)
if err != nil {
return req.jmapError(accountId, err, sessionState, lang)
return req.jmapError(accountId, err, result)
}
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
return req.respond(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang)
if mailboxes, ok := result.Payload[accountId]; ok {
return req.respond(accountId, sortMailboxSlice(mailboxes), MailboxResponseObjectType, result)
} else {
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
return req.notFound(accountId, MailboxResponseObjectType, result)
}
} else {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(single(accountId), ctx)
result, err := g.jmap.GetAllMailboxes(single(accountId), ctx)
if err != nil {
return req.jmapError(accountId, err, sessionState, lang)
return req.jmapError(accountId, err, result)
}
if mailboxes, ok := mailboxesByAccountId[accountId]; ok {
return req.respond(accountId, sortMailboxSlice(mailboxes), sessionState, MailboxResponseObjectType, state, lang)
if mailboxes, ok := result.Payload[accountId]; ok {
return req.respond(accountId, sortMailboxSlice(mailboxes), MailboxResponseObjectType, result)
} else {
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
return req.notFound(accountId, MailboxResponseObjectType, result)
}
}
})
@@ -125,17 +125,17 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
}
if hasCriteria {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, filter, ctx)
result, err := g.jmap.SearchMailboxes(accountIds, filter, ctx)
if err != nil {
return req.jmapErrorN(accountIds, err, sessionState, lang)
return req.jmapErrorN(accountIds, err, result)
}
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(accountIds, sortMailboxesMap(result.Payload), MailboxResponseObjectType, result)
} else {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, ctx)
result, err := g.jmap.GetAllMailboxes(accountIds, ctx)
if err != nil {
return req.jmapErrorN(accountIds, err, sessionState, lang)
return req.jmapErrorN(accountIds, err, result)
}
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(accountIds, sortMailboxesMap(result.Payload), MailboxResponseObjectType, result)
}
})
}
@@ -160,11 +160,11 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
Role: role,
}
mailboxesByAccountId, sessionState, state, lang, jerr := g.jmap.SearchMailboxes(accountIds, filter, ctx)
result, jerr := g.jmap.SearchMailboxes(accountIds, filter, ctx)
if jerr != nil {
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
return req.jmapErrorN(accountIds, jerr, result)
}
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(accountIds, sortMailboxesMap(result.Payload), MailboxResponseObjectType, result)
})
}
@@ -206,12 +206,12 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
ctx := req.ctx.WithLogger(logger)
sinceStateMap := structs.MapValues(sinceStateStrMap, toState)
changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, sinceStateMap, maxChanges, ctx)
result, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, sinceStateMap, maxChanges, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
return req.jmapErrorN(allAccountIds, jerr, result)
}
return req.respondN(allAccountIds, changesByAccountId, sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(allAccountIds, result.Payload, MailboxResponseObjectType, result)
})
}
@@ -225,12 +225,12 @@ func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) {
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, ctx)
result, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
return req.jmapErrorN(allAccountIds, jerr, result)
}
return req.respondN(allAccountIds, rolesByAccountId, sessionState, MailboxResponseObjectType, state, lang)
return req.respondN(allAccountIds, result.Payload, MailboxResponseObjectType, result)
})
}
@@ -238,11 +238,6 @@ func (g *Groupware) CreateMailbox(w http.ResponseWriter, r *http.Request) {
create(Mailbox, w, r, g, nil, g.jmap.CreateMailbox)
}
// Delete Mailboxes by their unique identifiers.
//
// Returns the identifiers of the Mailboxes that have successfully been deleted.
//
// @api:example deletedmailboxes
func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) {
delete(Mailbox, w, r, g, g.jmap.DeleteMailboxes)
}

View File

@@ -121,15 +121,15 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
objs, sessionState, state, lang, jerr := g.jmap.GetObjects(accountId,
result, jerr := g.jmap.GetObjects(accountId,
mailboxIds, emailIds, addressbookIds, contactIds, calendarIds, eventIds, quotaIds, identityIds, emailSubmissionIds,
ctx,
)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
var body jmap.Objects = objs
var body jmap.Objects = result.Payload
return req.respond(accountId, body, sessionState, "", state, lang)
return req.respond(accountId, body, UnspecifiedResponseObjectType, result)
})
}

View File

@@ -13,7 +13,7 @@ import (
//
// Note that there may be multiple Quota objects for different resource types.
func (g *Groupware) GetQuota(w http.ResponseWriter, r *http.Request) {
getFromMap(Quota, w, r, g, func(accountIds, _ []string, ctx jmap.Context) (map[string]jmap.QuotaGetResponse, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
getFromMap(Quota, w, r, g, func(accountIds, _ []string, ctx jmap.Context) (jmap.Result[map[string]jmap.QuotaGetResponse], jmap.Error) {
return g.jmap.GetQuotas(accountIds, ctx)
})
}
@@ -36,19 +36,19 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
ctx := req.ctx.WithLogger(logger)
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(accountIds, ctx)
result, jerr := g.jmap.GetQuotas(accountIds, ctx)
if jerr != nil {
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
return req.jmapErrorN(accountIds, jerr, result)
}
result := make(map[string]AccountQuota, len(res))
for accountId, accountQuotas := range res {
result[accountId] = AccountQuota{
body := make(map[string]AccountQuota, len(result.Payload))
for accountId, accountQuotas := range result.Payload {
body[accountId] = AccountQuota{
State: accountQuotas.State,
Quotas: accountQuotas.List,
}
}
return req.respondN(accountIds, result, sessionState, QuotaResponseObjectType, state, lang)
return req.respondN(accountIds, body, QuotaResponseObjectType, result)
})
}

View File

@@ -16,7 +16,8 @@ func (g *Groupware) GetTaskLists(w http.ResponseWriter, r *http.Request) {
var _ string = accountId
var body []jmap.TaskList = AllTaskLists
return req.respond(accountId, body, req.session.State, TaskListResponseObjectType, TaskListsState, jmap.NoLanguage)
meta := TaskListsMeta{SessionState: req.session.State}
return req.respond(accountId, body, TaskListResponseObjectType, meta)
})
}
@@ -34,9 +35,10 @@ func (g *Groupware) GetTaskListById(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
// TODO replace with proper implementation
meta := TaskListsMeta{SessionState: req.session.State}
for _, tasklist := range AllTaskLists {
if tasklist.Id == tasklistId {
return req.respond(accountId, tasklist, req.session.State, TaskListResponseObjectType, TaskListsState, jmap.NoLanguage)
return req.respond(accountId, tasklist, TaskListResponseObjectType, meta)
}
}
return req.etaggedNotFound(accountId, req.session.State, TaskListResponseObjectType, TaskListsState)
@@ -57,10 +59,11 @@ func (g *Groupware) GetTasksInTaskList(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
// TODO replace with proper implementation
meta := TaskMeta{SessionState: req.session.State}
tasks, ok := TaskMapByTaskListId[tasklistId]
if !ok {
return req.notFound(accountId, req.session.State, TaskResponseObjectType, TaskState)
return req.notFound(accountId, TaskResponseObjectType, meta)
}
return req.respond(accountId, tasks, req.session.State, TaskResponseObjectType, TaskState, jmap.NoLanguage)
return req.respond(accountId, tasks, TaskResponseObjectType, meta)
})
}

View File

@@ -13,7 +13,7 @@ import (
//
// The VacationResponse object represents the state of vacation-response-related settings for an account.
func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
get(VacationResponse, w, r, g, func(accountId string, ids []string, ctx jmap.Context) (jmap.VacationResponseGetResponse, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
get(VacationResponse, w, r, g, func(accountId string, ids []string, ctx jmap.Context) (jmap.Result[jmap.VacationResponseGetResponse], jmap.Error) {
return g.jmap.GetVacationResponse(accountId, ctx)
})
}
@@ -23,7 +23,7 @@ func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
// A vacation response sends an automatic reply when a message is delivered to the mail store, informing the original
// sender that their message may not be read for some time.
func (g *Groupware) SetVacation(w http.ResponseWriter, r *http.Request) {
modify(VacationResponse, w, r, g, func(accountId string, id string, change jmap.VacationResponseChange, ctx jmap.Context) (jmap.VacationResponse, jmap.SessionState, jmap.State, jmap.Language, jmap.Error) {
modify(VacationResponse, w, r, g, func(accountId string, id string, change jmap.VacationResponseChange, ctx jmap.Context) (jmap.Result[jmap.VacationResponse], jmap.Error) {
return g.jmap.SetVacationResponse(accountId, change, ctx)
})
}

View File

@@ -134,11 +134,11 @@ func groupwareErrorFromJmap(j jmap.Error) *GroupwareError {
return &ErrorSendingRequest
case jmap.JmapErrorInvalidSessionResponse:
return &ErrorInvalidSessionResponse
case jmap.JmapErrorInvalidJmapRequestPayload:
case jmap.JmapErrorInvalidJmapRequestPayload, jmap.JmapErrorInvalidProperties:
return &ErrorInvalidRequestPayload
case jmap.JmapErrorInvalidJmapResponsePayload:
return &ErrorInvalidResponsePayload
case jmap.JmapInvalidObjectState:
case jmap.JmapErrorInvalidObjectState:
return &ErrorInvalidObjectState
case jmap.JmapErrorUnspecifiedType, jmap.JmapErrorUnknownMethod, jmap.JmapErrorInvalidArguments, jmap.JmapErrorInvalidResultReference:
return &ErrorInvalidGroupwareRequest
@@ -748,18 +748,18 @@ func (r *Request) error(accountId string, err *Error) Response {
return errorResponse(single(accountId), err, r.session.State, jmap.NoLanguage)
}
func (r *Request) errorS(accountId string, err *Error, sessionState jmap.SessionState) Response {
return errorResponse(single(accountId), err, sessionState, jmap.NoLanguage)
func (r *Request) errorS(accountId string, err *Error, result jmap.ResultMetadata) Response {
return errorResponse(single(accountId), err, result.GetSessionState(), result.GetLanguage())
}
func (r *Request) errorN(accountIds []string, err *Error) Response {
return errorResponse(accountIds, err, r.session.State, jmap.NoLanguage)
}
func (r *Request) jmapError(accountId string, err jmap.Error, sessionState jmap.SessionState, lang jmap.Language) Response {
return errorResponse(single(accountId), r.apiErrorFromJmap(r.observeJmapError(err)), sessionState, lang)
func (r *Request) jmapError(accountId string, err jmap.Error, result jmap.ResultMetadata) Response {
return errorResponse(single(accountId), r.apiErrorFromJmap(r.observeJmapError(err)), result.GetSessionState(), result.GetLanguage())
}
func (r *Request) jmapErrorN(accountIds []string, err jmap.Error, sessionState jmap.SessionState, lang jmap.Language) Response {
return errorResponse(accountIds, r.apiErrorFromJmap(r.observeJmapError(err)), sessionState, lang)
func (r *Request) jmapErrorN(accountIds []string, err jmap.Error, result jmap.ResultMetadata) Response {
return errorResponse(accountIds, r.apiErrorFromJmap(r.observeJmapError(err)), result.GetSessionState(), result.GetLanguage())
}

View File

@@ -201,6 +201,16 @@ var AllTaskLists = []jmap.TaskList{TL1}
var TaskListsState = jmap.State("mock")
type TaskListsMeta struct {
SessionState jmap.SessionState
}
func (t TaskListsMeta) GetSessionState() jmap.SessionState { return t.SessionState }
func (t TaskListsMeta) GetState() jmap.State { return TaskListsState }
func (t TaskListsMeta) GetLanguage() jmap.Language { return jmap.NoLanguage }
var _ jmap.ResultMetadata = TaskListsMeta{}
var TaskMapByTaskListId = map[string][]jmap.Task{
TL1.Id: {
T1,
@@ -209,6 +219,16 @@ var TaskMapByTaskListId = map[string][]jmap.Task{
var TaskState = jmap.State("mock")
type TaskMeta struct {
SessionState jmap.SessionState
}
func (t TaskMeta) GetSessionState() jmap.SessionState { return t.SessionState }
func (t TaskMeta) GetState() jmap.State { return TaskState }
func (t TaskMeta) GetLanguage() jmap.Language { return jmap.NoLanguage }
var _ jmap.ResultMetadata = TaskListsMeta{}
func mustParseTime(text string) time.Time {
t, err := time.Parse(time.RFC3339, text)
if err != nil {

View File

@@ -413,10 +413,13 @@ func (r *Request) body(target any) *Error {
}
}(body)
err := json.NewDecoder(body).Decode(target)
decoder := json.NewDecoder(body)
decoder.DisallowUnknownFields()
err := decoder.Decode(target)
if err != nil {
r.logger.Warn().Msgf("failed to deserialize the request body: %s", err.Error())
return r.observedParameterError(ErrorInvalidRequestBody, withSource(&ErrorSource{Pointer: "/"})) // we don't get any details here
// we don't get any structured details here
return r.observedParameterError(ErrorInvalidRequestBody, withSource(&ErrorSource{Pointer: "/"}), withDetail(err.Error()))
}
return nil
}

View File

@@ -9,6 +9,7 @@ import (
type ResponseObjectType string
const (
UnspecifiedResponseObjectType = ResponseObjectType("")
IndexResponseObjectType = ResponseObjectType("index")
AccountResponseObjectType = ResponseObjectType("account")
IdentityResponseObjectType = ResponseObjectType("identity")
@@ -74,12 +75,12 @@ func etaggedResponse(accountIds []string, body any, sessionState jmap.SessionSta
}
}
func (r *Request) respond(accountId string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, lang jmap.Language) Response {
return etaggedResponse(single(accountId), body, sessionState, objectType, etag, lang)
func (r *Request) respond(accountId string, body any, objectType ResponseObjectType, result jmap.ResultMetadata) Response {
return etaggedResponse(single(accountId), body, result.GetSessionState(), objectType, result.GetState(), result.GetLanguage())
}
func (r *Request) respondN(accountIds []string, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, lang jmap.Language) Response {
return etaggedResponse(accountIds, body, sessionState, objectType, etag, lang)
func (r *Request) respondN(accountIds []string, body any, objectType ResponseObjectType, result jmap.ResultMetadata) Response {
return etaggedResponse(accountIds, body, result.GetSessionState(), objectType, result.GetState(), result.GetLanguage())
}
/*
@@ -126,8 +127,8 @@ func noContentResponseWithEtag(accountIds []string, sessionState jmap.SessionSta
}
}
func (r *Request) noContent(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return noContentResponseWithEtag(single(accountId), sessionState, objectType, etag)
func (r *Request) noContent(accountId string, objectType ResponseObjectType, result jmap.ResultMetadata) Response {
return noContentResponseWithEtag(single(accountId), result.GetSessionState(), objectType, result.GetState())
}
/*
@@ -166,8 +167,8 @@ func notFoundResponse(accountIds []string, sessionState jmap.SessionState, objec
}
}
func (r *Request) notFound(accountId string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
return notFoundResponse(single(accountId), sessionState, objectType, etag)
func (r *Request) notFound(accountId string, objectType ResponseObjectType, result jmap.ResultMetadata) Response {
return notFoundResponse(single(accountId), result.GetSessionState(), objectType, result.GetState())
}
func etaggedNotFoundResponse(accountIds []string, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {

View File

@@ -15,7 +15,7 @@ func create[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
w http.ResponseWriter, r *http.Request,
g *Groupware,
bodyFunc func(r Request, accountId string, body *CHANGE, ctx jmap.Context) (bool, Response),
createFunc func(accountId string, change CHANGE, ctx jmap.Context) (*T, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
createFunc func(accountId string, change CHANGE, ctx jmap.Context) (jmap.Result[*T], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -43,11 +43,11 @@ func create[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
}
}
created, sessionState, state, lang, jerr := createFunc(accountId, create, ctx)
result, jerr := createFunc(accountId, create, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
return req.respond(accountId, created, sessionState, o.responseType, state, lang)
return req.respond(accountId, result.Payload, o.responseType, result)
})
}
@@ -57,7 +57,7 @@ func getall[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.G
o ObjectType[T, CHANGE, CHANGES],
w http.ResponseWriter, r *http.Request,
g *Groupware,
getFunc func(accountId string, ids []string, ctx jmap.Context) (RESP, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
getFunc func(accountId string, ids []string, ctx jmap.Context) (jmap.Result[RESP], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -72,11 +72,11 @@ func getall[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.G
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
objs, sessionState, state, lang, jerr := getFunc(accountId, []string{}, ctx)
result, jerr := getFunc(accountId, []string{}, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
return req.respond(accountId, objs, sessionState, o.responseType, state, lang)
return req.respond(accountId, result.Payload, o.responseType, result)
})
}
@@ -91,7 +91,7 @@ func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER
withContainerId bool,
filterFunc func(containerId string) FILTER,
sortBy []COMP,
queryFunc func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (SEARCHRESULTS, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
queryFunc func(req Request, accountId string, filter FILTER, sortBy []COMP, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (jmap.Result[SEARCHRESULTS], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -160,20 +160,20 @@ func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
results, sessionState, state, lang, jerr := queryFunc(req, accountId, filter, sortBy, position, anchor, anchorOffset, jmaplimit, ctx)
result, jerr := queryFunc(req, accountId, filter, sortBy, position, anchor, anchorOffset, jmaplimit, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if limit != nil && *limit == 0 {
results.RemoveResults()
results.SetLimit(UintPtrZero)
result.Payload.RemoveResults()
result.Payload.SetLimit(UintPtrZero)
}
if anchor != "" && results.GetPosition() != nil && *results.GetPosition() == 0 {
results.SetPosition(nil)
if anchor != "" && result.Payload.GetPosition() != nil && *result.Payload.GetPosition() == 0 {
result.Payload.SetPosition(nil)
}
return req.respond(accountId, results, sessionState, o.responseType, state, lang)
return req.respond(accountId, result.Payload, o.responseType, result)
})
}
@@ -184,7 +184,7 @@ func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULT
w http.ResponseWriter, r *http.Request,
g *Groupware,
defaultLimit uint,
queryFunc func(req Request, accountId string, containerId string, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (SEARCHRESULTS, jmap.SessionState, jmap.State, jmap.Language, *Error),
queryFunc func(req Request, accountId string, containerId string, position int, anchor string, anchorOffset *int, limit *uint, ctx jmap.Context) (jmap.Result[SEARCHRESULTS], *Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -250,20 +250,20 @@ func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULT
jmaplimit = UintPtrOne
}
results, sessionState, state, lang, err := queryFunc(req, accountId, containerId, position, anchor, anchorOffset, jmaplimit, ctx)
result, err := queryFunc(req, accountId, containerId, position, anchor, anchorOffset, jmaplimit, ctx)
if err != nil {
return req.error(accountId, err)
}
if limit != nil && *limit == 0 {
results.RemoveResults()
results.SetLimit(UintPtrZero)
result.Payload.RemoveResults()
result.Payload.SetLimit(UintPtrZero)
}
if anchor != "" && results.GetPosition() != nil && *results.GetPosition() == 0 {
results.SetPosition(nil)
if anchor != "" && result.Payload.GetPosition() != nil && *result.Payload.GetPosition() == 0 {
result.Payload.SetPosition(nil)
}
return req.respond(accountId, results, sessionState, o.responseType, state, lang)
return req.respond(accountId, result.Payload, o.responseType, result)
})
}
@@ -274,7 +274,7 @@ func get[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetR
o ObjectType[T, CHANGE, CHANGES],
w http.ResponseWriter, r *http.Request,
g *Groupware,
getFunc func(accountId string, ids []string, ctx jmap.Context) (RESP, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
getFunc func(accountId string, ids []string, ctx jmap.Context) (jmap.Result[RESP], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -298,20 +298,20 @@ func get[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetR
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
objs, sessionState, state, lang, jerr := getFunc(accountId, ids, ctx)
result, jerr := getFunc(accountId, ids, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
n := len(objs.GetList())
n := len(result.Payload.GetList())
switch n {
case 0:
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
return req.notFound(accountId, ContactResponseObjectType, result)
case 1:
return req.respond(accountId, objs.GetList()[0], sessionState, ContactResponseObjectType, state, lang)
return req.respond(accountId, result.Payload.GetList()[0], ContactResponseObjectType, result)
default:
logger.Error().Msgf("found %d %s matching '%s' instead of 1", n, o.responseType, ids)
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), sessionState)
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), result)
}
})
}
@@ -323,7 +323,7 @@ func getFromMap[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jm
o ObjectType[T, CHANGE, CHANGES],
w http.ResponseWriter, r *http.Request,
g *Groupware,
getFunc func(accountIds []string, ids []string, ctx jmap.Context) (map[string]RESP, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
getFunc func(accountIds []string, ids []string, ctx jmap.Context) (jmap.Result[map[string]RESP], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -343,24 +343,24 @@ func getFromMap[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jm
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
objMap, sessionState, state, lang, jerr := getFunc(single(accountId), single(id), ctx)
result, jerr := getFunc(single(accountId), single(id), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
if objs, ok := objMap[accountId]; ok {
if objs, ok := result.Payload[accountId]; ok {
n := len(objs.GetList())
switch n {
case 0:
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
return req.notFound(accountId, ContactResponseObjectType, result)
case 1:
return req.respond(accountId, objs.GetList()[0], sessionState, ContactResponseObjectType, state, lang)
return req.respond(accountId, objs.GetList()[0], ContactResponseObjectType, result)
default:
logger.Error().Msgf("found %d %s matching '%s' instead of 1", n, o.responseType, id)
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), sessionState)
return req.errorS(accountId, req.apiError(&ErrorMultipleIdMatches), result)
}
} else {
return req.notFound(accountId, sessionState, ContactResponseObjectType, state)
return req.notFound(accountId, ContactResponseObjectType, result)
}
})
}
@@ -374,7 +374,7 @@ func changes[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
o ObjectType[T, CHANGE, CHANGES],
w http.ResponseWriter, r *http.Request,
g *Groupware,
changesFunc func(accountId string, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (CHANGES, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
changesFunc func(accountId string, sinceState jmap.State, maxChanges uint, ctx jmap.Context) (jmap.Result[CHANGES], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -402,23 +402,24 @@ func changes[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := changesFunc(accountId, sinceState, maxChanges, ctx)
result, jerr := changesFunc(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
return req.respond(accountId, changes, sessionState, o.responseType, state, lang)
return req.respond(accountId, result.Payload, o.responseType, result)
})
}
// Delete a specific {{.Name}} referenced by its unique identifier as specified in the path parameter `{{.UriParamName}}` in the path `{{.Path}}`
// @api:success 204
// @api:response 204 when the referenced {{.Name}} has been deleted successfully
// @api:response 404 when there is no {{.Name}} for the requested identifier
func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
o ObjectType[T, CHANGE, CHANGES],
w http.ResponseWriter, r *http.Request,
g *Groupware,
deleteFunc func(accountId string, ids []string, ctx jmap.Context) (map[string]jmap.SetError, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
deleteFunc func(accountId string, ids []string, ctx jmap.Context) (jmap.Result[map[string]jmap.SetError], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -438,12 +439,12 @@ func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
setErrors, sessionState, state, lang, jerr := deleteFunc(accountId, single(id), ctx)
result, jerr := deleteFunc(accountId, single(id), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
for _, e := range setErrors {
for _, e := range result.Payload {
desc := e.Description
if desc != "" {
return req.error(accountId, apiError(
@@ -458,7 +459,7 @@ func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
))
}
}
return req.noContent(accountId, sessionState, o.responseType, state)
return req.noContent(accountId, o.responseType, result)
})
}
@@ -472,7 +473,7 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
o ObjectType[T, CHANGE, CHANGES],
w http.ResponseWriter, r *http.Request,
g *Groupware,
deleteFunc func(accountId string, ids []string, ctx jmap.Context) (map[string]jmap.SetError, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
deleteFunc func(accountId string, ids []string, ctx jmap.Context) (jmap.Result[map[string]jmap.SetError], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -523,12 +524,12 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
setErrors, sessionState, state, lang, jerr := deleteFunc(accountId, ids, ctx)
result, jerr := deleteFunc(accountId, ids, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
for _, e := range setErrors {
for _, e := range result.Payload {
desc := e.Description
if desc != "" {
return req.error(accountId, apiError(
@@ -543,7 +544,7 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
))
}
}
return req.noContent(accountId, sessionState, o.responseType, state)
return req.noContent(accountId, o.responseType, result)
})
}
@@ -553,7 +554,7 @@ func modify[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
o ObjectType[T, CHANGE, CHANGES],
w http.ResponseWriter, r *http.Request,
g *Groupware,
updateFunc func(accountId string, id string, change CHANGE, ctx jmap.Context) (T, jmap.SessionState, jmap.State, jmap.Language, jmap.Error),
updateFunc func(accountId string, id string, change CHANGE, ctx jmap.Context) (jmap.Result[T], jmap.Error),
) {
g.respond(w, r, func(req Request) Response {
ok, accountId, resp := o.accountFunc(&req)
@@ -579,10 +580,10 @@ func modify[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
updated, sessionState, state, lang, jerr := updateFunc(accountId, id, change, ctx)
result, jerr := updateFunc(accountId, id, change, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
return req.jmapError(accountId, jerr, result)
}
return req.respond(accountId, updated, sessionState, o.responseType, state, lang)
return req.respond(accountId, result.Payload, o.responseType, result)
})
}

View File

@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@redocly/cli':
specifier: ^2.30.1
version: 2.30.1(@opentelemetry/api@1.9.1)(core-js@3.45.1)
specifier: ^2.30.2
version: 2.30.2(@opentelemetry/api@1.9.1)(core-js@3.45.1)
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
@@ -197,8 +197,8 @@ packages:
'@redocly/cli-otel@0.1.2':
resolution: {integrity: sha512-Bg7BoO5t1x3lVK+KhA5aGPmeXpQmdf6WtTYHhelKJCsQ+tRMiJoFAQoKHoBHAoNxXrhlS3K9lKFLHGmtxsFQfA==}
'@redocly/cli@2.30.1':
resolution: {integrity: sha512-n5lRNAuA5Sz+pFn6VKhngUlj3E6bR0NtUF3eWzsuVWc3ffu5TyLhD12xRcASyi+aW7Z1z33/leGwIvKRKeG3xg==}
'@redocly/cli@2.30.2':
resolution: {integrity: sha512-DWTydfVgEJkqDMcriRuy+MX+IYaEPU0AvV/nKuT1/1ajTugofkxlClHfZOK8Kwv47qAHvZ9w3oF+WCP/fVyW2g==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
hasBin: true
@@ -212,12 +212,12 @@ packages:
resolution: {integrity: sha512-4Tm4ysZkexx6ZTX7knqSZTqPlNgIvXc7Ha0pd30I694/GD0KtJE2xrElycfPds0vCLFAqoKyIzBtOF1xrLo8KA==}
engines: {node: '>=18.17.0', npm: '>=9.5.0'}
'@redocly/openapi-core@2.30.1':
resolution: {integrity: sha512-ggv0nRy9Y7D1PxsmXE8MWU/x6EOVC/njZw1s7Z5TVt7OzHzLUiB2AroZzsU1dIMl2KRm4n3ygdo2VlNAAovyGQ==}
'@redocly/openapi-core@2.30.2':
resolution: {integrity: sha512-J1UB/I1s9eRpirIVgzH/B1Jj+hYQHYExruLk+edPOqneFIlFc38wKiTRkj/TVpwcmzRHJNu5SSI6NTrrfPa4BA==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
'@redocly/respect-core@2.30.1':
resolution: {integrity: sha512-b3IiUa+oFcUppfUNcNubyKJ1/Gnt8brlU2+MrF0E+tpFJANMLXejQ0DcNN7t6lq2ZgCyp9DDK76tlJMmOIIcAQ==}
'@redocly/respect-core@2.30.2':
resolution: {integrity: sha512-4dVg57ItG19MGsLWDPmbUid81kHEbGqLcSSp1Q/8wHGJVzxmFgX9JOkqEw059/dwCYjP+txPhcMDORh0pK9ivQ==}
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
'@tsconfig/node10@1.0.12':
@@ -1133,15 +1133,15 @@ snapshots:
dependencies:
ulid: 2.4.0
'@redocly/cli@2.30.1(@opentelemetry/api@1.9.1)(core-js@3.45.1)':
'@redocly/cli@2.30.2(@opentelemetry/api@1.9.1)(core-js@3.45.1)':
dependencies:
'@opentelemetry/exporter-trace-otlp-http': 0.214.0(@opentelemetry/api@1.9.1)
'@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
'@opentelemetry/sdk-trace-node': 2.6.1(@opentelemetry/api@1.9.1)
'@opentelemetry/semantic-conventions': 1.40.0
'@redocly/cli-otel': 0.1.2
'@redocly/openapi-core': 2.30.1
'@redocly/respect-core': 2.30.1
'@redocly/openapi-core': 2.30.2
'@redocly/respect-core': 2.30.2
ajv: '@redocly/ajv@8.18.0'
ajv-formats: 3.0.1(@redocly/ajv@8.18.0)
colorette: 1.4.0
@@ -1192,7 +1192,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@redocly/openapi-core@2.30.1':
'@redocly/openapi-core@2.30.2':
dependencies:
'@redocly/ajv': 8.18.0
'@redocly/config': 0.48.1
@@ -1205,12 +1205,12 @@ snapshots:
pluralize: 8.0.0
yaml-ast-parser: 0.0.43
'@redocly/respect-core@2.30.1':
'@redocly/respect-core@2.30.2':
dependencies:
'@faker-js/faker': 7.6.0
'@noble/hashes': 1.8.0
'@redocly/ajv': 8.18.0
'@redocly/openapi-core': 2.30.1
'@redocly/openapi-core': 2.30.2
ajv: '@redocly/ajv@8.18.0'
better-ajv-errors: 1.2.0(@redocly/ajv@8.18.0)
colorette: 2.0.20