groupware: add /bootstrap

* add a GET /accounts/{a}/boostrap URI that delivers the same as GET /
   but also mailboxes for a given account, in case the UI remembers the
   last used account identifier, to avoid an additional roundtrip

 * streamline the use of simpleError()

 * add logging of errors at the calling site

 * add logging of evictions of Sessions from the cache

 * change default Session cache TTL to 5min instead of 30sec
This commit is contained in:
Pascal Bleser
2025-08-21 15:27:45 +02:00
parent 7af4a01de6
commit 0e8bbe7038
17 changed files with 370 additions and 126 deletions

View File

@@ -26,18 +26,21 @@ func (j *Client) GetBlob(accountId string, session *Session, ctx context.Context
}, "0"),
)
if err != nil {
return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (BlobResponse, Error) {
var response BlobGetResponse
err = retrieveResponseMatchParameters(body, CommandBlobGet, "0", &response)
if err != nil {
return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(response.List) != 1 {
return BlobResponse{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Msgf("%T.List has %v entries instead of 1", response, len(response.List))
return BlobResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
get := response.List[0]
return BlobResponse{Blob: &get, State: response.State, SessionState: body.SessionState}, nil
@@ -104,32 +107,38 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
invocation(CommandBlobGet, getHash, "1"),
)
if err != nil {
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UploadedBlob, Error) {
var uploadResponse BlobUploadResponse
err = retrieveResponseMatchParameters(body, CommandBlobUpload, "0", &uploadResponse)
if err != nil {
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var getResponse BlobGetResponse
err = retrieveResponseMatchParameters(body, CommandBlobGet, "1", &getResponse)
if err != nil {
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(uploadResponse.Created) != 1 {
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Msgf("%T.Created has %v entries instead of 1", uploadResponse, len(uploadResponse.Created))
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
upload, ok := uploadResponse.Created["0"]
if !ok {
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Msgf("%T.Created has no item '0'", uploadResponse)
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(getResponse.List) != 1 {
return UploadedBlob{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Msgf("%T.List has %v entries instead of 1", getResponse, len(getResponse.List))
return UploadedBlob{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
get := getResponse.List[0]

View File

@@ -42,13 +42,15 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
cmd, err := request(invocation(CommandEmailGet, get, "0"))
if err != nil {
return Emails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return Emails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) {
var response EmailGetResponse
err = retrieveResponseMatchParameters(body, CommandEmailGet, "0", &response)
if err != nil {
return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return Emails{Emails: response.List, State: response.State, SessionState: body.SessionState}, nil
})
@@ -88,19 +90,22 @@ func (j *Client) GetAllEmails(accountId string, session *Session, ctx context.Co
invocation(CommandEmailGet, get, "1"),
)
if err != nil {
return Emails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return Emails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Emails, Error) {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse)
if err != nil {
return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var getResponse EmailGetResponse
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &getResponse)
if err != nil {
return Emails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return Emails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return Emails{
@@ -161,26 +166,30 @@ func (j *Client) GetEmailsInMailboxSince(accountId string, session *Session, ctx
invocation(CommandEmailGet, getUpdated, "2"),
)
if err != nil {
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) {
var mailboxResponse MailboxChangesResponse
err = retrieveResponseMatchParameters(body, CommandMailboxChanges, "0", &mailboxResponse)
if err != nil {
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var createdResponse EmailGetResponse
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse)
if err != nil {
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var updatedResponse EmailGetResponse
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse)
if err != nil {
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return EmailsSince{
@@ -232,26 +241,29 @@ func (j *Client) GetEmailsSince(accountId string, session *Session, ctx context.
invocation(CommandEmailGet, getUpdated, "2"),
)
if err != nil {
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailsSince, Error) {
var changesResponse EmailChangesResponse
err = retrieveResponseMatchParameters(body, CommandEmailChanges, "0", &changesResponse)
if err != nil {
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var createdResponse EmailGetResponse
err = retrieveResponseMatchParameters(body, CommandEmailGet, "1", &createdResponse)
if err != nil {
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var updatedResponse EmailGetResponse
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &updatedResponse)
if err != nil {
return EmailsSince{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return EmailsSince{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return EmailsSince{
@@ -311,20 +323,23 @@ func (j *Client) QueryEmailSnippets(accountId string, filter EmailFilterElement,
)
if err != nil {
return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailSnippetQueryResult, Error) {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse)
if err != nil {
return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var snippetResponse SearchSnippetGetResponse
err = retrieveResponseMatchParameters(body, CommandSearchSnippetGet, "1", &snippetResponse)
if err != nil {
return EmailSnippetQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return EmailSnippetQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return EmailSnippetQueryResult{
@@ -401,26 +416,27 @@ func (j *Client) QueryEmails(accountId string, filter EmailFilterElement, sessio
)
if err != nil {
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (EmailQueryResult, Error) {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(body, CommandEmailQuery, "0", &queryResponse)
if err != nil {
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var snippetResponse SearchSnippetGetResponse
err = retrieveResponseMatchParameters(body, CommandSearchSnippetGet, "1", &snippetResponse)
if err != nil {
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var emailsResponse EmailGetResponse
err = retrieveResponseMatchParameters(body, CommandEmailGet, "2", &emailsResponse)
if err != nil {
return EmailQueryResult{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
return EmailQueryResult{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
snippetsById := map[string][]SearchSnippet{}
@@ -496,32 +512,37 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
invocation(CommandBlobGet, getHash, "1"),
)
if err != nil {
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UploadedEmail, Error) {
var uploadResponse BlobUploadResponse
err = retrieveResponseMatchParameters(body, CommandBlobUpload, "0", &uploadResponse)
if err != nil {
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var getResponse BlobGetResponse
err = retrieveResponseMatchParameters(body, CommandBlobGet, "1", &getResponse)
if err != nil {
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(uploadResponse.Created) != 1 {
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Msgf("%T.Created has %v elements instead of 1", uploadResponse, len(uploadResponse.Created))
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
upload, ok := uploadResponse.Created["0"]
if !ok {
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Msgf("%T.Created has no element '0'", uploadResponse)
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(getResponse.List) != 1 {
return UploadedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Msgf("%T.List has %v elements instead of 1", getResponse, len(getResponse.List))
return UploadedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
get := getResponse.List[0]
@@ -554,14 +575,16 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Sessi
}, "0"),
)
if err != nil {
return CreatedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (CreatedEmail, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse)
if err != nil {
return CreatedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(setResponse.NotCreated) > 0 {
@@ -571,12 +594,14 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, session *Sessi
setErr, notok := setResponse.NotCreated["c"]
if notok {
logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr)
return CreatedEmail{}, setErrorError(setErr, EmailType)
}
created, ok := setResponse.Created["c"]
if !ok {
err = fmt.Errorf("failed to find %s in %s response", string(EmailType), string(CommandEmailSet))
logger.Error().Err(err)
return CreatedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
@@ -612,14 +637,16 @@ func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate,
}, "0"),
)
if err != nil {
return UpdatedEmails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return UpdatedEmails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (UpdatedEmails, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse)
if err != nil {
return UpdatedEmails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return UpdatedEmails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(setResponse.NotUpdated) != len(updates) {
// error occured
@@ -648,14 +675,16 @@ func (j *Client) DeleteEmails(accountId string, destroy []string, session *Sessi
}, "0"),
)
if err != nil {
return DeletedEmails{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return DeletedEmails{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (DeletedEmails, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse)
if err != nil {
return DeletedEmails{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return DeletedEmails{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(setResponse.NotDestroyed) != len(destroy) {
// error occured
@@ -724,14 +753,16 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
invocation(CommandEmailSubmissionGet, get, "1"),
)
if err != nil {
return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (SubmittedEmail, Error) {
var submissionResponse EmailSubmissionSetResponse
err = retrieveResponseMatchParameters(body, CommandEmailSubmissionSet, "0", &submissionResponse)
if err != nil {
return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(submissionResponse.NotCreated) > 0 {
@@ -747,13 +778,15 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(body, CommandEmailSet, "0", &setResponse)
if err != nil {
return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
var getResponse EmailSubmissionGetResponse
err = retrieveResponseMatchParameters(body, CommandEmailSubmissionGet, "1", &getResponse)
if err != nil {
return SubmittedEmail{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return SubmittedEmail{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(getResponse.List) != 1 {

View File

@@ -21,16 +21,21 @@ func (j *Client) GetIdentity(accountId string, session *Session, ctx context.Con
logger = j.logger(aid, "GetIdentity", session, logger)
cmd, err := request(invocation(CommandIdentityGet, IdentityGetCommand{AccountId: aid}, "0"))
if err != nil {
return Identities{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return Identities{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Identities, Error) {
var response IdentityGetResponse
err = retrieveResponseMatchParameters(body, CommandIdentityGet, "0", &response)
if err != nil {
logger.Error().Err(err)
return Identities{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return Identities{
Identities: response.List,
State: response.State,
SessionState: body.SessionState,
}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}, nil
})
}
@@ -55,7 +60,8 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex
cmd, err := request(calls...)
if err != nil {
return IdentitiesGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return IdentitiesGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (IdentitiesGetResponse, Error) {
identities := make(map[string][]Identity, len(uniqueAccountIds))
@@ -65,6 +71,7 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex
var response IdentityGetResponse
err = retrieveResponseMatchParameters(body, CommandIdentityGet, strconv.Itoa(i), &response)
if err != nil {
logger.Error().Err(err)
return IdentitiesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
} else {
identities[accountId] = response.List
@@ -81,3 +88,63 @@ func (j *Client) GetIdentities(accountIds []string, session *Session, ctx contex
}, nil
})
}
type IdentitiesAndMailboxesGetResponse struct {
Identities map[string][]Identity `json:"identities,omitempty"`
NotFound []string `json:"notFound,omitempty"`
State string `json:"state"`
SessionState string `json:"sessionState"`
Mailboxes []Mailbox `json:"mailboxes"`
}
func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger) (IdentitiesAndMailboxesGetResponse, Error) {
uniqueAccountIds := structs.Uniq(accountIds)
logger = j.loggerParams("", "GetIdentitiesAndMailboxes", session, logger, func(l zerolog.Context) zerolog.Context {
return l.Array(logAccountId, log.SafeStringArray(uniqueAccountIds))
})
calls := make([]Invocation, len(uniqueAccountIds)+1)
calls[0] = invocation(CommandMailboxGet, MailboxGetCommand{AccountId: mailboxAccountId}, "0")
for i, accountId := range uniqueAccountIds {
calls[i+1] = invocation(CommandIdentityGet, IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i+1))
}
cmd, err := request(calls...)
if err != nil {
logger.Error().Err(err)
return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (IdentitiesAndMailboxesGetResponse, Error) {
identities := make(map[string][]Identity, len(uniqueAccountIds))
lastState := ""
notFound := []string{}
for i, accountId := range uniqueAccountIds {
var response IdentityGetResponse
err = retrieveResponseMatchParameters(body, CommandIdentityGet, strconv.Itoa(i+1), &response)
if err != nil {
logger.Error().Err(err)
return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
} else {
identities[accountId] = response.List
}
lastState = response.State
notFound = append(notFound, response.NotFound...)
}
var mailboxResponse MailboxGetResponse
err = retrieveResponseMatchParameters(body, CommandMailboxGet, "0", &mailboxResponse)
if err != nil {
logger.Error().Err(err)
return IdentitiesAndMailboxesGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return IdentitiesAndMailboxesGetResponse{
Identities: identities,
NotFound: structs.Uniq(notFound),
State: lastState,
SessionState: body.SessionState,
Mailboxes: mailboxResponse.List,
}, nil
})
}

View File

@@ -19,13 +19,15 @@ func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Cont
logger = j.logger(aid, "GetMailbox", session, logger)
cmd, err := request(invocation(CommandMailboxGet, MailboxGetCommand{AccountId: aid, Ids: ids}, "0"))
if err != nil {
return MailboxesResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return MailboxesResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (MailboxesResponse, Error) {
var response MailboxGetResponse
err = retrieveResponseMatchParameters(body, CommandMailboxGet, "0", &response)
if err != nil {
logger.Error().Err(err)
return MailboxesResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
@@ -77,14 +79,16 @@ func (j *Client) SearchMailboxes(accountId string, session *Session, ctx context
}, "1"),
)
if err != nil {
return Mailboxes{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return Mailboxes{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (Mailboxes, Error) {
var response MailboxGetResponse
err = retrieveResponseMatchParameters(body, CommandMailboxGet, "1", &response)
if err != nil {
return Mailboxes{}, SimpleError{code: JmapErrorInvalidJmapResponsePayload, err: err}
logger.Error().Err(err)
return Mailboxes{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return Mailboxes{Mailboxes: response.List, State: response.State, SessionState: body.SessionState}, nil
})

View File

@@ -18,12 +18,17 @@ func (j *Client) GetVacationResponse(accountId string, session *Session, ctx con
logger = j.logger(aid, "GetVacationResponse", session, logger)
cmd, err := request(invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: aid}, "0"))
if err != nil {
return VacationResponseGetResponse{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return VacationResponseGetResponse{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseGetResponse, Error) {
var response VacationResponseGetResponse
err = retrieveResponseMatchParameters(body, CommandVacationResponseGet, "0", &response)
return response, simpleError(err, JmapErrorInvalidJmapResponsePayload)
if err != nil {
logger.Error().Err(err)
return VacationResponseGetResponse{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
return response, nil
})
}
@@ -80,29 +85,34 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
invocation(CommandVacationResponseGet, VacationResponseGetCommand{AccountId: aid}, "1"),
)
if err != nil {
return VacationResponseChange{}, SimpleError{code: JmapErrorInvalidJmapRequestPayload, err: err}
logger.Error().Err(err)
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapRequestPayload)
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, func(body *Response) (VacationResponseChange, Error) {
var setResponse VacationResponseSetResponse
err = retrieveResponseMatchParameters(body, CommandVacationResponseSet, "0", &setResponse)
if err != nil {
logger.Error().Err(err)
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
setErr, notok := setResponse.NotCreated[vacationResponseId]
if notok {
// this means that the VacationResponse was not updated
logger.Error().Msgf("%T.NotCreated contains an error: %v", setResponse, setErr)
return VacationResponseChange{}, setErrorError(setErr, VacationResponseType)
}
var getResponse VacationResponseGetResponse
err = retrieveResponseMatchParameters(body, CommandVacationResponseGet, "1", &getResponse)
if err != nil {
logger.Error().Err(err)
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(getResponse.List) != 1 {
err = fmt.Errorf("failed to find %s in %s response", string(VacationResponseType), string(CommandVacationResponseGet))
logger.Error().Err(err)
return VacationResponseChange{}, simpleError(err, JmapErrorInvalidJmapResponsePayload)
}

View File

@@ -32,9 +32,9 @@ func (j *Client) AddSessionEventListener(listener SessionEventListener) {
j.sessionEventListeners.add(listener)
}
func (j *Client) onSessionOutdated(session *Session) {
func (j *Client) onSessionOutdated(session *Session, newSessionState string) {
j.sessionEventListeners.signal(func(listener SessionEventListener) {
listener.OnSessionOutdated(session)
listener.OnSessionOutdated(session, newSessionState)
})
}

View File

@@ -8,7 +8,7 @@ import (
)
type SessionEventListener interface {
OnSessionOutdated(session *Session)
OnSessionOutdated(session *Session, newSessionState string)
}
// Cached user related information

View File

@@ -42,7 +42,7 @@ func command[T any](api ApiClient,
logger *log.Logger,
ctx context.Context,
session *Session,
sessionOutdatedHandler func(session *Session),
sessionOutdatedHandler func(session *Session, newState string),
request Request,
mapper func(body *Response) (T, Error)) (T, Error) {
@@ -62,7 +62,7 @@ func command[T any](api ApiClient,
if response.SessionState != session.State {
if sessionOutdatedHandler != nil {
sessionOutdatedHandler(session)
sessionOutdatedHandler(session, response.SessionState)
}
}

View File

@@ -2,6 +2,9 @@
package structs
import (
"maps"
"slices"
orderedmap "github.com/wk8/go-ordered-map"
)
@@ -30,3 +33,11 @@ func Uniq[T comparable](source []T) []T {
}
return set
}
func Keys[K comparable, V any](source map[K]V) []K {
if source == nil {
var zero []K
return zero
}
return slices.Collect(maps.Keys(source))
}

View File

@@ -83,3 +83,19 @@ func TestUniqWithStructs(t *testing.T) {
})
}
}
func TestKeys(t *testing.T) {
tests := []struct {
input map[int]string
expected []int
}{
{map[int]string{5: "cinq", 1: "un", 3: "trois", 4: "vier"}, []int{5, 1, 3, 4}},
{map[int]string{1: "un"}, []int{1}},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) {
result := Keys(tt.input)
assert.ElementsMatch(t, tt.expected, result)
})
}
}