From 0cbf4b7287ebf0f86ae45857f09140768bf7eb6a Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Wed, 29 Apr 2026 14:58:26 +0200
Subject: [PATCH] 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
---
pkg/jmap/api.go | 54 +++++
pkg/jmap/api_addressbook.go | 10 +-
pkg/jmap/api_blob.go | 8 +-
pkg/jmap/api_bootstrap.go | 4 +-
pkg/jmap/api_calendar.go | 14 +-
pkg/jmap/api_changes.go | 4 +-
pkg/jmap/api_contact.go | 12 +-
pkg/jmap/api_email.go | 97 ++++----
pkg/jmap/api_event.go | 12 +-
pkg/jmap/api_identity.go | 16 +-
pkg/jmap/api_mailbox.go | 30 +--
pkg/jmap/api_objects.go | 4 +-
pkg/jmap/api_principal.go | 4 +-
pkg/jmap/api_quota.go | 6 +-
pkg/jmap/api_vacation.go | 6 +-
pkg/jmap/error.go | 12 +-
...ion_test.go => export_integration_test.go} | 92 ++++----
pkg/jmap/integration_addressbook_test.go | 134 ++++++-----
pkg/jmap/integration_calendar_test.go | 96 ++++----
pkg/jmap/integration_email_test.go | 179 ++++++++-------
pkg/jmap/integration_ws_test.go | 90 ++++----
pkg/jmap/session.go | 6 +
pkg/jmap/templates.go | 72 +++---
pkg/jmap/tools.go | 25 +--
services/groupware/package.json | 2 +-
.../groupware/pkg/groupware/api_account.go | 12 +-
services/groupware/pkg/groupware/api_blob.go | 4 +-
.../groupware/pkg/groupware/api_changes.go | 8 +-
.../groupware/pkg/groupware/api_emails.go | 212 +++++++++---------
.../groupware/pkg/groupware/api_events.go | 20 +-
services/groupware/pkg/groupware/api_index.go | 8 +-
.../groupware/pkg/groupware/api_mailbox.go | 55 +++--
.../groupware/pkg/groupware/api_objects.go | 8 +-
services/groupware/pkg/groupware/api_quota.go | 14 +-
.../groupware/pkg/groupware/api_tasklists.go | 11 +-
.../groupware/pkg/groupware/api_vacation.go | 4 +-
services/groupware/pkg/groupware/error.go | 16 +-
.../groupware/pkg/groupware/mock_tasks.go | 20 ++
services/groupware/pkg/groupware/request.go | 7 +-
services/groupware/pkg/groupware/response.go | 17 +-
services/groupware/pkg/groupware/templates.go | 113 +++++-----
services/groupware/pnpm-lock.yaml | 28 +--
42 files changed, 827 insertions(+), 719 deletions(-)
rename pkg/jmap/{integration_test.go => export_integration_test.go} (94%)
diff --git a/pkg/jmap/api.go b/pkg/jmap/api.go
index dad83a99c1..1ff108c8fa 100644
--- a/pkg/jmap/api.go
+++ b/pkg/jmap/api.go
@@ -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]{}
+}
diff --git a/pkg/jmap/api_addressbook.go b/pkg/jmap/api_addressbook.go
index 44d79ebc36..2c046a546a 100644
--- a/pkg/jmap/api_addressbook.go
+++ b/pkg/jmap/api_addressbook.go
@@ -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}
diff --git a/pkg/jmap/api_blob.go b/pkg/jmap/api_blob.go
index b64b95f0e0..e118961f6d 100644
--- a/pkg/jmap/api_blob.go
+++ b/pkg/jmap/api_blob.go
@@ -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) {
diff --git a/pkg/jmap/api_bootstrap.go b/pkg/jmap/api_bootstrap.go
index 27767ca309..6074449703 100644
--- a/pkg/jmap/api_bootstrap.go
+++ b/pkg/jmap/api_bootstrap.go
@@ -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{}
diff --git a/pkg/jmap/api_calendar.go b/pkg/jmap/api_calendar.go
index 0aa62ef0a0..843e52e294 100644
--- a/pkg/jmap/api_calendar.go
+++ b/pkg/jmap/api_calendar.go
@@ -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}
diff --git a/pkg/jmap/api_changes.go b/pkg/jmap/api_changes.go
index ab8f32b53c..f51acde6e1 100644
--- a/pkg/jmap/api_changes.go
+++ b/pkg/jmap/api_changes.go
@@ -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) {
diff --git a/pkg/jmap/api_contact.go b/pkg/jmap/api_contact.go
index cfbb165b9f..f9e97fc2a9 100644
--- a/pkg/jmap/api_contact.go
+++ b/pkg/jmap/api_contact.go
@@ -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}
diff --git a/pkg/jmap/api_email.go b/pkg/jmap/api_email.go
index a8552df8f5..fd06a078cf 100644
--- a/pkg/jmap/api_email.go
+++ b/pkg/jmap/api_email.go
@@ -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)}
diff --git a/pkg/jmap/api_event.go b/pkg/jmap/api_event.go
index 6bffeb9c1b..7127d3281d 100644
--- a/pkg/jmap/api_event.go
+++ b/pkg/jmap/api_event.go
@@ -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}
diff --git a/pkg/jmap/api_identity.go b/pkg/jmap/api_identity.go
index 7237a8f796..f0e7e6c13e 100644
--- a/pkg/jmap/api_identity.go
+++ b/pkg/jmap/api_identity.go
@@ -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)}
diff --git a/pkg/jmap/api_mailbox.go b/pkg/jmap/api_mailbox.go
index 28e151d071..6c214d9a51 100644
--- a/pkg/jmap/api_mailbox.go
+++ b/pkg/jmap/api_mailbox.go
@@ -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}
diff --git a/pkg/jmap/api_objects.go b/pkg/jmap/api_objects.go
index 1d8c7a0d1a..bb8dde7c7a 100644
--- a/pkg/jmap/api_objects.go
+++ b/pkg/jmap/api_objects.go
@@ -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) {
diff --git a/pkg/jmap/api_principal.go b/pkg/jmap/api_principal.go
index e0641e9adc..9010c1a707 100644
--- a/pkg/jmap/api_principal.go
+++ b/pkg/jmap/api_principal.go
@@ -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 {
diff --git a/pkg/jmap/api_quota.go b/pkg/jmap/api_quota.go
index 4b56b87f23..7a925f1926 100644
--- a/pkg/jmap/api_quota.go
+++ b/pkg/jmap/api_quota.go
@@ -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)}
diff --git a/pkg/jmap/api_vacation.go b/pkg/jmap/api_vacation.go
index 40ae7b2d59..e3113e07c9 100644
--- a/pkg/jmap/api_vacation.go
+++ b/pkg/jmap/api_vacation.go
@@ -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
diff --git a/pkg/jmap/error.go b/pkg/jmap/error.go
index 6ea5f84a46..82d55c6d01 100644
--- a/pkg/jmap/error.go
+++ b/pkg/jmap/error.go
@@ -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}
}
diff --git a/pkg/jmap/integration_test.go b/pkg/jmap/export_integration_test.go
similarity index 94%
rename from pkg/jmap/integration_test.go
rename to pkg/jmap/export_integration_test.go
index a9c46c4fb1..40fbcd7b66 100644
--- a/pkg/jmap/integration_test.go
+++ b/pkg/jmap/export_integration_test.go
@@ -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)
diff --git a/pkg/jmap/integration_addressbook_test.go b/pkg/jmap/integration_addressbook_test.go
index 18bd0c296f..7de4bb0dc3 100644
--- a/pkg/jmap/integration_addressbook_test.go
+++ b/pkg/jmap/integration_addressbook_test.go
@@ -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
}
diff --git a/pkg/jmap/integration_calendar_test.go b/pkg/jmap/integration_calendar_test.go
index 1f4e0e0434..1411524c3a 100644
--- a/pkg/jmap/integration_calendar_test.go
+++ b/pkg/jmap/integration_calendar_test.go
@@ -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))
}
diff --git a/pkg/jmap/integration_email_test.go b/pkg/jmap/integration_email_test.go
index 59c10949fe..227fb8b361 100644
--- a/pkg/jmap/integration_email_test.go
+++ b/pkg/jmap/integration_email_test.go
@@ -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{}
diff --git a/pkg/jmap/integration_ws_test.go b/pkg/jmap/integration_ws_test.go
index 1f6ebd0a13..499141038b 100644
--- a/pkg/jmap/integration_ws_test.go
+++ b/pkg/jmap/integration_ws_test.go
@@ -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()
diff --git a/pkg/jmap/session.go b/pkg/jmap/session.go
index 6b2b4487f7..143841cbf1 100644
--- a/pkg/jmap/session.go
+++ b/pkg/jmap/session.go
@@ -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)
diff --git a/pkg/jmap/templates.go b/pkg/jmap/templates.go
index 61f826bdad..ffe556c611 100644
--- a/pkg/jmap/templates.go
+++ b/pkg/jmap/templates.go
@@ -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) {
diff --git a/pkg/jmap/tools.go b/pkg/jmap/tools.go
index 4ac99c3191..38315ebd6b 100644
--- a/pkg/jmap/tools.go
+++ b/pkg/jmap/tools.go
@@ -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 {
diff --git a/services/groupware/package.json b/services/groupware/package.json
index 4471364077..d1cec61936 100644
--- a/services/groupware/package.json
+++ b/services/groupware/package.json
@@ -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",
diff --git a/services/groupware/pkg/groupware/api_account.go b/services/groupware/pkg/groupware/api_account.go
index 84873a949e..92b6b7ef1d 100644
--- a/services/groupware/pkg/groupware/api_account.go
+++ b/services/groupware/pkg/groupware/api_account.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/api_blob.go b/services/groupware/pkg/groupware/api_blob.go
index 0815305a2e..d613850c4c 100644
--- a/services/groupware/pkg/groupware/api_blob.go
+++ b/services/groupware/pkg/groupware/api_blob.go
@@ -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)
diff --git a/services/groupware/pkg/groupware/api_changes.go b/services/groupware/pkg/groupware/api_changes.go
index 40a386ef76..fa7ec29068 100644
--- a/services/groupware/pkg/groupware/api_changes.go
+++ b/services/groupware/pkg/groupware/api_changes.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/api_emails.go b/services/groupware/pkg/groupware/api_emails.go
index b32f9e4501..5b2f3c22bc 100644
--- a/services/groupware/pkg/groupware/api_emails.go
+++ b/services/groupware/pkg/groupware/api_emails.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/api_events.go b/services/groupware/pkg/groupware/api_events.go
index d3ac836622..4cf146a5b0 100644
--- a/services/groupware/pkg/groupware/api_events.go
+++ b/services/groupware/pkg/groupware/api_events.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/api_index.go b/services/groupware/pkg/groupware/api_index.go
index 13a37998eb..db6efe5c17 100644
--- a/services/groupware/pkg/groupware/api_index.go
+++ b/services/groupware/pkg/groupware/api_index.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/api_mailbox.go b/services/groupware/pkg/groupware/api_mailbox.go
index 48947bc09c..f7b26cb0ac 100644
--- a/services/groupware/pkg/groupware/api_mailbox.go
+++ b/services/groupware/pkg/groupware/api_mailbox.go
@@ -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)
}
diff --git a/services/groupware/pkg/groupware/api_objects.go b/services/groupware/pkg/groupware/api_objects.go
index 87a7692f74..be75e2b177 100644
--- a/services/groupware/pkg/groupware/api_objects.go
+++ b/services/groupware/pkg/groupware/api_objects.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/api_quota.go b/services/groupware/pkg/groupware/api_quota.go
index 486c09d147..4d07e53ec2 100644
--- a/services/groupware/pkg/groupware/api_quota.go
+++ b/services/groupware/pkg/groupware/api_quota.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/api_tasklists.go b/services/groupware/pkg/groupware/api_tasklists.go
index ee4ad494d4..fee45a857e 100644
--- a/services/groupware/pkg/groupware/api_tasklists.go
+++ b/services/groupware/pkg/groupware/api_tasklists.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/api_vacation.go b/services/groupware/pkg/groupware/api_vacation.go
index 6226610089..19c81fe519 100644
--- a/services/groupware/pkg/groupware/api_vacation.go
+++ b/services/groupware/pkg/groupware/api_vacation.go
@@ -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)
})
}
diff --git a/services/groupware/pkg/groupware/error.go b/services/groupware/pkg/groupware/error.go
index c4fe9043ca..a6749c7d08 100644
--- a/services/groupware/pkg/groupware/error.go
+++ b/services/groupware/pkg/groupware/error.go
@@ -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())
}
diff --git a/services/groupware/pkg/groupware/mock_tasks.go b/services/groupware/pkg/groupware/mock_tasks.go
index 59d875a47b..5b91ce0028 100644
--- a/services/groupware/pkg/groupware/mock_tasks.go
+++ b/services/groupware/pkg/groupware/mock_tasks.go
@@ -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 {
diff --git a/services/groupware/pkg/groupware/request.go b/services/groupware/pkg/groupware/request.go
index 258fa08238..06008abc4b 100644
--- a/services/groupware/pkg/groupware/request.go
+++ b/services/groupware/pkg/groupware/request.go
@@ -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
}
diff --git a/services/groupware/pkg/groupware/response.go b/services/groupware/pkg/groupware/response.go
index 1a7916a0c5..22a7451896 100644
--- a/services/groupware/pkg/groupware/response.go
+++ b/services/groupware/pkg/groupware/response.go
@@ -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 {
diff --git a/services/groupware/pkg/groupware/templates.go b/services/groupware/pkg/groupware/templates.go
index 9f7bf435bb..6fd308ca60 100644
--- a/services/groupware/pkg/groupware/templates.go
+++ b/services/groupware/pkg/groupware/templates.go
@@ -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)
})
}
diff --git a/services/groupware/pnpm-lock.yaml b/services/groupware/pnpm-lock.yaml
index 533c3b8ef2..9d18b5c1be 100644
--- a/services/groupware/pnpm-lock.yaml
+++ b/services/groupware/pnpm-lock.yaml
@@ -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