add clues, fault to selectors (#2335)

## Description

Adds clues and fault handling to selectors pkg.
Some bleed upward into the CLI occured from
where the cli directly calls selectors.Reduce.

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

- [x]  No 

## Type of change

- [x] 🧹 Tech Debt/Cleanup

## Issue(s)

* #1970

## Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-02-06 15:06:50 -07:00 committed by GitHub
parent faad5d35a4
commit 5537a11948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 134 additions and 91 deletions

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
@ -470,9 +471,10 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
defer utils.CloseRepo(ctx, r) defer utils.CloseRepo(ctx, r)
ds, err := runDetailsExchangeCmd(ctx, r, backupID, opts) ds, errs := runDetailsExchangeCmd(ctx, r, backupID, opts)
if err != nil { if errs.Err() != nil {
return Only(ctx, err) // TODO: log/display iterated errors
return Only(ctx, errs.Err())
} }
if len(ds.Entries) == 0 { if len(ds.Entries) == 0 {
@ -486,29 +488,33 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
} }
// runDetailsExchangeCmd actually performs the lookup in backup details. // runDetailsExchangeCmd actually performs the lookup in backup details.
// the fault.Errors return is always non-nil. Callers should check if
// errs.Err() == nil.
func runDetailsExchangeCmd( func runDetailsExchangeCmd(
ctx context.Context, ctx context.Context,
r repository.BackupGetter, r repository.BackupGetter,
backupID string, backupID string,
opts utils.ExchangeOpts, opts utils.ExchangeOpts,
) (*details.Details, error) { ) (*details.Details, *fault.Errors) {
errs := fault.New(false)
if err := utils.ValidateExchangeRestoreFlags(backupID, opts); err != nil { if err := utils.ValidateExchangeRestoreFlags(backupID, opts); err != nil {
return nil, err return nil, errs.Fail(err)
} }
d, _, err := r.BackupDetails(ctx, backupID) d, _, err := r.BackupDetails(ctx, backupID)
if err != nil { if err != nil {
if errors.Is(err, kopia.ErrNotFound) { if errors.Is(err, kopia.ErrNotFound) {
return nil, errors.Errorf("No backup exists with the id %s", backupID) return nil, errs.Fail(errors.Errorf("No backup exists with the id %s", backupID))
} }
return nil, errors.Wrap(err, "Failed to get backup details in the repository") return nil, errs.Fail(errors.Wrap(err, "Failed to get backup details in the repository"))
} }
sel := utils.IncludeExchangeRestoreDataSelectors(opts) sel := utils.IncludeExchangeRestoreDataSelectors(opts)
utils.FilterExchangeRestoreInfoSelectors(sel, opts) utils.FilterExchangeRestoreInfoSelectors(sel, opts)
return sel.Reduce(ctx, d), nil return sel.Reduce(ctx, d, errs), errs
} }
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------

View File

@ -223,33 +223,14 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectors() {
ctx, ctx,
test.BackupGetter, test.BackupGetter,
"backup-ID", "backup-ID",
test.Opts, test.Opts)
) assert.NoError(t, err.Err(), "failure")
assert.NoError(t, err) assert.Empty(t, err.Errs(), "recovered errors")
assert.ElementsMatch(t, test.Expected, output.Entries) assert.ElementsMatch(t, test.Expected, output.Entries)
}) })
} }
} }
func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadBackupID() {
t := suite.T()
ctx, flush := tester.NewContext()
backupGetter := &testdata.MockBackupGetter{}
defer flush()
output, err := runDetailsExchangeCmd(
ctx,
backupGetter,
"backup-ID",
utils.ExchangeOpts{},
)
assert.Error(t, err)
assert.Empty(t, output)
}
func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadFormats() { func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadFormats() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
@ -260,10 +241,9 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailsSelectorsBadFormats() {
ctx, ctx,
test.BackupGetter, test.BackupGetter,
"backup-ID", "backup-ID",
test.Opts, test.Opts)
) assert.Error(t, err.Err(), "failure")
assert.Empty(t, err.Errs(), "recovered errors")
assert.Error(t, err)
assert.Empty(t, output) assert.Empty(t, output)
}) })
} }

View File

@ -16,6 +16,7 @@ import (
"github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
@ -362,9 +363,10 @@ func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
Populated: utils.GetPopulatedFlags(cmd), Populated: utils.GetPopulatedFlags(cmd),
} }
ds, err := runDetailsOneDriveCmd(ctx, r, backupID, opts) ds, errs := runDetailsOneDriveCmd(ctx, r, backupID, opts)
if err != nil { if errs.Err() != nil {
return Only(ctx, err) // TODO: log/display iterated errors
return Only(ctx, errs.Err())
} }
if len(ds.Entries) == 0 { if len(ds.Entries) == 0 {
@ -378,29 +380,33 @@ func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
} }
// runDetailsOneDriveCmd actually performs the lookup in backup details. // runDetailsOneDriveCmd actually performs the lookup in backup details.
// the fault.Errors return is always non-nil. Callers should check if
// errs.Err() == nil.
func runDetailsOneDriveCmd( func runDetailsOneDriveCmd(
ctx context.Context, ctx context.Context,
r repository.BackupGetter, r repository.BackupGetter,
backupID string, backupID string,
opts utils.OneDriveOpts, opts utils.OneDriveOpts,
) (*details.Details, error) { ) (*details.Details, *fault.Errors) {
errs := fault.New(false)
if err := utils.ValidateOneDriveRestoreFlags(backupID, opts); err != nil { if err := utils.ValidateOneDriveRestoreFlags(backupID, opts); err != nil {
return nil, err return nil, errs.Fail(err)
} }
d, _, err := r.BackupDetails(ctx, backupID) d, _, err := r.BackupDetails(ctx, backupID)
if err != nil { if err != nil {
if errors.Is(err, kopia.ErrNotFound) { if errors.Is(err, kopia.ErrNotFound) {
return nil, errors.Errorf("no backup exists with the id %s", backupID) return nil, errs.Fail(errors.Errorf("no backup exists with the id %s", backupID))
} }
return nil, errors.Wrap(err, "Failed to get backup details in the repository") return nil, errs.Fail(errors.Wrap(err, "Failed to get backup details in the repository"))
} }
sel := utils.IncludeOneDriveRestoreDataSelectors(opts) sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
utils.FilterOneDriveRestoreInfoSelectors(sel, opts) utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
return sel.Reduce(ctx, d), nil return sel.Reduce(ctx, d, errs), errs
} }
// `corso backup delete onedrive [<flag>...]` // `corso backup delete onedrive [<flag>...]`

View File

@ -98,10 +98,9 @@ func (suite *OneDriveSuite) TestOneDriveBackupDetailsSelectors() {
ctx, ctx,
test.BackupGetter, test.BackupGetter,
"backup-ID", "backup-ID",
test.Opts, test.Opts)
) assert.NoError(t, err.Err())
assert.NoError(t, err) assert.Empty(t, err.Errs())
assert.ElementsMatch(t, test.Expected, output.Entries) assert.ElementsMatch(t, test.Expected, output.Entries)
}) })
} }
@ -117,10 +116,9 @@ func (suite *OneDriveSuite) TestOneDriveBackupDetailsSelectorsBadFormats() {
ctx, ctx,
test.BackupGetter, test.BackupGetter,
"backup-ID", "backup-ID",
test.Opts, test.Opts)
) assert.Error(t, err.Err())
assert.Empty(t, err.Errs())
assert.Error(t, err)
assert.Empty(t, output) assert.Empty(t, output)
}) })
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/repository" "github.com/alcionai/corso/src/pkg/repository"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
@ -481,9 +482,10 @@ func detailsSharePointCmd(cmd *cobra.Command, args []string) error {
Populated: utils.GetPopulatedFlags(cmd), Populated: utils.GetPopulatedFlags(cmd),
} }
ds, err := runDetailsSharePointCmd(ctx, r, backupID, opts) ds, errs := runDetailsSharePointCmd(ctx, r, backupID, opts)
if err != nil { if errs.Err() != nil {
return Only(ctx, err) // TODO: log/display iterated errors
return Only(ctx, errs.Err())
} }
if len(ds.Entries) == 0 { if len(ds.Entries) == 0 {
@ -497,27 +499,31 @@ func detailsSharePointCmd(cmd *cobra.Command, args []string) error {
} }
// runDetailsSharePointCmd actually performs the lookup in backup details. // runDetailsSharePointCmd actually performs the lookup in backup details.
// the fault.Errors return is always non-nil. Callers should check if
// errs.Err() == nil.
func runDetailsSharePointCmd( func runDetailsSharePointCmd(
ctx context.Context, ctx context.Context,
r repository.BackupGetter, r repository.BackupGetter,
backupID string, backupID string,
opts utils.SharePointOpts, opts utils.SharePointOpts,
) (*details.Details, error) { ) (*details.Details, *fault.Errors) {
errs := fault.New(false)
if err := utils.ValidateSharePointRestoreFlags(backupID, opts); err != nil { if err := utils.ValidateSharePointRestoreFlags(backupID, opts); err != nil {
return nil, err return nil, errs.Fail(err)
} }
d, _, err := r.BackupDetails(ctx, backupID) d, _, err := r.BackupDetails(ctx, backupID)
if err != nil { if err != nil {
if errors.Is(err, kopia.ErrNotFound) { if errors.Is(err, kopia.ErrNotFound) {
return nil, errors.Errorf("no backup exists with the id %s", backupID) return nil, errs.Fail(errors.Errorf("no backup exists with the id %s", backupID))
} }
return nil, errors.Wrap(err, "Failed to get backup details in the repository") return nil, errs.Fail(errors.Wrap(err, "Failed to get backup details in the repository"))
} }
sel := utils.IncludeSharePointRestoreDataSelectors(opts) sel := utils.IncludeSharePointRestoreDataSelectors(opts)
utils.FilterSharePointRestoreInfoSelectors(sel, opts) utils.FilterSharePointRestoreInfoSelectors(sel, opts)
return sel.Reduce(ctx, d), nil return sel.Reduce(ctx, d, errs), errs
} }

View File

@ -213,10 +213,9 @@ func (suite *SharePointSuite) TestSharePointBackupDetailsSelectors() {
ctx, ctx,
test.BackupGetter, test.BackupGetter,
"backup-ID", "backup-ID",
test.Opts, test.Opts)
) assert.NoError(t, err.Err())
assert.NoError(t, err) assert.Empty(t, err.Errs())
assert.ElementsMatch(t, test.Expected, output.Entries) assert.ElementsMatch(t, test.Expected, output.Entries)
}) })
} }
@ -232,10 +231,9 @@ func (suite *SharePointSuite) TestSharePointBackupDetailsSelectorsBadFormats() {
ctx, ctx,
test.BackupGetter, test.BackupGetter,
"backup-ID", "backup-ID",
test.Opts, test.Opts)
) assert.Error(t, err.Err())
assert.Empty(t, err.Errs())
assert.Error(t, err)
assert.Empty(t, output) assert.Empty(t, output)
}) })
} }

View File

@ -4,7 +4,7 @@ go 1.19
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0
github.com/alcionai/clues v0.0.0-20230131232239-cee86233b005 github.com/alcionai/clues v0.0.0-20230202001016-cbda58c9de9e
github.com/aws/aws-sdk-go v1.44.192 github.com/aws/aws-sdk-go v1.44.192
github.com/aws/aws-xray-sdk-go v1.8.0 github.com/aws/aws-xray-sdk-go v1.8.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0

View File

@ -54,6 +54,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/alcionai/clues v0.0.0-20230131232239-cee86233b005 h1:eTgICcmcydEWG8J+hgnidf0pzujV3Gd2XqmknykZkzA= github.com/alcionai/clues v0.0.0-20230131232239-cee86233b005 h1:eTgICcmcydEWG8J+hgnidf0pzujV3Gd2XqmknykZkzA=
github.com/alcionai/clues v0.0.0-20230131232239-cee86233b005/go.mod h1:UlAs8jkWIpsOMakiC8NxPgQQVQRdvyf1hYMszlYYLb4= github.com/alcionai/clues v0.0.0-20230131232239-cee86233b005/go.mod h1:UlAs8jkWIpsOMakiC8NxPgQQVQRdvyf1hYMszlYYLb4=
github.com/alcionai/clues v0.0.0-20230202001016-cbda58c9de9e h1:KMRGDB9lh0wC/WYVmQ28MJ07qiHszCSH2PRwkw2YElM=
github.com/alcionai/clues v0.0.0-20230202001016-cbda58c9de9e/go.mod h1:UlAs8jkWIpsOMakiC8NxPgQQVQRdvyf1hYMszlYYLb4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=

View File

@ -344,7 +344,7 @@ func formatDetailsForRestoration(
deets *details.Details, deets *details.Details,
errs *fault.Errors, errs *fault.Errors,
) ([]path.Path, error) { ) ([]path.Path, error) {
fds, err := sel.Reduce(ctx, deets) fds, err := sel.Reduce(ctx, deets, errs)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
) )
@ -52,7 +53,7 @@ func Example_newSelector() {
bSel.ToOneDriveBackup() bSel.ToOneDriveBackup()
} }
// Output: OneDrive service is not Exchange: wrong selector service type // Output: wrong selector service type: OneDrive is not Exchange
} }
// ExampleIncludeFoldersAndItems demonstrates how to select for granular data. // ExampleIncludeFoldersAndItems demonstrates how to select for granular data.
@ -141,10 +142,11 @@ func Example_reduceDetails() {
ser := selectors.NewExchangeRestore( ser := selectors.NewExchangeRestore(
[]string{"your-user-id", "foo-user-id", "bar-user-id"}, []string{"your-user-id", "foo-user-id", "bar-user-id"},
) )
errAgg := fault.New(false)
// The Reduce() call is where our constructed selectors are applied to the data // The Reduce() call is where our constructed selectors are applied to the data
// from a previous backup record. // from a previous backup record.
filteredDetails := ser.Reduce(ctxBG, exampleDetails) filteredDetails := ser.Reduce(ctxBG, exampleDetails, errAgg)
// We haven't added any scopes to our selector yet, so none of the data is retained. // We haven't added any scopes to our selector yet, so none of the data is retained.
fmt.Println("Before adding scopes:", len(filteredDetails.Entries)) fmt.Println("Before adding scopes:", len(filteredDetails.Entries))
@ -153,7 +155,7 @@ func Example_reduceDetails() {
ser.Filter(ser.MailSubject("the answer to life")) ser.Filter(ser.MailSubject("the answer to life"))
// Now that we've selected our data, we should find a result. // Now that we've selected our data, we should find a result.
filteredDetails = ser.Reduce(ctxBG, exampleDetails) filteredDetails = ser.Reduce(ctxBG, exampleDetails, errAgg)
fmt.Println("After adding scopes:", len(filteredDetails.Entries)) fmt.Println("After adding scopes:", len(filteredDetails.Entries))
// Output: Before adding scopes: 0 // Output: Before adding scopes: 0

View File

@ -6,6 +6,7 @@ import (
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters" "github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -704,7 +705,11 @@ func (s ExchangeScope) setDefaults() {
// Reduce filters the entries in a details struct to only those that match the // Reduce filters the entries in a details struct to only those that match the
// inclusions, filters, and exclusions in the selector. // inclusions, filters, and exclusions in the selector.
func (s exchange) Reduce(ctx context.Context, deets *details.Details) *details.Details { func (s exchange) Reduce(
ctx context.Context,
deets *details.Details,
errs fault.Adder,
) *details.Details {
return reduce[ExchangeScope]( return reduce[ExchangeScope](
ctx, ctx,
deets, deets,
@ -714,7 +719,7 @@ func (s exchange) Reduce(ctx context.Context, deets *details.Details) *details.D
path.EventsCategory: ExchangeEvent, path.EventsCategory: ExchangeEvent,
path.EmailCategory: ExchangeMail, path.EmailCategory: ExchangeMail,
}, },
) errs)
} }
// matchesInfo handles the standard behavior when comparing a scope and an ExchangeFilter // matchesInfo handles the standard behavior when comparing a scope and an ExchangeFilter

View File

@ -11,6 +11,7 @@ import (
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/filters" "github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -1029,10 +1030,13 @@ func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
errs := mock.NewAdder()
sel := test.makeSelector() sel := test.makeSelector()
results := sel.Reduce(ctx, test.deets) results := sel.Reduce(ctx, test.deets, errs)
paths := results.Paths() paths := results.Paths()
assert.Equal(t, test.expect, paths) assert.Equal(t, test.expect, paths)
assert.Empty(t, errs.Errs)
}) })
} }
} }

View File

@ -5,6 +5,7 @@ import (
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters" "github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -483,7 +484,11 @@ func (s OneDriveScope) DiscreteCopy(user string) OneDriveScope {
// Reduce filters the entries in a details struct to only those that match the // Reduce filters the entries in a details struct to only those that match the
// inclusions, filters, and exclusions in the selector. // inclusions, filters, and exclusions in the selector.
func (s oneDrive) Reduce(ctx context.Context, deets *details.Details) *details.Details { func (s oneDrive) Reduce(
ctx context.Context,
deets *details.Details,
errs fault.Adder,
) *details.Details {
return reduce[OneDriveScope]( return reduce[OneDriveScope](
ctx, ctx,
deets, deets,
@ -491,7 +496,7 @@ func (s oneDrive) Reduce(ctx context.Context, deets *details.Details) *details.D
map[path.CategoryType]oneDriveCategory{ map[path.CategoryType]oneDriveCategory{
path.FilesCategory: OneDriveItem, path.FilesCategory: OneDriveItem,
}, },
) errs)
} }
// matchesInfo handles the standard behavior when comparing a scope and an oneDriveInfo // matchesInfo handles the standard behavior when comparing a scope and an oneDriveInfo

View File

@ -11,6 +11,7 @@ import (
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -241,10 +242,13 @@ func (suite *OneDriveSelectorSuite) TestOneDriveRestore_Reduce() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
errs := mock.NewAdder()
sel := test.makeSelector() sel := test.makeSelector()
results := sel.Reduce(ctx, test.deets) results := sel.Reduce(ctx, test.deets, errs)
paths := results.Paths() paths := results.Paths()
assert.Equal(t, test.expect, paths) assert.Equal(t, test.expect, paths)
assert.Empty(t, errs.Errs)
}) })
} }
} }

View File

@ -5,10 +5,11 @@ import (
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"github.com/alcionai/clues"
D "github.com/alcionai/corso/src/internal/diagnostics" D "github.com/alcionai/corso/src/internal/diagnostics"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters" "github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -286,6 +287,7 @@ func reduce[T scopeT, C categoryT](
deets *details.Details, deets *details.Details,
s Selector, s Selector,
dataCategories map[path.CategoryType]C, dataCategories map[path.CategoryType]C,
errs fault.Adder,
) *details.Details { ) *details.Details {
ctx, end := D.Span(ctx, "selectors:reduce") ctx, end := D.Span(ctx, "selectors:reduce")
defer end() defer end()
@ -311,7 +313,7 @@ func reduce[T scopeT, C categoryT](
for _, ent := range deets.Items() { for _, ent := range deets.Items() {
repoPath, err := path.FromDataLayerPath(ent.RepoRef, true) repoPath, err := path.FromDataLayerPath(ent.RepoRef, true)
if err != nil { if err != nil {
logger.Ctx(ctx).Debugw("transforming repoRef to path", "err", err) errs.Add(clues.Wrap(err, "transforming repoRef to path").WithClues(ctx))
continue continue
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/filters" "github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -273,13 +274,17 @@ func (suite *SelectorScopesSuite) TestReduce() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
errs := mock.NewAdder()
ds := deets() ds := deets()
result := reduce[mockScope]( result := reduce[mockScope](
ctx, ctx,
&ds, &ds,
test.sel().Selector, test.sel().Selector,
dataCats) dataCats,
errs)
require.NotNil(t, result) require.NotNil(t, result)
require.Empty(t, errs.Errs, "iteration errors")
assert.Len(t, result.Entries, test.expectLen) assert.Len(t, result.Entries, test.expectLen)
}) })
} }

View File

@ -5,9 +5,11 @@ import (
"encoding/json" "encoding/json"
"strings" "strings"
"github.com/alcionai/clues"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/filters" "github.com/alcionai/corso/src/pkg/filters"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -30,8 +32,9 @@ var serviceToPathType = map[service]path.ServiceType{
} }
var ( var (
ErrorBadSelectorCast = errors.New("wrong selector service type") ErrorBadSelectorCast = errors.New("wrong selector service type")
ErrorNoMatchingItems = errors.New("no items match the specified selectors") ErrorNoMatchingItems = errors.New("no items match the specified selectors")
ErrorUnrecognizedService = errors.New("unrecognized service")
) )
const ( const (
@ -67,7 +70,7 @@ var (
const All = "All" const All = "All"
type Reducer interface { type Reducer interface {
Reduce(context.Context, *details.Details) *details.Details Reduce(context.Context, *details.Details, fault.Adder) *details.Details
} }
// selectorResourceOwners aggregates all discrete path category types described // selectorResourceOwners aggregates all discrete path category types described
@ -234,13 +237,17 @@ func (s Selector) PathService() path.ServiceType {
// from the generic selector by interpreting the selector service type rather // from the generic selector by interpreting the selector service type rather
// than have the caller make that interpretation. Returns an error if the // than have the caller make that interpretation. Returns an error if the
// service is unsupported. // service is unsupported.
func (s Selector) Reduce(ctx context.Context, deets *details.Details) (*details.Details, error) { func (s Selector) Reduce(
ctx context.Context,
deets *details.Details,
errs fault.Adder,
) (*details.Details, error) {
r, err := selectorAsIface[Reducer](s) r, err := selectorAsIface[Reducer](s)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return r.Reduce(ctx, deets), nil return r.Reduce(ctx, deets, errs), nil
} }
// returns the sets of path categories identified in each scope set. // returns the sets of path categories identified in each scope set.
@ -272,7 +279,7 @@ func selectorAsIface[T any](s Selector) (T, error) {
a, err = func() (any, error) { return s.ToSharePointRestore() }() a, err = func() (any, error) { return s.ToSharePointRestore() }()
t = a.(T) t = a.(T)
default: default:
err = errors.New("service not supported: " + s.Service.String()) err = clues.Stack(ErrorUnrecognizedService, errors.New(s.Service.String()))
} }
return t, err return t, err
@ -374,7 +381,7 @@ func pathComparator() option {
} }
func badCastErr(cast, is service) error { func badCastErr(cast, is service) error {
return errors.Wrapf(ErrorBadSelectorCast, "%s service is not %s", cast, is) return clues.Stack(ErrorBadSelectorCast, errors.Errorf("%s is not %s", cast, is))
} }
func join(s ...string) string { func join(s ...string) string {

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/internal/common" "github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
"github.com/alcionai/corso/src/pkg/selectors/testdata" "github.com/alcionai/corso/src/pkg/selectors/testdata"
) )
@ -264,8 +265,11 @@ func (suite *SelectorReduceSuite) TestReduce() {
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
output := test.selFunc().Reduce(ctx, allDetails) errs := mock.NewAdder()
output := test.selFunc().Reduce(ctx, allDetails, errs)
assert.ElementsMatch(t, test.expected, output.Entries) assert.ElementsMatch(t, test.expected, output.Entries)
assert.Empty(t, errs.Errs)
}) })
} }
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -555,7 +556,11 @@ func (s SharePointScope) DiscreteCopy(site string) SharePointScope {
// Reduce filters the entries in a details struct to only those that match the // Reduce filters the entries in a details struct to only those that match the
// inclusions, filters, and exclusions in the selector. // inclusions, filters, and exclusions in the selector.
func (s sharePoint) Reduce(ctx context.Context, deets *details.Details) *details.Details { func (s sharePoint) Reduce(
ctx context.Context,
deets *details.Details,
errs fault.Adder,
) *details.Details {
return reduce[SharePointScope]( return reduce[SharePointScope](
ctx, ctx,
deets, deets,
@ -565,7 +570,7 @@ func (s sharePoint) Reduce(ctx context.Context, deets *details.Details) *details
path.ListsCategory: SharePointListItem, path.ListsCategory: SharePointListItem,
path.PagesCategory: SharePointPage, path.PagesCategory: SharePointPage,
}, },
) errs)
} }
// matchesInfo handles the standard behavior when comparing a scope and an sharePointInfo // matchesInfo handles the standard behavior when comparing a scope and an sharePointInfo

View File

@ -9,6 +9,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup/details" "github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -305,10 +306,13 @@ func (suite *SharePointSelectorSuite) TestSharePointRestore_Reduce() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
errs := mock.NewAdder()
sel := test.makeSelector() sel := test.makeSelector()
results := sel.Reduce(ctx, test.deets) results := sel.Reduce(ctx, test.deets, errs)
paths := results.Paths() paths := results.Paths()
assert.Equal(t, test.expect, paths) assert.Equal(t, test.expect, paths)
assert.Empty(t, errs.Errs)
}) })
} }
} }