add an examples test to selectors (#1590)

## Type of change

- [x] 🐹 Trivial/Minor

## Issue(s)

* #1224
This commit is contained in:
Keepers 2022-12-12 18:08:12 -07:00 committed by GitHub
parent 4d46847f6c
commit c00b5811e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -0,0 +1,223 @@
package selectors_test
import (
"context"
"fmt"
"github.com/alcionai/corso/src/pkg/backup/details"
"github.com/alcionai/corso/src/pkg/selectors"
)
// ExampleNewSelector demonstrates creation and distribution of a Selector.
func Example_newSelector() {
// Selectors should use application-specific constructors.
// Generate a selector for backup operations.
seb := selectors.NewExchangeBackup()
// Generate a selector for restore and 'backup details' operations.
ser := selectors.NewExchangeRestore()
// The core selector can be passed around without slicing any
// application-specific data.
bSel := seb.Selector
rSel := ser.Selector
// And can be re-cast to the application instance again.
seb, _ = bSel.ToExchangeBackup()
ser, _ = rSel.ToExchangeRestore()
// Casting the core selector to a different application will
// result in an error.
if _, err := bSel.ToOneDriveBackup(); err != nil {
// this errors, because bSel is an Exchange selector.
fmt.Println(err)
}
// You can inspect the selector.Service to know which application to use.
switch bSel.Service {
case selectors.ServiceExchange:
//nolint
bSel.ToExchangeBackup()
case selectors.ServiceOneDrive:
//nolint
bSel.ToOneDriveBackup()
}
// Output: OneDrive service is not Exchange: wrong selector service type
}
// ExampleIncludeUsers demonstrates how to specify users in a selector.
func Example_includeUsers() {
seb := selectors.NewExchangeBackup()
// Selectors specify the data that should be handled
// in an operation by specifying the Scope of data.
seb.Include(
// Selector application instances own the API which describes
// the scopes of data that callers may specify.
seb.Users([]string{"my-user-id"}),
)
// Selection scopes can be passed around independently.
yourUser := seb.Users([]string{"your-user-id"})
// Most scopes accept multiple values, unioning them into the final selection.
otherUsers := seb.Users([]string{"foo-user-id", "bar-user-id"})
// Multiple scopes can be added at a time.
// All calls to Include append those scopes to the current set,
// so this addition will also include "my-user-id" from before.
seb.Include(
yourUser,
otherUsers,
)
// Two predefined sets of values exist: any and none.
// Any is a wildcard that accepts all values.
seb.Users(selectors.Any())
// None is the opposite of Any: rejecting all values.
seb.Users(selectors.None())
}
// ExampleIncludeFoldersAndItems demonstrates how to select for granular data.
func Example_includeFoldersAndItems() {
seb := selectors.NewExchangeBackup()
// Much of the data handled by Corso exists within an established hierarchy.
// Resource Owner-level data (such as users) sits at the top, with Folder
// structures and individual items below. Higher level scopes will automatically
// involve all descendant data in the hierarchy.
// Users will select all Exchange data owned by the specified user.
seb.Users([]string{"foo-user-id"})
// Lower level Scopes are described on a per-data-type basis. This scope will
// select all email in the Inbox folder, for all users in the tenant.
seb.MailFolders(selectors.Any(), []string{"Inbox"})
// Folder-level scopes will, by default, include every folder whose name matches
// the provided value, regardless of its position in the hierarchy. If you want
// to restrict the scope to a specific path, you can use the PrefixMatch option.
// This scope selects all data in /foolder, but will skip /other/foolder.
seb.MailFolders(
selectors.Any(),
[]string{"foolder"},
selectors.PrefixMatch())
// Individual items can be selected, too. You don't have to use the Any()
// selection for users and folders when specifying an item, but these ids are
// usually unique, and have a low chance of collision.
seb.Mails(
selectors.Any(),
selectors.Any(),
[]string{"item-id-1", "item-id-2"},
)
}
// ExampleFilters demonstrates selector filters.
func Example_filters() {
ser := selectors.NewExchangeRestore()
// In addition to data ownership details (user, folder, itemID), certain operations
// like `backup details` and restores allow items to be selected by filtering on
// previously gathered metadata.
// Unlike `Include()`, which will incorporate data so long as any Scope matches,
// scopes in the `Filter()` category work as an intersection. Data must pass
// every filter to be selected. The following selector will only include emails
// received before the data, and with the given subject
ser.Filter(
// Note that the ReceivedBefore scope only accepts a single string instead of
// a slice. Since Filters act as intersections rather than unions, it wouldn't
// make much sense to accept multiple values here.
ser.MailReceivedBefore("2006-01-02"),
// But you can still make a compound filter by adding each scope individually.
ser.MailSubject("the answer to life, the universe, and everything"),
)
// Selectors can specify both Filter and Inclusion scopes. Now, not only will the
// data only include emails matching the filters above, it will only include emails
// owned by this one user.
ser.Include(ser.Users([]string{"foo-user-id"}))
}
var (
//nolint
ctxBG = context.Background()
exampleDetails = &details.Details{
DetailsModel: details.DetailsModel{
Entries: []details.DetailsEntry{
{
RepoRef: "tID/exchange/uID/email/example/itemID",
ShortRef: "xyz",
ItemInfo: details.ItemInfo{
Exchange: &details.ExchangeInfo{
ItemType: details.ExchangeMail,
Subject: "the answer to life, the universe, and everything",
},
},
},
},
},
}
)
// ExampleReduceDetails demonstrates how selectors are used to filter backup details.
func Example_reduceDetails() {
ser := selectors.NewExchangeRestore()
// The Reduce() call is where our constructed selectors are applied to the data
// from a previous backup record.
filteredDetails := ser.Reduce(ctxBG, exampleDetails)
// 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))
ser.Include(ser.Mails([]string{"uID"}, []string{"example"}, []string{"xyz"}))
ser.Filter(ser.MailSubject("the answer to life"))
// Now that we've selected our data, we should find a result.
filteredDetails = ser.Reduce(ctxBG, exampleDetails)
fmt.Println("After adding scopes:", len(filteredDetails.Entries))
// Output: Before adding scopes: 0
// After adding scopes: 1
}
// ExampleScopeMatching demonstrates how to compare data against an individual scope.
func Example_scopeMatching() {
// Just like sets of backup data can be filtered down using Reduce(), we can check
// if an individual bit of data matches our scopes, too.
scope := selectors.
NewExchangeBackup().
Mails(
[]string{"id-1"},
[]string{"Inbox"},
selectors.Any(),
)[0]
// To compare data against a scope, you need to specify the category of data,
// and input the value to check.
result := scope.Matches(selectors.ExchangeMailFolder, "inbox")
fmt.Println("Matches the mail folder 'inbox':", result)
// Non-matching values will return false.
result = scope.Matches(selectors.ExchangeUser, "id-42")
fmt.Println("Matches the user by id 'id-42':", result)
// If you specify a category that doesn't belong to the expected
// data type, the result is always false, even if the underlying
// comparators match.
result = scope.Matches(selectors.ExchangeContact, "id-1")
fmt.Println("Matches the contact by id 'id-1':", result)
// When in doubt, you can check the category of data in the scope
// with the Category() method.
cat := scope.Category()
fmt.Println("Scope Category:", cat)
// Output: Matches the mail folder 'inbox': true
// Matches the user by id 'id-42': false
// Matches the contact by id 'id-1': false
// Scope Category: ExchangeMail
}