mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-07-13 00:52:01 -04:00
groupware: add tracking of backend call durations
* add new configuration setting GROUPWARE_SEND_DURATIONS_RESPONSE (defaults to false) * keep track of lists of durations of backend calls * when enabled, report them as response headers Durations (human readable) and Durations-Nanos (as raw nanosecond values for machine consumption)
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -143,6 +143,8 @@
|
||||
"GROUPWARE_JMAP_MASTER_USERNAME": "master",
|
||||
"GROUPWARE_JMAP_MASTER_PASSWORD": "admin",
|
||||
|
||||
"GROUPWARE_SEND_DURATIONS_RESPONSE": "true",
|
||||
|
||||
"AUTHAPI_HTTP_ADDR": "0.0.0.0:10000",
|
||||
"AUTHAPI_AUTH_REQUIRE_SHARED_SECRET": "true",
|
||||
"AUTHAPI_AUTH_SHARED_SECRETS": "stalwart=maethaR9eiXaiph8ahn8ohH6dahPiequ;unused=eeyaigh6hae1zo5ahGeete6oohaiquei",
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
@@ -68,6 +69,7 @@ type ResultMetadata interface {
|
||||
GetSessionState() SessionState
|
||||
GetState() State
|
||||
GetLanguage() Language
|
||||
GetDurations() []time.Duration
|
||||
}
|
||||
|
||||
type Result[T any] struct {
|
||||
@@ -75,21 +77,22 @@ type Result[T any] struct {
|
||||
SessionState SessionState
|
||||
State State
|
||||
Language Language
|
||||
Durations []time.Duration
|
||||
}
|
||||
|
||||
func RefineResultPayload[A, B any](a Result[A], refiner func(A) (B, bool, error)) (Result[B], error) {
|
||||
if payloads, ok, err := refiner(a.Payload); err != nil {
|
||||
return ZeroResult[B](), err
|
||||
return ZeroResult[B](a.Durations), err
|
||||
} else if ok {
|
||||
return newResult(payloads, a.SessionState, a.State, a.Language), nil
|
||||
return NewResult(payloads, a.SessionState, a.State, a.Language, a.Durations), nil
|
||||
} else {
|
||||
return ZeroResult[B](), nil
|
||||
return ZeroResult[B](a.Durations), nil
|
||||
}
|
||||
}
|
||||
|
||||
func RefineResult[A, B any](a Result[A], refiner func(A, SessionState, State, Language) (B, SessionState, State, Language)) Result[B] {
|
||||
b, bss, bs, bl := refiner(a.Payload, a.SessionState, a.State, a.Language)
|
||||
return newResult(b, bss, bs, bl)
|
||||
return NewResult(b, bss, bs, bl, a.Durations)
|
||||
}
|
||||
|
||||
func RefineResultSlice[A, B any](a []*Result[A], refiner func([]*A, []*SessionState, []*State, []*Language) (B, SessionState, State, Language, error)) (Result[B], error) {
|
||||
@@ -121,38 +124,52 @@ func RefineResultSlice[A, B any](a []*Result[A], refiner func([]*A, []*SessionSt
|
||||
return nil
|
||||
}
|
||||
})
|
||||
durations := structs.Flatten(structs.Map(a, func(e *Result[A]) []time.Duration {
|
||||
return e.Durations
|
||||
}))
|
||||
b, bss, bs, bl, err := refiner(payloads, sessionStates, states, languages)
|
||||
return newResult(b, bss, bs, bl), err
|
||||
return NewResult(b, bss, bs, bl, durations), err
|
||||
}
|
||||
|
||||
func (r Result[T]) GetSessionState() SessionState {
|
||||
return r.SessionState
|
||||
}
|
||||
|
||||
func (r Result[T]) GetState() State {
|
||||
return r.State
|
||||
}
|
||||
|
||||
func (r Result[T]) GetLanguage() Language {
|
||||
return r.Language
|
||||
}
|
||||
func (r Result[T]) GetDurations() []time.Duration {
|
||||
return r.Durations
|
||||
}
|
||||
|
||||
func newResult[T any](result T, sessionState SessionState, state State, language Language) Result[T] {
|
||||
func NewResult[T any](payload T, sessionState SessionState, state State, language Language, durations []time.Duration) Result[T] {
|
||||
return Result[T]{
|
||||
Payload: result,
|
||||
Payload: payload,
|
||||
SessionState: sessionState,
|
||||
State: state,
|
||||
Language: language,
|
||||
Durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func newPartialResult[T any](sessionState SessionState, language Language) Result[T] {
|
||||
func newPartialResult[T any](sessionState SessionState, language Language, durations []time.Duration) Result[T] {
|
||||
return Result[T]{
|
||||
SessionState: sessionState,
|
||||
Language: language,
|
||||
Durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func ZeroResult[T any]() Result[T] {
|
||||
return Result[T]{}
|
||||
func ZeroResult[T any](durations []time.Duration) Result[T] {
|
||||
return Result[T]{Durations: durations}
|
||||
}
|
||||
|
||||
func ZeroResultV[T any]() Result[T] {
|
||||
return Result[T]{Durations: nil}
|
||||
}
|
||||
|
||||
func ZeroResultM[T any](t Result[T]) Result[T] {
|
||||
return Result[T]{Durations: t.GetDurations()}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func (j *Client) GetBlobMetadata(accountId AccountId, ids []string, ctx Context)
|
||||
invocation(get, "0"),
|
||||
)
|
||||
if jerr != nil {
|
||||
return ZeroResult[BlobGetResponse](), jerr
|
||||
return ZeroResultV[BlobGetResponse](), jerr
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (BlobGetResponse, State, Error) {
|
||||
@@ -92,7 +92,7 @@ func (j *Client) UploadBlob(accountId AccountId, data []byte, contentType string
|
||||
invocation(getHash, "1"),
|
||||
)
|
||||
if jerr != nil {
|
||||
return ZeroResult[UploadedBlobWithHash](), jerr
|
||||
return ZeroResultV[UploadedBlobWithHash](), jerr
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (UploadedBlobWithHash, State, Error) {
|
||||
|
||||
@@ -25,7 +25,7 @@ func (j *Client) GetBootstrap(accountIds []AccountId, ctx Context) (Result[map[A
|
||||
|
||||
cmd, err := j.request(ctx, NS_MAIL_QUOTA, calls...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId]AccountBootstrapResult](), err
|
||||
return ZeroResultV[map[AccountId]AccountBootstrapResult](), err
|
||||
}
|
||||
return command(j, ctx, cmd, func(body *Response) (map[AccountId]AccountBootstrapResult, State, Error) {
|
||||
identityPerAccount := map[AccountId][]Identity{}
|
||||
|
||||
@@ -10,7 +10,7 @@ func (j *Client) ParseICalendarBlob(accountId AccountId, blobIds []string, ctx C
|
||||
invocation(parse, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[CalendarEventParseResponse](), err
|
||||
return ZeroResultV[CalendarEventParseResponse](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (CalendarEventParseResponse, State, Error) {
|
||||
|
||||
@@ -107,7 +107,7 @@ func (j *Client) GetChanges(accountId AccountId, stateMap StateMap, maxChanges u
|
||||
|
||||
cmd, err := j.request(ctx, NS_CHANGES, methodCalls...)
|
||||
if err != nil {
|
||||
return ZeroResult[ObjectChanges](), err
|
||||
return ZeroResultV[ObjectChanges](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (ObjectChanges, State, Error) {
|
||||
|
||||
@@ -51,7 +51,7 @@ func (j *Client) GetEmails(accountId AccountId, ids []string, //NOSONAR
|
||||
|
||||
cmd, err := j.request(ctx, NS_MAIL, methodCalls...)
|
||||
if err != nil {
|
||||
return ZeroResult[EmailGetResponse](), err
|
||||
return ZeroResultV[EmailGetResponse](), err
|
||||
}
|
||||
return command(j, ctx, cmd, func(body *Response) (EmailGetResponse, State, Error) {
|
||||
if markAsSeen {
|
||||
@@ -89,7 +89,7 @@ func (j *Client) GetEmailBlobId(accountId AccountId, id string, ctx Context) (Re
|
||||
get := EmailGetCommand{AccountId: accountId, Ids: []string{id}, FetchAllBodyValues: false, Properties: []string{"blobId"}}
|
||||
cmd, err := j.request(ctx, NS_MAIL, invocation(get, "0"))
|
||||
if err != nil {
|
||||
return ZeroResult[string](), err
|
||||
return ZeroResultV[string](), err
|
||||
}
|
||||
return command(j, ctx, cmd, func(body *Response) (string, State, Error) {
|
||||
var response EmailGetResponse
|
||||
@@ -172,7 +172,7 @@ func (j *Client) GetAllEmailsInMailbox(accountId AccountId, mailboxId string, //
|
||||
|
||||
cmd, err := j.request(ctx, NS_MAIL, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[*EmailSearchResults](), err
|
||||
return ZeroResultV[*EmailSearchResults](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (*EmailSearchResults, State, Error) {
|
||||
@@ -259,7 +259,7 @@ func (j *Client) GetEmailChanges(accountId AccountId,
|
||||
invocation(getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[EmailChanges](), err
|
||||
return ZeroResultV[EmailChanges](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (EmailChanges, State, Error) {
|
||||
@@ -358,7 +358,7 @@ func (j *Client) QueryEmailSnippets(accountIds []AccountId, //NOSONAR
|
||||
|
||||
cmd, err := j.request(ctx, NS_MAIL, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId]EmailSnippetSearchResults](), err
|
||||
return ZeroResultV[map[AccountId]EmailSnippetSearchResults](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (map[AccountId]EmailSnippetSearchResults, State, Error) {
|
||||
@@ -470,7 +470,7 @@ func (j *Client) QueryEmails(accountIds []AccountId,
|
||||
|
||||
cmd, err := j.request(ctx, NS_MAIL, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId]EmailSearchResults](), err
|
||||
return ZeroResultV[map[AccountId]EmailSearchResults](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (map[AccountId]EmailSearchResults, State, Error) {
|
||||
@@ -565,7 +565,7 @@ func (j *Client) QueryEmailsWithSnippets(accountIds []AccountId, //NOSONAR
|
||||
|
||||
cmd, err := j.request(ctx, NS_MAIL, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId]EmailQueryWithSnippetsResult](), err
|
||||
return ZeroResultV[map[AccountId]EmailQueryWithSnippetsResult](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (map[AccountId]EmailQueryWithSnippetsResult, State, Error) {
|
||||
@@ -659,7 +659,7 @@ func (j *Client) ImportEmail(accountId AccountId, data []byte, ctx Context) (Res
|
||||
invocation(getHash, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[UploadedEmail](), err
|
||||
return ZeroResultV[UploadedEmail](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (UploadedEmail, State, Error) {
|
||||
@@ -717,7 +717,7 @@ func (j *Client) CreateEmail(accountId AccountId, email EmailChange, replaceId s
|
||||
invocation(set, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[*Email](), err
|
||||
return ZeroResultV[*Email](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (*Email, State, Error) {
|
||||
@@ -764,7 +764,7 @@ func (j *Client) UpdateEmails(accountId AccountId, updates map[string]PatchObjec
|
||||
}
|
||||
cmd, err := j.request(ctx, NS_MAIL, invocation(set, "0"))
|
||||
if err != nil {
|
||||
return ZeroResult[map[string]*Email](), err
|
||||
return ZeroResultV[map[string]*Email](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (map[string]*Email, State, Error) {
|
||||
@@ -878,7 +878,7 @@ func (j *Client) SubmitEmail(accountId AccountId, identityId string, emailId str
|
||||
invocation(get, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[EmailSubmission](), err
|
||||
return ZeroResultV[EmailSubmission](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (EmailSubmission, State, Error) {
|
||||
@@ -936,7 +936,7 @@ func (j *Client) GetEmailSubmissionStatus(accountId AccountId, submissionIds []s
|
||||
}
|
||||
cmd, err := j.request(ctx, NS_MAIL_SUBMISSION, invocation(get, "0"))
|
||||
if err != nil {
|
||||
return ZeroResult[EmailSubmissionGetResponse](), err
|
||||
return ZeroResultV[EmailSubmissionGetResponse](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (EmailSubmissionGetResponse, State, Error) {
|
||||
@@ -978,7 +978,7 @@ func (j *Client) EmailsInThread(accountId AccountId, threadId string,
|
||||
invocation(get, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[[]Email](), err
|
||||
return ZeroResultV[[]Email](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) ([]Email, State, Error) {
|
||||
@@ -1067,7 +1067,7 @@ func (j *Client) QueryEmailSummaries(accountIds []AccountId, //NOSONAR
|
||||
}
|
||||
cmd, err := j.request(ctx, NS_MAIL, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId]EmailsSummary](), err
|
||||
return ZeroResultV[map[AccountId]EmailsSummary](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (map[AccountId]EmailsSummary, State, Error) {
|
||||
|
||||
@@ -53,7 +53,7 @@ func (j *Client) GetIdentitiesAndMailboxes(mailboxAccountId AccountId, accountId
|
||||
|
||||
cmd, err := j.request(ctx, NS_IDENTITY, calls...)
|
||||
if err != nil {
|
||||
return ZeroResult[IdentitiesAndMailboxesGetResponse](), err
|
||||
return ZeroResultV[IdentitiesAndMailboxesGetResponse](), err
|
||||
}
|
||||
return command(j, ctx, cmd, func(body *Response) (IdentitiesAndMailboxesGetResponse, State, Error) {
|
||||
identities := make(map[AccountId][]Identity, len(uniqueAccountIds))
|
||||
|
||||
@@ -52,7 +52,7 @@ func (j *Client) SearchMailboxes(accountIds []AccountId, filter MailboxFilterEle
|
||||
}
|
||||
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId][]Mailbox](), err
|
||||
return ZeroResultV[map[AccountId][]Mailbox](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (map[AccountId][]Mailbox, State, Error) {
|
||||
@@ -86,7 +86,7 @@ func (j *Client) SearchMailboxIdsPerRole(accountIds []AccountId, roles []string,
|
||||
}
|
||||
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId]map[string]string](), err
|
||||
return ZeroResultV[map[AccountId]map[string]string](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (map[AccountId]map[string]string, State, Error) {
|
||||
@@ -210,7 +210,7 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []AccountId, ctx Con
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return ZeroResult[map[AccountId]string](), nil
|
||||
return ZeroResultV[map[AccountId]string](), nil
|
||||
}
|
||||
|
||||
invocations := make([]Invocation, n*2)
|
||||
@@ -225,7 +225,7 @@ func (j *Client) GetInboxNameForMultipleAccounts(accountIds []AccountId, ctx Con
|
||||
|
||||
cmd, err := j.request(ctx, NS_MAILBOX, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId]string](), err
|
||||
return ZeroResultV[map[AccountId]string](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (map[AccountId]string, State, Error) {
|
||||
|
||||
@@ -90,7 +90,7 @@ func (j *Client) GetObjects(accountId AccountId, //NOSONAR
|
||||
|
||||
cmd, err := j.request(ctx, NS_OBJECTS, methodCalls...)
|
||||
if err != nil {
|
||||
return ZeroResult[Objects](), err
|
||||
return ZeroResultV[Objects](), err
|
||||
}
|
||||
|
||||
return command(j, ctx, cmd, func(body *Response) (Objects, State, Error) {
|
||||
|
||||
@@ -92,7 +92,7 @@ func (j *Client) SetVacationResponse(accountId AccountId, vacation VacationRespo
|
||||
invocation(get, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[VacationResponse](), err
|
||||
return ZeroResultV[VacationResponse](), err
|
||||
}
|
||||
return command(j, ctx, cmd, func(body *Response) (VacationResponse, State, Error) {
|
||||
var setResponse VacationResponseSetResponse
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SessionEventListener interface {
|
||||
@@ -60,6 +61,7 @@ var _ ResultMetadata = Session{}
|
||||
func (s Session) GetSessionState() SessionState { return s.State }
|
||||
func (s Session) GetState() State { return EmptyState }
|
||||
func (s Session) GetLanguage() Language { return NoLanguage }
|
||||
func (s Session) GetDurations() []time.Duration { return nil }
|
||||
|
||||
var (
|
||||
invalidSessionResponseErrorMissingUsername = jmapError(errors.New("JMAP session response does not provide a username"), JmapErrorInvalidSessionResponse)
|
||||
|
||||
@@ -19,7 +19,7 @@ func get[T Foo, GETREQ GetCommand[T], GETRESP GetResponse[T], ID any, RESP any](
|
||||
get := getCommandFactory(accountId, ids)
|
||||
cmd, err := client.request(ctx, objType.Namespaces, invocation(get, "0"))
|
||||
if err != nil {
|
||||
return ZeroResult[RESP](), err
|
||||
return ZeroResultV[RESP](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
|
||||
@@ -70,7 +70,7 @@ func getN[T Foo, ITEM any, GETREQ GetCommand[T], GETRESP GetResponse[T], ID any,
|
||||
|
||||
cmd, err := client.request(ctx, objType.Namespaces, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[RESP](), err
|
||||
return ZeroResultV[RESP](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
|
||||
@@ -109,7 +109,7 @@ func create[T Foo, C any, SETREQ SetCommand[T], GETREQ GetCommand[T], SETRESP Se
|
||||
invocation(get, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[*T](), err
|
||||
return ZeroResultV[*T](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (*T, State, Error) {
|
||||
@@ -162,7 +162,7 @@ func destroy[T Foo, REQ SetCommand[T], RESP SetResponse[T]](client *Client, name
|
||||
invocation(set, "0"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[map[string]SetError](), err
|
||||
return ZeroResultV[map[string]SetError](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (map[string]SetError, State, Error) {
|
||||
@@ -210,7 +210,7 @@ func changes[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
|
||||
invocation(getUpdated, "2"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[RESP](), err
|
||||
return ZeroResultV[RESP](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
|
||||
@@ -268,7 +268,7 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
|
||||
uniqueAccountIds := structs.Uniq(accountIds)
|
||||
n := len(uniqueAccountIds)
|
||||
if n < 1 {
|
||||
return ZeroResult[RESP](), nil
|
||||
return ZeroResultV[RESP](), nil
|
||||
}
|
||||
|
||||
invocations := make([]Invocation, n*3)
|
||||
@@ -299,7 +299,7 @@ func changesN[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGES
|
||||
|
||||
cmd, err := client.request(ctx, objType.Namespaces, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[RESP](), err
|
||||
return ZeroResultV[RESP](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
|
||||
@@ -354,7 +354,7 @@ func updates[T Foo, CHANGESREQ ChangesCommand[T], GETREQ GetCommand[T], CHANGESR
|
||||
invocation(getUpdated, "1"),
|
||||
)
|
||||
if err != nil {
|
||||
return ZeroResult[RESP](), err
|
||||
return ZeroResultV[RESP](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
|
||||
@@ -395,14 +395,14 @@ func update[T Foo, CHANGES Change, SET SetCommand[T], GET GetCommand[T], RESP an
|
||||
{
|
||||
patch, err := changes.AsPatch()
|
||||
if err != nil {
|
||||
return ZeroResult[RESP](), jmapError(err, JmapErrorPatchObjectSerialization)
|
||||
return ZeroResultV[RESP](), jmapError(err, JmapErrorPatchObjectSerialization)
|
||||
}
|
||||
update = setCommandFactory(map[string]PatchObject{id: patch})
|
||||
}
|
||||
get := getCommandFactory(id)
|
||||
cmd, err := client.request(ctx, objType.Namespaces, invocation(update, "0"), invocation(get, "1"))
|
||||
if err != nil {
|
||||
return ZeroResult[RESP](), err
|
||||
return ZeroResultV[RESP](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (RESP, State, Error) {
|
||||
@@ -525,7 +525,7 @@ func queryN[T Foo, FILTER any, SORT any, QUERY QueryCommand[T, QUERY], GET GetCo
|
||||
|
||||
cmd, err := client.request(ctx, objType.Namespaces, invocations...)
|
||||
if err != nil {
|
||||
return ZeroResult[map[AccountId]*RESP](), err
|
||||
return ZeroResultV[map[AccountId]*RESP](), err
|
||||
}
|
||||
|
||||
return command(client, ctx, cmd, func(body *Response) (map[AccountId]*RESP, State, Error) {
|
||||
|
||||
@@ -14,6 +14,10 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/jscalendar"
|
||||
)
|
||||
|
||||
func single[S any](s S) []S {
|
||||
return []S{s}
|
||||
}
|
||||
|
||||
var UintPtrOne *uint = uintPtr(1)
|
||||
var UintPtrZero *uint = uintPtr(0)
|
||||
|
||||
@@ -65,18 +69,18 @@ func command[T any](client Cmdr, //NOSONAR
|
||||
|
||||
logger := ctx.Logger
|
||||
|
||||
before := time.Now()
|
||||
responseBody, language, jmapErr := client.Api().Command(request, ctx)
|
||||
duration := time.Since(before)
|
||||
if jmapErr != nil {
|
||||
var zero Result[T]
|
||||
return zero, jmapErr
|
||||
return ZeroResult[T](single(duration)), jmapErr
|
||||
}
|
||||
|
||||
var response Response
|
||||
err := json.Unmarshal(responseBody, &response)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("failed to deserialize body JSON payload into a %T", response)
|
||||
var zero Result[T]
|
||||
return zero, jmapError(err, JmapErrorDecodingResponseBody)
|
||||
return ZeroResult[T](single(duration)), jmapError(err, JmapErrorDecodingResponseBody)
|
||||
}
|
||||
|
||||
if response.SessionState != ctx.Session.State {
|
||||
@@ -124,20 +128,20 @@ func command[T any](client Cmdr, //NOSONAR
|
||||
msg := fmt.Sprintf("found method level error in response '%v', type: '%v', description: '%v'", mr.Tag, errorParameters.Type, errorParameters.Description)
|
||||
err = errors.New(msg)
|
||||
logger.Warn().Int("code", code).Str("type", errorParameters.Type).Msg(msg)
|
||||
return newPartialResult[T](response.SessionState, language), jmapResponseError(code, err, errorParameters.Type, errorParameters.Description)
|
||||
return newPartialResult[T](response.SessionState, language, single(duration)), jmapResponseError(code, err, errorParameters.Type, errorParameters.Description)
|
||||
} else {
|
||||
code := JmapErrorUnspecifiedType
|
||||
msg := fmt.Sprintf("found method level error in response '%v'", mr.Tag)
|
||||
err := errors.New(msg)
|
||||
logger.Warn().Int("code", code).Msg(msg)
|
||||
return newPartialResult[T](response.SessionState, language), jmapResponseError(code, err, errorParameters.Type, errorParameters.Description)
|
||||
return newPartialResult[T](response.SessionState, language, single(duration)), jmapResponseError(code, err, errorParameters.Type, errorParameters.Description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result, state, jerr := mapper(&response)
|
||||
sessionState := response.SessionState
|
||||
return newResult(result, sessionState, state, language), jerr
|
||||
return NewResult(result, sessionState, state, language, single(duration)), jerr
|
||||
}
|
||||
|
||||
func mapstructStringToTimeHook() mapstructure.DecodeHookFunc {
|
||||
|
||||
@@ -373,3 +373,23 @@ func First[T any](values []T, predicate func(T) bool) (T, bool) {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func Reduce[T any](initialValue T, s []T, reducer func(a, b T) T) T {
|
||||
reduced := initialValue
|
||||
for _, value := range s {
|
||||
reduced = reducer(reduced, value)
|
||||
}
|
||||
return reduced
|
||||
}
|
||||
|
||||
func Flatten[T any](s [][]T) []T {
|
||||
l := 0
|
||||
for _, r := range s {
|
||||
l += len(r)
|
||||
}
|
||||
result := make([]T, 0, l)
|
||||
for _, r := range s {
|
||||
result = append(result, r...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -275,3 +275,37 @@ func TestSet(t *testing.T) {
|
||||
assert.True(t, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReduce(t *testing.T) {
|
||||
{
|
||||
result := Reduce(0, []int{1, 2, 3}, func(a, b int) int { return a + b })
|
||||
assert.Equal(t, 6, result)
|
||||
}
|
||||
{
|
||||
result := Reduce(0, []int{}, func(a, b int) int { return a + b })
|
||||
assert.Equal(t, 0, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
{
|
||||
result := Flatten([][]int{{1, 2, 3}, {4}, {5, 6, 7}, {}, {8}})
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8}, result)
|
||||
}
|
||||
{
|
||||
result := Flatten([][]int{})
|
||||
assert.Equal(t, []int{}, result)
|
||||
}
|
||||
{
|
||||
result := Flatten([][]int{nil, nil})
|
||||
assert.Equal(t, []int{}, result)
|
||||
}
|
||||
{
|
||||
result := Flatten([][]int{nil, {}, {1}})
|
||||
assert.Equal(t, []int{1}, result)
|
||||
}
|
||||
{
|
||||
result := Flatten[int](nil)
|
||||
assert.Equal(t, []int{}, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ func DefaultConfig() *config.Config {
|
||||
AllowedHeaders: []string{"Authorization", "Origin", "Content-Type", "Accept", "X-Requested-With", "X-Request-Id", "Trace-Id", "Cache-Control"},
|
||||
AllowCredentials: true,
|
||||
},
|
||||
OpenCloudPublicURL: "https://localhost:9200/",
|
||||
OpenCloudPublicURL: "https://localhost:9200/",
|
||||
SendDurationsResponse: false,
|
||||
},
|
||||
Service: config.Service{
|
||||
Name: "groupware",
|
||||
|
||||
@@ -12,10 +12,11 @@ type CORS struct {
|
||||
|
||||
// HTTP defines the available http configuration.
|
||||
type HTTP struct {
|
||||
Addr string `yaml:"addr" env:"GROUPWARE_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"1.0.0"`
|
||||
TLS shared.HTTPServiceTLS `yaml:"tls"`
|
||||
Root string `yaml:"root" env:"GROUPWARE_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"1.0.0"`
|
||||
Namespace string `yaml:"-"`
|
||||
CORS CORS `yaml:"cors"`
|
||||
OpenCloudPublicURL string `yaml:"opencloud_public_url" env:"OC_URL;OC_PUBLIC_URL;GROUPWARE_PUBLIC_URL"`
|
||||
Addr string `yaml:"addr" env:"GROUPWARE_HTTP_ADDR" desc:"The bind address of the HTTP service." introductionVersion:"1.0.0"`
|
||||
TLS shared.HTTPServiceTLS `yaml:"tls"`
|
||||
Root string `yaml:"root" env:"GROUPWARE_HTTP_ROOT" desc:"Subdirectory that serves as the root for this HTTP service." introductionVersion:"1.0.0"`
|
||||
Namespace string `yaml:"-"`
|
||||
CORS CORS `yaml:"cors"`
|
||||
OpenCloudPublicURL string `yaml:"opencloud_public_url" env:"OC_URL;OC_PUBLIC_URL;GROUPWARE_PUBLIC_URL"`
|
||||
SendDurationsResponse bool `yaml:"send_durations_response" env:"GROUPWARE_SEND_DURATIONS_RESPONSE"`
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (c *JmapContactCardSupplier) GetAll(accountId jmap.AccountId, ids []string,
|
||||
}
|
||||
func (c *JmapContactCardSupplier) Query(accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, filter jmap.ContactCardFilterElement, sortBy []jmap.ContactCardComparator, calculateTotal bool, ctx jmap.Context) (jmap.Result[map[jmap.AccountId]*jmap.ContactCardSearchResults], error) { //NOSONAR
|
||||
if m, err := mapQueryParams(c.GetId(), accountIds, qps); err != nil {
|
||||
return jmap.ZeroResult[map[jmap.AccountId]*jmap.ContactCardSearchResults](), err
|
||||
return jmap.ZeroResultV[map[jmap.AccountId]*jmap.ContactCardSearchResults](), err
|
||||
} else {
|
||||
return c.client.QueryContactCards(m, limit, filter, sortBy, calculateTotal, ctx)
|
||||
}
|
||||
|
||||
@@ -75,16 +75,17 @@ func (c *MockContactCardSupplier) GetAll(accountId jmap.AccountId, ids []string,
|
||||
if len(ids) > 0 {
|
||||
abooks = structs.Filter(abooks, func(a jmap.AddressBook) bool { return slices.Contains(ids, a.Id) })
|
||||
}
|
||||
return jmap.Result[jmap.AddressBookGetResponse]{
|
||||
SessionState: jmap.EmptySessionState,
|
||||
State: "mock",
|
||||
Language: jmap.NoLanguage,
|
||||
Payload: jmap.AddressBookGetResponse{
|
||||
return jmap.NewResult(
|
||||
jmap.AddressBookGetResponse{
|
||||
AccountId: accountId,
|
||||
State: "mock",
|
||||
List: abooks,
|
||||
},
|
||||
}, nil
|
||||
jmap.EmptySessionState,
|
||||
jmap.State("mock"),
|
||||
jmap.NoLanguage,
|
||||
nil,
|
||||
), nil
|
||||
}
|
||||
func (c *MockContactCardSupplier) Query(accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, filter jmap.ContactCardFilterElement, sortBy []jmap.ContactCardComparator, calculateTotal bool, ctx jmap.Context) (jmap.Result[map[jmap.AccountId]*jmap.ContactCardSearchResults], error) { //NOSONAR
|
||||
payload := make(map[jmap.AccountId]*jmap.ContactCardSearchResults, len(accountIds))
|
||||
@@ -93,7 +94,7 @@ func (c *MockContactCardSupplier) Query(accountIds []jmap.AccountId, qps QueryPa
|
||||
all := []jmap.ContactCard{}
|
||||
var qp jmap.QueryParams
|
||||
if q, ok, err := qps.ForSupplier(c.GetId(), accountId); err != nil {
|
||||
return jmap.ZeroResult[map[jmap.AccountId]*jmap.ContactCardSearchResults](), err
|
||||
return jmap.ZeroResultV[map[jmap.AccountId]*jmap.ContactCardSearchResults](), err
|
||||
} else if ok {
|
||||
qp = q
|
||||
} else {
|
||||
@@ -141,10 +142,5 @@ func (c *MockContactCardSupplier) Query(accountIds []jmap.AccountId, qps QueryPa
|
||||
payload[accountId] = res
|
||||
}
|
||||
|
||||
return jmap.Result[map[jmap.AccountId]*jmap.ContactCardSearchResults]{
|
||||
Payload: payload,
|
||||
SessionState: jmap.EmptySessionState,
|
||||
State: jmap.EmptyState,
|
||||
Language: jmap.NoLanguage,
|
||||
}, nil
|
||||
return jmap.NewResult(payload, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, nil), nil
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func (g *Groupware) GetAccountById(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, account, err := req.GetAccountForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
var body jmap.Account = account
|
||||
return req.respond(accountId, body, AccountResponseObjectType, req.session)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
@@ -32,16 +33,18 @@ func (g *Groupware) UploadBlob(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
accountId, err := req.GetAccountIdForBlob()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
logger := log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
before := time.Now()
|
||||
resp, _, jerr := g.jmap.UploadBlobStream(accountId, contentType, body, ctx)
|
||||
duration := time.Since(before)
|
||||
if jerr != nil {
|
||||
return req.jmapError(accountId, jerr, req.session)
|
||||
}
|
||||
|
||||
return req.respondWithoutStatus(accountId, resp)
|
||||
return req.respondWithoutStatus(accountId, resp, single(duration))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,21 +58,22 @@ func (g *Groupware) DownloadBlob(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
blobId, err := req.PathParam(UriParamBlobId) // the unique identifier of the blob to download
|
||||
if err != nil {
|
||||
return false, req.error(accountId, err)
|
||||
return false, req.errorV(accountId, err)
|
||||
}
|
||||
name, err := req.PathParam(UriParamBlobName) // the filename of the blob to download, which is then used in the response and may be arbitrary if unknown
|
||||
if err != nil {
|
||||
return false, req.error(accountId, err)
|
||||
return false, req.errorV(accountId, err)
|
||||
}
|
||||
typ, _ := req.getStringParam(QueryParamBlobType, "") // optionally, the Content-Type of the blob, which is then used in the response
|
||||
|
||||
logger := log.From(req.logger.With().Str(logAccountId, log.SafeString(accountId)).Str(UriParamBlobId, blobId))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
before := time.Now()
|
||||
if err := req.serveBlob(blobId, name, typ, ctx, accountId, w); err != nil {
|
||||
return false, req.error(accountId, err)
|
||||
return false, req.error(accountId, err, single(time.Since(before)))
|
||||
} else {
|
||||
return true, req.noop(accountId)
|
||||
return true, req.noop(accountId, single(time.Since(before)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,13 +32,13 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
l := req.logger.With()
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
var maxChanges uint = 0
|
||||
if v, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0); err != nil { // The maximum amount of changes to emit for each type of object.
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
maxChanges = v
|
||||
l = l.Uint(QueryParamMaxChanges, v)
|
||||
@@ -72,7 +72,7 @@ func (g *Groupware) GetChanges(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
}
|
||||
//if state, ok := req.getStringParam(QueryParamQuotas, ""); ok { sinceState.Quotas = ptr(toState(state)) }
|
||||
if sinceState.IsZero() {
|
||||
return req.noop(accountId) // No content response if no object IDs were requested.
|
||||
return req.noop(accountId, zeroDurations) // No content response if no object IDs were requested.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ func (g *Groupware) GetAllEmailsInMailbox(w http.ResponseWriter, r *http.Request
|
||||
func(req Request, accountId jmap.AccountId, containerId string, qp jmap.QueryParams, limit *uint, ctx jmap.Context) (jmap.Result[*jmap.EmailSearchResults], error) { //NOSONAR
|
||||
result, jerr := g.jmap.GetAllEmailsInMailbox(accountId, containerId, qp, limit, collapseThreads, fetchBodies, g.config.maxBodyValueBytes, withThreads, ctx)
|
||||
if jerr != nil {
|
||||
return jmap.ZeroResult[*jmap.EmailSearchResults](), jerr
|
||||
return jmap.ZeroResultM(result), jerr
|
||||
}
|
||||
sanitized, err := req.sanitizeEmails(result.Payload.Results)
|
||||
if err != nil {
|
||||
return jmap.ZeroResult[*jmap.EmailSearchResults](), err
|
||||
return jmap.ZeroResultM(result), err
|
||||
}
|
||||
return jmap.RefineResultPayload(result, func(orig *jmap.EmailSearchResults) (*jmap.EmailSearchResults, bool, error) {
|
||||
return &jmap.EmailSearchResults{
|
||||
@@ -70,12 +70,12 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
g.stream(w, r, func(req Request, w http.ResponseWriter) (bool, Response) {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return false, req.error(accountId, err)
|
||||
return false, req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
id, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return false, req.error(accountId, err)
|
||||
return false, req.errorV(accountId, err)
|
||||
}
|
||||
ids := strings.Split(id, ",")
|
||||
if len(ids) != 1 {
|
||||
@@ -84,7 +84,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
|
||||
_, ok, err := req.parseBoolParam(QueryParamMarkAsSeen, false)
|
||||
if err != nil {
|
||||
return false, req.error(accountId, err)
|
||||
return false, req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
return false, req.parameterErrorResponse(accountId, QueryParamMarkAsSeen, fmt.Sprintf("when the Accept header is set to '%s', the API does not support setting %s", accept, QueryParamMarkAsSeen))
|
||||
@@ -97,18 +97,19 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
return false, req.jmapError(accountId, jerr, result)
|
||||
}
|
||||
if result.Payload == "" {
|
||||
return true, req.noop(accountId)
|
||||
return true, req.noop(accountId, result.GetDurations())
|
||||
} else {
|
||||
name := result.Payload + ".eml"
|
||||
typ := accept
|
||||
accountId, gwerr := req.GetAccountIdForBlob()
|
||||
if gwerr != nil {
|
||||
return false, req.error(accountId, gwerr)
|
||||
return false, req.error(accountId, gwerr, result.GetDurations())
|
||||
}
|
||||
before := time.Now()
|
||||
if err := req.serveBlob(result.Payload, name, typ, ctx, accountId, w); err != nil {
|
||||
return false, req.error(accountId, err)
|
||||
return false, req.error(accountId, err, append(result.GetDurations(), time.Since(before)))
|
||||
} else {
|
||||
return true, req.noop(accountId)
|
||||
return true, req.noop(accountId, append(result.GetDurations(), time.Since(before)))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -116,13 +117,13 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
id, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
ids := strings.Split(id, ",")
|
||||
if len(ids) < 1 {
|
||||
@@ -131,7 +132,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
|
||||
markAsSeen, ok, err := req.parseBoolParam(QueryParamMarkAsSeen, false)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Bool(QueryParamMarkAsSeen, markAsSeen)
|
||||
@@ -149,7 +150,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
} else {
|
||||
sanitized, err := req.sanitizeEmail(result.Payload.List[0])
|
||||
if err != nil {
|
||||
return req.error(accountId, req.apiError(err))
|
||||
return req.errorV(accountId, req.apiError(err))
|
||||
}
|
||||
return req.respond(accountId, sanitized, EmailResponseObjectType, result)
|
||||
}
|
||||
@@ -165,7 +166,7 @@ func (g *Groupware) GetEmailsById(w http.ResponseWriter, r *http.Request) { //NO
|
||||
} else {
|
||||
sanitized, err := req.sanitizeEmails(result.Payload.List)
|
||||
if err != nil {
|
||||
return req.error(accountId, req.apiError(err))
|
||||
return req.errorV(accountId, req.apiError(err))
|
||||
}
|
||||
return req.respond(accountId, sanitized, EmailResponseObjectType, result)
|
||||
}
|
||||
@@ -209,7 +210,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
id, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
@@ -222,7 +223,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
return req.notFound(accountId, EmailResponseObjectType, result)
|
||||
}
|
||||
if email, err := req.sanitizeEmail(result.Payload.List[0]); err != nil {
|
||||
return req.error(accountId, req.apiError(err))
|
||||
return req.errorV(accountId, req.apiError(err))
|
||||
} else {
|
||||
var body []jmap.EmailBodyPart = email.Attachments
|
||||
return req.respond(accountId, body, EmailResponseObjectType, result)
|
||||
@@ -241,7 +242,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
id, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return false, req.error(mailAccountId, err)
|
||||
return false, req.errorV(mailAccountId, err)
|
||||
}
|
||||
|
||||
l := req.logger.With().
|
||||
@@ -256,12 +257,12 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
return false, req.jmapError(mailAccountId, jerr, result)
|
||||
}
|
||||
if len(result.Payload.List) < 1 {
|
||||
return true, req.noop(mailAccountId)
|
||||
return true, req.noop(mailAccountId, result.GetDurations())
|
||||
}
|
||||
|
||||
email, gwerr := req.sanitizeEmail(result.Payload.List[0])
|
||||
if gwerr != nil {
|
||||
return false, req.error(mailAccountId, req.apiError(gwerr))
|
||||
return false, req.errorV(mailAccountId, req.apiError(gwerr))
|
||||
}
|
||||
var attachment *jmap.EmailBodyPart = nil
|
||||
for _, part := range email.Attachments {
|
||||
@@ -271,9 +272,10 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
if attachment == nil {
|
||||
return true, req.noop(mailAccountId)
|
||||
return true, req.noop(mailAccountId, result.GetDurations())
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
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) {
|
||||
@@ -288,7 +290,7 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
if blob == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return true, req.noop(blobAccountId)
|
||||
return true, req.noop(blobAccountId, result.GetDurations())
|
||||
}
|
||||
|
||||
if blob.Type != "" {
|
||||
@@ -307,11 +309,12 @@ func (g *Groupware) GetEmailAttachments(w http.ResponseWriter, r *http.Request)
|
||||
w.Header().Add("Content-Language", string(lang))
|
||||
}
|
||||
_, cerr := io.Copy(w, blob.Body)
|
||||
durations := append(result.GetDurations(), time.Since(before))
|
||||
if cerr != nil {
|
||||
return false, req.error(blobAccountId, req.observedParameterError(ErrorStreamingResponse))
|
||||
return false, req.error(blobAccountId, req.observedParameterError(ErrorStreamingResponse), durations)
|
||||
}
|
||||
|
||||
return true, req.noop(blobAccountId)
|
||||
return true, req.noop(blobAccountId, durations)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -322,13 +325,13 @@ func (g *Groupware) getEmailsSince(w http.ResponseWriter, r *http.Request, since
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
@@ -555,14 +558,14 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
ok, filter, makesSnippets, position, anchor, anchorOffset, limit, logger, err := g.buildEmailFilter(req)
|
||||
if !ok {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
if !filter.IsNotEmpty() {
|
||||
@@ -571,7 +574,7 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
|
||||
calculateTotal := true
|
||||
if b, ok, err := req.parseBoolParam(QueryParamCalculateTotal, true); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
calculateTotal = b
|
||||
l = l.Bool(QueryParamCalculateTotal, calculateTotal)
|
||||
@@ -599,11 +602,11 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
flattened = nil
|
||||
} else {
|
||||
flattened = make([]EmailWithSnippets, len(results.Results))
|
||||
for i, result := range results.Results {
|
||||
for i, emails := range results.Results {
|
||||
var snippets []SnippetWithoutEmailId
|
||||
if makesSnippets {
|
||||
snippets := make([]SnippetWithoutEmailId, len(result.Snippets))
|
||||
for j, snippet := range result.Snippets {
|
||||
snippets := make([]SnippetWithoutEmailId, len(emails.Snippets))
|
||||
for j, snippet := range emails.Snippets {
|
||||
snippets[j] = SnippetWithoutEmailId{
|
||||
Subject: snippet.Subject,
|
||||
Preview: snippet.Preview,
|
||||
@@ -612,9 +615,9 @@ func (g *Groupware) GetEmails(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
} else {
|
||||
snippets = nil
|
||||
}
|
||||
sanitized, err := req.sanitizeEmail(result.Email)
|
||||
sanitized, err := req.sanitizeEmail(emails.Email)
|
||||
if err != nil {
|
||||
return req.error(accountId, req.apiError(err))
|
||||
return req.error(accountId, req.apiError(err), result.GetDurations())
|
||||
}
|
||||
flattened[i] = EmailWithSnippets{
|
||||
Email: sanitized,
|
||||
@@ -647,7 +650,7 @@ func (g *Groupware) GetEmailsForAllAccounts(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
ok, filter, makesSnippets, position, anchor, anchorOffset, limit, logger, err := g.buildEmailFilter(req)
|
||||
if !ok {
|
||||
return req.errorN(allAccountIds, err)
|
||||
return req.errorNV(allAccountIds, err)
|
||||
}
|
||||
logger = log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(structs.ToStrings(allAccountIds))))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
@@ -770,7 +773,7 @@ func findDraftsMailboxId(j *jmap.Client, accountId jmap.AccountId, req Request,
|
||||
}
|
||||
// couldn't find a Mailbox with the drafts role for that account,
|
||||
// we have to return an error... ?
|
||||
return "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithDraftRole))
|
||||
return "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithDraftRole), result.GetDurations())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,7 +797,7 @@ func findSentMailboxId(j *jmap.Client, accountId jmap.AccountId, req Request, ct
|
||||
}
|
||||
}
|
||||
if sentMailboxId == "" {
|
||||
return "", "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithSentRole))
|
||||
return "", "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithSentRole), result.GetDurations())
|
||||
}
|
||||
draftsMailboxId := ""
|
||||
for _, role := range draftEmailAutoMailboxRolePrecedence {
|
||||
@@ -804,7 +807,7 @@ func findSentMailboxId(j *jmap.Client, accountId jmap.AccountId, req Request, ct
|
||||
}
|
||||
}
|
||||
if draftsMailboxId == "" {
|
||||
return "", "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithDraftRole))
|
||||
return "", "", req.error(accountId, apiError(req.errorId(), ErrorNoMailboxWithDraftRole), result.GetDurations())
|
||||
}
|
||||
return draftsMailboxId, sentMailboxId, Response{}
|
||||
}
|
||||
@@ -847,7 +850,7 @@ func (g *Groupware) ReplaceEmail(w http.ResponseWriter, r *http.Request) {
|
||||
var err *Error
|
||||
replaceId, err = r.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return false, r.error(accountId, err)
|
||||
return false, r.errorV(accountId, err)
|
||||
}
|
||||
|
||||
return true, Response{}
|
||||
@@ -881,13 +884,13 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return req.error(accountId, gwerr)
|
||||
return req.errorV(accountId, gwerr)
|
||||
}
|
||||
l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
emailId, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEmailId, log.SafeString(emailId))
|
||||
|
||||
@@ -897,11 +900,11 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
var body emailKeywordUpdates
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
if body.IsEmpty() {
|
||||
return req.noop(accountId)
|
||||
return req.noopV(accountId)
|
||||
}
|
||||
|
||||
patch := jmap.PatchObject{}
|
||||
@@ -922,12 +925,12 @@ func (g *Groupware) UpdateEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
if result.Payload == nil {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response", //NOSONAR
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint"))) //NOSONAR
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")), result.GetDurations()) //NOSONAR
|
||||
}
|
||||
updatedEmail, ok := result.Payload[emailId]
|
||||
if !ok {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID", //NOSONAR
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint"))) //NOSONAR
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")), result.GetDurations()) //NOSONAR
|
||||
}
|
||||
|
||||
return req.respond(accountId, updatedEmail, EmailResponseObjectType, result)
|
||||
@@ -943,13 +946,13 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return req.error(accountId, gwerr)
|
||||
return req.errorV(accountId, gwerr)
|
||||
}
|
||||
l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
emailId, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEmailId, log.SafeString(emailId))
|
||||
|
||||
@@ -959,11 +962,11 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
|
||||
var body []string
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
if len(body) < 1 {
|
||||
return req.noop(accountId)
|
||||
return req.noopV(accountId)
|
||||
}
|
||||
|
||||
patch := jmap.PatchObject{}
|
||||
@@ -981,12 +984,12 @@ func (g *Groupware) AddEmailKeywords(w http.ResponseWriter, r *http.Request) { /
|
||||
|
||||
if result.Payload == nil {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")), result.GetDurations())
|
||||
}
|
||||
updatedEmail, ok := result.Payload[emailId]
|
||||
if !ok {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")), result.GetDurations())
|
||||
}
|
||||
|
||||
if updatedEmail == nil {
|
||||
@@ -1006,13 +1009,13 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
emailId, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEmailId, log.SafeString(emailId))
|
||||
|
||||
@@ -1022,11 +1025,11 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
var body []string
|
||||
err = req.body(&body)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
if len(body) < 1 {
|
||||
return req.noop(accountId)
|
||||
return req.noopV(accountId)
|
||||
}
|
||||
|
||||
patch := jmap.PatchObject{}
|
||||
@@ -1044,12 +1047,12 @@ func (g *Groupware) RemoveEmailKeywords(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
if result.Payload == nil {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Missing Email Update Response",
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")))
|
||||
"An internal API behaved unexpectedly: missing Email update response from JMAP endpoint")), result.GetDurations())
|
||||
}
|
||||
updatedEmail, ok := result.Payload[emailId]
|
||||
if !ok {
|
||||
return req.error(accountId, apiError(req.errorId(), ErrorApiInconsistency, withTitle("API Inconsistency: Wrong Email Update Response ID",
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")))
|
||||
"An internal API behaved unexpectedly: wrong Email update ID response from JMAP endpoint")), result.GetDurations())
|
||||
}
|
||||
|
||||
if updatedEmail == nil {
|
||||
@@ -1080,19 +1083,19 @@ func (g *Groupware) SendEmail(w http.ResponseWriter, r *http.Request) { //NOSONA
|
||||
|
||||
accountId, gwerr := req.GetAccountIdForMail()
|
||||
if gwerr != nil {
|
||||
return req.error(accountId, gwerr)
|
||||
return req.errorV(accountId, gwerr)
|
||||
}
|
||||
l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
emailId, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(UriParamEmailId, log.SafeString(emailId))
|
||||
|
||||
identityId, err := req.getMandatoryStringParam(QueryParamIdentityId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(QueryParamIdentityId, log.SafeString(identityId))
|
||||
|
||||
@@ -1198,30 +1201,30 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
id, err := req.PathParam(UriParamEmailId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l = l.Str(logEmailId, log.SafeString(id))
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, 10) // TODO configurable default limit
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint("limit", limit)
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
}
|
||||
|
||||
days, ok, err := req.parseUIntParam(QueryParamDays, 5) // TODO configurable default days
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint("days", days)
|
||||
l = l.Uint(QueryParamDays, days)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
@@ -1297,7 +1300,7 @@ func (g *Groupware) RelatedToEmail(w http.ResponseWriter, r *http.Request) { //N
|
||||
})
|
||||
|
||||
if sanitized, err := req.sanitizeEmail(email); err != nil {
|
||||
return req.error(accountId, req.apiError(err))
|
||||
return req.errorV(accountId, req.apiError(err))
|
||||
} else {
|
||||
return req.respond(accountId, AboutEmailResponse{
|
||||
Email: sanitized,
|
||||
@@ -1499,7 +1502,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
|
||||
limit, ok, err := req.parseUIntParam(QueryParamLimit, 10) // TODO from configuration
|
||||
if err != nil {
|
||||
return req.errorN(allAccountIds, err)
|
||||
return req.errorNV(allAccountIds, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, limit)
|
||||
@@ -1507,7 +1510,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
|
||||
position, ok, err := req.parseIntParam(QueryParamPosition, 0)
|
||||
if err != nil {
|
||||
return req.errorN(allAccountIds, err)
|
||||
return req.errorNV(allAccountIds, err)
|
||||
}
|
||||
if position != 0 {
|
||||
return req.notImplementedN(allAccountIds, EmailResponseObjectType)
|
||||
@@ -1528,7 +1531,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
{
|
||||
v, ok, err := req.parseIntParam(QueryParamAnchorOffset, 0) // optional offset relative to the anchor
|
||||
if err != nil {
|
||||
return req.errorN(allAccountIds, err)
|
||||
return req.errorNV(allAccountIds, err)
|
||||
}
|
||||
if ok {
|
||||
return req.notImplementedN(allAccountIds, EmailResponseObjectType)
|
||||
@@ -1541,7 +1544,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
|
||||
seen, ok, err := req.parseBoolParam(QueryParamSeen, false)
|
||||
if err != nil {
|
||||
return req.errorN(allAccountIds, err)
|
||||
return req.errorNV(allAccountIds, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Bool(QueryParamSeen, seen)
|
||||
@@ -1549,7 +1552,7 @@ func (g *Groupware) GetLatestEmailsSummaryForAllAccounts(w http.ResponseWriter,
|
||||
|
||||
undesirable, ok, err := req.parseBoolParam(QueryParamUndesirable, false)
|
||||
if err != nil {
|
||||
return req.errorN(allAccountIds, err)
|
||||
return req.errorNV(allAccountIds, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Bool(QueryParamUndesirable, undesirable)
|
||||
|
||||
@@ -82,12 +82,12 @@ func (g *Groupware) ParseIcalBlob(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountId, err := req.GetAccountIdForBlob()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
blobId, err := req.PathParam(UriParamBlobId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
blobIds := strings.Split(blobId, ",")
|
||||
|
||||
@@ -55,12 +55,12 @@ func (g *Groupware) GetMailboxes(w http.ResponseWriter, r *http.Request) { //NOS
|
||||
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
subscribed, set, err := req.parseBoolParam(QueryParamMailboxSearchSubscribed, false)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if set {
|
||||
filter.IsSubscribed = &subscribed
|
||||
@@ -104,7 +104,7 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountIds := req.AllAccountIds()
|
||||
if len(accountIds) < 1 {
|
||||
return req.noopN(nil) // when the user has no accounts
|
||||
return req.noopN(nil, zeroDurations) // when the user has no accounts
|
||||
}
|
||||
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(structs.ToStrings(accountIds))))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
@@ -118,7 +118,7 @@ func (g *Groupware) GetMailboxesForAllAccounts(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
if subscribed, set, err := req.parseBoolParam(QueryParamMailboxSearchSubscribed, false); err != nil {
|
||||
return req.errorN(accountIds, err)
|
||||
return req.errorNV(accountIds, err)
|
||||
} else if set {
|
||||
filter.IsSubscribed = &subscribed
|
||||
hasCriteria = true
|
||||
@@ -145,12 +145,12 @@ func (g *Groupware) GetMailboxByRoleForAllAccounts(w http.ResponseWriter, r *htt
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountIds := req.AllAccountIds()
|
||||
if len(accountIds) < 1 {
|
||||
return req.noopN(accountIds) // when the user has no accounts
|
||||
return req.noopN(accountIds, zeroDurations) // when the user has no accounts
|
||||
}
|
||||
|
||||
role, err := req.PathParamDoc(UriParamRole, "Role of the mailboxes to retrieve across all accounts")
|
||||
if err != nil {
|
||||
return req.errorN(accountIds, err)
|
||||
return req.errorNV(accountIds, err)
|
||||
}
|
||||
|
||||
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(structs.ToStrings(accountIds))).Str("role", role))
|
||||
@@ -184,7 +184,7 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
|
||||
|
||||
sinceStateStrMap, ok, err := req.parseMapParam(QueryParamSince)
|
||||
if err != nil {
|
||||
return req.errorN(allAccountIds, err)
|
||||
return req.errorNV(allAccountIds, err)
|
||||
}
|
||||
if ok {
|
||||
dict := zerolog.Dict()
|
||||
@@ -196,7 +196,7 @@ func (g *Groupware) GetMailboxChangesForAllAccounts(w http.ResponseWriter, r *ht
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.errorN(allAccountIds, err)
|
||||
return req.errorNV(allAccountIds, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
|
||||
@@ -43,7 +43,7 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
l := req.logger.With()
|
||||
accountId, err := req.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l = l.Str(logAccountId, log.SafeString(accountId))
|
||||
|
||||
@@ -59,7 +59,7 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
{
|
||||
var objects ObjectsRequest
|
||||
if ok, err := req.optBody(&objects); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
mailboxIds = append(mailboxIds, objects.Mailboxes...)
|
||||
emailIds = append(emailIds, objects.Emails...)
|
||||
@@ -74,47 +74,47 @@ func (g *Groupware) GetObjects(w http.ResponseWriter, r *http.Request) { //NOSON
|
||||
}
|
||||
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamMailboxes); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
mailboxIds = append(mailboxIds, list...)
|
||||
}
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamEmails); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
emailIds = append(emailIds, list...)
|
||||
}
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamAddressbooks); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
addressbookIds = append(addressbookIds, list...)
|
||||
}
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamContacts); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
contactIds = append(contactIds, list...)
|
||||
}
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamCalendars); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
calendarIds = append(calendarIds, list...)
|
||||
}
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamEvents); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
eventIds = append(eventIds, list...)
|
||||
}
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamQuotas); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
quotaIds = append(quotaIds, list...)
|
||||
}
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamIdentities); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
identityIds = append(identityIds, list...)
|
||||
}
|
||||
if list, ok, err := req.parseOptStringListParam(QueryParamEmailSubmissions); err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
} else if ok {
|
||||
emailSubmissionIds = append(emailSubmissionIds, list...)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func (g *Groupware) GetQuotaForAllAccounts(w http.ResponseWriter, r *http.Reques
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
accountIds := req.AllAccountIds()
|
||||
if len(accountIds) < 1 {
|
||||
return req.noopN(accountIds) // user has no accounts
|
||||
return req.noopN(accountIds, zeroDurations) // user has no accounts
|
||||
}
|
||||
logger := log.From(req.logger.With().Array(logAccountId, log.SafeStringArray(structs.ToStrings(accountIds))))
|
||||
ctx := req.ctx.WithLogger(logger)
|
||||
|
||||
@@ -32,7 +32,7 @@ func (g *Groupware) GetTaskListById(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
tasklistId, err := req.PathParam(UriParamTaskListId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
// TODO replace with proper implementation
|
||||
meta := TaskListsMeta{SessionState: req.session.State}
|
||||
@@ -41,7 +41,7 @@ func (g *Groupware) GetTaskListById(w http.ResponseWriter, r *http.Request) {
|
||||
return req.respond(accountId, tasklist, TaskListResponseObjectType, meta)
|
||||
}
|
||||
}
|
||||
return req.etaggedNotFound(accountId, req.session.State, TaskListResponseObjectType, TaskListsState)
|
||||
return req.etaggedNotFound(accountId, req.session.State, TaskListResponseObjectType, TaskListsState, zeroDurations)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (g *Groupware) GetTasksInTaskList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
tasklistId, err := req.PathParam(UriParamTaskListId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
// TODO replace with proper implementation
|
||||
meta := TaskMeta{SessionState: req.session.State}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/render"
|
||||
@@ -750,32 +751,41 @@ func errorResponses(errors ...Error) ErrorResponse {
|
||||
return ErrorResponse{Errors: errors}
|
||||
}
|
||||
|
||||
func (r *Request) error(accountId jmap.AccountId, err *Error) Response {
|
||||
return errorResponse(single(accountId), err, r.session.State, jmap.NoLanguage)
|
||||
// Validation error prior to performing any requests
|
||||
func (r *Request) errorV(accountId jmap.AccountId, err *Error) Response {
|
||||
return errorResponse(single(accountId), err, r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
|
||||
func (r *Request) error(accountId jmap.AccountId, err *Error, durations []time.Duration) Response {
|
||||
return errorResponse(single(accountId), err, r.session.State, jmap.NoLanguage, durations)
|
||||
}
|
||||
|
||||
func (r *Request) errorS(accountId jmap.AccountId, err *Error, result jmap.ResultMetadata) Response {
|
||||
return errorResponse(single(accountId), err, result.GetSessionState(), result.GetLanguage())
|
||||
return errorResponse(single(accountId), err, result.GetSessionState(), result.GetLanguage(), result.GetDurations())
|
||||
}
|
||||
|
||||
func (r *Request) errorN(accountIds []jmap.AccountId, err *Error) Response {
|
||||
return errorResponse(accountIds, err, r.session.State, jmap.NoLanguage)
|
||||
func (r *Request) errorN(accountIds []jmap.AccountId, err *Error, durations []time.Duration) Response {
|
||||
return errorResponse(accountIds, err, r.session.State, jmap.NoLanguage, durations)
|
||||
}
|
||||
|
||||
func (r *Request) errorNV(accountIds []jmap.AccountId, err *Error) Response {
|
||||
return errorResponse(accountIds, err, r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
|
||||
func (r *Request) jmapError(accountId jmap.AccountId, err error, result jmap.ResultMetadata) Response {
|
||||
switch e := err.(type) {
|
||||
case jmap.Error:
|
||||
if result != nil {
|
||||
return errorResponse(single(accountId), r.apiErrorFromJmap(r.observeJmapError(e)), result.GetSessionState(), result.GetLanguage())
|
||||
return errorResponse(single(accountId), r.apiErrorFromJmap(r.observeJmapError(e)), result.GetSessionState(), result.GetLanguage(), result.GetDurations())
|
||||
} else {
|
||||
return errorResponse(single(accountId), r.apiErrorFromJmap(r.observeJmapError(e)), r.session.GetSessionState(), jmap.NoLanguage)
|
||||
return errorResponse(single(accountId), r.apiErrorFromJmap(r.observeJmapError(e)), r.session.GetSessionState(), jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
case GroupwareError:
|
||||
errorId := r.errorId()
|
||||
return r.error(accountId, apiError(errorId, e))
|
||||
return r.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := r.errorId()
|
||||
return r.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return r.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,15 +793,15 @@ func (r *Request) jmapErrorN(accountIds []jmap.AccountId, err error, result jmap
|
||||
switch e := err.(type) {
|
||||
case jmap.Error:
|
||||
if result != nil {
|
||||
return errorResponse(accountIds, r.apiErrorFromJmap(r.observeJmapError(e)), result.GetSessionState(), result.GetLanguage())
|
||||
return errorResponse(accountIds, r.apiErrorFromJmap(r.observeJmapError(e)), result.GetSessionState(), result.GetLanguage(), result.GetDurations())
|
||||
} else {
|
||||
return errorResponse(accountIds, r.apiErrorFromJmap(r.observeJmapError(e)), r.session.GetSessionState(), jmap.NoLanguage)
|
||||
return errorResponse(accountIds, r.apiErrorFromJmap(r.observeJmapError(e)), r.session.GetSessionState(), jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
case GroupwareError:
|
||||
errorId := r.errorId()
|
||||
return r.errorN(accountIds, apiError(errorId, e))
|
||||
return r.errorN(accountIds, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := r.errorId()
|
||||
return r.errorN(accountIds, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return r.errorN(accountIds, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -82,9 +83,10 @@ type Job struct {
|
||||
}
|
||||
|
||||
type groupwareConfig struct {
|
||||
maxBodyValueBytes uint
|
||||
sanitize bool
|
||||
publicUrl *url.URL
|
||||
maxBodyValueBytes uint
|
||||
sanitize bool
|
||||
publicUrl *url.URL
|
||||
sendDurationsResponse bool
|
||||
}
|
||||
|
||||
type groupwareDefaults struct {
|
||||
@@ -187,6 +189,7 @@ func NewGroupware(config *config.Config, logger *log.Logger, mux *chi.Mux, prome
|
||||
|
||||
defaultEmailLimit := ptrIfNot(max(config.Mail.DefaultEmailLimit, 0), 0)
|
||||
maxBodyValueBytes := max(config.Mail.MaxBodyValueBytes, 0)
|
||||
sendDurationsResponse := config.HTTP.SendDurationsResponse
|
||||
defaultContactLimit := ptrIfNot(max(config.Mail.DefaultContactLimit, 0), 0)
|
||||
responseHeaderTimeout := max(config.Mail.ResponseHeaderTimeout, 0)
|
||||
sessionCacheMaxCapacity := uint64(max(config.Mail.SessionCache.MaxCapacity, 0))
|
||||
@@ -426,9 +429,10 @@ func NewGroupware(config *config.Config, logger *log.Logger, mux *chi.Mux, prome
|
||||
contactLimit: defaultContactLimit,
|
||||
},
|
||||
config: groupwareConfig{
|
||||
maxBodyValueBytes: maxBodyValueBytes,
|
||||
sanitize: sanitize,
|
||||
publicUrl: publicUrl,
|
||||
maxBodyValueBytes: maxBodyValueBytes,
|
||||
sanitize: sanitize,
|
||||
publicUrl: publicUrl,
|
||||
sendDurationsResponse: sendDurationsResponse,
|
||||
},
|
||||
eventChannel: eventChannel,
|
||||
jobsChannel: jobsChannel,
|
||||
@@ -701,11 +705,17 @@ func (g *Groupware) withSession(w http.ResponseWriter, r *http.Request, handler
|
||||
}
|
||||
|
||||
const (
|
||||
SessionStateResponseHeader = "Session-State"
|
||||
StateResponseHeader = "State"
|
||||
ObjectTypeResponseHeader = "Object-Type"
|
||||
AccountIdResponseHeader = "Account-Id"
|
||||
AccountIdsResponseHeader = "Account-Ids"
|
||||
SessionStateResponseHeader = "Session-State"
|
||||
StateResponseHeader = "State"
|
||||
ObjectTypeResponseHeader = "Object-Type"
|
||||
AccountIdResponseHeader = "Account-Id"
|
||||
AccountIdsResponseHeader = "Account-Ids"
|
||||
LinkResponseHeader = "Link"
|
||||
DurationsResponseHeader = "Durations"
|
||||
DurationsNanosResponseHeader = "Durations-Nanos"
|
||||
NextResponseHeader = "Next"
|
||||
ContentLanguageResponseHeader = "Content-Language"
|
||||
ETagResponseHeader = "ETag"
|
||||
)
|
||||
|
||||
// Send the Response object as an HTTP response.
|
||||
@@ -725,11 +735,11 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons
|
||||
}
|
||||
|
||||
if response.contentLanguage != "" {
|
||||
w.Header().Add("Content-Language", string(response.contentLanguage))
|
||||
w.Header().Add(ContentLanguageResponseHeader, string(response.contentLanguage))
|
||||
}
|
||||
|
||||
if response.next != "" && response.next != NoNextToken {
|
||||
w.Header().Add("next", string(response.next)) // TODO add RESTful links for next?
|
||||
w.Header().Add(NextResponseHeader, string(response.next)) // TODO add RESTful links for next?
|
||||
|
||||
base := g.config.publicUrl.JoinPath("/groupware/contacts")
|
||||
|
||||
@@ -745,7 +755,7 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons
|
||||
q.Set(QueryParamLimit, limit)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
w.Header().Add("Link", fmt.Sprintf(`<%s>; rel="next">`, u.String()))
|
||||
w.Header().Add(LinkResponseHeader, fmt.Sprintf(`<%s>; rel="next">`, u.String()))
|
||||
}
|
||||
{
|
||||
u := base
|
||||
@@ -754,7 +764,7 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons
|
||||
q.Set(QueryParamLimit, limit)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
w.Header().Add("Link", fmt.Sprintf(`<%s>; rel="first">`, u.String()))
|
||||
w.Header().Add(LinkResponseHeader, fmt.Sprintf(`<%s>; rel="first">`, u.String()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,7 +775,7 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons
|
||||
challenge := r.Header.Get("if-none-match") // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/If-None-Match
|
||||
quotedEtag := "\"" + etag + "\"" // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag#etag_value
|
||||
notModified = challenge != "" && (challenge == etag || challenge == quotedEtag) // be a bit flexible/permissive here with the quoting
|
||||
w.Header().Add("ETag", quotedEtag)
|
||||
w.Header().Add(ETagResponseHeader, quotedEtag)
|
||||
w.Header().Add(StateResponseHeader, etag)
|
||||
}
|
||||
}
|
||||
@@ -787,6 +797,11 @@ func (g *Groupware) sendResponse(w http.ResponseWriter, r *http.Request, respons
|
||||
w.Header().Add(AccountIdsResponseHeader, value)
|
||||
}
|
||||
|
||||
if g.config.sendDurationsResponse && len(response.durations) > 0 {
|
||||
w.Header().Add(DurationsNanosResponseHeader, strings.Join(structs.Map(response.durations, func(d time.Duration) string { return strconv.FormatInt(d.Nanoseconds(), 10) }), ", "))
|
||||
w.Header().Add(DurationsResponseHeader, strings.Join(structs.Map(response.durations, func(d time.Duration) string { return d.String() }), ", "))
|
||||
}
|
||||
|
||||
if notModified {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
} else {
|
||||
|
||||
@@ -208,6 +208,7 @@ type TaskListsMeta struct {
|
||||
func (t TaskListsMeta) GetSessionState() jmap.SessionState { return t.SessionState }
|
||||
func (t TaskListsMeta) GetState() jmap.State { return TaskListsState }
|
||||
func (t TaskListsMeta) GetLanguage() jmap.Language { return jmap.NoLanguage }
|
||||
func (t TaskListsMeta) GetDurations() []time.Duration { return zeroDurations }
|
||||
|
||||
var _ jmap.ResultMetadata = TaskListsMeta{}
|
||||
|
||||
@@ -226,6 +227,7 @@ type TaskMeta struct {
|
||||
func (t TaskMeta) GetSessionState() jmap.SessionState { return t.SessionState }
|
||||
func (t TaskMeta) GetState() jmap.State { return TaskState }
|
||||
func (t TaskMeta) GetLanguage() jmap.Language { return jmap.NoLanguage }
|
||||
func (t TaskMeta) GetDurations() []time.Duration { return zeroDurations }
|
||||
|
||||
var _ jmap.ResultMetadata = TaskListsMeta{}
|
||||
|
||||
|
||||
@@ -74,11 +74,11 @@ func curryNoNextMapQuery[SRES jmap.SearchResults[T], T jmap.Idable, FILTER any,
|
||||
) func(req Request, accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, filter FILTER, sortBy []COMP, ctx jmap.Context) (jmap.Result[SRES], NextToken, error) {
|
||||
return func(_ Request, accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, filter FILTER, sortBy []COMP, ctx jmap.Context) (jmap.Result[SRES], NextToken, error) { //NOSONAR
|
||||
if m, err := mapQueryParams("", accountIds, qps); err != nil {
|
||||
return jmap.ZeroResult[SRES](), NoNextToken, err
|
||||
return jmap.ZeroResultV[SRES](), NoNextToken, err
|
||||
} else {
|
||||
result, err := f(m, limit, filter, sortBy, true, ctx)
|
||||
if err != nil {
|
||||
return jmap.ZeroResult[SRES](), NoNextToken, err
|
||||
return jmap.ZeroResult[SRES](result.Durations), NoNextToken, err
|
||||
} else {
|
||||
var singleAccountId jmap.AccountId = ""
|
||||
// TODO what about requests with zero accountIds, can these even happen at all?
|
||||
@@ -155,15 +155,15 @@ func flattenMultipleAccounts[SRES jmap.SearchResults[T], T jmap.Idable](
|
||||
n[accountId] = jmap.QueryParams{Anchor: lastId, AnchorOffset: ptr(1)}
|
||||
}
|
||||
if err := fillMissingAccounts(qps, "", accountIds, n); err != nil {
|
||||
return jmap.ZeroResult[SRES](), NoNextToken, err
|
||||
return jmap.ZeroResult[SRES](result.Durations), NoNextToken, err
|
||||
}
|
||||
if t, err := nextSingle(n); err != nil {
|
||||
return jmap.ZeroResult[SRES](), NoNextToken, err
|
||||
return jmap.ZeroResult[SRES](result.Durations), NoNextToken, err
|
||||
} else {
|
||||
if r, err := jmap.RefineResultPayload(result, func(a map[jmap.AccountId]SRES) (SRES, bool, error) {
|
||||
return searchResultCtor(jmap.ChangeCalculation(cc), nil, limit, &total, all), true, nil
|
||||
}); err != nil {
|
||||
return jmap.ZeroResult[SRES](), NoNextToken, err
|
||||
return jmap.ZeroResult[SRES](result.Durations), NoNextToken, err
|
||||
} else {
|
||||
return r, t, nil
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func flattenMultipleAccounts[SRES jmap.SearchResults[T], T jmap.Idable](
|
||||
if r, err := jmap.RefineResultPayload(result, func(a map[jmap.AccountId]SRES) (SRES, bool, error) {
|
||||
return searchResultCtor(jmap.ChangeCalculation(cc), nil, limit, &total, all), true, nil
|
||||
}); err != nil {
|
||||
return jmap.ZeroResult[SRES](), NoNextToken, err
|
||||
return jmap.ZeroResult[SRES](result.Durations), NoNextToken, err
|
||||
} else {
|
||||
return r, NoNextToken, nil
|
||||
}
|
||||
|
||||
@@ -224,11 +224,11 @@ func (r *Request) parameterError(param string, detail string) *Error {
|
||||
}
|
||||
|
||||
func (r *Request) parameterErrorResponse(accountId jmap.AccountId, param string, detail string) Response {
|
||||
return r.error(accountId, r.parameterError(param, detail))
|
||||
return r.errorV(accountId, r.parameterError(param, detail))
|
||||
}
|
||||
|
||||
func (r *Request) parameterErrorResponseN(accountIds []jmap.AccountId, param string, detail string) Response {
|
||||
return r.errorN(accountIds, r.parameterError(param, detail))
|
||||
return r.errorNV(accountIds, r.parameterError(param, detail))
|
||||
}
|
||||
|
||||
type supportedQueryParams map[string]struct{}
|
||||
@@ -472,7 +472,7 @@ func (r *Request) observeJmapError(jerr jmap.Error) jmap.Error {
|
||||
|
||||
func (r *Request) needBlob(accountId jmap.AccountId) (bool, Response) {
|
||||
if r.session.Capabilities.Blob == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingBlobSessionCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingBlobSessionCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -483,10 +483,10 @@ func (r *Request) needBlobForAccount(accountId jmap.AccountId) (bool, Response)
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
if account.AccountCapabilities.Blob == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingBlobAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingBlobAccountCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -494,7 +494,7 @@ func (r *Request) needBlobForAccount(accountId jmap.AccountId) (bool, Response)
|
||||
func (r *Request) needBloblWithAccount() (bool, jmap.AccountId, Response) {
|
||||
accountId, err := r.GetAccountIdForBlob()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
return false, "", r.errorV(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needBlobForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
@@ -504,7 +504,7 @@ func (r *Request) needBloblWithAccount() (bool, jmap.AccountId, Response) {
|
||||
|
||||
func (r *Request) needMail(accountId jmap.AccountId) (bool, Response) {
|
||||
if r.session.Capabilities.Mail == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingMailsSessionCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingMailsSessionCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -515,10 +515,10 @@ func (r *Request) needMailForAccount(accountId jmap.AccountId) (bool, Response)
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
if account.AccountCapabilities.Contacts == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingMailsAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingMailsAccountCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -526,7 +526,7 @@ func (r *Request) needMailForAccount(accountId jmap.AccountId) (bool, Response)
|
||||
func (r *Request) needMailWithAccount() (bool, jmap.AccountId, Response) {
|
||||
accountId, err := r.GetAccountIdForMail()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
return false, "", r.errorV(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needMailForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
@@ -537,7 +537,7 @@ func (r *Request) needMailWithAccount() (bool, jmap.AccountId, Response) {
|
||||
func (r *Request) needTask(accountId jmap.AccountId) (bool, Response) {
|
||||
if !IgnoreSessionCapabilityChecksForTasks {
|
||||
if r.session.Capabilities.Tasks == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingTasksSessionCapability), r.session.State, jmap.Language(r.language()))
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingTasksSessionCapability), r.session.State, jmap.Language(r.language()), zeroDurations)
|
||||
}
|
||||
}
|
||||
return true, Response{}
|
||||
@@ -549,10 +549,10 @@ func (r *Request) needTaskForAccount(accountId jmap.AccountId) (bool, Response)
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
if account.AccountCapabilities.Tasks == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingTasksAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingTasksAccountCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -560,7 +560,7 @@ func (r *Request) needTaskForAccount(accountId jmap.AccountId) (bool, Response)
|
||||
func (r *Request) needTaskWithAccount() (bool, jmap.AccountId, Response) {
|
||||
accountId, err := r.GetAccountIdForTask()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
return false, "", r.errorV(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needTaskForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
@@ -570,7 +570,7 @@ func (r *Request) needTaskWithAccount() (bool, jmap.AccountId, Response) {
|
||||
|
||||
func (r *Request) needCalendar(accountId jmap.AccountId) (bool, Response) {
|
||||
if r.session.Capabilities.Calendars == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingCalendarsSessionCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingCalendarsSessionCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -581,10 +581,10 @@ func (r *Request) needCalendarForAccount(accountId jmap.AccountId) (bool, Respon
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
if account.AccountCapabilities.Calendars == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingCalendarsAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingCalendarsAccountCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -592,7 +592,7 @@ func (r *Request) needCalendarForAccount(accountId jmap.AccountId) (bool, Respon
|
||||
func (r *Request) needCalendarWithAccount() (bool, jmap.AccountId, Response) {
|
||||
accountId, err := r.GetAccountIdForCalendar()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
return false, "", r.errorV(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needCalendarForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
@@ -602,7 +602,7 @@ func (r *Request) needCalendarWithAccount() (bool, jmap.AccountId, Response) {
|
||||
|
||||
func (r *Request) needContact(accountId jmap.AccountId) (bool, Response) {
|
||||
if r.session.Capabilities.Contacts == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingContactsSessionCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingContactsSessionCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -613,10 +613,10 @@ func (r *Request) needContactForAccount(accountId jmap.AccountId) (bool, Respons
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
if account.AccountCapabilities.Contacts == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingContactsAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingContactsAccountCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -624,7 +624,7 @@ func (r *Request) needContactForAccount(accountId jmap.AccountId) (bool, Respons
|
||||
func (r *Request) needContactWithAccount() (bool, jmap.AccountId, Response) {
|
||||
accountId, err := r.GetAccountIdForContact()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
return false, "", r.errorV(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needContactForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
@@ -634,7 +634,7 @@ func (r *Request) needContactWithAccount() (bool, jmap.AccountId, Response) {
|
||||
|
||||
func (r *Request) needQuota(accountId jmap.AccountId) (bool, Response) {
|
||||
if r.session.Capabilities.Quota == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingQuotaSessionCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingQuotaSessionCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -645,10 +645,10 @@ func (r *Request) needQuotaForAccount(accountId jmap.AccountId) (bool, Response)
|
||||
}
|
||||
account, ok := r.session.Accounts[accountId]
|
||||
if !ok {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorAccountNotFound), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
if account.AccountCapabilities.Quota == nil {
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingQuotaAccountCapability), r.session.State, jmap.NoLanguage)
|
||||
return false, errorResponse(single(accountId), r.apiError(&ErrorMissingQuotaAccountCapability), r.session.State, jmap.NoLanguage, zeroDurations)
|
||||
}
|
||||
return true, Response{}
|
||||
}
|
||||
@@ -656,7 +656,7 @@ func (r *Request) needQuotaForAccount(accountId jmap.AccountId) (bool, Response)
|
||||
func (r *Request) needQuotaWithAccount() (bool, jmap.AccountId, Response) {
|
||||
accountId, err := r.GetAccountIdForQuota()
|
||||
if err != nil {
|
||||
return false, "", r.error(accountId, err)
|
||||
return false, "", r.errorV(accountId, err)
|
||||
}
|
||||
if ok, resp := r.needQuotaForAccount(accountId); !ok {
|
||||
return false, accountId, resp
|
||||
|
||||
@@ -2,6 +2,7 @@ package groupware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
)
|
||||
@@ -26,6 +27,8 @@ const (
|
||||
VacationResponseResponseObjectType = ResponseObjectType("vacationresponse")
|
||||
)
|
||||
|
||||
var zeroDurations = []time.Duration{time.Duration(0)}
|
||||
|
||||
type Response struct {
|
||||
body any
|
||||
status int
|
||||
@@ -36,9 +39,16 @@ type Response struct {
|
||||
sessionState jmap.SessionState
|
||||
contentLanguage jmap.Language
|
||||
next NextToken
|
||||
durations []time.Duration
|
||||
}
|
||||
|
||||
func errorResponse(accountIds []jmap.AccountId, err *Error, sessionState jmap.SessionState, contentLanguage jmap.Language) Response {
|
||||
func errorResponse(
|
||||
accountIds []jmap.AccountId,
|
||||
err *Error,
|
||||
sessionState jmap.SessionState,
|
||||
contentLanguage jmap.Language,
|
||||
durations []time.Duration,
|
||||
) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
body: nil,
|
||||
@@ -47,10 +57,17 @@ func errorResponse(accountIds []jmap.AccountId, err *Error, sessionState jmap.Se
|
||||
sessionState: sessionState,
|
||||
contentLanguage: contentLanguage,
|
||||
next: NoNextToken,
|
||||
durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func response(accountIds []jmap.AccountId, body any, sessionState jmap.SessionState, contentLanguage jmap.Language) Response {
|
||||
func response(
|
||||
accountIds []jmap.AccountId,
|
||||
body any,
|
||||
sessionState jmap.SessionState,
|
||||
contentLanguage jmap.Language,
|
||||
durations []time.Duration,
|
||||
) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
body: body,
|
||||
@@ -59,14 +76,23 @@ func response(accountIds []jmap.AccountId, body any, sessionState jmap.SessionSt
|
||||
sessionState: sessionState,
|
||||
contentLanguage: contentLanguage,
|
||||
next: NoNextToken,
|
||||
durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) respondWithoutStatus(accountId jmap.AccountId, body any) Response {
|
||||
return response(single(accountId), body, r.session.State, jmap.Language(r.language()))
|
||||
func (r *Request) respondWithoutStatus(accountId jmap.AccountId, body any, durations []time.Duration) Response {
|
||||
return response(single(accountId), body, r.session.State, jmap.Language(r.language()), durations)
|
||||
}
|
||||
|
||||
func etaggedResponse(accountIds []jmap.AccountId, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
|
||||
func etaggedResponse(
|
||||
accountIds []jmap.AccountId,
|
||||
body any,
|
||||
sessionState jmap.SessionState,
|
||||
objectType ResponseObjectType,
|
||||
etag jmap.State,
|
||||
contentLanguage jmap.Language,
|
||||
durations []time.Duration,
|
||||
) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
body: body,
|
||||
@@ -76,18 +102,38 @@ func etaggedResponse(accountIds []jmap.AccountId, body any, sessionState jmap.Se
|
||||
sessionState: sessionState,
|
||||
contentLanguage: contentLanguage,
|
||||
next: NoNextToken,
|
||||
durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) respond(accountId jmap.AccountId, body any, objectType ResponseObjectType, result jmap.ResultMetadata) Response {
|
||||
return etaggedResponse(single(accountId), body, result.GetSessionState(), objectType, result.GetState(), result.GetLanguage())
|
||||
func (r *Request) respond(
|
||||
accountId jmap.AccountId,
|
||||
body any,
|
||||
objectType ResponseObjectType,
|
||||
result jmap.ResultMetadata,
|
||||
) Response {
|
||||
return etaggedResponse(single(accountId), body, result.GetSessionState(), objectType, result.GetState(), result.GetLanguage(), result.GetDurations())
|
||||
}
|
||||
|
||||
func (r *Request) respondN(accountIds []jmap.AccountId, body any, objectType ResponseObjectType, result jmap.ResultMetadata) Response {
|
||||
return etaggedResponse(accountIds, body, result.GetSessionState(), objectType, result.GetState(), result.GetLanguage())
|
||||
func (r *Request) respondN(
|
||||
accountIds []jmap.AccountId,
|
||||
body any,
|
||||
objectType ResponseObjectType,
|
||||
result jmap.ResultMetadata,
|
||||
) Response {
|
||||
return etaggedResponse(accountIds, body, result.GetSessionState(), objectType, result.GetState(), result.GetLanguage(), result.GetDurations())
|
||||
}
|
||||
|
||||
func etaggedNextResponse(accountIds []jmap.AccountId, body any, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language, next NextToken) Response {
|
||||
func etaggedNextResponse(
|
||||
accountIds []jmap.AccountId,
|
||||
body any,
|
||||
sessionState jmap.SessionState,
|
||||
objectType ResponseObjectType,
|
||||
etag jmap.State,
|
||||
contentLanguage jmap.Language,
|
||||
next NextToken,
|
||||
durations []time.Duration,
|
||||
) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
body: body,
|
||||
@@ -97,11 +143,18 @@ func etaggedNextResponse(accountIds []jmap.AccountId, body any, sessionState jma
|
||||
sessionState: sessionState,
|
||||
contentLanguage: contentLanguage,
|
||||
next: next,
|
||||
durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) respondNext(accountId jmap.AccountId, body any, objectType ResponseObjectType, result jmap.ResultMetadata, next NextToken) Response {
|
||||
return etaggedNextResponse(single(accountId), body, result.GetSessionState(), objectType, result.GetState(), result.GetLanguage(), next)
|
||||
func (r *Request) respondNext(
|
||||
accountId jmap.AccountId,
|
||||
body any,
|
||||
objectType ResponseObjectType,
|
||||
result jmap.ResultMetadata,
|
||||
next NextToken,
|
||||
) Response {
|
||||
return etaggedNextResponse(single(accountId), body, result.GetSessionState(), objectType, result.GetState(), result.GetLanguage(), next, result.GetDurations())
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -117,7 +170,7 @@ func etagOnlyResponse(body any, etag jmap.State, objectType ResponseObjectType,
|
||||
}
|
||||
*/
|
||||
|
||||
func noContentResponse(accountIds []jmap.AccountId, sessionState jmap.SessionState) Response {
|
||||
func noContentResponse(accountIds []jmap.AccountId, sessionState jmap.SessionState, durations []time.Duration) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
body: nil,
|
||||
@@ -126,18 +179,29 @@ func noContentResponse(accountIds []jmap.AccountId, sessionState jmap.SessionSta
|
||||
etag: jmap.State(sessionState),
|
||||
sessionState: sessionState,
|
||||
next: NoNextToken,
|
||||
durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) noop(accountId jmap.AccountId) Response {
|
||||
return noContentResponse(single(accountId), r.session.State)
|
||||
func (r *Request) noop(accountId jmap.AccountId, durations []time.Duration) Response {
|
||||
return noContentResponse(single(accountId), r.session.State, durations)
|
||||
}
|
||||
|
||||
func (r *Request) noopN(accountIds []jmap.AccountId) Response {
|
||||
return noContentResponse(accountIds, r.session.State)
|
||||
func (r *Request) noopV(accountId jmap.AccountId) Response {
|
||||
return noContentResponse(single(accountId), r.session.State, zeroDurations)
|
||||
}
|
||||
|
||||
func noContentResponseWithEtag(accountIds []jmap.AccountId, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
|
||||
func (r *Request) noopN(accountIds []jmap.AccountId, durations []time.Duration) Response {
|
||||
return noContentResponse(accountIds, r.session.State, durations)
|
||||
}
|
||||
|
||||
func noContentResponseWithEtag(
|
||||
accountIds []jmap.AccountId,
|
||||
sessionState jmap.SessionState,
|
||||
objectType ResponseObjectType,
|
||||
etag jmap.State,
|
||||
durations []time.Duration,
|
||||
) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
body: nil,
|
||||
@@ -147,11 +211,16 @@ func noContentResponseWithEtag(accountIds []jmap.AccountId, sessionState jmap.Se
|
||||
objectType: objectType,
|
||||
sessionState: sessionState,
|
||||
next: NoNextToken,
|
||||
durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) noContent(accountId jmap.AccountId, objectType ResponseObjectType, result jmap.ResultMetadata) Response {
|
||||
return noContentResponseWithEtag(single(accountId), result.GetSessionState(), objectType, result.GetState())
|
||||
func (r *Request) noContent(
|
||||
accountId jmap.AccountId,
|
||||
objectType ResponseObjectType,
|
||||
result jmap.ResultMetadata,
|
||||
) Response {
|
||||
return noContentResponseWithEtag(single(accountId), result.GetSessionState(), objectType, result.GetState(), result.GetDurations())
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -178,7 +247,13 @@ func timeoutResponse(sessionState jmap.SessionState) Response {
|
||||
}
|
||||
*/
|
||||
|
||||
func notFoundResponse(accountIds []jmap.AccountId, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
|
||||
func notFoundResponse(
|
||||
accountIds []jmap.AccountId,
|
||||
sessionState jmap.SessionState,
|
||||
objectType ResponseObjectType,
|
||||
etag jmap.State,
|
||||
durations []time.Duration,
|
||||
) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
body: nil,
|
||||
@@ -188,14 +263,26 @@ func notFoundResponse(accountIds []jmap.AccountId, sessionState jmap.SessionStat
|
||||
etag: etag,
|
||||
sessionState: sessionState,
|
||||
next: NoNextToken,
|
||||
durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) notFound(accountId jmap.AccountId, objectType ResponseObjectType, result jmap.ResultMetadata) Response {
|
||||
return notFoundResponse(single(accountId), result.GetSessionState(), objectType, result.GetState())
|
||||
func (r *Request) notFound(
|
||||
accountId jmap.AccountId,
|
||||
objectType ResponseObjectType,
|
||||
result jmap.ResultMetadata,
|
||||
) Response {
|
||||
return notFoundResponse(single(accountId), result.GetSessionState(), objectType, result.GetState(), result.GetDurations())
|
||||
}
|
||||
|
||||
func etaggedNotFoundResponse(accountIds []jmap.AccountId, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State, contentLanguage jmap.Language) Response {
|
||||
func etaggedNotFoundResponse(
|
||||
accountIds []jmap.AccountId,
|
||||
sessionState jmap.SessionState,
|
||||
objectType ResponseObjectType,
|
||||
etag jmap.State,
|
||||
contentLanguage jmap.Language,
|
||||
durations []time.Duration,
|
||||
) Response {
|
||||
return Response{
|
||||
accountIds: accountIds,
|
||||
body: nil,
|
||||
@@ -206,11 +293,18 @@ func etaggedNotFoundResponse(accountIds []jmap.AccountId, sessionState jmap.Sess
|
||||
sessionState: sessionState,
|
||||
contentLanguage: contentLanguage,
|
||||
next: NoNextToken,
|
||||
durations: durations,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Request) etaggedNotFound(accountId jmap.AccountId, sessionState jmap.SessionState, objectType ResponseObjectType, etag jmap.State) Response {
|
||||
return etaggedNotFoundResponse(single(accountId), sessionState, objectType, etag, jmap.Language(r.language()))
|
||||
func (r *Request) etaggedNotFound(
|
||||
accountId jmap.AccountId,
|
||||
sessionState jmap.SessionState,
|
||||
objectType ResponseObjectType,
|
||||
etag jmap.State,
|
||||
durations []time.Duration,
|
||||
) Response {
|
||||
return etaggedNotFoundResponse(single(accountId), sessionState, objectType, etag, jmap.Language(r.language()), durations)
|
||||
}
|
||||
|
||||
func notImplementedResponse(accountIds []jmap.AccountId, sessionState jmap.SessionState, objectType ResponseObjectType) Response {
|
||||
@@ -222,6 +316,7 @@ func notImplementedResponse(accountIds []jmap.AccountId, sessionState jmap.Sessi
|
||||
objectType: objectType,
|
||||
sessionState: sessionState,
|
||||
next: NoNextToken,
|
||||
durations: nil,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jmap"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
@@ -35,7 +36,7 @@ func curryQueryFunc[SRES jmap.SearchResults[T], T jmap.Foo, FILTER any, COMP any
|
||||
return func(_ Request, accountIds []jmap.AccountId, qps QueryParamsSupplier, limit *uint, filter FILTER, sortBy []COMP, ctx jmap.Context) (jmap.Result[SRES], NextToken, error) { //NOSONAR
|
||||
result, next, err := f(accountIds, qps, limit, filter, sortBy, true, ctx)
|
||||
if err != nil {
|
||||
return jmap.ZeroResult[SRES](), next, err
|
||||
return jmap.ZeroResult[SRES](result.Durations), next, err
|
||||
} else {
|
||||
return result, next, err
|
||||
}
|
||||
@@ -91,7 +92,7 @@ func slist[T jmap.Idable, G jmap.GetResponse[T], S ListSupplier[T, G]](suppliers
|
||||
ctor func(accountId jmap.AccountId, state jmap.State, notFound []string, list []T) G) (jmap.Result[G], error) {
|
||||
switch len(suppliers) {
|
||||
case 0:
|
||||
return jmap.ZeroResult[G](), nil
|
||||
return jmap.ZeroResultV[G](), nil
|
||||
case 1:
|
||||
return suppliers[0].GetAll(accountId, ids, ctx)
|
||||
default:
|
||||
@@ -177,16 +178,16 @@ func squery[T jmap.Idable, R jmap.SearchResults[T], S QuerySupplier[T, R, F, C],
|
||||
) {
|
||||
s := len(suppliers)
|
||||
if s == 0 {
|
||||
return jmap.ZeroResult[R](), NoNextToken, nil
|
||||
return jmap.ZeroResultV[R](), NoNextToken, nil
|
||||
}
|
||||
|
||||
switch s {
|
||||
case 1:
|
||||
supplier := suppliers[0]
|
||||
if result, err := supplier.Query(accountIds, qps, limit, filter, sortBy, calculateTotal, ctx); err != nil {
|
||||
return jmap.ZeroResult[R](), NoNextToken, err
|
||||
return jmap.ZeroResult[R](result.Durations), NoNextToken, err
|
||||
} else if len(accountIds) == 0 {
|
||||
return jmap.ZeroResult[R](), NoNextToken, nil
|
||||
return jmap.ZeroResult[R](result.Durations), NoNextToken, nil
|
||||
} else if len(accountIds) == 1 {
|
||||
accountId := accountIds[0]
|
||||
// use anchor and anchorOffset and limit:
|
||||
@@ -199,7 +200,7 @@ func squery[T jmap.Idable, R jmap.SearchResults[T], S QuerySupplier[T, R, F, C],
|
||||
n[accountId] = jmap.QueryParams{Anchor: last.GetId(), AnchorOffset: ptr(1)}
|
||||
}
|
||||
if nextToken, err := nextSingle(n); err != nil {
|
||||
return jmap.ZeroResult[R](), NoNextToken, err
|
||||
return jmap.ZeroResult[R](result.Durations), NoNextToken, err
|
||||
} else {
|
||||
single, err := jmap.RefineResultPayload(result, func(m map[jmap.AccountId]R) (R, bool, error) {
|
||||
if r, ok := m[accountId]; ok {
|
||||
@@ -272,19 +273,18 @@ func squery[T jmap.Idable, R jmap.SearchResults[T], S QuerySupplier[T, R, F, C],
|
||||
n[accountId] = jmap.QueryParams{Anchor: lastId, AnchorOffset: ptr(1)}
|
||||
}
|
||||
if err := fillMissingAccounts(qps, supplier.GetId(), accountIds, n); err != nil {
|
||||
return jmap.ZeroResult[R](), NoNextToken, err
|
||||
return jmap.ZeroResult[R](result.Durations), NoNextToken, err
|
||||
}
|
||||
|
||||
nextBySupplier := map[SupplierId]map[jmap.AccountId]jmap.QueryParams{supplier.GetId(): n}
|
||||
if nextToken, err := nextMulti(nextBySupplier); err != nil {
|
||||
return jmap.ZeroResult[R](), NoNextToken, err
|
||||
return jmap.ZeroResult[R](result.Durations), NoNextToken, err
|
||||
} else {
|
||||
return jmap.Result[R]{
|
||||
Payload: r,
|
||||
SessionState: result.GetSessionState(),
|
||||
State: result.GetState(),
|
||||
Language: result.GetLanguage(),
|
||||
}, nextToken, nil
|
||||
if refined, err := jmap.RefineResultPayload(result, func(a map[jmap.AccountId]R) (R, bool, error) { return r, true, nil }); err != nil {
|
||||
return jmap.ZeroResult[R](result.Durations), NoNextToken, err
|
||||
} else {
|
||||
return refined, nextToken, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
@@ -295,11 +295,13 @@ func squery[T jmap.Idable, R jmap.SearchResults[T], S QuerySupplier[T, R, F, C],
|
||||
sessionState := jmap.EmptySessionState
|
||||
states := map[SupplierId]jmap.State{}
|
||||
lang := jmap.NoLanguage
|
||||
for _, supplier := range suppliers {
|
||||
durations := make([][]time.Duration, len(suppliers))
|
||||
for i, supplier := range suppliers {
|
||||
// we are not injecting id prefixes here for all the objects, as each supplier is responsible for doing that if necessary
|
||||
if result, err := supplier.Query(accountIds, qps, limit, filter, sortBy, calculateTotal, ctx); err != nil {
|
||||
return jmap.ZeroResult[R](), NoNextToken, err
|
||||
return jmap.ZeroResult[R](result.Durations), NoNextToken, err
|
||||
} else {
|
||||
durations[i] = result.Durations
|
||||
if result.GetSessionState() != jmap.EmptySessionState {
|
||||
sessionState = result.GetSessionState()
|
||||
}
|
||||
@@ -375,23 +377,18 @@ func squery[T jmap.Idable, R jmap.SearchResults[T], S QuerySupplier[T, R, F, C],
|
||||
n[accountId] = jmap.QueryParams{Anchor: lastId, AnchorOffset: ptr(1)}
|
||||
}
|
||||
if err := fillMissingAccounts(qps, supplierId, accountIds, n); err != nil {
|
||||
return jmap.ZeroResult[R](), NoNextToken, err
|
||||
return jmap.ZeroResult[R](structs.Flatten(durations)), NoNextToken, err
|
||||
}
|
||||
nextBySupplier[supplierId] = n
|
||||
}
|
||||
|
||||
if nextToken, err := nextMulti(nextBySupplier); err != nil {
|
||||
return jmap.ZeroResult[R](), NoNextToken, err
|
||||
return jmap.ZeroResult[R](structs.Flatten(durations)), NoNextToken, err
|
||||
} else {
|
||||
if state, err := combineState(states); err != nil {
|
||||
return jmap.ZeroResult[R](), NoNextToken, err
|
||||
return jmap.ZeroResult[R](structs.Flatten(durations)), NoNextToken, err
|
||||
} else {
|
||||
return jmap.Result[R]{
|
||||
Payload: r,
|
||||
SessionState: sessionState,
|
||||
State: state,
|
||||
Language: lang,
|
||||
}, nextToken, nil
|
||||
return jmap.NewResult(r, sessionState, state, lang, structs.Flatten(durations)), nextToken, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func inmemquery[T jmap.Idable, R jmap.SearchResults[T]](
|
||||
for _, accountId := range accountIds {
|
||||
qp := jmap.NullQueryParams
|
||||
if q, ok, err := qps.ForSupplier(supplierId, accountId); err != nil {
|
||||
return jmap.ZeroResult[map[jmap.AccountId]R](), err
|
||||
return jmap.ZeroResultV[map[jmap.AccountId]R](), err
|
||||
} else if ok {
|
||||
qp = q
|
||||
}
|
||||
@@ -91,12 +91,7 @@ func inmemquery[T jmap.Idable, R jmap.SearchResults[T]](
|
||||
|
||||
payload[accountId] = res
|
||||
}
|
||||
return jmap.Result[map[jmap.AccountId]R]{
|
||||
Payload: payload,
|
||||
SessionState: jmap.EmptySessionState,
|
||||
State: jmap.EmptyState,
|
||||
Language: jmap.NoLanguage,
|
||||
}, nil
|
||||
return jmap.NewResult(payload, jmap.EmptySessionState, jmap.EmptyState, jmap.NoLanguage, nil), nil
|
||||
}
|
||||
|
||||
func pets(
|
||||
@@ -158,7 +153,7 @@ func TestSquery(t *testing.T) {
|
||||
}
|
||||
n := func(accountIds []jmap.AccountId, nextToken NextToken, limit *uint) (jmap.Result[*PetSearchResults], NextToken, error) {
|
||||
if qps, err := unnext(nextToken); err != nil {
|
||||
return jmap.ZeroResult[*PetSearchResults](), NoNextToken, err
|
||||
return jmap.ZeroResultV[*PetSearchResults](), NoNextToken, err
|
||||
} else {
|
||||
return pets(suppliers, accountIds, qps, limit, PetFilterCondition{}, []PetComparator{{Property: "id", IsAscending: true}}, true, jmap.Context{})
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func create[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
var create CHANGE
|
||||
err := req.body(&create)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
@@ -49,10 +49,10 @@ func create[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
return req.respond(accountId, result.Payload, o.responseType, result)
|
||||
@@ -87,10 +87,10 @@ func getall[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.G
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
return req.respond(accountId, result.Payload, o.responseType, result)
|
||||
@@ -125,7 +125,7 @@ func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER
|
||||
{
|
||||
v, ok, err := req.parseUIntParam(QueryParamLimit, uint(0))
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, v)
|
||||
@@ -140,7 +140,7 @@ func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER
|
||||
supportedQueryParams = nextQueryParams
|
||||
if n, err := unnext(NextToken(s)); err != nil {
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(err.Error()))) // TODO replace ErrorGeneric
|
||||
return req.errorV(accountId, apiError(errorId, ErrorGeneric, withDetail(err.Error()))) // TODO replace ErrorGeneric
|
||||
} else {
|
||||
qps = n
|
||||
}
|
||||
@@ -155,7 +155,7 @@ func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER
|
||||
var err *Error
|
||||
containerId, err = req.PathParam(o.containerUriParamName)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l = l.Str(o.containerUriParamName, log.SafeString(containerId))
|
||||
}
|
||||
@@ -174,10 +174,10 @@ func getallpaged[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], FILTER
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
return req.respondNext(accountId, result.Payload, o.responseType, result, nextNextToken)
|
||||
@@ -206,7 +206,7 @@ func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULT
|
||||
var err *Error
|
||||
containerId, err = req.PathParam(o.containerUriParamName)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l = l.Str(o.containerUriParamName, log.SafeString(containerId))
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULT
|
||||
{
|
||||
v, ok, err := req.parseUIntParam(QueryParamLimit, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamLimit, v)
|
||||
@@ -232,11 +232,11 @@ func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULT
|
||||
supportedQueryParams = nextQueryParams
|
||||
if n, err := unnext(NextToken(s)); err != nil {
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(err.Error()))) // TODO replace ErrorGeneric
|
||||
return req.errorV(accountId, apiError(errorId, ErrorGeneric, withDetail(err.Error()))) // TODO replace ErrorGeneric
|
||||
} else {
|
||||
if q, ok, err := n.ForAccountId(accountId); err != nil {
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(err.Error()))) // TODO replace ErrorGeneric
|
||||
return req.errorV(accountId, apiError(errorId, ErrorGeneric, withDetail(err.Error()))) // TODO replace ErrorGeneric
|
||||
} else if ok {
|
||||
qp = q
|
||||
} else {
|
||||
@@ -310,10 +310,10 @@ func query[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], SEARCHRESULT
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
@@ -350,7 +350,7 @@ func get[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetR
|
||||
if o.uriParamName != "" {
|
||||
id, err := req.PathParamDoc(o.uriParamName, "The unique identifier of the object to retrieve")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
ids = single(id)
|
||||
@@ -368,10 +368,10 @@ func get[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jmap.GetR
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
n := len(result.Payload.GetList())
|
||||
@@ -405,7 +405,7 @@ func getFromMap[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jm
|
||||
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(o.uriParamName, "The unique identifier of the object to retrieve")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
@@ -421,10 +421,10 @@ func getFromMap[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T], RESP jm
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
if objs, ok := result.Payload[accountId]; ok {
|
||||
@@ -465,7 +465,7 @@ func changes[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
|
||||
maxChanges, ok, err := req.parseUIntParam(QueryParamMaxChanges, 0)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
l = l.Uint(QueryParamMaxChanges, maxChanges)
|
||||
@@ -488,10 +488,10 @@ func changes[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
return req.respond(accountId, result.Payload, o.responseType, result)
|
||||
@@ -517,7 +517,7 @@ func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(o.uriParamName, "The unique identifier of the object to delete")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
@@ -533,10 +533,10 @@ func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
for _, e := range result.Payload {
|
||||
@@ -546,12 +546,12 @@ func delete[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSONAR
|
||||
req.errorId(),
|
||||
o.failedToDeleteError,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
), result.GetDurations())
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
o.failedToDeleteError,
|
||||
))
|
||||
), result.GetDurations())
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, o.responseType, result)
|
||||
@@ -582,7 +582,7 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
|
||||
if o.uriParamName != "" {
|
||||
pathId, err := req.PathParam(o.uriParamName)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
ids = append(ids, pathId)
|
||||
@@ -591,7 +591,7 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
|
||||
{
|
||||
queryIds, ok, err := req.parseOptStringListParam(QueryParamId)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
if ok {
|
||||
ids = append(ids, queryIds...)
|
||||
@@ -601,13 +601,13 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
|
||||
var bodyIds []string
|
||||
err := req.body(&bodyIds)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
ids = append(ids, bodyIds...)
|
||||
}
|
||||
switch len(ids) {
|
||||
case 0:
|
||||
return req.noop(accountId)
|
||||
return req.noop(accountId, zeroDurations)
|
||||
case 1:
|
||||
l.Str("id", log.SafeString(ids[0]))
|
||||
default:
|
||||
@@ -626,10 +626,10 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
for _, e := range result.Payload {
|
||||
@@ -639,12 +639,12 @@ func deleteMany[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]]( //NOSO
|
||||
req.errorId(),
|
||||
o.failedToDeleteError,
|
||||
withDetail(e.Description),
|
||||
))
|
||||
), result.GetDurations())
|
||||
} else {
|
||||
return req.error(accountId, apiError(
|
||||
req.errorId(),
|
||||
o.failedToDeleteError,
|
||||
))
|
||||
), result.GetDurations())
|
||||
}
|
||||
}
|
||||
return req.noContent(accountId, o.responseType, result)
|
||||
@@ -668,7 +668,7 @@ func modify[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
l := req.logger.With().Str(logAccountId, log.SafeString(accountId))
|
||||
id, err := req.PathParamDoc(o.uriParamName, "The unique identifier of the object to modify")
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
l.Str(o.uriParamName, log.SafeString(id))
|
||||
|
||||
@@ -679,7 +679,7 @@ func modify[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
var change CHANGE
|
||||
err = req.body(&change)
|
||||
if err != nil {
|
||||
return req.error(accountId, err)
|
||||
return req.errorV(accountId, err)
|
||||
}
|
||||
|
||||
logger := log.From(l)
|
||||
@@ -690,10 +690,10 @@ func modify[T jmap.Foo, CHANGE jmap.Change, CHANGES jmap.Changes[T]](
|
||||
return req.jmapError(accountId, e, result)
|
||||
case GroupwareError:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, e))
|
||||
return req.error(accountId, apiError(errorId, e), result.GetDurations())
|
||||
default:
|
||||
errorId := req.errorId()
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())))
|
||||
return req.error(accountId, apiError(errorId, ErrorGeneric, withDetail(e.Error())), result.GetDurations())
|
||||
}
|
||||
} else {
|
||||
return req.respond(accountId, result.Payload, o.responseType, result)
|
||||
|
||||
Reference in New Issue
Block a user