diff --git a/pkg/jmap/api_addressbook.go b/pkg/jmap/api_addressbook.go index beab91e810..650f5d41d1 100644 --- a/pkg/jmap/api_addressbook.go +++ b/pkg/jmap/api_addressbook.go @@ -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, ) } diff --git a/pkg/jmap/api_blob.go b/pkg/jmap/api_blob.go index 40b6402f07..27b16cdb49 100644 --- a/pkg/jmap/api_blob.go +++ b/pkg/jmap/api_blob.go @@ -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 } diff --git a/pkg/jmap/api_bootstrap.go b/pkg/jmap/api_bootstrap.go index 31c44e6e42..2101863af6 100644 --- a/pkg/jmap/api_bootstrap.go +++ b/pkg/jmap/api_bootstrap.go @@ -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...) diff --git a/pkg/jmap/api_calendar.go b/pkg/jmap/api_calendar.go index 7260fda306..9f52663345 100644 --- a/pkg/jmap/api_calendar.go +++ b/pkg/jmap/api_calendar.go @@ -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, ) } diff --git a/pkg/jmap/api_changes.go b/pkg/jmap/api_changes.go index 369f79410d..95aef289db 100644 --- a/pkg/jmap/api_changes.go +++ b/pkg/jmap/api_changes.go @@ -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")) } diff --git a/pkg/jmap/api_contact.go b/pkg/jmap/api_contact.go index d872a97f89..28d2397889 100644 --- a/pkg/jmap/api_contact.go +++ b/pkg/jmap/api_contact.go @@ -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"), diff --git a/pkg/jmap/api_email.go b/pkg/jmap/api_email.go index 26261a64b9..8b286e6504 100644 --- a/pkg/jmap/api_email.go +++ b/pkg/jmap/api_email.go @@ -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, ) } diff --git a/pkg/jmap/api_identity.go b/pkg/jmap/api_identity.go index eb183d37c3..82d9bacc43 100644 --- a/pkg/jmap/api_identity.go +++ b/pkg/jmap/api_identity.go @@ -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, ) } diff --git a/pkg/jmap/api_mailbox.go b/pkg/jmap/api_mailbox.go index 2a2ef23a13..01cbc5ff7c 100644 --- a/pkg/jmap/api_mailbox.go +++ b/pkg/jmap/api_mailbox.go @@ -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, diff --git a/pkg/jmap/api_objects.go b/pkg/jmap/api_objects.go index 04ef44ec7f..6e07bb1b6a 100644 --- a/pkg/jmap/api_objects.go +++ b/pkg/jmap/api_objects.go @@ -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...) diff --git a/pkg/jmap/api_principal.go b/pkg/jmap/api_principal.go index 2a741720e5..9eb42df7e0 100644 --- a/pkg/jmap/api_principal.go +++ b/pkg/jmap/api_principal.go @@ -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 diff --git a/pkg/jmap/api_quota.go b/pkg/jmap/api_quota.go index 1c9946af20..c61a67bdc8 100644 --- a/pkg/jmap/api_quota.go +++ b/pkg/jmap/api_quota.go @@ -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, ) } diff --git a/pkg/jmap/api_vacation.go b/pkg/jmap/api_vacation.go index 8eeeae75f4..f513ec0ff6 100644 --- a/pkg/jmap/api_vacation.go +++ b/pkg/jmap/api_vacation.go @@ -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) } diff --git a/pkg/jmap/export_test.go b/pkg/jmap/export_test.go new file mode 100644 index 0000000000..094a9ca3bf --- /dev/null +++ b/pkg/jmap/export_test.go @@ -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 +} diff --git a/pkg/jmap/integration_contact_test.go b/pkg/jmap/integration_contact_test.go index 9c062058d3..aa8eca699c 100644 --- a/pkg/jmap/integration_contact_test.go +++ b/pkg/jmap/integration_contact_test.go @@ -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) } diff --git a/pkg/jmap/integration_event_test.go b/pkg/jmap/integration_event_test.go index c3c5585f03..fa555b54b6 100644 --- a/pkg/jmap/integration_event_test.go +++ b/pkg/jmap/integration_event_test.go @@ -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{ diff --git a/pkg/jmap/integration_test.go b/pkg/jmap/integration_test.go index fb3c7d6577..1953d2f2d6 100644 --- a/pkg/jmap/integration_test.go +++ b/pkg/jmap/integration_test.go @@ -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, }, diff --git a/pkg/jmap/integration_ws_test.go b/pkg/jmap/integration_ws_test.go index f421413966..9177d2152c 100644 --- a/pkg/jmap/integration_ws_test.go +++ b/pkg/jmap/integration_ws_test.go @@ -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 }) diff --git a/pkg/jmap/model.go b/pkg/jmap/model.go index 1e1de8503a..c56757da45 100644 --- a/pkg/jmap/model.go +++ b/pkg/jmap/model.go @@ -9,7 +9,16 @@ import ( ) // https://www.iana.org/assignments/jmap/jmap.xml#jmap-data-types -type ObjectType string +type ObjectTypeName string + +type ObjectType struct { + Name ObjectTypeName + Namespaces []JmapNamespace +} + +func (o ObjectType) String() string { + return string(o.Name) +} // Where `UTCDate` is given as a type, it means a `Date` where the "time-offset" // component MUST be `"Z"` (i.e., it must be in UTC time). @@ -111,29 +120,31 @@ const ( JmapTasksAlerts = JmapNamespace("urn:ietf:params:jmap:tasks:alerts") JmapTasksMultilingual = JmapNamespace("urn:ietf:params:jmap:tasks:multilingual") JmapTasksCustomTimezones = JmapNamespace("urn:ietf:params:jmap:tasks:customtimezones") + JmapFileNode = JmapNamespace("urn:ietf:params:jmap:filenode") - CoreType = ObjectType("Core") - PushSubscriptionType = ObjectType("PushSubscription") - MailboxType = ObjectType("Mailbox") - ThreadType = ObjectType("Thread") - EmailType = ObjectType("Email") - EmailDeliveryType = ObjectType("EmailDelivery") - SearchSnippetType = ObjectType("SearchSnippet") - IdentityType = ObjectType("Identity") - EmailSubmissionType = ObjectType("EmailSubmission") - VacationResponseType = ObjectType("VacationResponse") - MDNType = ObjectType("MDN") - QuotaType = ObjectType("Quota") - SieveScriptType = ObjectType("SieveScript") - PrincipalType = ObjectType("PrincipalType") - ShareNotificationType = ObjectType("ShareNotification") - AddressBookType = ObjectType("AddressBook") - ContactCardType = ObjectType("ContactCard") - CalendarType = ObjectType("Calendar") - CalendarEventType = ObjectType("CalendarEvent") - CalendarEventNotificationType = ObjectType("CalendarEventNotification") - ParticipantIdentityType = ObjectType("ParticipantIdentity") - FileNodeType = ObjectType("FileNode") + CoreName = ObjectTypeName("Core") + BlobName = ObjectTypeName("Blob") + PushSubscriptionName = ObjectTypeName("PushSubscription") + MailboxName = ObjectTypeName("Mailbox") + ThreadName = ObjectTypeName("Thread") + EmailName = ObjectTypeName("Email") + EmailDeliveryName = ObjectTypeName("EmailDelivery") + SearchSnippetName = ObjectTypeName("SearchSnippet") + IdentityName = ObjectTypeName("Identity") + EmailSubmissionName = ObjectTypeName("EmailSubmission") + VacationResponseName = ObjectTypeName("VacationResponse") + MDNName = ObjectTypeName("MDN") + QuotaName = ObjectTypeName("Quota") + SieveScriptName = ObjectTypeName("SieveScript") + PrincipalName = ObjectTypeName("Principal") + ShareNotificationName = ObjectTypeName("ShareNotification") + AddressBookName = ObjectTypeName("AddressBook") + ContactCardName = ObjectTypeName("ContactCard") + CalendarName = ObjectTypeName("Calendar") + CalendarEventName = ObjectTypeName("CalendarEvent") + CalendarEventNotificationName = ObjectTypeName("CalendarEventNotification") + ParticipantIdentityName = ObjectTypeName("ParticipantIdentity") + FileNodeName = ObjectTypeName("FileNode") JmapKeywordPrefix = "$" JmapKeywordSeen = "$seen" @@ -261,6 +272,10 @@ const ( IncludeInAvailabilityNone = IncludeInAvailability("none") ) +func newObjectType(name ObjectTypeName, namespaces ...JmapNamespace) ObjectType { + return ObjectType{Name: name, Namespaces: ns(namespaces...)} +} + var ( JmapNamespaces = []JmapNamespace{ JmapCore, @@ -282,8 +297,59 @@ var ( JmapTasksAlerts, JmapTasksMultilingual, JmapTasksCustomTimezones, + JmapFileNode, } + ObjectTypeNames = []ObjectTypeName{ + CoreName, + BlobName, + PushSubscriptionName, + MailboxName, + ThreadName, + EmailName, + EmailDeliveryName, + SearchSnippetName, + IdentityName, + EmailSubmissionName, + VacationResponseName, + MDNName, + QuotaName, + SieveScriptName, + PrincipalName, + ShareNotificationName, + AddressBookName, + ContactCardName, + CalendarName, + CalendarEventName, + CalendarEventNotificationName, + ParticipantIdentityName, + FileNodeName, + } + + CoreType = newObjectType(CoreName) + BlobType = newObjectType(BlobName, JmapBlob) + PushSubscriptionType = newObjectType(PushSubscriptionName) + MailboxType = newObjectType(MailboxName, JmapMail) + ThreadType = newObjectType(ThreadName, JmapMail) + EmailType = newObjectType(EmailName, JmapMail) + EmailDeliveryType = newObjectType(EmailDeliveryName, JmapMail) + SearchSnippetType = newObjectType(SearchSnippetName, JmapMail) + IdentityType = newObjectType(IdentityName, JmapMail) + EmailSubmissionType = newObjectType(EmailSubmissionName, JmapMail) + VacationResponseType = newObjectType(VacationResponseName, JmapVacationResponse) + MDNType = newObjectType(MDNName, JmapMDN) + QuotaType = newObjectType(QuotaName, JmapQuota) + SieveScriptType = newObjectType(SieveScriptName, JmapSieve) + PrincipalType = newObjectType(PrincipalName, JmapPrincipals) + ShareNotificationType = newObjectType(ShareNotificationName, JmapPrincipals) + AddressBookType = newObjectType(AddressBookName, JmapContacts) + ContactCardType = newObjectType(ContactCardName, JmapContacts) + CalendarType = newObjectType(CalendarName, JmapCalendars) + CalendarEventType = newObjectType(CalendarEventName, JmapCalendars) + CalendarEventNotificationType = newObjectType(CalendarEventNotificationName, JmapCalendars) + ParticipantIdentityType = newObjectType(ParticipantIdentityName, JmapCalendars) + FileNodeType = newObjectType(FileNodeName, JmapFileNode) + ObjectTypes = []ObjectType{ CoreType, PushSubscriptionType, @@ -1091,6 +1157,200 @@ type SetError struct { InvalidRecipients []string `json:"invalidRecipients,omitempty"` } +type Command string + +type Invocation struct { + Command Command + Parameters any + Tag string +} + +func invocation(parameters JmapCommand, tag string) Invocation { + return Invocation{ + Command: parameters.GetCommand(), + Parameters: parameters, + Tag: tag, + } +} + +type TypeOfRequest string + +const RequestType = TypeOfRequest("Request") + +type Request struct { + // The set of capabilities the client wishes to use. + // + // The client MAY include capability identifiers even if the method calls it makes do not utilise those capabilities. + // The server advertises the set of specifications it supports in the Session object + // (see [Section 2](https://jmap.io/spec-core.html#the-jmap-session-resource)), as keys on the capabilities property. + Using []JmapNamespace `json:"using"` + + // An array of method calls to process on the server. + // + // The method calls MUST be processed sequentially, in order. + MethodCalls []Invocation `json:"methodCalls"` + + // A map of a (client-specified) creation id to the id the server assigned when a record was successfully created (optional). + CreatedIds map[string]string `json:"createdIds,omitempty"` + + // This MUST be the string "Request". + // The specification extends the Response object with two additional arguments when used over a WebSocket. + Type TypeOfRequest `json:"@type,omitempty"` + + // A client-specified identifier for the request to be echoed back in the response to this request (optional). + Id string `json:"id,omitempty"` +} + +type TypeOfResponse string + +const ResponseType = TypeOfResponse("Response") + +type Response struct { + // An array of responses, in the same format as the methodCalls on the Request object. + // The output of the methods MUST be added to the methodResponses array in the same order that the methods are processed. + MethodResponses []Invocation `json:"methodResponses"` + + // A map of a (client-specified) creation id to the id the server assigned when a record was successfully created. + // + // Optional; only returned if given in the request. + // + // This MUST include all creation ids passed in the original createdIds parameter of the Request object, as well as any + // additional ones added for newly created records. + CreatedIds map[string]string `json:"createdIds,omitempty"` + + // The current value of the “state” string on the Session object, as described in [Section 2](https://jmap.io/spec-core.html#the-jmap-session-resource). + // Clients may use this to detect if this object has changed and needs to be refetched. + SessionState SessionState `json:"sessionState"` + + // This MUST be the string "Response". + // The specification extends the Response object with two additional arguments when used over a WebSocket. + Type TypeOfResponse `json:"@type,omitempty"` + + // MUST be returned if an identifier is included in the request (optional). + RequestId string `json:"requestId,omitempty"` +} + +// Patch Object. +// +// Example: +// +// - moves it from the drafts folder (which has Mailbox id `"7cb4e8ee-df87-4757-b9c4-2ea1ca41b38e"“) +// to the sent folder (which we presume has Mailbox id `"73dbcb4b-bffc-48bd-8c2a-a2e91ca672f6"“) +// - removes the `$draft` flag +// +// ```json +// +// { +// "mailboxIds/7cb4e8ee-df87-4757-b9c4-2ea1ca41b38e": null, +// "mailboxIds/73dbcb4b-bffc-48bd-8c2a-a2e91ca672f6": true, +// "keywords/$draft": null +// } +// +// ``` +type PatchObject map[string]any + +// Reference to Previous Method Results +// +// To allow clients to make more efficient use of the network and avoid round trips, an argument to one method +// can be taken from the result of a previous method call in the same request. +// +// To do this, the client prefixes the argument name with # (an [octothorpe](https://en.wiktionary.org/wiki/octothorpe)). +// +// When processing a method call, the server MUST first check the arguments object for any names beginning with #. +// +// If found, the result reference should be resolved and the value used as the “real” argument. +// +// The method is then processed as normal. +// +// If any result reference fails to resolve, the whole method MUST be rejected with an invalidResultReference error. +// +// If an arguments object contains the same argument name in normal and referenced form (e.g., foo and #foo), +// the method MUST return an invalidArguments error. +// +// To resolve: +// +// 1. Find the first response with a method call id identical to the resultOf property of the ResultReference +// in the methodResponses array from previously processed method calls in the same request. +// If none, evaluation fails. +// 2. If the response name is not identical to the name property of the ResultReference, evaluation fails. +// 3. Apply the path to the arguments object of the response (the second item in the response array) +// following the JSON Pointer algorithm [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901), +// except with the following addition in “Evaluation” (see Section 4): +// 4. If the currently referenced value is a JSON array, the reference token may be exactly the single character *, +// making the new referenced value the result of applying the rest of the JSON Pointer tokens to every item in the +// array and returning the results in the same order in a new array. +// 5. If the result of applying the rest of the pointer tokens to each item was itself an array, the contents of this +// array are added to the output rather than the array itself (i.e., the result is flattened from an array of +// arrays to a single array). +type ResultReference struct { + // The method call id of a previous method call in the current request. + ResultOf string `json:"resultOf"` + + // The required name of a response to that method call. + Name Command `json:"name"` + + // A pointer into the arguments of the response selected via the name and resultOf properties. + // + // This is a JSON Pointer [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901), except it also allows + // the use of * to map through an array. + Path string `json:"path,omitempty"` +} + +type JmapCommand interface { + GetCommand() Command + GetObjectType() ObjectType +} + +type GetCommand interface { + JmapCommand + GetResponse() GetResponse +} + +type GetResponse interface { + GetState() State + GetNotFound() []string +} + +type SetCommand interface { + JmapCommand + GetResponse() SetResponse +} + +type SetResponse interface { + GetNotCreated() map[string]SetError + GetNotUpdated() map[string]SetError + GetNotDestroyed() map[string]SetError + GetOldState() State + GetNewState() State +} + +type Change interface { + AsPatch() PatchObject +} + +type ChangesCommand interface { + JmapCommand + GetResponse() ChangesResponse +} + +type ChangesResponse interface { + GetOldState() State + GetNewState() State + GetHasMoreChanges() bool + GetCreated() []string + GetUpdated() []string + GetDestroyed() []string +} + +type QueryCommand interface { + JmapCommand + GetResponse() QueryResponse +} + +type QueryResponse interface { + GetQueryState() State +} + type FilterOperatorTerm string const ( @@ -1308,6 +1568,8 @@ type MailboxChange struct { IsSubscribed *bool `json:"isSubscribed,omitempty"` } +var _ Change = MailboxChange{} + func (m MailboxChange) AsPatch() PatchObject { p := PatchObject{} if m.Name != "" { @@ -1333,11 +1595,23 @@ type MailboxGetCommand struct { Ids []string `json:"ids,omitempty"` } +var _ GetCommand = &MailboxGetCommand{} + +func (c MailboxGetCommand) GetCommand() Command { return CommandMailboxGet } +func (c MailboxGetCommand) GetObjectType() ObjectType { return MailboxType } +func (c MailboxGetCommand) GetResponse() GetResponse { return MailboxGetResponse{} } + type MailboxGetRefCommand struct { AccountId string `json:"accountId"` IdsRef *ResultReference `json:"#ids,omitempty"` } +var _ GetCommand = &MailboxGetRefCommand{} + +func (c MailboxGetRefCommand) GetCommand() Command { return CommandMailboxGet } +func (c MailboxGetRefCommand) GetObjectType() ObjectType { return MailboxType } +func (c MailboxGetRefCommand) GetResponse() GetResponse { return MailboxGetResponse{} } + type MailboxSetCommand struct { AccountId string `json:"accountId"` IfInState string `json:"ifInState,omitempty"` @@ -1346,6 +1620,12 @@ type MailboxSetCommand struct { Destroy []string `json:"destroy,omitempty"` } +var _ SetCommand = &MailboxSetCommand{} + +func (c MailboxSetCommand) GetCommand() Command { return CommandMailboxSet } +func (c MailboxSetCommand) GetObjectType() ObjectType { return MailboxType } +func (c MailboxSetCommand) GetResponse() SetResponse { return MailboxSetResponse{} } + type MailboxSetResponse struct { AccountId string `json:"accountId"` OldState State `json:"oldState,omitempty"` @@ -1358,6 +1638,14 @@ type MailboxSetResponse struct { NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } +var _ SetResponse = &MailboxSetResponse{} + +func (r MailboxSetResponse) GetOldState() State { return r.OldState } +func (r MailboxSetResponse) GetNewState() State { return r.NewState } +func (r MailboxSetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r MailboxSetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r MailboxSetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } + type MailboxFilterElement interface { _isAMailboxFilterElement() // marker method } @@ -1402,6 +1690,12 @@ type MailboxQueryCommand struct { FilterAsTree bool `json:"filterAsTree,omitempty"` } +var _ QueryCommand = &MailboxQueryCommand{} + +func (c MailboxQueryCommand) GetCommand() Command { return CommandMailboxQuery } +func (c MailboxQueryCommand) GetObjectType() ObjectType { return MailboxType } +func (c MailboxQueryCommand) GetResponse() QueryResponse { return MailboxQueryResponse{} } + type EmailFilterElement interface { _isAnEmailFilterElement() // marker method IsNotEmpty() bool @@ -1692,6 +1986,12 @@ type EmailQueryCommand struct { CalculateTotal bool `json:"calculateTotal,omitempty"` } +var _ QueryCommand = &EmailQueryCommand{} + +func (c EmailQueryCommand) GetCommand() Command { return CommandEmailQuery } +func (c EmailQueryCommand) GetObjectType() ObjectType { return MailboxType } +func (c EmailQueryCommand) GetResponse() QueryResponse { return EmailQueryResponse{} } + type EmailGetCommand struct { // The ids of the Email objects to return. // @@ -1752,52 +2052,11 @@ type EmailGetCommand struct { MaxBodyValueBytes uint `json:"maxBodyValueBytes,omitempty"` } -// Reference to Previous Method Results -// -// To allow clients to make more efficient use of the network and avoid round trips, an argument to one method -// can be taken from the result of a previous method call in the same request. -// -// To do this, the client prefixes the argument name with # (an [octothorpe](https://en.wiktionary.org/wiki/octothorpe)). -// -// When processing a method call, the server MUST first check the arguments object for any names beginning with #. -// -// If found, the result reference should be resolved and the value used as the “real” argument. -// -// The method is then processed as normal. -// -// If any result reference fails to resolve, the whole method MUST be rejected with an invalidResultReference error. -// -// If an arguments object contains the same argument name in normal and referenced form (e.g., foo and #foo), -// the method MUST return an invalidArguments error. -// -// To resolve: -// -// 1. Find the first response with a method call id identical to the resultOf property of the ResultReference -// in the methodResponses array from previously processed method calls in the same request. -// If none, evaluation fails. -// 2. If the response name is not identical to the name property of the ResultReference, evaluation fails. -// 3. Apply the path to the arguments object of the response (the second item in the response array) -// following the JSON Pointer algorithm [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901), -// except with the following addition in “Evaluation” (see Section 4): -// 4. If the currently referenced value is a JSON array, the reference token may be exactly the single character *, -// making the new referenced value the result of applying the rest of the JSON Pointer tokens to every item in the -// array and returning the results in the same order in a new array. -// 5. If the result of applying the rest of the pointer tokens to each item was itself an array, the contents of this -// array are added to the output rather than the array itself (i.e., the result is flattened from an array of -// arrays to a single array). -type ResultReference struct { - // The method call id of a previous method call in the current request. - ResultOf string `json:"resultOf"` +var _ GetCommand = &EmailGetCommand{} - // The required name of a response to that method call. - Name Command `json:"name"` - - // A pointer into the arguments of the response selected via the name and resultOf properties. - // - // This is a JSON Pointer [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901), except it also allows - // the use of * to map through an array. - Path string `json:"path,omitempty"` -} +func (c EmailGetCommand) GetCommand() Command { return CommandEmailGet } +func (c EmailGetCommand) GetObjectType() ObjectType { return EmailType } +func (c EmailGetCommand) GetResponse() GetResponse { return EmailGetResponse{} } type EmailGetRefCommand struct { // The ids of the Email objects to return. @@ -1855,6 +2114,12 @@ type EmailGetRefCommand struct { MaxBodyValueBytes uint `json:"maxBodyValueBytes,omitempty"` } +var _ GetCommand = &EmailGetRefCommand{} + +func (c EmailGetRefCommand) GetCommand() Command { return CommandEmailGet } +func (c EmailGetRefCommand) GetObjectType() ObjectType { return EmailType } +func (c EmailGetRefCommand) GetResponse() GetResponse { return EmailGetResponse{} } + type EmailChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -1873,6 +2138,12 @@ type EmailChangesCommand struct { MaxChanges *uint `json:"maxChanges,omitempty"` } +var _ ChangesCommand = &EmailChangesCommand{} + +func (c EmailChangesCommand) GetCommand() Command { return CommandEmailChanges } +func (c EmailChangesCommand) GetObjectType() ObjectType { return EmailType } +func (c EmailChangesCommand) GetResponse() ChangesResponse { return EmailChangesResponse{} } + type EmailAddress struct { // The display-name of the mailbox [RFC5322](https://www.rfc-editor.org/rfc/rfc5322.html). // @@ -2483,6 +2754,12 @@ type EmailSubmissionGetCommand struct { Properties []string `json:"properties,omitempty"` } +var _ GetCommand = &EmailSubmissionGetCommand{} + +func (c EmailSubmissionGetCommand) GetCommand() Command { return CommandEmailSubmissionGet } +func (c EmailSubmissionGetCommand) GetObjectType() ObjectType { return EmailSubmissionType } +func (c EmailSubmissionGetCommand) GetResponse() GetResponse { return EmailSubmissionGetResponse{} } + type EmailSubmissionGetRefCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -2501,6 +2778,12 @@ type EmailSubmissionGetRefCommand struct { Properties []string `json:"properties,omitempty"` } +var _ GetCommand = &EmailSubmissionGetRefCommand{} + +func (c EmailSubmissionGetRefCommand) GetCommand() Command { return CommandEmailSubmissionGet } +func (c EmailSubmissionGetRefCommand) GetObjectType() ObjectType { return EmailSubmissionType } +func (c EmailSubmissionGetRefCommand) GetResponse() GetResponse { return EmailSubmissionGetResponse{} } + type EmailSubmissionGetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -2533,6 +2816,11 @@ type EmailSubmissionGetResponse struct { NotFound []string `json:"notFound,omitempty"` } +var _ GetResponse = &EmailSubmissionGetResponse{} + +func (r EmailSubmissionGetResponse) GetState() State { return r.State } +func (r EmailSubmissionGetResponse) GetNotFound() []string { return r.NotFound } + type EmailSubmissionChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -2556,6 +2844,14 @@ type EmailSubmissionChangesCommand struct { MaxChanges *uint `json:"maxChanges,omitzero"` } +var _ ChangesCommand = &EmailSubmissionChangesCommand{} + +func (c EmailSubmissionChangesCommand) GetCommand() Command { return CommandEmailSubmissionChanges } +func (c EmailSubmissionChangesCommand) GetObjectType() ObjectType { return EmailSubmissionType } +func (c EmailSubmissionChangesCommand) GetResponse() ChangesResponse { + return EmailSubmissionChangesResponse{} +} + type EmailSubmissionChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -2581,24 +2877,14 @@ type EmailSubmissionChangesResponse struct { Destroyed []string `json:"destroyed,omitempty"` } -// Patch Object. -// -// Example: -// -// - moves it from the drafts folder (which has Mailbox id `"7cb4e8ee-df87-4757-b9c4-2ea1ca41b38e"“) -// to the sent folder (which we presume has Mailbox id `"73dbcb4b-bffc-48bd-8c2a-a2e91ca672f6"“) -// - removes the `$draft` flag -// -// ```json -// -// { -// "mailboxIds/7cb4e8ee-df87-4757-b9c4-2ea1ca41b38e": null, -// "mailboxIds/73dbcb4b-bffc-48bd-8c2a-a2e91ca672f6": true, -// "keywords/$draft": null -// } -// -// ``` -type PatchObject map[string]any +var _ ChangesResponse = &EmailSubmissionChangesResponse{} + +func (r EmailSubmissionChangesResponse) GetOldState() State { return r.OldState } +func (r EmailSubmissionChangesResponse) GetNewState() State { return r.NewState } +func (r EmailSubmissionChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r EmailSubmissionChangesResponse) GetCreated() []string { return r.Created } +func (r EmailSubmissionChangesResponse) GetUpdated() []string { return r.Updated } +func (r EmailSubmissionChangesResponse) GetDestroyed() []string { return r.Destroyed } // same as EmailSubmission but without the server-set attributes type EmailSubmissionCreate struct { @@ -2636,6 +2922,12 @@ type EmailSubmissionSetCommand struct { OnSuccessDestroyEmail []string `json:"onSuccessDestroyEmail,omitempty"` } +var _ SetCommand = &EmailSubmissionSetCommand{} + +func (c EmailSubmissionSetCommand) GetCommand() Command { return CommandEmailSubmissionSet } +func (c EmailSubmissionSetCommand) GetObjectType() ObjectType { return EmailSubmissionType } +func (c EmailSubmissionSetCommand) GetResponse() SetResponse { return EmailSubmissionSetResponse{} } + type CreatedEmailSubmission struct { Id string `json:"id"` } @@ -2663,81 +2955,17 @@ type EmailSubmissionSetResponse struct { // null if all successful. NotCreated map[string]SetError `json:"notCreated,omitempty"` - // TODO(pbleser-oc) add updated and destroyed when they are needed + NotUpdated map[string]SetError `json:"notUpdated,omitempty"` + NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } -type Command string +var _ SetResponse = &EmailSubmissionSetResponse{} -type Invocation struct { - Command Command - Parameters any - Tag string -} - -func invocation(command Command, parameters any, tag string) Invocation { - return Invocation{ - Command: command, - Parameters: parameters, - Tag: tag, - } -} - -type TypeOfRequest string - -const RequestType = TypeOfRequest("Request") - -type Request struct { - // The set of capabilities the client wishes to use. - // - // The client MAY include capability identifiers even if the method calls it makes do not utilise those capabilities. - // The server advertises the set of specifications it supports in the Session object - // (see [Section 2](https://jmap.io/spec-core.html#the-jmap-session-resource)), as keys on the capabilities property. - Using []JmapNamespace `json:"using"` - - // An array of method calls to process on the server. - // - // The method calls MUST be processed sequentially, in order. - MethodCalls []Invocation `json:"methodCalls"` - - // A map of a (client-specified) creation id to the id the server assigned when a record was successfully created (optional). - CreatedIds map[string]string `json:"createdIds,omitempty"` - - // This MUST be the string "Request". - // The specification extends the Response object with two additional arguments when used over a WebSocket. - Type TypeOfRequest `json:"@type,omitempty"` - - // A client-specified identifier for the request to be echoed back in the response to this request (optional). - Id string `json:"id,omitempty"` -} - -type TypeOfResponse string - -const ResponseType = TypeOfResponse("Response") - -type Response struct { - // An array of responses, in the same format as the methodCalls on the Request object. - // The output of the methods MUST be added to the methodResponses array in the same order that the methods are processed. - MethodResponses []Invocation `json:"methodResponses"` - - // A map of a (client-specified) creation id to the id the server assigned when a record was successfully created. - // - // Optional; only returned if given in the request. - // - // This MUST include all creation ids passed in the original createdIds parameter of the Request object, as well as any - // additional ones added for newly created records. - CreatedIds map[string]string `json:"createdIds,omitempty"` - - // The current value of the “state” string on the Session object, as described in [Section 2](https://jmap.io/spec-core.html#the-jmap-session-resource). - // Clients may use this to detect if this object has changed and needs to be refetched. - SessionState SessionState `json:"sessionState"` - - // This MUST be the string "Response". - // The specification extends the Response object with two additional arguments when used over a WebSocket. - Type TypeOfResponse `json:"@type,omitempty"` - - // MUST be returned if an identifier is included in the request (optional). - RequestId string `json:"requestId,omitempty"` -} +func (r EmailSubmissionSetResponse) GetOldState() State { return r.OldState } +func (r EmailSubmissionSetResponse) GetNewState() State { return r.NewState } +func (r EmailSubmissionSetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r EmailSubmissionSetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r EmailSubmissionSetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } type EmailQueryResponse struct { // The id of the account used for the call. @@ -2789,6 +3017,10 @@ type EmailQueryResponse struct { Limit uint `json:"limit,omitempty,omitzero"` } +var _ QueryResponse = &EmailQueryResponse{} + +func (r EmailQueryResponse) GetQueryState() State { return r.QueryState } + type EmailGetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -2816,6 +3048,11 @@ type EmailGetResponse struct { NotFound []string `json:"notFound"` } +var _ GetResponse = &EmailGetResponse{} + +func (r EmailGetResponse) GetState() State { return r.State } +func (r EmailGetResponse) GetNotFound() []string { return r.NotFound } + type EmailChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -2840,6 +3077,15 @@ type EmailChangesResponse struct { Destroyed []string `json:"destroyed,omitempty"` } +var _ ChangesResponse = &EmailChangesResponse{} + +func (r EmailChangesResponse) GetOldState() State { return r.OldState } +func (r EmailChangesResponse) GetNewState() State { return r.NewState } +func (r EmailChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r EmailChangesResponse) GetCreated() []string { return r.Created } +func (r EmailChangesResponse) GetUpdated() []string { return r.Updated } +func (r EmailChangesResponse) GetDestroyed() []string { return r.Destroyed } + type MailboxGetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -2864,6 +3110,11 @@ type MailboxGetResponse struct { NotFound []string `json:"notFound"` } +var _ GetResponse = &MailboxGetResponse{} + +func (r MailboxGetResponse) GetState() State { return r.State } +func (r MailboxGetResponse) GetNotFound() []string { return r.NotFound } + type MailboxChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -2887,6 +3138,12 @@ type MailboxChangesCommand struct { MaxChanges *uint `json:"maxChanges,omitzero"` } +var _ ChangesCommand = &MailboxChangesCommand{} + +func (c MailboxChangesCommand) GetCommand() Command { return CommandMailboxChanges } +func (c MailboxChangesCommand) GetObjectType() ObjectType { return MailboxType } +func (c MailboxChangesCommand) GetResponse() ChangesResponse { return MailboxChangesResponse{} } + type MailboxChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -2918,6 +3175,15 @@ type MailboxChangesResponse struct { UpdatedProperties []string `json:"updatedProperties,omitempty"` } +var _ ChangesResponse = &MailboxChangesResponse{} + +func (r MailboxChangesResponse) GetOldState() State { return r.OldState } +func (r MailboxChangesResponse) GetNewState() State { return r.NewState } +func (r MailboxChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r MailboxChangesResponse) GetCreated() []string { return r.Created } +func (r MailboxChangesResponse) GetUpdated() []string { return r.Updated } +func (r MailboxChangesResponse) GetDestroyed() []string { return r.Destroyed } + type MailboxQueryResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -2965,6 +3231,10 @@ type MailboxQueryResponse struct { Limit int `json:"limit,omitzero"` } +var _ QueryResponse = &MailboxQueryResponse{} + +func (r MailboxQueryResponse) GetQueryState() State { return r.QueryState } + type EmailCreate struct { // The set of Mailbox ids this Email belongs to. // @@ -3122,6 +3392,12 @@ type EmailSetCommand struct { Destroy []string `json:"destroy,omitempty"` } +var _ SetCommand = &EmailSetCommand{} + +func (c EmailSetCommand) GetCommand() Command { return CommandEmailSet } +func (c EmailSetCommand) GetObjectType() ObjectType { return EmailType } +func (c EmailSetCommand) GetResponse() SetResponse { return EmailSubmissionSetResponse{} } + type EmailSetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -3169,6 +3445,14 @@ type EmailSetResponse struct { NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } +var _ SetResponse = &EmailSetResponse{} + +func (r EmailSetResponse) GetOldState() State { return r.OldState } +func (r EmailSetResponse) GetNewState() State { return r.NewState } +func (r EmailSetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r EmailSetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r EmailSetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } + const ( EmailMimeType = "message/rfc822" ) @@ -3264,29 +3548,58 @@ type ThreadGetCommand struct { Ids []string `json:"ids,omitempty"` } +var _ GetCommand = &ThreadGetCommand{} + +func (c ThreadGetCommand) GetCommand() Command { return CommandThreadGet } +func (c ThreadGetCommand) GetObjectType() ObjectType { return ThreadType } +func (c ThreadGetCommand) GetResponse() GetResponse { return ThreadGetResponse{} } + type ThreadGetRefCommand struct { AccountId string `json:"accountId"` IdsRef *ResultReference `json:"#ids,omitempty"` } +var _ GetCommand = &ThreadGetRefCommand{} + +func (c ThreadGetRefCommand) GetCommand() Command { return CommandThreadGet } +func (c ThreadGetRefCommand) GetObjectType() ObjectType { return ThreadType } +func (c ThreadGetRefCommand) GetResponse() GetResponse { return ThreadGetResponse{} } + type ThreadGetResponse struct { AccountId string State State List []Thread - NotFound []any + NotFound []string } +var _ GetResponse = &ThreadGetResponse{} + +func (r ThreadGetResponse) GetState() State { return r.State } +func (r ThreadGetResponse) GetNotFound() []string { return r.NotFound } + type IdentityGetCommand struct { AccountId string `json:"accountId"` Ids []string `json:"ids,omitempty"` } +var _ GetCommand = &IdentityGetCommand{} + +func (c IdentityGetCommand) GetCommand() Command { return CommandIdentityGet } +func (c IdentityGetCommand) GetObjectType() ObjectType { return IdentityType } +func (c IdentityGetCommand) GetResponse() GetResponse { return IdentityGetResponse{} } + type IdentityGetRefCommand struct { AccountId string `json:"accountId"` IdsRef *ResultReference `json:"#ids,omitempty"` PropertiesRef *ResultReference `json:"#properties,omitempty"` } +var _ GetCommand = &IdentityGetRefCommand{} + +func (c IdentityGetRefCommand) GetCommand() Command { return CommandIdentityGet } +func (c IdentityGetRefCommand) GetObjectType() ObjectType { return IdentityType } +func (c IdentityGetRefCommand) GetResponse() GetResponse { return IdentityGetResponse{} } + type IdentityChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -3310,6 +3623,12 @@ type IdentityChangesCommand struct { MaxChanges *uint `json:"maxChanges,omitzero"` } +var _ ChangesCommand = &IdentityChangesCommand{} + +func (c IdentityChangesCommand) GetCommand() Command { return CommandIdentityChanges } +func (c IdentityChangesCommand) GetObjectType() ObjectType { return IdentityType } +func (c IdentityChangesCommand) GetResponse() ChangesResponse { return IdentityChangesResponse{} } + type IdentityChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -3335,6 +3654,15 @@ type IdentityChangesResponse struct { Destroyed []string `json:"destroyed,omitempty"` } +var _ ChangesResponse = &IdentityChangesResponse{} + +func (r IdentityChangesResponse) GetOldState() State { return r.OldState } +func (r IdentityChangesResponse) GetNewState() State { return r.NewState } +func (r IdentityChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r IdentityChangesResponse) GetCreated() []string { return r.Created } +func (r IdentityChangesResponse) GetUpdated() []string { return r.Updated } +func (r IdentityChangesResponse) GetDestroyed() []string { return r.Destroyed } + type IdentitySetCommand struct { AccountId string `json:"accountId"` IfInState string `json:"ifInState,omitempty"` @@ -3343,6 +3671,12 @@ type IdentitySetCommand struct { Destroy []string `json:"destroy,omitempty"` } +var _ SetCommand = &IdentitySetCommand{} + +func (c IdentitySetCommand) GetCommand() Command { return CommandIdentitySet } +func (c IdentitySetCommand) GetObjectType() ObjectType { return IdentityType } +func (c IdentitySetCommand) GetResponse() SetResponse { return IdentitySetResponse{} } + type IdentitySetResponse struct { AccountId string `json:"accountId"` OldState State `json:"oldState,omitempty"` @@ -3355,6 +3689,14 @@ type IdentitySetResponse struct { NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } +var _ SetResponse = &IdentitySetResponse{} + +func (r IdentitySetResponse) GetOldState() State { return r.OldState } +func (r IdentitySetResponse) GetNewState() State { return r.NewState } +func (r IdentitySetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r IdentitySetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r IdentitySetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } + // An Identity object stores information about an email address or domain the user may send from. type Identity struct { // The id of the Identity. @@ -3399,6 +3741,8 @@ type Identity struct { MayDelete bool `json:"mayDelete,omitzero"` } +var _ Change = Identity{} + func (i Identity) AsPatch() PatchObject { p := PatchObject{} if i.Name != "" { @@ -3429,10 +3773,21 @@ type IdentityGetResponse struct { NotFound []string `json:"notFound,omitempty"` } +var _ GetResponse = &IdentityGetResponse{} + +func (r IdentityGetResponse) GetState() State { return r.State } +func (r IdentityGetResponse) GetNotFound() []string { return r.NotFound } + type VacationResponseGetCommand struct { AccountId string `json:"accountId"` } +var _ GetCommand = &VacationResponseGetCommand{} + +func (c VacationResponseGetCommand) GetCommand() Command { return CommandVacationResponseGet } +func (c VacationResponseGetCommand) GetObjectType() ObjectType { return VacationResponseType } +func (c VacationResponseGetCommand) GetResponse() GetResponse { return VacationResponseGetResponse{} } + // Vacation Response // // A vacation response sends an automatic reply when a message is delivered to the mail store, @@ -3491,9 +3846,14 @@ type VacationResponseGetResponse struct { List []VacationResponse `json:"list,omitempty"` // Contains identifiers of requested objects that were not found. - NotFound []any `json:"notFound,omitempty"` + NotFound []string `json:"notFound,omitempty"` } +var _ GetResponse = &VacationResponseGetResponse{} + +func (r VacationResponseGetResponse) GetState() State { return r.State } +func (r VacationResponseGetResponse) GetNotFound() []string { return r.NotFound } + type VacationResponseSetCommand struct { AccountId string `json:"accountId"` IfInState string `json:"ifInState,omitempty"` @@ -3502,6 +3862,12 @@ type VacationResponseSetCommand struct { Destroy []string `json:"destroy,omitempty"` } +var _ SetCommand = &VacationResponseSetCommand{} + +func (c VacationResponseSetCommand) GetCommand() Command { return CommandVacationResponseSet } +func (c VacationResponseSetCommand) GetObjectType() ObjectType { return VacationResponseType } +func (c VacationResponseSetCommand) GetResponse() SetResponse { return VacationResponseSetResponse{} } + type VacationResponseSetResponse struct { AccountId string `json:"accountId"` OldState State `json:"oldState,omitempty"` @@ -3514,6 +3880,14 @@ type VacationResponseSetResponse struct { NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } +var _ SetResponse = &VacationResponseSetResponse{} + +func (r VacationResponseSetResponse) GetOldState() State { return r.OldState } +func (r VacationResponseSetResponse) GetNewState() State { return r.NewState } +func (r VacationResponseSetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r VacationResponseSetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r VacationResponseSetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } + // One of these attributes must be set, but not both. type DataSourceObject struct { DataAsText string `json:"data:asText,omitempty"` @@ -3530,6 +3904,11 @@ type BlobUploadCommand struct { Create map[string]UploadObject `json:"create"` } +var _ JmapCommand = &BlobUploadCommand{} + +func (c BlobUploadCommand) GetCommand() Command { return CommandBlobUpload } +func (c BlobUploadCommand) GetObjectType() ObjectType { return BlobType } + type BlobUploadCreateResult struct { Id string `json:"id"` Type string `json:"type,omitempty"` @@ -3561,6 +3940,12 @@ type BlobGetCommand struct { Length int `json:"length,omitzero"` } +var _ GetCommand = &BlobGetCommand{} + +func (c BlobGetCommand) GetCommand() Command { return CommandBlobGet } +func (c BlobGetCommand) GetObjectType() ObjectType { return BlobType } +func (c BlobGetCommand) GetResponse() GetResponse { return BlobGetResponse{} } + type BlobGetRefCommand struct { AccountId string `json:"accountId"` IdRef *ResultReference `json:"#ids,omitempty"` @@ -3569,6 +3954,12 @@ type BlobGetRefCommand struct { Length int `json:"length,omitzero"` } +var _ GetCommand = &BlobGetRefCommand{} + +func (c BlobGetRefCommand) GetCommand() Command { return CommandBlobGet } +func (c BlobGetRefCommand) GetObjectType() ObjectType { return BlobType } +func (c BlobGetRefCommand) GetResponse() GetResponse { return BlobGetResponse{} } + type Blob struct { // The unique identifier of the blob. Id string `json:"id"` @@ -3646,9 +4037,14 @@ type BlobGetResponse struct { // // The array is empty if all requested ids were found or if the ids argument passed // in was either null or an empty array. - NotFound []any `json:"notFound,omitempty"` + NotFound []string `json:"notFound,omitempty"` } +var _ GetResponse = &BlobGetResponse{} + +func (r BlobGetResponse) GetState() State { return r.State } +func (r BlobGetResponse) GetNotFound() []string { return r.NotFound } + type BlobDownload struct { Body io.ReadCloser Size int @@ -3710,12 +4106,20 @@ type SearchSnippetGetRefCommand struct { EmailIdRef *ResultReference `json:"#emailIds,omitempty"` } +var _ JmapCommand = &SearchSnippetGetRefCommand{} + +func (c SearchSnippetGetRefCommand) GetCommand() Command { return CommandSearchSnippetGet } +func (c SearchSnippetGetRefCommand) GetObjectType() ObjectType { return SearchSnippetType } + type SearchSnippetGetResponse struct { AccountId string `json:"accountId"` List []SearchSnippet `json:"list,omitempty"` NotFound []string `json:"notFound,omitempty"` } +// this is not true, as the response to SearchSnippet/get does not have a state +// var _ GetResponse = &SearchSnippetGetResponse{} + type StateChangeType string const TypeOfStateChange = StateChangeType("StateChange") @@ -3735,7 +4139,7 @@ type StateChange struct { // The client can compare the new state strings with its current values to see whether // it has the current data for these types. If not, the changes can then be efficiently // fetched in a single standard API request (using the /changes type methods). - Changed map[string]map[ObjectType]string `json:"changed"` + Changed map[string]map[ObjectTypeName]string `json:"changed"` // A (preferably short) string that encodes the entire server state visible to the user // (not just the objects returned in this call). @@ -5209,12 +5613,24 @@ type QuotaGetCommand struct { Ids []string `json:"ids,omitempty"` } +var _ GetCommand = &QuotaGetCommand{} + +func (c QuotaGetCommand) GetCommand() Command { return CommandQuotaGet } +func (c QuotaGetCommand) GetObjectType() ObjectType { return QuotaType } +func (c QuotaGetCommand) GetResponse() GetResponse { return QuotaGetResponse{} } + type QuotaGetRefCommand struct { AccountId string `json:"accountId"` IdsRef *ResultReference `json:"#ids,omitempty"` PropertiesRef *ResultReference `json:"#properties,omitempty"` } +var _ GetCommand = &QuotaGetRefCommand{} + +func (c QuotaGetRefCommand) GetCommand() Command { return CommandQuotaGet } +func (c QuotaGetRefCommand) GetObjectType() ObjectType { return QuotaType } +func (c QuotaGetRefCommand) GetResponse() GetResponse { return QuotaGetResponse{} } + type QuotaGetResponse struct { AccountId string `json:"accountId"` State State `json:"state,omitempty"` @@ -5222,6 +5638,11 @@ type QuotaGetResponse struct { NotFound []string `json:"notFound,omitempty"` } +var _ GetResponse = &QuotaGetResponse{} + +func (r QuotaGetResponse) GetState() State { return r.State } +func (r QuotaGetResponse) GetNotFound() []string { return r.NotFound } + type QuotaChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -5244,6 +5665,12 @@ type QuotaChangesCommand struct { UpdatedProperties []string `json:"updatedProperties,omitempty"` } +var _ ChangesCommand = &QuotaChangesCommand{} + +func (c QuotaChangesCommand) GetCommand() Command { return CommandQuotaChanges } +func (c QuotaChangesCommand) GetObjectType() ObjectType { return QuotaType } +func (c QuotaChangesCommand) GetResponse() ChangesResponse { return QuotaChangesResponse{} } + type QuotaChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -5268,16 +5695,37 @@ type QuotaChangesResponse struct { Destroyed []string `json:"destroyed,omitempty"` } +var _ ChangesResponse = &QuotaChangesResponse{} + +func (r QuotaChangesResponse) GetOldState() State { return r.OldState } +func (r QuotaChangesResponse) GetNewState() State { return r.NewState } +func (r QuotaChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r QuotaChangesResponse) GetCreated() []string { return r.Created } +func (r QuotaChangesResponse) GetUpdated() []string { return r.Updated } +func (r QuotaChangesResponse) GetDestroyed() []string { return r.Destroyed } + type AddressBookGetCommand struct { AccountId string `json:"accountId"` Ids []string `json:"ids,omitempty"` } +var _ GetCommand = &AddressBookGetCommand{} + +func (c AddressBookGetCommand) GetCommand() Command { return CommandAddressBookGet } +func (c AddressBookGetCommand) GetObjectType() ObjectType { return AddressBookType } +func (c AddressBookGetCommand) GetResponse() GetResponse { return AddressBookGetResponse{} } + type AddressBookGetRefCommand struct { AccountId string `json:"accountId"` IdsRef *ResultReference `json:"#ids,omitempty"` } +var _ GetCommand = &AddressBookGetRefCommand{} + +func (c AddressBookGetRefCommand) GetCommand() Command { return CommandAddressBookGet } +func (c AddressBookGetRefCommand) GetObjectType() ObjectType { return AddressBookType } +func (c AddressBookGetRefCommand) GetResponse() GetResponse { return AddressBookGetResponse{} } + type AddressBookGetResponse struct { AccountId string `json:"accountId"` State State `json:"state,omitempty"` @@ -5285,6 +5733,11 @@ type AddressBookGetResponse struct { NotFound []string `json:"notFound,omitempty"` } +var _ GetResponse = &AddressBookGetResponse{} + +func (r AddressBookGetResponse) GetState() State { return r.State } +func (r AddressBookGetResponse) GetNotFound() []string { return r.NotFound } + type AddressBookChange struct { // The user-visible name of the AddressBook. // @@ -5329,6 +5782,8 @@ type AddressBookChange struct { ShareWith map[string]AddressBookRights `json:"shareWith,omitempty"` } +var _ Change = AddressBookChange{} + func (a AddressBookChange) AsPatch() PatchObject { p := PatchObject{} if a.Name != nil { @@ -5354,6 +5809,12 @@ type AddressBookSetCommand struct { Destroy []string `json:"destroy,omitempty"` } +var _ SetCommand = &AddressBookSetCommand{} + +func (c AddressBookSetCommand) GetCommand() Command { return CommandAddressBookSet } +func (c AddressBookSetCommand) GetObjectType() ObjectType { return AddressBookType } +func (c AddressBookSetCommand) GetResponse() SetResponse { return AddressBookSetResponse{} } + type AddressBookSetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -5401,6 +5862,14 @@ type AddressBookSetResponse struct { NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } +var _ SetResponse = &AddressBookSetResponse{} + +func (r AddressBookSetResponse) GetOldState() State { return r.OldState } +func (r AddressBookSetResponse) GetNewState() State { return r.NewState } +func (r AddressBookSetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r AddressBookSetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r AddressBookSetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } + type AddressBookChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -5424,6 +5893,12 @@ type AddressBookChangesCommand struct { MaxChanges *uint `json:"maxChanges,omitzero"` } +var _ ChangesCommand = &AddressBookChangesCommand{} + +func (c AddressBookChangesCommand) GetCommand() Command { return CommandAddressBookChanges } +func (c AddressBookChangesCommand) GetObjectType() ObjectType { return AddressBookType } +func (c AddressBookChangesCommand) GetResponse() ChangesResponse { return AddressBookChangesResponse{} } + type AddressBookChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -5449,6 +5924,15 @@ type AddressBookChangesResponse struct { Destroyed []string `json:"destroyed,omitempty"` } +var _ ChangesResponse = &AddressBookChangesResponse{} + +func (r AddressBookChangesResponse) GetOldState() State { return r.OldState } +func (r AddressBookChangesResponse) GetNewState() State { return r.NewState } +func (r AddressBookChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r AddressBookChangesResponse) GetCreated() []string { return r.Created } +func (r AddressBookChangesResponse) GetUpdated() []string { return r.Updated } +func (r AddressBookChangesResponse) GetDestroyed() []string { return r.Destroyed } + type ContactCardComparator struct { // The name of the property on the objects to compare. Property string `json:"property,omitempty"` @@ -5688,6 +6172,12 @@ type ContactCardQueryCommand struct { CalculateTotal bool `json:"calculateTotal,omitzero"` } +var _ QueryCommand = &ContactCardQueryCommand{} + +func (c ContactCardQueryCommand) GetCommand() Command { return CommandContactCardQuery } +func (c ContactCardQueryCommand) GetObjectType() ObjectType { return ContactCardType } +func (c ContactCardQueryCommand) GetResponse() QueryResponse { return ContactCardQueryResponse{} } + type ContactCardQueryResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -5738,6 +6228,10 @@ type ContactCardQueryResponse struct { Limit uint `json:"limit,omitempty,omitzero"` } +var _ QueryResponse = &ContactCardQueryResponse{} + +func (r ContactCardQueryResponse) GetQueryState() State { return r.QueryState } + type ContactCardGetCommand struct { // The ids of the ContactCard objects to return. // @@ -5756,6 +6250,12 @@ type ContactCardGetCommand struct { Properties []string `json:"properties,omitempty"` } +var _ GetCommand = &ContactCardGetCommand{} + +func (c ContactCardGetCommand) GetCommand() Command { return CommandContactCardGet } +func (c ContactCardGetCommand) GetObjectType() ObjectType { return ContactCardType } +func (c ContactCardGetCommand) GetResponse() GetResponse { return ContactCardGetResponse{} } + type ContactCardGetRefCommand struct { // The ids of the ContactCard objects to return. // @@ -5774,6 +6274,12 @@ type ContactCardGetRefCommand struct { Properties []string `json:"properties,omitempty"` } +var _ GetCommand = &ContactCardGetRefCommand{} + +func (c ContactCardGetRefCommand) GetCommand() Command { return CommandContactCardGet } +func (c ContactCardGetRefCommand) GetObjectType() ObjectType { return ContactCardType } +func (c ContactCardGetRefCommand) GetResponse() GetResponse { return ContactCardGetResponse{} } + type ContactCardGetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -5801,6 +6307,11 @@ type ContactCardGetResponse struct { NotFound []string `json:"notFound"` } +var _ GetResponse = &ContactCardGetResponse{} + +func (r ContactCardGetResponse) GetState() State { return r.State } +func (r ContactCardGetResponse) GetNotFound() []string { return r.NotFound } + type ContactCardChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -5818,6 +6329,12 @@ type ContactCardChangesCommand struct { MaxChanges *uint `json:"maxChanges,omitempty"` } +var _ ChangesCommand = &ContactCardChangesCommand{} + +func (c ContactCardChangesCommand) GetCommand() Command { return CommandContactCardChanges } +func (c ContactCardChangesCommand) GetObjectType() ObjectType { return ContactCardType } +func (c ContactCardChangesCommand) GetResponse() ChangesResponse { return ContactCardChangesResponse{} } + type ContactCardChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -5842,6 +6359,15 @@ type ContactCardChangesResponse struct { Destroyed []string `json:"destroyed,omitempty"` } +var _ ChangesResponse = &ContactCardChangesResponse{} + +func (r ContactCardChangesResponse) GetOldState() State { return r.OldState } +func (r ContactCardChangesResponse) GetNewState() State { return r.NewState } +func (r ContactCardChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r ContactCardChangesResponse) GetCreated() []string { return r.Created } +func (r ContactCardChangesResponse) GetUpdated() []string { return r.Updated } +func (r ContactCardChangesResponse) GetDestroyed() []string { return r.Destroyed } + type ContactCardUpdate map[string]any type ContactCardSetCommand struct { @@ -5897,6 +6423,12 @@ type ContactCardSetCommand struct { Destroy []string `json:"destroy,omitempty"` } +var _ SetCommand = &ContactCardSetCommand{} + +func (c ContactCardSetCommand) GetCommand() Command { return CommandContactCardSet } +func (c ContactCardSetCommand) GetObjectType() ObjectType { return ContactCardType } +func (c ContactCardSetCommand) GetResponse() SetResponse { return ContactCardSetResponse{} } + type ContactCardSetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -5944,6 +6476,14 @@ type ContactCardSetResponse struct { NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } +var _ SetResponse = &ContactCardSetResponse{} + +func (r ContactCardSetResponse) GetOldState() State { return r.OldState } +func (r ContactCardSetResponse) GetNewState() State { return r.NewState } +func (r ContactCardSetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r ContactCardSetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r ContactCardSetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } + type CalendarEventParseCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -5957,6 +6497,11 @@ type CalendarEventParseCommand struct { Properties []string `json:"properties,omitempty"` } +var _ JmapCommand = &CalendarEventParseCommand{} + +func (c CalendarEventParseCommand) GetCommand() Command { return CommandCalendarEventParse } +func (c CalendarEventParseCommand) GetObjectType() ObjectType { return CalendarEventType } + type CalendarEventParseResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -5978,11 +6523,23 @@ type CalendarGetCommand struct { Ids []string `json:"ids,omitempty"` } +var _ GetCommand = &CalendarGetCommand{} + +func (c CalendarGetCommand) GetCommand() Command { return CommandCalendarGet } +func (c CalendarGetCommand) GetObjectType() ObjectType { return CalendarType } +func (c CalendarGetCommand) GetResponse() GetResponse { return CalendarGetResponse{} } + type CalendarGetRefCommand struct { AccountId string `json:"accountId"` IdsRef *ResultReference `json:"#ids,omitempty"` } +var _ GetCommand = &CalendarGetRefCommand{} + +func (c CalendarGetRefCommand) GetCommand() Command { return CommandCalendarGet } +func (c CalendarGetRefCommand) GetObjectType() ObjectType { return CalendarType } +func (c CalendarGetRefCommand) GetResponse() GetResponse { return CalendarGetResponse{} } + type CalendarGetResponse struct { AccountId string `json:"accountId"` State State `json:"state,omitempty"` @@ -5990,6 +6547,11 @@ type CalendarGetResponse struct { NotFound []string `json:"notFound,omitempty"` } +var _ GetResponse = &CalendarGetResponse{} + +func (r CalendarGetResponse) GetState() State { return r.State } +func (r CalendarGetResponse) GetNotFound() []string { return r.NotFound } + type CalendarSetCommand struct { AccountId string `json:"accountId"` IfInState string `json:"ifInState,omitempty"` @@ -5998,6 +6560,12 @@ type CalendarSetCommand struct { Destroy []string `json:"destroy,omitempty"` } +var _ SetCommand = &CalendarSetCommand{} + +func (c CalendarSetCommand) GetCommand() Command { return CommandCalendarSet } +func (c CalendarSetCommand) GetObjectType() ObjectType { return CalendarType } +func (c CalendarSetCommand) GetResponse() SetResponse { return CalendarSetResponse{} } + type CalendarSetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -6045,6 +6613,14 @@ type CalendarSetResponse struct { NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } +var _ SetResponse = &CalendarSetResponse{} + +func (r CalendarSetResponse) GetOldState() State { return r.OldState } +func (r CalendarSetResponse) GetNewState() State { return r.NewState } +func (r CalendarSetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r CalendarSetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r CalendarSetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } + type CalendarChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -6068,6 +6644,12 @@ type CalendarChangesCommand struct { MaxChanges *uint `json:"maxChanges,omitzero"` } +var _ ChangesCommand = &CalendarChangesCommand{} + +func (c CalendarChangesCommand) GetCommand() Command { return CommandCalendarChanges } +func (c CalendarChangesCommand) GetObjectType() ObjectType { return CalendarType } +func (c CalendarChangesCommand) GetResponse() ChangesResponse { return CalendarChangesResponse{} } + type CalendarChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -6092,6 +6674,15 @@ type CalendarChangesResponse struct { Destroyed []string `json:"destroyed,omitempty"` } +var _ ChangesResponse = &CalendarChangesResponse{} + +func (r CalendarChangesResponse) GetOldState() State { return r.OldState } +func (r CalendarChangesResponse) GetNewState() State { return r.NewState } +func (r CalendarChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r CalendarChangesResponse) GetCreated() []string { return r.Created } +func (r CalendarChangesResponse) GetUpdated() []string { return r.Updated } +func (r CalendarChangesResponse) GetDestroyed() []string { return r.Destroyed } + type CalendarEventComparator struct { // The name of the property on the objects to compare. Property string `json:"property,omitempty"` @@ -6283,6 +6874,12 @@ type CalendarEventQueryCommand struct { CalculateTotal bool `json:"calculateTotal,omitempty" doc:"opt" default:"false"` } +var _ QueryCommand = &CalendarEventQueryCommand{} + +func (c CalendarEventQueryCommand) GetCommand() Command { return CommandCalendarEventQuery } +func (c CalendarEventQueryCommand) GetObjectType() ObjectType { return CalendarEventType } +func (c CalendarEventQueryCommand) GetResponse() QueryResponse { return CalendarEventQueryResponse{} } + type CalendarEventQueryResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -6333,6 +6930,10 @@ type CalendarEventQueryResponse struct { Limit uint `json:"limit,omitempty,omitzero"` } +var _ QueryResponse = &CalendarEventQueryResponse{} + +func (r CalendarEventQueryResponse) GetQueryState() State { return r.QueryState } + type CalendarEventGetCommand struct { // The ids of the CalendarEvent objects to return. // @@ -6351,6 +6952,12 @@ type CalendarEventGetCommand struct { Properties []string `json:"properties,omitempty"` } +var _ GetCommand = &CalendarEventGetCommand{} + +func (c CalendarEventGetCommand) GetCommand() Command { return CommandCalendarEventGet } +func (c CalendarEventGetCommand) GetObjectType() ObjectType { return CalendarEventType } +func (c CalendarEventGetCommand) GetResponse() GetResponse { return CalendarEventGetResponse{} } + type CalendarEventGetRefCommand struct { // The ids of the CalendarEvent objects to return. // @@ -6369,6 +6976,12 @@ type CalendarEventGetRefCommand struct { Properties []string `json:"properties,omitempty"` } +var _ GetCommand = &CalendarEventGetRefCommand{} + +func (c CalendarEventGetRefCommand) GetCommand() Command { return CommandCalendarEventGet } +func (c CalendarEventGetRefCommand) GetObjectType() ObjectType { return CalendarEventType } +func (c CalendarEventGetRefCommand) GetResponse() GetResponse { return CalendarEventGetResponse{} } + type CalendarEventGetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -6396,6 +7009,11 @@ type CalendarEventGetResponse struct { NotFound []string `json:"notFound"` } +var _ GetResponse = &CalendarEventGetResponse{} + +func (r CalendarEventGetResponse) GetState() State { return r.State } +func (r CalendarEventGetResponse) GetNotFound() []string { return r.NotFound } + type CalendarEventChangesCommand struct { // The id of the account to use. AccountId string `json:"accountId"` @@ -6419,6 +7037,12 @@ type CalendarEventChangesCommand struct { MaxChanges *uint `json:"maxChanges,omitzero"` } +var _ ChangesCommand = &CalendarEventChangesCommand{} + +func (c CalendarEventChangesCommand) GetCommand() Command { return CommandCalendarEventChanges } +func (c CalendarEventChangesCommand) GetObjectType() ObjectType { return CalendarEventType } +func (c CalendarEventChangesCommand) GetResponse() ChangesResponse { return CalendarChangesResponse{} } + type CalendarEventChangesResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -6443,6 +7067,15 @@ type CalendarEventChangesResponse struct { Destroyed []string `json:"destroyed,omitempty"` } +var _ ChangesResponse = &CalendarEventChangesResponse{} + +func (r CalendarEventChangesResponse) GetOldState() State { return r.OldState } +func (r CalendarEventChangesResponse) GetNewState() State { return r.NewState } +func (r CalendarEventChangesResponse) GetHasMoreChanges() bool { return r.HasMoreChanges } +func (r CalendarEventChangesResponse) GetCreated() []string { return r.Created } +func (r CalendarEventChangesResponse) GetUpdated() []string { return r.Updated } +func (r CalendarEventChangesResponse) GetDestroyed() []string { return r.Destroyed } + type CalendarEventUpdate map[string]any type CalendarEventSetCommand struct { @@ -6498,6 +7131,12 @@ type CalendarEventSetCommand struct { Destroy []string `json:"destroy,omitempty"` } +var _ SetCommand = &CalendarEventSetCommand{} + +func (c CalendarEventSetCommand) GetCommand() Command { return CommandCalendarEventSet } +func (c CalendarEventSetCommand) GetObjectType() ObjectType { return CalendarEventType } +func (c CalendarEventSetCommand) GetResponse() SetResponse { return CalendarSetResponse{} } + type CalendarEventSetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -6545,16 +7184,36 @@ type CalendarEventSetResponse struct { NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"` } +var _ SetResponse = &CalendarEventSetResponse{} + +func (r CalendarEventSetResponse) GetOldState() State { return r.OldState } +func (r CalendarEventSetResponse) GetNewState() State { return r.NewState } +func (r CalendarEventSetResponse) GetNotCreated() map[string]SetError { return r.NotCreated } +func (r CalendarEventSetResponse) GetNotUpdated() map[string]SetError { return r.NotUpdated } +func (r CalendarEventSetResponse) GetNotDestroyed() map[string]SetError { return r.NotDestroyed } + type PrincipalGetCommand struct { AccountId string `json:"accountId"` Ids []string `json:"ids,omitempty"` } +var _ GetCommand = &PrincipalGetCommand{} + +func (c PrincipalGetCommand) GetCommand() Command { return CommandPrincipalGet } +func (c PrincipalGetCommand) GetObjectType() ObjectType { return PrincipalType } +func (c PrincipalGetCommand) GetResponse() GetResponse { return PrincipalGetResponse{} } + type PrincipalGetRefCommand struct { AccountId string `json:"accountId"` IdsRef *ResultReference `json:"#ids,omitempty"` } +var _ GetCommand = &PrincipalGetRefCommand{} + +func (c PrincipalGetRefCommand) GetCommand() Command { return CommandPrincipalGet } +func (c PrincipalGetRefCommand) GetObjectType() ObjectType { return PrincipalType } +func (c PrincipalGetRefCommand) GetResponse() GetResponse { return PrincipalGetResponse{} } + type PrincipalGetResponse struct { // The id of the account used for the call. AccountId string `json:"accountId"` @@ -6579,6 +7238,11 @@ type PrincipalGetResponse struct { NotFound []string `json:"notFound"` } +var _ GetResponse = &PrincipalGetResponse{} + +func (r PrincipalGetResponse) GetState() State { return r.State } +func (r PrincipalGetResponse) GetNotFound() []string { return r.NotFound } + type PrincipalFilterElement interface { _isAPrincipalFilterElement() // marker method } @@ -6678,6 +7342,10 @@ type PrincipalQueryResponse struct { Limit int `json:"limit,omitzero"` } +var _ QueryResponse = &PrincipalQueryResponse{} + +func (r PrincipalQueryResponse) GetQueryState() State { return r.QueryState } + type ErrorResponse struct { Type string `json:"type"` Description string `json:"description,omitempty"` diff --git a/pkg/jmap/model_test.go b/pkg/jmap/model_test.go new file mode 100644 index 0000000000..e44b1f9234 --- /dev/null +++ b/pkg/jmap/model_test.go @@ -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) + } +} diff --git a/pkg/jmap/templates.go b/pkg/jmap/templates.go index f2fb8021b6..117fee7ac9 100644 --- a/pkg/jmap/templates.go +++ b/pkg/jmap/templates.go @@ -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 + }) +} diff --git a/pkg/jmap/tools.go b/pkg/jmap/tools.go index 284f789221..678ff9ab96 100644 --- a/pkg/jmap/tools.go +++ b/pkg/jmap/tools.go @@ -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 } diff --git a/pkg/structs/structs.go b/pkg/structs/structs.go index 80aaf457fe..6131817532 100644 --- a/pkg/structs/structs.go +++ b/pkg/structs/structs.go @@ -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 {