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