fixup mailbox info (#3189)

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🐛 Bugfix
- [x] 🧹 Tech Debt/Cleanup

#### Test Plan

- [x] 💪 Manual
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-04-24 14:06:41 -06:00 committed by GitHub
parent 41f742eba2
commit b331f38654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 246 additions and 238 deletions

View File

@ -18,19 +18,19 @@ import (
"github.com/alcionai/corso/src/pkg/services/m365/api"
)
type DiscoveryIntegrationSuite struct {
type DiscoveryIntgSuite struct {
tester.Suite
}
func TestDiscoveryIntegrationSuite(t *testing.T) {
suite.Run(t, &DiscoveryIntegrationSuite{
func TestDiscoveryIntgSuite(t *testing.T) {
suite.Run(t, &DiscoveryIntgSuite{
Suite: tester.NewIntegrationSuite(
t,
[][]string{tester.M365AcctCredEnvs}),
})
}
func (suite *DiscoveryIntegrationSuite) TestUsers() {
func (suite *DiscoveryIntgSuite) TestUsers() {
ctx, flush := tester.NewContext()
defer flush()
@ -55,7 +55,7 @@ func (suite *DiscoveryIntegrationSuite) TestUsers() {
assert.NotEmpty(t, users)
}
func (suite *DiscoveryIntegrationSuite) TestUsers_InvalidCredentials() {
func (suite *DiscoveryIntgSuite) TestUsers_InvalidCredentials() {
table := []struct {
name string
acct func(t *testing.T) account.Account
@ -101,7 +101,7 @@ func (suite *DiscoveryIntegrationSuite) TestUsers_InvalidCredentials() {
}
}
func (suite *DiscoveryIntegrationSuite) TestSites() {
func (suite *DiscoveryIntgSuite) TestSites() {
ctx, flush := tester.NewContext()
defer flush()
@ -120,7 +120,7 @@ func (suite *DiscoveryIntegrationSuite) TestSites() {
assert.NotEmpty(t, sites)
}
func (suite *DiscoveryIntegrationSuite) TestSites_InvalidCredentials() {
func (suite *DiscoveryIntgSuite) TestSites_InvalidCredentials() {
ctx, flush := tester.NewContext()
defer flush()
@ -171,10 +171,9 @@ func (suite *DiscoveryIntegrationSuite) TestSites_InvalidCredentials() {
}
}
func (suite *DiscoveryIntegrationSuite) TestUserInfo() {
func (suite *DiscoveryIntgSuite) TestUserInfo() {
t := suite.T()
acct := tester.NewM365Account(t)
userID := tester.M365UserID(t)
creds, err := acct.M365Config()
require.NoError(t, err)
@ -188,34 +187,31 @@ func (suite *DiscoveryIntegrationSuite) TestUserInfo() {
name string
user string
expect *api.UserInfo
expectErr require.ErrorAssertionFunc
}{
{
name: "standard test user",
user: userID,
user: tester.M365UserID(t),
expect: &api.UserInfo{
DiscoveredServices: map[path.ServiceType]struct{}{
path.ExchangeService: {},
path.OneDriveService: {},
},
HasMailBox: true,
HasOneDrive: true,
Mailbox: api.MailboxInfo{
Purpose: "user",
ErrGetMailBoxSetting: nil,
},
},
expectErr: require.NoError,
},
{
name: "user does not exist",
user: uuid.NewString(),
expect: &api.UserInfo{
DiscoveredServices: map[path.ServiceType]struct{}{},
HasMailBox: false,
HasOneDrive: false,
Mailbox: api.MailboxInfo{
ErrGetMailBoxSetting: api.ErrMailBoxSettingsNotFound,
},
Mailbox: api.MailboxInfo{},
},
expectErr: require.NoError,
},
}
for _, test := range table {
@ -226,15 +222,18 @@ func (suite *DiscoveryIntegrationSuite) TestUserInfo() {
t := suite.T()
result, err := discovery.UserInfo(ctx, uapi, test.user)
require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, test.expect.HasMailBox, result.HasMailBox)
assert.Equal(t, test.expect.HasOneDrive, result.HasOneDrive)
test.expectErr(t, err, clues.ToCore(err))
if err != nil {
return
}
assert.Equal(t, test.expect.DiscoveredServices, result.DiscoveredServices)
})
}
}
func (suite *DiscoveryIntegrationSuite) TestUserWithoutDrive() {
func (suite *DiscoveryIntgSuite) TestUserWithoutDrive() {
t := suite.T()
acct := tester.NewM365Account(t)
userID := tester.M365UserID(t)
@ -249,10 +248,8 @@ func (suite *DiscoveryIntegrationSuite) TestUserWithoutDrive() {
user: "a53c26f7-5100-4acb-a910-4d20960b2c19", // User: testevents@10rqc2.onmicrosoft.com
expect: &api.UserInfo{
DiscoveredServices: map[path.ServiceType]struct{}{},
HasOneDrive: false,
HasMailBox: false,
Mailbox: api.MailboxInfo{
ErrGetMailBoxSetting: api.ErrMailBoxSettingsNotFound,
ErrGetMailBoxSetting: []error{api.ErrMailBoxSettingsNotFound},
},
},
},
@ -264,11 +261,9 @@ func (suite *DiscoveryIntegrationSuite) TestUserWithoutDrive() {
path.ExchangeService: {},
path.OneDriveService: {},
},
HasOneDrive: true,
HasMailBox: true,
Mailbox: api.MailboxInfo{
Purpose: "user",
ErrGetMailBoxSetting: nil,
ErrGetMailBoxSetting: []error{},
},
},
},
@ -283,8 +278,6 @@ func (suite *DiscoveryIntegrationSuite) TestUserWithoutDrive() {
result, err := discovery.GetUserInfo(ctx, acct, test.user, fault.New(true))
require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, test.expect.DiscoveredServices, result.DiscoveredServices)
assert.Equal(t, test.expect.HasOneDrive, result.HasOneDrive)
assert.Equal(t, test.expect.HasMailBox, result.HasMailBox)
assert.Equal(t, test.expect.Mailbox.ErrGetMailBoxSetting, result.Mailbox.ErrGetMailBoxSetting)
assert.Equal(t, test.expect.Mailbox.Purpose, result.Mailbox.Purpose)
})

View File

@ -14,10 +14,10 @@ import (
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/pkg/errors"
"golang.org/x/exp/slices"
"github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/logger"
)
@ -25,20 +25,22 @@ import (
// Error Interpretation Helpers
// ---------------------------------------------------------------------------
type errorCode string
const (
errCodeActivityLimitReached = "activityLimitReached"
errCodeItemNotFound = "ErrorItemNotFound"
errCodeItemNotFoundShort = "itemNotFound"
errCodeEmailFolderNotFound = "ErrorSyncFolderNotFound"
errCodeResyncRequired = "ResyncRequired" // alt: resyncRequired
errCodeMalwareDetected = "malwareDetected"
errCodeSyncFolderNotFound = "ErrorSyncFolderNotFound"
errCodeSyncStateNotFound = "SyncStateNotFound"
errCodeSyncStateInvalid = "SyncStateInvalid"
errCodeResourceNotFound = "ResourceNotFound"
errCodeRequestResourceNotFound = "Request_ResourceNotFound"
errCodeMailboxNotEnabledForRESTAPI = "MailboxNotEnabledForRESTAPI"
errCodeErrorAccessDenied = "ErrorAccessDenied"
activityLimitReached errorCode = "activityLimitReached"
emailFolderNotFound errorCode = "ErrorSyncFolderNotFound"
errorAccessDenied errorCode = "ErrorAccessDenied"
itemNotFound errorCode = "ErrorItemNotFound"
itemNotFoundShort errorCode = "itemNotFound"
mailboxNotEnabledForRESTAPI errorCode = "MailboxNotEnabledForRESTAPI"
malwareDetected errorCode = "malwareDetected"
requestResourceNotFound errorCode = "Request_ResourceNotFound"
resourceNotFound errorCode = "ResourceNotFound"
resyncRequired errorCode = "ResyncRequired" // alt: resyncRequired
syncFolderNotFound errorCode = "ErrorSyncFolderNotFound"
syncStateInvalid errorCode = "SyncStateInvalid"
syncStateNotFound errorCode = "SyncStateNotFound"
)
const (
@ -84,9 +86,9 @@ func IsErrDeletedInFlight(err error) bool {
if hasErrorCode(
err,
errCodeItemNotFound,
errCodeItemNotFoundShort,
errCodeSyncFolderNotFound,
itemNotFound,
itemNotFoundShort,
syncFolderNotFound,
) {
return true
}
@ -95,20 +97,24 @@ func IsErrDeletedInFlight(err error) bool {
}
func IsErrInvalidDelta(err error) bool {
return hasErrorCode(err, errCodeSyncStateNotFound, errCodeResyncRequired, errCodeSyncStateInvalid) ||
return hasErrorCode(err, syncStateNotFound, resyncRequired, syncStateInvalid) ||
errors.Is(err, ErrInvalidDelta)
}
func IsErrExchangeMailFolderNotFound(err error) bool {
return hasErrorCode(err, errCodeResourceNotFound, errCodeMailboxNotEnabledForRESTAPI)
return hasErrorCode(err, resourceNotFound, mailboxNotEnabledForRESTAPI)
}
func IsErrUserNotFound(err error) bool {
return hasErrorCode(err, errCodeRequestResourceNotFound)
return hasErrorCode(err, requestResourceNotFound)
}
func IsErrResourceNotFound(err error) bool {
return hasErrorCode(err, resourceNotFound)
}
func IsErrAccessDenied(err error) bool {
return hasErrorCode(err, errCodeErrorAccessDenied)
return hasErrorCode(err, errorAccessDenied) || clues.HasLabel(err, LabelStatus(http.StatusForbidden))
}
func IsErrTimeout(err error) bool {
@ -143,7 +149,7 @@ func LabelStatus(statusCode int) string {
// IsMalware is true if the graphAPI returns a "malware detected" error code.
func IsMalware(err error) bool {
return hasErrorCode(err, errCodeMalwareDetected)
return hasErrorCode(err, malwareDetected)
}
func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
@ -159,7 +165,7 @@ func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
return false
}
if strings.Contains(string(respDump), errCodeMalwareDetected) {
if strings.Contains(string(respDump), string(malwareDetected)) {
return true
}
@ -170,7 +176,7 @@ func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
// error parsers
// ---------------------------------------------------------------------------
func hasErrorCode(err error, codes ...string) bool {
func hasErrorCode(err error, codes ...errorCode) bool {
if err == nil {
return false
}
@ -180,16 +186,17 @@ func hasErrorCode(err error, codes ...string) bool {
return false
}
if oDataError.GetError().GetCode() == nil {
code, ok := ptr.ValOK(oDataError.GetError().GetCode())
if !ok {
return false
}
lcodes := []string{}
for _, c := range codes {
lcodes = append(lcodes, strings.ToLower(c))
cs := make([]string, len(codes))
for i, c := range codes {
cs[i] = string(c)
}
return slices.Contains(lcodes, strings.ToLower(*oDataError.GetError().GetCode()))
return filters.Equal(cs).Compare(code)
}
// Wrap is a helper function that extracts ODataError metadata from

View File

@ -90,12 +90,12 @@ func (suite *GraphErrorsUnitSuite) TestIsErrDeletedInFlight() {
},
{
name: "not-found oDataErr",
err: odErr(errCodeItemNotFound),
err: odErr(string(itemNotFound)),
expect: assert.True,
},
{
name: "sync-not-found oDataErr",
err: odErr(errCodeSyncFolderNotFound),
err: odErr(string(syncFolderNotFound)),
expect: assert.True,
},
}
@ -134,12 +134,12 @@ func (suite *GraphErrorsUnitSuite) TestIsErrInvalidDelta() {
},
{
name: "resync-required oDataErr",
err: odErr(errCodeResyncRequired),
err: odErr(string(resyncRequired)),
expect: assert.True,
},
{
name: "sync state invalid oDataErr",
err: odErr(errCodeSyncStateInvalid),
err: odErr(string(syncStateInvalid)),
expect: assert.True,
},
// next two tests are to make sure the checks are case insensitive
@ -184,7 +184,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUserNotFound() {
},
{
name: "request resource not found oDataErr",
err: odErr(errCodeRequestResourceNotFound),
err: odErr(string(requestResourceNotFound)),
expect: assert.True,
},
}

View File

@ -286,8 +286,7 @@ func TestOneDriveDriveSuite(t *testing.T) {
suite.Run(t, &OneDriveSuite{
Suite: tester.NewIntegrationSuite(
t,
[][]string{tester.M365AcctCredEnvs},
),
[][]string{tester.M365AcctCredEnvs}),
})
}

View File

@ -3,6 +3,7 @@ package api
import (
"context"
"fmt"
"net/http"
"github.com/alcionai/clues"
abstractions "github.com/microsoft/kiota-abstractions-go"
@ -41,8 +42,6 @@ type Users struct {
type UserInfo struct {
DiscoveredServices map[path.ServiceType]struct{}
HasMailBox bool
HasOneDrive bool
Mailbox MailboxInfo
}
@ -56,7 +55,7 @@ type MailboxInfo struct {
AutomaticRepliesSetting AutomaticRepliesSettings
Language Language
WorkingHours WorkingHours
ErrGetMailBoxSetting error
ErrGetMailBoxSetting []error
}
type AutomaticRepliesSettings struct {
@ -229,204 +228,212 @@ func (c Users) GetIDAndName(ctx context.Context, userID string) (string, string,
func (c Users) GetInfo(ctx context.Context, userID string) (*UserInfo, error) {
// Assume all services are enabled
// then filter down to only services the user has enabled
var (
err error
userInfo = newUserInfo()
userInfo := newUserInfo()
requestParameters = &users.ItemMailFoldersRequestBuilderGetQueryParameters{
requestParameters := users.ItemMailFoldersRequestBuilderGetQueryParameters{
Select: []string{"id"},
Top: ptr.To[int32](1), // if we get any folders, then we have access.
}
options = users.ItemMailFoldersRequestBuilderGetRequestConfiguration{
QueryParameters: requestParameters,
}
)
userInfo.HasMailBox = true
err = c.GetExchange(ctx, userID, options)
if err != nil {
if !graph.IsErrExchangeMailFolderNotFound(err) {
logger.Ctx(ctx).Errorf("err getting user's mail folder: %s", err)
return nil, graph.Wrap(ctx, err, "getting user's mail folder")
options := users.ItemMailFoldersRequestBuilderGetRequestConfiguration{
QueryParameters: &requestParameters,
}
logger.Ctx(ctx).Infof("resource owner does not have a mailbox enabled")
if _, err := c.GetMailFolders(ctx, userID, options); err != nil {
if graph.IsErrUserNotFound(err) {
logger.CtxErr(ctx, err).Error("user not found")
return nil, err
}
if !graph.IsErrExchangeMailFolderNotFound(err) ||
clues.HasLabel(err, graph.LabelStatus(http.StatusNotFound)) {
logger.CtxErr(ctx, err).Error("getting user's mail folder")
return nil, err
}
logger.Ctx(ctx).Info("resource owner does not have a mailbox enabled")
delete(userInfo.DiscoveredServices, path.ExchangeService)
userInfo.HasMailBox = false
}
userInfo.HasOneDrive = true
err = c.GetOnedrive(ctx, userID)
if err != nil {
err = graph.Stack(ctx, err)
if _, err := c.GetDrives(ctx, userID); err != nil {
if !clues.HasLabel(err, graph.LabelsMysiteNotFound) {
logger.Ctx(ctx).Errorf("err getting user's onedrive's data: %s", err)
logger.CtxErr(ctx, err).Error("getting user's drives")
return nil, graph.Wrap(ctx, err, "getting user's onedrive's data")
return nil, graph.Wrap(ctx, err, "getting user's drives")
}
logger.Ctx(ctx).Infof("resource owner does not have a drive")
logger.Ctx(ctx).Info("resource owner does not have a drive")
delete(userInfo.DiscoveredServices, path.OneDriveService)
userInfo.HasOneDrive = false
}
err = c.getAdditionalData(ctx, userID, &userInfo.Mailbox)
mbxInfo, err := c.getMailboxSettings(ctx, userID)
if err != nil {
return nil, err
}
userInfo.Mailbox = mbxInfo
return userInfo, nil
}
// verify mailbox enabled for user
func (c Users) GetExchange(
// TODO: remove when exchange api goes into this package
func (c Users) GetMailFolders(
ctx context.Context,
userID string,
options users.ItemMailFoldersRequestBuilderGetRequestConfiguration,
) error {
_, err := c.stable.Client().UsersById(userID).MailFolders().Get(ctx, &options)
) (models.MailFolderCollectionResponseable, error) {
mailFolders, err := c.stable.Client().UsersById(userID).MailFolders().Get(ctx, &options)
if err != nil {
return err
return nil, graph.Wrap(ctx, err, "getting MailFolders")
}
return nil
return mailFolders, nil
}
// verify onedrive enabled for user
func (c Users) GetOnedrive(ctx context.Context, userID string) error {
_, err := c.stable.Client().UsersById(userID).Drives().Get(ctx, nil)
// TODO: remove when drive api goes into this package
func (c Users) GetDrives(ctx context.Context, userID string) (models.DriveCollectionResponseable, error) {
drives, err := c.stable.Client().UsersById(userID).Drives().Get(ctx, nil)
if err != nil {
return err
return nil, graph.Wrap(ctx, err, "getting drives")
}
return nil
return drives, nil
}
func (c Users) getAdditionalData(ctx context.Context, userID string, mailbox *MailboxInfo) error {
func (c Users) getMailboxSettings(
ctx context.Context,
userID string,
) (MailboxInfo, error) {
var (
rawURL = fmt.Sprintf("https://graph.microsoft.com/v1.0/users/%s/mailboxSettings", userID)
adapter = c.stable.Adapter()
mailBoundErr clues.Err
mi = MailboxInfo{
ErrGetMailBoxSetting: []error{},
}
)
settings, err := users.NewUserItemRequestBuilder(rawURL, adapter).Get(ctx, nil)
if err != nil && !(graph.IsErrAccessDenied(err) || graph.IsErrExchangeMailFolderNotFound(err)) {
logger.CtxErr(ctx, err).Error("getting mailbox settings")
return graph.Wrap(ctx, err, "getting additional data")
return mi, graph.Wrap(ctx, err, "getting additional data")
}
if graph.IsErrAccessDenied(err) {
logger.Ctx(ctx).Info("err getting additional data: access denied")
mailbox.ErrGetMailBoxSetting = clues.New("access denied")
mi.ErrGetMailBoxSetting = append(mi.ErrGetMailBoxSetting, clues.New("access denied"))
return nil
return mi, nil
}
if graph.IsErrExchangeMailFolderNotFound(err) {
logger.Ctx(ctx).Info("err exchange mail folder not found")
logger.Ctx(ctx).Info("mailfolders not found")
mailbox.ErrGetMailBoxSetting = ErrMailBoxSettingsNotFound
mi.ErrGetMailBoxSetting = append(mi.ErrGetMailBoxSetting, ErrMailBoxSettingsNotFound)
return nil
return mi, nil
}
additionalData := settings.GetAdditionalData()
mailbox.ArchiveFolder = toString(ctx, additionalData["archiveFolder"], &mailBoundErr)
mailbox.Timezone = toString(ctx, additionalData["timeZone"], &mailBoundErr)
mailbox.DateFormat = toString(ctx, additionalData["dateFormat"], &mailBoundErr)
mailbox.TimeFormat = toString(ctx, additionalData["timeFormat"], &mailBoundErr)
mailbox.Purpose = toString(ctx, additionalData["userPurpose"], &mailBoundErr)
mailbox.DelegateMeetMsgDeliveryOpt = toString(
ctx,
additionalData["delegateMeetingMessageDeliveryOptions"],
&mailBoundErr)
mi.ArchiveFolder, err = toString(ctx, "archiveFolder", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.Timezone, err = toString(ctx, "timeZone", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.DateFormat, err = toString(ctx, "dateFormat", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.TimeFormat, err = toString(ctx, "timeFormat", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.Purpose, err = toString(ctx, "userPurpose", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.DelegateMeetMsgDeliveryOpt, err = toString(ctx, "delegateMeetingMessageDeliveryOptions", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
// decode automatic replies settings
replySetting := toMap(ctx, additionalData["automaticRepliesSetting"], &mailBoundErr)
mailbox.AutomaticRepliesSetting.Status = toString(
ctx,
replySetting["status"],
&mailBoundErr)
mailbox.AutomaticRepliesSetting.ExternalAudience = toString(
ctx,
replySetting["externalAudience"],
&mailBoundErr)
mailbox.AutomaticRepliesSetting.ExternalReplyMessage = toString(
ctx,
replySetting["externalReplyMessage"],
&mailBoundErr)
mailbox.AutomaticRepliesSetting.InternalReplyMessage = toString(
ctx,
replySetting["internalReplyMessage"],
&mailBoundErr)
replySetting, err := toT[map[string]any](ctx, "automaticRepliesSetting", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.AutomaticRepliesSetting.Status, err = toString(ctx, "status", replySetting)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.AutomaticRepliesSetting.ExternalAudience, err = toString(ctx, "externalAudience", replySetting)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.AutomaticRepliesSetting.ExternalReplyMessage, err = toString(ctx, "externalReplyMessage", replySetting)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.AutomaticRepliesSetting.InternalReplyMessage, err = toString(ctx, "internalReplyMessage", replySetting)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
// decode scheduledStartDateTime
startDateTime := toMap(ctx, replySetting["scheduledStartDateTime"], &mailBoundErr)
mailbox.AutomaticRepliesSetting.ScheduledStartDateTime.DateTime = toString(
ctx,
startDateTime["dateTime"],
&mailBoundErr)
mailbox.AutomaticRepliesSetting.ScheduledStartDateTime.Timezone = toString(
ctx,
startDateTime["timeZone"],
&mailBoundErr)
startDateTime, err := toT[map[string]any](ctx, "scheduledStartDateTime", replySetting)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
endDateTime := toMap(ctx, replySetting["scheduledEndDateTime"], &mailBoundErr)
mailbox.AutomaticRepliesSetting.ScheduledEndDateTime.DateTime = toString(
ctx,
endDateTime["dateTime"],
&mailBoundErr)
mailbox.AutomaticRepliesSetting.ScheduledEndDateTime.Timezone = toString(
ctx,
endDateTime["timeZone"],
&mailBoundErr)
mi.AutomaticRepliesSetting.ScheduledStartDateTime.DateTime, err = toString(ctx, "dateTime", startDateTime)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.AutomaticRepliesSetting.ScheduledStartDateTime.Timezone, err = toString(ctx, "timeZone", startDateTime)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
endDateTime, err := toT[map[string]any](ctx, "scheduledEndDateTime", replySetting)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.AutomaticRepliesSetting.ScheduledEndDateTime.DateTime, err = toString(ctx, "dateTime", endDateTime)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.AutomaticRepliesSetting.ScheduledEndDateTime.Timezone, err = toString(ctx, "timeZone", endDateTime)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
// Language decode
language := toMap(ctx, additionalData["language"], &mailBoundErr)
mailbox.Language.DisplayName = toString(
ctx,
language["displayName"],
&mailBoundErr)
mailbox.Language.Locale = toString(ctx, language["locale"], &mailBoundErr)
language, err := toT[map[string]any](ctx, "language", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.Language.DisplayName, err = toString(ctx, "displayName", language)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.Language.Locale, err = toString(ctx, "locale", language)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
// working hours
workingHours := toMap(ctx, additionalData["workingHours"], &mailBoundErr)
mailbox.WorkingHours.StartTime = toString(
ctx,
workingHours["startTime"],
&mailBoundErr)
mailbox.WorkingHours.EndTime = toString(
ctx,
workingHours["endTime"],
&mailBoundErr)
workingHours, err := toT[map[string]any](ctx, "workingHours", additionalData)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
timeZone := toMap(ctx, workingHours["timeZone"], &mailBoundErr)
mailbox.WorkingHours.TimeZone.Name = toString(
ctx,
timeZone["name"],
&mailBoundErr)
mi.WorkingHours.StartTime, err = toString(ctx, "startTime", workingHours)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.WorkingHours.EndTime, err = toString(ctx, "endTime", workingHours)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
timeZone, err := toT[map[string]any](ctx, "timeZone", workingHours)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.WorkingHours.TimeZone.Name, err = toString(ctx, "name", timeZone)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
days, err := toT[[]any](ctx, "daysOfWeek", workingHours)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
days := toArray(ctx, workingHours["daysOfWeek"], &mailBoundErr)
for _, day := range days {
mailbox.WorkingHours.DaysOfWeek = append(mailbox.WorkingHours.DaysOfWeek,
toString(ctx, day, &mailBoundErr))
s, err := anyToString(ctx, "dayOfTheWeek", day)
mi.ErrGetMailBoxSetting = appendIfErr(mi.ErrGetMailBoxSetting, err)
mi.WorkingHours.DaysOfWeek = append(mi.WorkingHours.DaysOfWeek, s)
}
if mailBoundErr.Core().Msg != "" {
mailbox.ErrGetMailBoxSetting = &mailBoundErr
return mi, nil
}
return nil
func appendIfErr(errs []error, err error) []error {
if err == nil {
return errs
}
return append(errs, err)
}
// ---------------------------------------------------------------------------
@ -453,50 +460,52 @@ func validateUser(item any) (models.Userable, error) {
return m, nil
}
func toString(ctx context.Context, data any, mailBoxErr *clues.Err) string {
dataPointer, ok := data.(*string)
func toString(ctx context.Context, key string, data map[string]any) (string, error) {
ctx = clues.Add(ctx, "setting_name", key)
if len(data) == 0 {
logger.Ctx(ctx).Info("not found: ", key)
return "", ErrMailBoxSettingsNotFound
}
return anyToString(ctx, key, data[key])
}
func anyToString(ctx context.Context, key string, val any) (string, error) {
if val == nil {
logger.Ctx(ctx).Info("nil value: ", key)
return "", ErrMailBoxSettingsNotFound
}
sp, ok := val.(*string)
if !ok {
logger.Ctx(ctx).Info("error getting data from mailboxSettings")
*mailBoxErr = *ErrMailBoxSettingsNotFound
return ""
logger.Ctx(ctx).Info("value is not a *string: ", key)
return "", ErrMailBoxSettingsNotFound
}
value, ok := ptr.ValOK(dataPointer)
return ptr.Val(sp), nil
}
func toT[T any](ctx context.Context, key string, data map[string]any) (T, error) {
ctx = clues.Add(ctx, "setting_name", key)
if len(data) == 0 {
logger.Ctx(ctx).Info("not found: ", key)
return *new(T), ErrMailBoxSettingsNotFound
}
val := data[key]
if data == nil {
logger.Ctx(ctx).Info("nil value: ", key)
return *new(T), ErrMailBoxSettingsNotFound
}
value, ok := val.(T)
if !ok {
logger.Ctx(ctx).Info("error getting value from pointer for mailboxSettings")
*mailBoxErr = *ErrMailBoxSettingsNotFound
return ""
logger.Ctx(ctx).Info(fmt.Sprintf("unexpected type for %s: %T", key, val))
return *new(T), ErrMailBoxSettingsNotFound
}
return value
}
func toMap(ctx context.Context, data any, mailBoxErr *clues.Err) map[string]interface{} {
value, ok := data.(map[string]interface{})
if !ok {
logger.Ctx(ctx).Info("error getting mailboxSettings")
*mailBoxErr = *clues.New("mailbox settings not found")
return value
}
return value
}
func toArray(ctx context.Context, data any, mailBoxErr *clues.Err) []interface{} {
value, ok := data.([]interface{})
if !ok {
logger.Ctx(ctx).Info("error getting mailboxSettings")
*mailBoxErr = *clues.New("mailbox settings not found")
return value
}
return value
return value, nil
}