groupware: refactor for conciseness

* introduce jmap.Context to hold multiple parameters and shorten
   function calls

 * introduce SearchResultsTemplate
This commit is contained in:
Pascal Bleser
2026-04-09 18:35:29 +02:00
parent 3449b5465b
commit b6cedcbe90
43 changed files with 1445 additions and 1431 deletions

View File

@@ -8,8 +8,23 @@ import (
"github.com/opencloud-eu/opencloud/pkg/log"
)
type Context struct {
Session *Session
Context context.Context
Logger *log.Logger
AcceptLanguage string
}
func (c Context) WithLogger(newLogger *log.Logger) Context {
return Context{Session: c.Session, Context: c.Context, AcceptLanguage: c.AcceptLanguage, Logger: newLogger}
}
func (c Context) WithContext(newContext context.Context) Context {
return Context{Session: c.Session, Context: newContext, AcceptLanguage: c.AcceptLanguage, Logger: c.Logger}
}
type ApiClient interface {
Command(ctx context.Context, logger *log.Logger, session *Session, request Request, acceptLanguage string) ([]byte, Language, Error)
Command(request Request, ctx Context) ([]byte, Language, Error)
io.Closer
}
@@ -33,8 +48,8 @@ type SessionClient interface {
}
type BlobClient interface {
UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, acceptLanguage string, content io.Reader) (UploadedBlob, Language, Error)
DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string, acceptLanguage string) (*BlobDownload, Language, Error)
UploadBinary(uploadUrl string, endpoint string, contentType string, content io.Reader, ctx Context) (UploadedBlob, Language, Error)
DownloadBinary(downloadUrl string, endpoint string, ctx Context) (*BlobDownload, Language, Error)
io.Closer
}

View File

@@ -1,21 +1,16 @@
package jmap
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/log"
)
var NS_ADDRESSBOOKS = ns(JmapContacts)
func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBookGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetAddressbooks(accountId string, ids []string, ctx Context) (AddressBookGetResponse, SessionState, State, Language, Error) {
return get(j, "GetAddressbooks", NS_ADDRESSBOOKS,
func(accountId string, ids []string) AddressBookGetCommand {
return AddressBookGetCommand{AccountId: accountId, Ids: ids}
},
AddressBookGetResponse{},
identity1,
accountId, session, ctx, logger, acceptLanguage, ids,
accountId, ids,
ctx,
)
}
@@ -23,10 +18,10 @@ type AddressBookChanges = ChangesTemplate[AddressBook]
// 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) {
func (j *Client) GetAddressbookChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (AddressBookChanges, SessionState, State, Language, Error) {
return changesA(j, "GetAddressbookChanges", NS_ADDRESSBOOKS,
func() AddressBookChangesCommand {
return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return AddressBookChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
AddressBookChangesResponse{},
AddressBookGetResponse{},
@@ -50,11 +45,11 @@ func (j *Client) GetAddressbookChanges(accountId string, session *Session, ctx c
Destroyed: destroyed,
}
},
session, ctx, logger, acceptLanguage,
ctx,
)
}
func (j *Client) CreateAddressBook(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, addressbook AddressBookChange) (*AddressBook, SessionState, State, Language, Error) {
func (j *Client) CreateAddressBook(accountId string, addressbook AddressBookChange, ctx Context) (*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}
@@ -68,21 +63,23 @@ func (j *Client) CreateAddressBook(accountId string, session *Session, ctx conte
func(resp AddressBookGetResponse) []AddressBook {
return resp.List
},
accountId, session, ctx, logger, acceptLanguage, addressbook,
accountId, addressbook,
ctx,
)
}
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) {
func (j *Client) DeleteAddressBook(accountId string, destroyIds []string, ctx Context) (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}
},
AddressBookSetResponse{},
accountId, destroyIds, session, ctx, logger, acceptLanguage,
accountId, destroyIds,
ctx,
)
}
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) {
func (j *Client) UpdateAddressBook(accountId string, id string, changes AddressBookChange, ctx Context) (AddressBook, SessionState, State, Language, Error) {
return update(j, "UpdateAddressBook", NS_ADDRESSBOOKS,
func(update map[string]PatchObject) AddressBookSetCommand {
return AddressBookSetCommand{AccountId: accountId, Update: update}
@@ -92,6 +89,7 @@ func (j *Client) UpdateAddressBook(accountId string, session *Session, ctx conte
},
func(resp AddressBookSetResponse) map[string]SetError { return resp.NotUpdated },
func(resp AddressBookGetResponse) AddressBook { return resp.List[0] },
id, changes, session, ctx, logger, acceptLanguage,
id, changes,
ctx,
)
}

View File

@@ -1,7 +1,6 @@
package jmap
import (
"context"
"encoding/base64"
"io"
"strings"
@@ -9,28 +8,31 @@ import (
"github.com/opencloud-eu/opencloud/pkg/log"
)
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(BlobGetCommand{
AccountId: accountId,
Ids: []string{id},
// add BlobPropertyData to retrieve the data
Properties: []string{BlobPropertyDigestSha256, BlobPropertyDigestSha512, BlobPropertySize},
}, "0"),
var NS_BLOB = ns(JmapBlob)
func (j *Client) GetBlobMetadata(accountId string, id string, ctx Context) (*Blob, SessionState, State, Language, Error) {
get := BlobGetCommand{
AccountId: accountId,
Ids: []string{id},
// add BlobPropertyData to retrieve the data
Properties: []string{BlobPropertyDigestSha256, BlobPropertyDigestSha512, BlobPropertySize},
}
cmd, jerr := j.request(ctx, NS_BLOB,
invocation(get, "0"),
)
if jerr != nil {
return nil, "", "", "", jerr
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*Blob, State, Error) {
return command(j, ctx, cmd, func(body *Response) (*Blob, State, Error) {
var response BlobGetResponse
err := retrieveResponseMatchParameters(logger, body, CommandBlobGet, "0", &response)
err := retrieveGet(ctx, body, get, "0", &response)
if err != nil {
return nil, "", err
}
if len(response.List) != 1 {
logger.Error().Msgf("%T.List has %v entries instead of 1", response, len(response.List))
ctx.Logger.Error().Msgf("%T.List has %v entries instead of 1", response, len(response.List))
return nil, "", jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
get := response.List[0]
@@ -45,26 +47,28 @@ type UploadedBlobWithHash struct {
Sha512 string `json:"sha:512,omitempty"`
}
func (j *Client) UploadBlobStream(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, contentType string, body io.Reader) (UploadedBlob, Language, Error) {
logger = log.From(logger.With().Str(logEndpoint, session.UploadEndpoint))
func (j *Client) UploadBlobStream(accountId string, contentType string, body io.Reader, ctx Context) (UploadedBlob, Language, Error) {
logger := log.From(ctx.Logger.With().Str(logEndpoint, ctx.Session.UploadEndpoint))
ctx = ctx.WithLogger(logger)
// TODO(pbleser-oc) use a library for proper URL template parsing
uploadUrl := strings.ReplaceAll(session.UploadUrlTemplate, "{accountId}", accountId)
return j.blob.UploadBinary(ctx, logger, session, uploadUrl, session.UploadEndpoint, contentType, acceptLanguage, body)
uploadUrl := strings.ReplaceAll(ctx.Session.UploadUrlTemplate, "{accountId}", accountId)
return j.blob.UploadBinary(uploadUrl, ctx.Session.UploadEndpoint, contentType, body, ctx)
}
func (j *Client) DownloadBlobStream(accountId string, blobId string, name string, typ string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (*BlobDownload, Language, Error) { //NOSONAR
logger = log.From(logger.With().Str(logEndpoint, session.DownloadEndpoint))
func (j *Client) DownloadBlobStream(accountId string, blobId string, name string, typ string, ctx Context) (*BlobDownload, Language, Error) { //NOSONAR
logger := log.From(ctx.Logger.With().Str(logEndpoint, ctx.Session.DownloadEndpoint))
ctx = ctx.WithLogger(logger)
// TODO(pbleser-oc) use a library for proper URL template parsing
downloadUrl := session.DownloadUrlTemplate
downloadUrl := ctx.Session.DownloadUrlTemplate
downloadUrl = strings.ReplaceAll(downloadUrl, "{accountId}", accountId)
downloadUrl = strings.ReplaceAll(downloadUrl, "{blobId}", blobId)
downloadUrl = strings.ReplaceAll(downloadUrl, "{name}", name)
downloadUrl = strings.ReplaceAll(downloadUrl, "{type}", typ)
logger = log.From(logger.With().Str(logDownloadUrl, downloadUrl).Str(logBlobId, blobId))
return j.blob.DownloadBinary(ctx, logger, session, downloadUrl, session.DownloadEndpoint, acceptLanguage)
return j.blob.DownloadBinary(downloadUrl, ctx.Session.DownloadEndpoint, ctx)
}
func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte, contentType string) (UploadedBlobWithHash, SessionState, State, Language, Error) {
func (j *Client) UploadBlob(accountId string, data []byte, contentType string, ctx Context) (UploadedBlobWithHash, SessionState, State, Language, Error) {
encoded := base64.StdEncoding.EncodeToString(data)
upload := BlobUploadCommand{
@@ -89,7 +93,7 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
Properties: []string{BlobPropertyDigestSha512},
}
cmd, jerr := j.request(session, logger, ns(JmapBlob),
cmd, jerr := j.request(ctx, ns(JmapBlob),
invocation(upload, "0"),
invocation(getHash, "1"),
)
@@ -97,31 +101,31 @@ func (j *Client) UploadBlob(accountId string, session *Session, ctx context.Cont
return UploadedBlobWithHash{}, "", "", "", jerr
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedBlobWithHash, State, Error) {
return command(j, ctx, cmd, func(body *Response) (UploadedBlobWithHash, State, Error) {
var uploadResponse BlobUploadResponse
err := retrieveResponseMatchParameters(logger, body, upload.GetCommand(), "0", &uploadResponse)
err := retrieveUpload(ctx, body, upload, "0", &uploadResponse)
if err != nil {
return UploadedBlobWithHash{}, "", err
}
var getResponse BlobGetResponse
err = retrieveResponseMatchParameters(logger, body, getHash.GetCommand(), "1", &getResponse)
err = retrieveGet(ctx, body, getHash, "1", &getResponse)
if err != nil {
return UploadedBlobWithHash{}, "", err
}
if len(uploadResponse.Created) != 1 {
logger.Error().Msgf("%T.Created has %v entries instead of 1", uploadResponse, len(uploadResponse.Created))
ctx.Logger.Error().Msgf("%T.Created has %v entries instead of 1", uploadResponse, len(uploadResponse.Created))
return UploadedBlobWithHash{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
upload, ok := uploadResponse.Created["0"]
if !ok {
logger.Error().Msgf("%T.Created has no item '0'", uploadResponse)
ctx.Logger.Error().Msgf("%T.Created has no item '0'", uploadResponse)
return UploadedBlobWithHash{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(getResponse.List) != 1 {
logger.Error().Msgf("%T.List has %v entries instead of 1", getResponse, len(getResponse.List))
ctx.Logger.Error().Msgf("%T.List has %v entries instead of 1", getResponse, len(getResponse.List))
return UploadedBlobWithHash{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
get := getResponse.List[0]

View File

@@ -1,9 +1,6 @@
package jmap
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
@@ -14,10 +11,11 @@ type AccountBootstrapResult struct {
var NS_MAIL_QUOTA = ns(JmapMail, JmapQuota)
func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]AccountBootstrapResult, SessionState, State, Language, Error) { //NOSONAR
func (j *Client) GetBootstrap(accountIds []string, ctx Context) (map[string]AccountBootstrapResult, SessionState, State, Language, Error) { //NOSONAR
uniqueAccountIds := structs.Uniq(accountIds)
logger = j.logger("GetBootstrap", session, logger)
logger := j.logger("GetBootstrap", ctx)
ctx = ctx.WithLogger(logger)
calls := make([]Invocation, len(uniqueAccountIds)*2)
for i, accountId := range uniqueAccountIds {
@@ -25,18 +23,18 @@ func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context
calls[i*2+1] = invocation(QuotaGetCommand{AccountId: accountId}, mcid(accountId, "Q"))
}
cmd, err := j.request(session, logger, NS_MAIL_QUOTA, calls...)
cmd, err := j.request(ctx, NS_MAIL_QUOTA, calls...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]AccountBootstrapResult, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]AccountBootstrapResult, State, Error) {
identityPerAccount := map[string][]Identity{}
quotaPerAccount := map[string][]Quota{}
identityStatesPerAccount := map[string]State{}
quotaStatesPerAccount := map[string]State{}
for _, accountId := range uniqueAccountIds {
var identityResponse IdentityGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, mcid(accountId, "I"), &identityResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandIdentityGet, mcid(accountId, "I"), &identityResponse)
if err != nil {
return nil, "", err
} else {
@@ -45,7 +43,7 @@ func (j *Client) GetBootstrap(accountIds []string, session *Session, ctx context
}
var quotaResponse QuotaGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandQuotaGet, mcid(accountId, "Q"), &quotaResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandQuotaGet, mcid(accountId, "Q"), &quotaResponse)
if err != nil {
return nil, "", err
} else {

View File

@@ -1,27 +1,21 @@
package jmap
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
var NS_CALENDARS = ns(JmapCalendars)
func (j *Client) ParseICalendarBlob(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, blobIds []string) (CalendarEventParseResponse, SessionState, State, Language, Error) {
logger = j.logger("ParseICalendarBlob", session, logger)
func (j *Client) ParseICalendarBlob(accountId string, blobIds []string, ctx Context) (CalendarEventParseResponse, SessionState, State, Language, Error) {
logger := j.logger("ParseICalendarBlob", ctx)
cmd, err := j.request(session, logger, NS_CALENDARS,
invocation(CalendarEventParseCommand{AccountId: accountId, BlobIds: blobIds}, "0"),
parse := CalendarEventParseCommand{AccountId: accountId, BlobIds: blobIds}
cmd, err := j.request(ctx.WithLogger(logger), NS_CALENDARS,
invocation(parse, "0"),
)
if err != nil {
return CalendarEventParseResponse{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (CalendarEventParseResponse, State, Error) {
return command(j, ctx, cmd, func(body *Response) (CalendarEventParseResponse, State, Error) {
var response CalendarEventParseResponse
err = retrieveResponseMatchParameters(logger, body, CommandCalendarEventParse, "0", &response)
err = retrieveParse(ctx, body, parse, "0", &response)
if err != nil {
return CalendarEventParseResponse{}, "", err
}
@@ -29,14 +23,15 @@ func (j *Client) ParseICalendarBlob(accountId string, session *Session, ctx cont
})
}
func (j *Client) GetCalendars(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (CalendarGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetCalendars(accountId string, ids []string, ctx Context) (CalendarGetResponse, SessionState, State, Language, Error) {
return get(j, "GetCalendars", NS_CALENDARS,
func(accountId string, ids []string) CalendarGetCommand {
return CalendarGetCommand{AccountId: accountId, Ids: ids}
},
CalendarGetResponse{},
identity1,
accountId, session, ctx, logger, acceptLanguage, ids,
accountId, ids,
ctx,
)
}
@@ -44,10 +39,10 @@ type CalendarChanges = ChangesTemplate[Calendar]
// 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) {
func (j *Client) GetCalendarChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (CalendarChanges, SessionState, State, Language, Error) {
return changes(j, "GetCalendarChanges", NS_CALENDARS,
func() CalendarChangesCommand {
return CalendarChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return CalendarChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
CalendarChangesResponse{},
func(path string, rof string) CalendarGetRefCommand {
@@ -71,78 +66,55 @@ func (j *Client) GetCalendarChanges(accountId string, session *Session, ctx cont
Destroyed: destroyed,
}
},
session, ctx, logger, acceptLanguage,
ctx,
)
}
func (j *Client) QueryCalendarEvents(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, //NOSONAR
type CalendarEventSearchResults SearchResultsTemplate[CalendarEvent]
var _ SearchResults[CalendarEvent] = CalendarEventSearchResults{}
func (r CalendarEventSearchResults) GetResults() []CalendarEvent { return r.Results }
func (r CalendarEventSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges }
func (r CalendarEventSearchResults) GetPosition() uint { return r.Position }
func (r CalendarEventSearchResults) GetLimit() uint { return r.Limit }
func (r CalendarEventSearchResults) GetTotal() *uint { return r.Total }
func (j *Client) QueryCalendarEvents(accountIds []string, //NOSONAR
filter CalendarEventFilterElement, sortBy []CalendarEventComparator,
position uint, limit uint) (map[string][]CalendarEvent, SessionState, State, Language, Error) {
logger = j.logger("QueryCalendarEvents", session, logger)
uniqueAccountIds := structs.Uniq(accountIds)
if sortBy == nil {
sortBy = []CalendarEventComparator{{Property: CalendarEventPropertyStart, IsAscending: false}}
}
invocations := make([]Invocation, len(uniqueAccountIds)*2)
for i, accountId := range uniqueAccountIds {
query := CalendarEventQueryCommand{
AccountId: accountId,
Filter: filter,
Sort: sortBy,
}
if limit > 0 {
query.Limit = limit
}
if position > 0 {
query.Position = position
}
invocations[i*2+0] = invocation(query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(CalendarEventGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
Name: CommandCalendarEventQuery,
Path: "/ids/*",
ResultOf: mcid(accountId, "0"),
},
// Properties: CalendarEventProperties, // to also retrieve UTCStart and UTCEnd
}, mcid(accountId, "1"))
}
cmd, err := j.request(session, logger, NS_CALENDARS, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]CalendarEvent, State, Error) {
resp := map[string][]CalendarEvent{}
stateByAccountId := map[string]State{}
for _, accountId := range uniqueAccountIds {
var response CalendarEventGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandCalendarEventGet, mcid(accountId, "1"), &response)
if err != nil {
return nil, "", err
position int, limit uint, calculateTotal bool,
ctx Context) (map[string]CalendarEventSearchResults, SessionState, State, Language, Error) {
return queryN(j, "QueryCalendarEvents", NS_CALENDARS,
[]CalendarEventComparator{{Property: CalendarEventPropertyStart, IsAscending: false}},
func(accountId string, filter CalendarEventFilterElement, sortBy []CalendarEventComparator, position int, limit uint) CalendarEventQueryCommand {
return CalendarEventQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: uintPtr(limit), CalculateTotal: calculateTotal}
},
func(accountId string, cmd Command, path string, rof string) CalendarEventGetRefCommand {
return CalendarEventGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}}
},
func(query CalendarEventQueryResponse, get CalendarEventGetResponse) CalendarEventSearchResults {
return CalendarEventSearchResults{
Results: get.List,
CanCalculateChanges: query.CanCalculateChanges,
Position: query.Position,
Total: uintPtrIf(query.Total, calculateTotal),
Limit: query.Limit,
}
if len(response.NotFound) > 0 {
// TODO what to do when there are not-found calendarevents here? potentially nothing, they could have been deleted between query and get?
}
resp[accountId] = response.List
stateByAccountId[accountId] = response.State
}
return resp, squashState(stateByAccountId), nil
})
},
accountIds,
filter, sortBy, limit, position, ctx,
)
}
type CalendarEventChanges = ChangesTemplate[CalendarEvent]
// Retrieve the changes in Calendar Events since a given State.
// @api:tags event,changes
func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, sinceState State, maxChanges uint) (CalendarEventChanges, SessionState, State, Language, Error) {
func (j *Client) GetCalendarEventChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (CalendarEventChanges, SessionState, State, Language, Error) {
return changes(j, "GetCalendarEventChanges", NS_CALENDARS,
func() CalendarEventChangesCommand {
return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return CalendarEventChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
CalendarEventChangesResponse{},
func(path string, rof string) CalendarEventGetRefCommand {
@@ -166,11 +138,11 @@ func (j *Client) GetCalendarEventChanges(accountId string, session *Session, ctx
Destroyed: destroyed,
}
},
session, ctx, logger, acceptLanguage,
ctx,
)
}
func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, event CalendarEvent) (*CalendarEvent, SessionState, State, Language, Error) {
func (j *Client) CreateCalendarEvent(accountId string, event CalendarEvent, ctx Context) (*CalendarEvent, SessionState, State, Language, Error) {
return create(j, "CreateCalendarEvent", NS_CALENDARS,
func(accountId string, create map[string]CalendarEvent) CalendarEventSetCommand {
return CalendarEventSetCommand{AccountId: accountId, Create: create}
@@ -184,19 +156,23 @@ func (j *Client) CreateCalendarEvent(accountId string, session *Session, ctx con
func(resp CalendarEventGetResponse) []CalendarEvent {
return resp.List
},
accountId, session, ctx, logger, acceptLanguage, event)
accountId, event,
ctx,
)
}
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) {
func (j *Client) DeleteCalendarEvent(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
return destroy(j, "DeleteCalendarEvent", NS_CALENDARS,
func(accountId string, destroy []string) CalendarEventSetCommand {
return CalendarEventSetCommand{AccountId: accountId, Destroy: destroy}
},
CalendarEventSetResponse{},
accountId, destroyIds, session, ctx, logger, acceptLanguage)
accountId, destroyIds,
ctx,
)
}
func (j *Client) CreateCalendar(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, calendar CalendarChange) (*Calendar, SessionState, State, Language, Error) {
func (j *Client) CreateCalendar(accountId string, calendar CalendarChange, ctx Context) (*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}
@@ -210,21 +186,23 @@ func (j *Client) CreateCalendar(accountId string, session *Session, ctx context.
func(resp CalendarGetResponse) []Calendar {
return resp.List
},
accountId, session, ctx, logger, acceptLanguage, calendar,
accountId, calendar,
ctx,
)
}
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) {
func (j *Client) DeleteCalendar(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
return destroy(j, "DeleteCalendar", NS_CALENDARS,
func(accountId string, destroy []string) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Destroy: destroy}
},
CalendarSetResponse{},
accountId, destroyIds, session, ctx, logger, acceptLanguage,
accountId, destroyIds,
ctx,
)
}
func (j *Client) UpdateCalendar(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string, changes CalendarChange) (Calendar, SessionState, State, Language, Error) {
func (j *Client) UpdateCalendar(accountId string, id string, changes CalendarChange, ctx Context) (Calendar, SessionState, State, Language, Error) {
return update(j, "UpdateCalendar", NS_CALENDARS,
func(update map[string]PatchObject) CalendarSetCommand {
return CalendarSetCommand{AccountId: accountId, Update: update}
@@ -234,6 +212,7 @@ func (j *Client) UpdateCalendar(accountId string, session *Session, ctx context.
},
func(resp CalendarSetResponse) map[string]SetError { return resp.NotUpdated },
func(resp CalendarGetResponse) Calendar { return resp.List[0] },
id, changes, session, ctx, logger, acceptLanguage,
id, changes,
ctx,
)
}

View File

@@ -1,8 +1,6 @@
package jmap
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/rs/zerolog"
)
@@ -76,49 +74,50 @@ func (s StateMap) MarshalZerologObject(e *zerolog.Event) {
// Retrieve the changes in any type of objects at once since a given State.
// @api:tags changes
func (j *Client) GetChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, stateMap StateMap, maxChanges uint) (Changes, SessionState, State, Language, Error) { //NOSONAR
logger = log.From(j.logger("GetChanges", session, logger).With().Object("state", stateMap).Uint("maxChanges", maxChanges))
func (j *Client) GetChanges(accountId string, stateMap StateMap, maxChanges uint, ctx Context) (Changes, SessionState, State, Language, Error) { //NOSONAR
logger := log.From(j.logger("GetChanges", ctx).With().Object("state", stateMap).Uint("maxChanges", maxChanges))
ctx = ctx.WithLogger(logger)
methodCalls := []Invocation{}
if stateMap.Mailboxes != nil {
methodCalls = append(methodCalls, invocation(MailboxChangesCommand{AccountId: accountId, SinceState: *stateMap.Mailboxes, MaxChanges: posUIntPtr(maxChanges)}, "mailboxes"))
methodCalls = append(methodCalls, invocation(MailboxChangesCommand{AccountId: accountId, SinceState: *stateMap.Mailboxes, MaxChanges: uintPtr(maxChanges)}, "mailboxes"))
}
if stateMap.Emails != nil {
methodCalls = append(methodCalls, invocation(EmailChangesCommand{AccountId: accountId, SinceState: *stateMap.Emails, MaxChanges: posUIntPtr(maxChanges)}, "emails"))
methodCalls = append(methodCalls, invocation(EmailChangesCommand{AccountId: accountId, SinceState: *stateMap.Emails, MaxChanges: uintPtr(maxChanges)}, "emails"))
}
if stateMap.Calendars != nil {
methodCalls = append(methodCalls, invocation(CalendarChangesCommand{AccountId: accountId, SinceState: *stateMap.Calendars, MaxChanges: posUIntPtr(maxChanges)}, "calendars"))
methodCalls = append(methodCalls, invocation(CalendarChangesCommand{AccountId: accountId, SinceState: *stateMap.Calendars, MaxChanges: uintPtr(maxChanges)}, "calendars"))
}
if stateMap.Events != nil {
methodCalls = append(methodCalls, invocation(CalendarEventChangesCommand{AccountId: accountId, SinceState: *stateMap.Events, MaxChanges: posUIntPtr(maxChanges)}, "events"))
methodCalls = append(methodCalls, invocation(CalendarEventChangesCommand{AccountId: accountId, SinceState: *stateMap.Events, MaxChanges: uintPtr(maxChanges)}, "events"))
}
if stateMap.Addressbooks != nil {
methodCalls = append(methodCalls, invocation(AddressBookChangesCommand{AccountId: accountId, SinceState: *stateMap.Addressbooks, MaxChanges: posUIntPtr(maxChanges)}, "addressbooks"))
methodCalls = append(methodCalls, invocation(AddressBookChangesCommand{AccountId: accountId, SinceState: *stateMap.Addressbooks, MaxChanges: uintPtr(maxChanges)}, "addressbooks"))
}
if stateMap.Contacts != nil {
methodCalls = append(methodCalls, invocation(ContactCardChangesCommand{AccountId: accountId, SinceState: *stateMap.Contacts, MaxChanges: posUIntPtr(maxChanges)}, "contacts"))
methodCalls = append(methodCalls, invocation(ContactCardChangesCommand{AccountId: accountId, SinceState: *stateMap.Contacts, MaxChanges: uintPtr(maxChanges)}, "contacts"))
}
if stateMap.Identities != nil {
methodCalls = append(methodCalls, invocation(IdentityChangesCommand{AccountId: accountId, SinceState: *stateMap.Identities, MaxChanges: posUIntPtr(maxChanges)}, "identities"))
methodCalls = append(methodCalls, invocation(IdentityChangesCommand{AccountId: accountId, SinceState: *stateMap.Identities, MaxChanges: uintPtr(maxChanges)}, "identities"))
}
if stateMap.EmailSubmissions != nil {
methodCalls = append(methodCalls, invocation(EmailSubmissionChangesCommand{AccountId: accountId, SinceState: *stateMap.EmailSubmissions, MaxChanges: posUIntPtr(maxChanges)}, "submissions"))
methodCalls = append(methodCalls, invocation(EmailSubmissionChangesCommand{AccountId: accountId, SinceState: *stateMap.EmailSubmissions, MaxChanges: uintPtr(maxChanges)}, "submissions"))
}
// if stateMap.Quotas != nil { methodCalls = append(methodCalls, invocation(CommandQuotaChanges, QuotaChangesCommand{AccountId: accountId, SinceState: *stateMap.Quotas, MaxChanges: posUIntPtr(maxChanges)}, "quotas")) }
cmd, err := j.request(session, logger, NS_CHANGES, methodCalls...)
cmd, err := j.request(ctx, NS_CHANGES, methodCalls...)
if err != nil {
return Changes{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Changes, State, Error) {
return command(j, ctx, cmd, func(body *Response) (Changes, State, Error) {
changes := Changes{
MaxChanges: maxChanges,
}
states := map[string]State{}
var mailboxes MailboxChangesResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandMailboxChanges, "mailboxes", &mailboxes); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandMailboxChanges, "mailboxes", &mailboxes); err != nil {
return Changes{}, "", err
} else if ok {
changes.Mailboxes = &mailboxes
@@ -126,7 +125,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
}
var emails EmailChangesResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailChanges, "emails", &emails); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailChanges, "emails", &emails); err != nil {
return Changes{}, "", err
} else if ok {
changes.Emails = &emails
@@ -134,7 +133,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
}
var calendars CalendarChangesResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandCalendarChanges, "calendars", &calendars); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarChanges, "calendars", &calendars); err != nil {
return Changes{}, "", err
} else if ok {
changes.Calendars = &calendars
@@ -142,7 +141,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
}
var events CalendarEventChangesResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandCalendarEventChanges, "events", &events); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarEventChanges, "events", &events); err != nil {
return Changes{}, "", err
} else if ok {
changes.Events = &events
@@ -150,7 +149,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
}
var addressbooks AddressBookChangesResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandAddressBookChanges, "addressbooks", &addressbooks); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandAddressBookChanges, "addressbooks", &addressbooks); err != nil {
return Changes{}, "", err
} else if ok {
changes.Addressbooks = &addressbooks
@@ -158,7 +157,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
}
var contacts ContactCardChangesResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandContactCardChanges, "contacts", &contacts); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandContactCardChanges, "contacts", &contacts); err != nil {
return Changes{}, "", err
} else if ok {
changes.Contacts = &contacts
@@ -166,7 +165,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
}
var identities IdentityChangesResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandIdentityChanges, "identities", &identities); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandIdentityChanges, "identities", &identities); err != nil {
return Changes{}, "", err
} else if ok {
changes.Identities = &identities
@@ -174,7 +173,7 @@ func (j *Client) GetChanges(accountId string, session *Session, ctx context.Cont
}
var submissions EmailSubmissionChangesResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailSubmissionChanges, "submissions", &submissions); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailSubmissionChanges, "submissions", &submissions); err != nil {
return Changes{}, "", err
} else if ok {
changes.EmailSubmissions = &submissions

View File

@@ -1,24 +1,16 @@
package jmap
import (
"context"
"fmt"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
var NS_CONTACTS = ns(JmapContacts)
func (j *Client) GetContactCards(accountId string, session *Session, ctx context.Context, logger *log.Logger,
acceptLanguage string, contactIds []string) (ContactCardGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetContactCards(accountId string, contactIds []string, ctx Context) (ContactCardGetResponse, SessionState, State, Language, Error) {
return get(j, "GetContactCards", NS_CONTACTS,
func(accountId string, ids []string) ContactCardGetCommand {
return ContactCardGetCommand{AccountId: accountId, Ids: contactIds}
},
ContactCardGetResponse{},
identity1,
accountId, session, ctx, logger, acceptLanguage, contactIds,
accountId, contactIds,
ctx,
)
}
@@ -26,11 +18,10 @@ type ContactCardChanges = ChangesTemplate[ContactCard]
// Retrieve the changes in Contact Cards since a given State.
// @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) {
func (j *Client) GetContactCardChanges(accountId string, sinceState State, maxChanges uint, ctx Context) (ContactCardChanges, SessionState, State, Language, Error) {
return changes(j, "GetContactCardChanges", NS_CONTACTS,
func() ContactCardChangesCommand {
return ContactCardChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return ContactCardChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
ContactCardChangesResponse{},
func(path string, rof string) ContactCardGetRefCommand {
@@ -54,128 +45,72 @@ func (j *Client) GetContactCardChanges(accountId string, session *Session, ctx c
Destroyed: destroyed,
}
},
session, ctx, logger, acceptLanguage,
ctx,
)
}
func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, //NOSONAR
type ContactCardSearchResults SearchResultsTemplate[ContactCard]
var _ SearchResults[ContactCard] = ContactCardSearchResults{}
func (r ContactCardSearchResults) GetResults() []ContactCard { return r.Results }
func (r ContactCardSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges }
func (r ContactCardSearchResults) GetPosition() uint { return r.Position }
func (r ContactCardSearchResults) GetLimit() uint { return r.Limit }
func (r ContactCardSearchResults) GetTotal() *uint { return r.Total }
func (j *Client) QueryContactCards(accountIds []string,
filter ContactCardFilterElement, sortBy []ContactCardComparator,
position uint, limit uint) (map[string][]ContactCard, SessionState, State, Language, Error) {
logger = j.logger("QueryContactCards", session, logger)
uniqueAccountIds := structs.Uniq(accountIds)
if sortBy == nil {
sortBy = []ContactCardComparator{{Property: ContactCardPropertyUpdated, IsAscending: false}}
}
invocations := make([]Invocation, len(uniqueAccountIds)*2)
for i, accountId := range uniqueAccountIds {
query := ContactCardQueryCommand{
AccountId: accountId,
Filter: filter,
Sort: sortBy,
}
if limit > 0 {
query.Limit = limit
}
if position > 0 {
query.Position = position
}
invocations[i*2+0] = invocation(query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(ContactCardGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
Name: CommandContactCardQuery,
Path: "/ids/*",
ResultOf: mcid(accountId, "0"),
},
}, mcid(accountId, "1"))
}
cmd, err := j.request(session, logger, NS_CONTACTS, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]ContactCard, State, Error) {
resp := map[string][]ContactCard{}
stateByAccountId := map[string]State{}
for _, accountId := range uniqueAccountIds {
var response ContactCardGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, mcid(accountId, "1"), &response)
if err != nil {
return nil, "", err
position int, limit uint, calculateTotal bool,
ctx Context) (map[string]ContactCardSearchResults, SessionState, State, Language, Error) {
return queryN(j, "QueryContactCards", NS_CONTACTS,
[]ContactCardComparator{{Property: ContactCardPropertyUpdated, IsAscending: false}},
func(accountId string, filter ContactCardFilterElement, sortBy []ContactCardComparator, position int, limit uint) ContactCardQueryCommand {
return ContactCardQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: uintPtr(limit), CalculateTotal: calculateTotal}
},
func(accountId string, cmd Command, path string, rof string) ContactCardGetRefCommand {
return ContactCardGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}}
},
func(query ContactCardQueryResponse, get ContactCardGetResponse) ContactCardSearchResults {
return ContactCardSearchResults{
Results: get.List,
CanCalculateChanges: query.CanCalculateChanges,
Position: query.Position,
Total: uintPtrIf(query.Total, calculateTotal),
Limit: query.Limit,
}
if len(response.NotFound) > 0 {
// TODO what to do when there are not-found emails here? potentially nothing, they could have been deleted between query and get?
}
resp[accountId] = response.List
stateByAccountId[accountId] = response.State
}
return resp, squashState(stateByAccountId), nil
})
}
func (j *Client) CreateContactCard(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, create ContactCard) (*ContactCard, SessionState, State, Language, Error) {
logger = j.logger("CreateContactCard", session, logger)
cmd, err := j.request(session, logger, NS_CONTACTS,
invocation(ContactCardSetCommand{
AccountId: accountId,
Create: map[string]ContactCard{
"c": create,
},
}, "0"),
invocation(ContactCardGetCommand{
AccountId: accountId,
Ids: []string{"#c"},
}, "1"),
},
accountIds,
filter, sortBy, limit, position, ctx,
)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*ContactCard, State, Error) {
var setResponse ContactCardSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandContactCardSet, "0", &setResponse)
if err != nil {
return nil, "", err
}
setErr, notok := setResponse.NotCreated["c"]
if notok {
logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr)
return nil, "", setErrorError(setErr, EmailType)
}
if created, ok := setResponse.Created["c"]; !ok || created == nil {
berr := fmt.Errorf("failed to find %s in %s response", ContactCardType, string(CommandContactCardSet))
logger.Error().Err(berr)
return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}
var getResponse ContactCardGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, "1", &getResponse)
if err != nil {
return nil, "", err
}
if len(getResponse.List) < 1 {
berr := fmt.Errorf("failed to find %s in %s response", ContactCardType, string(CommandContactCardSet))
logger.Error().Err(berr)
return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}
return &getResponse.List[0], setResponse.NewState, nil
})
}
func (j *Client) DeleteContactCard(accountId string, destroyIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]SetError, SessionState, State, Language, Error) {
func (j *Client) CreateContactCard(accountId string, contact ContactCard, ctx Context) (*ContactCard, SessionState, State, Language, Error) {
return create(j, "CreateContactCard", NS_CONTACTS,
func(accountId string, create map[string]ContactCard) ContactCardSetCommand {
return ContactCardSetCommand{AccountId: accountId, Create: create}
},
func(accountId string, ids string) ContactCardGetCommand {
return ContactCardGetCommand{AccountId: accountId, Ids: []string{ids}}
},
func(resp ContactCardSetResponse) map[string]*ContactCard {
return resp.Created
},
func(resp ContactCardGetResponse) []ContactCard {
return resp.List
},
accountId, contact,
ctx,
)
}
func (j *Client) DeleteContactCard(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
return destroy(j, "DeleteContactCard", NS_CONTACTS,
func(accountId string, destroy []string) ContactCardSetCommand {
return ContactCardSetCommand{AccountId: accountId, Destroy: destroy}
},
ContactCardSetResponse{},
accountId, destroyIds, session, ctx, logger, acceptLanguage,
accountId, destroyIds,
ctx,
)
}

View File

@@ -1,7 +1,6 @@
package jmap
import (
"context"
"encoding/base64"
"fmt"
"time"
@@ -27,8 +26,11 @@ type getEmailsResult struct {
}
// Retrieve specific Emails by their id.
func (j *Client) GetEmails(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string, fetchBodies bool, maxBodyValueBytes uint, markAsSeen bool, withThreads bool) ([]Email, []string, SessionState, State, Language, Error) { //NOSONAR
logger = j.logger("GetEmails", session, logger)
func (j *Client) GetEmails(accountId string, ids []string, //NOSONAR
fetchBodies bool, maxBodyValueBytes uint, markAsSeen bool, withThreads bool,
ctx Context) ([]Email, []string, SessionState, State, Language, Error) {
logger := j.logger("GetEmails", ctx)
ctx = ctx.WithLogger(logger)
get := EmailGetCommand{AccountId: accountId, Ids: ids, FetchAllBodyValues: fetchBodies}
if maxBodyValueBytes > 0 {
@@ -57,14 +59,14 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
methodCalls = append(methodCalls, invocation(threads, "2"))
}
cmd, err := j.request(session, logger, NS_MAIL, methodCalls...)
cmd, err := j.request(ctx, NS_MAIL, methodCalls...)
if err != nil {
return nil, nil, "", "", "", err
}
result, sessionState, state, language, gwerr := command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (getEmailsResult, State, Error) {
result, sessionState, state, language, gwerr := command(j, ctx, cmd, func(body *Response) (getEmailsResult, State, Error) {
if markAsSeen {
var markResponse EmailSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &markResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailSet, "0", &markResponse)
if err != nil {
return getEmailsResult{}, "", err
}
@@ -74,13 +76,13 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
}
}
var response EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &response)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, "1", &response)
if err != nil {
return getEmailsResult{}, "", err
}
if withThreads {
var threads ThreadGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, "2", &threads)
err = retrieveResponseMatchParameters(ctx, body, CommandThreadGet, "2", &threads)
if err != nil {
return getEmailsResult{}, "", err
}
@@ -91,17 +93,18 @@ func (j *Client) GetEmails(accountId string, session *Session, ctx context.Conte
return result.emails, result.notFound, sessionState, state, language, gwerr
}
func (j *Client) GetEmailBlobId(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string) (string, SessionState, State, Language, Error) {
logger = j.logger("GetEmailBlobId", session, logger)
func (j *Client) GetEmailBlobId(accountId string, id string, ctx Context) (string, SessionState, State, Language, Error) {
logger := j.logger("GetEmailBlobId", ctx)
ctx = ctx.WithLogger(logger)
get := EmailGetCommand{AccountId: accountId, Ids: []string{id}, FetchAllBodyValues: false, Properties: []string{"blobId"}}
cmd, err := j.request(session, logger, NS_MAIL, invocation(get, "0"))
cmd, err := j.request(ctx, NS_MAIL, invocation(get, "0"))
if err != nil {
return "", "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (string, State, Error) {
return command(j, ctx, cmd, func(body *Response) (string, State, Error) {
var response EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "0", &response)
err = retrieveGet(ctx, body, get, "0", &response)
if err != nil {
return "", "", err
}
@@ -114,10 +117,13 @@ func (j *Client) GetEmailBlobId(accountId string, session *Session, ctx context.
}
// Retrieve all the Emails in a given Mailbox by its id.
func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, mailboxId string, offset int, limit uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool) (Emails, SessionState, State, Language, Error) { //NOSONAR
logger = j.loggerParams("GetAllEmailsInMailbox", session, logger, func(z zerolog.Context) zerolog.Context {
func (j *Client) GetAllEmailsInMailbox(accountId string, mailboxId string, //NOSONAR
offset int, limit uint, collapseThreads bool, fetchBodies bool, maxBodyValueBytes uint, withThreads bool,
ctx Context) (Emails, SessionState, State, Language, Error) {
logger := j.loggerParams("GetAllEmailsInMailbox", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Int(logOffset, offset).Uint(logLimit, limit)
})
ctx = ctx.WithLogger(logger)
query := EmailQueryCommand{
AccountId: accountId,
@@ -147,8 +153,9 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
invocation(get, "1"),
}
threads := ThreadGetRefCommand{}
if withThreads {
threads := ThreadGetRefCommand{
threads = ThreadGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
ResultOf: "1",
@@ -159,19 +166,19 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
invocations = append(invocations, invocation(threads, "2"))
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return Emails{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Emails, State, Error) {
return command(j, ctx, cmd, func(body *Response) (Emails, State, Error) {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, "0", &queryResponse)
err = retrieveQuery(ctx, body, query, "0", &queryResponse)
if err != nil {
return Emails{}, "", err
}
var getResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &getResponse)
err = retrieveGet(ctx, body, get, "1", &getResponse)
if err != nil {
logger.Error().Err(err).Send()
return Emails{}, "", err
@@ -179,7 +186,7 @@ func (j *Client) GetAllEmailsInMailbox(accountId string, session *Session, ctx c
if withThreads {
var thread ThreadGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, "2", &thread)
err = retrieveGet(ctx, body, threads, "2", &thread)
if err != nil {
return Emails{}, "", err
}
@@ -206,10 +213,13 @@ type EmailChanges struct {
// Retrieve the changes in Emails since a given State.
// @api:tags email,changes
func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, sinceState State, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint) (EmailChanges, SessionState, State, Language, Error) { //NOSONAR
logger = j.loggerParams("GetEmailChanges", session, logger, func(z zerolog.Context) zerolog.Context {
func (j *Client) GetEmailChanges(accountId string,
sinceState State, fetchBodies bool, maxBodyValueBytes uint, maxChanges uint,
ctx Context) (EmailChanges, SessionState, State, Language, Error) { //NOSONAR
logger := j.loggerParams("GetEmailChanges", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str(logSinceState, string(sinceState))
})
ctx = ctx.WithLogger(logger)
changes := EmailChangesCommand{
AccountId: accountId,
@@ -236,7 +246,7 @@ func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context
getUpdated.MaxBodyValueBytes = maxBodyValueBytes
}
cmd, err := j.request(session, logger, NS_MAIL,
cmd, err := j.request(ctx, NS_MAIL,
invocation(changes, "0"),
invocation(getCreated, "1"),
invocation(getUpdated, "2"),
@@ -245,22 +255,22 @@ func (j *Client) GetEmailChanges(accountId string, session *Session, ctx context
return EmailChanges{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailChanges, State, Error) {
return command(j, ctx, cmd, func(body *Response) (EmailChanges, State, Error) {
var changesResponse EmailChangesResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailChanges, "0", &changesResponse)
err = retrieveChanges(ctx, body, changes, "0", &changesResponse)
if err != nil {
return EmailChanges{}, "", err
}
var createdResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &createdResponse)
err = retrieveGet(ctx, body, getCreated, "1", &createdResponse)
if err != nil {
logger.Error().Err(err).Send()
return EmailChanges{}, "", err
}
var updatedResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "2", &updatedResponse)
err = retrieveGet(ctx, body, getUpdated, "2", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
return EmailChanges{}, "", err
@@ -291,10 +301,13 @@ type EmailSnippetQueryResult struct {
QueryState State `json:"queryState"`
}
func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset int, limit uint) (map[string]EmailSnippetQueryResult, SessionState, State, Language, Error) { //NOSONAR
logger = j.loggerParams("QueryEmailSnippets", session, logger, func(z zerolog.Context) zerolog.Context {
func (j *Client) QueryEmailSnippets(accountIds []string, //NOSONAR
filter EmailFilterElement, offset int, limit uint,
ctx Context) (map[string]EmailSnippetQueryResult, SessionState, State, Language, Error) {
logger := j.loggerParams("QueryEmailSnippets", ctx, func(z zerolog.Context) zerolog.Context {
return z.Uint(logLimit, limit).Int(logOffset, offset)
})
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
invocations := make([]Invocation, len(uniqueAccountIds)*3)
@@ -340,28 +353,28 @@ func (j *Client) QueryEmailSnippets(accountIds []string, filter EmailFilterEleme
invocations[i*3+2] = invocation(snippet, mcid(accountId, "2"))
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailSnippetQueryResult, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]EmailSnippetQueryResult, State, Error) {
results := make(map[string]EmailSnippetQueryResult, len(uniqueAccountIds))
for _, accountId := range uniqueAccountIds {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
if err != nil {
return nil, "", err
}
var mailResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &mailResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, mcid(accountId, "1"), &mailResponse)
if err != nil {
return nil, "", err
}
var snippetResponse SearchSnippetGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandSearchSnippetGet, mcid(accountId, "2"), &snippetResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandSearchSnippetGet, mcid(accountId, "2"), &snippetResponse)
if err != nil {
return nil, "", err
}
@@ -407,10 +420,13 @@ type EmailQueryResult struct {
QueryState State `json:"queryState"`
}
func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset int, limit uint, fetchBodies bool, maxBodyValueBytes uint) (map[string]EmailQueryResult, SessionState, State, Language, Error) { //NOSONAR
logger = j.loggerParams("QueryEmails", session, logger, func(z zerolog.Context) zerolog.Context {
func (j *Client) QueryEmails(accountIds []string,
filter EmailFilterElement, offset int, limit uint, fetchBodies bool, maxBodyValueBytes uint,
ctx Context) (map[string]EmailQueryResult, SessionState, State, Language, Error) { //NOSONAR
logger := j.loggerParams("QueryEmails", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies)
})
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
invocations := make([]Invocation, len(uniqueAccountIds)*2)
@@ -444,22 +460,22 @@ func (j *Client) QueryEmails(accountIds []string, filter EmailFilterElement, ses
invocations[i*2+1] = invocation(mails, mcid(accountId, "1"))
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailQueryResult, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]EmailQueryResult, State, Error) {
results := make(map[string]EmailQueryResult, len(uniqueAccountIds))
for _, accountId := range uniqueAccountIds {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
if err != nil {
return nil, "", err
}
var emailsResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &emailsResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, mcid(accountId, "1"), &emailsResponse)
if err != nil {
return nil, "", err
}
@@ -489,10 +505,13 @@ type EmailQueryWithSnippetsResult struct {
QueryState State `json:"queryState"`
}
func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilterElement, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, offset int, limit uint, fetchBodies bool, maxBodyValueBytes uint) (map[string]EmailQueryWithSnippetsResult, SessionState, State, Language, Error) { //NOSONAR
logger = j.loggerParams("QueryEmailsWithSnippets", session, logger, func(z zerolog.Context) zerolog.Context {
func (j *Client) QueryEmailsWithSnippets(accountIds []string, //NOSONAR
filter EmailFilterElement, offset int, limit uint, fetchBodies bool, maxBodyValueBytes uint,
ctx Context) (map[string]EmailQueryWithSnippetsResult, SessionState, State, Language, Error) {
logger := j.loggerParams("QueryEmailsWithSnippets", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies)
})
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
invocations := make([]Invocation, len(uniqueAccountIds)*3)
@@ -536,28 +555,28 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []string, filter EmailFilter
invocations[i*3+2] = invocation(mails, mcid(accountId, "2"))
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailQueryWithSnippetsResult, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]EmailQueryWithSnippetsResult, State, Error) {
result := make(map[string]EmailQueryWithSnippetsResult, len(uniqueAccountIds))
for _, accountId := range uniqueAccountIds {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
if err != nil {
return nil, "", err
}
var snippetResponse SearchSnippetGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandSearchSnippetGet, mcid(accountId, "1"), &snippetResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandSearchSnippetGet, mcid(accountId, "1"), &snippetResponse)
if err != nil {
return nil, "", err
}
var emailsResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "2"), &emailsResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, mcid(accountId, "2"), &emailsResponse)
if err != nil {
return nil, "", err
}
@@ -602,7 +621,7 @@ type UploadedEmail struct {
Sha512 string `json:"sha:512"`
}
func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, data []byte) (UploadedEmail, SessionState, State, Language, Error) {
func (j *Client) ImportEmail(accountId string, data []byte, ctx Context) (UploadedEmail, SessionState, State, Language, Error) {
encoded := base64.StdEncoding.EncodeToString(data)
upload := BlobUploadCommand{
@@ -627,7 +646,7 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
Properties: []string{BlobPropertyDigestSha512},
}
cmd, err := j.request(session, logger, NS_MAIL,
cmd, err := j.request(ctx, NS_MAIL,
invocation(upload, "0"),
invocation(getHash, "1"),
)
@@ -635,32 +654,32 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
return UploadedEmail{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (UploadedEmail, State, Error) {
return command(j, ctx, cmd, func(body *Response) (UploadedEmail, State, Error) {
var uploadResponse BlobUploadResponse
err = retrieveResponseMatchParameters(logger, body, CommandBlobUpload, "0", &uploadResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandBlobUpload, "0", &uploadResponse)
if err != nil {
return UploadedEmail{}, "", err
}
var getResponse BlobGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandBlobGet, "1", &getResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandBlobGet, "1", &getResponse)
if err != nil {
logger.Error().Err(err).Send()
ctx.Logger.Error().Err(err).Send()
return UploadedEmail{}, "", err
}
if len(uploadResponse.Created) != 1 {
logger.Error().Msgf("%T.Created has %v elements instead of 1", uploadResponse, len(uploadResponse.Created))
ctx.Logger.Error().Msgf("%T.Created has %v elements instead of 1", uploadResponse, len(uploadResponse.Created))
return UploadedEmail{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
upload, ok := uploadResponse.Created["0"]
if !ok {
logger.Error().Msgf("%T.Created has no element '0'", uploadResponse)
ctx.Logger.Error().Msgf("%T.Created has no element '0'", uploadResponse)
return UploadedEmail{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
if len(getResponse.List) != 1 {
logger.Error().Msgf("%T.List has %v elements instead of 1", getResponse, len(getResponse.List))
ctx.Logger.Error().Msgf("%T.List has %v elements instead of 1", getResponse, len(getResponse.List))
return UploadedEmail{}, "", jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
get := getResponse.List[0]
@@ -675,7 +694,7 @@ func (j *Client) ImportEmail(accountId string, session *Session, ctx context.Con
}
func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (*Email, SessionState, State, Language, Error) {
func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId string, ctx Context) (*Email, SessionState, State, Language, Error) {
set := EmailSetCommand{
AccountId: accountId,
Create: map[string]EmailCreate{
@@ -686,16 +705,16 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
set.Destroy = []string{replaceId}
}
cmd, err := j.request(session, logger, NS_MAIL,
cmd, err := j.request(ctx, NS_MAIL,
invocation(set, "0"),
)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*Email, State, Error) {
return command(j, ctx, cmd, func(body *Response) (*Email, State, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailSet, "0", &setResponse)
if err != nil {
return nil, "", err
}
@@ -707,14 +726,14 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
setErr, notok := setResponse.NotCreated["c"]
if notok {
logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr)
ctx.Logger.Error().Msgf("%T.NotCreated returned an error %v", setResponse, setErr)
return nil, "", setErrorError(setErr, EmailType)
}
created, ok := setResponse.Created["c"]
if !ok {
berr := fmt.Errorf("failed to find %s in %s response", EmailType, string(CommandEmailSet))
logger.Error().Err(berr)
ctx.Logger.Error().Err(berr)
return nil, "", jmapError(berr, JmapErrorInvalidJmapResponsePayload)
}
@@ -730,20 +749,19 @@ func (j *Client) CreateEmail(accountId string, email EmailCreate, replaceId stri
// To create drafts, use the CreateEmail function instead.
//
// To delete mails, use the DeleteEmails function instead.
func (j *Client) UpdateEmails(accountId string, updates map[string]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(EmailSetCommand{
AccountId: accountId,
Update: updates,
}, "0"),
)
func (j *Client) UpdateEmails(accountId string, updates map[string]EmailUpdate, ctx Context) (map[string]*Email, SessionState, State, Language, Error) {
set := EmailSetCommand{
AccountId: accountId,
Update: updates,
}
cmd, err := j.request(ctx, NS_MAIL, invocation(set, "0"))
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]*Email, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]*Email, State, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
err = retrieveSet(ctx, body, set, "0", &setResponse)
if err != nil {
return nil, "", err
}
@@ -757,20 +775,19 @@ 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(EmailSetCommand{
AccountId: accountId,
Destroy: destroy,
}, "0"),
)
func (j *Client) DeleteEmails(accountId string, destroy []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
set := EmailSetCommand{
AccountId: accountId,
Destroy: destroy,
}
cmd, err := j.request(ctx, NS_MAIL, invocation(set, "0"))
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]SetError, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]SetError, State, Error) {
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
err = retrieveSet(ctx, body, set, "0", &setResponse)
if err != nil {
return nil, "", err
}
@@ -807,8 +824,10 @@ type MoveMail struct {
ToMailboxId string
}
func (j *Client) SubmitEmail(accountId string, identityId string, emailId string, move *MoveMail, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (EmailSubmission, SessionState, State, Language, Error) { //NOSONAR
logger = j.logger("SubmitEmail", session, logger)
func (j *Client) SubmitEmail(accountId string, identityId string, emailId string, move *MoveMail, //NOSONAR
ctx Context) (EmailSubmission, SessionState, State, Language, Error) {
logger := j.logger("SubmitEmail", ctx)
ctx = ctx.WithLogger(logger)
update := map[string]any{
EmailPropertyKeywords + "/" + JmapKeywordDraft: nil, // unmark as draft
@@ -821,7 +840,7 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
id := "s0"
set := EmailSubmissionSetCommand{
submit := EmailSubmissionSetCommand{
AccountId: accountId,
Create: map[string]EmailSubmissionCreate{
id: {
@@ -840,17 +859,17 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
Ids: []string{"#" + id},
}
cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION,
invocation(set, "0"),
cmd, err := j.request(ctx, NS_MAIL_SUBMISSION,
invocation(submit, "0"),
invocation(get, "1"),
)
if err != nil {
return EmailSubmission{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (EmailSubmission, State, Error) {
return command(j, ctx, cmd, func(body *Response) (EmailSubmission, State, Error) {
var submissionResponse EmailSubmissionSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionSet, "0", &submissionResponse)
err = retrieveSet(ctx, body, submit, "0", &submissionResponse)
if err != nil {
return EmailSubmission{}, "", err
}
@@ -866,14 +885,14 @@ func (j *Client) SubmitEmail(accountId string, identityId string, emailId string
// The response to this MUST be returned after the EmailSubmission/set response."
// from an example in the spec, it has the same tag as the EmailSubmission/set command ("0" in this case)
var setResponse EmailSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSet, "0", &setResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailSet, "0", &setResponse)
if err != nil {
return EmailSubmission{}, "", err
}
if len(setResponse.Updated) == 1 {
var getResponse EmailSubmissionGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionGet, "1", &getResponse)
err = retrieveGet(ctx, body, get, "1", &getResponse)
if err != nil {
return EmailSubmission{}, "", err
}
@@ -898,20 +917,22 @@ type emailSubmissionResult struct {
notFound []string
}
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)
func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []string, ctx Context) (map[string]EmailSubmission, []string, SessionState, State, Language, Error) {
logger := j.logger("GetEmailSubmissionStatus", ctx)
ctx = ctx.WithLogger(logger)
cmd, err := j.request(session, logger, NS_MAIL_SUBMISSION, invocation(EmailSubmissionGetCommand{
get := EmailSubmissionGetCommand{
AccountId: accountId,
Ids: submissionIds,
}, "0"))
}
cmd, err := j.request(ctx, NS_MAIL_SUBMISSION, invocation(get, "0"))
if err != nil {
return nil, nil, "", "", "", err
}
result, sessionState, state, lang, err := command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (emailSubmissionResult, State, Error) {
result, sessionState, state, lang, err := command(j, ctx, cmd, func(body *Response) (emailSubmissionResult, State, Error) {
var response EmailSubmissionGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailSubmissionGet, "0", &response)
err = retrieveGet(ctx, body, get, "0", &response)
if err != nil {
return emailSubmissionResult{}, "", err
}
@@ -925,34 +946,41 @@ func (j *Client) GetEmailSubmissionStatus(accountId string, submissionIds []stri
return result.submissions, result.notFound, sessionState, state, lang, err
}
func (j *Client) EmailsInThread(accountId string, threadId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, fetchBodies bool, maxBodyValueBytes uint) ([]Email, SessionState, State, Language, Error) { //NOSONAR
logger = j.loggerParams("EmailsInThread", session, logger, func(z zerolog.Context) zerolog.Context {
func (j *Client) EmailsInThread(accountId string, threadId string,
fetchBodies bool, maxBodyValueBytes uint,
ctx Context) ([]Email, SessionState, State, Language, Error) { //NOSONAR
logger := j.loggerParams("EmailsInThread", ctx, func(z zerolog.Context) zerolog.Context {
return z.Bool(logFetchBodies, fetchBodies).Str("threadId", log.SafeString(threadId))
})
ctx = ctx.WithLogger(logger)
cmd, err := j.request(session, logger, NS_MAIL,
invocation(ThreadGetCommand{
AccountId: accountId,
Ids: []string{threadId},
}, "0"),
invocation(EmailGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
ResultOf: "0",
Name: CommandThreadGet,
Path: "/list/*/emailIds",
},
FetchAllBodyValues: fetchBodies,
MaxBodyValueBytes: maxBodyValueBytes,
}, "1"),
thread := ThreadGetCommand{
AccountId: accountId,
Ids: []string{threadId},
}
get := EmailGetRefCommand{
AccountId: accountId,
IdsRef: &ResultReference{
ResultOf: "0",
Name: CommandThreadGet,
Path: "/list/*/emailIds",
},
FetchAllBodyValues: fetchBodies,
MaxBodyValueBytes: maxBodyValueBytes,
}
cmd, err := j.request(ctx, NS_MAIL,
invocation(thread, "0"),
invocation(get, "1"),
)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]Email, State, Error) {
return command(j, ctx, cmd, func(body *Response) ([]Email, State, Error) {
var emailsResponse EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, "1", &emailsResponse)
err = retrieveGet(ctx, body, get, "1", &emailsResponse)
if err != nil {
return nil, "", err
}
@@ -987,8 +1015,11 @@ var EmailSummaryProperties = []string{
EmailPropertyPreview,
}
func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter EmailFilterElement, limit uint, withThreads bool) (map[string]EmailsSummary, SessionState, State, Language, Error) { //NOSONAR
logger = j.logger("QueryEmailSummaries", session, logger)
func (j *Client) QueryEmailSummaries(accountIds []string, //NOSONAR
filter EmailFilterElement, limit uint, withThreads bool,
ctx Context) (map[string]EmailsSummary, SessionState, State, Language, Error) {
logger := j.logger("QueryEmailSummaries", ctx)
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
@@ -1029,22 +1060,22 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
}, mcid(accountId, "2"))
}
}
cmd, err := j.request(session, logger, NS_MAIL, invocations...)
cmd, err := j.request(ctx, NS_MAIL, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]EmailsSummary, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]EmailsSummary, State, Error) {
resp := map[string]EmailsSummary{}
for _, accountId := range uniqueAccountIds {
var queryResponse EmailQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailQuery, mcid(accountId, "0"), &queryResponse)
if err != nil {
return nil, "", err
}
var response EmailGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandEmailGet, mcid(accountId, "1"), &response)
err = retrieveResponseMatchParameters(ctx, body, CommandEmailGet, mcid(accountId, "1"), &response)
if err != nil {
return nil, "", err
}
@@ -1053,7 +1084,7 @@ func (j *Client) QueryEmailSummaries(accountIds []string, session *Session, ctx
}
if withThreads {
var thread ThreadGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandThreadGet, mcid(accountId, "2"), &thread)
err = retrieveResponseMatchParameters(ctx, body, CommandThreadGet, mcid(accountId, "2"), &thread)
if err != nil {
return nil, "", err
}
@@ -1076,11 +1107,11 @@ type EmailSubmissionChanges = ChangesTemplate[EmailSubmission]
// Retrieve the changes in Email Submissions since a given State.
// @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) {
func (j *Client) GetEmailSubmissionChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (EmailSubmissionChanges, SessionState, State, Language, Error) {
return changes(j, "GetEmailSubmissionChanges", NS_MAIL_SUBMISSION,
func() EmailSubmissionChangesCommand {
return EmailSubmissionChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return EmailSubmissionChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
EmailSubmissionChangesResponse{},
func(path string, rof string) EmailSubmissionGetRefCommand {
@@ -1104,7 +1135,7 @@ func (j *Client) GetEmailSubmissionChanges(accountId string, session *Session, c
Destroyed: destroyed,
}
},
session, ctx, logger, acceptLanguage,
ctx,
)
}

View File

@@ -1,36 +1,36 @@
package jmap
import (
"context"
"strconv"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
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) {
func (j *Client) GetAllIdentities(accountId string, ctx Context) ([]Identity, SessionState, State, Language, Error) {
return getA(j, "GetAllIdentities", NS_IDENTITY,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
},
IdentityGetResponse{},
accountId, session, ctx, logger, acceptLanguage, []string{},
accountId, []string{},
ctx,
)
}
func (j *Client) GetIdentities(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, identityIds []string) ([]Identity, SessionState, State, Language, Error) {
func (j *Client) GetIdentities(accountId string, identityIds []string, ctx Context) ([]Identity, SessionState, State, Language, Error) {
return getA(j, "GetIdentities", NS_IDENTITY,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId, Ids: ids}
},
IdentityGetResponse{},
accountId, session, ctx, logger, acceptLanguage, identityIds,
accountId, identityIds,
ctx,
)
}
func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Identity, SessionState, State, Language, Error) {
func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, ctx Context) (map[string][]Identity, SessionState, State, Language, Error) {
return getN(j, "GetIdentitiesForAllAccounts", NS_IDENTITY,
func(accountId string, ids []string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId}
@@ -38,7 +38,8 @@ func (j *Client) GetIdentitiesForAllAccounts(accountIds []string, session *Sessi
IdentityGetResponse{},
func(resp IdentityGetResponse) []Identity { return resp.List },
identity1,
accountIds, session, ctx, logger, acceptLanguage, []string{},
accountIds, []string{},
ctx,
)
}
@@ -48,10 +49,11 @@ type IdentitiesAndMailboxesGetResponse struct {
Mailboxes []Mailbox `json:"mailboxes"`
}
func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (IdentitiesAndMailboxesGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds []string, ctx Context) (IdentitiesAndMailboxesGetResponse, SessionState, State, Language, Error) {
uniqueAccountIds := structs.Uniq(accountIds)
logger = j.logger("GetIdentitiesAndMailboxes", session, logger)
logger := j.logger("GetIdentitiesAndMailboxes", ctx)
ctx = ctx.WithLogger(logger)
calls := make([]Invocation, len(uniqueAccountIds)+1)
calls[0] = invocation(MailboxGetCommand{AccountId: mailboxAccountId}, "0")
@@ -59,17 +61,17 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
calls[i+1] = invocation(IdentityGetCommand{AccountId: accountId}, strconv.Itoa(i+1))
}
cmd, err := j.request(session, logger, NS_IDENTITY, calls...)
cmd, err := j.request(ctx, NS_IDENTITY, calls...)
if err != nil {
return IdentitiesAndMailboxesGetResponse{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (IdentitiesAndMailboxesGetResponse, State, Error) {
return command(j, ctx, cmd, func(body *Response) (IdentitiesAndMailboxesGetResponse, State, Error) {
identities := make(map[string][]Identity, len(uniqueAccountIds))
stateByAccountId := make(map[string]State, len(uniqueAccountIds))
notFound := []string{}
for i, accountId := range uniqueAccountIds {
var response IdentityGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandIdentityGet, strconv.Itoa(i+1), &response)
err = retrieveResponseMatchParameters(ctx, body, CommandIdentityGet, strconv.Itoa(i+1), &response)
if err != nil {
return IdentitiesAndMailboxesGetResponse{}, "", err
} else {
@@ -80,7 +82,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId string, accountIds [
}
var mailboxResponse MailboxGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, "0", &mailboxResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, "0", &mailboxResponse)
if err != nil {
return IdentitiesAndMailboxesGetResponse{}, "", err
}
@@ -93,91 +95,60 @@ 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(IdentitySetCommand{
AccountId: accountId,
Create: map[string]Identity{
"c": identity,
func (j *Client) CreateIdentity(accountId string, identity IdentityChange, ctx Context) (*Identity, SessionState, State, Language, Error) {
return create(j, "CreateIdentity", NS_IDENTITY,
func(accountId string, create map[string]IdentityChange) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Create: create}
},
}, "0"))
if err != nil {
return Identity{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identity, State, Error) {
var response IdentitySetResponse
err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response)
if err != nil {
return Identity{}, response.NewState, err
}
setErr, notok := response.NotCreated["c"]
if notok {
logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr) //NOSONAR
return Identity{}, "", setErrorError(setErr, IdentityType)
}
return response.Created["c"], response.NewState, nil
})
func(accountId string, ids string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId, Ids: []string{ids}}
},
func(resp IdentitySetResponse) map[string]*Identity {
return resp.Created
},
func(resp IdentityGetResponse) []Identity {
return resp.List
},
accountId, identity,
ctx,
)
}
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(IdentitySetCommand{
AccountId: accountId,
Update: map[string]PatchObject{
"c": identity.AsPatch(),
func (j *Client) UpdateIdentity(accountId string, id string, changes IdentityChange, ctx Context) (Identity, SessionState, State, Language, Error) {
return update(j, "UpdateIdentity", NS_IDENTITY,
func(update map[string]PatchObject) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Update: update}
},
}, "0"))
if err != nil {
return Identity{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Identity, State, Error) {
var response IdentitySetResponse
err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response)
if err != nil {
return Identity{}, response.NewState, err
}
setErr, notok := response.NotCreated["c"]
if notok {
logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr)
return Identity{}, "", setErrorError(setErr, IdentityType)
}
return response.Created["c"], response.NewState, nil
})
func(id string) IdentityGetCommand {
return IdentityGetCommand{AccountId: accountId, Ids: []string{id}}
},
func(resp IdentitySetResponse) map[string]SetError { return resp.NotUpdated },
func(resp IdentityGetResponse) Identity { return resp.List[0] },
id, changes,
ctx,
)
}
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(IdentitySetCommand{
AccountId: accountId,
Destroy: ids,
}, "0"))
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]string, State, Error) {
var response IdentitySetResponse
err = retrieveResponseMatchParameters(logger, body, CommandIdentitySet, "0", &response)
if err != nil {
return nil, "", err
}
for _, setErr := range response.NotDestroyed {
// TODO only returning the first error here, we should probably aggregate them instead
logger.Error().Msgf("%T.NotCreated returned an error %v", response, setErr)
return nil, "", setErrorError(setErr, IdentityType)
}
return response.Destroyed, response.NewState, nil
})
func (j *Client) DeleteIdentity(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
return destroy(j, "DeleteIdentity", NS_IDENTITY,
func(accountId string, destroy []string) IdentitySetCommand {
return IdentitySetCommand{AccountId: accountId, Destroy: destroyIds}
},
IdentitySetResponse{},
accountId, destroyIds,
ctx,
)
}
type IdentityChanges = ChangesTemplate[Identity]
// Retrieve the changes in Email Identities since a given State.
// @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) {
func (j *Client) GetIdentityChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (IdentityChanges, SessionState, State, Language, Error) {
return changes(j, "GetIdentityChanges", NS_IDENTITY,
func() IdentityChangesCommand {
return IdentityChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return IdentityChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
IdentityChangesResponse{},
func(path string, rof string) IdentityGetRefCommand {
@@ -201,6 +172,6 @@ func (j *Client) GetIdentityChanges(accountId string, session *Session, ctx cont
Destroyed: destroyed,
}
},
session, ctx, logger, acceptLanguage,
ctx,
)
}

View File

@@ -1,17 +1,14 @@
package jmap
import (
"context"
"fmt"
"slices"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
var NS_MAILBOX = ns(JmapMail)
func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (MailboxGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetMailbox(accountId string, ids []string, ctx Context) (MailboxGetResponse, SessionState, State, Language, Error) {
/*
return get(j, "GetMailbox", NS_MAILBOX,
func(accountId string, ids []string) MailboxGetCommand {
@@ -23,10 +20,10 @@ func (j *Client) GetMailbox(accountId string, session *Session, ctx context.Cont
)
*/
return fget[Mailboxes](MAILBOX, j, "GetMailbox", accountId, ids, session, ctx, logger, acceptLanguage)
return fget[Mailboxes](MAILBOX, j, "GetMailbox", accountId, ids, ctx)
}
func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]Mailbox, SessionState, State, Language, Error) {
func (j *Client) GetAllMailboxes(accountIds []string, ctx Context) (map[string][]Mailbox, SessionState, State, Language, Error) {
/*
return getAN(j, "GetAllMailboxes", NS_MAILBOX,
func(accountId string, ids []string) MailboxGetCommand {
@@ -37,11 +34,12 @@ func (j *Client) GetAllMailboxes(accountIds []string, session *Session, ctx cont
accountIds, session, ctx, logger, acceptLanguage, []string{},
)
*/
return fgetAN[Mailboxes](MAILBOX, j, "GetAllMailboxes", identity1, accountIds, []string{}, session, ctx, logger, acceptLanguage)
return fgetAN[Mailboxes](MAILBOX, j, "GetAllMailboxes", identity1, accountIds, []string{}, ctx)
}
func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, filter MailboxFilterElement) (map[string][]Mailbox, SessionState, State, Language, Error) {
logger = j.logger("SearchMailboxes", session, logger)
func (j *Client) SearchMailboxes(accountIds []string, filter MailboxFilterElement, ctx Context) (map[string][]Mailbox, SessionState, State, Language, Error) {
logger := j.logger("SearchMailboxes", ctx)
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
@@ -57,17 +55,17 @@ func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx cont
},
}, mcid(accountId, "1"))
}
cmd, err := j.request(session, logger, NS_MAILBOX, invocations...)
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]Mailbox, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string][]Mailbox, State, Error) {
resp := map[string][]Mailbox{}
stateByAccountid := map[string]State{}
for _, accountId := range uniqueAccountIds {
var response MailboxGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &response)
err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, mcid(accountId, "1"), &response)
if err != nil {
return nil, "", err
}
@@ -79,8 +77,9 @@ func (j *Client) SearchMailboxes(accountIds []string, session *Session, ctx cont
})
}
func (j *Client) SearchMailboxIdsPerRole(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, roles []string) (map[string]map[string]string, SessionState, State, Language, Error) { //NOSONAR
logger = j.logger("SearchMailboxIdsPerRole", session, logger)
func (j *Client) SearchMailboxIdsPerRole(accountIds []string, roles []string, ctx Context) (map[string]map[string]string, SessionState, State, Language, Error) { //NOSONAR
logger := j.logger("SearchMailboxIdsPerRole", ctx)
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
@@ -90,19 +89,19 @@ func (j *Client) SearchMailboxIdsPerRole(accountIds []string, session *Session,
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...)
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]map[string]string, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]map[string]string, State, Error) {
resp := map[string]map[string]string{}
stateByAccountid := map[string]State{}
for _, accountId := range uniqueAccountIds {
mailboxIdsByRole := map[string]string{}
for _, role := range roles {
var response MailboxQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxQuery, mcid(accountId, role), &response)
err = retrieveResponseMatchParameters(ctx, body, CommandMailboxQuery, mcid(accountId, role), &response)
if err != nil {
return nil, "", err
}
@@ -134,10 +133,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) {
func (j *Client) GetMailboxChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (MailboxChanges, SessionState, State, Language, Error) {
return changesA(j, "GetMailboxChanges", NS_MAILBOX,
func() MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return MailboxChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
MailboxChangesResponse{},
MailboxGetResponse{},
@@ -152,17 +152,19 @@ func (j *Client) GetMailboxChanges(accountId string, session *Session, ctx conte
}
},
newMailboxChanges,
session, ctx, logger, acceptLanguage,
ctx,
)
}
// 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
func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, //NOSONAR
sinceStateMap map[string]State, maxChanges uint,
ctx Context) (map[string]MailboxChanges, SessionState, State, Language, Error) {
return changesN(j, "GetMailboxChangesForMultipleAccounts", NS_MAILBOX,
accountIds, sinceStateMap,
func(accountId string, state State) MailboxChangesCommand {
return MailboxChangesCommand{AccountId: accountId, SinceState: state, MaxChanges: posUIntPtr(maxChanges)}
return MailboxChangesCommand{AccountId: accountId, SinceState: state, MaxChanges: uintPtr(maxChanges)}
},
MailboxChangesResponse{},
func(accountId string, path string, ref string) MailboxGetRefCommand {
@@ -172,12 +174,13 @@ func (j *Client) GetMailboxChangesForMultipleAccounts(accountIds []string, sessi
newMailboxChanges,
identity1,
func(resp MailboxGetResponse) State { return resp.State },
session, ctx, logger, acceptLanguage,
ctx,
)
}
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string][]string, SessionState, State, Language, Error) {
logger = j.logger("GetMailboxRolesForMultipleAccounts", session, logger)
func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, ctx Context) (map[string][]string, SessionState, State, Language, Error) {
logger := j.logger("GetMailboxRolesForMultipleAccounts", ctx)
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
n := len(uniqueAccountIds)
@@ -205,17 +208,17 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session
}, mcid(accountId, "1"))
}
cmd, err := j.request(session, logger, NS_MAILBOX, invocations...)
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string][]string, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string][]string, State, Error) {
resp := make(map[string][]string, n)
stateByAccountId := make(map[string]State, n)
for _, accountId := range uniqueAccountIds {
var getResponse MailboxGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "1"), &getResponse)
err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, mcid(accountId, "1"), &getResponse)
if err != nil {
return nil, "", err
}
@@ -231,8 +234,9 @@ func (j *Client) GetMailboxRolesForMultipleAccounts(accountIds []string, session
})
}
func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (map[string]string, SessionState, State, Language, Error) {
logger = j.logger("GetInboxNameForMultipleAccounts", session, logger)
func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, ctx Context) (map[string]string, SessionState, State, Language, Error) {
logger := j.logger("GetInboxNameForMultipleAccounts", ctx)
ctx = ctx.WithLogger(logger)
uniqueAccountIds := structs.Uniq(accountIds)
n := len(uniqueAccountIds)
@@ -250,17 +254,17 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []string, session *S
}, mcid(accountId, "0"))
}
cmd, err := j.request(session, logger, NS_MAILBOX, invocations...)
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]string, State, Error) {
return command(j, ctx, cmd, func(body *Response) (map[string]string, State, Error) {
resp := make(map[string]string, n)
stateByAccountId := make(map[string]State, n)
for _, accountId := range uniqueAccountIds {
var r MailboxQueryResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxGet, mcid(accountId, "0"), &r)
err = retrieveResponseMatchParameters(ctx, body, CommandMailboxGet, mcid(accountId, "0"), &r)
if err != nil {
return nil, "", err
}
@@ -280,89 +284,48 @@ 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(MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Update: map[string]PatchObject{
mailboxId: update.AsPatch(),
func (j *Client) UpdateMailbox(accountId string, mailboxId string, change MailboxChange, //NOSONAR
ctx Context) (Mailbox, SessionState, State, Language, Error) {
return update(j, "UpdateMailbox", NS_MAILBOX,
func(update map[string]PatchObject) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Update: update}
},
}, "0"))
if err != nil {
return Mailbox{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Mailbox, State, Error) {
var setResp MailboxSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxSet, "0", &setResp)
if err != nil {
return Mailbox{}, "", err
}
setErr, notok := setResp.NotUpdated["u"]
if notok {
logger.Error().Msgf("%T.NotUpdated returned an error %v", setResp, setErr)
return Mailbox{}, "", setErrorError(setErr, MailboxType)
}
return setResp.Updated["c"], setResp.NewState, nil
})
}
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(MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Create: map[string]MailboxChange{
"c": create,
func(id string) MailboxGetCommand {
return MailboxGetCommand{AccountId: accountId, Ids: []string{id}}
},
}, "0"))
if err != nil {
return Mailbox{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Mailbox, State, Error) {
var setResp MailboxSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandMailboxSet, "0", &setResp)
if err != nil {
return Mailbox{}, "", err
}
setErr, notok := setResp.NotCreated["c"]
if notok {
logger.Error().Msgf("%T.NotCreated returned an error %v", setResp, setErr)
return Mailbox{}, "", setErrorError(setErr, MailboxType)
}
if mailbox, ok := setResp.Created["c"]; ok {
return mailbox, setResp.NewState, nil
} else {
return Mailbox{}, "", jmapError(fmt.Errorf("failed to find created %T in response", Mailbox{}), JmapErrorMissingCreatedObject)
}
})
func(resp MailboxSetResponse) map[string]SetError { return resp.NotUpdated },
func(resp MailboxGetResponse) Mailbox { return resp.List[0] },
mailboxId, change,
ctx,
)
}
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)
set := MailboxSetCommand{
AccountId: accountId,
IfInState: ifInState,
Destroy: mailboxIds,
}
cmd, err := j.request(session, logger, NS_MAILBOX, invocation(set, "0"))
if err != nil {
return nil, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) ([]string, State, Error) {
var setResp MailboxSetResponse
err = retrieveSet(logger, body, set, "0", &setResp)
if err != nil {
return nil, "", err
}
setErr, notok := setResp.NotDestroyed["u"]
if notok {
logger.Error().Msgf("%T.NotDestroyed returned an error %v", setResp, setErr)
return nil, "", setErrorError(setErr, MailboxType)
}
return setResp.Destroyed, setResp.NewState, nil
})
func (j *Client) CreateMailbox(accountId string, mailbox MailboxChange, ctx Context) (*Mailbox, SessionState, State, Language, Error) {
return create(j, "CreateMailbox", NS_MAILBOX,
func(accountId string, create map[string]MailboxChange) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Create: create}
},
func(accountId string, ids string) MailboxGetCommand {
return MailboxGetCommand{AccountId: accountId, Ids: []string{ids}}
},
func(resp MailboxSetResponse) map[string]*Mailbox {
return resp.Created
},
func(resp MailboxGetResponse) []Mailbox {
return resp.List
},
accountId, mailbox,
ctx,
)
}
func (j *Client) DeleteMailboxes(accountId string, destroyIds []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
return destroy(j, "DeleteMailboxes", NS_MAILBOX,
func(accountId string, destroy []string) MailboxSetCommand {
return MailboxSetCommand{AccountId: accountId, Destroy: destroyIds}
},
MailboxSetResponse{},
accountId, destroyIds,
ctx,
)
}

View File

@@ -1,8 +1,6 @@
package jmap
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/log"
)
@@ -22,14 +20,15 @@ type Objects struct {
// Retrieve objects of all types by their identifiers in a single batch.
// @api:tags changes
func (j *Client) GetObjects(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, //NOSONAR
func (j *Client) GetObjects(accountId string, //NOSONAR
mailboxIds []string, emailIds []string,
addressbookIds []string, contactIds []string,
calendarIds []string, eventIds []string,
quotaIds []string, identityIds []string,
emailSubmissionIds []string,
ctx Context,
) (Objects, SessionState, State, Language, Error) {
l := j.logger("GetObjects", session, logger).With()
l := j.logger("GetObjects", ctx).With()
if len(mailboxIds) > 0 {
l = l.Array("mailboxIds", log.SafeStringArray(mailboxIds))
}
@@ -57,7 +56,8 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
if len(emailSubmissionIds) > 0 {
l = l.Array("emailSubmissionIds", log.SafeStringArray(emailSubmissionIds))
}
logger = log.From(l)
logger := log.From(l)
ctx = ctx.WithLogger(logger)
methodCalls := []Invocation{}
if len(mailboxIds) > 0 {
@@ -88,17 +88,17 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
methodCalls = append(methodCalls, invocation(EmailSubmissionGetCommand{AccountId: accountId, Ids: emailSubmissionIds}, "emailSubmissionIds"))
}
cmd, err := j.request(session, logger, NS_OBJECTS, methodCalls...)
cmd, err := j.request(ctx, NS_OBJECTS, methodCalls...)
if err != nil {
return Objects{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (Objects, State, Error) {
return command(j, ctx, cmd, func(body *Response) (Objects, State, Error) {
objs := Objects{}
states := map[string]State{}
var mailboxes MailboxGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandMailboxGet, "mailboxes", &mailboxes); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandMailboxGet, "mailboxes", &mailboxes); err != nil {
return Objects{}, "", err
} else if ok {
objs.Mailboxes = &mailboxes
@@ -106,7 +106,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
}
var emails EmailGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailGet, "emails", &emails); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailGet, "emails", &emails); err != nil {
return Objects{}, "", err
} else if ok {
objs.Emails = &emails
@@ -114,7 +114,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
}
var calendars CalendarGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandCalendarGet, "calendars", &calendars); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarGet, "calendars", &calendars); err != nil {
return Objects{}, "", err
} else if ok {
objs.Calendars = &calendars
@@ -122,7 +122,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
}
var events CalendarEventGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandCalendarEventGet, "events", &events); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandCalendarEventGet, "events", &events); err != nil {
return Objects{}, "", err
} else if ok {
objs.Events = &events
@@ -130,7 +130,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
}
var addressbooks AddressBookGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandAddressBookGet, "addressbooks", &addressbooks); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandAddressBookGet, "addressbooks", &addressbooks); err != nil {
return Objects{}, "", err
} else if ok {
objs.Addressbooks = &addressbooks
@@ -138,7 +138,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
}
var contacts ContactCardGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandContactCardGet, "contacts", &contacts); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandContactCardGet, "contacts", &contacts); err != nil {
return Objects{}, "", err
} else if ok {
objs.Contacts = &contacts
@@ -146,7 +146,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
}
var quotas QuotaGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandQuotaGet, "quotas", &quotas); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandQuotaGet, "quotas", &quotas); err != nil {
return Objects{}, "", err
} else if ok {
objs.Quotas = &quotas
@@ -154,7 +154,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
}
var identities IdentityGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandIdentityGet, "identities", &identities); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandIdentityGet, "identities", &identities); err != nil {
return Objects{}, "", err
} else if ok {
objs.Identities = &identities
@@ -162,7 +162,7 @@ func (j *Client) GetObjects(accountId string, session *Session, ctx context.Cont
}
var submissions EmailSubmissionGetResponse
if ok, err := tryRetrieveResponseMatchParameters(logger, body, CommandEmailSubmissionGet, "submissions", &submissions); err != nil {
if ok, err := tryRetrieveResponseMatchParameters(ctx, body, CommandEmailSubmissionGet, "submissions", &submissions); err != nil {
return Objects{}, "", err
} else if ok {
objs.EmailSubmissions = &submissions

View File

@@ -1,37 +1,50 @@
package jmap
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/log"
)
var NS_PRINCIPALS = ns(JmapPrincipals)
type PrincipalsResponse struct {
Principals []Principal `json:"principals"`
NotFound []string `json:"notFound,omitempty"`
}
func (j *Client) GetPrincipals(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (PrincipalsResponse, SessionState, State, Language, Error) {
logger = j.logger("GetPrincipals", session, logger)
cmd, err := j.request(session, logger, NS_PRINCIPALS,
invocation(PrincipalGetCommand{AccountId: accountId, Ids: ids}, "0"),
func (j *Client) GetPrincipals(accountId string, ids []string, ctx Context) (PrincipalGetResponse, SessionState, State, Language, Error) {
return get(j, "GetPrincipals", NS_PRINCIPALS,
func(accountId string, ids []string) PrincipalGetCommand {
return PrincipalGetCommand{AccountId: accountId, Ids: ids}
},
PrincipalGetResponse{},
identity1,
accountId, ids,
ctx,
)
}
type PrincipalSearchResults SearchResultsTemplate[Principal]
var _ SearchResults[Principal] = PrincipalSearchResults{}
func (r PrincipalSearchResults) GetResults() []Principal { return r.Results }
func (r PrincipalSearchResults) GetCanCalculateChanges() bool { return r.CanCalculateChanges }
func (r PrincipalSearchResults) GetPosition() uint { return r.Position }
func (r PrincipalSearchResults) GetLimit() uint { return r.Limit }
func (r PrincipalSearchResults) GetTotal() *uint { return r.Total }
func (j *Client) QueryPrincipals(accountId string,
filter PrincipalFilterElement, sortBy []PrincipalComparator,
position uint, limit uint, calculateTotal bool,
ctx Context) (PrincipalSearchResults, SessionState, State, Language, Error) {
return query(j, "QueryPrincipals", NS_PRINCIPALS,
[]PrincipalComparator{{Property: PrincipalPropertyName, IsAscending: true}},
func(filter PrincipalFilterElement, sortBy []PrincipalComparator, position uint, limit uint) PrincipalQueryCommand {
return PrincipalQueryCommand{AccountId: accountId, Filter: filter, Sort: sortBy, Position: position, Limit: limit, CalculateTotal: calculateTotal}
},
func(cmd Command, path string, rof string) PrincipalGetRefCommand {
return PrincipalGetRefCommand{AccountId: accountId, IdsRef: &ResultReference{Name: cmd, Path: path, ResultOf: rof}}
},
func(query PrincipalQueryResponse, get PrincipalGetResponse) PrincipalSearchResults {
return PrincipalSearchResults{
Results: get.List,
CanCalculateChanges: query.CanCalculateChanges,
Position: query.Position,
Total: uintPtrIf(query.Total, calculateTotal),
Limit: query.Limit,
}
},
filter, sortBy, limit, position, ctx,
)
if err != nil {
return PrincipalsResponse{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (PrincipalsResponse, State, Error) {
var response PrincipalGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandPrincipalGet, "0", &response)
if err != nil {
return PrincipalsResponse{}, response.State, err
}
return PrincipalsResponse{
Principals: response.List,
NotFound: response.NotFound,
}, response.State, nil
})
}

View File

@@ -1,14 +1,8 @@
package jmap
import (
"context"
"github.com/opencloud-eu/opencloud/pkg/log"
)
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) {
func (j *Client) GetQuotas(accountIds []string, ctx Context) (map[string]QuotaGetResponse, SessionState, State, Language, Error) {
return getN(j, "GetQuotas", NS_QUOTA,
func(accountId string, ids []string) QuotaGetCommand {
return QuotaGetCommand{AccountId: accountId}
@@ -16,7 +10,8 @@ func (j *Client) GetQuotas(accountIds []string, session *Session, ctx context.Co
QuotaGetResponse{},
identity1,
identity1,
accountIds, session, ctx, logger, acceptLanguage, []string{},
accountIds, []string{},
ctx,
)
}
@@ -24,11 +19,11 @@ type QuotaChanges = ChangesTemplate[Quota]
// Retrieve the changes in Quotas since a given State.
// @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) {
func (j *Client) GetQuotaChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (QuotaChanges, SessionState, State, Language, Error) {
return changesA(j, "GetQuotaChanges", NS_QUOTA,
func() QuotaChangesCommand {
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
QuotaChangesResponse{},
QuotaGetResponse{},
@@ -52,15 +47,15 @@ func (j *Client) GetQuotaChanges(accountId string, session *Session, ctx context
Destroyed: destroyed,
}
},
session, ctx, logger, acceptLanguage,
ctx,
)
}
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) {
func (j *Client) GetQuotaUsageChanges(accountId string, sinceState State, maxChanges uint,
ctx Context) (QuotaChanges, SessionState, State, Language, Error) {
return updates(j, "GetQuotaUsageChanges", NS_QUOTA,
func() QuotaChangesCommand {
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: posUIntPtr(maxChanges)}
return QuotaChangesCommand{AccountId: accountId, SinceState: sinceState, MaxChanges: uintPtr(maxChanges)}
},
QuotaChangesResponse{},
func(path string, rof string) QuotaGetRefCommand {
@@ -87,6 +82,6 @@ func (j *Client) GetQuotaUsageChanges(accountId string, session *Session, ctx co
Updated: updated,
}
},
session, ctx, logger, acceptLanguage,
ctx,
)
}

View File

@@ -1,11 +1,8 @@
package jmap
import (
"context"
"fmt"
"time"
"github.com/opencloud-eu/opencloud/pkg/log"
)
var NS_VACATION = ns(JmapVacationResponse)
@@ -14,19 +11,20 @@ const (
vacationResponseId = "singleton"
)
func (j *Client) GetVacationResponse(accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponseGetResponse, SessionState, State, Language, Error) {
func (j *Client) GetVacationResponse(accountId string, ctx Context) (VacationResponseGetResponse, SessionState, State, Language, Error) {
return get(j, "GetVacationResponse", NS_VACATION,
func(accountId string, ids []string) VacationResponseGetCommand {
return VacationResponseGetCommand{AccountId: accountId}
},
VacationResponseGetResponse{},
identity1,
accountId, session, ctx, logger, acceptLanguage, []string{},
accountId, []string{},
ctx,
)
}
// Same as VacationResponse but without the id.
type VacationResponsePayload struct {
type VacationResponseChange struct {
// Should a vacation response be sent if a message arrives between the "fromDate" and "toDate"?
IsEnabled bool `json:"isEnabled"`
// If "isEnabled" is true, messages that arrive on or after this date-time (but before the "toDate" if defined) should receive the
@@ -49,33 +47,39 @@ type VacationResponsePayload struct {
HtmlBody string `json:"htmlBody,omitempty"`
}
func (j *Client) SetVacationResponse(accountId string, vacation VacationResponsePayload, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (VacationResponse, SessionState, State, Language, Error) {
logger = j.logger("SetVacationResponse", session, logger)
func (j *Client) SetVacationResponse(accountId string, vacation VacationResponseChange,
ctx Context) (VacationResponse, SessionState, State, Language, Error) {
logger := j.logger("SetVacationResponse", ctx)
ctx = ctx.WithLogger(logger)
cmd, err := j.request(session, logger, NS_VACATION,
invocation(VacationResponseSetCommand{
AccountId: accountId,
Create: map[string]VacationResponse{
vacationResponseId: {
IsEnabled: vacation.IsEnabled,
FromDate: vacation.FromDate,
ToDate: vacation.ToDate,
Subject: vacation.Subject,
TextBody: vacation.TextBody,
HtmlBody: vacation.HtmlBody,
},
set := VacationResponseSetCommand{
AccountId: accountId,
Create: map[string]VacationResponse{
vacationResponseId: {
IsEnabled: vacation.IsEnabled,
FromDate: vacation.FromDate,
ToDate: vacation.ToDate,
Subject: vacation.Subject,
TextBody: vacation.TextBody,
HtmlBody: vacation.HtmlBody,
},
}, "0"),
},
}
get := VacationResponseGetCommand{AccountId: accountId}
cmd, err := j.request(ctx, NS_VACATION,
invocation(set, "0"),
// chain a second request to get the current complete VacationResponse object
// after performing the changes, as that makes for a better API
invocation(VacationResponseGetCommand{AccountId: accountId}, "1"),
invocation(get, "1"),
)
if err != nil {
return VacationResponse{}, "", "", "", err
}
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (VacationResponse, State, Error) {
return command(j, ctx, cmd, func(body *Response) (VacationResponse, State, Error) {
var setResponse VacationResponseSetResponse
err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseSet, "0", &setResponse)
err = retrieveSet(ctx, body, set, "0", &setResponse)
if err != nil {
return VacationResponse{}, "", err
}
@@ -88,7 +92,7 @@ func (j *Client) SetVacationResponse(accountId string, vacation VacationResponse
}
var getResponse VacationResponseGetResponse
err = retrieveResponseMatchParameters(logger, body, CommandVacationResponseGet, "1", &getResponse)
err = retrieveGet(ctx, body, get, "1", &getResponse)
if err != nil {
return VacationResponse{}, "", err
}

View File

@@ -22,13 +22,27 @@ type Client struct {
WsPushListener
}
type ApiSupplier interface {
Api() ApiClient
}
type Hooks interface {
OnSessionOutdated(session *Session, newState SessionState)
}
var _ io.Closer = &Client{}
var _ WsPushListener = &Client{}
var _ ApiSupplier = &Client{}
var _ Hooks = &Client{}
func (j *Client) Close() error {
return errors.Join(j.api.Close(), j.session.Close(), j.blob.Close(), j.ws.Close())
}
func (j *Client) Api() ApiClient {
return j.api
}
func NewClient(session SessionClient, api ApiClient, blob BlobClient, ws WsClientFactory) Client {
return Client{
session: session,
@@ -44,7 +58,7 @@ func (j *Client) AddSessionEventListener(listener SessionEventListener) {
j.sessionEventListeners.add(listener)
}
func (j *Client) onSessionOutdated(session *Session, newSessionState SessionState) {
func (j *Client) OnSessionOutdated(session *Session, newSessionState SessionState) {
j.sessionEventListeners.signal(func(listener SessionEventListener) {
listener.OnSessionOutdated(session, newSessionState)
})
@@ -65,25 +79,25 @@ func (j *Client) FetchSession(ctx context.Context, sessionUrl *url.URL, username
return newSession(wk)
}
func (j *Client) logger(operation string, _ *Session, logger *log.Logger) *log.Logger {
l := logger.With().Str(logOperation, operation)
func (j *Client) logger(operation string, ctx Context) *log.Logger {
l := ctx.Logger.With().Str(logOperation, operation)
return log.From(l)
}
func (j *Client) loggerParams(operation string, _ *Session, logger *log.Logger, params func(zerolog.Context) zerolog.Context) *log.Logger {
l := logger.With().Str(logOperation, operation)
func (j *Client) loggerParams(operation string, ctx Context, params func(zerolog.Context) zerolog.Context) *log.Logger {
l := ctx.Logger.With().Str(logOperation, operation)
if params != nil {
l = params(l)
}
return log.From(l)
}
func (j *Client) maxCallsCheck(calls int, session *Session, logger *log.Logger) Error {
if calls > session.Capabilities.Core.MaxCallsInRequest {
logger.Error().
Int("max-calls-in-request", session.Capabilities.Core.MaxCallsInRequest).
func (j *Client) maxCallsCheck(calls int, ctx Context) Error {
if calls > ctx.Session.Capabilities.Core.MaxCallsInRequest {
ctx.Logger.Error().
Int("max-calls-in-request", ctx.Session.Capabilities.Core.MaxCallsInRequest).
Int("calls-in-request", calls).
Msgf("number of calls in request payload (%d) exceeds the allowed maximum (%d)", session.Capabilities.Core.MaxCallsInRequest, calls)
Msgf("number of calls in request payload (%d) exceeds the allowed maximum (%d)", ctx.Session.Capabilities.Core.MaxCallsInRequest, calls)
return jmapError(errTooManyMethodCalls, JmapErrorTooManyMethodCalls)
}
return nil
@@ -92,8 +106,8 @@ func (j *Client) maxCallsCheck(calls int, session *Session, logger *log.Logger)
// Construct a Request from the given list of Invocation objects.
//
// If an issue occurs, then it is logged prior to returning it.
func (j *Client) request(session *Session, logger *log.Logger, using []JmapNamespace, methodCalls ...Invocation) (Request, Error) {
err := j.maxCallsCheck(len(methodCalls), session, logger)
func (j *Client) request(ctx Context, using []JmapNamespace, methodCalls ...Invocation) (Request, Error) {
err := j.maxCallsCheck(len(methodCalls), ctx)
if err != nil {
return Request{}, err
}

View File

@@ -217,7 +217,12 @@ func (h *HttpJmapClient) GetSession(ctx context.Context, sessionUrl *url.URL, us
return data, nil
}
func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, session *Session, request Request, acceptLanguage string) ([]byte, Language, Error) { //NOSONAR
func (h *HttpJmapClient) Command(request Request, ctx Context) ([]byte, Language, Error) { //NOSONAR
session := ctx.Session
logger := ctx.Logger
acceptLanguage := ctx.AcceptLanguage
cotx := ctx.Context
jmapUrl := session.JmapUrl.String()
endpoint := session.JmapEndpoint
logger = log.From(logger.With().Str(logEndpoint, endpoint))
@@ -228,7 +233,7 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio
return nil, "", jmapError(err, JmapErrorEncodingRequestBody)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, jmapUrl, bytes.NewBuffer(bodyBytes))
req, err := http.NewRequestWithContext(cotx, http.MethodPost, jmapUrl, bytes.NewBuffer(bodyBytes))
if err != nil {
logger.Error().Err(err).Msgf("failed to create POST request for %v", jmapUrl)
return nil, "", jmapError(err, JmapErrorCreatingRequest)
@@ -249,7 +254,7 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio
logger.Trace().Str(logEndpoint, endpoint).Str(logProto, logProtoJmap).Str(logType, logTypeRequest).Msg(string(requestBytes))
}
}
if err := h.auth(ctx, session.Username, logger, req); err != nil {
if err := h.auth(cotx, session.Username, logger, req); err != nil {
return nil, "", err
}
@@ -295,10 +300,15 @@ func (h *HttpJmapClient) Command(ctx context.Context, logger *log.Logger, sessio
return body, language, nil
}
func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, acceptLanguage string, body io.Reader) (UploadedBlob, Language, Error) { //NOSONAR
func (h *HttpJmapClient) UploadBinary(uploadUrl string, endpoint string, contentType string, body io.Reader, ctx Context) (UploadedBlob, Language, Error) { //NOSONAR
session := ctx.Session
logger := ctx.Logger
acceptLanguage := ctx.AcceptLanguage
cotx := ctx.Context
logger = log.From(logger.With().Str(logEndpoint, endpoint))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadUrl, body)
req, err := http.NewRequestWithContext(cotx, http.MethodPost, uploadUrl, body)
if err != nil {
logger.Error().Err(err).Msgf("failed to create POST request for %v", uploadUrl)
return UploadedBlob{}, "", jmapError(err, JmapErrorCreatingRequest)
@@ -315,7 +325,7 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s
}
}
if err := h.auth(ctx, session.Username, logger, req); err != nil {
if err := h.auth(cotx, session.Username, logger, req); err != nil {
return UploadedBlob{}, "", err
}
@@ -357,8 +367,6 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s
return UploadedBlob{}, language, jmapError(err, JmapErrorServerResponse)
}
logger.Trace()
var result UploadedBlob
err = json.Unmarshal(responseBody, &result)
if err != nil {
@@ -370,10 +378,15 @@ func (h *HttpJmapClient) UploadBinary(ctx context.Context, logger *log.Logger, s
return result, language, nil
}
func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string, acceptLanguage string) (*BlobDownload, Language, Error) { //NOSONAR
func (h *HttpJmapClient) DownloadBinary(downloadUrl string, endpoint string, ctx Context) (*BlobDownload, Language, Error) { //NOSONAR
session := ctx.Session
logger := ctx.Logger
acceptLanguage := ctx.AcceptLanguage
cotx := ctx.Context
logger = log.From(logger.With().Str(logEndpoint, endpoint))
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadUrl, nil)
req, err := http.NewRequestWithContext(cotx, http.MethodGet, downloadUrl, nil)
if err != nil {
logger.Error().Err(err).Msgf("failed to create GET request for %v", downloadUrl)
return nil, "", jmapError(err, JmapErrorCreatingRequest)
@@ -389,7 +402,7 @@ func (h *HttpJmapClient) DownloadBinary(ctx context.Context, logger *log.Logger,
}
}
if err := h.auth(ctx, session.Username, logger, req); err != nil {
if err := h.auth(cotx, session.Username, logger, req); err != nil {
return nil, "", err
}

View File

@@ -1,7 +1,6 @@
package jmap
import (
"context"
golog "log"
"math/rand"
"regexp"
@@ -20,7 +19,6 @@ import (
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/brianvoe/gofakeit/v7"
"github.com/opencloud-eu/opencloud/pkg/jscontact"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
@@ -45,17 +43,17 @@ func TestAddressBooks(t *testing.T) {
func(session *Session) string { return session.PrimaryAccounts.Contacts },
list,
getid,
func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (AddressBookGetResponse, SessionState, State, Language, Error) {
return s.client.GetAddressbooks(accountId, session, ctx, logger, acceptLanguage, ids)
func(s *StalwartTest, accountId string, ids []string, ctx Context) (AddressBookGetResponse, SessionState, State, Language, Error) {
return s.client.GetAddressbooks(accountId, ids, ctx)
},
func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string, change AddressBookChange) (AddressBook, SessionState, State, Language, Error) { //NOSONAR
return s.client.UpdateAddressBook(accountId, session, ctx, logger, acceptLanguage, id, change)
func(s *StalwartTest, accountId string, id string, change AddressBookChange, ctx Context) (AddressBook, SessionState, State, Language, Error) { //NOSONAR
return s.client.UpdateAddressBook(accountId, id, change, ctx)
},
func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR
return s.client.DeleteAddressBook(accountId, ids, session, ctx, logger, acceptLanguage)
func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR
return s.client.DeleteAddressBook(accountId, ids, ctx)
},
func(s *StalwartTest, t *testing.T, accountId string, count uint, session *Session, user User, principalIds []string) (AddressBookBoxes, []AddressBook, SessionState, State, error) {
return s.fillAddressBook(t, accountId, count, session, user, principalIds)
func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, user User, principalIds []string) (AddressBookBoxes, []AddressBook, SessionState, State, error) {
return s.fillAddressBook(t, accountId, count, ctx, user, principalIds)
},
func(orig AddressBook) AddressBookChange {
return AddressBookChange{
@@ -86,6 +84,7 @@ func TestContacts(t *testing.T) {
user := pickUser()
session := s.Session(user.name)
ctx := s.Context(session)
accountId, addressbookId, expectedContactCardsById, boxes, err := s.fillContacts(t, count, session, user)
require.NoError(err)
@@ -99,15 +98,19 @@ func TestContacts(t *testing.T) {
{Property: ContactCardPropertyCreated, IsAscending: true},
}
contactsByAccount, _, _, _, err := s.client.QueryContactCards([]string{accountId}, session, t.Context(), s.logger, "", filter, sortBy, 0, 0)
contactsByAccount, _, _, _, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, 0, true, ctx)
require.NoError(err)
require.Len(contactsByAccount, 1)
require.Contains(contactsByAccount, accountId)
contacts := contactsByAccount[accountId]
require.Len(contacts, int(count))
results := contactsByAccount[accountId]
require.Len(results.Results, int(count))
require.Equal(uint(0), results.Limit)
require.Equal(uint(0), results.Position)
require.Equal(uint(0), results.Total)
require.Equal(true, results.CanCalculateChanges)
for _, actual := range contacts {
for _, actual := range results.Results {
expected, ok := expectedContactCardsById[actual.Id]
require.True(ok, "failed to find created contact by its id")
matchContact(t, actual, expected)
@@ -115,13 +118,13 @@ func TestContacts(t *testing.T) {
// retrieve all objects at once
{
ids := structs.Map(contacts, func(c ContactCard) string { return c.Id })
fetched, _, _, _, err := s.client.GetContactCards(accountId, session, t.Context(), s.logger, "", ids)
ids := structs.Map(results.Results, func(c ContactCard) string { return c.Id })
fetched, _, _, _, err := s.client.GetContactCards(accountId, ids, ctx)
require.NoError(err)
require.Empty(fetched.NotFound)
require.Len(fetched.List, len(ids))
byId := structs.Index(fetched.List, func(r ContactCard) string { return r.Id })
for _, actual := range contacts {
for _, actual := range results.Results {
expected, ok := byId[actual.Id]
require.True(ok, "failed to find created contact by its id")
matchContact(t, actual, expected)
@@ -129,8 +132,8 @@ func TestContacts(t *testing.T) {
}
// retrieve each object one by one
for _, actual := range contacts {
fetched, _, _, _, err := s.client.GetContactCards(accountId, session, t.Context(), s.logger, "", []string{actual.Id})
for _, actual := range results.Results {
fetched, _, _, _, err := s.client.GetContactCards(accountId, []string{actual.Id}, ctx)
require.NoError(err)
require.Len(fetched.List, 1)
matchContact(t, fetched.List[0], actual)
@@ -169,7 +172,7 @@ func (s *StalwartTest) fillAddressBook( //NOSONAR
t *testing.T,
accountId string,
count uint,
session *Session,
ctx Context,
_ User,
principalIds []string,
) (AddressBookBoxes, []AddressBook, SessionState, State, error) {
@@ -192,7 +195,7 @@ func (s *StalwartTest) fillAddressBook( //NOSONAR
IsSubscribed: &subscribed,
}
if i%2 == 0 {
abook.SortOrder = posUIntPtr(gofakeit.Uint())
abook.SortOrder = uintPtr(gofakeit.Uint())
boxes.sortOrdered = true
}
var sharing *AddressBookRights = nil
@@ -218,7 +221,7 @@ func (s *StalwartTest) fillAddressBook( //NOSONAR
abook.ShareWith = m
}
a, sessionState, state, _, err := s.client.CreateAddressBook(accountId, session, s.ctx, s.logger, "", abook)
a, sessionState, state, _, err := s.client.CreateAddressBook(accountId, abook, ctx)
if err != nil {
return boxes, created, ss, as, err
}

View File

@@ -40,10 +40,11 @@ func TestEmails(t *testing.T) {
user := pickUser()
session := s.Session(user.name)
ctx := s.Context(session)
accountId := session.PrimaryAccounts.Mail
inboxId, inboxFolder := s.findInbox(t, accountId, session)
inboxId, inboxFolder := s.findInbox(t, accountId, ctx)
var threads int = 0
var mails []filledMail = nil
@@ -55,7 +56,7 @@ func TestEmails(t *testing.T) {
{
{
resp, sessionState, _, _, err := s.client.GetAllIdentities(accountId, session, s.ctx, s.logger, "")
resp, sessionState, _, _, err := s.client.GetAllIdentities(accountId, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Len(resp, 1)
@@ -64,7 +65,7 @@ func TestEmails(t *testing.T) {
}
{
respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, session, s.ctx, s.logger, "")
respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Len(respByAccountId, 1)
@@ -80,7 +81,7 @@ func TestEmails(t *testing.T) {
}
{
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, session, s.ctx, s.logger, "", inboxId, 0, 0, true, false, 0, true)
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, 0, true, false, 0, true, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
@@ -94,7 +95,7 @@ func TestEmails(t *testing.T) {
}
{
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, session, s.ctx, s.logger, "", inboxId, 0, 0, false, false, 0, true)
resp, sessionState, _, _, err := s.client.GetAllEmailsInMailbox(accountId, inboxId, 0, 0, false, false, 0, true, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
@@ -122,6 +123,7 @@ func TestSendingEmails(t *testing.T) {
from := pickUser()
session := s.Session(from.name)
ctx := s.Context(session)
accountId := session.PrimaryAccounts.Mail
var to User
@@ -142,7 +144,7 @@ func TestSendingEmails(t *testing.T) {
var mailboxPerRole map[string]Mailbox
{
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{accountId}, session, s.ctx, s.logger, "")
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
require.NoError(err)
mailboxPerRole = structs.Index(mailboxes[accountId], func(m Mailbox) string { return m.Role })
require.Contains(mailboxPerRole, JmapMailboxRoleInbox)
@@ -152,7 +154,7 @@ func TestSendingEmails(t *testing.T) {
}
{
roles := []string{JmapMailboxRoleDrafts, JmapMailboxRoleSent, JmapMailboxRoleInbox}
m, _, _, _, err := s.client.SearchMailboxIdsPerRole([]string{accountId}, session, s.ctx, s.logger, "", roles)
m, _, _, _, err := s.client.SearchMailboxIdsPerRole([]string{accountId}, roles, ctx)
require.NoError(err)
require.Contains(m, accountId)
a := m[accountId]
@@ -166,7 +168,7 @@ func TestSendingEmails(t *testing.T) {
accountId string
session *Session
}{{toAccountId, toSession}, {ccAccountId, ccSession}} {
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{u.accountId}, u.session, s.ctx, s.logger, "")
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{u.accountId}, ctx)
require.NoError(err)
for _, mailbox := range mailboxes[u.accountId] {
require.Equal(0, mailbox.TotalEmails)
@@ -180,7 +182,7 @@ func TestSendingEmails(t *testing.T) {
{
var identity Identity
{
identities, _, _, _, err := s.client.GetAllIdentities(accountId, session, s.ctx, s.logger, "")
identities, _, _, _, err := s.client.GetAllIdentities(accountId, ctx)
require.NoError(err)
require.NotEmpty(identities)
identity = identities[0]
@@ -191,12 +193,12 @@ func TestSendingEmails(t *testing.T) {
Subject: subject,
MailboxIds: toBoolMapS(mailboxPerRole[JmapMailboxRoleDrafts].Id),
}
created, _, _, _, err := s.client.CreateEmail(accountId, create, "", session, s.ctx, s.logger, "")
created, _, _, _, err := s.client.CreateEmail(accountId, create, "", ctx)
require.NoError(err)
require.NotEmpty(created.Id)
{
emails, notFound, _, _, _, err := s.client.GetEmails(accountId, session, s.ctx, s.logger, "", []string{created.Id}, true, 0, false, false)
emails, notFound, _, _, _, err := s.client.GetEmails(accountId, []string{created.Id}, true, 0, false, false, ctx)
require.NoError(err)
require.Len(emails, 1)
require.Empty(notFound)
@@ -215,14 +217,14 @@ func TestSendingEmails(t *testing.T) {
Subject: subject,
MailboxIds: toBoolMapS(mailboxPerRole[JmapMailboxRoleDrafts].Id),
}
updated, _, _, _, err := s.client.CreateEmail(accountId, update, created.Id, session, s.ctx, s.logger, "")
updated, _, _, _, err := s.client.CreateEmail(accountId, update, created.Id, ctx)
require.NoError(err)
require.NotEmpty(updated.Id)
require.NotEqual(created.Id, updated.Id)
var updatedMailboxId string
{
emails, notFound, _, _, _, err := s.client.GetEmails(accountId, session, s.ctx, s.logger, "", []string{created.Id, updated.Id}, true, 0, false, false)
emails, notFound, _, _, _, err := s.client.GetEmails(accountId, []string{created.Id, updated.Id}, true, 0, false, false, ctx)
require.NoError(err)
require.Len(emails, 1)
require.Len(notFound, 1)
@@ -241,7 +243,7 @@ func TestSendingEmails(t *testing.T) {
ToMailboxId: mailboxPerRole[JmapMailboxRoleSent].Id,
}
sub, _, _, _, err := s.client.SubmitEmail(accountId, identity.Id, updated.Id, &move, session, s.ctx, s.logger, "")
sub, _, _, _, err := s.client.SubmitEmail(accountId, identity.Id, updated.Id, &move, ctx)
require.NoError(err)
require.NotEmpty(sub.Id)
require.NotEmpty(sub.ThreadId)
@@ -272,7 +274,7 @@ func TestSendingEmails(t *testing.T) {
}
time.Sleep(1 * time.Second)
subs, notFound, _, _, _, err := s.client.GetEmailSubmissionStatus(accountId, []string{sub.Id}, session, s.ctx, s.logger, "")
subs, notFound, _, _, _, err := s.client.GetEmailSubmissionStatus(accountId, []string{sub.Id}, ctx)
require.NoError(err)
require.Empty(notFound)
require.Contains(subs, sub.Id)
@@ -286,7 +288,7 @@ func TestSendingEmails(t *testing.T) {
accountId string
session *Session
}{{to, toAccountId, toSession}, {cc, ccAccountId, ccSession}} {
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{r.accountId}, r.session, s.ctx, s.logger, "")
mailboxes, _, _, _, err := s.client.GetAllMailboxes([]string{r.accountId}, ctx)
require.NoError(err)
inboxId := ""
for _, mailbox := range mailboxes[r.accountId] {
@@ -297,7 +299,7 @@ func TestSendingEmails(t *testing.T) {
}
require.NotEmpty(inboxId, "failed to find the Mailbox with the 'inbox' role for %v", r.user.name)
emails, _, _, _, err := s.client.QueryEmails([]string{r.accountId}, EmailFilterCondition{InMailbox: inboxId}, r.session, s.ctx, s.logger, "", 0, 0, true, 0)
emails, _, _, _, err := s.client.QueryEmails([]string{r.accountId}, EmailFilterCondition{InMailbox: inboxId}, 0, 0, true, 0, ctx)
require.NoError(err)
require.Contains(emails, r.accountId)
require.Len(emails[r.accountId].Emails, 1)
@@ -354,11 +356,11 @@ func matchEmail(t *testing.T, actual Email, expected filledMail, hasBodies bool)
}
}
func (s *StalwartTest) findInbox(t *testing.T, accountId string, session *Session) (string, string) {
func (s *StalwartTest) findInbox(t *testing.T, accountId string, ctx Context) (string, string) {
require := require.New(t)
respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, session, s.ctx, s.logger, "")
respByAccountId, sessionState, _, _, err := s.client.GetAllMailboxes([]string{accountId}, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Equal(ctx.Session.State, sessionState)
require.Len(respByAccountId, 1)
require.Contains(respByAccountId, accountId)
resp := respByAccountId[accountId]

View File

@@ -1,7 +1,6 @@
package jmap
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
@@ -17,7 +16,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/opencloud-eu/opencloud/pkg/jscalendar"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
@@ -36,17 +34,17 @@ func TestCalendars(t *testing.T) { //NOSONAR
func(session *Session) string { return session.PrimaryAccounts.Calendars },
func(resp CalendarGetResponse) []Calendar { return resp.List },
func(obj Calendar) string { return obj.Id },
func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (CalendarGetResponse, SessionState, State, Language, Error) {
return s.client.GetCalendars(accountId, session, ctx, logger, acceptLanguage, ids)
func(s *StalwartTest, accountId string, ids []string, ctx Context) (CalendarGetResponse, SessionState, State, Language, Error) {
return s.client.GetCalendars(accountId, ids, ctx)
},
func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, id string, change CalendarChange) (Calendar, SessionState, State, Language, Error) { //NOSONAR
return s.client.UpdateCalendar(accountId, session, ctx, logger, acceptLanguage, id, change)
func(s *StalwartTest, accountId string, id string, change CalendarChange, ctx Context) (Calendar, SessionState, State, Language, Error) { //NOSONAR
return s.client.UpdateCalendar(accountId, id, change, ctx)
},
func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR
return s.client.DeleteCalendar(accountId, ids, session, ctx, logger, acceptLanguage)
func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) { //NOSONAR
return s.client.DeleteCalendar(accountId, ids, ctx)
},
func(s *StalwartTest, t *testing.T, accountId string, count uint, session *Session, user User, principalIds []string) (CalendarBoxes, []Calendar, SessionState, State, error) {
return s.fillCalendar(t, accountId, count, session, user, principalIds)
func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, user User, principalIds []string) (CalendarBoxes, []Calendar, SessionState, State, error) {
return s.fillCalendar(t, accountId, count, ctx, user, principalIds)
},
func(orig Calendar) CalendarChange {
return CalendarChange{
@@ -77,6 +75,7 @@ func TestEvents(t *testing.T) {
user := pickUser()
session := s.Session(user.name)
ctx := s.Context(session)
accountId, calendarId, expectedEventsById, boxes, err := s.fillEvents(t, count, session, user)
require.NoError(err)
@@ -90,18 +89,49 @@ func TestEvents(t *testing.T) {
{Property: CalendarEventPropertyStart, IsAscending: true},
}
contactsByAccount, _, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, session, t.Context(), s.logger, "", filter, sortBy, 0, 0)
require.NoError(err)
{
resultsByAccount, _, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, 0, 0, true, ctx)
require.NoError(err)
require.Len(contactsByAccount, 1)
require.Contains(contactsByAccount, accountId)
contacts := contactsByAccount[accountId]
require.Len(contacts, int(count))
require.Len(resultsByAccount, 1)
require.Contains(resultsByAccount, accountId)
results := resultsByAccount[accountId]
require.Len(results.Results, int(count))
require.Equal(uint(0), results.Limit)
require.Equal(uint(0), results.Position)
require.Equal(true, results.CanCalculateChanges)
require.NotNil(results.Total)
require.Equal(count, *results.Total)
for _, actual := range contacts {
expected, ok := expectedEventsById[actual.Id]
require.True(ok, "failed to find created contact by its id")
matchEvent(t, actual, expected)
for _, actual := range results.Results {
expected, ok := expectedEventsById[actual.Id]
require.True(ok, "failed to find created contact by its id")
matchEvent(t, actual, expected)
}
}
{
limit := uint(10)
slices := count / limit
remainder := count
require.Greater(slices, uint(1), "we need to have more than 10 objects in order to test the pagination of search results")
for i := range slices {
position := int(i * limit)
page := min(remainder, limit)
m, _, _, _, err := s.client.QueryCalendarEvents([]string{accountId}, filter, sortBy, position, limit, true, ctx)
fmt.Printf("=== i=%d | limit=%d | remainder=%d | position=%d | limit=%d | results=%d\n", i, limit, remainder, position, limit, len(m[accountId].Results))
require.NoError(err)
require.Len(m, 1)
require.Contains(m, accountId)
results := m[accountId]
require.Equal(len(results.Results), int(page))
require.Equal(limit, results.Limit)
require.Equal(position, results.Position)
require.Equal(true, results.CanCalculateChanges)
require.NotNil(results.Total)
require.Equal(count, *results.Total)
remainder -= uint(len(results.Results))
}
}
exceptions := []string{}
@@ -127,7 +157,7 @@ func (s *StalwartTest) fillCalendar( //NOSONAR
t *testing.T,
accountId string,
count uint,
session *Session,
ctx Context,
_ User,
principalIds []string,
) (CalendarBoxes, []Calendar, SessionState, State, error) {
@@ -180,7 +210,7 @@ func (s *StalwartTest) fillCalendar( //NOSONAR
},
}
if i%2 == 0 {
cal.SortOrder = posUIntPtr(gofakeit.Uint())
cal.SortOrder = uintPtr(gofakeit.Uint())
boxes.sortOrdered = true
}
var sharing *CalendarRights = nil
@@ -233,7 +263,7 @@ func (s *StalwartTest) fillCalendar( //NOSONAR
cal.ShareWith = m
}
a, sessionState, state, _, err := s.client.CreateCalendar(accountId, session, s.ctx, s.logger, "", cal)
a, sessionState, state, _, err := s.client.CreateCalendar(accountId, cal, ctx)
if err != nil {
return boxes, created, ss, as, err
}

View File

@@ -175,6 +175,15 @@ func (s *StalwartTest) Close() error {
return nil
}
func (s *StalwartTest) Context(session *Session) Context {
return Context{
Session: session,
Context: s.ctx,
Logger: s.logger,
AcceptLanguage: "",
}
}
func (s *StalwartTest) Session(username string) *Session {
session, jerr := s.client.FetchSession(s.ctx, s.sessionUrl, username, s.logger)
require.NoError(s.t, jerr)
@@ -1218,10 +1227,10 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
acc func(session *Session) string,
obj func(RESP) []OBJ,
id func(OBJ) string,
get func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *clog.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error),
update func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *clog.Logger, acceptLanguage string, id string, change CHANGE) (OBJ, SessionState, State, Language, Error),
destroy func(s *StalwartTest, accountId string, session *Session, ctx context.Context, logger *clog.Logger, acceptLanguage string, ids []string) (map[string]SetError, SessionState, State, Language, Error),
fill func(s *StalwartTest, t *testing.T, accountId string, count uint, session *Session, _ User, principalIds []string) (BOXES, []OBJ, SessionState, State, error),
get func(s *StalwartTest, accountId string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error),
update func(s *StalwartTest, accountId string, id string, change CHANGE, ctx Context) (OBJ, SessionState, State, Language, Error),
destroy func(s *StalwartTest, accountId string, ids []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error),
fill func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, _ User, principalIds []string) (BOXES, []OBJ, SessionState, State, error),
change func(OBJ) CHANGE,
checkChanged func(t *testing.T, orig OBJ, change CHANGE, changed OBJ),
) {
@@ -1233,16 +1242,17 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
user := pickUser()
session := s.Session(user.name)
ctx := s.Context(session)
accountId := acc(session)
// we first need to retrieve the list of all the Principals in order to be able to use and test sharing
principalIds := []string{}
{
principals, _, _, _, err := s.client.GetPrincipals(accountId, session, s.ctx, s.logger, "", []string{})
principals, _, _, _, err := s.client.GetPrincipals(accountId, []string{}, ctx)
require.NoError(err)
require.NotEmpty(principals.Principals)
principalIds = structs.Map(principals.Principals, func(p Principal) string { return p.Id })
require.NotEmpty(principals.List)
principalIds = structs.Map(principals.List, func(p Principal) string { return p.Id })
}
ss := SessionState("")
@@ -1252,7 +1262,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
// from the tests below
defaultContainerId := ""
{
resp, sessionState, state, _, err := get(s, accountId, session, s.ctx, s.logger, "", []string{})
resp, sessionState, state, _, err := get(s, accountId, []string{}, ctx)
require.NoError(err)
require.Empty(resp.GetNotFound())
objs := obj(resp)
@@ -1265,7 +1275,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
// we are going to create a random amount of objects
num := uint(5 + rand.Intn(30))
{
boxes, all, sessionState, state, err := fill(s, t, accountId, num, session, user, principalIds)
boxes, all, sessionState, state, err := fill(s, t, accountId, num, ctx, user, principalIds)
require.NoError(err)
require.Len(all, int(num))
ss = sessionState
@@ -1273,7 +1283,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
{
// lets retrieve all the existing objects by passing an empty ID slice
resp, sessionState, state, _, err := get(s, accountId, session, s.ctx, s.logger, "", []string{})
resp, sessionState, state, _, err := get(s, accountId, []string{}, ctx)
require.NoError(err)
require.Empty(resp.GetNotFound())
objs := obj(resp)
@@ -1300,7 +1310,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
// lets retrieve every object we created by its ID
for _, a := range all {
i := id(a)
resp, sessionState, state, _, err := get(s, accountId, session, s.ctx, s.logger, "", []string{i})
resp, sessionState, state, _, err := get(s, accountId, []string{i}, ctx)
require.NoError(err)
require.Empty(resp.GetNotFound())
objs := obj(resp)
@@ -1314,7 +1324,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
for _, a := range all {
i := id(a)
ch := change(a)
changed, sessionState, state, _, err := update(s, accountId, session, s.ctx, s.logger, "", i, ch)
changed, sessionState, state, _, err := update(s, accountId, i, ch, ctx)
require.NoError(err)
require.NotEqual(a, changed)
require.Equal(sessionState, ss)
@@ -1325,7 +1335,7 @@ func containerTest[OBJ Idable, RESP GetResponse[OBJ], BOXES any, CHANGE Change](
// now lets delete each object that we created, all at once
ids := structs.Map(all, id)
{
errMap, sessionState, state, _, err := destroy(s, accountId, session, s.ctx, s.logger, "", ids)
errMap, sessionState, state, _, err := destroy(s, accountId, ids, ctx)
require.NoError(err)
require.Empty(errMap)
require.Equal(sessionState, ss)

View File

@@ -1,7 +1,6 @@
package jmap
import (
"context"
"sync"
"sync/atomic"
"testing"
@@ -61,7 +60,7 @@ func TestWs(t *testing.T) {
require := require.New(t)
ctx := context.Background()
cotx := t.Context()
s, err := newStalwartTest(t)
require.NoError(err)
@@ -69,11 +68,12 @@ func TestWs(t *testing.T) {
user := pickUser()
session := s.Session(user.name)
ctx := s.Context(session)
mailAccountId := session.PrimaryAccounts.Mail
inboxFolder := ""
{
_, inboxFolder = s.findInbox(t, mailAccountId, session)
_, inboxFolder = s.findInbox(t, mailAccountId, ctx)
}
l := &testWsPushListener{t: t, username: user.name, logger: s.logger, mailAccountId: mailAccountId}
@@ -90,7 +90,7 @@ func TestWs(t *testing.T) {
var initialState State
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", State(""), true, 0, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, EmptyState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEmpty(state)
@@ -104,7 +104,7 @@ func TestWs(t *testing.T) {
require.NotEmpty(initialState)
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", initialState, true, 0, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, initialState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.Equal(initialState, state)
@@ -114,7 +114,7 @@ func TestWs(t *testing.T) {
require.Empty(changes.Updated)
}
wsc, err := s.client.EnablePushNotifications(ctx, initialState, func() (*Session, error) { return session, nil })
wsc, err := s.client.EnablePushNotifications(cotx, initialState, func() (*Session, error) { return session, nil })
require.NoError(err)
defer wsc.Close()
@@ -147,7 +147,7 @@ func TestWs(t *testing.T) {
}
var lastState State
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", initialState, true, 0, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, initialState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(initialState, state)
@@ -181,7 +181,7 @@ func TestWs(t *testing.T) {
l.m.Unlock()
}
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", lastState, true, 0, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, lastState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(lastState, state)
@@ -215,7 +215,7 @@ func TestWs(t *testing.T) {
l.m.Unlock()
}
{
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, session, s.ctx, s.logger, "", lastState, true, 0, 0)
changes, sessionState, state, _, err := s.client.GetEmailChanges(mailAccountId, lastState, true, 0, 0, ctx)
require.NoError(err)
require.Equal(session.State, sessionState)
require.NotEqual(lastState, state)

View File

@@ -1354,13 +1354,32 @@ type ChangesResponse[T Foo] interface {
GetMarker() T
}
type QueryCommand interface {
type QueryCommand[T Foo] interface {
JmapCommand
GetResponse() QueryResponse
GetResponse() QueryResponse[T]
}
type QueryResponse interface {
type QueryResponse[T Foo] interface {
GetQueryState() State
GetMarker() T
}
type UploadCommand[T Foo] interface {
JmapCommand
GetResponse() UploadResponse[T]
}
type UploadResponse[T Foo] interface {
GetMarker() T
}
type ParseCommand[T Foo] interface {
JmapCommand
GetResponse() ParseResponse[T]
}
type ParseResponse[T Foo] interface {
GetMarker() T
}
type ChangesTemplate[T Foo] struct {
@@ -1372,6 +1391,22 @@ type ChangesTemplate[T Foo] struct {
Destroyed []string `json:"destroyed,omitempty"`
}
type SearchResultsTemplate[T Foo] struct {
Results []T `json:"results"`
CanCalculateChanges bool `json:"canCalculateChanges"`
Position uint `json:"position"`
Limit uint `json:"limit,omitzero"`
Total *uint `json:"total,omitzero"`
}
type SearchResults[T Foo] interface {
GetResults() []T
GetCanCalculateChanges() bool
GetPosition() uint
GetTotal() *uint
GetLimit() uint
}
type FilterOperatorTerm string
const (
@@ -1656,8 +1691,8 @@ type MailboxSetResponse struct {
AccountId string `json:"accountId"`
OldState State `json:"oldState,omitempty"`
NewState State `json:"newState,omitempty"`
Created map[string]Mailbox `json:"created,omitempty"`
Updated map[string]Mailbox `json:"updated,omitempty"`
Created map[string]*Mailbox `json:"created,omitempty"`
Updated map[string]*Mailbox `json:"updated,omitempty"`
Destroyed []string `json:"destroyed,omitempty"`
NotCreated map[string]SetError `json:"notCreated,omitempty"`
NotUpdated map[string]SetError `json:"notUpdated,omitempty"`
@@ -1717,11 +1752,11 @@ type MailboxQueryCommand struct {
FilterAsTree bool `json:"filterAsTree,omitempty"`
}
var _ QueryCommand = &MailboxQueryCommand{}
var _ QueryCommand[Mailbox] = &MailboxQueryCommand{}
func (c MailboxQueryCommand) GetCommand() Command { return CommandMailboxQuery }
func (c MailboxQueryCommand) GetObjectType() ObjectType { return MailboxType }
func (c MailboxQueryCommand) GetResponse() QueryResponse { return MailboxQueryResponse{} }
func (c MailboxQueryCommand) GetCommand() Command { return CommandMailboxQuery }
func (c MailboxQueryCommand) GetObjectType() ObjectType { return MailboxType }
func (c MailboxQueryCommand) GetResponse() QueryResponse[Mailbox] { return MailboxQueryResponse{} }
type EmailFilterElement interface {
_isAnEmailFilterElement() // marker method
@@ -2013,11 +2048,11 @@ type EmailQueryCommand struct {
CalculateTotal bool `json:"calculateTotal,omitempty"`
}
var _ QueryCommand = &EmailQueryCommand{}
var _ QueryCommand[Email] = &EmailQueryCommand{}
func (c EmailQueryCommand) GetCommand() Command { return CommandEmailQuery }
func (c EmailQueryCommand) GetObjectType() ObjectType { return MailboxType }
func (c EmailQueryCommand) GetResponse() QueryResponse { return EmailQueryResponse{} }
func (c EmailQueryCommand) GetCommand() Command { return CommandEmailQuery }
func (c EmailQueryCommand) GetObjectType() ObjectType { return MailboxType }
func (c EmailQueryCommand) GetResponse() QueryResponse[Email] { return EmailQueryResponse{} }
type EmailGetCommand struct {
// The ids of the Email objects to return.
@@ -3063,9 +3098,10 @@ type EmailQueryResponse struct {
Limit uint `json:"limit,omitempty,omitzero"`
}
var _ QueryResponse = &EmailQueryResponse{}
var _ QueryResponse[Email] = &EmailQueryResponse{}
func (r EmailQueryResponse) GetQueryState() State { return r.QueryState }
func (r EmailQueryResponse) GetMarker() Email { return Email{} }
type EmailGetResponse struct {
// The id of the account used for the call.
@@ -3284,9 +3320,10 @@ type MailboxQueryResponse struct {
Limit int `json:"limit,omitzero"`
}
var _ QueryResponse = &MailboxQueryResponse{}
var _ QueryResponse[Mailbox] = &MailboxQueryResponse{}
func (r MailboxQueryResponse) GetQueryState() State { return r.QueryState }
func (r MailboxQueryResponse) GetMarker() Mailbox { return Mailbox{} }
type EmailCreate struct {
// The set of Mailbox ids this Email belongs to.
@@ -3727,11 +3764,11 @@ func (r IdentityChangesResponse) GetDestroyed() []string { return r.Destroyed }
func (r IdentityChangesResponse) GetMarker() Identity { return Identity{} }
type IdentitySetCommand struct {
AccountId string `json:"accountId"`
IfInState string `json:"ifInState,omitempty"`
Create map[string]Identity `json:"create,omitempty"`
Update map[string]PatchObject `json:"update,omitempty"`
Destroy []string `json:"destroy,omitempty"`
AccountId string `json:"accountId"`
IfInState string `json:"ifInState,omitempty"`
Create map[string]IdentityChange `json:"create,omitempty"`
Update map[string]PatchObject `json:"update,omitempty"`
Destroy []string `json:"destroy,omitempty"`
}
var _ SetCommand[Identity] = &IdentitySetCommand{}
@@ -3741,15 +3778,15 @@ func (c IdentitySetCommand) GetObjectType() ObjectType { return Identit
func (c IdentitySetCommand) GetResponse() SetResponse[Identity] { return IdentitySetResponse{} }
type IdentitySetResponse struct {
AccountId string `json:"accountId"`
OldState State `json:"oldState,omitempty"`
NewState State `json:"newState,omitempty"`
Created map[string]Identity `json:"created,omitempty"`
Updated map[string]Identity `json:"updated,omitempty"`
Destroyed []string `json:"destroyed,omitempty"`
NotCreated map[string]SetError `json:"notCreated,omitempty"`
NotUpdated map[string]SetError `json:"notUpdated,omitempty"`
NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"`
AccountId string `json:"accountId"`
OldState State `json:"oldState,omitempty"`
NewState State `json:"newState,omitempty"`
Created map[string]*Identity `json:"created,omitempty"`
Updated map[string]*Identity `json:"updated,omitempty"`
Destroyed []string `json:"destroyed,omitempty"`
NotCreated map[string]SetError `json:"notCreated,omitempty"`
NotUpdated map[string]SetError `json:"notUpdated,omitempty"`
NotDestroyed map[string]SetError `json:"notDestroyed,omitempty"`
}
var _ SetResponse[Identity] = &IdentitySetResponse{}
@@ -3810,9 +3847,41 @@ var _ Idable = &Identity{}
func (f Identity) GetObjectType() ObjectType { return IdentityType }
func (f Identity) GetId() string { return f.Id }
var _ Change = Identity{}
type IdentityChange struct {
// The “From” name the client SHOULD use when creating a new Email from this Identity.
Name string `json:"name,omitempty" doc:"req"`
func (i Identity) AsPatch() PatchObject {
// The “From” email address the client MUST use when creating a new Email from this Identity.
//
// If the mailbox part of the address (the section before the “@”) is the single character
// * (e.g., *@example.com) then the client may use any valid address ending in that domain
// (e.g., foo@example.com).
Email string `json:"email,omitempty"`
// The Reply-To value the client SHOULD set when creating a new Email from this Identity.
ReplyTo string `json:"replyTo,omitempty"`
// The Bcc value the client SHOULD set when creating a new Email from this Identity.
Bcc *[]EmailAddress `json:"bcc,omitempty"`
// A signature the client SHOULD insert into new plaintext messages that will be sent from
// this Identity.
//
// Clients MAY ignore this and/or combine this with a client-specific signature preference.
TextSignature *string `json:"textSignature,omitempty"`
// A signature the client SHOULD insert into new HTML messages that will be sent from this
// Identity.
//
// This text MUST be an HTML snippet to be inserted into the <body></body> section of the HTML.
//
// Clients MAY ignore this and/or combine this with a client-specific signature preference.
HtmlSignature *string `json:"htmlSignature,omitempty"`
}
var _ Change = IdentityChange{}
func (i IdentityChange) AsPatch() PatchObject {
p := PatchObject{}
if i.Name != "" {
p["name"] = i.Name
@@ -3985,10 +4054,11 @@ type BlobUploadCommand struct {
Create map[string]UploadObject `json:"create"`
}
var _ JmapCommand = &BlobUploadCommand{}
var _ UploadCommand[Blob] = &BlobUploadCommand{}
func (c BlobUploadCommand) GetCommand() Command { return CommandBlobUpload }
func (c BlobUploadCommand) GetObjectType() ObjectType { return BlobType }
func (c BlobUploadCommand) GetCommand() Command { return CommandBlobUpload }
func (c BlobUploadCommand) GetObjectType() ObjectType { return BlobType }
func (c BlobUploadCommand) GetResponse() UploadResponse[Blob] { return BlobUploadResponse{} }
type BlobUploadCreateResult struct {
Id string `json:"id"`
@@ -4001,6 +4071,10 @@ type BlobUploadResponse struct {
Created map[string]BlobUploadCreateResult `json:"created"`
}
var _ UploadResponse[Blob] = &BlobUploadResponse{}
func (r BlobUploadResponse) GetMarker() Blob { return Blob{} }
const (
BlobPropertyDataAsText = "data:asText"
BlobPropertyDataAsBase64 = "data:asBase64"
@@ -5837,6 +5911,28 @@ var _ Idable = &Principal{}
func (f Principal) GetObjectType() ObjectType { return PrincipalType }
func (f Principal) GetId() string { return f.Id }
const (
PrincipalPropertyId = "id"
PrincipalPropertyType = "type"
PrincipalPropertyName = "name"
PrincipalPropertyDescription = "description"
PrincipalPropertyEmail = "email"
PrincipalPropertyTimeZone = "timeZone"
PrincipalPropertyCapabilities = "capabilites"
PrincipalPropertyAccounts = "accounts"
)
var PrincipalProperties = []string{
PrincipalPropertyId,
PrincipalPropertyType,
PrincipalPropertyName,
PrincipalPropertyDescription,
PrincipalPropertyEmail,
PrincipalPropertyTimeZone,
PrincipalPropertyCapabilities,
PrincipalPropertyAccounts,
}
type ShareChangePerson struct {
// The name of the person who made the change.
Name string `json:"name"`
@@ -6589,7 +6685,7 @@ type ContactCardQueryCommand struct {
//
// If the index is greater than or equal to the total number of objects in the results
// list, then the ids array in the response will be empty, but this is not an error.
Position uint `json:"position,omitzero" default:"0" doc:"opt"`
Position int `json:"position,omitzero" default:"0" doc:"opt"`
// An Email id.
//
@@ -6615,7 +6711,7 @@ type ContactCardQueryCommand struct {
// to the maximum; the new limit is returned with the response so the client is aware.
//
// If a negative value is given, the call MUST be rejected with an invalidArguments error.
Limit uint `json:"limit,omitzero" doc:"opt"`
Limit *uint `json:"limit,omitzero" doc:"opt"`
// Does the client wish to know the total number of results in the query?
//
@@ -6624,11 +6720,13 @@ type ContactCardQueryCommand struct {
CalculateTotal bool `json:"calculateTotal,omitzero"`
}
var _ QueryCommand = &ContactCardQueryCommand{}
var _ QueryCommand[ContactCard] = &ContactCardQueryCommand{}
func (c ContactCardQueryCommand) GetCommand() Command { return CommandContactCardQuery }
func (c ContactCardQueryCommand) GetObjectType() ObjectType { return ContactCardType }
func (c ContactCardQueryCommand) GetResponse() QueryResponse { return ContactCardQueryResponse{} }
func (c ContactCardQueryCommand) GetCommand() Command { return CommandContactCardQuery }
func (c ContactCardQueryCommand) GetObjectType() ObjectType { return ContactCardType }
func (c ContactCardQueryCommand) GetResponse() QueryResponse[ContactCard] {
return ContactCardQueryResponse{}
}
type ContactCardQueryResponse struct {
// The id of the account used for the call.
@@ -6680,9 +6778,10 @@ type ContactCardQueryResponse struct {
Limit uint `json:"limit,omitempty,omitzero"`
}
var _ QueryResponse = &ContactCardQueryResponse{}
var _ QueryResponse[ContactCard] = &ContactCardQueryResponse{}
func (r ContactCardQueryResponse) GetQueryState() State { return r.QueryState }
func (r ContactCardQueryResponse) GetQueryState() State { return r.QueryState }
func (r ContactCardQueryResponse) GetMarker() ContactCard { return ContactCard{} }
type ContactCardGetCommand struct {
// The ids of the ContactCard objects to return.
@@ -6960,10 +7059,13 @@ type CalendarEventParseCommand struct {
Properties []string `json:"properties,omitempty"`
}
var _ JmapCommand = &CalendarEventParseCommand{}
var _ ParseCommand[CalendarEvent] = &CalendarEventParseCommand{}
func (c CalendarEventParseCommand) GetCommand() Command { return CommandCalendarEventParse }
func (c CalendarEventParseCommand) GetObjectType() ObjectType { return CalendarEventType }
func (c CalendarEventParseCommand) GetResponse() ParseResponse[CalendarEvent] {
return CalendarEventParseResponse{}
}
type CalendarEventParseResponse struct {
// The id of the account used for the call.
@@ -6981,6 +7083,10 @@ type CalendarEventParseResponse struct {
NotParsable []string `json:"notParsable,omitempty"`
}
var _ ParseResponse[CalendarEvent] = &CalendarEventParseResponse{}
func (r CalendarEventParseResponse) GetMarker() CalendarEvent { return CalendarEvent{} }
type CalendarGetCommand struct {
AccountId string `json:"accountId"`
Ids []string `json:"ids,omitempty"`
@@ -7309,7 +7415,7 @@ type CalendarEventQueryCommand struct {
//
// If the index is greater than or equal to the total number of objects in the results
// list, then the ids array in the response will be empty, but this is not an error.
Position uint `json:"position,omitempty" doc:"opt"`
Position int `json:"position,omitempty" doc:"opt"`
// An Email id.
//
@@ -7335,7 +7441,7 @@ type CalendarEventQueryCommand struct {
// to the maximum; the new limit is returned with the response so the client is aware.
//
// If a negative value is given, the call MUST be rejected with an invalidArguments error.
Limit uint `json:"limit,omitempty" doc:"opt" default:"0"`
Limit *uint `json:"limit,omitempty" doc:"opt" default:"0"`
// Does the client wish to know the total number of results in the query?
//
@@ -7344,11 +7450,13 @@ type CalendarEventQueryCommand struct {
CalculateTotal bool `json:"calculateTotal,omitempty" doc:"opt" default:"false"`
}
var _ QueryCommand = &CalendarEventQueryCommand{}
var _ QueryCommand[CalendarEvent] = &CalendarEventQueryCommand{}
func (c CalendarEventQueryCommand) GetCommand() Command { return CommandCalendarEventQuery }
func (c CalendarEventQueryCommand) GetObjectType() ObjectType { return CalendarEventType }
func (c CalendarEventQueryCommand) GetResponse() QueryResponse { return CalendarEventQueryResponse{} }
func (c CalendarEventQueryCommand) GetCommand() Command { return CommandCalendarEventQuery }
func (c CalendarEventQueryCommand) GetObjectType() ObjectType { return CalendarEventType }
func (c CalendarEventQueryCommand) GetResponse() QueryResponse[CalendarEvent] {
return CalendarEventQueryResponse{}
}
type CalendarEventQueryResponse struct {
// The id of the account used for the call.
@@ -7400,9 +7508,10 @@ type CalendarEventQueryResponse struct {
Limit uint `json:"limit,omitempty,omitzero"`
}
var _ QueryResponse = &CalendarEventQueryResponse{}
var _ QueryResponse[CalendarEvent] = &CalendarEventQueryResponse{}
func (r CalendarEventQueryResponse) GetQueryState() State { return r.QueryState }
func (r CalendarEventQueryResponse) GetQueryState() State { return r.QueryState }
func (r CalendarEventQueryResponse) GetMarker() CalendarEvent { return CalendarEvent{} }
type CalendarEventGetCommand struct {
// The ids of the CalendarEvent objects to return.
@@ -7771,11 +7880,60 @@ type PrincipalComparator struct {
}
type PrincipalQueryCommand struct {
AccountId string `json:"accountId"`
Filter PrincipalFilterElement `json:"filter,omitempty"`
Sort []PrincipalComparator `json:"sort,omitempty"`
SortAsTree bool `json:"sortAsTree,omitempty"`
FilterAsTree bool `json:"filterAsTree,omitempty"`
AccountId string `json:"accountId"`
Filter PrincipalFilterElement `json:"filter,omitempty"`
Sort []PrincipalComparator `json:"sort,omitempty"`
// The zero-based index of the first id in the full list of results to return.
//
// If a negative value is given, it is an offset from the end of the list.
// Specifically, the negative value MUST be added to the total number of results given
// the filter, and if still negative, its clamped to 0. This is now the zero-based
// index of the first id to return.
//
// If the index is greater than or equal to the total number of objects in the results
// list, then the ids array in the response will be empty, but this is not an error.
Position uint `json:"position,omitzero" default:"0" doc:"opt"`
// An Email id.
//
// If supplied, the position argument is ignored.
// The index of this id in the results will be used in combination with the anchorOffset
// argument to determine the index of the first result to return.
Anchor string `json:"anchor,omitempty" doc:"opt"`
// The index of the first result to return relative to the index of the anchor,
// if an anchor is given.
//
// This MAY be negative.
//
// For example, -1 means the Principal immediately preceding the anchor is the first result in
// the list returned.
AnchorOffset int `json:"anchorOffset,omitzero" default:"0" doc:"opt"`
// The maximum number of results to return.
//
// If null, no limit presumed.
// The server MAY choose to enforce a maximum limit argument.
// In this case, if a greater value is given (or if it is null), the limit is clamped
// to the maximum; the new limit is returned with the response so the client is aware.
//
// If a negative value is given, the call MUST be rejected with an invalidArguments error.
Limit uint `json:"limit,omitzero" doc:"opt"`
// Does the client wish to know the total number of results in the query?
//
// This may be slow and expensive for servers to calculate, particularly with complex filters,
// so clients should take care to only request the total when needed.
CalculateTotal bool `json:"calculateTotal,omitzero"`
}
var _ QueryCommand[Principal] = PrincipalQueryCommand{}
func (c PrincipalQueryCommand) GetCommand() Command { return CommandPrincipalQuery }
func (c PrincipalQueryCommand) GetObjectType() ObjectType { return PrincipalType }
func (c PrincipalQueryCommand) GetResponse() QueryResponse[Principal] {
return PrincipalQueryResponse{}
}
type PrincipalQueryResponse struct {
@@ -7796,38 +7954,39 @@ type PrincipalQueryResponse struct {
//
// Should a client receive back a response with a different queryState string to a previous call, it MUST either
// throw away the currently cached query and fetch it again (note, this does not require fetching the records
// again, just the list of ids) or call Mailbox/queryChanges to get the difference.
// again, just the list of ids) or call Principal/queryChanges to get the difference.
QueryState State `json:"queryState"`
// This is true if the server supports calling Mailbox/queryChanges with these filter/sort parameters.
// This is true if the server supports calling Principal/queryChanges with these filter/sort parameters.
//
// Note, this does not guarantee that the Mailbox/queryChanges call will succeed, as it may only be possible for
// Note, this does not guarantee that the Principal/queryChanges call will succeed, as it may only be possible for
// a limited time afterwards due to server internal implementation details.
CanCalculateChanges bool `json:"canCalculateChanges"`
// The zero-based index of the first result in the ids array within the complete list of query results.
Position int `json:"position"`
Position uint `json:"position"`
// The list of ids for each Mailbox in the query results, starting at the index given by the position argument
// The list of ids for each Principal in the query results, starting at the index given by the position argument
// of this response and continuing until it hits the end of the results or reaches the limit number of ids.
//
// If position is >= total, this MUST be the empty list.
Ids []string `json:"ids"`
// The total number of Mailbox in the results (given the filter) (only if requested).
// The total number of Principal in the results (given the filter) (only if requested).
//
// This argument MUST be omitted if the calculateTotal request argument is not true.
Total int `json:"total,omitzero"`
Total uint `json:"total,omitzero"`
// The limit enforced by the server on the maximum number of results to return (if set by the server).
//
// This is only returned if the server set a limit or used a different limit than that given in the request.
Limit int `json:"limit,omitzero"`
Limit uint `json:"limit,omitzero"`
}
var _ QueryResponse = &PrincipalQueryResponse{}
var _ QueryResponse[Principal] = &PrincipalQueryResponse{}
func (r PrincipalQueryResponse) GetQueryState() State { return r.QueryState }
func (r PrincipalQueryResponse) GetMarker() Principal { return Principal{} }
type ErrorResponse struct {
Type string `json:"type"`

View File

@@ -1,7 +1,6 @@
package jmap
import (
"context"
"fmt"
"github.com/opencloud-eu/opencloud/pkg/log"
@@ -48,25 +47,28 @@ func (f Mailboxes) MapChanges(oldState, newState State, hasMoreChanges bool, cre
func fget[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR
client *Client, name string,
accountId string, ids []string,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (GETRESP, SessionState, State, Language, Error) {
ctx Context) (GETRESP, SessionState, State, Language, Error) {
var getresp GETRESP
return get(client, name, f.Namespaces(),
f.CreateGetCommand,
getresp,
identity1,
accountId, session, ctx, logger, acceptLanguage, ids,
accountId, ids,
ctx,
)
}
func fgetA[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR
client *Client, name string,
accountId string, ids []string,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) ([]T, SessionState, State, Language, Error) {
ctx Context) ([]T, SessionState, State, Language, Error) {
var getresp GETRESP
return getA(client, name, f.Namespaces(),
f.CreateGetCommand,
getresp,
accountId, session, ctx, logger, acceptLanguage, ids,
accountId,
ids,
ctx,
)
}
@@ -75,22 +77,20 @@ func get[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOSON
getCommandFactory func(string, []string) GETREQ,
_ GETRESP,
mapper func(GETRESP) RESP,
accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
accountId string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) {
ctx = ctx.WithLogger(client.logger(name, ctx))
var zero RESP
get := getCommandFactory(accountId, ids)
cmd, err := client.request(session, logger, using,
invocation(get, "0"),
)
cmd, err := client.request(ctx, using, invocation(get, "0"))
if err != nil {
return zero, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var response GETRESP
err = retrieveGet(logger, body, get, "0", &response)
err = retrieveGet(ctx, body, get, "0", &response)
if err != nil {
return zero, "", err
}
@@ -103,21 +103,22 @@ func getA[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T]]( //NOSONAR
client *Client, name string, using []JmapNamespace,
getCommandFactory func(string, []string) GETREQ,
resp GETRESP,
accountId string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) ([]T, SessionState, State, Language, Error) {
return get(client, name, using, getCommandFactory, resp, func(r GETRESP) []T { return r.GetList() }, accountId, session, ctx, logger, acceptLanguage, ids)
accountId string, ids []string, ctx Context) ([]T, SessionState, State, Language, Error) {
return get(client, name, using, getCommandFactory, resp, func(r GETRESP) []T { return r.GetList() }, accountId, ids, ctx)
}
func fgetAN[F Factory[T, GETREQ, GETRESP, CHANGES], T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any, CHANGES any](f Factory[T, GETREQ, GETRESP, CHANGES], //NOSONAR
client *Client, name string,
respMapper func(map[string][]T) RESP,
accountIds []string, ids []string,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
ctx Context) (RESP, SessionState, State, Language, Error) {
var getresp GETRESP
return getAN(client, name, f.Namespaces(),
f.CreateGetCommand,
getresp,
respMapper,
accountIds, session, ctx, logger, acceptLanguage, ids,
accountIds, ids,
ctx,
)
}
@@ -126,11 +127,12 @@ func getAN[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP any]( //NOS
getCommandFactory func(string, []string) GETREQ,
resp GETRESP,
respMapper func(map[string][]T) RESP,
accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) {
accountIds []string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) {
return getN(client, name, using, getCommandFactory, resp,
func(r GETRESP) []T { return r.GetList() },
respMapper,
accountIds, session, ctx, logger, acceptLanguage, ids,
accountIds, ids,
ctx,
)
}
@@ -140,8 +142,9 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an
_ GETRESP,
itemMapper func(GETRESP) ITEM,
respMapper func(map[string]ITEM) RESP,
accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string, ids []string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
accountIds []string, ids []string, ctx Context) (RESP, SessionState, State, Language, Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
var zero RESP
@@ -155,17 +158,17 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], RESP an
invocations[i] = invocation(get, mcid(accountId, "0"))
}
cmd, err := client.request(session, logger, using, invocations...)
cmd, err := client.request(ctx, using, invocations...)
if err != nil {
return zero, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
result := map[string]ITEM{}
responses := map[string]GETRESP{}
for _, accountId := range uniqueAccountIds {
var resp GETRESP
err = retrieveResponseMatchParameters(logger, body, c, mcid(accountId, "0"), &resp)
err = retrieveResponseMatchParameters(ctx, body, c, mcid(accountId, "0"), &resp)
if err != nil {
return zero, "", err
}
@@ -182,13 +185,15 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
getCommandFactory func(string, string) GETREQ,
createdMapper func(SETRESP) map[string]*T,
listMapper func(GETRESP) []T,
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)
accountId string, create C,
ctx Context) (*T, SessionState, State, Language, Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
createMap := map[string]C{"c": create}
get := getCommandFactory(accountId, "#c")
set := setCommandFactory(accountId, createMap)
cmd, err := client.request(session, logger, using,
cmd, err := client.request(ctx, using,
invocation(set, "0"),
invocation(get, "1"),
)
@@ -196,9 +201,9 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
return nil, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (*T, State, Error) {
return command(client, ctx, cmd, func(body *Response) (*T, State, Error) {
var setResponse SETRESP
err = retrieveSet(logger, body, set, "0", &setResponse)
err = retrieveSet(ctx, body, set, "0", &setResponse)
if err != nil {
return nil, "", err
}
@@ -218,7 +223,7 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
}
var getResponse GETRESP
err = retrieveGet(logger, body, get, "1", &getResponse)
err = retrieveGet(ctx, body, get, "1", &getResponse)
if err != nil {
return nil, "", err
}
@@ -237,20 +242,21 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
func destroy[T Foo, REQ SetCommand[T], RESP SetResponse[T]](client *Client, name string, using []JmapNamespace, //NOSONAR
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)
accountId string, destroy []string, ctx Context) (map[string]SetError, SessionState, State, Language, Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
set := setCommandFactory(accountId, destroy)
cmd, err := client.request(session, logger, using,
cmd, err := client.request(ctx, using,
invocation(set, "0"),
)
if err != nil {
return nil, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]SetError, State, Error) {
return command(client, ctx, cmd, func(body *Response) (map[string]SetError, State, Error) {
var setResponse RESP
err = retrieveSet(logger, body, set, "0", &setResponse)
err = retrieveSet(ctx, body, set, "0", &setResponse)
if err != nil {
return nil, "", err
}
@@ -265,12 +271,12 @@ func changesA[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
_ GETRESP,
getCommandFactory func(string, string) GETREQ,
respMapper func(State, State, bool, []T, []T, []string) RESP,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
ctx Context) (RESP, SessionState, State, Language, Error) {
return changes(client, name, using, changesCommandFactory, changesResp, getCommandFactory,
func(r GETRESP) []T { return r.GetList() },
respMapper,
session, ctx, logger, acceptLanguage,
ctx,
)
}
@@ -281,15 +287,15 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
getCommandFactory func(string, string) GETREQ,
getMapper func(GETRESP) []ITEM,
respMapper func(State, State, bool, []ITEM, []ITEM, []string) RESP,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
ctx Context) (RESP, SessionState, State, Language, Error) {
logger := client.logger(name, ctx)
var zero RESP
changes := changesCommandFactory()
getCreated := getCommandFactory("/created", "0") //NOSONAR
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
cmd, err := client.request(session, logger, using,
cmd, err := client.request(ctx.WithLogger(logger), using,
invocation(changes, "0"),
invocation(getCreated, "1"),
invocation(getUpdated, "2"),
@@ -298,22 +304,22 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
return zero, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var changesResponse CHANGESRESP
err = retrieveChanges(logger, body, changes, "0", &changesResponse)
err = retrieveChanges(ctx, body, changes, "0", &changesResponse)
if err != nil {
return zero, "", err
}
var createdResponse GETRESP
err = retrieveGet(logger, body, getCreated, "1", &createdResponse)
err = retrieveGet(ctx, body, getCreated, "1", &createdResponse)
if err != nil {
logger.Error().Err(err).Send()
return zero, "", err
}
var updatedResponse GETRESP
err = retrieveGet(logger, body, getUpdated, "2", &updatedResponse)
err = retrieveGet(ctx, body, getUpdated, "2", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
return zero, "", err
@@ -338,8 +344,8 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
changesItemMapper func(State, State, bool, []ITEM, []ITEM, []string) CHANGESITEM,
respMapper func(map[string]CHANGESITEM) RESP,
stateMapper func(GETRESP) State,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
logger = client.loggerParams(name, session, logger, func(z zerolog.Context) zerolog.Context {
ctx Context) (RESP, SessionState, State, Language, Error) {
logger := client.loggerParams(name, ctx, func(z zerolog.Context) zerolog.Context {
sinceStateLogDict := zerolog.Dict()
for k, v := range sinceStateMap {
sinceStateLogDict.Str(log.SafeString(k), log.SafeString(string(v)))
@@ -377,29 +383,31 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
getCommand = getCreated.GetCommand()
}
cmd, err := client.request(session, logger, using, invocations...)
ctx = ctx.WithLogger(logger)
cmd, err := client.request(ctx, using, invocations...)
if err != nil {
return zero, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
changesItemByAccount := make(map[string]CHANGESITEM, n)
stateByAccountId := make(map[string]State, n)
for _, accountId := range uniqueAccountIds {
var changesResponse CHANGESRESP
err = retrieveResponseMatchParameters(logger, body, changesCommand, mcid(accountId, "0"), &changesResponse)
err = retrieveResponseMatchParameters(ctx, body, changesCommand, mcid(accountId, "0"), &changesResponse)
if err != nil {
return zero, "", err
}
var createdResponse GETRESP
err = retrieveResponseMatchParameters(logger, body, getCommand, mcid(accountId, "1"), &createdResponse)
err = retrieveResponseMatchParameters(ctx, body, getCommand, mcid(accountId, "1"), &createdResponse)
if err != nil {
return zero, "", err
}
var updatedResponse GETRESP
err = retrieveResponseMatchParameters(logger, body, getCommand, mcid(accountId, "2"), &updatedResponse)
err = retrieveResponseMatchParameters(ctx, body, getCommand, mcid(accountId, "2"), &updatedResponse)
if err != nil {
return zero, "", err
}
@@ -420,13 +428,14 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
getCommandFactory func(string, string) GETREQ,
getMapper func(GETRESP) []ITEM,
respMapper func(State, State, bool, []ITEM) RESP,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
ctx Context) (RESP, SessionState, State, Language, Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
var zero RESP
changes := changesCommandFactory()
getUpdated := getCommandFactory("/updated", "0") //NOSONAR
cmd, err := client.request(session, logger, using,
cmd, err := client.request(ctx, using,
invocation(changes, "0"),
invocation(getUpdated, "1"),
)
@@ -434,15 +443,15 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
return zero, "", "", "", err
}
return command(client.api, logger, ctx, session, client.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (RESP, State, Error) {
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var changesResponse CHANGESRESP
err = retrieveChanges(logger, body, changes, "0", &changesResponse)
err = retrieveChanges(ctx, body, changes, "0", &changesResponse)
if err != nil {
return zero, "", err
}
var updatedResponse GETRESP
err = retrieveGet(logger, body, getUpdated, "1", &updatedResponse)
err = retrieveGet(ctx, body, getUpdated, "1", &updatedResponse)
if err != nil {
logger.Error().Err(err).Send()
return zero, "", err
@@ -461,19 +470,20 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an
notUpdatedExtractor func(SETRESP) map[string]SetError,
objExtractor func(GETRESP) RESP,
id string, changes CHANGES,
session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string) (RESP, SessionState, State, Language, Error) {
logger = client.logger(name, session, logger)
ctx Context) (RESP, SessionState, State, Language, Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
update := setCommandFactory(map[string]PatchObject{id: changes.AsPatch()})
get := getCommandFactory(id)
cmd, err := client.request(session, logger, using, invocation(update, "0"), invocation(get, "1"))
cmd, err := client.request(ctx, 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) {
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var setResponse SETRESP
err = retrieveSet(logger, body, update, "0", &setResponse)
err = retrieveSet(ctx, body, update, "0", &setResponse)
if err != nil {
return zero, setResponse.GetNewState(), err
}
@@ -484,10 +494,107 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an
return zero, "", setErrorError(setErr, update.GetObjectType())
}
var getResponse GETRESP
err = retrieveGet(logger, body, get, "1", &getResponse)
err = retrieveGet(ctx, body, get, "1", &getResponse)
if err != nil {
return zero, setResponse.GetNewState(), err
}
return objExtractor(getResponse), setResponse.GetNewState(), nil
})
}
func query[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T], QUERYRESP QueryResponse[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace,
defaultSortBy []SORT,
queryCommandFactory func(filter FILTER, sortBy []SORT, limit uint, position uint) QUERY,
getCommandFactory func(cmd Command, path string, rof string) GET,
respMapper func(query QUERYRESP, get GETRESP) RESP,
filter FILTER, sortBy []SORT, limit uint, position uint,
ctx Context) (RESP, SessionState, State, Language, Error) {
logger := client.logger(name, ctx)
ctx = ctx.WithLogger(logger)
if sortBy == nil {
sortBy = defaultSortBy
}
query := queryCommandFactory(filter, sortBy, limit, position)
get := getCommandFactory(query.GetCommand(), "/ids/*", "0")
var zero RESP
cmd, err := client.request(ctx, using, invocation(query, "0"), invocation(get, "1"))
if err != nil {
return zero, "", "", "", err
}
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
var queryResponse QUERYRESP
err = retrieveQuery(ctx, body, query, "0", &queryResponse)
if err != nil {
return zero, EmptyState, err
}
var getResponse GETRESP
err = retrieveGet(ctx, body, get, "1", &getResponse)
if err != nil {
return zero, EmptyState, err
}
return respMapper(queryResponse, getResponse), queryResponse.GetQueryState(), nil
})
}
func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T], GET GetCommand[T], QUERYRESP QueryResponse[T], GETRESP GetResponse[T], RESP any]( //NOSONAR
client *Client, name string, using []JmapNamespace,
defaultSortBy []SORT,
queryCommandFactory func(accountId string, filter FILTER, sortBy []SORT, position int, limit uint) QUERY,
getCommandFactory func(accountId string, cmd Command, path string, rof string) GET,
respMapper func(query QUERYRESP, get GETRESP) RESP,
accountIds []string,
filter FILTER, sortBy []SORT, limit uint, position int,
ctx Context) (map[string]RESP, SessionState, State, Language, Error) {
uniqueAccountIds := structs.Uniq(accountIds)
if sortBy == nil {
sortBy = defaultSortBy
}
invocations := make([]Invocation, len(uniqueAccountIds)*2)
var g GET
var q QUERY
for i, accountId := range uniqueAccountIds {
query := queryCommandFactory(accountId, filter, sortBy, position, limit)
get := getCommandFactory(accountId, query.GetCommand(), "/ids/*", mcid(accountId, "0"))
invocations[i*2+0] = invocation(query, mcid(accountId, "0"))
invocations[i*2+1] = invocation(get, mcid(accountId, "1"))
q = query
g = get
}
cmd, err := client.request(ctx, NS_CALENDARS, invocations...)
if err != nil {
return nil, "", "", "", err
}
return command(client, ctx, cmd, func(body *Response) (map[string]RESP, State, Error) {
resp := map[string]RESP{}
stateByAccountId := map[string]State{}
for _, accountId := range uniqueAccountIds {
var queryResponse QUERYRESP
err = retrieveQuery(ctx, body, q, mcid(accountId, "0"), &queryResponse)
if err != nil {
return nil, "", err
}
var getResponse GETRESP
err = retrieveGet(ctx, body, g, mcid(accountId, "1"), &getResponse)
if err != nil {
return nil, "", err
}
if len(getResponse.GetNotFound()) > 0 {
// TODO what to do when there are not-found calendarevents here? potentially nothing, they could have been deleted between query and get?
}
resp[accountId] = respMapper(queryResponse, getResponse)
stateByAccountId[accountId] = getResponse.GetState()
}
return resp, squashState(stateByAccountId), nil
})
}

View File

@@ -1,7 +1,6 @@
package jmap
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -13,7 +12,6 @@ import (
"github.com/mitchellh/mapstructure"
"github.com/opencloud-eu/opencloud/pkg/jscalendar"
"github.com/opencloud-eu/opencloud/pkg/log"
)
type eventListeners[T any] struct {
@@ -52,16 +50,19 @@ func mcid(accountId string, tag string) string {
return accountId + ":" + tag
}
func command[T any](api ApiClient, //NOSONAR
logger *log.Logger,
ctx context.Context,
session *Session,
sessionOutdatedHandler func(session *Session, newState SessionState),
type Cmdr interface {
ApiSupplier
Hooks
}
func command[T any](client Cmdr, //NOSONAR
ctx Context,
request Request,
acceptLanguage string,
mapper func(body *Response) (T, State, Error)) (T, SessionState, State, Language, Error) {
responseBody, language, jmapErr := api.Command(ctx, logger, session, request, acceptLanguage)
logger := ctx.Logger
responseBody, language, jmapErr := client.Api().Command(request, ctx)
if jmapErr != nil {
var zero T
return zero, "", "", language, jmapErr
@@ -75,10 +76,8 @@ func command[T any](api ApiClient, //NOSONAR
return zero, "", "", language, jmapError(err, JmapErrorDecodingResponseBody)
}
if response.SessionState != session.State {
if sessionOutdatedHandler != nil {
sessionOutdatedHandler(session, response.SessionState)
}
if response.SessionState != ctx.Session.State {
client.OnSessionOutdated(ctx.Session, response.SessionState)
}
// search for an "error" response
@@ -199,25 +198,25 @@ func retrieveResponseMatch(data *Response, command Command, tag string) (Invocat
return Invocation{}, false
}
func retrieveResponseMatchParameters[T any](logger *log.Logger, data *Response, command Command, tag string, target *T) Error {
func retrieveResponseMatchParameters[T any](ctx Context, 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) // NOSONAR
logger.Error().Msg(err.Error())
ctx.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) // NOSONAR
logger.Error().Msg(err.Error())
ctx.Logger.Error().Msg(err.Error())
return jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
*target = typedParams
return nil
}
func tryRetrieveResponseMatchParameters[T any](logger *log.Logger, data *Response, command Command, tag string, target *T) (bool, Error) {
func tryRetrieveResponseMatchParameters[T any](ctx Context, data *Response, command Command, tag string, target *T) (bool, Error) {
match, ok := retrieveResponseMatch(data, command, tag)
if !ok {
return false, nil
@@ -226,23 +225,35 @@ func tryRetrieveResponseMatchParameters[T any](logger *log.Logger, data *Respons
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)
logger.Error().Msg(err.Error())
ctx.Logger.Error().Msg(err.Error())
return true, jmapError(err, JmapErrorInvalidJmapResponsePayload)
}
*target = typedParams
return true, nil
}
func retrieveGet[T Foo, C GetCommand[T], R GetResponse[T]](logger *log.Logger, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target)
func retrieveGet[T Foo, C GetCommand[T], R GetResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target)
}
func retrieveSet[T Foo, C SetCommand[T], R SetResponse[T]](logger *log.Logger, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target)
func retrieveSet[T Foo, C SetCommand[T], R SetResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target)
}
func retrieveChanges[T Foo, C ChangesCommand[T], R ChangesResponse[T]](logger *log.Logger, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(logger, data, command.GetCommand(), tag, target)
func retrieveQuery[T Foo, C QueryCommand[T], R QueryResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target)
}
func retrieveChanges[T Foo, C ChangesCommand[T], R ChangesResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target)
}
func retrieveUpload[T Foo, C UploadCommand[T], R UploadResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target)
}
func retrieveParse[T Foo, C ParseCommand[T], R ParseResponse[T]](ctx Context, data *Response, command C, tag string, target *R) Error {
return retrieveResponseMatchParameters(ctx, data, command.GetCommand(), tag, target)
}
func (i *Invocation) MarshalJSON() ([]byte, error) {
@@ -396,7 +407,7 @@ func identity1[T any](t T) T {
func list[T Foo, GETRESP GetResponse[T]](r GETRESP) []T { return r.GetList() }
func getid[T Idable](r T) string { return r.GetId() }
func posUIntPtr(i uint) *uint {
func uintPtr(i uint) *uint {
if i > 0 {
return &i
} else {
@@ -404,6 +415,14 @@ func posUIntPtr(i uint) *uint {
}
}
func uintPtrIf(i uint, condition bool) *uint {
if condition {
return uintPtr(i)
} else {
return nil
}
}
func ns(namespaces ...JmapNamespace) []JmapNamespace {
result := make([]JmapNamespace, len(namespaces)+1)
result[0] = JmapCore

View File

@@ -2012,324 +2012,3 @@ type PersonalInfo struct {
// Note that succinct labels are best for proper display on small graphical interfaces and screens.
Label string `json:"label,omitempty"`
}
// A ContactCard object contains information about a person, company, or other entity, or represents a group of such entities.
//
// It is a JSCard (JSContact) object, as defined in [RFC9553], with two additional properties.
//
// A contact card with a `kind` property equal to `group` represents a group of contacts.
// Clients often present these separately from other contact cards.
//
// The `members` property, as defined in RFC XXX, Section XXX, contains a set of UIDs for other contacts that are the members
// of this group.
// Clients should consider the group to contain any `ContactCard` with a matching UID, from any account they have access to with
// support for the `urn:ietf:params:jmap:contacts` capability.
// UIDs that cannot be found SHOULD be ignored but preserved.
//
// For example, suppose a user adds contacts from a shared address book to their private group, then temporarily loses access to
// this address book.
// The UIDs cannot be resolved so the contacts will disappear from the group.
// However, if they are given permission to access the data again the UIDs will be found and the contacts will reappear.
type ContactCard struct {
// The id of the Card (immutable; server-set).
//
// The id uniquely identifies a Card with a particular “uid” within a particular account.
//
// This is a JMAP extension and not part of [RFC9553].
Id string `json:"id,omitempty" doc:"!request,req"`
// The set of AddressBook ids this Card belongs to.
//
// A card MUST belong to at least one AddressBook at all times (until it is destroyed).
//
// The set is represented as an object, with each key being an AddressBook id.
//
// The value for each key in the object MUST be true.
//
// This is a JMAP extension and not part of [RFC9553].
AddressBookIds map[string]bool `json:"addressBookIds,omitempty"`
// The JSContact type of the Card object: the value MUST be "Card".
Type TypeOfContactCard `json:"@type,omitempty"`
// The JSContact version of this Card.
//
// The value MUST be one of the IANA-registered JSContact Version values for the version property.
Version JSContactVersion `json:"version"`
// The date and time when the Card was created (UTCDateTime).
Created time.Time `json:"created,omitzero"`
// The kind of the entity the Card represents (default: `individual`).
//
// Values are:
// * `individual`: a single person
// * `group`: a group of people or entities
// * `org`: an organization
// * `location`: a named location
// * `device`: a device such as an appliance, a computer, or a network element
// * `application`: a software application
Kind ContactCardKind `json:"kind,omitempty"`
// The language tag, as defined in [RFC5646].
//
// The language tag that best describes the language used for text in the Card, optionally including
// additional information such as the script.
//
// Note that values MAY be localized in the `localizations` property.
Language string `json:"language,omitempty"`
// The set of Cards that are members of this group Card.
//
// Each key in the set is the uid property value of the member, and each boolean value MUST be `true`.
// If this property is set, then the value of the kind property MUST be `group`.
//
// The opposite is not true. A group Card will usually contain the members property to specify the members
// of the group, but it is not required to.
//
// A group Card without the members property can be considered an abstract grouping or one whose members
// are known empirically (e.g., `IETF Participants`).
Members map[string]bool `json:"members,omitempty"`
// The identifier for the product that created the Card.
//
// If set, the value MUST be at least one character long.
ProdId string `json:"prodId,omitempty"`
// The set of Card objects that relate to the Card.
//
// The value is a map, where each key is the uid property value of the related Card, and the value
// defines the relation
//
// ```json
// {
// "relatedTo": {
// "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6": {
// "relation": {"friend": true}
// },
// "8cacdfb7d1ffdb59@example.com": {
// "relation": {}
// }
// }
// }
// ```
RelatedTo map[string]Relation `json:"relatedTo,omitempty"`
// An identifier that associates the object as the same across different systems, address books, and views.
//
// The value SHOULD be a URN [RFC8141], but for compatibility with [RFC6350], it MAY also be a URI [RFC3986]
// or free-text value.
//
// The value of the URN SHOULD be in the "uuid" namespace [RFC9562].
//
// [RFC9562] describes multiple versions of Universally Unique IDentifiers (UUIDs); UUID version 4 is RECOMMENDED.
Uid string `json:"uid,omitempty"`
// The date and time when the data in the Card was last modified (UTCDateTime).
Updated time.Time `json:"updated,omitzero"`
// The name of the entity represented by the Card.
//
// This can be any type of name, e.g., it can, but need not, be the legal name of a person.
Name *Name `json:"name,omitempty"`
// The nicknames of the entity represented by the Card.
Nicknames map[string]Nickname `json:"nicknames,omitempty"`
// The company or organization names and units associated with the Card.
Organizations map[string]Organization `json:"organizations,omitempty"`
// The information that directs how to address, speak to, or refer to the entity that is represented by the Card.
SpeakToAs *SpeakToAs `json:"speakToAs,omitempty"`
// The job titles or functional positions of the entity represented by the Card.
Titles map[string]Title `json:"titles,omitempty"`
// The email addresses in which to contact the entity represented by the Card.
Emails map[string]EmailAddress `json:"emails,omitempty"`
// The online services that are associated with the entity represented by the Card.
//
// This can be messaging services, social media profiles, and other.
OnlineServices map[string]OnlineService `json:"onlineServices,omitempty"`
// The phone numbers by which to contact the entity represented by the Card.
Phones map[string]Phone `json:"phones,omitempty"`
// The preferred languages for contacting the entity associated with the Card.
PreferredLanguages map[string]LanguagePref `json:"preferredLanguages,omitempty"`
// The calendaring resources of the entity represented by the Card, such as to look up free-busy information.
//
// A Calendar object has all properties of the Resource data type, with the following additional definitions:
// * The `@type` property value MUST be `Calendar`, if set
// * The `kind` property is mandatory. Its enumerated values are:
// * `calendar`: The resource is a calendar that contains entries such as calendar events or tasks
// * `freeBusy`: The resource allows for free-busy lookups, for example, to schedule group events
Calendars map[string]Calendar `json:"calendars,omitempty"`
// The scheduling addresses by which the entity may receive calendar scheduling invitations.
SchedulingAddresses map[string]SchedulingAddress `json:"schedulingAddresses,omitempty"`
// The addresses of the entity represented by the Card, such as postal addresses or geographic locations.
Addresses map[string]Address `json:"addresses,omitempty"`
// The cryptographic resources such as public keys and certificates associated with the entity represented by the Card.
//
// A CryptoKey object has all properties of the `Resource` data type, with the following additional definition:
// the `@type` property value MUST be `CryptoKey`, if set.
//
// The following example shows how to refer to an external cryptographic resource:
// ```json
// "cryptoKeys": {
// "mykey1": {
// "uri": "https://www.example.com/keys/jdoe.cer"
// }
// }
// ```
CryptoKeys map[string]CryptoKey `json:"cryptoKeys,omitempty"`
// The directories containing information about the entity represented by the Card.
//
// A Directory object has all properties of the `Resource` data type, with the following additional definitions:
// * The `@type` property value MUST be `Directory`, if set
// * The `kind` property is mandatory; tts enumerated values are:
// * `directory`: the resource is a directory service that the entity represented by the Card is a part of; this
// typically is an organizational directory that also contains associated entities, e.g., co-workers and management
// in a company directory
// * `entry`: the resource is a directory entry of the entity represented by the Card; in contrast to the `directory`
// type, this is the specific URI for the entity within a directory
Directories map[string]Directory `json:"directories,omitempty"`
// The links to resources that do not fit any of the other use-case-specific resource properties.
//
// A Link object has all properties of the `Resource` data type, with the following additional definitions:
// * The `@type` property value MUST be `Link`, if set
// * The `kind` property is optional; tts enumerated values are:
// * `contact`: the resource is a URI by which the entity represented by the Card may be contacted;
// this includes web forms or other media that require user interaction
Links map[string]Link `json:"links,omitempty"`
// The media resources such as photographs, avatars, or sounds that are associated with the entity represented by the Card.
//
// A Media object has all properties of the Resource data type, with the following additional definitions:
// * the `@type` property value MUST be `Media`, if set
// * the `kind` property is mandatory; its enumerated values are:
// * `photo`: the resource is a photograph or avatar
// * `sound`: the resource is audio media, e.g., to specify the proper pronunciation of the name property contents
// * `logo`: the resource is a graphic image or logo associated with the entity represented by the Card
Media map[string]Media `json:"media,omitempty"`
// The property values localized to languages other than the main `language` of the Card.
//
// Localizations provide language-specific alternatives for existing property values and SHOULD NOT add new properties.
//
// The keys in the localizations property value are language tags [RFC5646]; the values are of type `PatchObject` and
// localize the Card in that language tag.
//
// The paths in the `PatchObject` are relative to the Card that includes the localizations property.
//
// A patch MUST NOT target the localizations property.
//
// Conceptually, a Card is localized as follows:
// * Determine the language tag in which the Card should be localized.
// * If the localizations property includes a key for that language, obtain the PatchObject value;
// if there is no such key, stop.
// * Create a copy of the Card, but do not copy the localizations property.
// * Apply all patches in the PatchObject to the copy of the Card.
// * Optionally, set the language property in the copy of the Card.
// * Use the patched copy of the Card as the localized variant of the original Card.
//
// A patch in the `PatchObject` may contain any value type.
//
// Its value MUST be a valid value according to the definition of the patched property.
Localizations map[string]PatchObject `json:"localizations,omitempty"`
// The memorable dates and events for the entity represented by the Card.
Anniversaries map[string]Anniversary `json:"anniversaries,omitempty"`
// The set of free-text keywords, also known as tags.
//
// Each key in the set is a keyword, and each boolean value MUST be `true`.
Keywords map[string]bool `json:"keywords,omitempty"`
// The free-text notes that are associated with the Card.
Notes map[string]Note `json:"notes,omitempty"`
// The personal information of the entity represented by the Card.
PersonalInfo map[string]PersonalInfo `json:"personalInfo,omitempty"`
}
func (f ContactCard) GetId() string { return f.Id }
const (
ContactCardPropertyId = "id"
ContactCardPropertyAddressBookIds = "addressBookIds"
ContactCardPropertyType = "@type"
ContactCardPropertyVersion = "version"
ContactCardPropertyCreated = "created"
ContactCardPropertyKind = "kind"
ContactCardPropertyLanguage = "language"
ContactCardPropertyMembers = "members"
ContactCardPropertyProdId = "prodId"
ContactCardPropertyRelatedTo = "relatedTo"
ContactCardPropertyUid = "uid"
ContactCardPropertyUpdated = "updated"
ContactCardPropertyName = "name"
ContactCardPropertyNicknames = "nicknames"
ContactCardPropertyOrganizations = "organizations"
ContactCardPropertySpeakToAs = "speakToAs"
ContactCardPropertyTitles = "titles"
ContactCardPropertyEmails = "emails"
ContactCardPropertyOnlineServices = "onlineServices"
ContactCardPropertyPhones = "phones"
ContactCardPropertyPreferredLanguages = "preferredLanguages"
ContactCardPropertyCalendars = "calendars"
ContactCardPropertySchedulingAddresses = "schedulingAddresses"
ContactCardPropertyAddresses = "addresses"
ContactCardPropertyCryptoKeys = "cryptoKeys"
ContactCardPropertyDirectories = "directories"
ContactCardPropertyLinks = "links"
ContactCardPropertyMedia = "media"
ContactCardPropertyLocalizations = "localizations"
ContactCardPropertyAnniversaries = "anniversaries"
ContactCardPropertyKeywords = "keywords"
ContactCardPropertyNotes = "notes"
ContactCardPropertyPersonalInfo = "personalInfo"
)
var ContactCardProperties = []string{
ContactCardPropertyId,
ContactCardPropertyAddressBookIds,
ContactCardPropertyType,
ContactCardPropertyVersion,
ContactCardPropertyCreated,
ContactCardPropertyKind,
ContactCardPropertyLanguage,
ContactCardPropertyMembers,
ContactCardPropertyProdId,
ContactCardPropertyRelatedTo,
ContactCardPropertyUid,
ContactCardPropertyUpdated,
ContactCardPropertyName,
ContactCardPropertyNicknames,
ContactCardPropertyOrganizations,
ContactCardPropertySpeakToAs,
ContactCardPropertyTitles,
ContactCardPropertyEmails,
ContactCardPropertyOnlineServices,
ContactCardPropertyPhones,
ContactCardPropertyPreferredLanguages,
ContactCardPropertyCalendars,
ContactCardPropertySchedulingAddresses,
ContactCardPropertyAddresses,
ContactCardPropertyCryptoKeys,
ContactCardPropertyDirectories,
ContactCardPropertyLinks,
ContactCardPropertyMedia,
ContactCardPropertyLocalizations,
ContactCardPropertyAnniversaries,
ContactCardPropertyKeywords,
ContactCardPropertyNotes,
ContactCardPropertyPersonalInfo,
}

View File

@@ -44,7 +44,7 @@ func (g *Groupware) GetAccounts(w http.ResponseWriter, r *http.Request) {
func (g *Groupware) GetAccountsWithTheirIdentities(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
allAccountIds := req.AllAccountIds()
resp, sessionState, state, lang, err := g.jmap.GetIdentitiesForAllAccounts(allAccountIds, req.session, req.ctx, req.logger, req.language())
resp, sessionState, state, lang, err := g.jmap.GetIdentitiesForAllAccounts(allAccountIds, req.ctx)
if err != nil {
return req.jmapErrorN(allAccountIds, err, sessionState, lang)
}

View File

@@ -15,7 +15,7 @@ func (g *Groupware) GetAddressbooks(w http.ResponseWriter, r *http.Request) {
return resp
}
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, req.logger, req.language(), nil)
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, []string{}, req.ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -42,7 +42,7 @@ func (g *Groupware) GetAddressbook(w http.ResponseWriter, r *http.Request) {
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
logger := log.From(l)
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, req.session, req.ctx, logger, req.language(), []string{addressBookId})
addressbooks, sessionState, state, lang, jerr := g.jmap.GetAddressbooks(accountId, []string{addressBookId}, req.ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -84,8 +84,8 @@ func (g *Groupware) GetAddressBookChanges(w http.ResponseWriter, r *http.Request
}
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetAddressbookChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetAddressbookChanges(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -110,7 +110,8 @@ func (g *Groupware) CreateAddressBook(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(l)
created, sessionState, state, lang, jerr := g.jmap.CreateAddressBook(accountId, req.session, req.ctx, logger, req.language(), create)
ctx := req.ctx.WithLogger(logger)
created, sessionState, state, lang, jerr := g.jmap.CreateAddressBook(accountId, create, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -133,8 +134,8 @@ func (g *Groupware) DeleteAddressBook(w http.ResponseWriter, r *http.Request) {
l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
logger := log.From(l)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteAddressBook(accountId, []string{addressBookId}, req.session, req.ctx, logger, req.language())
ctx := req.ctx.WithLogger(logger)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteAddressBook(accountId, []string{addressBookId}, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -5,6 +5,7 @@ import (
"net/http"
"strconv"
"github.com/opencloud-eu/opencloud/pkg/jmap"
"github.com/opencloud-eu/opencloud/pkg/log"
)
@@ -27,8 +28,9 @@ func (g *Groupware) GetBlobMeta(w http.ResponseWriter, r *http.Request) {
l = l.Str(UriParamBlobId, blobId)
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, req.session, req.ctx, logger, req.language(), blobId)
res, sessionState, state, lang, jerr := g.jmap.GetBlobMetadata(accountId, blobId, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -57,8 +59,8 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
resp, lang, jerr := g.jmap.UploadBlobStream(accountId, req.session, req.ctx, logger, req.language(), contentType, body)
ctx := req.ctx.WithLogger(logger)
resp, lang, jerr := g.jmap.UploadBlobStream(accountId, contentType, body, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, req.session.State, lang)
}
@@ -85,21 +87,22 @@ func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) {
return gwerr
}
logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(UriParamBlobId, blobId))
ctx := req.ctx.WithLogger(logger)
return req.serveBlob(blobId, name, typ, logger, accountId, w)
return req.serveBlob(blobId, name, typ, ctx, accountId, w)
})
}
func (r *Request) serveBlob(blobId string, name string, typ string, logger *log.Logger, accountId string, w http.ResponseWriter) *Error {
func (r *Request) serveBlob(blobId string, name string, typ string, ctx jmap.Context, accountId string, w http.ResponseWriter) *Error {
if typ == "" {
typ = DefaultBlobDownloadType
}
blob, lang, jerr := r.g.jmap.DownloadBlobStream(accountId, blobId, name, typ, r.session, r.ctx, logger, r.language())
blob, lang, jerr := r.g.jmap.DownloadBlobStream(accountId, blobId, name, typ, ctx)
if blob != nil && blob.Body != nil {
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
logger.Error().Err(err).Msg("failed to close response body")
ctx.Logger.Error().Err(err).Msg("failed to close response body")
}
}(blob.Body)
}

View File

@@ -15,7 +15,7 @@ func (g *Groupware) GetCalendars(w http.ResponseWriter, r *http.Request) {
return resp
}
calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, req.session, req.ctx, req.logger, req.language(), nil)
calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, nil, req.ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -41,7 +41,7 @@ func (g *Groupware) GetCalendarById(w http.ResponseWriter, r *http.Request) {
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
logger := log.From(l)
calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, req.session, req.ctx, logger, req.language(), []string{calendarId})
calendars, sessionState, state, lang, jerr := g.jmap.GetCalendars(accountId, single(calendarId), req.ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -83,8 +83,8 @@ func (g *Groupware) GetCalendarChanges(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetCalendarChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetCalendarChanges(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -109,7 +109,8 @@ func (g *Groupware) CreateCalendar(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(l)
created, sessionState, state, lang, jerr := g.jmap.CreateCalendar(accountId, req.session, req.ctx, logger, req.language(), create)
ctx := req.ctx.WithLogger(logger)
created, sessionState, state, lang, jerr := g.jmap.CreateCalendar(accountId, create, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -132,8 +133,8 @@ func (g *Groupware) DeleteCalendar(w http.ResponseWriter, r *http.Request) {
l.Str(UriParamCalendarId, log.SafeString(calendarId))
logger := log.From(l)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendar(accountId, []string{calendarId}, req.session, req.ctx, logger, req.language())
ctx := req.ctx.WithLogger(logger)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendar(accountId, single(calendarId), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -77,7 +77,8 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON
}
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetChanges(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -57,12 +57,12 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
}
l = l.Str(UriParamAddressBookId, log.SafeString(addressBookId))
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
offset, ok, err := req.parseIntParam(QueryParamOffset, 0)
if err != nil {
return req.errorN(accountIds, err)
}
if ok {
l = l.Uint(QueryParamOffset, offset)
l = l.Int(QueryParamOffset, offset)
}
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
@@ -84,7 +84,8 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
}
logger := log.From(l)
contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards(accountIds, req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
ctx := req.ctx.WithLogger(logger)
contactsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryContactCards(accountIds, filter, sortBy, offset, limit, true, ctx)
if jerr != nil {
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
}
@@ -113,7 +114,8 @@ func (g *Groupware) GetContactById(w http.ResponseWriter, r *http.Request) {
l = l.Str(UriParamContactId, log.SafeString(contactId))
logger := log.From(l)
contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, req.session, req.ctx, logger, req.language(), []string{contactId})
ctx := req.ctx.WithLogger(logger)
contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, single(contactId), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -140,7 +142,8 @@ func (g *Groupware) GetAllContacts(w http.ResponseWriter, r *http.Request) {
l := req.logger.With()
logger := log.From(l)
contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, req.session, req.ctx, logger, req.language(), []string{})
ctx := req.ctx.WithLogger(logger)
contacts, sessionState, state, lang, jerr := g.jmap.GetContactCards(accountId, []string{}, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -173,7 +176,8 @@ func (g *Groupware) GetContactsChanges(w http.ResponseWriter, r *http.Request) {
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetContactCardChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetContactCardChanges(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -205,7 +209,8 @@ func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(l)
created, sessionState, state, lang, jerr := g.jmap.CreateContactCard(accountId, req.session, req.ctx, logger, req.language(), create)
ctx := req.ctx.WithLogger(logger)
created, sessionState, state, lang, jerr := g.jmap.CreateContactCard(accountId, create, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -228,8 +233,8 @@ func (g *Groupware) DeleteContact(w http.ResponseWriter, r *http.Request) {
l.Str(UriParamContactId, log.SafeString(contactId))
logger := log.From(l)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteContactCard(accountId, []string{contactId}, req.session, req.ctx, logger, req.language())
ctx := req.ctx.WithLogger(logger)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteContactCard(accountId, single(contactId), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -48,8 +48,8 @@ func (g *Groupware) GetEmailChanges(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, true, g.config.maxBodyValueBytes, maxChanges)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, sinceState, true, g.config.maxBodyValueBytes, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -98,12 +98,13 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
}
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
collapseThreads := false
fetchBodies := false
withThreads := true
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, offset, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads)
emails, sessionState, state, lang, jerr := g.jmap.GetAllEmailsInMailbox(accountId, mailboxId, offset, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -151,8 +152,8 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
}
logger := log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)).Str("id", log.SafeString(id)).Str("accept", log.SafeString(accept)))
blobId, _, _, _, jerr := g.jmap.GetEmailBlobId(accountId, req.session, req.ctx, logger, req.language(), id)
ctx := req.ctx.WithLogger(logger)
blobId, _, _, _, jerr := g.jmap.GetEmailBlobId(accountId, id, ctx)
if jerr != nil {
return req.apiErrorFromJmap(req.observeJmapError(jerr))
}
@@ -165,7 +166,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
if gwerr != nil {
return gwerr
}
return req.serveBlob(blobId, name, typ, logger, accountId, w)
return req.serveBlob(blobId, name, typ, ctx, accountId, w)
}
})
} else {
@@ -195,8 +196,8 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
if len(ids) == 1 {
logger := log.From(l.Str("id", log.SafeString(id)))
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.config.maxBodyValueBytes, markAsSeen, true)
ctx := req.ctx.WithLogger(logger)
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, ids, true, g.config.maxBodyValueBytes, markAsSeen, true, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -211,8 +212,8 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
}
} else {
logger := log.From(l.Array("ids", log.SafeStringArray(ids)))
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), ids, true, g.config.maxBodyValueBytes, markAsSeen, false)
ctx := req.ctx.WithLogger(logger)
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, ids, true, g.config.maxBodyValueBytes, markAsSeen, false, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -269,7 +270,8 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
}
logger := log.From(l)
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false)
ctx := req.ctx.WithLogger(logger)
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, single(id), false, 0, false, false, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -305,8 +307,8 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
Str(UriParamEmailId, log.SafeString(id))
l = contextAppender(l)
logger := log.From(l)
emails, _, _, _, lang, jerr := g.jmap.GetEmails(mailAccountId, req.session, req.ctx, logger, req.language(), []string{id}, false, 0, false, false)
ctx := req.ctx.WithLogger(logger)
emails, _, _, _, lang, jerr := g.jmap.GetEmails(mailAccountId, single(id), false, 0, false, false, ctx)
if jerr != nil {
return req.apiErrorFromJmap(req.observeJmapError(jerr))
}
@@ -329,7 +331,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
return nil
}
blob, lang, jerr := g.jmap.DownloadBlobStream(blobAccountId, attachment.BlobId, attachment.Name, attachment.Type, req.session, req.ctx, logger, req.language())
blob, lang, jerr := g.jmap.DownloadBlobStream(blobAccountId, attachment.BlobId, attachment.Name, attachment.Type, ctx)
if blob != nil && blob.Body != nil {
defer func(Body io.ReadCloser) {
err := Body.Close()
@@ -390,8 +392,8 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
}
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, req.session, req.ctx, logger, req.language(), since, true, g.config.maxBodyValueBytes, maxChanges)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetEmailChanges(accountId, since, true, g.config.maxBodyValueBytes, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -612,6 +614,7 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
return req.error(accountId, err)
}
logger = log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)))
ctx := req.ctx.WithLogger(logger)
if !filter.IsNotEmpty() {
filter = nil
@@ -619,7 +622,7 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
fetchBodies := false
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, req.session, req.ctx, logger, req.language(), offset, limit, fetchBodies, g.config.maxBodyValueBytes)
resultsByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailsWithSnippets(single(accountId), filter, offset, limit, fetchBodies, g.config.maxBodyValueBytes, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -671,13 +674,14 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
return req.errorN(allAccountIds, err)
}
logger = log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(allAccountIds)))
ctx := req.ctx.WithLogger(logger)
if !filter.IsNotEmpty() {
filter = nil
}
if makesSnippets {
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, req.session, req.ctx, logger, req.language(), offset, limit)
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSnippets(allAccountIds, filter, offset, limit, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
@@ -717,7 +721,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
} else {
withThreads := true
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, withThreads)
resultsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, limit, withThreads, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
@@ -762,8 +766,8 @@ var draftEmailAutoMailboxRolePrecedence = []string{
jmap.JmapMailboxRoleInbox, // but if there is none, we will use the Mailbox with the inbox role instead
}
func findDraftsMailboxId(j *jmap.Client, accountId string, req Request, logger *log.Logger) (string, Response) {
mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), req.session, req.ctx, logger, req.language(), draftEmailAutoMailboxRolePrecedence)
func findDraftsMailboxId(j *jmap.Client, accountId string, req Request, ctx jmap.Context) (string, Response) {
mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), draftEmailAutoMailboxRolePrecedence, ctx)
if jerr != nil {
return "", req.jmapError(accountId, jerr, sessionState, lang)
} else {
@@ -785,8 +789,8 @@ var sentEmailAutoMailboxRolePrecedence = []string{
var draftAndSentMailboxRoles = structs.Uniq(structs.Concat(draftEmailAutoMailboxRolePrecedence, sentEmailAutoMailboxRolePrecedence))
func findSentMailboxId(j *jmap.Client, accountId string, req Request, logger *log.Logger) (string, string, Response) { //NOSONAR
mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), req.session, req.ctx, logger, req.language(), draftAndSentMailboxRoles)
func findSentMailboxId(j *jmap.Client, accountId string, req Request, ctx jmap.Context) (string, string, Response) { //NOSONAR
mailboxIdsPerAccountIds, sessionState, _, lang, jerr := j.SearchMailboxIdsPerRole(single(accountId), draftAndSentMailboxRoles, ctx)
if jerr != nil {
return "", "", req.jmapError(accountId, jerr, sessionState, lang)
} else {
@@ -823,6 +827,7 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, gwerr)
}
logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
ctx := req.ctx.WithLogger(logger)
var body jmap.EmailCreate
err := req.body(&body)
@@ -831,7 +836,7 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
}
if len(body.MailboxIds) < 1 {
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, logger)
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, ctx)
if mailboxId != "" {
body.MailboxIds[mailboxId] = true
} else {
@@ -839,7 +844,7 @@ func (g *Groupware) CreateEmail(w http.ResponseWriter, r *http.Request) {
}
}
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, "", req.session, req.ctx, logger, req.language())
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, "", ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -863,6 +868,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
}
logger = log.From(logger.With().Str(logAccountId, log.SafeString(accountId)))
ctx := req.ctx.WithLogger(logger)
var body jmap.EmailCreate
err = req.body(&body)
@@ -871,7 +877,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
}
if len(body.MailboxIds) < 1 {
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, logger)
mailboxId, resp := findDraftsMailboxId(g.jmap, accountId, req, ctx)
if mailboxId != "" {
body.MailboxIds[mailboxId] = true
} else {
@@ -879,7 +885,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
}
}
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, replaceId, req.session, req.ctx, logger, req.language())
created, sessionState, state, lang, jerr := g.jmap.CreateEmail(accountId, body, replaceId, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -905,6 +911,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
l.Str(UriParamEmailId, log.SafeString(emailId))
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
var body map[string]any
err = req.body(&body)
@@ -916,7 +923,7 @@ func (g *Groupware) UpdateEmail(w http.ResponseWriter, r *http.Request) {
emailId: body,
}
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, updates, req.session, req.ctx, logger, req.language())
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, updates, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -964,6 +971,7 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
l.Str(UriParamEmailId, log.SafeString(emailId))
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
var body emailKeywordUpdates
err = req.body(&body)
@@ -986,7 +994,7 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
emailId: patch,
}
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -1025,6 +1033,7 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
l.Str(UriParamEmailId, log.SafeString(emailId))
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
var body []string
err = req.body(&body)
@@ -1044,7 +1053,7 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
emailId: patch,
}
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -1087,6 +1096,7 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
l.Str(UriParamEmailId, log.SafeString(emailId))
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
var body []string
err = req.body(&body)
@@ -1106,7 +1116,7 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
emailId: patch,
}
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, req.session, req.ctx, logger, req.language())
result, sessionState, state, lang, jerr := g.jmap.UpdateEmails(accountId, patches, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -1147,8 +1157,8 @@ func (g *Groupware) DeleteEmail(w http.ResponseWriter, r *http.Request) {
l.Str(UriParamEmailId, log.SafeString(emailId))
logger := log.From(l)
resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, []string{emailId}, req.session, req.ctx, logger, req.language())
ctx := req.ctx.WithLogger(logger)
resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, single(emailId), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -1196,8 +1206,8 @@ func (g *Groupware) DeleteEmails(w http.ResponseWriter, r *http.Request) {
l.Array("emailIds", log.SafeStringArray(emailIds))
logger := log.From(l)
resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, emailIds, req.session, req.ctx, logger, req.language())
ctx := req.ctx.WithLogger(logger)
resp, sessionState, state, lang, jerr := g.jmap.DeleteEmails(accountId, emailIds, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -1245,7 +1255,8 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { //NOSONA
moveToMailboxId, _ := req.getStringParam(QueryParamMoveToMailboxId, "")
if moveFromMailboxId == "" || moveToMailboxId == "" {
draftsMailboxId, sentMailboxId, resp := findSentMailboxId(g.jmap, accountId, req, req.logger)
ctx := req.ctx.WithLogger(log.From(l))
draftsMailboxId, sentMailboxId, resp := findSentMailboxId(g.jmap, accountId, req, ctx)
if draftsMailboxId != "" && sentMailboxId != "" {
if moveFromMailboxId == "" {
moveFromMailboxId = draftsMailboxId
@@ -1265,8 +1276,8 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { //NOSONA
}
logger := log.From(l)
resp, sessionState, state, lang, jerr := g.jmap.SubmitEmail(accountId, identityId, emailId, move, req.session, req.ctx, logger, req.language())
ctx := req.ctx.WithLogger(logger)
resp, sessionState, state, lang, jerr := g.jmap.SubmitEmail(accountId, identityId, emailId, move, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -1366,10 +1377,11 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
}
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
reqId := req.GetRequestId()
getEmailsBefore := time.Now()
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, req.session, req.ctx, logger, req.language(), []string{id}, true, g.config.maxBodyValueBytes, false, false)
emails, _, sessionState, state, lang, jerr := g.jmap.GetEmails(accountId, single(id), true, g.config.maxBodyValueBytes, false, false, ctx)
getEmailsDuration := time.Since(getEmailsBefore)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
@@ -1394,7 +1406,8 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
g.job(logger, RelationTypeSameSender, func(jobId uint64, l *log.Logger) {
before := time.Now()
resultsByAccountId, _, _, lang, jerr := g.jmap.QueryEmails(single(accountId), filter, req.session, bgctx, l, req.language(), 0, limit, false, g.config.maxBodyValueBytes)
ctx = ctx.WithLogger(logger).WithContext(bgctx)
resultsByAccountId, _, _, lang, jerr := g.jmap.QueryEmails(single(accountId), filter, 0, limit, false, g.config.maxBodyValueBytes, ctx)
if results, ok := resultsByAccountId[accountId]; ok {
duration := time.Since(before)
if jerr != nil {
@@ -1415,7 +1428,8 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
g.job(logger, RelationTypeSameThread, func(jobId uint64, l *log.Logger) {
before := time.Now()
emails, _, _, lang, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, req.session, bgctx, l, req.language(), false, g.config.maxBodyValueBytes)
ctx = ctx.WithLogger(logger).WithContext(bgctx)
emails, _, _, lang, jerr := g.jmap.EmailsInThread(accountId, email.ThreadId, false, g.config.maxBodyValueBytes, ctx)
duration := time.Since(before)
if jerr != nil {
_ = req.observeJmapError(jerr)
@@ -1683,8 +1697,9 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
}
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, req.session, req.ctx, logger, req.language(), filter, limit, true)
emailsSummariesByAccount, sessionState, state, lang, jerr := g.jmap.QueryEmailSummaries(allAccountIds, filter, limit, true, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}

View File

@@ -24,12 +24,12 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
}
l = l.Str(UriParamCalendarId, log.SafeString(calendarId))
offset, ok, err := req.parseUIntParam(QueryParamOffset, 0)
offset, ok, err := req.parseIntParam(QueryParamOffset, 0)
if err != nil {
return req.error(accountId, err)
}
if ok {
l = l.Uint(QueryParamOffset, offset)
l = l.Int(QueryParamOffset, offset)
}
limit, ok, err := req.parseUIntParam(QueryParamLimit, g.defaults.contactLimit)
@@ -46,7 +46,8 @@ func (g *Groupware) GetEventsInCalendar(w http.ResponseWriter, r *http.Request)
sortBy := []jmap.CalendarEventComparator{{Property: jmap.CalendarEventPropertyUpdated, IsAscending: false}}
logger := log.From(l)
eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), req.session, req.ctx, logger, req.language(), filter, sortBy, offset, limit)
ctx := req.ctx.WithLogger(logger)
eventsByAccountId, sessionState, state, lang, jerr := g.jmap.QueryCalendarEvents(single(accountId), filter, sortBy, offset, limit, true, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -82,7 +83,8 @@ func (g *Groupware) GetEventChanges(w http.ResponseWriter, r *http.Request) {
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetCalendarEventChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetCalendarEventChanges(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -108,7 +110,8 @@ func (g *Groupware) CreateCalendarEvent(w http.ResponseWriter, r *http.Request)
}
logger := log.From(l)
created, sessionState, state, lang, jerr := g.jmap.CreateCalendarEvent(accountId, req.session, req.ctx, logger, req.language(), create)
ctx := req.ctx.WithLogger(logger)
created, sessionState, state, lang, jerr := g.jmap.CreateCalendarEvent(accountId, create, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -131,8 +134,8 @@ func (g *Groupware) DeleteCalendarEvent(w http.ResponseWriter, r *http.Request)
l.Str(UriParamEventId, log.SafeString(eventId))
logger := log.From(l)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendarEvent(accountId, []string{eventId}, req.session, req.ctx, logger, req.language())
ctx := req.ctx.WithLogger(logger)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteCalendarEvent(accountId, single(eventId), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -174,8 +177,8 @@ func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
blobIds := strings.Split(blobId, ",")
l := req.logger.With().Array(UriParamBlobId, log.SafeStringArray(blobIds))
logger := log.From(l)
resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, req.session, req.ctx, logger, req.language(), blobIds)
ctx := req.ctx.WithLogger(logger)
resp, sessionState, state, lang, jerr := g.jmap.ParseICalendarBlob(accountId, blobIds, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/opencloud-eu/opencloud/pkg/jmap"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
// Get the list of identities that are associated with an account.
@@ -18,7 +17,8 @@ func (g *Groupware) GetIdentities(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
res, sessionState, state, lang, jerr := g.jmap.GetAllIdentities(accountId, req.session, req.ctx, logger, req.language())
ctx := req.ctx.WithLogger(logger)
res, sessionState, state, lang, jerr := g.jmap.GetAllIdentities(accountId, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -37,7 +37,8 @@ func (g *Groupware) GetIdentityById(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(logIdentityId, id))
res, sessionState, state, lang, jerr := g.jmap.GetIdentities(accountId, req.session, req.ctx, logger, req.language(), []string{id})
ctx := req.ctx.WithLogger(logger)
res, sessionState, state, lang, jerr := g.jmap.GetIdentities(accountId, single(id), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -56,14 +57,15 @@ func (g *Groupware) AddIdentity(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
ctx := req.ctx.WithLogger(logger)
var identity jmap.Identity
var identity jmap.IdentityChange
err = req.body(&identity)
if err != nil {
return req.error(accountId, err)
}
created, sessionState, state, lang, jerr := g.jmap.CreateIdentity(accountId, req.session, req.ctx, logger, req.language(), identity)
created, sessionState, state, lang, jerr := g.jmap.CreateIdentity(accountId, identity, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -77,15 +79,21 @@ func (g *Groupware) ModifyIdentity(w http.ResponseWriter, r *http.Request) {
if err != nil {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
id, err := req.PathParamDoc(UriParamIdentityId, "The unique identifier of the Identity to modify")
if err != nil {
return req.error(accountId, err)
}
var identity jmap.Identity
logger := log.From(req.logger.With().Str(logAccountId, accountId).Str(UriParamIdentityId, log.SafeString(id)))
ctx := req.ctx.WithLogger(logger)
var identity jmap.IdentityChange
err = req.body(&identity)
if err != nil {
return req.error(accountId, err)
}
updated, sessionState, state, lang, jerr := g.jmap.UpdateIdentity(accountId, req.session, req.ctx, logger, req.language(), identity)
updated, sessionState, state, lang, jerr := g.jmap.UpdateIdentity(accountId, id, identity, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -100,7 +108,6 @@ func (g *Groupware) DeleteIdentity(w http.ResponseWriter, r *http.Request) {
if err != nil {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
id, err := req.PathParam(UriParamIdentityId)
if err != nil {
@@ -111,18 +118,19 @@ func (g *Groupware) DeleteIdentity(w http.ResponseWriter, r *http.Request) {
return req.parameterErrorResponse(single(accountId), UriParamIdentityId, fmt.Sprintf("Invalid value for path parameter '%v': '%s': %s", UriParamIdentityId, log.SafeString(id), "empty list of identity ids"))
}
deletion, sessionState, state, lang, jerr := g.jmap.DeleteIdentity(accountId, req.session, req.ctx, logger, req.language(), ids)
logger := log.From(req.logger.With().Str(logAccountId, accountId).Array(UriParamIdentityId, log.SafeStringArray(ids)))
ctx := req.ctx.WithLogger(logger)
notDeleted, sessionState, state, lang, jerr := g.jmap.DeleteIdentity(accountId, ids, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
notDeletedIds := structs.Missing(ids, deletion)
if len(notDeletedIds) == 0 {
if len(notDeleted) == 0 {
return req.noContent(accountId, sessionState, IdentityResponseObjectType, state)
} else {
logger.Error().Array("not-deleted", log.SafeStringArray(notDeletedIds)).Msgf("failed to delete %d identities", len(notDeletedIds))
return req.errorS(accountId, req.apiError(&ErrorFailedToDeleteSomeIdentities,
withMeta(map[string]any{"ids": notDeletedIds})), sessionState)
logger.Error().Msgf("failed to delete %d identities", len(notDeleted))
return req.errorS(accountId, req.apiError(&ErrorFailedToDeleteSomeIdentities), sessionState)
}
})
}
@@ -150,7 +158,8 @@ func (g *Groupware) GetIdentityChanges(w http.ResponseWriter, r *http.Request) {
l = l.Str(HeaderParamSince, log.SafeString(string(sinceState)))
logger := log.From(l)
changes, sessionState, state, lang, jerr := g.jmap.GetIdentityChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
ctx := req.ctx.WithLogger(logger)
changes, sessionState, state, lang, jerr := g.jmap.GetIdentityChanges(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -148,7 +148,7 @@ func (g *Groupware) Index(w http.ResponseWriter, r *http.Request) {
g.respond(w, r, func(req Request) Response {
accountIds := req.AllAccountIds()
boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.session, req.ctx, req.logger, req.language())
boot, sessionState, state, lang, err := g.jmap.GetBootstrap(accountIds, req.ctx)
if err != nil {
return req.jmapErrorN(accountIds, err, sessionState, lang)
}

View File

@@ -30,7 +30,7 @@ func (g *Groupware) GetMailbox(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
mailboxes, sessionState, state, lang, jerr := g.jmap.GetMailbox(accountId, req.session, req.ctx, req.logger, req.language(), []string{mailboxId})
mailboxes, sessionState, state, lang, jerr := g.jmap.GetMailbox(accountId, single(mailboxId), req.ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -83,9 +83,10 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
ctx := req.ctx.WithLogger(logger)
if hasCriteria {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(single(accountId), req.session, req.ctx, logger, req.language(), filter)
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(single(accountId), filter, ctx)
if err != nil {
return req.jmapError(accountId, err, sessionState, lang)
}
@@ -96,7 +97,7 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS
return req.notFound(accountId, sessionState, MailboxResponseObjectType, state)
}
} else {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(single(accountId), req.session, req.ctx, logger, req.language())
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(single(accountId), ctx)
if err != nil {
return req.jmapError(accountId, err, sessionState, lang)
}
@@ -117,6 +118,7 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
return req.noopN(nil) // when the user has no accounts
}
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
ctx := req.ctx.WithLogger(logger)
var filter jmap.MailboxFilterCondition
hasCriteria := false
@@ -134,13 +136,13 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
}
if hasCriteria {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.SearchMailboxes(accountIds, filter, ctx)
if err != nil {
return req.jmapErrorN(accountIds, err, sessionState, lang)
}
return req.respondN(accountIds, sortMailboxesMap(mailboxesByAccountId), sessionState, MailboxResponseObjectType, state)
} else {
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, req.session, req.ctx, logger, req.language())
mailboxesByAccountId, sessionState, state, lang, err := g.jmap.GetAllMailboxes(accountIds, ctx)
if err != nil {
return req.jmapErrorN(accountIds, err, sessionState, lang)
}
@@ -163,12 +165,13 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
}
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)).Str("role", role))
ctx := req.ctx.WithLogger(logger)
filter := jmap.MailboxFilterCondition{
Role: role,
}
mailboxesByAccountId, sessionState, state, lang, jerr := g.jmap.SearchMailboxes(accountIds, req.session, req.ctx, logger, req.language(), filter)
mailboxesByAccountId, sessionState, state, lang, jerr := g.jmap.SearchMailboxes(accountIds, filter, ctx)
if jerr != nil {
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
}
@@ -202,6 +205,7 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
// As for Emails and Contacts, one would expect this request without any prior state to
// be usable to list all the objects that currently exist, but that is not the case for
@@ -216,7 +220,7 @@ func (g *Groupware) GetMailboxChanges(w http.ResponseWriter, r *http.Request) {
)))
}
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, req.session, req.ctx, logger, req.language(), sinceState, maxChanges)
changes, sessionState, state, lang, jerr := g.jmap.GetMailboxChanges(accountId, sinceState, maxChanges, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -254,9 +258,10 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
}
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
sinceStateMap := structs.MapValues(sinceStateStrMap, toState)
changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language(), sinceStateMap, maxChanges)
changesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxChangesForMultipleAccounts(allAccountIds, sinceStateMap, maxChanges, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
@@ -273,8 +278,9 @@ func (g *Groupware) GetMailboxRoles(w http.ResponseWriter, r *http.Request) {
allAccountIds := req.AllAccountIds()
l.Array(logAccountId, log.SafeStringArray(allAccountIds))
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, req.session, req.ctx, logger, req.language())
rolesByAccountId, sessionState, state, lang, jerr := g.jmap.GetMailboxRolesForMultipleAccounts(allAccountIds, ctx)
if jerr != nil {
return req.jmapErrorN(allAccountIds, jerr, sessionState, lang)
}
@@ -305,8 +311,9 @@ func (g *Groupware) UpdateMailbox(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
updated, sessionState, state, lang, jerr := g.jmap.UpdateMailbox(accountId, req.session, req.ctx, logger, req.language(), mailboxId, "", body)
updated, sessionState, state, lang, jerr := g.jmap.UpdateMailbox(accountId, mailboxId, body, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -330,8 +337,9 @@ func (g *Groupware) CreateMailbox(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
created, sessionState, state, lang, jerr := g.jmap.CreateMailbox(accountId, req.session, req.ctx, logger, req.language(), "", body)
created, sessionState, state, lang, jerr := g.jmap.CreateMailbox(accountId, body, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -365,8 +373,9 @@ func (g *Groupware) DeleteMailbox(w http.ResponseWriter, r *http.Request) {
}
logger := log.From(l)
ctx := req.ctx.WithLogger(logger)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteMailboxes(accountId, req.session, req.ctx, logger, req.language(), "", mailboxIds)
deleted, sessionState, state, lang, jerr := g.jmap.DeleteMailboxes(accountId, mailboxIds, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -120,8 +120,11 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON
}
logger := log.From(l)
objs, sessionState, state, lang, jerr := g.jmap.GetObjects(accountId, req.session, req.ctx, logger, req.language(),
mailboxIds, emailIds, addressbookIds, contactIds, calendarIds, eventIds, quotaIds, identityIds, emailSubmissionIds)
ctx := req.ctx.WithLogger(logger)
objs, sessionState, state, lang, jerr := g.jmap.GetObjects(accountId,
mailboxIds, emailIds, addressbookIds, contactIds, calendarIds, eventIds, quotaIds, identityIds, emailSubmissionIds,
ctx,
)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -19,8 +19,9 @@ func (g *Groupware) GetQuota(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
ctx := req.ctx.WithLogger(logger)
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(single(accountId), req.session, req.ctx, logger, req.language())
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(single(accountId), ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -47,8 +48,9 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
return req.noopN(accountIds) // user has no accounts
}
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(accountIds)))
ctx := req.ctx.WithLogger(logger)
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(accountIds, req.session, req.ctx, logger, req.language())
res, sessionState, state, lang, jerr := g.jmap.GetQuotas(accountIds, ctx)
if jerr != nil {
return req.jmapErrorN(accountIds, jerr, sessionState, lang)
}

View File

@@ -20,8 +20,9 @@ func (g *Groupware) GetVacation(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
ctx := req.ctx.WithLogger(logger)
res, sessionState, state, lang, jerr := g.jmap.GetVacationResponse(accountId, req.session, req.ctx, logger, req.language())
res, sessionState, state, lang, jerr := g.jmap.GetVacationResponse(accountId, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}
@@ -40,14 +41,15 @@ func (g *Groupware) SetVacation(w http.ResponseWriter, r *http.Request) {
return req.error(accountId, err)
}
logger := log.From(req.logger.With().Str(logAccountId, accountId))
ctx := req.ctx.WithLogger(logger)
var body jmap.VacationResponsePayload
var body jmap.VacationResponseChange
err = req.body(&body)
if err != nil {
return req.error(accountId, err)
}
res, sessionState, state, lang, jerr := g.jmap.SetVacationResponse(accountId, body, req.session, req.ctx, logger, req.language())
res, sessionState, state, lang, jerr := g.jmap.SetVacationResponse(accountId, body, ctx)
if jerr != nil {
return req.jmapError(accountId, jerr, sessionState, lang)
}

View File

@@ -632,7 +632,7 @@ func errorId(r *http.Request, ctx context.Context) string {
}
func (r *Request) errorId() string {
return errorId(r.r, r.ctx)
return errorId(r.r, r.cotx)
}
func apiError(id string, gwerr GroupwareError, options ...ErrorOpt) *Error {

View File

@@ -582,6 +582,15 @@ func (g *Groupware) serveError(w http.ResponseWriter, r *http.Request, error *Er
}
}
func newContext(session *jmap.Session, cotx context.Context, logger *log.Logger, acceptLanguage string) jmap.Context {
return jmap.Context{
Session: session,
Context: cotx,
Logger: logger,
AcceptLanguage: acceptLanguage,
}
}
// Execute a closure with a JMAP Session.
//
// Returns
@@ -589,23 +598,23 @@ func (g *Groupware) serveError(w http.ResponseWriter, r *http.Request, error *Er
// - if an error occurs, after which timestamp a retry is allowed
// - whether the request was sent to the server or not
func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler func(r Request) Response) (Response, time.Time, bool) {
ctx := r.Context()
sl := g.logger.SubloggerWithRequestID(ctx)
cotx := r.Context()
sl := g.logger.SubloggerWithRequestID(cotx)
logger := &sl
// retrieve the current user from the inbound request
var user user
{
var err error
user, err = g.userProvider.GetUser(r, ctx, logger)
user, err = g.userProvider.GetUser(r, cotx, logger)
if err != nil {
g.metrics.AuthenticationFailureCounter.Inc()
g.serveError(w, r, apiError(errorId(r, ctx), ErrorInvalidAuthentication), time.Time{})
g.serveError(w, r, apiError(errorId(r, cotx), ErrorInvalidAuthentication), time.Time{})
return Response{}, time.Time{}, false
}
if user == nil {
g.metrics.AuthenticationFailureCounter.Inc()
g.serveError(w, r, apiError(errorId(r, ctx), ErrorMissingAuthentication), time.Time{})
g.serveError(w, r, apiError(errorId(r, cotx), ErrorMissingAuthentication), time.Time{})
return Response{}, time.Time{}, false
}
@@ -615,10 +624,10 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler
// retrieve a JMAP Session for that user
var session jmap.Session
{
s, ok, gwerr, retryAfter := g.session(ctx, user, logger)
s, ok, gwerr, retryAfter := g.session(cotx, user, logger)
if gwerr != nil {
g.metrics.SessionFailureCounter.Inc()
errorId := errorId(r, ctx)
errorId := errorId(r, cotx)
logger.Error().Str("code", gwerr.Code).Str("error", gwerr.Title).Str("detail", gwerr.Detail).Str(logErrorId, errorId).Msg("failed to determine JMAP session")
g.serveError(w, r, apiError(errorId, *gwerr), retryAfter)
return Response{}, retryAfter, false
@@ -628,7 +637,7 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler
} else {
// no session = authentication failed
g.metrics.SessionFailureCounter.Inc()
errorId := errorId(r, ctx)
errorId := errorId(r, cotx)
logger.Error().Str(logErrorId, errorId).Msg("could not authenticate, failed to find Session")
gwerr = &ErrorInvalidAuthentication
g.serveError(w, r, apiError(errorId, *gwerr), retryAfter)
@@ -638,11 +647,16 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler
decoratedLogger := decorateLogger(logger, session)
language := r.Header.Get("Accept-Language")
ctx := newContext(&session, cotx, decoratedLogger, language)
// build the Request object
req := Request{
g: g,
user: user,
r: r,
cotx: cotx,
ctx: ctx,
logger: decoratedLogger,
session: &session,
@@ -735,32 +749,32 @@ func (g *Groupware) respond(w http.ResponseWriter, r *http.Request, handler func
}
func (g *Groupware) stream(w http.ResponseWriter, r *http.Request, handler func(r Request, w http.ResponseWriter) *Error) {
ctx := r.Context()
sl := g.logger.SubloggerWithRequestID(ctx)
cotx := r.Context()
sl := g.logger.SubloggerWithRequestID(cotx)
logger := &sl
user, err := g.userProvider.GetUser(r, ctx, logger)
user, err := g.userProvider.GetUser(r, cotx, logger)
if err != nil {
g.serveError(w, r, apiError(errorId(r, ctx), ErrorInvalidAuthentication), time.Time{})
g.serveError(w, r, apiError(errorId(r, cotx), ErrorInvalidAuthentication), time.Time{})
return
}
if user == nil {
g.serveError(w, r, apiError(errorId(r, ctx), ErrorMissingAuthentication), time.Time{})
g.serveError(w, r, apiError(errorId(r, cotx), ErrorMissingAuthentication), time.Time{})
return
}
logger = log.From(logger.With().Str(logUserId, log.SafeString(user.GetId())))
session, ok, gwerr, retryAfter := g.session(ctx, user, logger)
session, ok, gwerr, retryAfter := g.session(cotx, user, logger)
if gwerr != nil {
errorId := errorId(r, ctx)
errorId := errorId(r, cotx)
logger.Error().Str("code", gwerr.Code).Str("error", gwerr.Title).Str("detail", gwerr.Detail).Str(logErrorId, errorId).Msg("failed to determine JMAP session")
g.serveError(w, r, apiError(errorId, *gwerr), retryAfter)
return
}
if !ok {
// no session = authentication failed
errorId := errorId(r, ctx)
errorId := errorId(r, cotx)
logger.Error().Str(logErrorId, errorId).Msg("could not authenticate, failed to find Session")
gwerr = &ErrorInvalidAuthentication
g.serveError(w, r, apiError(errorId, *gwerr), retryAfter)
@@ -769,13 +783,18 @@ func (g *Groupware) stream(w http.ResponseWriter, r *http.Request, handler func(
decoratedLogger := decorateLogger(logger, session)
language := r.Header.Get("Accept-Language")
ctx := newContext(&session, cotx, decoratedLogger, language)
req := Request{
g: g,
user: user,
r: r,
ctx: ctx,
cotx: cotx,
logger: decoratedLogger,
session: &session,
ctx: ctx,
}
apierr := handler(req, w)

View File

@@ -39,9 +39,10 @@ type Request struct {
g *Groupware
user user
r *http.Request
ctx context.Context
cotx context.Context
logger *log.Logger
session *jmap.Session
ctx jmap.Context
}
func isDefaultAccountId(accountId string) bool {
@@ -57,11 +58,11 @@ func (r *Request) GetUser() user {
}
func (r *Request) GetRequestId() string {
return chimiddleware.GetReqID(r.ctx)
return chimiddleware.GetReqID(r.cotx)
}
func (r *Request) GetTraceId() string {
return groupwaremiddleware.GetTraceID(r.ctx)
return groupwaremiddleware.GetTraceID(r.cotx)
}
var (

View File

@@ -1,7 +1,6 @@
package groupware
import (
"context"
"fmt"
"net/http"
"net/url"
@@ -12,8 +11,8 @@ import (
func TestParseSort(t *testing.T) {
req := Request{
r: &http.Request{},
ctx: context.Background(),
r: &http.Request{},
cotx: t.Context(),
}
require := require.New(t)
{
@@ -84,7 +83,7 @@ func TestParseMap(t *testing.T) {
{
u, err := url.Parse(tt.uri)
require.NoError(err)
req = Request{r: &http.Request{URL: u}, ctx: context.Background()}
req = Request{r: &http.Request{URL: u}, cotx: t.Context()}
}
res, ok, err := req.parseMapParam("map")
require.Nil(err)