groupware: significant refactorings of the JMAP framework, adding methods and more intelligence to the various request and response types to improve the use of template functions, reducing the risks of typos and copy/paste mistakes

This commit is contained in:
Pascal Bleser
2026-04-07 17:00:48 +02:00
parent 898c99d8c7
commit 49c2425172
23 changed files with 1365 additions and 516 deletions

View File

@@ -14,26 +14,16 @@ type AddressBooksResponse struct {
}
func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBooksResponse, SessionState, State, Language, Error) {
logger = j.logger("GetAddressbooks", session, logger)
cmd, err := j.request(session, logger, NS_ADDRESSBOOKS,
invocation(CommandAddressBookGet, AddressBookGetCommand{AccountId: accountId, Ids: ids}, "0"),
return get(j, "GetAddressbooks", NS_ADDRESSBOOKS,
func(accountId string, ids []string) AddressBookGetCommand {
return AddressBookGetCommand{AccountId: accountId, Ids: ids}
},
AddressBookGetResponse{},
func(resp AddressBookGetResponse) AddressBooksResponse {
return AddressBooksResponse{AddressBooks: resp.List, NotFound: resp.NotFound}
},
accountId, session, ctx, logger, acceptLanguage, ids,
)
if err != nil {
return AddressBooksResponse{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (AddressBooksResponse, State, Error) {
var response AddressBookGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandAddressBookGet, "0", &response)
if err != nil {
return AddressBooksResponse{}, response.State, err
}
return AddressBooksResponse{
AddressBooks: response.List,
NotFound: response.NotFound,
}, response.State, nil
})
}
type AddressBookChanges struct {
@@ -48,11 +38,11 @@ type AddressBookChanges struct {
// Retrieve Address Book changes since a given state.
// @apidoc addressbook,changes
func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (AddressBookChanges, SessionState, State, Language, Error) {
return changesTemplate(j, "GetAddressbookChanges", NS_ADDRESSBOOKS,
CommandAddressBookChanges, CommandAddressBookGet,
return changes(j, "GetAddressbookChanges", NS_ADDRESSBOOKS,
func() AddressBookChangesCommand {
return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
AddressBookChangesResponse{},
func(path string, rof string) AddressBookGetRefCommand {
return AddressBookGetRefCommand{
AccountId: accountId,
@@ -63,9 +53,6 @@ func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx c
},
}
},
func(resp AddressBookChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp AddressBookGetResponse) []AddressBook { return resp.List },
func(oldState, newState State, hasMoreChanges bool, created, updated []AddressBook, destroyed []string) AddressBookChanges {
return AddressBookChanges{
@@ -77,42 +64,48 @@ func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx c
Destroyed: destroyed,
}
},
func(resp AddressBookGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}
func (j *Client) CreateAddressBook(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create AddressBookChange) (*AddressBook, SessionState, State, Language, Error) {
return createTemplate(j, "CreateAddressBook", NS_ADDRESSBOOKS, AddressBookType, CommandAddressBookSet, CommandAddressBookGet,
func (j *Client) CreateAddressBook(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, addressbook AddressBookChange) (*AddressBook, SessionState, State, Language, Error) {
return create(j, "CreateAddressBook", NS_ADDRESSBOOKS,
func(accountId string, create map[string]AddressBookChange) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Create: create}
},
func(accountId string, ref string) AddressBookGetCommand {
return AddressBookGetCommand{AccountId: accountId, Ids: []string{ref}}
func(accountId string, ids string) AddressBookGetCommand {
return AddressBookGetCommand{AccountId: accountId, Ids: []string{ids}}
},
func(resp AddressBookSetResponse) map[string]*AddressBook {
return resp.Created
},
func(resp AddressBookSetResponse) map[string]SetError {
return resp.NotCreated
},
func(resp AddressBookGetResponse) []AddressBook {
return resp.List
},
func(resp AddressBookSetResponse) State {
return resp.NewState
},
accountId, session, ctx, logger, acceptLanguage, create,
accountId, session, ctx, logger, acceptLanguage, addressbook,
)
}
func (j *Client) DeleteAddressBook(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
return deleteTemplate(j, "DeleteAddressBook", NS_ADDRESSBOOKS, CommandAddressBookSet,
func (j *Client) DeleteAddressBook(accountId string, destroyIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
return destroy(j, "DeleteAddressBook", NS_ADDRESSBOOKS,
func(accountId string, destroy []string) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Destroy: destroy}
},
func(resp AddressBookSetResponse) map[string]SetError { return resp.NotDestroyed },
func(resp AddressBookSetResponse) State { return resp.NewState },
accountId, destroy, session, ctx, logger, acceptLanguage,
AddressBookSetResponse{},
accountId, destroyIds, session, ctx, logger, acceptLanguage,
)
}
func (j *Client) UpdateAddressBook(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string, changes AddressBookChange) (AddressBook, SessionState, State, Language, Error) {
return update(j, "UpdateAddressBook", NS_ADDRESSBOOKS,
func(update map[string]PatchObject) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Update: update}
},
func(id string) AddressBookGetCommand {
return AddressBookGetCommand{AccountId: accountId, Ids: []string{id}}
},
func(resp AddressBookSetResponse) map[string]SetError { return resp.NotUpdated },
func(resp AddressBookGetResponse) AddressBook { return resp.List[0] },
id, changes, session, ctx, logger, acceptLanguage,
)
}

View File

@@ -11,7 +11,7 @@ import (
func (j *Client) GetBlobMetadata(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (*Blob, SessionState, State, Language, Error) {
cmd, jerr := j.request(session, logger, ns(JmapBlob),
invocation(CommandBlobGet, BlobGetCommand{
invocation(BlobGetCommand{
AccountId: accountId,
Ids: []string{id},
// add BlobPropertyData to retrieve the data
@@ -90,8 +90,8 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
}
cmd, jerr := j.request(session, logger, ns(JmapBlob),
invocation(CommandBlobUpload, upload, "0"),
invocation(CommandBlobGet, getHash, "1"),
invocation(upload, "0"),
invocation(getHash, "1"),
)
if jerr != nil {
return UploadedBlobWithHash{}, "", "", "", jerr
@@ -99,13 +99,13 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedBlobWithHash, State, Error) {
var uploadResponse BlobUploadResponse
err := retrieveResponseMatchParameters(logger, body, CommandBlobUpload, "0", &uploadResponse)
err := retrieveResponseMatchParameters(logger, body, upload.GetCommand(), "0", &uploadResponse)
if err != nil {
return UploadedBlobWithHash{}, "", err
}
var getResponse BlobGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandBlobGet, "1", &getResponse)
err = retrieveResponseMatchParameters(logger, body, getHash.GetCommand(), "1", &getResponse)
if err != nil {
return UploadedBlobWithHash{}, "", err
}

View File

@@ -21,8 +21,8 @@ func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context
calls := make([]Invocation, len(uniqueAccountIds)*2)
for i, accountId := range uniqueAccountIds {
calls[i*2+0] = invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, mcid(accountId, "I"))
calls[i*2+1] = invocation(CommandQuotaGet, QuotaGetCommand{AccountId: accountId}, mcid(accountId, "Q"))
calls[i*2+0] = invocation(IdentityGetCommand{AccountId: accountId}, mcid(accountId, "I"))
calls[i*2+1] = invocation(QuotaGetCommand{AccountId: accountId}, mcid(accountId, "Q"))
}
cmd, err := j.request(session, logger, NS_MAIL_QUOTA, calls...)

View File

@@ -13,7 +13,7 @@ func (j *Client) ParseICalendarBlob(accountId string, session *Session, ctx cont
logger = j.logger("ParseICalendarBlob", session, logger)
cmd, err := j.request(session, logger, NS_CALENDARS,
invocation(CommandCalendarEventParse, CalendarEventParseCommand{AccountId: accountId, BlobIds: blobIds}, "0"),
invocation(CalendarEventParseCommand{AccountId: accountId, BlobIds: blobIds}, "0"),
)
if err != nil {
return CalendarEventParseResponse{}, "", "", "", err
@@ -35,14 +35,14 @@ type CalendarsResponse struct {
}
func (j *Client) GetCalendars(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (CalendarsResponse, SessionState, State, Language, Error) {
return getTemplate(j, "GetCalendars", NS_CALENDARS, CommandCalendarGet,
return get(j, "GetCalendars", NS_CALENDARS,
func(accountId string, ids []string) CalendarGetCommand {
return CalendarGetCommand{AccountId: accountId, Ids: ids}
},
CalendarGetResponse{},
func(resp CalendarGetResponse) CalendarsResponse {
return CalendarsResponse{Calendars: resp.List, NotFound: resp.NotFound}
},
func(resp CalendarGetResponse) State { return resp.State },
accountId, session, ctx, logger, acceptLanguage, ids,
)
}
@@ -59,11 +59,11 @@ type CalendarChanges struct {
// Retrieve Calendar changes since a given state.
// @apidoc calendar,changes
func (j *Client) GetCalendarChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (CalendarChanges, SessionState, State, Language, Error) {
return changesTemplate(j, "GetCalendarChanges", NS_CALENDARS,
CommandCalendarChanges, CommandCalendarGet,
return changes(j, "GetCalendarChanges", NS_CALENDARS,
func() CalendarChangesCommand {
return CalendarChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
CalendarChangesResponse{},
func(path string, rof string) CalendarGetRefCommand {
return CalendarGetRefCommand{
AccountId: accountId,
@@ -74,9 +74,6 @@ func (j *Client) GetCalendarChanges(accountId string, session *Session, ctx cont
},
}
},
func(resp CalendarChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp CalendarGetResponse) []Calendar { return resp.List },
func(oldState, newState State, hasMoreChanges bool, created, updated []Calendar, destroyed []string) CalendarChanges {
return CalendarChanges{
@@ -88,7 +85,6 @@ func (j *Client) GetCalendarChanges(accountId string, session *Session, ctx cont
Destroyed: destroyed,
}
},
func(resp CalendarGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}
@@ -117,8 +113,8 @@ func (j *Client) QueryCalendarEvents(accountIds []string, session *Session, ctx
if position > 0 {
query.Position = position
}
invocations[i*2+0] = invocation(CommandCalendarEventQuery, query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(CommandCalendarEventGet, CalendarEventGetRefCommand{
invocations[i*2+0] = invocation(query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(CalendarEventGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
Name: CommandCalendarEventQuery,
@@ -165,11 +161,11 @@ type CalendarEventChanges struct {
// @api:tags event,changes
func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (CalendarEventChanges, SessionState, State, Language, Error) {
return changesTemplate(j, "GetCalendarEventChanges", NS_CALENDARS,
CommandCalendarEventChanges, CommandCalendarEventGet,
return changes(j, "GetCalendarEventChanges", NS_CALENDARS,
func() CalendarEventChangesCommand {
return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
CalendarEventChangesResponse{},
func(path string, rof string) CalendarEventGetRefCommand {
return CalendarEventGetRefCommand{
AccountId: accountId,
@@ -180,9 +176,6 @@ func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx
},
}
},
func(resp CalendarEventChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp CalendarEventGetResponse) []CalendarEvent { return resp.List },
func(oldState, newState State, hasMoreChanges bool, created, updated []CalendarEvent, destroyed []string) CalendarEventChanges {
return CalendarEventChanges{
@@ -194,13 +187,12 @@ func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx
Destroyed: destroyed,
}
},
func(resp CalendarEventGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}
func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create CalendarEvent) (*CalendarEvent, SessionState, State, Language, Error) {
return createTemplate(j, "CreateCalendarEvent", NS_CALENDARS, CalendarEventType, CommandCalendarEventSet, CommandCalendarEventGet,
func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, event CalendarEvent) (*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}
},
@@ -210,30 +202,23 @@ func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx con
func(resp CalendarEventSetResponse) map[string]*CalendarEvent {
return resp.Created
},
func(resp CalendarEventSetResponse) map[string]SetError {
return resp.NotCreated
},
func(resp CalendarEventGetResponse) []CalendarEvent {
return resp.List
},
func(resp CalendarEventSetResponse) State {
return resp.NewState
},
accountId, session, ctx, logger, acceptLanguage, create)
accountId, session, ctx, logger, acceptLanguage, event)
}
func (j *Client) DeleteCalendarEvent(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
return deleteTemplate(j, "DeleteCalendarEvent", NS_CALENDARS, CommandCalendarEventSet,
func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (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}
},
func(resp CalendarEventSetResponse) map[string]SetError { return resp.NotDestroyed },
func(resp CalendarEventSetResponse) State { return resp.NewState },
accountId, destroy, session, ctx, logger, acceptLanguage)
CalendarEventSetResponse{},
accountId, destroyIds, session, ctx, logger, acceptLanguage)
}
func (j *Client) CreateCalendar(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create CalendarChange) (*Calendar, SessionState, State, Language, Error) {
return createTemplate(j, "CreateCalendar", NS_CALENDARS, CalendarType, CommandAddressBookSet, CommandAddressBookGet,
func (j *Client) CreateCalendar(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, calendar CalendarChange) (*Calendar, SessionState, State, Language, Error) {
return create(j, "CreateCalendar", NS_CALENDARS,
func(accountId string, create map[string]CalendarChange) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Create: create}
},
@@ -243,26 +228,19 @@ func (j *Client) CreateCalendar(accountId string, session *Session, ctx context.
func(resp CalendarSetResponse) map[string]*Calendar {
return resp.Created
},
func(resp CalendarSetResponse) map[string]SetError {
return resp.NotCreated
},
func(resp CalendarGetResponse) []Calendar {
return resp.List
},
func(resp CalendarSetResponse) State {
return resp.NewState
},
accountId, session, ctx, logger, acceptLanguage, create,
accountId, session, ctx, logger, acceptLanguage, calendar,
)
}
func (j *Client) DeleteCalendar(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
return deleteTemplate(j, "DeleteCalendar", NS_ADDRESSBOOKS, CommandAddressBookSet,
func (j *Client) DeleteCalendar(accountId string, destroyIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
return destroy(j, "DeleteCalendar", NS_ADDRESSBOOKS,
func(accountId string, destroy []string) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Destroy: destroy}
},
func(resp CalendarSetResponse) map[string]SetError { return resp.NotDestroyed },
func(resp CalendarSetResponse) State { return resp.NewState },
accountId, destroy, session, ctx, logger, acceptLanguage,
CalendarSetResponse{},
accountId, destroyIds, session, ctx, logger, acceptLanguage,
)
}

View File

@@ -81,31 +81,31 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
methodCalls := []Invocation{}
if stateMap.Mailboxes != nil {
methodCalls = append(methodCalls, invocation(CommandMailboxChanges, MailboxChangesCommand{AccountId: accountId, SinceState: *stateMap.Mailboxes, MaxChanges: posUIntPtr(maxChanges)}, "mailboxes"))
methodCalls = append(methodCalls, invocation(MailboxChangesCommand{AccountId: accountId, SinceState: *stateMap.Mailboxes, MaxChanges: posUIntPtr(maxChanges)}, "mailboxes"))
}
if stateMap.Emails != nil {
methodCalls = append(methodCalls, invocation(CommandEmailChanges, EmailChangesCommand{AccountId: accountId, SinceState: *stateMap.Emails, MaxChanges: posUIntPtr(maxChanges)}, "emails"))
methodCalls = append(methodCalls, invocation(EmailChangesCommand{AccountId: accountId, SinceState: *stateMap.Emails, MaxChanges: posUIntPtr(maxChanges)}, "emails"))
}
if stateMap.Calendars != nil {
methodCalls = append(methodCalls, invocation(CommandCalendarChanges, CalendarChangesCommand{AccountId: accountId, SinceState: *stateMap.Calendars, MaxChanges: posUIntPtr(maxChanges)}, "calendars"))
methodCalls = append(methodCalls, invocation(CalendarChangesCommand{AccountId: accountId, SinceState: *stateMap.Calendars, MaxChanges: posUIntPtr(maxChanges)}, "calendars"))
}
if stateMap.Events != nil {
methodCalls = append(methodCalls, invocation(CommandCalendarEventChanges, CalendarEventChangesCommand{AccountId: accountId, SinceState: *stateMap.Events, MaxChanges: posUIntPtr(maxChanges)}, "events"))
methodCalls = append(methodCalls, invocation(CalendarEventChangesCommand{AccountId: accountId, SinceState: *stateMap.Events, MaxChanges: posUIntPtr(maxChanges)}, "events"))
}
if stateMap.Addressbooks != nil {
methodCalls = append(methodCalls, invocation(CommandAddressBookChanges, AddressBookChangesCommand{AccountId: accountId, SinceState: *stateMap.Addressbooks, MaxChanges: posUIntPtr(maxChanges)}, "addressbooks"))
methodCalls = append(methodCalls, invocation(AddressBookChangesCommand{AccountId: accountId, SinceState: *stateMap.Addressbooks, MaxChanges: posUIntPtr(maxChanges)}, "addressbooks"))
}
if stateMap.Addressbooks != nil {
methodCalls = append(methodCalls, invocation(CommandAddressBookChanges, AddressBookChangesCommand{AccountId: accountId, SinceState: *stateMap.Addressbooks, MaxChanges: posUIntPtr(maxChanges)}, "addressbooks"))
methodCalls = append(methodCalls, invocation(AddressBookChangesCommand{AccountId: accountId, SinceState: *stateMap.Addressbooks, MaxChanges: posUIntPtr(maxChanges)}, "addressbooks"))
}
if stateMap.Contacts != nil {
methodCalls = append(methodCalls, invocation(CommandContactCardChanges, ContactCardChangesCommand{AccountId: accountId, SinceState: *stateMap.Contacts, MaxChanges: posUIntPtr(maxChanges)}, "contacts"))
methodCalls = append(methodCalls, invocation(ContactCardChangesCommand{AccountId: accountId, SinceState: *stateMap.Contacts, MaxChanges: posUIntPtr(maxChanges)}, "contacts"))
}
if stateMap.Identities != nil {
methodCalls = append(methodCalls, invocation(CommandIdentityChanges, IdentityChangesCommand{AccountId: accountId, SinceState: *stateMap.Identities, MaxChanges: posUIntPtr(maxChanges)}, "identities"))
methodCalls = append(methodCalls, invocation(IdentityChangesCommand{AccountId: accountId, SinceState: *stateMap.Identities, MaxChanges: posUIntPtr(maxChanges)}, "identities"))
}
if stateMap.EmailSubmissions != nil {
methodCalls = append(methodCalls, invocation(CommandEmailSubmissionChanges, EmailSubmissionChangesCommand{AccountId: accountId, SinceState: *stateMap.EmailSubmissions, MaxChanges: posUIntPtr(maxChanges)}, "submissions"))
methodCalls = append(methodCalls, invocation(EmailSubmissionChangesCommand{AccountId: accountId, SinceState: *stateMap.EmailSubmissions, MaxChanges: posUIntPtr(maxChanges)}, "submissions"))
}
// if stateMap.Quotas != nil { methodCalls = append(methodCalls, invocation(CommandQuotaChanges, QuotaChangesCommand{AccountId: accountId, SinceState: *stateMap.Quotas, MaxChanges: posUIntPtr(maxChanges)}, "quotas")) }

View File

@@ -15,7 +15,7 @@ func (j *Client) GetContactCardsById(accountId string, session *Session, ctx con
acceptLanguage string, contactIds []string) (map[string]jscontact.ContactCard, SessionState, State, Language, Error) {
logger = j.logger("GetContactCardsById", session, logger)
cmd, err := j.request(session, logger, NS_CONTACTS, invocation(CommandContactCardGet, ContactCardGetCommand{
cmd, err := j.request(session, logger, NS_CONTACTS, invocation(ContactCardGetCommand{
Ids: contactIds,
AccountId: accountId,
}, "0"))
@@ -39,12 +39,12 @@ func (j *Client) GetContactCardsById(accountId string, session *Session, ctx con
func (j *Client) GetContactCards(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, contactIds []string) ([]jscontact.ContactCard, SessionState, State, Language, Error) {
return getTemplate(j, "GetContactCards", NS_CONTACTS, CommandContactCardGet,
return get(j, "GetContactCards", NS_CONTACTS,
func(accountId string, ids []string) ContactCardGetCommand {
return ContactCardGetCommand{AccountId: accountId, Ids: contactIds}
},
ContactCardGetResponse{},
func(resp ContactCardGetResponse) []jscontact.ContactCard { return resp.List },
func(resp ContactCardGetResponse) State { return resp.State },
accountId, session, ctx, logger, acceptLanguage, contactIds,
)
}
@@ -62,11 +62,11 @@ type ContactCardChanges struct {
// @api:tags contact,changes
func (j *Client) GetContactCardChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (ContactCardChanges, SessionState, State, Language, Error) {
return changesTemplate(j, "GetContactCardChanges", NS_CONTACTS,
CommandContactCardChanges, CommandContactCardGet,
return changes(j, "GetContactCardChanges", NS_CONTACTS,
func() ContactCardChangesCommand {
return ContactCardChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
ContactCardChangesResponse{},
func(path string, rof string) ContactCardGetRefCommand {
return ContactCardGetRefCommand{
AccountId: accountId,
@@ -77,9 +77,6 @@ func (j *Client) GetContactCardChanges(accountId string, session *Session, ctx c
},
}
},
func(resp ContactCardChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp ContactCardGetResponse) []jscontact.ContactCard { return resp.List },
func(oldState, newState State, hasMoreChanges bool, created, updated []jscontact.ContactCard, destroyed []string) ContactCardChanges {
return ContactCardChanges{
@@ -91,7 +88,6 @@ func (j *Client) GetContactCardChanges(accountId string, session *Session, ctx c
Destroyed: destroyed,
}
},
func(resp ContactCardGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}
@@ -120,8 +116,8 @@ func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx co
if position > 0 {
query.Position = position
}
invocations[i*2+0] = invocation(CommandContactCardQuery, query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(CommandContactCardGet, ContactCardGetRefCommand{
invocations[i*2+0] = invocation(query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(ContactCardGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
Name: CommandContactCardQuery,
@@ -158,13 +154,13 @@ func (j *Client) CreateContactCard(accountId string, session *Session, ctx conte
logger = j.logger("CreateContactCard", session, logger)
cmd, err := j.request(session, logger, NS_CONTACTS,
invocation(CommandContactCardSet, ContactCardSetCommand{
invocation(ContactCardSetCommand{
AccountId: accountId,
Create: map[string]jscontact.ContactCard{
"c": create,
},
}, "0"),
invocation(CommandContactCardGet, ContactCardGetCommand{
invocation(ContactCardGetCommand{
AccountId: accountId,
Ids: []string{"#c"},
}, "1"),
@@ -187,7 +183,7 @@ func (j *Client) CreateContactCard(accountId string, session *Session, ctx conte
}
if created, ok := setResponse.Created["c"]; !ok || created == nil {
berr := fmt.Errorf("failed to find %s in %s response", string(ContactCardType), string(CommandContactCardSet))
berr := fmt.Errorf("failed to find %s in %s response", ContactCardType, string(CommandContactCardSet))
logger.Error().Err(berr)
return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}
@@ -199,7 +195,7 @@ func (j *Client) CreateContactCard(accountId string, session *Session, ctx conte
}
if len(getResponse.List) < 1 {
berr := fmt.Errorf("failed to find %s in %s response", string(ContactCardType), string(CommandContactCardSet))
berr := fmt.Errorf("failed to find %s in %s response", ContactCardType, string(CommandContactCardSet))
logger.Error().Err(berr)
return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}
@@ -212,7 +208,7 @@ func (j *Client) DeleteContactCard(accountId string, destroy []string, session *
logger = j.logger("DeleteContactCard", session, logger)
cmd, err := j.request(session, logger, NS_CONTACTS,
invocation(CommandContactCardSet, ContactCardSetCommand{
invocation(ContactCardSetCommand{
AccountId: accountId,
Destroy: destroy,
}, "0"),

View File

@@ -34,7 +34,7 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
if maxBodyValueBytes > 0 {
get.MaxBodyValueBytes = maxBodyValueBytes
}
invokeGet := invocation(CommandEmailGet, get, "1")
invokeGet := invocation(get, "1")
methodCalls := []Invocation{invokeGet}
if markAsSeen {
@@ -43,7 +43,7 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
updates[id] = EmailUpdate{EmailPropertyKeywords + "/" + JmapKeywordSeen: true}
}
mark := EmailSetCommand{AccountId: accountId, Update: updates}
methodCalls = []Invocation{invocation(CommandEmailSet, mark, "0"), invokeGet}
methodCalls = []Invocation{invocation(mark, "0"), invokeGet}
}
if withThreads {
threads := ThreadGetRefCommand{
@@ -54,7 +54,7 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
Path: "/list/*/" + EmailPropertyThreadId, //NOSONAR
},
}
methodCalls = append(methodCalls, invocation(CommandThreadGet, threads, "2"))
methodCalls = append(methodCalls, invocation(threads, "2"))
}
cmd, err := j.request(session, logger, NS_MAIL, methodCalls...)
@@ -95,7 +95,7 @@ func (j *Client) GetEmailBlobId(accountId string, session *Session, ctx context.
logger = j.logger("GetEmailBlobId", session, logger)
get := EmailGetCommand{AccountId: accountId, Ids: []string{id}, FetchAllBodyValues: false, Properties: []string{"blobId"}}
cmd, err := j.request(session, logger, NS_MAIL, invocation(CommandEmailGet, get, "0"))
cmd, err := j.request(session, logger, NS_MAIL, invocation(get, "0"))
if err != nil {
return "", "", "", "", err
}
@@ -143,8 +143,8 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
}
invocations := []Invocation{
invocation(CommandEmailQuery, query, "0"),
invocation(CommandEmailGet, get, "1"),
invocation(query, "0"),
invocation(get, "1"),
}
if withThreads {
@@ -156,7 +156,7 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
Path: "/list/*/" + EmailPropertyThreadId,
},
}
invocations = append(invocations, invocation(CommandThreadGet, threads, "2"))
invocations = append(invocations, invocation(threads, "2"))
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
@@ -237,9 +237,9 @@ func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context
}
cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandEmailChanges, changes, "0"),
invocation(CommandEmailGet, getCreated, "1"),
invocation(CommandEmailGet, getUpdated, "2"),
invocation(changes, "0"),
invocation(getCreated, "1"),
invocation(getUpdated, "2"),
)
if err != nil {
return EmailChanges{}, "", "", "", err
@@ -335,9 +335,9 @@ func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterEleme
},
}
invocations[i*3+0] = invocation(CommandEmailQuery, query, mcid(accountId, "0"))
invocations[i*3+1] = invocation(CommandEmailGet, mails, mcid(accountId, "1"))
invocations[i*3+2] = invocation(CommandSearchSnippetGet, snippet, mcid(accountId, "2"))
invocations[i*3+0] = invocation(query, mcid(accountId, "0"))
invocations[i*3+1] = invocation(mails, mcid(accountId, "1"))
invocations[i*3+2] = invocation(snippet, mcid(accountId, "2"))
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
@@ -440,8 +440,8 @@ func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, ses
MaxBodyValueBytes: maxBodyValueBytes,
}
invocations[i*2+0] = invocation(CommandEmailQuery, query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(CommandEmailGet, mails, mcid(accountId, "1"))
invocations[i*2+0] = invocation(query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(mails, mcid(accountId, "1"))
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
@@ -531,9 +531,9 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilter
FetchAllBodyValues: fetchBodies,
MaxBodyValueBytes: maxBodyValueBytes,
}
invocations[i*3+0] = invocation(CommandEmailQuery, query, mcid(accountId, "0"))
invocations[i*3+1] = invocation(CommandSearchSnippetGet, snippet, mcid(accountId, "1"))
invocations[i*3+2] = invocation(CommandEmailGet, mails, mcid(accountId, "2"))
invocations[i*3+0] = invocation(query, mcid(accountId, "0"))
invocations[i*3+1] = invocation(snippet, mcid(accountId, "1"))
invocations[i*3+2] = invocation(mails, mcid(accountId, "2"))
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
@@ -628,8 +628,8 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
}
cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandBlobUpload, upload, "0"),
invocation(CommandBlobGet, getHash, "1"),
invocation(upload, "0"),
invocation(getHash, "1"),
)
if err != nil {
return UploadedEmail{}, "", "", "", err
@@ -687,7 +687,7 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
}
cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandEmailSet, set, "0"),
invocation(set, "0"),
)
if err != nil {
return nil, "", "", "", err
@@ -713,7 +713,7 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
created, ok := setResponse.Created["c"]
if !ok {
berr := fmt.Errorf("failed to find %s in %s response", string(EmailType), string(CommandEmailSet))
berr := fmt.Errorf("failed to find %s in %s response", EmailType, string(CommandEmailSet))
logger.Error().Err(berr)
return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}
@@ -732,7 +732,7 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
// To delete mails, use the DeleteEmails function instead.
func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]*Email, SessionState, State, Language, Error) {
cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandEmailSet, EmailSetCommand{
invocation(EmailSetCommand{
AccountId: accountId,
Update: updates,
}, "0"),
@@ -759,7 +759,7 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate,
func (j *Client) DeleteEmails(accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandEmailSet, EmailSetCommand{
invocation(EmailSetCommand{
AccountId: accountId,
Destroy: destroy,
}, "0"),
@@ -841,8 +841,8 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
}
cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION,
invocation(CommandEmailSubmissionSet, set, "0"),
invocation(CommandEmailSubmissionGet, get, "1"),
invocation(set, "0"),
invocation(get, "1"),
)
if err != nil {
return EmailSubmission{}, "", "", "", err
@@ -901,7 +901,7 @@ type emailSubmissionResult struct {
func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]EmailSubmission, []string, SessionState, State, Language, Error) {
logger = j.logger("GetEmailSubmissionStatus", session, logger)
cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION, invocation(CommandEmailSubmissionGet, EmailSubmissionGetCommand{
cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION, invocation(EmailSubmissionGetCommand{
AccountId: accountId,
Ids: submissionIds,
}, "0"))
@@ -931,11 +931,11 @@ func (j *Client) EmailsInThread(accountId string, threadId string, session *Sess
})
cmd, err := j.request(session, logger, NS_MAIL,
invocation(CommandThreadGet, ThreadGetCommand{
invocation(ThreadGetCommand{
AccountId: accountId,
Ids: []string{threadId},
}, "0"),
invocation(CommandEmailGet, EmailGetRefCommand{
invocation(EmailGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
ResultOf: "0",
@@ -1007,9 +1007,9 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
if limit > 0 {
get.Limit = &limit
}
invocations[i*factor+0] = invocation(CommandEmailQuery, get, mcid(accountId, "0"))
invocations[i*factor+0] = invocation(get, mcid(accountId, "0"))
invocations[i*factor+1] = invocation(CommandEmailGet, EmailGetRefCommand{
invocations[i*factor+1] = invocation(EmailGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
Name: CommandEmailQuery,
@@ -1019,7 +1019,7 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
Properties: EmailSummaryProperties,
}, mcid(accountId, "1"))
if withThreads {
invocations[i*factor+2] = invocation(CommandThreadGet, ThreadGetRefCommand{
invocations[i*factor+2] = invocation(ThreadGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
Name: CommandEmailGet,
@@ -1085,11 +1085,11 @@ type EmailSubmissionChanges struct {
// @api:tags email,changes
func (j *Client) GetEmailSubmissionChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (EmailSubmissionChanges, SessionState, State, Language, Error) {
return changesTemplate(j, "GetEmailSubmissionChanges", NS_MAIL_SUBMISSION,
CommandEmailSubmissionChanges, CommandEmailSubmissionGet,
return changes(j, "GetEmailSubmissionChanges", NS_MAIL_SUBMISSION,
func() EmailSubmissionChangesCommand {
return EmailSubmissionChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
EmailSubmissionChangesResponse{},
func(path string, rof string) EmailSubmissionGetRefCommand {
return EmailSubmissionGetRefCommand{
AccountId: accountId,
@@ -1100,9 +1100,6 @@ func (j *Client) GetEmailSubmissionChanges(accountId string, session *Session, c
},
}
},
func(resp EmailSubmissionChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp EmailSubmissionGetResponse) []EmailSubmission { return resp.List },
func(oldState, newState State, hasMoreChanges bool, created, updated []EmailSubmission, destroyed []string) EmailSubmissionChanges {
return EmailSubmissionChanges{
@@ -1114,7 +1111,6 @@ func (j *Client) GetEmailSubmissionChanges(accountId string, session *Session, c
Destroyed: destroyed,
}
},
func(resp EmailSubmissionGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}

View File

@@ -11,35 +11,35 @@ import (
var NS_IDENTITY = ns(JmapMail)
func (j *Client) GetAllIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) ([]Identity, SessionState, State, Language, Error) {
return getTemplate(j, "GetAllIdentities", NS_IDENTITY, CommandIdentityGet,
return get(j, "GetAllIdentities", NS_IDENTITY,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
},
IdentityGetResponse{},
func(resp IdentityGetResponse) []Identity { return resp.List },
func(resp IdentityGetResponse) State { return resp.State },
accountId, session, ctx, logger, acceptLanguage, []string{},
)
}
func (j *Client) GetIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identityIds []string) ([]Identity, SessionState, State, Language, Error) {
return getTemplate(j, "GetIdentities", NS_IDENTITY, CommandIdentityGet,
return get(j, "GetIdentities", NS_IDENTITY,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId, Ids: ids}
},
IdentityGetResponse{},
func(resp IdentityGetResponse) []Identity { return resp.List },
func(resp IdentityGetResponse) State { return resp.State },
accountId, session, ctx, logger, acceptLanguage, identityIds,
)
}
func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Identity, SessionState, State, Language, Error) {
return getTemplateN(j, "GetIdentitiesForAllAccounts", NS_IDENTITY, CommandIdentityGet,
return getN(j, "GetIdentitiesForAllAccounts", NS_IDENTITY,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
},
IdentityGetResponse{},
func(resp IdentityGetResponse) []Identity { return resp.List },
identity1,
func(resp IdentityGetResponse) State { return resp.State },
accountIds, session, ctx, logger, acceptLanguage, []string{},
)
}
@@ -56,9 +56,9 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
logger = j.logger("GetIdentitiesAndMailboxes", session, logger)
calls := make([]Invocation, len(uniqueAccountIds)+1)
calls[0] = invocation(CommandMailboxGet, MailboxGetCommand{AccountId: mailboxAccountId}, "0")
calls[0] = invocation(MailboxGetCommand{AccountId: mailboxAccountId}, "0")
for i, accountId := range uniqueAccountIds {
calls[i+1] = invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i+1))
calls[i+1] = invocation(IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i+1))
}
cmd, err := j.request(session, logger, NS_IDENTITY, calls...)
@@ -97,7 +97,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
func (j *Client) CreateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (Identity, SessionState, State, Language, Error) {
logger = j.logger("CreateIdentity", session, logger)
cmd, err := j.request(session, logger, NS_IDENTITY, invocation(CommandIdentitySet, IdentitySetCommand{
cmd, err := j.request(session, logger, NS_IDENTITY, invocation(IdentitySetCommand{
AccountId: accountId,
Create: map[string]Identity{
"c": identity,
@@ -123,7 +123,7 @@ func (j *Client) CreateIdentity(accountId string, session *Session, ctx context.
func (j *Client) UpdateIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identity Identity) (Identity, SessionState, State, Language, Error) {
logger = j.logger("UpdateIdentity", session, logger)
cmd, err := j.request(session, logger, NS_IDENTITY, invocation(CommandIdentitySet, IdentitySetCommand{
cmd, err := j.request(session, logger, NS_IDENTITY, invocation(IdentitySetCommand{
AccountId: accountId,
Update: map[string]PatchObject{
"c": identity.AsPatch(),
@@ -149,7 +149,7 @@ func (j *Client) UpdateIdentity(accountId string, session *Session, ctx context.
func (j *Client) DeleteIdentity(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) ([]string, SessionState, State, Language, Error) {
logger = j.logger("DeleteIdentity", session, logger)
cmd, err := j.request(session, logger, NS_IDENTITY, invocation(CommandIdentitySet, IdentitySetCommand{
cmd, err := j.request(session, logger, NS_IDENTITY, invocation(IdentitySetCommand{
AccountId: accountId,
Destroy: ids,
}, "0"))
@@ -184,11 +184,11 @@ type IdentityChanges struct {
// @api:tags email,changes
func (j *Client) GetIdentityChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (IdentityChanges, SessionState, State, Language, Error) {
return changesTemplate(j, "GetIdentityChanges", NS_IDENTITY,
CommandIdentityChanges, CommandIdentityGet,
return changes(j, "GetIdentityChanges", NS_IDENTITY,
func() IdentityChangesCommand {
return IdentityChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
IdentityChangesResponse{},
func(path string, rof string) IdentityGetRefCommand {
return IdentityGetRefCommand{
AccountId: accountId,
@@ -199,9 +199,6 @@ func (j *Client) GetIdentityChanges(accountId string, session *Session, ctx cont
},
}
},
func(resp IdentityChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp IdentityGetResponse) []Identity { return resp.List },
func(oldState, newState State, hasMoreChanges bool, created, updated []Identity, destroyed []string) IdentityChanges {
return IdentityChanges{
@@ -213,7 +210,6 @@ func (j *Client) GetIdentityChanges(accountId string, session *Session, ctx cont
Destroyed: destroyed,
}
},
func(resp IdentityGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}

View File

@@ -17,29 +17,29 @@ type MailboxesResponse struct {
}
func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (MailboxesResponse, SessionState, State, Language, Error) {
return getTemplate(j, "GetMailbox", NS_MAILBOX, CommandCalendarGet,
return get(j, "GetMailbox", NS_MAILBOX,
func(accountId string, ids []string) MailboxGetCommand {
return MailboxGetCommand{AccountId: accountId, Ids: ids}
},
MailboxGetResponse{},
func(resp MailboxGetResponse) MailboxesResponse {
return MailboxesResponse{
Mailboxes: resp.List,
NotFound: resp.NotFound,
}
},
func(resp MailboxGetResponse) State { return resp.State },
accountId, session, ctx, logger, acceptLanguage, ids,
)
}
func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Mailbox, SessionState, State, Language, Error) {
return getTemplateN(j, "GetAllMailboxes", NS_MAILBOX, CommandCalendarGet,
return getN(j, "GetAllMailboxes", NS_MAILBOX,
func(accountId string, ids []string) MailboxGetCommand {
return MailboxGetCommand{AccountId: accountId}
},
MailboxGetResponse{},
func(resp MailboxGetResponse) []Mailbox { return resp.List },
identity1,
func(resp MailboxGetResponse) State { return resp.State },
accountIds, session, ctx, logger, acceptLanguage, []string{},
)
}
@@ -51,8 +51,8 @@ func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx cont
invocations := make([]Invocation, len(uniqueAccountIds)*2)
for i, accountId := range uniqueAccountIds {
invocations[i*2+0] = invocation(CommandMailboxQuery, MailboxQueryCommand{AccountId: accountId, Filter: filter}, mcid(accountId, "0"))
invocations[i*2+1] = invocation(CommandMailboxGet, MailboxGetRefCommand{
invocations[i*2+0] = invocation(MailboxQueryCommand{AccountId: accountId, Filter: filter}, mcid(accountId, "0"))
invocations[i*2+1] = invocation(MailboxGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
Name: CommandMailboxQuery,
@@ -91,7 +91,7 @@ func (j *Client) SearchMailboxIdsPerRole(accountIds []string, session *Session,
invocations := make([]Invocation, len(uniqueAccountIds)*len(roles))
for i, accountId := range uniqueAccountIds {
for j, role := range roles {
invocations[i*len(roles)+j] = invocation(CommandMailboxQuery, MailboxQueryCommand{AccountId: accountId, Filter: MailboxFilterCondition{Role: role}}, mcid(accountId, role))
invocations[i*len(roles)+j] = invocation(MailboxQueryCommand{AccountId: accountId, Filter: MailboxFilterCondition{Role: role}}, mcid(accountId, role))
}
}
cmd, err := j.request(session, logger, NS_MAILBOX, invocations...)
@@ -146,11 +146,11 @@ 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, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, maxChanges uint) (MailboxChanges, SessionState, State, Language, Error) {
return changesTemplate(j, "GetMailboxChanges", NS_MAILBOX,
CommandMailboxChanges, CommandMailboxGet,
return changes(j, "GetMailboxChanges", NS_MAILBOX,
func() MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
MailboxChangesResponse{},
func(path string, rof string) MailboxGetRefCommand {
return MailboxGetRefCommand{
AccountId: accountId,
@@ -161,12 +161,8 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
},
}
},
func(resp MailboxChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp MailboxGetResponse) []Mailbox { return resp.List },
newMailboxChanges,
func(resp MailboxGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}
@@ -174,17 +170,15 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
// Retrieve Mailbox changes of multiple Accounts.
// @api:tags email,changes
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceStateMap map[string]State, maxChanges uint) (map[string]MailboxChanges, SessionState, State, Language, Error) { //NOSONAR
return changesTemplateN(j, "GetMailboxChangesForMultipleAccounts", NS_MAILBOX,
accountIds, sinceStateMap, CommandMailboxChanges, CommandMailboxGet,
return changesN(j, "GetMailboxChangesForMultipleAccounts", NS_MAILBOX,
accountIds, sinceStateMap,
func(accountId string, state State) MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: state, MaxChanges: posUIntPtr(maxChanges)}
},
MailboxChangesResponse{},
func(accountId string, path string, ref string) MailboxGetRefCommand {
return MailboxGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: CommandMailboxChanges, Path: path, ResultOf: ref}}
},
func(resp MailboxChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp MailboxGetResponse) []Mailbox { return resp.List },
newMailboxChanges,
identity1,
@@ -206,13 +200,13 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session
invocations := make([]Invocation, n*2)
for i, accountId := range uniqueAccountIds {
invocations[i*2+0] = invocation(CommandMailboxQuery, MailboxQueryCommand{
invocations[i*2+0] = invocation(MailboxQueryCommand{
AccountId: accountId,
Filter: MailboxFilterCondition{
HasAnyRole: &t,
},
}, mcid(accountId, "0"))
invocations[i*2+1] = invocation(CommandMailboxGet, MailboxGetRefCommand{
invocations[i*2+1] = invocation(MailboxGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
ResultOf: mcid(accountId, "0"),
@@ -259,7 +253,7 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *S
invocations := make([]Invocation, n*2)
for i, accountId := range uniqueAccountIds {
invocations[i*2+0] = invocation(CommandMailboxQuery, MailboxQueryCommand{
invocations[i*2+0] = invocation(MailboxQueryCommand{
AccountId: accountId,
Filter: MailboxFilterCondition{
Role: JmapMailboxRoleInbox,
@@ -299,7 +293,7 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *S
func (j *Client) UpdateMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, ifInState string, update MailboxChange) (Mailbox, SessionState, State, Language, Error) { //NOSONAR
logger = j.logger("UpdateMailbox", session, logger)
cmd, err := j.request(session, logger, NS_MAILBOX, invocation(CommandMailboxSet, MailboxSetCommand{
cmd, err := j.request(session, logger, NS_MAILBOX, invocation(MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Update: map[string]PatchObject{
@@ -327,7 +321,7 @@ func (j *Client) UpdateMailbox(accountId string, session *Session, ctx context.C
func (j *Client) CreateMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ifInState string, create MailboxChange) (Mailbox, SessionState, State, Language, Error) {
logger = j.logger("CreateMailbox", session, logger)
cmd, err := j.request(session, logger, NS_MAILBOX, invocation(CommandMailboxSet, MailboxSetCommand{
cmd, err := j.request(session, logger, NS_MAILBOX, invocation(MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Create: map[string]MailboxChange{
@@ -359,7 +353,7 @@ func (j *Client) CreateMailbox(accountId string, session *Session, ctx context.C
func (j *Client) DeleteMailboxes(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ifInState string, mailboxIds []string) ([]string, SessionState, State, Language, Error) {
logger = j.logger("DeleteMailbox", session, logger)
cmd, err := j.request(session, logger, NS_MAILBOX, invocation(CommandMailboxSet, MailboxSetCommand{
cmd, err := j.request(session, logger, NS_MAILBOX, invocation(MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Destroy: mailboxIds,

View File

@@ -61,31 +61,31 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
methodCalls := []Invocation{}
if len(mailboxIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandMailboxGet, MailboxGetCommand{AccountId: accountId, Ids: mailboxIds}, "mailboxes"))
methodCalls = append(methodCalls, invocation(MailboxGetCommand{AccountId: accountId, Ids: mailboxIds}, "mailboxes"))
}
if len(emailIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandEmailGet, EmailGetCommand{AccountId: accountId, Ids: emailIds}, "emails"))
methodCalls = append(methodCalls, invocation(EmailGetCommand{AccountId: accountId, Ids: emailIds}, "emails"))
}
if len(addressbookIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandAddressBookGet, AddressBookGetCommand{AccountId: accountId, Ids: addressbookIds}, "addressbooks"))
methodCalls = append(methodCalls, invocation(AddressBookGetCommand{AccountId: accountId, Ids: addressbookIds}, "addressbooks"))
}
if len(contactIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandContactCardGet, ContactCardGetCommand{AccountId: accountId, Ids: contactIds}, "contacts"))
methodCalls = append(methodCalls, invocation(ContactCardGetCommand{AccountId: accountId, Ids: contactIds}, "contacts"))
}
if len(calendarIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandCalendarGet, CalendarGetCommand{AccountId: accountId, Ids: calendarIds}, "calendars"))
methodCalls = append(methodCalls, invocation(CalendarGetCommand{AccountId: accountId, Ids: calendarIds}, "calendars"))
}
if len(eventIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandCalendarEventGet, CalendarEventGetCommand{AccountId: accountId, Ids: eventIds}, "events"))
methodCalls = append(methodCalls, invocation(CalendarEventGetCommand{AccountId: accountId, Ids: eventIds}, "events"))
}
if len(quotaIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandQuotaGet, QuotaGetCommand{AccountId: accountId, Ids: quotaIds}, "quotas"))
methodCalls = append(methodCalls, invocation(QuotaGetCommand{AccountId: accountId, Ids: quotaIds}, "quotas"))
}
if len(identityIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId, Ids: identityIds}, "identities"))
methodCalls = append(methodCalls, invocation(IdentityGetCommand{AccountId: accountId, Ids: identityIds}, "identities"))
}
if len(emailSubmissionIds) > 0 {
methodCalls = append(methodCalls, invocation(CommandEmailSubmissionGet, EmailSubmissionGetCommand{AccountId: accountId, Ids: emailSubmissionIds}, "emailSubmissionIds"))
methodCalls = append(methodCalls, invocation(EmailSubmissionGetCommand{AccountId: accountId, Ids: emailSubmissionIds}, "emailSubmissionIds"))
}
cmd, err := j.request(session, logger, NS_OBJECTS, methodCalls...)

View File

@@ -17,7 +17,7 @@ func (j *Client) GetPrincipals(accountId string, session *Session, ctx context.C
logger = j.logger("GetPrincipals", session, logger)
cmd, err := j.request(session, logger, NS_PRINCIPALS,
invocation(CommandPrincipalGet, PrincipalGetCommand{AccountId: accountId, Ids: ids}, "0"),
invocation(PrincipalGetCommand{AccountId: accountId, Ids: ids}, "0"),
)
if err != nil {
return PrincipalsResponse{}, "", "", "", err

View File

@@ -9,13 +9,13 @@ import (
var NS_QUOTA = ns(JmapQuota)
func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]QuotaGetResponse, SessionState, State, Language, Error) {
return getTemplateN(j, "GetQuotas", NS_QUOTA, CommandQuotaGet,
return getN(j, "GetQuotas", NS_QUOTA,
func(accountId string, ids []string) QuotaGetCommand {
return QuotaGetCommand{AccountId: accountId}
},
QuotaGetResponse{},
identity1,
identity1,
func(resp QuotaGetResponse) State { return resp.State },
accountIds, session, ctx, logger, acceptLanguage, []string{},
)
}
@@ -33,11 +33,11 @@ type QuotaChanges struct {
// @api:tags quota,changes
func (j *Client) GetQuotaChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (QuotaChanges, SessionState, State, Language, Error) {
return changesTemplate(j, "GetQuotaChanges", NS_QUOTA,
CommandQuotaChanges, CommandQuotaGet,
return changes(j, "GetQuotaChanges", NS_QUOTA,
func() QuotaChangesCommand {
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
QuotaChangesResponse{},
func(path string, rof string) QuotaGetRefCommand {
return QuotaGetRefCommand{
AccountId: accountId,
@@ -48,9 +48,6 @@ func (j *Client) GetQuotaChanges(accountId string, session *Session, ctx context
},
}
},
func(resp QuotaChangesResponse) (State, State, bool, []string) {
return resp.OldState, resp.NewState, resp.HasMoreChanges, resp.Destroyed
},
func(resp QuotaGetResponse) []Quota { return resp.List },
func(oldState, newState State, hasMoreChanges bool, created, updated []Quota, destroyed []string) QuotaChanges {
return QuotaChanges{
@@ -62,18 +59,17 @@ func (j *Client) GetQuotaChanges(accountId string, session *Session, ctx context
Destroyed: destroyed,
}
},
func(resp QuotaGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}
func (j *Client) GetQuotaUsageChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (QuotaChanges, SessionState, State, Language, Error) {
return updatedTemplate(j, "GetQuotaUsageChanges", NS_QUOTA,
CommandQuotaChanges, CommandQuotaGet,
return updates(j, "GetQuotaUsageChanges", NS_QUOTA,
func() QuotaChangesCommand {
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
},
QuotaChangesResponse{},
func(path string, rof string) QuotaGetRefCommand {
return QuotaGetRefCommand{
AccountId: accountId,
@@ -89,9 +85,6 @@ func (j *Client) GetQuotaUsageChanges(accountId string, session *Session, ctx co
},
}
},
func(resp QuotaChangesResponse) (State, State, bool) {
return resp.OldState, resp.NewState, resp.HasMoreChanges
},
func(resp QuotaGetResponse) []Quota { return resp.List },
func(oldState, newState State, hasMoreChanges bool, updated []Quota) QuotaChanges {
return QuotaChanges{
@@ -101,7 +94,6 @@ func (j *Client) GetQuotaUsageChanges(accountId string, session *Session, ctx co
Updated: updated,
}
},
func(resp QuotaGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
)
}

View File

@@ -15,12 +15,12 @@ const (
)
func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseGetResponse, SessionState, State, Language, Error) {
return getTemplate(j, "GetVacationResponse", NS_VACATION, CommandVacationResponseGet,
return get(j, "GetVacationResponse", NS_VACATION,
func(accountId string, ids []string) VacationResponseGetCommand {
return VacationResponseGetCommand{AccountId: accountId}
},
VacationResponseGetResponse{},
identity1,
func(resp VacationResponseGetResponse) State { return resp.State },
accountId, session, ctx, logger, acceptLanguage, []string{},
)
}
@@ -53,7 +53,7 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
logger = j.logger("SetVacationResponse", session, logger)
cmd, err := j.request(session, logger, NS_VACATION,
invocation(CommandVacationResponseSet, VacationResponseSetCommand{
invocation(VacationResponseSetCommand{
AccountId: accountId,
Create: map[string]VacationResponse{
vacationResponseId: {
@@ -68,7 +68,7 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
}, "0"),
// chain a second request to get the current complete VacationResponse object
// after performing the changes, as that makes for a better API
invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: accountId}, "1"),
invocation(VacationResponseGetCommand{AccountId: accountId}, "1"),
)
if err != nil {
return VacationResponse{}, "", "", "", err
@@ -94,7 +94,7 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
}
if len(getResponse.List) != 1 {
berr := fmt.Errorf("failed to find %s in %s response", string(VacationResponseType), string(CommandVacationResponseGet))
berr := fmt.Errorf("failed to find %s in %s response", VacationResponseType, string(CommandVacationResponseGet))
logger.Error().Msg(berr.Error())
return VacationResponse{}, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}

68
pkg/jmap/export_test.go Normal file
View File

@@ -0,0 +1,68 @@
package jmap
// This is for functions that are only supposed to be visible in tests.
import (
"fmt"
"go/ast"
"go/token"
"log"
"strings"
"golang.org/x/tools/go/packages"
)
func parseConsts(pkgID string, suffix string, typeName string) (map[string]string, error) { //NOSONAR
result := map[string]string{}
{
cfg := &packages.Config{
Mode: packages.LoadSyntax,
Dir: ".",
Tests: false,
}
pkgs, err := packages.Load(cfg, ".")
if err != nil {
log.Fatal(err)
}
if packages.PrintErrors(pkgs) > 0 {
return nil, fmt.Errorf("failed to parse the package '%s'", pkgID)
}
for _, p := range pkgs {
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, `"`)
}
}
}
}
}
}
}
}
}
}
}
}
}
}
return result, nil
}

View File

@@ -3,6 +3,7 @@ package jmap
import (
"math/rand"
"regexp"
"slices"
"testing"
"github.com/stretchr/testify/require"
@@ -47,6 +48,7 @@ func TestAddressBooks(t *testing.T) {
user := pickUser()
session := s.Session(user.name)
// we first need to retrieve the list of all the Principals in order to be able to use and test AddressBook sharing
principalIds := []string{}
{
principals, _, _, _, err := s.client.GetPrincipals(session.PrimaryAccounts.Mail, session, s.ctx, s.logger, "", []string{})
@@ -55,20 +57,87 @@ func TestAddressBooks(t *testing.T) {
principalIds = structs.Map(principals.Principals, func(p Principal) string { return p.Id })
}
accountId := session.PrimaryAccounts.Contacts
ss := SessionState("")
as := EmptyState
// we need to fetch the ID of the default AddressBook that automatically exists for each user, in order to exclude it
// from the tests below
defaultAddressBookId := ""
{
resp, sessionState, state, _, err := s.client.GetAddressbooks(accountId, session, s.ctx, s.logger, "", []string{})
require.NoError(err)
require.Empty(resp.NotFound)
require.Len(resp.AddressBooks, 1) // the personal addressbook that exists by default
defaultAddressBookId = resp.AddressBooks[0].Id
ss = sessionState
as = state
}
// we are going to create a random amount of AddressBook objects
num := uint(5 + rand.Intn(30))
{
accountId := ""
a, boxes, abooks, err := s.fillAddressBook(t, num, session, user, principalIds)
boxes, abooks, sessionState, state, err := s.fillAddressBook(t, accountId, num, session, user, principalIds)
require.NoError(err)
require.NotEmpty(a)
require.Len(abooks, int(num))
accountId = a
ss = sessionState
as = state
{
// lets retrieve all the existing AddressBook objects by passing an empty ID slice
resp, sessionState, state, _, err := s.client.GetAddressbooks(accountId, session, s.ctx, s.logger, "", []string{})
require.NoError(err)
require.Empty(resp.NotFound)
// lets skip the default AddressBook since we did not create that one
found := structs.Filter(resp.AddressBooks, func(a AddressBook) bool { return a.Id != defaultAddressBookId })
require.Len(found, int(num))
m := structs.Index(found, func(a AddressBook) string { return a.Id })
require.Len(m, int(num))
require.Equal(sessionState, ss)
require.Equal(state, as)
for _, a := range abooks {
require.Contains(m, a.Id)
found, ok := m[a.Id]
require.True(ok)
require.Equal(a, found)
}
ss = sessionState
as = state
}
// lets retrieve every AddressBook object we created by its ID
for _, a := range abooks {
resp, sessionState, state, _, err := s.client.GetAddressbooks(accountId, session, s.ctx, s.logger, "", []string{a.Id})
require.NoError(err)
require.Empty(resp.NotFound)
require.Len(resp.AddressBooks, 1)
require.Equal(sessionState, ss)
require.Equal(state, as)
require.Equal(resp.AddressBooks[0], a)
}
// lets modify each AddressBook
for _, a := range abooks {
changed, sessionState, state, _, err := s.client.UpdateAddressBook(accountId, session, s.ctx, s.logger, "", a.Id, AddressBookChange{Description: strPtr(a.Description + " (changed)"), IsSubscribed: boolPtr(!a.IsSubscribed)})
require.NoError(err)
require.NotEqual(a, changed)
require.Equal(sessionState, ss)
require.NotEqual(state, as)
require.Equal(a.Description+" (changed)", changed.Description)
require.Equal(!a.IsSubscribed, changed.IsSubscribed)
}
// now lets delete each AddressBook that we created, all at once
ids := structs.Map(abooks, func(a AddressBook) string { return a.Id })
{
errMap, _, _, _, err := s.client.DeleteAddressBook(accountId, ids, session, s.ctx, s.logger, "")
errMap, sessionState, state, _, err := s.client.DeleteAddressBook(accountId, ids, session, s.ctx, s.logger, "")
require.NoError(err)
require.Empty(errMap)
require.Equal(sessionState, ss)
require.NotEqual(state, as)
}
allBoxesAreTicked(t, boxes)
@@ -146,20 +215,20 @@ type ContactsBoxes struct {
var streetNumberRegex = regexp.MustCompile(`^(\d+)\s+(.+)$`)
func (s *StalwartTest) fillAddressBook(
func (s *StalwartTest) fillAddressBook( //NOSONAR
t *testing.T,
accountId string,
count uint,
session *Session,
_ User,
principalIds []string,
) (string, AddressBooksBoxes, []AddressBook, error) {
) (AddressBooksBoxes, []AddressBook, SessionState, State, error) {
require := require.New(t)
accountId := session.PrimaryAccounts.Contacts
require.NotEmpty(accountId, "no primary account for contacts in session")
boxes := AddressBooksBoxes{}
created := []AddressBook{}
ss := SessionState("")
as := EmptyState
printer := func(s string) { log.Println(s) }
@@ -201,16 +270,24 @@ func (s *StalwartTest) fillAddressBook(
a, sessionState, state, _, err := s.client.CreateAddressBook(accountId, session, s.ctx, s.logger, "", abook)
if err != nil {
return accountId, boxes, created, err
return boxes, created, ss, as, err
}
require.NotEmpty(sessionState)
require.NotEmpty(state)
if ss != SessionState("") {
require.Equal(ss, sessionState)
}
if as != EmptyState {
require.NotEqual(as, state)
}
require.NotNil(a)
created = append(created, *a)
ss = sessionState
as = state
printer(fmt.Sprintf("📔 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, a.Id))
}
return accountId, boxes, created, nil
return boxes, created, ss, as, nil
}
func (s *StalwartTest) fillContacts( //NOSONAR
@@ -233,7 +310,7 @@ func (s *StalwartTest) fillContacts( //NOSONAR
addressbookId := ""
{
addressBooksById, err := c.objectsById(accountId, AddressBookType, JmapContacts)
addressBooksById, err := c.objectsById(accountId, AddressBookType)
require.NoError(err)
for id, addressbook := range addressBooksById {
@@ -242,8 +319,15 @@ func (s *StalwartTest) fillContacts( //NOSONAR
addressbookId = id
break
}
} else {
printer(fmt.Sprintf("abook without isDefault: %v", addressbook))
}
}
if addressbookId == "" {
ids := structs.Keys(addressBooksById)
slices.Sort(ids)
addressbookId = ids[0]
}
}
require.NotEmpty(addressbookId)
@@ -606,5 +690,5 @@ func (s *StalwartTest) fillContacts( //NOSONAR
}
func (s *StalwartTest) CreateContact(j *TestJmapClient, accountId string, contact map[string]any) (string, error) {
return j.create1(accountId, ContactCardType, JmapContacts, contact)
return j.create1(accountId, ContactCardType, contact)
}

View File

@@ -105,7 +105,7 @@ func (s *StalwartTest) fillEvents( //NOSONAR
calendarId := ""
{
calendarsById, err := c.objectsById(accountId, CalendarType, JmapCalendars)
calendarsById, err := c.objectsById(accountId, CalendarType)
require.NoError(err)
for id, calendar := range calendarsById {
@@ -359,7 +359,7 @@ func (s *StalwartTest) fillEvents( //NOSONAR
}
func (s *StalwartTest) CreateEvent(j *TestJmapClient, accountId string, event map[string]any) (string, error) {
return j.create1(accountId, CalendarEventType, JmapCalendars, event)
return j.create1(accountId, CalendarEventType, event)
}
var rooms = []jscalendar.Location{

View File

@@ -721,12 +721,12 @@ func (j *TestJmapClient) create(id string, objectType ObjectType, body map[strin
}).command(body)
}
func (j *TestJmapClient) create1(accountId string, objectType ObjectType, ns JmapNamespace, obj map[string]any) (string, error) {
func (j *TestJmapClient) create1(accountId string, objectType ObjectType, obj map[string]any) (string, error) {
body := map[string]any{
"using": []string{string(JmapCore), string(ns)},
"using": structs.Map(objectType.Namespaces, func(n JmapNamespace) string { return string(n) }),
"methodCalls": []any{
[]any{
objectType + "/set",
objectType.Name + "/set",
map[string]any{
"accountId": accountId,
"create": map[string]any{
@@ -740,14 +740,14 @@ func (j *TestJmapClient) create1(accountId string, objectType ObjectType, ns Jma
return j.create("c", objectType, body)
}
func (j *TestJmapClient) objectsById(accountId string, objectType ObjectType, scope JmapNamespace) (map[string]map[string]any, error) {
func (j *TestJmapClient) objectsById(accountId string, objectType ObjectType) (map[string]map[string]any, error) {
m := map[string]map[string]any{}
{
body := map[string]any{
"using": []string{string(JmapCore), string(scope)},
"using": structs.Map(objectType.Namespaces, func(n JmapNamespace) string { return string(n) }),
"methodCalls": []any{
[]any{
objectType + "/get",
objectType.Name + "/get",
map[string]any{
"accountId": accountId,
},

View File

@@ -32,18 +32,18 @@ func (l *testWsPushListener) OnNotification(username string, pushState StateChan
l.logger.Debug().Msgf("received %T: %v", pushState, pushState)
if changed, ok := pushState.Changed[l.mailAccountId]; ok {
l.m.Lock()
if st, ok := changed[EmailType]; ok {
if st, ok := changed[EmailName]; ok {
l.emailStates = append(l.emailStates, st)
}
if st, ok := changed[ThreadType]; ok {
if st, ok := changed[ThreadName]; ok {
l.threadStates = append(l.threadStates, st)
}
if st, ok := changed[MailboxType]; ok {
if st, ok := changed[MailboxName]; ok {
l.mailboxStates = append(l.mailboxStates, st)
}
l.m.Unlock()
unsupportedKeys := structs.Filter(structs.Keys(changed), func(o ObjectType) bool { return o != EmailType && o != ThreadType && o != MailboxType })
unsupportedKeys := structs.Filter(structs.Keys(changed), func(o ObjectTypeName) bool { return o != EmailName && o != ThreadName && o != MailboxName })
assert.Empty(l.t, unsupportedKeys)
}
unsupportedAccounts := structs.Filter(structs.Keys(pushState.Changed), func(s string) bool { return s != l.mailAccountId })

View File

File diff suppressed because it is too large Load Diff

19
pkg/jmap/model_test.go Normal file
View File

@@ -0,0 +1,19 @@
package jmap
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestObjectNames(t *testing.T) { //NOSONAR
require := require.New(t)
objectTypeNames, err := parseConsts("github.com/opencloud-eu/opencloud/pkg/jmap", "Name", "ObjectTypeName")
require.NoError(err)
for n, v := range objectTypeNames {
require.True(strings.HasSuffix(n, "Name"))
prefix := n[0 : len(n)-len("Name")]
require.Equal(prefix, v)
}
}

View File

@@ -9,18 +9,19 @@ import (
"github.com/rs/zerolog"
)
func getTemplate[GETREQ any, GETRESP any, RESP any]( //NOSONAR
func get[GETREQ GetCommand, GETRESP GetResponse, RESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace,
getCommand Command, getCommandFactory func(string, []string) GETREQ,
getCommandFactory func(string, []string) GETREQ,
_ GETRESP,
mapper func(GETRESP) RESP,
stateMapper func(GETRESP) State,
accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
var zero RESP
get := getCommandFactory(accountId, ids)
cmd, err := client.request(session, logger, using,
invocation(getCommand, getCommandFactory(accountId, ids), "0"),
invocation(get, "0"),
)
if err != nil {
return zero, "", "", "", err
@@ -28,21 +29,21 @@ func getTemplate[GETREQ any, GETRESP any, RESP any]( //NOSONAR
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
var response GETRESP
err = retrieveResponseMatchParameters(logger, body, getCommand, "0", &response)
err = retrieveGet(logger, body, get, "0", &response)
if err != nil {
return zero, "", err
}
return mapper(response), stateMapper(response), nil
return mapper(response), response.GetState(), nil
})
}
func getTemplateN[GETREQ any, GETRESP any, ITEM any, RESP any]( //NOSONAR
func getN[GETREQ GetCommand, GETRESP GetResponse, ITEM any, RESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace,
getCommand Command, getCommandFactory func(string, []string) GETREQ,
getCommandFactory func(string, []string) GETREQ,
_ GETRESP,
itemMapper func(GETRESP) ITEM,
respMapper func(map[string]ITEM) RESP,
stateMapper func(GETRESP) State,
accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
@@ -51,8 +52,11 @@ func getTemplateN[GETREQ any, GETRESP any, ITEM any, RESP any]( //NOSONAR
uniqueAccountIds := structs.Uniq(accountIds)
invocations := make([]Invocation, len(uniqueAccountIds))
var c Command
for i, accountId := range uniqueAccountIds {
invocations[i] = invocation(getCommand, getCommandFactory(accountId, ids), mcid(accountId, "0"))
get := getCommandFactory(accountId, ids)
c = get.GetCommand()
invocations[i] = invocation(get, mcid(accountId, "0"))
}
cmd, err := client.request(session, logger, using, invocations...)
@@ -65,33 +69,32 @@ func getTemplateN[GETREQ any, GETRESP any, ITEM any, RESP any]( //NOSONAR
responses := map[string]GETRESP{}
for _, accountId := range uniqueAccountIds {
var resp GETRESP
err = retrieveResponseMatchParameters(logger, body, getCommand, mcid(accountId, "0"), &resp)
err = retrieveResponseMatchParameters(logger, body, c, mcid(accountId, "0"), &resp)
if err != nil {
return zero, "", err
}
responses[accountId] = resp
result[accountId] = itemMapper(resp)
}
return respMapper(result), squashStateFunc(responses, stateMapper), nil
return respMapper(result), squashStateFunc(responses, func(r GETRESP) State { return r.GetState() }), nil
})
}
func createTemplate[T any, C any, SETREQ any, GETREQ any, SETRESP any, GETRESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace, t ObjectType,
setCommand Command, getCommand Command,
func create[T any, C any, SETREQ SetCommand, GETREQ GetCommand, SETRESP SetResponse, GETRESP GetResponse]( //NOSONAR
client *Client, name string, using []JmapNamespace,
setCommandFactory func(string, map[string]C) SETREQ,
getCommandFactory func(string, string) GETREQ,
createdMapper func(SETRESP) map[string]*T,
notCreatedMapper func(SETRESP) map[string]SetError,
listMapper func(GETRESP) []T,
stateMapper func(SETRESP) State,
accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create C) (*T, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
createMap := map[string]C{"c": create}
get := getCommandFactory(accountId, "#c")
set := setCommandFactory(accountId, createMap)
cmd, err := client.request(session, logger, using,
invocation(setCommand, setCommandFactory(accountId, createMap), "0"),
invocation(getCommand, getCommandFactory(accountId, "#c"), "1"),
invocation(set, "0"),
invocation(get, "1"),
)
if err != nil {
return nil, "", "", "", err
@@ -99,27 +102,27 @@ func createTemplate[T any, C any, SETREQ any, GETREQ any, SETRESP any, GETRESP a
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*T, State, Error) {
var setResponse SETRESP
err = retrieveResponseMatchParameters(logger, body, setCommand, "0", &setResponse)
err = retrieveSet(logger, body, set, "0", &setResponse)
if err != nil {
return nil, "", err
}
notCreatedMap := notCreatedMapper(setResponse)
notCreatedMap := setResponse.GetNotCreated()
setErr, notok := notCreatedMap["c"]
if notok {
logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr)
return nil, "", setErrorError(setErr, t)
return nil, "", setErrorError(setErr, set.GetObjectType())
}
createdMap := createdMapper(setResponse)
if created, ok := createdMap["c"]; !ok || created == nil {
berr := fmt.Errorf("failed to find %s in %s response", string(t), string(setCommand))
berr := fmt.Errorf("failed to find %s in %s response", set.GetObjectType(), set.GetCommand())
logger.Error().Err(berr)
return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}
var getResponse GETRESP
err = retrieveResponseMatchParameters(logger, body, getCommand, "1", &getResponse)
err = retrieveGet(logger, body, get, "1", &getResponse)
if err != nil {
return nil, "", err
}
@@ -127,24 +130,23 @@ func createTemplate[T any, C any, SETREQ any, GETREQ any, SETRESP any, GETRESP a
list := listMapper(getResponse)
if len(list) < 1 {
berr := fmt.Errorf("failed to find %s in %s response", string(t), string(getCommand))
berr := fmt.Errorf("failed to find %s in %s response", get.GetObjectType(), get.GetCommand())
logger.Error().Err(berr)
return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}
return &list[0], stateMapper(setResponse), nil
return &list[0], setResponse.GetNewState(), nil
})
}
func deleteTemplate[REQ any, RESP any](client *Client, name string, using []JmapNamespace, //NOSONAR
c Command, commandFactory func(string, []string) REQ,
notDestroyedMapper func(RESP) map[string]SetError,
stateMapper func(RESP) State,
func destroy[REQ SetCommand, RESP SetResponse](client *Client, name string, using []JmapNamespace, //NOSONAR
setCommandFactory func(string, []string) REQ, _ RESP,
accountId string, destroy []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
set := setCommandFactory(accountId, destroy)
cmd, err := client.request(session, logger, using,
invocation(c, commandFactory(accountId, destroy), "0"),
invocation(set, "0"),
)
if err != nil {
return nil, "", "", "", err
@@ -152,23 +154,21 @@ func deleteTemplate[REQ any, RESP any](client *Client, name string, using []Jmap
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]SetError, State, Error) {
var setResponse RESP
err = retrieveResponseMatchParameters(logger, body, c, "0", &setResponse)
err = retrieveSet(logger, body, set, "0", &setResponse)
if err != nil {
return nil, "", err
}
return notDestroyedMapper(setResponse), stateMapper(setResponse), nil
return setResponse.GetNotDestroyed(), setResponse.GetNewState(), nil
})
}
func changesTemplate[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, ITEM any, RESP any]( //NOSONAR
func changes[CHANGESREQ ChangesCommand, GETREQ GetCommand, CHANGESRESP ChangesResponse, GETRESP GetResponse, ITEM any, RESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace,
changesCommand Command, getCommand Command,
changesCommandFactory func() CHANGESREQ,
_ CHANGESRESP,
getCommandFactory func(string, string) GETREQ,
changesMapper func(CHANGESRESP) (State, State, bool, []string),
getMapper func(GETRESP) []ITEM,
respMapper func(State, State, bool, []ITEM, []ITEM, []string) RESP,
stateMapper func(GETRESP) State,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
var zero RESP
@@ -176,10 +176,11 @@ func changesTemplate[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, I
changes := changesCommandFactory()
getCreated := getCommandFactory("/created", "0") //NOSONAR
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
cmd, err := client.request(session, logger, using,
invocation(changesCommand, changes, "0"),
invocation(getCommand, getCreated, "1"),
invocation(getCommand, getUpdated, "2"),
invocation(changes, "0"),
invocation(getCreated, "1"),
invocation(getUpdated, "2"),
)
if err != nil {
return zero, "", "", "", err
@@ -187,88 +188,40 @@ func changesTemplate[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, I
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
var changesResponse CHANGESRESP
err = retrieveResponseMatchParameters(logger, body, changesCommand, "0", &changesResponse)
err = retrieveChanges(logger, body, changes, "0", &changesResponse)
if err != nil {
return zero, "", err
}
var createdResponse GETRESP
err = retrieveResponseMatchParameters(logger, body, getCommand, "1", &createdResponse)
err = retrieveGet(logger, body, getCreated, "1", &createdResponse)
if err != nil {
logger.Error().Err(err).Send()
return zero, "", err
}
var updatedResponse GETRESP
err = retrieveResponseMatchParameters(logger, body, getCommand, "2", &updatedResponse)
err = retrieveGet(logger, body, getUpdated, "2", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
return zero, "", err
}
oldState, newState, hasMoreChanges, destroyed := changesMapper(changesResponse)
created := getMapper(createdResponse)
updated := getMapper(updatedResponse)
result := respMapper(oldState, newState, hasMoreChanges, created, updated, destroyed)
result := respMapper(changesResponse.GetOldState(), changesResponse.GetNewState(), changesResponse.GetHasMoreChanges(), created, updated, changesResponse.GetDestroyed())
return result, stateMapper(createdResponse), nil
return result, changesResponse.GetNewState(), nil
})
}
func updatedTemplate[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, ITEM any, RESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace,
changesCommand Command, getCommand Command,
changesCommandFactory func() CHANGESREQ,
getCommandFactory func(string, string) GETREQ,
changesMapper func(CHANGESRESP) (State, State, bool),
getMapper func(GETRESP) []ITEM,
respMapper func(State, State, bool, []ITEM) RESP,
stateMapper func(GETRESP) State,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
var zero RESP
changes := changesCommandFactory()
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
cmd, err := client.request(session, logger, using,
invocation(changesCommand, changes, "0"),
invocation(getCommand, getUpdated, "1"),
)
if err != nil {
return zero, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
var changesResponse CHANGESRESP
err = retrieveResponseMatchParameters(logger, body, changesCommand, "0", &changesResponse)
if err != nil {
return zero, "", err
}
var updatedResponse GETRESP
err = retrieveResponseMatchParameters(logger, body, getCommand, "1", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
return zero, "", err
}
oldState, newState, hasMoreChanges := changesMapper(changesResponse)
updated := getMapper(updatedResponse)
result := respMapper(oldState, newState, hasMoreChanges, updated)
return result, stateMapper(updatedResponse), nil
})
}
func changesTemplateN[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any, ITEM any, CHANGESITEM any, RESP any]( //NOSONAR
func changesN[CHANGESREQ ChangesCommand, GETREQ GetCommand, CHANGESRESP ChangesResponse, GETRESP GetResponse, ITEM any, CHANGESITEM any, RESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace,
accountIds []string, sinceStateMap map[string]State,
changesCommand Command, getCommand Command,
changesCommandFactory func(string, State) CHANGESREQ,
_ CHANGESRESP,
getCommandFactory func(string, string, string) GETREQ,
changesMapper func(CHANGESRESP) (State, State, bool, []string),
getMapper func(GETRESP) []ITEM,
changesItemMapper func(State, State, bool, []ITEM, []ITEM, []string) CHANGESITEM,
respMapper func(map[string]CHANGESITEM) RESP,
@@ -291,6 +244,8 @@ func changesTemplateN[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any,
}
invocations := make([]Invocation, n*3)
getCommand := Command("")
changesCommand := Command("")
for i, accountId := range uniqueAccountIds {
sinceState, ok := sinceStateMap[accountId]
if !ok {
@@ -302,9 +257,12 @@ func changesTemplateN[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any,
getCreated := getCommandFactory(accountId, "/created", ref)
getUpdated := getCommandFactory(accountId, "/updated", ref)
invocations[i*3+0] = invocation(changesCommand, changes, ref)
invocations[i*3+1] = invocation(getCommand, getCreated, mcid(accountId, "1"))
invocations[i*3+2] = invocation(getCommand, getUpdated, mcid(accountId, "2"))
invocations[i*3+0] = invocation(changes, ref)
invocations[i*3+1] = invocation(getCreated, mcid(accountId, "1"))
invocations[i*3+2] = invocation(getUpdated, mcid(accountId, "2"))
changesCommand = changes.GetCommand()
getCommand = getCreated.GetCommand()
}
cmd, err := client.request(session, logger, using, invocations...)
@@ -334,12 +292,90 @@ func changesTemplateN[CHANGESREQ any, GETREQ any, CHANGESRESP any, GETRESP any,
return zero, "", err
}
oldState, newState, hasMoreChanges, destroyed := changesMapper(changesResponse)
created := getMapper(createdResponse)
updated := getMapper(updatedResponse)
changesItemByAccount[accountId] = changesItemMapper(oldState, newState, hasMoreChanges, created, updated, destroyed)
changesItemByAccount[accountId] = changesItemMapper(changesResponse.GetOldState(), changesResponse.GetNewState(), changesResponse.GetHasMoreChanges(), created, updated, changesResponse.GetDestroyed())
stateByAccountId[accountId] = stateMapper(createdResponse)
}
return respMapper(changesItemByAccount), squashState(stateByAccountId), nil
})
}
func updates[CHANGESREQ ChangesCommand, GETREQ GetCommand, CHANGESRESP ChangesResponse, GETRESP GetResponse, ITEM any, RESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace,
changesCommandFactory func() CHANGESREQ,
_ CHANGESRESP,
getCommandFactory func(string, string) GETREQ,
getMapper func(GETRESP) []ITEM,
respMapper func(State, State, bool, []ITEM) RESP,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
var zero RESP
changes := changesCommandFactory()
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
cmd, err := client.request(session, logger, using,
invocation(changes, "0"),
invocation(getUpdated, "1"),
)
if err != nil {
return zero, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
var changesResponse CHANGESRESP
err = retrieveChanges(logger, body, changes, "0", &changesResponse)
if err != nil {
return zero, "", err
}
var updatedResponse GETRESP
err = retrieveGet(logger, body, getUpdated, "1", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
return zero, "", err
}
updated := getMapper(updatedResponse)
result := respMapper(changesResponse.GetOldState(), changesResponse.GetNewState(), changesResponse.GetHasMoreChanges(), updated)
return result, changesResponse.GetNewState(), nil
})
}
func update[CHANGES Change, SET SetCommand, GET GetCommand, RESP any, SETRESP SetResponse, GETRESP GetResponse](client *Client, name string, using []JmapNamespace, //NOSONAR
setCommandFactory func(map[string]PatchObject) SET,
getCommandFactory func(string) GET,
notUpdatedExtractor func(SETRESP) map[string]SetError,
objExtractor func(GETRESP) RESP,
id string, changes CHANGES,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
update := setCommandFactory(map[string]PatchObject{id: changes.AsPatch()})
get := getCommandFactory(id)
cmd, err := client.request(session, logger, using, invocation(update, "0"), invocation(get, "1"))
var zero RESP
if err != nil {
return zero, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
var setResponse SETRESP
err = retrieveSet(logger, body, update, "0", &setResponse)
if err != nil {
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)
return zero, "", setErrorError(setErr, update.GetObjectType())
}
var getResponse GETRESP
err = retrieveGet(logger, body, get, "1", &getResponse)
if err != nil {
return zero, setResponse.GetNewState(), err
}
return objExtractor(getResponse), setResponse.GetNewState(), nil
})
}

View File

@@ -202,14 +202,14 @@ func retrieveResponseMatch(data *Response, command Command, tag string) (Invocat
func retrieveResponseMatchParameters[T any](logger *log.Logger, data *Response, command Command, tag string, target *T) Error {
match, ok := retrieveResponseMatch(data, command, tag)
if !ok {
err := fmt.Errorf("failed to find JMAP response invocation match for command '%v' and tag '%v'", command, tag)
err := fmt.Errorf("failed to find JMAP response invocation match for command '%v' and tag '%v'", command, tag) // NOSONAR
logger.Error().Msg(err.Error())
return jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
params := match.Parameters
typedParams, ok := params.(T)
if !ok {
err := fmt.Errorf("JMAP response invocation matches command '%v' and tag '%v' but the type %T does not match the expected %T", command, tag, params, *target)
err := fmt.Errorf("JMAP response invocation matches command '%v' and tag '%v' but the type %T does not match the expected %T", command, tag, params, *target) // NOSONAR
logger.Error().Msg(err.Error())
return jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
@@ -233,6 +233,22 @@ func tryRetrieveResponseMatchParameters[T any](logger *log.Logger, data *Respons
return true, nil
}
func retrieveGet[C GetCommand, T GetResponse](logger *log.Logger, data *Response, command C, tag string, target *T) Error {
return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target)
}
func retrieveSet[C SetCommand, T SetResponse](logger *log.Logger, data *Response, command C, tag string, target *T) Error {
return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target)
}
func retrieveQuery[C QueryCommand, T QueryResponse](logger *log.Logger, data *Response, command C, tag string, target *T) Error {
return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target)
}
func retrieveChanges[C ChangesCommand, T ChangesResponse](logger *log.Logger, data *Response, command C, tag string, target *T) Error {
return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target)
}
func (i *Invocation) MarshalJSON() ([]byte, error) {
// JMAP requests have a slightly unusual structure since they are not a JSON object
// but, instead, a three-element array composed of
@@ -363,6 +379,10 @@ func mapPairs[K comparable, L, R any](left map[K]L, right map[K]R) map[K]pair[L,
return result
}
func strPtr(s string) *string {
return &s
}
func intPtr(i int) *int {
return &i
}

View File

@@ -99,6 +99,15 @@ func MapKeys2[S comparable, T comparable, V any](m map[S]V, mapper func(S, V) T)
return r
}
func ToMap[E any, K comparable, V any](source []E, mapper func(E) (K, V)) map[K]V {
m := map[K]V{}
for _, e := range source {
k, v := mapper(e)
m[k] = v
}
return m
}
func ToBoolMap[E comparable](source []E) map[E]bool {
m := make(map[E]bool, len(source))
for _, v := range source {