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" "github.com/alcionai/corso/src/pkg/services/m365/api"
) )
type DiscoveryIntegrationSuite struct { type DiscoveryIntgSuite struct {
tester.Suite tester.Suite
} }
func TestDiscoveryIntegrationSuite(t *testing.T) { func TestDiscoveryIntgSuite(t *testing.T) {
suite.Run(t, &DiscoveryIntegrationSuite{ suite.Run(t, &DiscoveryIntgSuite{
Suite: tester.NewIntegrationSuite( Suite: tester.NewIntegrationSuite(
t, t,
[][]string{tester.M365AcctCredEnvs}), [][]string{tester.M365AcctCredEnvs}),
}) })
} }
func (suite *DiscoveryIntegrationSuite) TestUsers() { func (suite *DiscoveryIntgSuite) TestUsers() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -55,7 +55,7 @@ func (suite *DiscoveryIntegrationSuite) TestUsers() {
assert.NotEmpty(t, users) assert.NotEmpty(t, users)
} }
func (suite *DiscoveryIntegrationSuite) TestUsers_InvalidCredentials() { func (suite *DiscoveryIntgSuite) TestUsers_InvalidCredentials() {
table := []struct { table := []struct {
name string name string
acct func(t *testing.T) account.Account 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() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -120,7 +120,7 @@ func (suite *DiscoveryIntegrationSuite) TestSites() {
assert.NotEmpty(t, sites) assert.NotEmpty(t, sites)
} }
func (suite *DiscoveryIntegrationSuite) TestSites_InvalidCredentials() { func (suite *DiscoveryIntgSuite) TestSites_InvalidCredentials() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -171,10 +171,9 @@ func (suite *DiscoveryIntegrationSuite) TestSites_InvalidCredentials() {
} }
} }
func (suite *DiscoveryIntegrationSuite) TestUserInfo() { func (suite *DiscoveryIntgSuite) TestUserInfo() {
t := suite.T() t := suite.T()
acct := tester.NewM365Account(t) acct := tester.NewM365Account(t)
userID := tester.M365UserID(t)
creds, err := acct.M365Config() creds, err := acct.M365Config()
require.NoError(t, err) require.NoError(t, err)
@ -185,37 +184,34 @@ func (suite *DiscoveryIntegrationSuite) TestUserInfo() {
uapi := cli.Users() uapi := cli.Users()
table := []struct { table := []struct {
name string name string
user string user string
expect *api.UserInfo expect *api.UserInfo
expectErr require.ErrorAssertionFunc
}{ }{
{ {
name: "standard test user", name: "standard test user",
user: userID, user: tester.M365UserID(t),
expect: &api.UserInfo{ expect: &api.UserInfo{
DiscoveredServices: map[path.ServiceType]struct{}{ DiscoveredServices: map[path.ServiceType]struct{}{
path.ExchangeService: {}, path.ExchangeService: {},
path.OneDriveService: {}, path.OneDriveService: {},
}, },
HasMailBox: true,
HasOneDrive: true,
Mailbox: api.MailboxInfo{ Mailbox: api.MailboxInfo{
Purpose: "user", Purpose: "user",
ErrGetMailBoxSetting: nil, ErrGetMailBoxSetting: nil,
}, },
}, },
expectErr: require.NoError,
}, },
{ {
name: "user does not exist", name: "user does not exist",
user: uuid.NewString(), user: uuid.NewString(),
expect: &api.UserInfo{ expect: &api.UserInfo{
DiscoveredServices: map[path.ServiceType]struct{}{}, DiscoveredServices: map[path.ServiceType]struct{}{},
HasMailBox: false, Mailbox: api.MailboxInfo{},
HasOneDrive: false,
Mailbox: api.MailboxInfo{
ErrGetMailBoxSetting: api.ErrMailBoxSettingsNotFound,
},
}, },
expectErr: require.NoError,
}, },
} }
for _, test := range table { for _, test := range table {
@ -226,15 +222,18 @@ func (suite *DiscoveryIntegrationSuite) TestUserInfo() {
t := suite.T() t := suite.T()
result, err := discovery.UserInfo(ctx, uapi, test.user) result, err := discovery.UserInfo(ctx, uapi, test.user)
require.NoError(t, err, clues.ToCore(err)) test.expectErr(t, err, clues.ToCore(err))
assert.Equal(t, test.expect.HasMailBox, result.HasMailBox)
assert.Equal(t, test.expect.HasOneDrive, result.HasOneDrive) if err != nil {
return
}
assert.Equal(t, test.expect.DiscoveredServices, result.DiscoveredServices) assert.Equal(t, test.expect.DiscoveredServices, result.DiscoveredServices)
}) })
} }
} }
func (suite *DiscoveryIntegrationSuite) TestUserWithoutDrive() { func (suite *DiscoveryIntgSuite) TestUserWithoutDrive() {
t := suite.T() t := suite.T()
acct := tester.NewM365Account(t) acct := tester.NewM365Account(t)
userID := tester.M365UserID(t) userID := tester.M365UserID(t)
@ -249,10 +248,8 @@ func (suite *DiscoveryIntegrationSuite) TestUserWithoutDrive() {
user: "a53c26f7-5100-4acb-a910-4d20960b2c19", // User: testevents@10rqc2.onmicrosoft.com user: "a53c26f7-5100-4acb-a910-4d20960b2c19", // User: testevents@10rqc2.onmicrosoft.com
expect: &api.UserInfo{ expect: &api.UserInfo{
DiscoveredServices: map[path.ServiceType]struct{}{}, DiscoveredServices: map[path.ServiceType]struct{}{},
HasOneDrive: false,
HasMailBox: false,
Mailbox: api.MailboxInfo{ Mailbox: api.MailboxInfo{
ErrGetMailBoxSetting: api.ErrMailBoxSettingsNotFound, ErrGetMailBoxSetting: []error{api.ErrMailBoxSettingsNotFound},
}, },
}, },
}, },
@ -264,11 +261,9 @@ func (suite *DiscoveryIntegrationSuite) TestUserWithoutDrive() {
path.ExchangeService: {}, path.ExchangeService: {},
path.OneDriveService: {}, path.OneDriveService: {},
}, },
HasOneDrive: true,
HasMailBox: true,
Mailbox: api.MailboxInfo{ Mailbox: api.MailboxInfo{
Purpose: "user", 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)) result, err := discovery.GetUserInfo(ctx, acct, test.user, fault.New(true))
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
assert.Equal(t, test.expect.DiscoveredServices, result.DiscoveredServices) 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.ErrGetMailBoxSetting, result.Mailbox.ErrGetMailBoxSetting)
assert.Equal(t, test.expect.Mailbox.Purpose, result.Mailbox.Purpose) 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"
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/exp/slices"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/logger"
) )
@ -25,20 +25,22 @@ import (
// Error Interpretation Helpers // Error Interpretation Helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
type errorCode string
const ( const (
errCodeActivityLimitReached = "activityLimitReached" activityLimitReached errorCode = "activityLimitReached"
errCodeItemNotFound = "ErrorItemNotFound" emailFolderNotFound errorCode = "ErrorSyncFolderNotFound"
errCodeItemNotFoundShort = "itemNotFound" errorAccessDenied errorCode = "ErrorAccessDenied"
errCodeEmailFolderNotFound = "ErrorSyncFolderNotFound" itemNotFound errorCode = "ErrorItemNotFound"
errCodeResyncRequired = "ResyncRequired" // alt: resyncRequired itemNotFoundShort errorCode = "itemNotFound"
errCodeMalwareDetected = "malwareDetected" mailboxNotEnabledForRESTAPI errorCode = "MailboxNotEnabledForRESTAPI"
errCodeSyncFolderNotFound = "ErrorSyncFolderNotFound" malwareDetected errorCode = "malwareDetected"
errCodeSyncStateNotFound = "SyncStateNotFound" requestResourceNotFound errorCode = "Request_ResourceNotFound"
errCodeSyncStateInvalid = "SyncStateInvalid" resourceNotFound errorCode = "ResourceNotFound"
errCodeResourceNotFound = "ResourceNotFound" resyncRequired errorCode = "ResyncRequired" // alt: resyncRequired
errCodeRequestResourceNotFound = "Request_ResourceNotFound" syncFolderNotFound errorCode = "ErrorSyncFolderNotFound"
errCodeMailboxNotEnabledForRESTAPI = "MailboxNotEnabledForRESTAPI" syncStateInvalid errorCode = "SyncStateInvalid"
errCodeErrorAccessDenied = "ErrorAccessDenied" syncStateNotFound errorCode = "SyncStateNotFound"
) )
const ( const (
@ -84,9 +86,9 @@ func IsErrDeletedInFlight(err error) bool {
if hasErrorCode( if hasErrorCode(
err, err,
errCodeItemNotFound, itemNotFound,
errCodeItemNotFoundShort, itemNotFoundShort,
errCodeSyncFolderNotFound, syncFolderNotFound,
) { ) {
return true return true
} }
@ -95,20 +97,24 @@ func IsErrDeletedInFlight(err error) bool {
} }
func IsErrInvalidDelta(err error) bool { func IsErrInvalidDelta(err error) bool {
return hasErrorCode(err, errCodeSyncStateNotFound, errCodeResyncRequired, errCodeSyncStateInvalid) || return hasErrorCode(err, syncStateNotFound, resyncRequired, syncStateInvalid) ||
errors.Is(err, ErrInvalidDelta) errors.Is(err, ErrInvalidDelta)
} }
func IsErrExchangeMailFolderNotFound(err error) bool { func IsErrExchangeMailFolderNotFound(err error) bool {
return hasErrorCode(err, errCodeResourceNotFound, errCodeMailboxNotEnabledForRESTAPI) return hasErrorCode(err, resourceNotFound, mailboxNotEnabledForRESTAPI)
} }
func IsErrUserNotFound(err error) bool { 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 { func IsErrAccessDenied(err error) bool {
return hasErrorCode(err, errCodeErrorAccessDenied) return hasErrorCode(err, errorAccessDenied) || clues.HasLabel(err, LabelStatus(http.StatusForbidden))
} }
func IsErrTimeout(err error) bool { 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. // IsMalware is true if the graphAPI returns a "malware detected" error code.
func IsMalware(err error) bool { func IsMalware(err error) bool {
return hasErrorCode(err, errCodeMalwareDetected) return hasErrorCode(err, malwareDetected)
} }
func IsMalwareResp(ctx context.Context, resp *http.Response) bool { func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
@ -159,7 +165,7 @@ func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
return false return false
} }
if strings.Contains(string(respDump), errCodeMalwareDetected) { if strings.Contains(string(respDump), string(malwareDetected)) {
return true return true
} }
@ -170,7 +176,7 @@ func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
// error parsers // error parsers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func hasErrorCode(err error, codes ...string) bool { func hasErrorCode(err error, codes ...errorCode) bool {
if err == nil { if err == nil {
return false return false
} }
@ -180,16 +186,17 @@ func hasErrorCode(err error, codes ...string) bool {
return false return false
} }
if oDataError.GetError().GetCode() == nil { code, ok := ptr.ValOK(oDataError.GetError().GetCode())
if !ok {
return false return false
} }
lcodes := []string{} cs := make([]string, len(codes))
for _, c := range codes { for i, c := range codes {
lcodes = append(lcodes, strings.ToLower(c)) 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 // Wrap is a helper function that extracts ODataError metadata from

View File

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

View File

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

View File

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