check for flag population instead of empty string (#1075)

Adds a processor that confirms whether user has
added a value for a flag in the cmd, or if it is the
default value.  This map of valued flags is added
to the service opts structs to for validation.

Also migrates many service flags to utils as
consts so that these values can be maintained
as consistent across packages.
This commit is contained in:
Keepers 2022-10-14 11:01:27 -06:00 committed by GitHub
parent 4a29d22216
commit 65d6780906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 396 additions and 250 deletions

View File

@ -110,11 +110,11 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
// More generic (ex: --user) and more frequently used flags take precedence.
fs.StringSliceVar(
&user,
"user", nil,
utils.UserFN, nil,
"Backup Exchange data by user ID; accepts '"+utils.Wildcard+"' to select all users")
fs.StringSliceVar(
&exchangeData,
"data", nil,
utils.DataFN, nil,
"Select one or more types of data to backup: "+dataEmail+", "+dataContacts+", or "+dataEvents)
options.AddOperationFlags(c)
@ -134,83 +134,83 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
// Flags addition ordering should follow the order we want them to appear in help and docs:
// More generic (ex: --user) and more frequently used flags take precedence.
fs.StringVar(&backupID,
"backup", "",
utils.BackupFN, "",
"ID of the backup to explore. (required)")
cobra.CheckErr(c.MarkFlagRequired("backup"))
cobra.CheckErr(c.MarkFlagRequired(utils.BackupFN))
fs.StringSliceVar(
&user,
"user", nil,
utils.UserFN, nil,
"Select backup details by user ID; accepts '"+utils.Wildcard+"' to select all users.")
// email flags
fs.StringSliceVar(
&email,
"email", nil,
utils.EmailFN, nil,
"Select backup details for emails by email ID; accepts '"+utils.Wildcard+"' to select all emails.")
fs.StringSliceVar(
&emailFolder,
"email-folder", nil,
utils.EmailFolderFN, nil,
"Select backup details for emails within a folder; accepts '"+utils.Wildcard+"' to select all email folders.")
fs.StringVar(
&emailSubject,
"email-subject", "",
utils.EmailSubjectFN, "",
"Select backup details for emails with a subject containing this value.")
fs.StringVar(
&emailSender,
"email-sender", "",
utils.EmailSenderFN, "",
"Select backup details for emails from a specific sender.")
fs.StringVar(
&emailReceivedAfter,
"email-received-after", "",
utils.EmailReceivedAfterFN, "",
"Select backup details for emails received after this datetime.")
fs.StringVar(
&emailReceivedBefore,
"email-received-before", "",
utils.EmailReceivedBeforeFN, "",
"Select backup details for emails received before this datetime.")
// event flags
fs.StringSliceVar(
&event,
"event", nil,
utils.EventFN, nil,
"Select backup details for events by event ID; accepts '"+utils.Wildcard+"' to select all events.")
fs.StringSliceVar(
&eventCalendar,
"event-calendar", nil,
utils.EventCalendarFN, nil,
"Select backup details for events under a calendar; accepts '"+utils.Wildcard+"' to select all events.")
fs.StringVar(
&eventSubject,
"event-subject", "",
utils.EventSubjectFN, "",
"Select backup details for events with a subject containing this value.")
fs.StringVar(
&eventOrganizer,
"event-organizer", "",
utils.EventOrganizerFN, "",
"Select backup details for events from a specific organizer.")
fs.StringVar(
&eventRecurs,
"event-recurs", "",
utils.EventRecursFN, "",
"Select backup details for recurring events. Use `--event-recurs false` to select non-recurring events.")
fs.StringVar(
&eventStartsAfter,
"event-starts-after", "",
utils.EventStartsAfterFN, "",
"Select backup details for events starting after this datetime.")
fs.StringVar(
&eventStartsBefore,
"event-starts-before", "",
utils.EventStartsBeforeFN, "",
"Select backup details for events starting before this datetime.")
// contact flags
fs.StringSliceVar(
&contact,
"contact", nil,
utils.ContactFN, nil,
"Select backup details for contacts by contact ID; accepts '"+utils.Wildcard+"' to select all contacts.")
fs.StringSliceVar(
&contactFolder,
"contact-folder", nil,
utils.ContactFolderFN, nil,
"Select backup details for contacts within a folder; accepts '"+utils.Wildcard+"' to select all contact folders.")
fs.StringVar(
&contactName,
"contact-name", "",
utils.ContactNameFN, "",
"Select backup details for contacts whose contact name contains this value.")
case deleteCommand:
@ -219,8 +219,10 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
c.Use = c.Use + " " + exchangeServiceCommandDeleteUseSuffix
c.Example = exchangeServiceCommandDeleteExamples
fs.StringVar(&backupID, "backup", "", "ID of the backup to delete. (required)")
cobra.CheckErr(c.MarkFlagRequired("backup"))
fs.StringVar(&backupID,
utils.BackupFN, "",
"ID of the backup to delete. (required)")
cobra.CheckErr(c.MarkFlagRequired(utils.BackupFN))
}
return c
@ -401,12 +403,12 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
opts := utils.ExchangeOpts{
Contacts: contact,
ContactFolders: contactFolder,
Emails: email,
EmailFolders: emailFolder,
Events: event,
EventCalendars: eventCalendar,
Contact: contact,
ContactFolder: contactFolder,
Email: email,
EmailFolder: emailFolder,
Event: event,
EventCalendar: eventCalendar,
Users: user,
ContactName: contactName,
EmailReceivedAfter: emailReceivedAfter,
@ -418,6 +420,8 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
EventStartsAfter: eventStartsAfter,
EventStartsBefore: eventStartsBefore,
EventSubject: eventSubject,
Populated: utils.GetPopulatedFlags(cmd),
}
s, acct, err := config.GetStorageAndAccount(ctx, true, nil)

View File

@ -14,6 +14,7 @@ import (
"github.com/alcionai/corso/src/cli"
"github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/cli/print"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/internal/connector/exchange"
"github.com/alcionai/corso/src/internal/operations"
"github.com/alcionai/corso/src/internal/tester"
@ -108,8 +109,8 @@ func (suite *BackupExchangeIntegrationSuite) TestExchangeBackupCmd() {
cmd := tester.StubRootCmd(
"backup", "create", "exchange",
"--config-file", suite.cfgFP,
"--user", suite.m365UserID,
"--data", set.String())
"--"+utils.UserFN, suite.m365UserID,
"--"+utils.DataFN, set.String())
cli.BuildCommandTree(cmd)
cmd.SetOut(&recorder)
@ -327,7 +328,7 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
cmd := tester.StubRootCmd(
"backup", "details", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(bID))
"--"+utils.BackupFN, string(bID))
cli.BuildCommandTree(cmd)
cmd.SetOut(&suite.recorder)
@ -358,7 +359,7 @@ func (suite *PreparedBackupExchangeIntegrationSuite) TestExchangeDetailsCmd() {
}
// At least the prefix of the path should be encoded as folders.
assert.Greater(suite.T(), foundFolders, 4)
assert.Greater(t, foundFolders, 4)
})
}
}
@ -441,7 +442,7 @@ func (suite *BackupDeleteExchangeIntegrationSuite) TestExchangeBackupDeleteCmd()
cmd := tester.StubRootCmd(
"backup", "delete", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(suite.backupOp.Results.BackupID))
"--"+utils.BackupFN, string(suite.backupOp.Results.BackupID))
cli.BuildCommandTree(cmd)
// run the command
@ -467,7 +468,7 @@ func (suite *BackupDeleteExchangeIntegrationSuite) TestExchangeBackupDeleteCmd_U
cmd := tester.StubRootCmd(
"backup", "delete", "exchange",
"--config-file", suite.cfgFP,
"--backup", uuid.NewString())
"--"+utils.BackupFN, uuid.NewString())
cli.BuildCommandTree(cmd)
// unknown backupIDs should error since the modelStore can't find the backup

View File

@ -79,7 +79,8 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
c.Use = c.Use + " " + oneDriveServiceCommandCreateUseSuffix
c.Example = oneDriveServiceCommandCreateExamples
fs.StringArrayVar(&user, "user", nil,
fs.StringArrayVar(&user,
utils.UserFN, nil,
"Backup OneDrive data by user ID; accepts '"+utils.Wildcard+"' to select all users. (required)")
options.AddOperationFlags(c)
@ -96,39 +97,41 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
c.Use = c.Use + " " + oneDriveServiceCommandDetailsUseSuffix
c.Example = oneDriveServiceCommandDetailsExamples
fs.StringVar(&backupID, "backup", "", "ID of the backup to explore. (required)")
cobra.CheckErr(c.MarkFlagRequired("backup"))
fs.StringVar(&backupID,
utils.BackupFN, "",
"ID of the backup to explore. (required)")
cobra.CheckErr(c.MarkFlagRequired(utils.BackupFN))
// onedrive hierarchy flags
fs.StringSliceVar(
&folderPaths,
"folder", nil,
utils.FolderFN, nil,
"Select backup details by OneDrive folder; defaults to root.")
fs.StringSliceVar(
&fileNames,
"file", nil,
utils.FileFN, nil,
"Select backup details by file name or ID.")
// onedrive info flags
fs.StringVar(
&fileCreatedAfter,
"file-created-after", "",
utils.FileCreatedAfterFN, "",
"Select backup details for files created after this datetime.")
fs.StringVar(
&fileCreatedBefore,
"file-created-before", "",
utils.FileCreatedBeforeFN, "",
"Select backup details for files created before this datetime.")
fs.StringVar(
&fileModifiedAfter,
"file-modified-after", "",
utils.FileModifiedAfterFN, "",
"Select backup details for files modified after this datetime.")
fs.StringVar(
&fileModifiedBefore,
"file-modified-before", "",
utils.FileModifiedBeforeFN, "",
"Select backup details for files modified before this datetime.")
case deleteCommand:
@ -137,8 +140,10 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
c.Use = c.Use + " " + oneDriveServiceCommandDeleteUseSuffix
c.Example = oneDriveServiceCommandDeleteExamples
fs.StringVar(&backupID, "backup", "", "ID of the backup to delete. (required)")
cobra.CheckErr(c.MarkFlagRequired("backup"))
fs.StringVar(&backupID,
utils.BackupFN, "",
"ID of the backup to delete. (required)")
cobra.CheckErr(c.MarkFlagRequired(utils.BackupFN))
}
return c
@ -311,13 +316,15 @@ func detailsOneDriveCmd(cmd *cobra.Command, args []string) error {
defer utils.CloseRepo(ctx, r)
opts := utils.OneDriveOpts{
Users: user,
Paths: folderPaths,
Names: fileNames,
CreatedAfter: fileCreatedAfter,
CreatedBefore: fileCreatedBefore,
ModifiedAfter: fileModifiedAfter,
ModifiedBefore: fileModifiedBefore,
Users: user,
Paths: folderPaths,
Names: fileNames,
FileCreatedAfter: fileCreatedAfter,
FileCreatedBefore: fileCreatedBefore,
FileModifiedAfter: fileModifiedAfter,
FileModifiedBefore: fileModifiedBefore,
Populated: utils.GetPopulatedFlags(cmd),
}
ds, err := runDetailsOneDriveCmd(ctx, r, backupID, opts)

View File

@ -56,79 +56,81 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command {
// Flags addition ordering should follow the order we want them to appear in help and docs:
// More generic (ex: --user) and more frequently used flags take precedence.
// general flags
fs.StringVar(&backupID, "backup", "", "ID of the backup to restore. (required)")
cobra.CheckErr(c.MarkFlagRequired("backup"))
fs.StringVar(&backupID,
utils.BackupFN, "",
"ID of the backup to restore. (required)")
cobra.CheckErr(c.MarkFlagRequired(utils.BackupFN))
fs.StringSliceVar(&user,
"user", nil,
utils.UserFN, nil,
"Restore data by user ID; accepts '"+utils.Wildcard+"' to select all users.")
// email flags
fs.StringSliceVar(&email,
"email", nil,
utils.EmailFN, nil,
"Restore emails by ID; accepts '"+utils.Wildcard+"' to select all emails.")
fs.StringSliceVar(
&emailFolder,
"email-folder", nil,
utils.EmailFolderFN, nil,
"Restore emails within a folder; accepts '"+utils.Wildcard+"' to select all email folders.")
fs.StringVar(
&emailSubject,
"email-subject", "",
utils.EmailSubjectFN, "",
"Restore emails with a subject containing this value.")
fs.StringVar(
&emailSender,
"email-sender", "",
utils.EmailSenderFN, "",
"Restore emails from a specific sender.")
fs.StringVar(
&emailReceivedAfter,
"email-received-after", "",
utils.EmailReceivedAfterFN, "",
"Restore emails received after this datetime.")
fs.StringVar(
&emailReceivedBefore,
"email-received-before", "",
utils.EmailReceivedBeforeFN, "",
"Restore emails received before this datetime.")
// event flags
fs.StringSliceVar(&event,
"event", nil,
utils.EventFN, nil,
"Restore events by event ID; accepts '"+utils.Wildcard+"' to select all events.")
fs.StringSliceVar(
&eventCalendar,
"event-calendar", nil,
utils.EventCalendarFN, nil,
"Restore events under a calendar; accepts '"+utils.Wildcard+"' to select all event calendars.")
fs.StringVar(
&eventSubject,
"event-subject", "",
utils.EventSubjectFN, "",
"Restore events with a subject containing this value.")
fs.StringVar(
&eventOrganizer,
"event-organizer", "",
utils.EventOrganizerFN, "",
"Restore events from a specific organizer.")
fs.StringVar(
&eventRecurs,
"event-recurs", "",
utils.EventRecursFN, "",
"Restore recurring events. Use `--event-recurs false` to restore non-recurring events.")
fs.StringVar(
&eventStartsAfter,
"event-starts-after", "",
utils.EventStartsAfterFN, "",
"Restore events starting after this datetime.")
fs.StringVar(
&eventStartsBefore,
"event-starts-before", "",
utils.EventStartsBeforeFN, "",
"Restore events starting before this datetime.")
// contacts flags
fs.StringSliceVar(
&contact,
"contact", nil,
utils.ContactFN, nil,
"Restore contacts by contact ID; accepts '"+utils.Wildcard+"' to select all contacts.")
fs.StringSliceVar(
&contactFolder,
"contact-folder", nil,
utils.ContactFolderFN, nil,
"Restore contacts within a folder; accepts '"+utils.Wildcard+"' to select all contact folders.")
fs.StringVar(
&contactName,
"contact-name", "",
utils.ContactNameFN, "",
"Restore contacts whose contact name contains this value.")
// others
@ -177,12 +179,12 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
}
opts := utils.ExchangeOpts{
Contacts: contact,
ContactFolders: contactFolder,
Emails: email,
EmailFolders: emailFolder,
Events: event,
EventCalendars: eventCalendar,
Contact: contact,
ContactFolder: contactFolder,
Email: email,
EmailFolder: emailFolder,
Event: event,
EventCalendar: eventCalendar,
Users: user,
ContactName: contactName,
EmailReceivedAfter: emailReceivedAfter,
@ -194,6 +196,8 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
EventStartsAfter: eventStartsAfter,
EventStartsBefore: eventStartsBefore,
EventSubject: eventSubject,
Populated: utils.GetPopulatedFlags(cmd),
}
if err := utils.ValidateExchangeRestoreFlags(backupID, opts); err != nil {

View File

@ -10,6 +10,7 @@ import (
"github.com/alcionai/corso/src/cli"
"github.com/alcionai/corso/src/cli/config"
"github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/internal/connector/exchange"
"github.com/alcionai/corso/src/internal/operations"
"github.com/alcionai/corso/src/internal/tester"
@ -136,7 +137,7 @@ func (suite *RestoreExchangeIntegrationSuite) TestExchangeRestoreCmd() {
cmd := tester.StubRootCmd(
"restore", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(suite.backupOps[set].Results.BackupID))
"--"+utils.BackupFN, string(suite.backupOps[set].Results.BackupID))
cli.BuildCommandTree(cmd)
// run the command
@ -160,15 +161,15 @@ func (suite *RestoreExchangeIntegrationSuite) TestExchangeRestoreCmd_badTimeFlag
var timeFilter string
switch set {
case email:
timeFilter = "--email-received-after"
timeFilter = "--" + utils.EmailReceivedAfterFN
case events:
timeFilter = "--event-starts-after"
timeFilter = "--" + utils.EventStartsAfterFN
}
cmd := tester.StubRootCmd(
"restore", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(suite.backupOps[set].Results.BackupID),
"--"+utils.BackupFN, string(suite.backupOps[set].Results.BackupID),
timeFilter, "smarf")
cli.BuildCommandTree(cmd)
@ -192,13 +193,13 @@ func (suite *RestoreExchangeIntegrationSuite) TestExchangeRestoreCmd_badBoolFlag
var timeFilter string
switch set {
case events:
timeFilter = "--event-recurs"
timeFilter = "--" + utils.EventRecursFN
}
cmd := tester.StubRootCmd(
"restore", "exchange",
"--config-file", suite.cfgFP,
"--backup", string(suite.backupOps[set].Results.BackupID),
"--"+utils.BackupFN, string(suite.backupOps[set].Results.BackupID),
timeFilter, "wingbat")
cli.BuildCommandTree(cmd)

View File

@ -42,43 +42,45 @@ func addOneDriveCommands(parent *cobra.Command) *cobra.Command {
// More generic (ex: --user) and more frequently used flags take precedence.
fs.SortFlags = false
fs.StringVar(&backupID, "backup", "", "ID of the backup to restore. (required)")
cobra.CheckErr(c.MarkFlagRequired("backup"))
fs.StringVar(&backupID,
utils.BackupFN, "",
"ID of the backup to restore. (required)")
cobra.CheckErr(c.MarkFlagRequired(utils.BackupFN))
fs.StringSliceVar(&user,
"user", nil,
utils.UserFN, nil,
"Restore data by user ID; accepts '"+utils.Wildcard+"' to select all users.")
// onedrive hierarchy (path/name) flags
fs.StringSliceVar(
&folderPaths,
"folder", nil,
utils.FolderFN, nil,
"Restore items by OneDrive folder; defaults to root")
fs.StringSliceVar(
&fileNames,
"file", nil,
utils.FileFN, nil,
"Restore items by file name or ID")
// onedrive info flags
fs.StringVar(
&fileCreatedAfter,
"file-created-after", "",
utils.FileCreatedAfterFN, "",
"Restore files created after this datetime")
fs.StringVar(
&fileCreatedBefore,
"file-created-before", "",
utils.FileCreatedBeforeFN, "",
"Restore files created before this datetime")
fs.StringVar(
&fileModifiedAfter,
"file-modified-after", "",
utils.FileModifiedAfterFN, "",
"Restore files modified after this datetime")
fs.StringVar(
&fileModifiedBefore,
"file-modified-before", "",
utils.FileModifiedBeforeFN, "",
"Restore files modified before this datetime")
// others
@ -124,13 +126,15 @@ func restoreOneDriveCmd(cmd *cobra.Command, args []string) error {
}
opts := utils.OneDriveOpts{
Users: user,
Paths: folderPaths,
Names: fileNames,
CreatedAfter: fileCreatedAfter,
CreatedBefore: fileCreatedBefore,
ModifiedAfter: fileModifiedAfter,
ModifiedBefore: fileModifiedBefore,
Users: user,
Paths: folderPaths,
Names: fileNames,
FileCreatedAfter: fileCreatedAfter,
FileCreatedBefore: fileCreatedBefore,
FileModifiedAfter: fileModifiedAfter,
FileModifiedBefore: fileModifiedBefore,
Populated: utils.GetPopulatedFlags(cmd),
}
if err := utils.ValidateOneDriveRestoreFlags(backupID, opts); err != nil {

View File

@ -6,13 +6,33 @@ import (
"github.com/alcionai/corso/src/pkg/selectors"
)
// flag names
const (
ContactFN = "contacts"
ContactFolderFN = "contact-folder"
EmailFN = "email"
EmailFolderFN = "email-folder"
EventFN = "events"
EventCalendarFN = "event-calendar"
ContactNameFN = "contact-name"
EmailReceivedAfterFN = "email-received-after"
EmailReceivedBeforeFN = "email-received-before"
EmailSenderFN = "email-sender"
EmailSubjectFN = "email-subject"
EventOrganizerFN = "event-organizer"
EventRecursFN = "event-recurs"
EventStartsAfterFN = "event-starts-after"
EventStartsBeforeFN = "event-starts-before"
EventSubjectFN = "event-subject"
)
type ExchangeOpts struct {
Contacts []string
ContactFolders []string
Emails []string
EmailFolders []string
Events []string
EventCalendars []string
Contact []string
ContactFolder []string
Email []string
EmailFolder []string
Event []string
EventCalendar []string
Users []string
ContactName string
EmailReceivedAfter string
@ -24,6 +44,8 @@ type ExchangeOpts struct {
EventStartsAfter string
EventStartsBefore string
EventSubject string
Populated PopulatedFlags
}
// AddExchangeInclude adds the scope of the provided values to the selector's
@ -77,23 +99,23 @@ func ValidateExchangeRestoreFlags(backupID string, opts ExchangeOpts) error {
return errors.New("a backup ID is required")
}
if !IsValidTimeFormat(opts.EmailReceivedAfter) {
if _, ok := opts.Populated[EmailReceivedAfterFN]; ok && !IsValidTimeFormat(opts.EmailReceivedAfter) {
return errors.New("invalid time format for email-received-after")
}
if !IsValidTimeFormat(opts.EmailReceivedBefore) {
if _, ok := opts.Populated[EmailReceivedBeforeFN]; ok && !IsValidTimeFormat(opts.EmailReceivedBefore) {
return errors.New("invalid time format for email-received-before")
}
if !IsValidTimeFormat(opts.EventStartsAfter) {
if _, ok := opts.Populated[EventStartsAfterFN]; ok && !IsValidTimeFormat(opts.EventStartsAfter) {
return errors.New("invalid time format for event-starts-after")
}
if !IsValidTimeFormat(opts.EventStartsBefore) {
if _, ok := opts.Populated[EventStartsBeforeFN]; ok && !IsValidTimeFormat(opts.EventStartsBefore) {
return errors.New("invalid time format for event-starts-before")
}
if !IsValidBool(opts.EventRecurs) {
if _, ok := opts.Populated[EventRecursFN]; ok && !IsValidBool(opts.EventRecurs) {
return errors.New("invalid format for event-recurs")
}
@ -106,9 +128,9 @@ func IncludeExchangeRestoreDataSelectors(
sel *selectors.ExchangeRestore,
opts ExchangeOpts,
) {
lc, lcf := len(opts.Contacts), len(opts.ContactFolders)
le, lef := len(opts.Emails), len(opts.EmailFolders)
lev, lec := len(opts.Events), len(opts.EventCalendars)
lc, lcf := len(opts.Contact), len(opts.ContactFolder)
le, lef := len(opts.Email), len(opts.EmailFolder)
lev, lec := len(opts.Event), len(opts.EventCalendar)
// either scope the request to a set of users
if lc+lcf+le+lef+lev+lec == 0 {
if len(opts.Users) == 0 {
@ -121,9 +143,9 @@ func IncludeExchangeRestoreDataSelectors(
}
// or add selectors for each type of data
AddExchangeInclude(sel, opts.Users, opts.ContactFolders, opts.Contacts, sel.Contacts)
AddExchangeInclude(sel, opts.Users, opts.EmailFolders, opts.Emails, sel.Mails)
AddExchangeInclude(sel, opts.Users, opts.EventCalendars, opts.Events, sel.Events)
AddExchangeInclude(sel, opts.Users, opts.ContactFolder, opts.Contact, sel.Contacts)
AddExchangeInclude(sel, opts.Users, opts.EmailFolder, opts.Email, sel.Mails)
AddExchangeInclude(sel, opts.Users, opts.EventCalendar, opts.Event, sel.Events)
}
// FilterExchangeRestoreInfoSelectors builds the common info-selector filters.

View File

@ -94,207 +94,207 @@ func (suite *ExchangeUtilsSuite) TestIncludeExchangeBackupDetailDataSelectors()
{
name: "any users, any data",
opts: utils.ExchangeOpts{
Contacts: a,
ContactFolders: a,
Emails: a,
EmailFolders: a,
Events: a,
EventCalendars: a,
Users: a,
Contact: a,
ContactFolder: a,
Email: a,
EmailFolder: a,
Event: a,
EventCalendar: a,
Users: a,
},
expectIncludeLen: 3,
},
{
name: "any users, any folders",
opts: utils.ExchangeOpts{
ContactFolders: a,
EmailFolders: a,
EventCalendars: a,
Users: a,
ContactFolder: a,
EmailFolder: a,
EventCalendar: a,
Users: a,
},
expectIncludeLen: 3,
},
{
name: "single user, single of each data",
opts: utils.ExchangeOpts{
Contacts: stub,
ContactFolders: stub,
Emails: stub,
EmailFolders: stub,
Events: stub,
EventCalendars: stub,
Users: stub,
Contact: stub,
ContactFolder: stub,
Email: stub,
EmailFolder: stub,
Event: stub,
EventCalendar: stub,
Users: stub,
},
expectIncludeLen: 3,
},
{
name: "single user, single of each folder",
opts: utils.ExchangeOpts{
ContactFolders: stub,
EmailFolders: stub,
EventCalendars: stub,
Users: stub,
ContactFolder: stub,
EmailFolder: stub,
EventCalendar: stub,
Users: stub,
},
expectIncludeLen: 3,
},
{
name: "any users, contacts",
opts: utils.ExchangeOpts{
Contacts: a,
ContactFolders: stub,
Users: a,
Contact: a,
ContactFolder: stub,
Users: a,
},
expectIncludeLen: 1,
},
{
name: "single user, contacts",
opts: utils.ExchangeOpts{
Contacts: stub,
ContactFolders: stub,
Users: stub,
Contact: stub,
ContactFolder: stub,
Users: stub,
},
expectIncludeLen: 1,
},
{
name: "any users, emails",
opts: utils.ExchangeOpts{
Emails: a,
EmailFolders: stub,
Users: a,
Email: a,
EmailFolder: stub,
Users: a,
},
expectIncludeLen: 1,
},
{
name: "single user, emails",
opts: utils.ExchangeOpts{
Emails: stub,
EmailFolders: stub,
Users: stub,
Email: stub,
EmailFolder: stub,
Users: stub,
},
expectIncludeLen: 1,
},
{
name: "any users, events",
opts: utils.ExchangeOpts{
Events: a,
EventCalendars: a,
Users: a,
Event: a,
EventCalendar: a,
Users: a,
},
expectIncludeLen: 1,
},
{
name: "single user, events",
opts: utils.ExchangeOpts{
Events: stub,
EventCalendars: stub,
Users: stub,
Event: stub,
EventCalendar: stub,
Users: stub,
},
expectIncludeLen: 1,
},
{
name: "any users, contacts + email",
opts: utils.ExchangeOpts{
Contacts: a,
ContactFolders: a,
Emails: a,
EmailFolders: a,
Users: a,
Contact: a,
ContactFolder: a,
Email: a,
EmailFolder: a,
Users: a,
},
expectIncludeLen: 2,
},
{
name: "single users, contacts + email",
opts: utils.ExchangeOpts{
Contacts: stub,
ContactFolders: stub,
Emails: stub,
EmailFolders: stub,
Users: stub,
Contact: stub,
ContactFolder: stub,
Email: stub,
EmailFolder: stub,
Users: stub,
},
expectIncludeLen: 2,
},
{
name: "any users, email + event",
opts: utils.ExchangeOpts{
Emails: a,
EmailFolders: a,
Events: a,
EventCalendars: a,
Users: a,
Email: a,
EmailFolder: a,
Event: a,
EventCalendar: a,
Users: a,
},
expectIncludeLen: 2,
},
{
name: "single users, email + event",
opts: utils.ExchangeOpts{
Emails: stub,
EmailFolders: stub,
Events: stub,
EventCalendars: stub,
Users: stub,
Email: stub,
EmailFolder: stub,
Event: stub,
EventCalendar: stub,
Users: stub,
},
expectIncludeLen: 2,
},
{
name: "any users, event + contact",
opts: utils.ExchangeOpts{
Contacts: a,
ContactFolders: a,
Events: a,
EventCalendars: a,
Users: a,
Contact: a,
ContactFolder: a,
Event: a,
EventCalendar: a,
Users: a,
},
expectIncludeLen: 2,
},
{
name: "single users, event + contact",
opts: utils.ExchangeOpts{
Contacts: stub,
ContactFolders: stub,
Events: stub,
EventCalendars: stub,
Users: stub,
Contact: stub,
ContactFolder: stub,
Event: stub,
EventCalendar: stub,
Users: stub,
},
expectIncludeLen: 2,
},
{
name: "many users, events",
opts: utils.ExchangeOpts{
Events: many,
EventCalendars: many,
Users: many,
Event: many,
EventCalendar: many,
Users: many,
},
expectIncludeLen: 1,
},
{
name: "many users, events + contacts",
opts: utils.ExchangeOpts{
Contacts: many,
ContactFolders: many,
Events: many,
EventCalendars: many,
Users: many,
Contact: many,
ContactFolder: many,
Event: many,
EventCalendar: many,
Users: many,
},
expectIncludeLen: 2,
},
{
name: "mail, no folder or user",
opts: utils.ExchangeOpts{
Emails: stub,
Email: stub,
},
expectIncludeLen: 1,
},
{
name: "contacts, no folder or user",
opts: utils.ExchangeOpts{
Contacts: stub,
Contact: stub,
},
expectIncludeLen: 1,
},
{
name: "event, no folder or user",
opts: utils.ExchangeOpts{
Events: stub,
Event: stub,
},
expectIncludeLen: 1,
},

52
src/cli/utils/flags.go Normal file
View File

@ -0,0 +1,52 @@
package utils
import (
"strconv"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/alcionai/corso/src/internal/common"
)
type PopulatedFlags map[string]struct{}
func (fs PopulatedFlags) populate(pf *pflag.Flag) {
if pf == nil {
return
}
if pf.Changed {
fs[pf.Name] = struct{}{}
}
}
// GetPopulatedFlags returns a map of flags that have been
// populated by the user. Entry keys match the flag's long
// name. Values are empty.
func GetPopulatedFlags(cmd *cobra.Command) PopulatedFlags {
pop := PopulatedFlags{}
fs := cmd.Flags()
if fs == nil {
return pop
}
fs.VisitAll(pop.populate)
return pop
}
// IsValidTimeFormat returns true if the input is recognized as a
// supported format by the common time parser.
func IsValidTimeFormat(in string) bool {
_, err := common.ParseTime(in)
return err == nil
}
// IsValidTimeFormat returns true if the input is recognized as a
// boolean.
func IsValidBool(in string) bool {
_, err := strconv.ParseBool(in)
return err == nil
}

View File

@ -6,14 +6,27 @@ import (
"github.com/alcionai/corso/src/pkg/selectors"
)
const (
FileFN = "file"
FolderFN = "folders"
NamesFN = "names"
PathsFN = "paths"
FileCreatedAfterFN = "file-created-after"
FileCreatedBeforeFN = "file-created-before"
FileModifiedAfterFN = "file-modified-after"
FileModifiedBeforeFN = "file-modified-before"
)
type OneDriveOpts struct {
Users []string
Names []string
Paths []string
CreatedAfter string
CreatedBefore string
ModifiedAfter string
ModifiedBefore string
Users []string
Names []string
Paths []string
FileCreatedAfter string
FileCreatedBefore string
FileModifiedAfter string
FileModifiedBefore string
Populated PopulatedFlags
}
// ValidateOneDriveRestoreFlags checks common flags for correctness and interdependencies
@ -22,19 +35,19 @@ func ValidateOneDriveRestoreFlags(backupID string, opts OneDriveOpts) error {
return errors.New("a backup ID is required")
}
if !IsValidTimeFormat(opts.CreatedAfter) {
if _, ok := opts.Populated[FileCreatedAfterFN]; ok && !IsValidTimeFormat(opts.FileCreatedAfter) {
return errors.New("invalid time format for created-after")
}
if !IsValidTimeFormat(opts.CreatedBefore) {
if _, ok := opts.Populated[FileCreatedBeforeFN]; ok && !IsValidTimeFormat(opts.FileCreatedBefore) {
return errors.New("invalid time format for created-before")
}
if !IsValidTimeFormat(opts.ModifiedAfter) {
if _, ok := opts.Populated[FileModifiedAfterFN]; ok && !IsValidTimeFormat(opts.FileModifiedAfter) {
return errors.New("invalid time format for modified-after")
}
if !IsValidTimeFormat(opts.ModifiedBefore) {
if _, ok := opts.Populated[FileModifiedAfterFN]; ok && !IsValidTimeFormat(opts.FileModifiedBefore) {
return errors.New("invalid time format for modified-before")
}
@ -90,8 +103,8 @@ func FilterOneDriveRestoreInfoSelectors(
sel *selectors.OneDriveRestore,
opts OneDriveOpts,
) {
AddOneDriveFilter(sel, opts.CreatedAfter, sel.CreatedAfter)
AddOneDriveFilter(sel, opts.CreatedBefore, sel.CreatedBefore)
AddOneDriveFilter(sel, opts.ModifiedAfter, sel.ModifiedAfter)
AddOneDriveFilter(sel, opts.ModifiedBefore, sel.ModifiedBefore)
AddOneDriveFilter(sel, opts.FileCreatedAfter, sel.CreatedAfter)
AddOneDriveFilter(sel, opts.FileCreatedBefore, sel.CreatedBefore)
AddOneDriveFilter(sel, opts.FileModifiedAfter, sel.ModifiedAfter)
AddOneDriveFilter(sel, opts.FileModifiedBefore, sel.ModifiedBefore)
}

View File

@ -31,30 +31,90 @@ var (
Name: "BadEmailReceiveAfter",
Opts: utils.ExchangeOpts{
EmailReceivedAfter: "foo",
Populated: utils.PopulatedFlags{
utils.EmailReceivedAfterFN: struct{}{},
},
},
},
{
Name: "EmptyEmailReceiveAfter",
Opts: utils.ExchangeOpts{
EmailReceivedAfter: "",
Populated: utils.PopulatedFlags{
utils.EmailReceivedAfterFN: struct{}{},
},
},
},
{
Name: "BadEmailReceiveBefore",
Opts: utils.ExchangeOpts{
EmailReceivedBefore: "foo",
Populated: utils.PopulatedFlags{
utils.EmailReceivedBeforeFN: struct{}{},
},
},
},
{
Name: "EmptyEmailReceiveBefore",
Opts: utils.ExchangeOpts{
EmailReceivedBefore: "",
Populated: utils.PopulatedFlags{
utils.EmailReceivedBeforeFN: struct{}{},
},
},
},
{
Name: "BadEventRecurs",
Opts: utils.ExchangeOpts{
EventRecurs: "foo",
Populated: utils.PopulatedFlags{
utils.EventRecursFN: struct{}{},
},
},
},
{
Name: "EmptyEventRecurs",
Opts: utils.ExchangeOpts{
EventRecurs: "",
Populated: utils.PopulatedFlags{
utils.EventRecursFN: struct{}{},
},
},
},
{
Name: "BadEventStartsAfter",
Opts: utils.ExchangeOpts{
EventStartsAfter: "foo",
Populated: utils.PopulatedFlags{
utils.EventStartsAfterFN: struct{}{},
},
},
},
{
Name: "EmptyEventStartsAfter",
Opts: utils.ExchangeOpts{
EventStartsAfter: "",
Populated: utils.PopulatedFlags{
utils.EventStartsAfterFN: struct{}{},
},
},
},
{
Name: "BadEventStartsBefore",
Opts: utils.ExchangeOpts{
EventStartsBefore: "foo",
Populated: utils.PopulatedFlags{
utils.EventStartsBeforeFN: struct{}{},
},
},
},
{
Name: "EmptyEventStartsBefore",
Opts: utils.ExchangeOpts{
EventStartsBefore: "",
Populated: utils.PopulatedFlags{
utils.EventStartsBeforeFN: struct{}{},
},
},
},
}
@ -68,14 +128,14 @@ var (
Name: "Emails",
Expected: testdata.ExchangeEmailItems,
Opts: utils.ExchangeOpts{
Emails: selectors.Any(),
Email: selectors.Any(),
},
},
{
Name: "EmailsFolderPrefixMatch",
Expected: testdata.ExchangeEmailItems,
Opts: utils.ExchangeOpts{
EmailFolders: []string{testdata.ExchangeEmailInboxPath.Folder()},
EmailFolder: []string{testdata.ExchangeEmailInboxPath.Folder()},
},
},
{
@ -109,21 +169,21 @@ var (
Name: "MailID",
Expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]},
Opts: utils.ExchangeOpts{
Emails: []string{testdata.ExchangeEmailItemPath1.Item()},
Email: []string{testdata.ExchangeEmailItemPath1.Item()},
},
},
{
Name: "MailShortRef",
Expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]},
Opts: utils.ExchangeOpts{
Emails: []string{testdata.ExchangeEmailItemPath1.ShortRef()},
Email: []string{testdata.ExchangeEmailItemPath1.ShortRef()},
},
},
{
Name: "MultipleMailShortRef",
Expected: testdata.ExchangeEmailItems,
Opts: utils.ExchangeOpts{
Emails: []string{
Email: []string{
testdata.ExchangeEmailItemPath1.ShortRef(),
testdata.ExchangeEmailItemPath2.ShortRef(),
},
@ -134,7 +194,7 @@ var (
Expected: []details.DetailsEntry{testdata.ExchangeEmailItems[0]},
Opts: utils.ExchangeOpts{
EmailSubject: "foo",
Events: selectors.Any(),
Event: selectors.Any(),
},
},
{
@ -152,8 +212,8 @@ var (
testdata.ExchangeEventsItems[0],
},
Opts: utils.ExchangeOpts{
Emails: []string{testdata.ExchangeEmailItemPath1.ShortRef()},
Events: []string{testdata.ExchangeEventsItemPath1.ShortRef()},
Email: []string{testdata.ExchangeEmailItemPath1.ShortRef()},
Event: []string{testdata.ExchangeEventsItemPath1.ShortRef()},
},
},
}

View File

@ -4,15 +4,20 @@ import (
"context"
"errors"
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/alcionai/corso/src/internal/common"
"github.com/alcionai/corso/src/pkg/repository"
)
// common flag names
const (
BackupFN = "backup"
DataFN = "data"
UserFN = "user"
)
const (
Wildcard = "*"
)
@ -59,30 +64,3 @@ func AddCommand(parent, c *cobra.Command) (*cobra.Command, *pflag.FlagSet) {
return c, c.Flags()
}
// IsValidTimeFormat returns true if the input is regonized as a
// supported format by the common time parser. Returns true if
// the input is zero valued, which indicates that the flag was not
// called.
func IsValidTimeFormat(in string) bool {
if len(in) == 0 {
return true
}
_, err := common.ParseTime(in)
return err == nil
}
// IsValidTimeFormat returns true if the input is regonized as a
// boolean. Returns true if the input is zero valued, which
// indicates that the flag was not called.
func IsValidBool(in string) bool {
if len(in) == 0 {
return true
}
_, err := strconv.ParseBool(in)
return err == nil
}