From 8f5fbb00a87e9b517a7a9a15cfd0db8cf08f54fd Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Mon, 13 Apr 2026 09:46:24 +0200
Subject: [PATCH] groupware: refactoring: pass object type instead of
namespaces
* make the JMAP internal API a bit more future-proof by passing
ObjectType objects instead of the JMAP namespaces
* remove the new attempt to contain operations even further using the
Factory objects
* move CalendarEvent operations to its own file, like everything else
* fix email tests
* ignore WS error when closing an already closed connection
---
pkg/jmap/api_addressbook.go | 10 +-
pkg/jmap/api_calendar.go | 112 +------------
pkg/jmap/api_contact.go | 10 +-
pkg/jmap/api_email.go | 114 ++++++-------
pkg/jmap/api_event.go | 105 ++++++++++++
pkg/jmap/api_identity.go | 14 +-
pkg/jmap/api_mailbox.go | 118 +++++---------
pkg/jmap/api_principal.go | 4 +-
pkg/jmap/api_quota.go | 6 +-
pkg/jmap/api_vacation.go | 2 +-
pkg/jmap/export_test.go | 59 ++++---
pkg/jmap/http.go | 3 +-
...t_test.go => integration_calendar_test.go} | 2 +-
pkg/jmap/integration_contact_test.go | 3 +-
pkg/jmap/integration_email_test.go | 26 ++-
pkg/jmap/integration_ws_test.go | 11 +-
pkg/jmap/model.go | 72 +++++++++
pkg/jmap/model_examples.go | 15 +-
pkg/jmap/templates.go | 152 +++++-------------
.../groupware/pkg/groupware/api_emails.go | 21 +--
20 files changed, 423 insertions(+), 436 deletions(-)
create mode 100644 pkg/jmap/api_event.go
rename pkg/jmap/{integration_event_test.go => integration_calendar_test.go} (99%)
diff --git a/pkg/jmap/api_addressbook.go b/pkg/jmap/api_addressbook.go
index 299b5ab944..9c1ee8d2ef 100644
--- a/pkg/jmap/api_addressbook.go
+++ b/pkg/jmap/api_addressbook.go
@@ -3,7 +3,7 @@ package jmap
var NS_ADDRESSBOOKS = ns(JmapContacts)
func (j *Client) GetAddressbooks(accountId string, ids []string, ctx Context) (AddressBookGetResponse, SessionState, State, Language, Error) {
- return get(j, "GetAddressbooks", NS_ADDRESSBOOKS,
+ return get(j, "GetAddressbooks", MailboxType,
func(accountId string, ids []string) AddressBookGetCommand {
return AddressBookGetCommand{AccountId: accountId, Ids: ids}
},
@@ -19,7 +19,7 @@ type AddressBookChanges = ChangesTemplate[AddressBook]
// 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) {
- return changesA(j, "GetAddressbookChanges", NS_ADDRESSBOOKS,
+ return changesA(j, "GetAddressbookChanges", MailboxType,
func() AddressBookChangesCommand {
return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
@@ -50,7 +50,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) {
- return create(j, "CreateAddressBook", NS_ADDRESSBOOKS,
+ return create(j, "CreateAddressBook", MailboxType,
func(accountId string, create map[string]AddressBookChange) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Create: create}
},
@@ -69,7 +69,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) {
- return destroy(j, "DeleteAddressBook", NS_ADDRESSBOOKS,
+ return destroy(j, "DeleteAddressBook", MailboxType,
func(accountId string, destroy []string) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Destroy: destroy}
},
@@ -80,7 +80,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) {
- return update(j, "UpdateAddressBook", NS_ADDRESSBOOKS,
+ return update(j, "UpdateAddressBook", MailboxType,
func(update map[string]PatchObject) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Update: update}
},
diff --git a/pkg/jmap/api_calendar.go b/pkg/jmap/api_calendar.go
index 6f13cf7683..536a34e7da 100644
--- a/pkg/jmap/api_calendar.go
+++ b/pkg/jmap/api_calendar.go
@@ -24,7 +24,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) {
- return get(j, "GetCalendars", NS_CALENDARS,
+ return get(j, "GetCalendars", CalendarType,
func(accountId string, ids []string) CalendarGetCommand {
return CalendarGetCommand{AccountId: accountId, Ids: ids}
},
@@ -40,7 +40,7 @@ type CalendarChanges = ChangesTemplate[Calendar]
// 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) {
- return changes(j, "GetCalendarChanges", NS_CALENDARS,
+ return changes(j, "GetCalendarChanges", CalendarType,
func() CalendarChangesCommand {
return CalendarChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
@@ -70,110 +70,8 @@ func (j *Client) GetCalendarChanges(accountId string, sinceState State, maxChang
)
}
-type CalendarEventSearchResults SearchResultsTemplate[CalendarEvent]
-
-var _ SearchResults[CalendarEvent] = CalendarEventSearchResults{}
-
-func (r CalendarEventSearchResults) GetResults() []CalendarEvent { return r.Results }
-func (r CalendarEventSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges }
-func (r CalendarEventSearchResults) GetPosition() uint { return r.Position }
-func (r CalendarEventSearchResults) GetLimit() uint { return r.Limit }
-func (r CalendarEventSearchResults) GetTotal() *uint { return r.Total }
-
-func (j *Client) QueryCalendarEvents(accountIds []string, //NOSONAR
- filter CalendarEventFilterElement, sortBy []CalendarEventComparator,
- position int, limit uint, calculateTotal bool,
- ctx Context) (map[string]CalendarEventSearchResults, SessionState, State, Language, Error) {
- return queryN(j, "QueryCalendarEvents", NS_CALENDARS,
- []CalendarEventComparator{{Property: CalendarEventPropertyStart, IsAscending: false}},
- func(accountId string, filter CalendarEventFilterElement, sortBy []CalendarEventComparator, position int, limit uint) CalendarEventQueryCommand {
- return CalendarEventQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: uintPtr(limit), CalculateTotal: calculateTotal}
- },
- func(accountId string, cmd Command, path string, rof string) CalendarEventGetRefCommand {
- return CalendarEventGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}}
- },
- func(query CalendarEventQueryResponse, get CalendarEventGetResponse) CalendarEventSearchResults {
- return CalendarEventSearchResults{
- Results: get.List,
- CanCalculateChanges: query.CanCalculateChanges,
- Position: query.Position,
- Total: uintPtrIf(query.Total, calculateTotal),
- Limit: query.Limit,
- }
- },
- accountIds,
- filter, sortBy, limit, position, ctx,
- )
-}
-
-type CalendarEventChanges = ChangesTemplate[CalendarEvent]
-
-// 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) {
- return changes(j, "GetCalendarEventChanges", NS_CALENDARS,
- func() CalendarEventChangesCommand {
- return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
- },
- CalendarEventChangesResponse{},
- func(path string, rof string) CalendarEventGetRefCommand {
- return CalendarEventGetRefCommand{
- AccountId: accountId,
- IdsRef: &ResultReference{
- Name: CommandCalendarEventChanges,
- Path: path,
- ResultOf: rof,
- },
- }
- },
- func(resp CalendarEventGetResponse) []CalendarEvent { return resp.List },
- func(oldState, newState State, hasMoreChanges bool, created, updated []CalendarEvent, destroyed []string) CalendarEventChanges {
- return CalendarEventChanges{
- OldState: oldState,
- NewState: newState,
- HasMoreChanges: hasMoreChanges,
- Created: created,
- Updated: updated,
- Destroyed: destroyed,
- }
- },
- ctx,
- )
-}
-
-func (j *Client) CreateCalendarEvent(accountId string, event CalendarEvent, ctx Context) (*CalendarEvent, SessionState, State, Language, Error) {
- return create(j, "CreateCalendarEvent", NS_CALENDARS,
- func(accountId string, create map[string]CalendarEvent) CalendarEventSetCommand {
- return CalendarEventSetCommand{AccountId: accountId, Create: create}
- },
- func(accountId string, ref string) CalendarEventGetCommand {
- return CalendarEventGetCommand{AccountId: accountId, Ids: []string{ref}}
- },
- func(resp CalendarEventSetResponse) map[string]*CalendarEvent {
- return resp.Created
- },
- func(resp CalendarEventGetResponse) []CalendarEvent {
- return resp.List
- },
- accountId, event,
- ctx,
- )
-}
-
-func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
- return destroy(j, "DeleteCalendarEvent", NS_CALENDARS,
- func(accountId string, destroy []string) CalendarEventSetCommand {
- return CalendarEventSetCommand{AccountId: accountId, Destroy: destroy}
- },
- CalendarEventSetResponse{},
- accountId, destroyIds,
- ctx,
- )
-}
-
func (j *Client) CreateCalendar(accountId string, calendar CalendarChange, ctx Context) (*Calendar, SessionState, State, Language, Error) {
- return create(j, "CreateCalendar", NS_CALENDARS,
+ return create(j, "CreateCalendar", CalendarEventType,
func(accountId string, create map[string]CalendarChange) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Create: create}
},
@@ -192,7 +90,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) {
- return destroy(j, "DeleteCalendar", NS_CALENDARS,
+ return destroy(j, "DeleteCalendar", CalendarEventType,
func(accountId string, destroy []string) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Destroy: destroy}
},
@@ -203,7 +101,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) {
- return update(j, "UpdateCalendar", NS_CALENDARS,
+ return update(j, "UpdateCalendar", CalendarEventType,
func(update map[string]PatchObject) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Update: update}
},
diff --git a/pkg/jmap/api_contact.go b/pkg/jmap/api_contact.go
index 1ae7e2f9e8..9d036e8da1 100644
--- a/pkg/jmap/api_contact.go
+++ b/pkg/jmap/api_contact.go
@@ -3,7 +3,7 @@ package jmap
var NS_CONTACTS = ns(JmapContacts)
func (j *Client) GetContactCards(accountId string, contactIds []string, ctx Context) (ContactCardGetResponse, SessionState, State, Language, Error) {
- return get(j, "GetContactCards", NS_CONTACTS,
+ return get(j, "GetContactCards", ContactCardType,
func(accountId string, ids []string) ContactCardGetCommand {
return ContactCardGetCommand{AccountId: accountId, Ids: contactIds}
},
@@ -19,7 +19,7 @@ type ContactCardChanges = ChangesTemplate[ContactCard]
// 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) {
- return changes(j, "GetContactCardChanges", NS_CONTACTS,
+ return changes(j, "GetContactCardChanges", ContactCardType,
func() ContactCardChangesCommand {
return ContactCardChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
@@ -63,7 +63,7 @@ func (j *Client) QueryContactCards(accountIds []string,
filter ContactCardFilterElement, sortBy []ContactCardComparator,
position int, limit uint, calculateTotal bool,
ctx Context) (map[string]ContactCardSearchResults, SessionState, State, Language, Error) {
- return queryN(j, "QueryContactCards", NS_CONTACTS,
+ return queryN(j, "QueryContactCards", ContactCardType,
[]ContactCardComparator{{Property: ContactCardPropertyUpdated, IsAscending: false}},
func(accountId string, filter ContactCardFilterElement, sortBy []ContactCardComparator, position int, limit uint) ContactCardQueryCommand {
return ContactCardQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: uintPtr(limit), CalculateTotal: calculateTotal}
@@ -86,7 +86,7 @@ func (j *Client) QueryContactCards(accountIds []string,
}
func (j *Client) CreateContactCard(accountId string, contact ContactCard, ctx Context) (*ContactCard, SessionState, State, Language, Error) {
- return create(j, "CreateContactCard", NS_CONTACTS,
+ return create(j, "CreateContactCard", ContactCardType,
func(accountId string, create map[string]ContactCard) ContactCardSetCommand {
return ContactCardSetCommand{AccountId: accountId, Create: create}
},
@@ -105,7 +105,7 @@ func (j *Client) CreateContactCard(accountId string, contact ContactCard, ctx Co
}
func (j *Client) DeleteContactCard(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
- return destroy(j, "DeleteContactCard", NS_CONTACTS,
+ return destroy(j, "DeleteContactCard", ContactCardType,
func(accountId string, destroy []string) ContactCardSetCommand {
return ContactCardSetCommand{AccountId: accountId, Destroy: destroy}
},
diff --git a/pkg/jmap/api_email.go b/pkg/jmap/api_email.go
index d46da23d8f..668823a4f0 100644
--- a/pkg/jmap/api_email.go
+++ b/pkg/jmap/api_email.go
@@ -13,13 +13,6 @@ import (
var NS_MAIL = ns(JmapMail)
var NS_MAIL_SUBMISSION = ns(JmapMail, JmapSubmission)
-type Emails struct {
- Emails []Email `json:"emails,omitempty"`
- Total uint `json:"total,omitzero"`
- Limit uint `json:"limit,omitzero"`
- Offset uint `json:"offset,omitzero"`
-}
-
type getEmailsResult struct {
emails []Email
notFound []string
@@ -32,23 +25,25 @@ func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR
logger := j.logger("GetEmails", ctx)
ctx = ctx.WithLogger(logger)
- get := EmailGetCommand{AccountId: accountId, Ids: ids, FetchAllBodyValues: fetchBodies}
+ getEmails := EmailGetCommand{AccountId: accountId, Ids: ids, FetchAllBodyValues: fetchBodies}
if maxBodyValueBytes > 0 {
- get.MaxBodyValueBytes = maxBodyValueBytes
+ getEmails.MaxBodyValueBytes = maxBodyValueBytes
}
- invokeGet := invocation(get, "1")
+ invokeGet := invocation(getEmails, "1")
methodCalls := []Invocation{invokeGet}
+ var markEmails EmailSetCommand
if markAsSeen {
updates := make(map[string]EmailUpdate, len(ids))
for _, id := range ids {
updates[id] = EmailUpdate{EmailPropertyKeywords + "/" + JmapKeywordSeen: true}
}
- mark := EmailSetCommand{AccountId: accountId, Update: updates}
- methodCalls = []Invocation{invocation(mark, "0"), invokeGet}
+ markEmails = EmailSetCommand{AccountId: accountId, Update: updates}
+ methodCalls = []Invocation{invocation(markEmails, "0"), invokeGet}
}
+ var getThreads ThreadGetRefCommand
if withThreads {
- threads := ThreadGetRefCommand{
+ getThreads = ThreadGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
ResultOf: "1",
@@ -56,7 +51,7 @@ func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR
Path: "/list/*/" + EmailPropertyThreadId, //NOSONAR
},
}
- methodCalls = append(methodCalls, invocation(threads, "2"))
+ methodCalls = append(methodCalls, invocation(getThreads, "2"))
}
cmd, err := j.request(ctx, NS_MAIL, methodCalls...)
@@ -66,7 +61,7 @@ func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR
result, sessionState, state, language, gwerr := command(j, ctx, cmd, func(body *Response) (getEmailsResult, State, Error) {
if markAsSeen {
var markResponse EmailSetResponse
- err = retrieveResponseMatchParameters(ctx, body, CommandEmailSet, "0", &markResponse)
+ err = retrieveSet(ctx, body, markEmails, "0", &markResponse)
if err != nil {
return getEmailsResult{}, "", err
}
@@ -76,13 +71,13 @@ func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR
}
}
var response EmailGetResponse
- err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, "1", &response)
+ err = retrieveGet(ctx, body, getEmails, "1", &response)
if err != nil {
return getEmailsResult{}, "", err
}
if withThreads {
var threads ThreadGetResponse
- err = retrieveResponseMatchParameters(ctx, body, CommandThreadGet, "2", &threads)
+ err = retrieveGet(ctx, body, getThreads, "2", &threads)
if err != nil {
return getEmailsResult{}, "", err
}
@@ -116,10 +111,12 @@ func (j *Client) GetEmailBlobId(accountId string, id string, ctx Context) (strin
})
}
+type EmailSearchResults = SearchResultsTemplate[Email]
+
// Retrieve all the Emails in a given Mailbox by its id.
func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOSONAR
offset int, limit uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool,
- ctx Context) (Emails, SessionState, State, Language, Error) {
+ ctx Context) (EmailSearchResults, SessionState, State, Language, Error) {
logger := j.loggerParams("GetAllEmailsInMailbox", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Int(logOffset, offset).Uint(logLimit, limit)
})
@@ -168,36 +165,37 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOS
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
- return Emails{}, "", "", "", err
+ return EmailSearchResults{}, "", "", "", err
}
- return command(j, ctx, cmd, func(body *Response) (Emails, State, Error) {
+ return command(j, ctx, cmd, func(body *Response) (EmailSearchResults, State, Error) {
var queryResponse EmailQueryResponse
err = retrieveQuery(ctx, body, query, "0", &queryResponse)
if err != nil {
- return Emails{}, "", err
+ return EmailSearchResults{}, "", err
}
var getResponse EmailGetResponse
err = retrieveGet(ctx, body, get, "1", &getResponse)
if err != nil {
logger.Error().Err(err).Send()
- return Emails{}, "", err
+ return EmailSearchResults{}, "", err
}
if withThreads {
var thread ThreadGetResponse
err = retrieveGet(ctx, body, threads, "2", &thread)
if err != nil {
- return Emails{}, "", err
+ return EmailSearchResults{}, "", err
}
setThreadSize(&thread, getResponse.List)
}
- return Emails{
- Emails: getResponse.List,
- Total: queryResponse.Total,
- Limit: queryResponse.Limit,
- Offset: queryResponse.Position,
+ return EmailSearchResults{
+ Results: getResponse.List,
+ CanCalculateChanges: queryResponse.CanCalculateChanges,
+ Position: queryResponse.Position,
+ Limit: queryResponse.Limit,
+ Total: uintPtr(queryResponse.Total),
}, queryResponse.QueryState, nil
})
}
@@ -293,17 +291,11 @@ type SearchSnippetWithMeta struct {
SearchSnippet
}
-type EmailSnippetQueryResult struct {
- Snippets []SearchSnippetWithMeta `json:"snippets,omitempty"`
- Total uint `json:"total"`
- Limit uint `json:"limit,omitzero"`
- Position uint `json:"position,omitzero"`
- QueryState State `json:"queryState"`
-}
+type EmailSnippetSearchResults SearchResultsTemplate[SearchSnippetWithMeta]
func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
filter EmailFilterElement, offset int, limit uint,
- ctx Context) (map[string]EmailSnippetQueryResult, SessionState, State, Language, Error) {
+ ctx Context) (map[string]EmailSnippetSearchResults, SessionState, State, Language, Error) {
logger := j.loggerParams("QueryEmailSnippets", ctx, func(z zerolog.Context) zerolog.Context {
return z.Uint(logLimit, limit).Int(logOffset, offset)
})
@@ -358,8 +350,9 @@ func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
return nil, "", "", "", err
}
- return command(j, ctx, cmd, func(body *Response) (map[string]EmailSnippetQueryResult, State, Error) {
- results := make(map[string]EmailSnippetQueryResult, len(uniqueAccountIds))
+ return command(j, ctx, cmd, func(body *Response) (map[string]EmailSnippetSearchResults, State, Error) {
+ results := make(map[string]EmailSnippetSearchResults, len(uniqueAccountIds))
+ states := make(map[string]State, len(uniqueAccountIds))
for _, accountId := range uniqueAccountIds {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
@@ -400,15 +393,17 @@ func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
i++
}
- results[accountId] = EmailSnippetQueryResult{
- Snippets: snippets,
- Total: queryResponse.Total,
- Limit: queryResponse.Limit,
- Position: queryResponse.Position,
- QueryState: queryResponse.QueryState,
+ states[accountId] = queryResponse.QueryState
+
+ results[accountId] = EmailSnippetSearchResults{
+ Results: snippets,
+ CanCalculateChanges: queryResponse.CanCalculateChanges,
+ Total: uintPtr(queryResponse.Total),
+ Limit: queryResponse.Limit,
+ Position: queryResponse.Position,
}
}
- return results, squashStateFunc(results, func(r EmailSnippetQueryResult) State { return r.QueryState }), nil
+ return results, squashState(states), nil
})
}
@@ -775,24 +770,15 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate,
})
}
-func (j *Client) DeleteEmails(accountId string, destroy []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
- set := EmailSetCommand{
- AccountId: accountId,
- Destroy: destroy,
- }
- cmd, err := j.request(ctx, NS_MAIL, invocation(set, "0"))
- if err != nil {
- return nil, "", "", "", err
- }
-
- return command(j, ctx, cmd, func(body *Response) (map[string]SetError, State, Error) {
- var setResponse EmailSetResponse
- err = retrieveSet(ctx, body, set, "0", &setResponse)
- if err != nil {
- return nil, "", err
- }
- return setResponse.NotDestroyed, setResponse.NewState, nil
- })
+func (j *Client) DeleteEmails(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
+ return destroy(j, "DeleteEmails", EmailType,
+ func(accountId string, destroy []string) EmailSetCommand {
+ return EmailSetCommand{AccountId: accountId, Destroy: destroy}
+ },
+ EmailSetResponse{},
+ accountId, destroyIds,
+ ctx,
+ )
}
type SubmittedEmail struct {
@@ -1109,7 +1095,7 @@ type EmailSubmissionChanges = ChangesTemplate[EmailSubmission]
// @api:tags email,changes
func (j *Client) GetEmailSubmissionChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (EmailSubmissionChanges, SessionState, State, Language, Error) {
- return changes(j, "GetEmailSubmissionChanges", NS_MAIL_SUBMISSION,
+ 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
new file mode 100644
index 0000000000..470e2958ee
--- /dev/null
+++ b/pkg/jmap/api_event.go
@@ -0,0 +1,105 @@
+package jmap
+
+var NS_CALENDAR_EVENTS = ns(JmapCalendars)
+
+type CalendarEventSearchResults SearchResultsTemplate[CalendarEvent]
+
+var _ SearchResults[CalendarEvent] = CalendarEventSearchResults{}
+
+func (r CalendarEventSearchResults) GetResults() []CalendarEvent { return r.Results }
+func (r CalendarEventSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges }
+func (r CalendarEventSearchResults) GetPosition() uint { return r.Position }
+func (r CalendarEventSearchResults) GetLimit() uint { return r.Limit }
+func (r CalendarEventSearchResults) GetTotal() *uint { return r.Total }
+
+func (j *Client) QueryCalendarEvents(accountIds []string, //NOSONAR
+ filter CalendarEventFilterElement, sortBy []CalendarEventComparator,
+ position int, limit uint, calculateTotal bool,
+ ctx Context) (map[string]CalendarEventSearchResults, SessionState, State, Language, Error) {
+ return queryN(j, "QueryCalendarEvents", CalendarEventType,
+ []CalendarEventComparator{{Property: CalendarEventPropertyStart, IsAscending: false}},
+ func(accountId string, filter CalendarEventFilterElement, sortBy []CalendarEventComparator, position int, limit uint) CalendarEventQueryCommand {
+ return CalendarEventQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: uintPtr(limit), CalculateTotal: calculateTotal}
+ },
+ func(accountId string, cmd Command, path string, rof string) CalendarEventGetRefCommand {
+ return CalendarEventGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}}
+ },
+ func(query CalendarEventQueryResponse, get CalendarEventGetResponse) CalendarEventSearchResults {
+ return CalendarEventSearchResults{
+ Results: get.List,
+ CanCalculateChanges: query.CanCalculateChanges,
+ Position: query.Position,
+ Total: uintPtrIf(query.Total, calculateTotal),
+ Limit: query.Limit,
+ }
+ },
+ accountIds,
+ filter, sortBy, limit, position, ctx,
+ )
+}
+
+type CalendarEventChanges = ChangesTemplate[CalendarEvent]
+
+// 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) {
+ return changes(j, "GetCalendarEventChanges", CalendarEventType,
+ func() CalendarEventChangesCommand {
+ return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
+ },
+ CalendarEventChangesResponse{},
+ func(path string, rof string) CalendarEventGetRefCommand {
+ return CalendarEventGetRefCommand{
+ AccountId: accountId,
+ IdsRef: &ResultReference{
+ Name: CommandCalendarEventChanges,
+ Path: path,
+ ResultOf: rof,
+ },
+ }
+ },
+ func(resp CalendarEventGetResponse) []CalendarEvent { return resp.List },
+ func(oldState, newState State, hasMoreChanges bool, created, updated []CalendarEvent, destroyed []string) CalendarEventChanges {
+ return CalendarEventChanges{
+ OldState: oldState,
+ NewState: newState,
+ HasMoreChanges: hasMoreChanges,
+ Created: created,
+ Updated: updated,
+ Destroyed: destroyed,
+ }
+ },
+ ctx,
+ )
+}
+
+func (j *Client) CreateCalendarEvent(accountId string, event CalendarEvent, ctx Context) (*CalendarEvent, SessionState, State, Language, Error) {
+ return create(j, "CreateCalendarEvent", CalendarEventType,
+ func(accountId string, create map[string]CalendarEvent) CalendarEventSetCommand {
+ return CalendarEventSetCommand{AccountId: accountId, Create: create}
+ },
+ func(accountId string, ref string) CalendarEventGetCommand {
+ return CalendarEventGetCommand{AccountId: accountId, Ids: []string{ref}}
+ },
+ func(resp CalendarEventSetResponse) map[string]*CalendarEvent {
+ return resp.Created
+ },
+ func(resp CalendarEventGetResponse) []CalendarEvent {
+ return resp.List
+ },
+ accountId, event,
+ ctx,
+ )
+}
+
+func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
+ return destroy(j, "DeleteCalendarEvent", CalendarEventType,
+ func(accountId string, destroy []string) CalendarEventSetCommand {
+ return CalendarEventSetCommand{AccountId: accountId, Destroy: destroy}
+ },
+ CalendarEventSetResponse{},
+ accountId, destroyIds,
+ ctx,
+ )
+}
diff --git a/pkg/jmap/api_identity.go b/pkg/jmap/api_identity.go
index 12df2eb1a3..e510de4e75 100644
--- a/pkg/jmap/api_identity.go
+++ b/pkg/jmap/api_identity.go
@@ -9,7 +9,7 @@ import (
var NS_IDENTITY = ns(JmapMail)
func (j *Client) GetAllIdentities(accountId string, ctx Context) ([]Identity, SessionState, State, Language, Error) {
- return getA(j, "GetAllIdentities", NS_IDENTITY,
+ return getA(j, "GetAllIdentities", IdentityType,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
},
@@ -20,7 +20,7 @@ func (j *Client) GetAllIdentities(accountId string, ctx Context) ([]Identity, Se
}
func (j *Client) GetIdentities(accountId string, identityIds []string, ctx Context) ([]Identity, SessionState, State, Language, Error) {
- return getA(j, "GetIdentities", NS_IDENTITY,
+ return getA(j, "GetIdentities", IdentityType,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId, Ids: ids}
},
@@ -31,7 +31,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) {
- return getN(j, "GetIdentitiesForAllAccounts", NS_IDENTITY,
+ return getN(j, "GetIdentitiesForAllAccounts", IdentityType,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
},
@@ -96,7 +96,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
}
func (j *Client) CreateIdentity(accountId string, identity IdentityChange, ctx Context) (*Identity, SessionState, State, Language, Error) {
- return create(j, "CreateIdentity", NS_IDENTITY,
+ return create(j, "CreateIdentity", IdentityType,
func(accountId string, create map[string]IdentityChange) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Create: create}
},
@@ -115,7 +115,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) {
- return update(j, "UpdateIdentity", NS_IDENTITY,
+ return update(j, "UpdateIdentity", IdentityType,
func(update map[string]PatchObject) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Update: update}
},
@@ -130,7 +130,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) {
- return destroy(j, "DeleteIdentity", NS_IDENTITY,
+ return destroy(j, "DeleteIdentity", IdentityType,
func(accountId string, destroy []string) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Destroy: destroyIds}
},
@@ -146,7 +146,7 @@ type IdentityChanges = ChangesTemplate[Identity]
// @api:tags email,changes
func (j *Client) GetIdentityChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (IdentityChanges, SessionState, State, Language, Error) {
- return changes(j, "GetIdentityChanges", NS_IDENTITY,
+ 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 7e845cf9c7..fc26b5a377 100644
--- a/pkg/jmap/api_mailbox.go
+++ b/pkg/jmap/api_mailbox.go
@@ -9,32 +9,27 @@ import (
var NS_MAILBOX = ns(JmapMail)
func (j *Client) GetMailbox(accountId string, ids []string, ctx Context) (MailboxGetResponse, SessionState, State, Language, Error) {
- /*
- return get(j, "GetMailbox", NS_MAILBOX,
- func(accountId string, ids []string) MailboxGetCommand {
- return MailboxGetCommand{AccountId: accountId, Ids: ids}
- },
- MailboxGetResponse{},
- identity1,
- accountId, session, ctx, logger, acceptLanguage, ids,
- )
- */
-
- return fget[Mailboxes](MAILBOX, j, "GetMailbox", accountId, ids, ctx)
+ return get(j, "GetMailbox", MailboxType,
+ func(accountId string, ids []string) MailboxGetCommand {
+ return MailboxGetCommand{AccountId: accountId, Ids: ids}
+ },
+ MailboxGetResponse{},
+ identity1,
+ accountId, ids,
+ ctx,
+ )
}
func (j *Client) GetAllMailboxes(accountIds []string, ctx Context) (map[string][]Mailbox, SessionState, State, Language, Error) {
- /*
- return getAN(j, "GetAllMailboxes", NS_MAILBOX,
- func(accountId string, ids []string) MailboxGetCommand {
- return MailboxGetCommand{AccountId: accountId}
- },
- MailboxGetResponse{},
- identity1,
- accountIds, session, ctx, logger, acceptLanguage, []string{},
- )
- */
- return fgetAN[Mailboxes](MAILBOX, j, "GetAllMailboxes", identity1, accountIds, []string{}, ctx)
+ return getAN(j, "GetAllMailboxes", MailboxType,
+ func(accountId string, ids []string) MailboxGetCommand {
+ return MailboxGetCommand{AccountId: accountId}
+ },
+ MailboxGetResponse{},
+ identity1,
+ accountIds, []string{},
+ ctx,
+ )
}
func (j *Client) SearchMailboxes(accountIds []string, filter MailboxFilterElement, ctx Context) (map[string][]Mailbox, SessionState, State, Language, Error) {
@@ -135,7 +130,7 @@ func newMailboxChanges(oldState, newState State, hasMoreChanges bool, created, u
// @apidoc mailboxes,changes
func (j *Client) GetMailboxChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (MailboxChanges, SessionState, State, Language, Error) {
- return changesA(j, "GetMailboxChanges", NS_MAILBOX,
+ return changesA(j, "GetMailboxChanges", MailboxType,
func() MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
@@ -161,7 +156,7 @@ func (j *Client) GetMailboxChanges(accountId string, sinceState State, maxChange
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, //NOSONAR
sinceStateMap map[string]State, maxChanges uint,
ctx Context) (map[string]MailboxChanges, SessionState, State, Language, Error) {
- return changesN(j, "GetMailboxChangesForMultipleAccounts", NS_MAILBOX,
+ return changesN(j, "GetMailboxChangesForMultipleAccounts", MailboxType,
accountIds, sinceStateMap,
func(accountId string, state State) MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: state, MaxChanges: uintPtr(maxChanges)}
@@ -179,59 +174,22 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, //NOS
}
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, ctx Context) (map[string][]string, SessionState, State, Language, Error) {
- logger := j.logger("GetMailboxRolesForMultipleAccounts", ctx)
- ctx = ctx.WithLogger(logger)
-
- uniqueAccountIds := structs.Uniq(accountIds)
- n := len(uniqueAccountIds)
- if n < 1 {
- return nil, "", "", "", nil
- }
-
- t := true
-
- invocations := make([]Invocation, n*2)
- for i, accountId := range uniqueAccountIds {
- invocations[i*2+0] = invocation(MailboxQueryCommand{
- AccountId: accountId,
- Filter: MailboxFilterCondition{
- HasAnyRole: &t,
- },
- }, mcid(accountId, "0"))
- invocations[i*2+1] = invocation(MailboxGetRefCommand{
- AccountId: accountId,
- IdsRef: &ResultReference{
- ResultOf: mcid(accountId, "0"),
- Name: CommandMailboxQuery,
- Path: "/ids",
- },
- }, mcid(accountId, "1"))
- }
-
- cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
- if err != nil {
- return nil, "", "", "", err
- }
-
- return command(j, ctx, cmd, func(body *Response) (map[string][]string, State, Error) {
- resp := make(map[string][]string, n)
- stateByAccountId := make(map[string]State, n)
- for _, accountId := range uniqueAccountIds {
- var getResponse MailboxGetResponse
- err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, mcid(accountId, "1"), &getResponse)
- if err != nil {
- return nil, "", err
- }
- roles := make([]string, len(getResponse.List))
- for i, mailbox := range getResponse.List {
- roles[i] = mailbox.Role
- }
+ return queryN(j, "GetMailboxRolesForMultipleAccounts", MailboxType,
+ []MailboxComparator{{Property: MailboxPropertySortOrder, IsAscending: true}},
+ func(accountId string, filter MailboxFilterCondition, sortBy []MailboxComparator, _ int, _ uint) MailboxQueryCommand {
+ return MailboxQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, SortAsTree: false, FilterAsTree: false, Position: 0, Limit: nil, CalculateTotal: false}
+ },
+ func(accountId string, cmd Command, path, rof string) MailboxGetRefCommand {
+ return MailboxGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}}
+ },
+ func(_ MailboxQueryResponse, get MailboxGetResponse) []string {
+ roles := structs.Map(get.List, func(m Mailbox) string { return m.Role })
slices.Sort(roles)
- resp[accountId] = roles
- stateByAccountId[accountId] = getResponse.State
- }
- return resp, squashState(stateByAccountId), nil
- })
+ return roles
+ },
+ accountIds, MailboxFilterCondition{HasAnyRole: boolPtr(true)}, nil, 0, 0,
+ ctx,
+ )
}
func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, ctx Context) (map[string]string, SessionState, State, Language, Error) {
@@ -286,7 +244,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) {
- return update(j, "UpdateMailbox", NS_MAILBOX,
+ return update(j, "UpdateMailbox", MailboxType,
func(update map[string]PatchObject) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Update: update}
},
@@ -301,7 +259,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) {
- return create(j, "CreateMailbox", NS_MAILBOX,
+ return create(j, "CreateMailbox", MailboxType,
func(accountId string, create map[string]MailboxChange) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Create: create}
},
@@ -320,7 +278,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) {
- return destroy(j, "DeleteMailboxes", NS_MAILBOX,
+ return destroy(j, "DeleteMailboxes", MailboxType,
func(accountId string, destroy []string) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Destroy: destroyIds}
},
diff --git a/pkg/jmap/api_principal.go b/pkg/jmap/api_principal.go
index 512c0559df..7876503faa 100644
--- a/pkg/jmap/api_principal.go
+++ b/pkg/jmap/api_principal.go
@@ -3,7 +3,7 @@ package jmap
var NS_PRINCIPALS = ns(JmapPrincipals)
func (j *Client) GetPrincipals(accountId string, ids []string, ctx Context) (PrincipalGetResponse, SessionState, State, Language, Error) {
- return get(j, "GetPrincipals", NS_PRINCIPALS,
+ return get(j, "GetPrincipals", PrincipalType,
func(accountId string, ids []string) PrincipalGetCommand {
return PrincipalGetCommand{AccountId: accountId, Ids: ids}
},
@@ -28,7 +28,7 @@ func (j *Client) QueryPrincipals(accountId string,
filter PrincipalFilterElement, sortBy []PrincipalComparator,
position uint, limit uint, calculateTotal bool,
ctx Context) (PrincipalSearchResults, SessionState, State, Language, Error) {
- return query(j, "QueryPrincipals", NS_PRINCIPALS,
+ return query(j, "QueryPrincipals", PrincipalType,
[]PrincipalComparator{{Property: PrincipalPropertyName, IsAscending: true}},
func(filter PrincipalFilterElement, sortBy []PrincipalComparator, position uint, limit uint) PrincipalQueryCommand {
return PrincipalQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: limit, CalculateTotal: calculateTotal}
diff --git a/pkg/jmap/api_quota.go b/pkg/jmap/api_quota.go
index 4aca032636..0e84d562a8 100644
--- a/pkg/jmap/api_quota.go
+++ b/pkg/jmap/api_quota.go
@@ -3,7 +3,7 @@ package jmap
var NS_QUOTA = ns(JmapQuota)
func (j *Client) GetQuotas(accountIds []string, ctx Context) (map[string]QuotaGetResponse, SessionState, State, Language, Error) {
- return getN(j, "GetQuotas", NS_QUOTA,
+ return getN(j, "GetQuotas", QuotaType,
func(accountId string, ids []string) QuotaGetCommand {
return QuotaGetCommand{AccountId: accountId}
},
@@ -21,7 +21,7 @@ type QuotaChanges = ChangesTemplate[Quota]
// @api:tags quota,changes
func (j *Client) GetQuotaChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (QuotaChanges, SessionState, State, Language, Error) {
- return changesA(j, "GetQuotaChanges", NS_QUOTA,
+ return changesA(j, "GetQuotaChanges", QuotaType,
func() QuotaChangesCommand {
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
@@ -53,7 +53,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) {
- return updates(j, "GetQuotaUsageChanges", NS_QUOTA,
+ 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 6b1ae9acaf..30625a04d0 100644
--- a/pkg/jmap/api_vacation.go
+++ b/pkg/jmap/api_vacation.go
@@ -12,7 +12,7 @@ const (
)
func (j *Client) GetVacationResponse(accountId string, ctx Context) (VacationResponseGetResponse, SessionState, State, Language, Error) {
- return get(j, "GetVacationResponse", NS_VACATION,
+ return get(j, "GetVacationResponse", VacationResponseType,
func(accountId string, ids []string) VacationResponseGetCommand {
return VacationResponseGetCommand{AccountId: accountId}
},
diff --git a/pkg/jmap/export_test.go b/pkg/jmap/export_test.go
index 094a9ca3bf..bf61f4263c 100644
--- a/pkg/jmap/export_test.go
+++ b/pkg/jmap/export_test.go
@@ -6,12 +6,35 @@ import (
"fmt"
"go/ast"
"go/token"
+ "iter"
"log"
"strings"
"golang.org/x/tools/go/packages"
)
+func valuesOf(p *packages.Package) iter.Seq[*ast.ValueSpec] { //NOSONAR
+ return func(yield func(*ast.ValueSpec) bool) {
+ for _, syn := range p.Syntax {
+ for _, decl := range syn.Decls {
+ g, ok := decl.(*ast.GenDecl)
+ if !ok {
+ continue
+ }
+ for _, s := range g.Specs {
+ e, ok := s.(*ast.ValueSpec)
+ if !ok {
+ continue
+ }
+ if !yield(e) {
+ return
+ }
+ }
+ }
+ }
+ }
+}
+
func parseConsts(pkgID string, suffix string, typeName string) (map[string]string, error) { //NOSONAR
result := map[string]string{}
{
@@ -31,29 +54,19 @@ func parseConsts(pkgID string, suffix string, typeName string) (map[string]strin
if p.ID != pkgID {
continue
}
- for _, syn := range p.Syntax {
- for _, decl := range syn.Decls {
- switch g := decl.(type) {
- case *ast.GenDecl:
- for _, s := range g.Specs {
- switch e := s.(type) {
- case *ast.ValueSpec:
- for i, ident := range e.Names {
- if ident != nil && strings.HasSuffix(ident.Name, suffix) {
- value := e.Values[i]
- switch c := value.(type) {
- case *ast.CallExpr:
- switch f := c.Fun.(type) {
- case *ast.Ident:
- if f.Name == typeName {
- switch a := c.Args[0].(type) {
- case *ast.BasicLit:
- if a.Kind == token.STRING {
- result[ident.Name] = strings.Trim(a.Value, `"`)
- }
- }
- }
- }
+ for v := range valuesOf(p) {
+ for i, ident := range v.Names {
+ if ident != nil && strings.HasSuffix(ident.Name, suffix) {
+ value := v.Values[i]
+ switch c := value.(type) {
+ case *ast.CallExpr:
+ switch f := c.Fun.(type) {
+ case *ast.Ident:
+ if f.Name == typeName {
+ switch a := c.Args[0].(type) {
+ case *ast.BasicLit:
+ if a.Kind == token.STRING {
+ result[ident.Name] = strings.Trim(a.Value, `"`)
}
}
}
diff --git a/pkg/jmap/http.go b/pkg/jmap/http.go
index ec337291a8..ff739c13b2 100644
--- a/pkg/jmap/http.go
+++ b/pkg/jmap/http.go
@@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
+ "net"
"net/http"
"net/http/httputil"
"net/url"
@@ -577,7 +578,7 @@ type HttpWsClient struct {
func (w *HttpWsClient) readPump() { //NOSONAR
logger := log.From(w.logger.With().Str("username", w.username))
defer func() {
- if err := w.c.Close(); err != nil {
+ if err := w.c.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
logger.Warn().Err(err).Msg("failed to close websocket connection")
}
}()
diff --git a/pkg/jmap/integration_event_test.go b/pkg/jmap/integration_calendar_test.go
similarity index 99%
rename from pkg/jmap/integration_event_test.go
rename to pkg/jmap/integration_calendar_test.go
index 47a8a1623e..cac25266fa 100644
--- a/pkg/jmap/integration_event_test.go
+++ b/pkg/jmap/integration_calendar_test.go
@@ -126,7 +126,7 @@ func TestEvents(t *testing.T) {
results := m[accountId]
require.Equal(len(results.Results), int(page))
require.Equal(limit, results.Limit)
- require.Equal(position, results.Position)
+ require.Equal(uint(position), results.Position)
require.Equal(true, results.CanCalculateChanges)
require.NotNil(results.Total)
require.Equal(count, *results.Total)
diff --git a/pkg/jmap/integration_contact_test.go b/pkg/jmap/integration_contact_test.go
index ef9cef8f6e..7f7ed5975f 100644
--- a/pkg/jmap/integration_contact_test.go
+++ b/pkg/jmap/integration_contact_test.go
@@ -107,7 +107,8 @@ func TestContacts(t *testing.T) {
require.Len(results.Results, int(count))
require.Equal(uint(0), results.Limit)
require.Equal(uint(0), results.Position)
- require.Equal(uint(0), results.Total)
+ require.NotNil(results.Total)
+ require.Equal(count, *results.Total)
require.Equal(true, results.CanCalculateChanges)
for _, actual := range results.Results {
diff --git a/pkg/jmap/integration_email_test.go b/pkg/jmap/integration_email_test.go
index 3e14d1a356..74aa484fd7 100644
--- a/pkg/jmap/integration_email_test.go
+++ b/pkg/jmap/integration_email_test.go
@@ -85,8 +85,8 @@ func TestEmails(t *testing.T) {
require.NoError(err)
require.Equal(session.State, sessionState)
- require.Equalf(threads, len(resp.Emails), "the number of collapsed emails in the inbox is expected to be %v, but is actually %v", threads, len(resp.Emails))
- for _, e := range resp.Emails {
+ 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.Len(e.MessageId, 1)
expectation, ok := mailsByMessageId[e.MessageId[0]]
require.True(ok)
@@ -99,8 +99,8 @@ func TestEmails(t *testing.T) {
require.NoError(err)
require.Equal(session.State, sessionState)
- require.Equalf(count, len(resp.Emails), "the number of emails in the inbox is expected to be %v, but is actually %v", count, len(resp.Emails))
- for _, e := range resp.Emails {
+ 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.Len(e.MessageId, 1)
expectation, ok := mailsByMessageId[e.MessageId[0]]
require.True(ok)
@@ -168,7 +168,13 @@ func TestSendingEmails(t *testing.T) {
accountId string
session *Session
}{{toAccountId, toSession}, {ccAccountId, ccSession}} {
- mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{u.accountId}, ctx)
+ uctx := Context{
+ Session: u.session,
+ Context: ctx.Context,
+ Logger: ctx.Logger,
+ AcceptLanguage: ctx.AcceptLanguage,
+ }
+ mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{u.accountId}, uctx)
require.NoError(err)
for _, mailbox := range mailboxes[u.accountId] {
require.Equal(0, mailbox.TotalEmails)
@@ -288,7 +294,13 @@ func TestSendingEmails(t *testing.T) {
accountId string
session *Session
}{{to, toAccountId, toSession}, {cc, ccAccountId, ccSession}} {
- mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{r.accountId}, ctx)
+ rctx := Context{
+ Session: r.session,
+ Context: ctx.Context,
+ 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] {
@@ -299,7 +311,7 @@ func TestSendingEmails(t *testing.T) {
}
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, ctx)
+ emails, _, _, _, 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)
diff --git a/pkg/jmap/integration_ws_test.go b/pkg/jmap/integration_ws_test.go
index 1a1825d1fb..1d6a8c1bb4 100644
--- a/pkg/jmap/integration_ws_test.go
+++ b/pkg/jmap/integration_ws_test.go
@@ -1,6 +1,7 @@
package jmap
import (
+ "slices"
"sync"
"sync/atomic"
"testing"
@@ -223,7 +224,15 @@ func TestWs(t *testing.T) {
require.Equal(state, changes.NewState)
require.Empty(changes.Created)
require.Len(changes.Destroyed, 2)
- require.EqualValues(emailIds, changes.Destroyed)
+ {
+ a := make([]string, len(emailIds))
+ copy(a, emailIds)
+ slices.Sort(emailIds)
+ b := make([]string, len(changes.Destroyed))
+ copy(b, changes.Destroyed)
+ slices.Sort(changes.Destroyed)
+ require.EqualValues(a, b)
+ }
require.Empty(changes.Updated)
lastState = state
}
diff --git a/pkg/jmap/model.go b/pkg/jmap/model.go
index 4b28edba03..2c70155fef 100644
--- a/pkg/jmap/model.go
+++ b/pkg/jmap/model.go
@@ -1564,6 +1564,34 @@ var _ Idable = &Mailbox{}
func (f Mailbox) GetObjectType() ObjectType { return MailboxType }
func (f Mailbox) GetId() string { return f.Id }
+const (
+ MailboxPropertyId = "id"
+ MailboxPropertyName = "name"
+ MailboxPropertyParentId = "parentId"
+ MailboxPropertyRole = "role"
+ MailboxPropertySortOrder = "sortOrder"
+ MailboxPropertyTotalEmails = "totalEmails"
+ MailboxPropertyUnreadEmails = "unreadEmails"
+ MailboxPropertyTotalThreads = "totalThreads"
+ MailboxPropertyUnreadThreads = "unreadThreads"
+ MailboxPropertyMyRights = "myRights"
+ MailboxPropertyIsSubscribed = "isSubscribed"
+)
+
+var MailboxProperties = []string{
+ MailboxPropertyId,
+ MailboxPropertyName,
+ MailboxPropertyParentId,
+ MailboxPropertyRole,
+ MailboxPropertySortOrder,
+ MailboxPropertyTotalEmails,
+ MailboxPropertyUnreadEmails,
+ MailboxPropertyTotalThreads,
+ MailboxPropertyUnreadThreads,
+ MailboxPropertyMyRights,
+ MailboxPropertyIsSubscribed,
+}
+
type MailboxChange struct {
// User-visible name for the Mailbox, e.g., “Inbox”.
//
@@ -1750,6 +1778,49 @@ type MailboxQueryCommand struct {
Sort []MailboxComparator `json:"sort,omitempty"`
SortAsTree bool `json:"sortAsTree,omitempty"`
FilterAsTree bool `json:"filterAsTree,omitempty"`
+
+ // The zero-based index of the first id in the full list of results to return.
+ //
+ // If a negative value is given, it is an offset from the end of the list.
+ // Specifically, the negative value MUST be added to the total number of results given
+ // the filter, and if still negative, it’s clamped to 0. This is now the zero-based
+ // index of the first id to return.
+ //
+ // If the index is greater than or equal to the total number of objects in the results
+ // list, then the ids array in the response will be empty, but this is not an error.
+ Position int `json:"position,omitempty"`
+
+ // An Email id.
+ //
+ // If supplied, the position argument is ignored.
+ // The index of this id in the results will be used in combination with the anchorOffset
+ // argument to determine the index of the first result to return.
+ Anchor string `json:"anchor,omitempty"`
+
+ // The index of the first result to return relative to the index of the anchor,
+ // if an anchor is given.
+ //
+ // This MAY be negative.
+ //
+ // For example, -1 means the object immediately preceding the anchor is the first result in
+ // the list returned.
+ AnchorOffset int `json:"anchorOffset,omitzero" doc:"opt" default:"0"`
+
+ // The maximum number of results to return.
+ //
+ // If null, no limit presumed.
+ // The server MAY choose to enforce a maximum limit argument.
+ // In this case, if a greater value is given (or if it is null), the limit is clamped
+ // to the maximum; the new limit is returned with the response so the client is aware.
+ //
+ // If a negative value is given, the call MUST be rejected with an invalidArguments error.
+ Limit *uint `json:"limit,omitempty"`
+
+ // Does the client wish to know the total number of results in the query?
+ //
+ // This may be slow and expensive for servers to calculate, particularly with complex filters,
+ // so clients should take care to only request the total when needed.
+ CalculateTotal bool `json:"calculateTotal,omitempty"`
}
var _ QueryCommand[Mailbox] = &MailboxQueryCommand{}
@@ -3673,6 +3744,7 @@ var _ GetResponse[Thread] = &ThreadGetResponse{}
func (r ThreadGetResponse) GetState() State { return r.State }
func (r ThreadGetResponse) GetNotFound() []string { return r.NotFound }
func (r ThreadGetResponse) GetList() []Thread { return r.List }
+func (r ThreadGetResponse) GetMarker() Thread { return Thread{} }
type IdentityGetCommand struct {
AccountId string `json:"accountId"`
diff --git a/pkg/jmap/model_examples.go b/pkg/jmap/model_examples.go
index c3a815529c..dec9f8020f 100644
--- a/pkg/jmap/model_examples.go
+++ b/pkg/jmap/model_examples.go
@@ -806,12 +806,13 @@ func (e Exemplar) EmailBodyPart() EmailBodyPart {
}
}
-func (e Exemplar) Emails() Emails {
- return Emails{
- Emails: []Email{e.Email()},
- Total: 132,
- Limit: 1,
- Offset: 5,
+func (e Exemplar) Emails() EmailSearchResults {
+ return EmailSearchResults{
+ Results: []Email{e.Email()},
+ Total: uintPtr(132),
+ Limit: 1,
+ Position: 5,
+ CanCalculateChanges: true,
}
}
@@ -820,7 +821,7 @@ func (e Exemplar) EmailGetResponse() EmailGetResponse {
AccountId: e.AccountId,
State: "aesh2ahj",
NotFound: []string{"ahx"},
- List: e.Emails().Emails,
+ List: e.Emails().Results,
}
}
diff --git a/pkg/jmap/templates.go b/pkg/jmap/templates.go
index 345a005443..421d249a1e 100644
--- a/pkg/jmap/templates.go
+++ b/pkg/jmap/templates.go
@@ -8,72 +8,8 @@ import (
"github.com/rs/zerolog"
)
-type Factory[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], CHANGES any] interface {
- Namespaces() []JmapNamespace
- CreateGetCommand(accountId string, ids []string) GETREQ
- CreateGetResponse() GETRESP
- MapChanges(oldState, newState State, hasMoreChanges bool, created, updated []T, destroyed []string) CHANGES
-}
-
-type Mailboxes string
-
-const MAILBOX = Mailboxes("MAILBOX")
-
-var _ Factory[Mailbox, MailboxGetCommand, MailboxGetResponse, MailboxChanges] = MAILBOX
-
-func (f Mailboxes) Namespaces() []JmapNamespace {
- return NS_MAILBOX
-}
-
-func (f Mailboxes) CreateGetCommand(accountId string, ids []string) MailboxGetCommand {
- return MailboxGetCommand{AccountId: accountId, Ids: ids}
-}
-
-func (f Mailboxes) CreateGetResponse() MailboxGetResponse {
- return MailboxGetResponse{}
-}
-
-func (f Mailboxes) MapChanges(oldState, newState State, hasMoreChanges bool, created, updated []Mailbox, destroyed []string) MailboxChanges {
- return MailboxChanges{
- OldState: oldState,
- NewState: newState,
- HasMoreChanges: hasMoreChanges,
- Created: created,
- Updated: updated,
- Destroyed: destroyed,
- }
-}
-
-func fget[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR
- client *Client, name string,
- accountId string, ids []string,
- ctx Context) (GETRESP, SessionState, State, Language, Error) {
- var getresp GETRESP
- return get(client, name, f.Namespaces(),
- f.CreateGetCommand,
- getresp,
- identity1,
- accountId, ids,
- ctx,
- )
-}
-
-func fgetA[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR
- client *Client, name string,
- accountId string, ids []string,
- ctx Context) ([]T, SessionState, State, Language, Error) {
- var getresp GETRESP
- return getA(client, name, f.Namespaces(),
- f.CreateGetCommand,
- getresp,
- accountId,
- ids,
- ctx,
- )
-}
-
func get[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
getCommandFactory func(string, []string) GETREQ,
_ GETRESP,
mapper func(GETRESP) RESP,
@@ -83,7 +19,7 @@ func get[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSON
var zero RESP
get := getCommandFactory(accountId, ids)
- cmd, err := client.request(ctx, using, invocation(get, "0"))
+ cmd, err := client.request(ctx, objType.Namespaces, invocation(get, "0"))
if err != nil {
return zero, "", "", "", err
}
@@ -100,35 +36,20 @@ func get[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSON
}
func getA[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T]]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
getCommandFactory func(string, []string) GETREQ,
resp GETRESP,
accountId string, ids []string, ctx Context) ([]T, SessionState, State, Language, Error) {
- return get(client, name, using, getCommandFactory, resp, func(r GETRESP) []T { return r.GetList() }, accountId, ids, ctx)
-}
-
-func fgetAN[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any, CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR
- client *Client, name string,
- respMapper func(map[string][]T) RESP,
- accountIds []string, ids []string,
- ctx Context) (RESP, SessionState, State, Language, Error) {
- var getresp GETRESP
- return getAN(client, name, f.Namespaces(),
- f.CreateGetCommand,
- getresp,
- respMapper,
- accountIds, ids,
- ctx,
- )
+ return get(client, name, objType, getCommandFactory, resp, func(r GETRESP) []T { return r.GetList() }, accountId, ids, ctx)
}
func getAN[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
getCommandFactory func(string, []string) GETREQ,
resp GETRESP,
respMapper func(map[string][]T) RESP,
accountIds []string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) {
- return getN(client, name, using, getCommandFactory, resp,
+ return getN(client, name, objType, getCommandFactory, resp,
func(r GETRESP) []T { return r.GetList() },
respMapper,
accountIds, ids,
@@ -137,7 +58,7 @@ func getAN[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOS
}
func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
getCommandFactory func(string, []string) GETREQ,
_ GETRESP,
itemMapper func(GETRESP) ITEM,
@@ -158,7 +79,7 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an
invocations[i] = invocation(get, mcid(accountId, "0"))
}
- cmd, err := client.request(ctx, using, invocations...)
+ cmd, err := client.request(ctx, objType.Namespaces, invocations...)
if err != nil {
return zero, "", "", "", err
}
@@ -180,7 +101,7 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an
}
func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP SetResponse[T], GETRESP GetResponse[T]]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
setCommandFactory func(string, map[string]C) SETREQ,
getCommandFactory func(string, string) GETREQ,
createdMapper func(SETRESP) map[string]*T,
@@ -193,7 +114,7 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
createMap := map[string]C{"c": create}
get := getCommandFactory(accountId, "#c")
set := setCommandFactory(accountId, createMap)
- cmd, err := client.request(ctx, using,
+ cmd, err := client.request(ctx, objType.Namespaces,
invocation(set, "0"),
invocation(get, "1"),
)
@@ -240,14 +161,14 @@ 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, using []JmapNamespace, //NOSONAR
+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) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
set := setCommandFactory(accountId, destroy)
- cmd, err := client.request(ctx, using,
+ cmd, err := client.request(ctx, objType.Namespaces,
invocation(set, "0"),
)
if err != nil {
@@ -265,7 +186,7 @@ func destroy[T Foo, REQ SetCommand[T], RESP SetResponse[T]](client *Client, name
}
func changesA[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESRESP ChangesResponse[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
changesCommandFactory func() CHANGESREQ,
changesResp CHANGESRESP,
_ GETRESP,
@@ -273,7 +194,7 @@ func changesA[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
respMapper func(State, State, bool, []T, []T, []string) RESP,
ctx Context) (RESP, SessionState, State, Language, Error) {
- return changes(client, name, using, changesCommandFactory, changesResp, getCommandFactory,
+ return changes(client, name, objType, changesCommandFactory, changesResp, getCommandFactory,
func(r GETRESP) []T { return r.GetList() },
respMapper,
ctx,
@@ -281,7 +202,7 @@ func changesA[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
}
func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESRESP ChangesResponse[T], GETRESP GetResponse[T], ITEM any, RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
changesCommandFactory func() CHANGESREQ,
_ CHANGESRESP,
getCommandFactory func(string, string) GETREQ,
@@ -295,7 +216,7 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
getCreated := getCommandFactory("/created", "0") //NOSONAR
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
- cmd, err := client.request(ctx.WithLogger(logger), using,
+ cmd, err := client.request(ctx.WithLogger(logger), objType.Namespaces,
invocation(changes, "0"),
invocation(getCreated, "1"),
invocation(getUpdated, "2"),
@@ -335,7 +256,7 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
}
func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESRESP ChangesResponse[T], GETRESP GetResponse[T], ITEM any, CHANGESITEM any, RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
accountIds []string, sinceStateMap map[string]State,
changesCommandFactory func(string, State) CHANGESREQ,
_ CHANGESRESP,
@@ -362,8 +283,9 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
}
invocations := make([]Invocation, n*3)
- getCommand := Command("")
- changesCommand := Command("")
+ var ch CHANGESREQ
+ var gc GETREQ
+ var gu GETREQ
for i, accountId := range uniqueAccountIds {
sinceState, ok := sinceStateMap[accountId]
if !ok {
@@ -379,13 +301,14 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
invocations[i*3+1] = invocation(getCreated, mcid(accountId, "1"))
invocations[i*3+2] = invocation(getUpdated, mcid(accountId, "2"))
- changesCommand = changes.GetCommand()
- getCommand = getCreated.GetCommand()
+ ch = changes
+ gc = getCreated
+ gu = getUpdated
}
ctx = ctx.WithLogger(logger)
- cmd, err := client.request(ctx, using, invocations...)
+ cmd, err := client.request(ctx, objType.Namespaces, invocations...)
if err != nil {
return zero, "", "", "", err
}
@@ -395,19 +318,19 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
stateByAccountId := make(map[string]State, n)
for _, accountId := range uniqueAccountIds {
var changesResponse CHANGESRESP
- err = retrieveResponseMatchParameters(ctx, body, changesCommand, mcid(accountId, "0"), &changesResponse)
+ err = retrieveChanges(ctx, body, ch, mcid(accountId, "0"), &changesResponse)
if err != nil {
return zero, "", err
}
var createdResponse GETRESP
- err = retrieveResponseMatchParameters(ctx, body, getCommand, mcid(accountId, "1"), &createdResponse)
+ err = retrieveGet(ctx, body, gc, mcid(accountId, "1"), &createdResponse)
if err != nil {
return zero, "", err
}
var updatedResponse GETRESP
- err = retrieveResponseMatchParameters(ctx, body, getCommand, mcid(accountId, "2"), &updatedResponse)
+ err = retrieveGet(ctx, body, gu, mcid(accountId, "2"), &updatedResponse)
if err != nil {
return zero, "", err
}
@@ -422,7 +345,7 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
}
func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESRESP ChangesResponse[T], GETRESP GetResponse[T], ITEM any, RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
changesCommandFactory func() CHANGESREQ,
_ CHANGESRESP,
getCommandFactory func(string, string) GETREQ,
@@ -435,7 +358,7 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
changes := changesCommandFactory()
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
- cmd, err := client.request(ctx, using,
+ cmd, err := client.request(ctx, objType.Namespaces,
invocation(changes, "0"),
invocation(getUpdated, "1"),
)
@@ -464,7 +387,8 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
})
}
-func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP any, SETRESP SetResponse[T], GETRESP GetResponse[T]](client *Client, name string, using []JmapNamespace, //NOSONAR
+func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP any, SETRESP SetResponse[T], GETRESP GetResponse[T]]( //NOSONAR
+ client *Client, name string, objType ObjectType,
setCommandFactory func(map[string]PatchObject) SET,
getCommandFactory func(string) GET,
notUpdatedExtractor func(SETRESP) map[string]SetError,
@@ -473,9 +397,10 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an
ctx Context) (RESP, SessionState, State, Language, Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
+
update := setCommandFactory(map[string]PatchObject{id: changes.AsPatch()})
get := getCommandFactory(id)
- cmd, err := client.request(ctx, using, invocation(update, "0"), invocation(get, "1"))
+ cmd, err := client.request(ctx, objType.Namespaces, invocation(update, "0"), invocation(get, "1"))
var zero RESP
if err != nil {
return zero, "", "", "", err
@@ -503,7 +428,7 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an
}
func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T], QUERYRESP QueryResponse[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
defaultSortBy []SORT,
queryCommandFactory func(filter FILTER, sortBy []SORT, limit uint, position uint) QUERY,
getCommandFactory func(cmd Command, path string, rof string) GET,
@@ -523,7 +448,7 @@ func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T]
var zero RESP
- cmd, err := client.request(ctx, using, invocation(query, "0"), invocation(get, "1"))
+ cmd, err := client.request(ctx, objType.Namespaces, invocation(query, "0"), invocation(get, "1"))
if err != nil {
return zero, "", "", "", err
}
@@ -544,7 +469,7 @@ func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T]
}
func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T], QUERYRESP QueryResponse[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
- client *Client, name string, using []JmapNamespace,
+ client *Client, name string, objType ObjectType,
defaultSortBy []SORT,
queryCommandFactory func(accountId string, filter FILTER, sortBy []SORT, position int, limit uint) QUERY,
getCommandFactory func(accountId string, cmd Command, path string, rof string) GET,
@@ -552,6 +477,9 @@ func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T
accountIds []string,
filter FILTER, sortBy []SORT, limit uint, position int,
ctx Context) (map[string]RESP, SessionState, State, Language, Error) {
+ logger := client.logger(name, ctx)
+ ctx = ctx.WithLogger(logger)
+
uniqueAccountIds := structs.Uniq(accountIds)
if sortBy == nil {
@@ -570,7 +498,7 @@ func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T
g = get
}
- cmd, err := client.request(ctx, NS_CALENDARS, invocations...)
+ cmd, err := client.request(ctx, objType.Namespaces, invocations...)
if err != nil {
return nil, "", "", "", err
}
diff --git a/services/groupware/pkg/groupware/api_emails.go b/services/groupware/pkg/groupware/api_emails.go
index de781c39c3..e8b791473a 100644
--- a/services/groupware/pkg/groupware/api_emails.go
+++ b/services/groupware/pkg/groupware/api_emails.go
@@ -109,16 +109,17 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
return req.jmapError(accountId, jerr, sessionState, lang)
}
- sanitized, err := req.sanitizeEmails(emails.Emails)
+ sanitized, err := req.sanitizeEmails(emails.Results)
if err != nil {
return req.error(accountId, err)
}
- safe := jmap.Emails{
- Emails: sanitized,
- Total: emails.Total,
- Limit: emails.Limit,
- Offset: emails.Offset,
+ safe := jmap.EmailSearchResults{
+ Results: sanitized,
+ Total: emails.Total,
+ Limit: emails.Limit,
+ Position: emails.Position,
+ CanCalculateChanges: emails.CanCalculateChanges,
}
return req.respond(accountId, safe, sessionState, EmailResponseObjectType, state)
@@ -689,15 +690,17 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
var totalOverAllAccounts uint = 0
total := 0
for _, results := range resultsByAccountId {
- totalOverAllAccounts += results.Total
- total += len(results.Snippets)
+ if results.Total != nil {
+ totalOverAllAccounts += *results.Total
+ }
+ total += len(results.Results)
}
flattened := make([]Snippet, total)
{
i := 0
for accountId, results := range resultsByAccountId {
- for _, result := range results.Snippets {
+ for _, result := range results.Results {
flattened[i] = Snippet{
AccountId: accountId,
SearchSnippetWithMeta: result,