Select subtrees of data from base snapshots based on reason snapshot was picked (#1828)

## Description

Pick what data to source from a base snapshot by examining the reason the snapshot was selected as a base. This helps avoid two issues:
* pulling in unwanted data when the base has a superset of what is being backed up. Example: pulling in contacts from the base when only email is being backed up
* clobbering already selected data from a different base with data in the base currently being examined. Example: two snapshots, one with contacts and emails and the other with just emails. Second snapshot is newer than the first. The email items in the first snapshot should not clobber those in the first when building the hierarchy

This PR also has the effect of, under some conditions, reducing the amount of data that is pulled from the remote kopia repo when building the hierarchy. This occurs because only the subtrees that will be used in the new backup are traversed instead of traversing the entire snapshot

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

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No 

## Type of change

<!--- Please check the type of change your PR introduces: --->
- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 💻 CI/Deployment
- [ ] 🐹 Trivial/Minor

## Issue(s)

* #1740 

## Test Plan

- [ ] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
ashmrtn 2022-12-16 14:31:13 -08:00 committed by GitHub
parent da929d8448
commit 8c15c3ce16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 466 additions and 145 deletions

View File

@ -15,7 +15,6 @@ import (
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/kopia/kopia/fs" "github.com/kopia/kopia/fs"
"github.com/kopia/kopia/fs/virtualfs" "github.com/kopia/kopia/fs/virtualfs"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/snapshotfs" "github.com/kopia/kopia/snapshot/snapshotfs"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -650,10 +649,33 @@ func traverseBaseDir(
return nil return nil
} }
// TODO(ashmrtn): We may want to move this to BackupOp and pass in
// (Manifest, path) to kopia.BackupCollections() instead of passing in
// ManifestEntry. That would keep kopia from having to know anything about how
// paths are formed. It would just encode/decode them and do basic manipulations
// like pushing/popping elements on a path based on location in the hierarchy.
func encodedElementsForPath(tenant string, r Reason) (*path.Builder, error) {
// This is hacky, but we want the path package to format the path the right
// way (e.x. proper order for service, category, etc), but we don't care about
// the folders after the prefix.
p, err := path.Builder{}.Append("tmp").ToDataLayerPath(
tenant,
r.ResourceOwner,
r.Service,
r.Category,
false,
)
if err != nil {
return nil, errors.Wrap(err, "building path")
}
return p.ToBuilder().Dir(), nil
}
func inflateBaseTree( func inflateBaseTree(
ctx context.Context, ctx context.Context,
loader snapshotLoader, loader snapshotLoader,
snap *snapshot.Manifest, snap *ManifestEntry,
updatedPaths map[string]path.Path, updatedPaths map[string]path.Path,
roots map[string]*treeMap, roots map[string]*treeMap,
) error { ) error {
@ -664,7 +686,7 @@ func inflateBaseTree(
return nil return nil
} }
root, err := loader.SnapshotRoot(snap) root, err := loader.SnapshotRoot(snap.Manifest)
if err != nil { if err != nil {
return errors.Wrapf(err, "getting snapshot %s root directory", snap.ID) return errors.Wrapf(err, "getting snapshot %s root directory", snap.ID)
} }
@ -674,22 +696,47 @@ func inflateBaseTree(
return errors.Errorf("snapshot %s root is not a directory", snap.ID) return errors.Errorf("snapshot %s root is not a directory", snap.ID)
} }
// TODO(ashmrtn): We should actually only traverse a subtree of the snapshot rootName, err := decodeElement(dir.Name())
// where the subtree corresponds to the "reason" this snapshot was chosen. if err != nil {
// Doing so will avoid pulling in data for categories that should not be return errors.Wrapf(err, "snapshot %s root has undecode-able name", snap.ID)
// included in the current backup or overwriting some entries with out-dated }
// information.
if err = traverseBaseDir( // For each subtree corresponding to the tuple
ctx, // (resource owner, service, category) merge the directories in the base with
0, // what has been reported in the collections we got.
updatedPaths, for _, reason := range snap.Reasons {
&path.Builder{}, pb, err := encodedElementsForPath(rootName, reason)
&path.Builder{}, if err != nil {
dir, return errors.Wrapf(err, "snapshot %s getting path elements", snap.ID)
roots, }
); err != nil {
return errors.Wrapf(err, "traversing base snapshot %s", snap.ID) // We're starting from the root directory so don't need it in the path.
pathElems := encodeElements(pb.PopFront().Elements()...)
ent, err := snapshotfs.GetNestedEntry(ctx, dir, pathElems)
if err != nil {
return errors.Wrapf(err, "snapshot %s getting subtree root", snap.ID)
}
subtreeDir, ok := ent.(fs.Directory)
if !ok {
return errors.Wrapf(err, "snapshot %s subtree root is not directory", snap.ID)
}
// We're assuming here that the prefix for the path has not changed (i.e.
// all of tenant, service, resource owner, and category are the same in the
// old snapshot (snap) and the snapshot we're currently trying to make.
if err = traverseBaseDir(
ctx,
0,
updatedPaths,
pb.Dir(),
pb.Dir(),
subtreeDir,
roots,
); err != nil {
return errors.Wrapf(err, "traversing base snapshot %s", snap.ID)
}
} }
return nil return nil
@ -704,7 +751,7 @@ func inflateBaseTree(
func inflateDirTree( func inflateDirTree(
ctx context.Context, ctx context.Context,
loader snapshotLoader, loader snapshotLoader,
baseSnaps []*snapshot.Manifest, baseSnaps []*ManifestEntry,
collections []data.Collection, collections []data.Collection,
progress *corsoProgress, progress *corsoProgress,
) (fs.Directory, error) { ) (fs.Directory, error) {

View File

@ -10,6 +10,7 @@ import (
"github.com/kopia/kopia/fs" "github.com/kopia/kopia/fs"
"github.com/kopia/kopia/fs/virtualfs" "github.com/kopia/kopia/fs/virtualfs"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/snapshot" "github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/snapshotfs" "github.com/kopia/kopia/snapshot/snapshotfs"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -31,30 +32,28 @@ func makePath(t *testing.T, elements []string) path.Path {
return p return p
} }
// baseWithChildren returns an fs.Entry hierarchy where the first four levels // baseWithChildren returns an fs.Entry hierarchy where the first len(basic)
// are the encoded values of tenant, service, user, and category respectively. // levels are the encoded values of basic in order. All items in children are
// All items in children are made a direct descendent of the category entry. // used as the direct descendents of the final entry in basic.
func baseWithChildren( func baseWithChildren(
tenant, service, user, category string, basic []string,
children []fs.Entry, children []fs.Entry,
) fs.Entry { ) fs.Entry {
if len(basic) == 0 {
return nil
}
if len(basic) == 1 {
return virtualfs.NewStaticDirectory(
encodeElements(basic[0])[0],
children,
)
}
return virtualfs.NewStaticDirectory( return virtualfs.NewStaticDirectory(
encodeElements(tenant)[0], encodeElements(basic[0])[0],
[]fs.Entry{ []fs.Entry{
virtualfs.NewStaticDirectory( baseWithChildren(basic[1:], children),
encodeElements(service)[0],
[]fs.Entry{
virtualfs.NewStaticDirectory(
encodeElements(user)[0],
[]fs.Entry{
virtualfs.NewStaticDirectory(
encodeElements(category)[0],
children,
),
},
),
},
),
}, },
) )
} }
@ -66,29 +65,27 @@ type expectedNode struct {
} }
// expectedTreeWithChildren returns an expectedNode hierarchy where the first // expectedTreeWithChildren returns an expectedNode hierarchy where the first
// four levels are the tenant, service, user, and category respectively. All // len(basic) levels are the values of basic in order. All items in children are
// items in children are made a direct descendent of the category node. // made a direct descendent of the final entry in basic.
func expectedTreeWithChildren( func expectedTreeWithChildren(
tenant, service, user, category string, basic []string,
children []*expectedNode, children []*expectedNode,
) *expectedNode { ) *expectedNode {
if len(basic) == 0 {
return nil
}
if len(basic) == 1 {
return &expectedNode{
name: basic[0],
children: children,
}
}
return &expectedNode{ return &expectedNode{
name: tenant, name: basic[0],
children: []*expectedNode{ children: []*expectedNode{
{ expectedTreeWithChildren(basic[1:], children),
name: service,
children: []*expectedNode{
{
name: user,
children: []*expectedNode{
{
name: category,
children: children,
},
},
},
},
},
}, },
} }
} }
@ -802,6 +799,25 @@ func (msw *mockSnapshotWalker) SnapshotRoot(*snapshot.Manifest) (fs.Entry, error
return msw.snapshotRoot, nil return msw.snapshotRoot, nil
} }
func mockSnapshotEntry(
id, resourceOwner string,
service path.ServiceType,
category path.CategoryType,
) *ManifestEntry {
return &ManifestEntry{
Manifest: &snapshot.Manifest{
ID: manifest.ID(id),
},
Reasons: []Reason{
{
ResourceOwner: resourceOwner,
Service: service,
Category: category,
},
},
}
}
func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSingleSubtree() { func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSingleSubtree() {
dirPath := makePath( dirPath := makePath(
suite.T(), suite.T(),
@ -812,10 +828,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSingleSubtree() {
// can only return its Reader once. // can only return its Reader once.
getBaseSnapshot := func() fs.Entry { getBaseSnapshot := func() fs.Entry {
return baseWithChildren( return baseWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]fs.Entry{ []fs.Entry{
virtualfs.NewStaticDirectory( virtualfs.NewStaticDirectory(
encodeElements(testInboxDir)[0], encodeElements(testInboxDir)[0],
@ -846,10 +864,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSingleSubtree() {
return []data.Collection{mc} return []data.Collection{mc}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testInboxDir, name: testInboxDir,
@ -869,10 +889,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSingleSubtree() {
return []data.Collection{mc} return []data.Collection{mc}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testInboxDir, name: testInboxDir,
@ -902,10 +924,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSingleSubtree() {
return []data.Collection{mc} return []data.Collection{mc}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testInboxDir, name: testInboxDir,
@ -937,7 +961,9 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSingleSubtree() {
dirTree, err := inflateDirTree( dirTree, err := inflateDirTree(
ctx, ctx,
msw, msw,
[]*snapshot.Manifest{{}}, []*ManifestEntry{
mockSnapshotEntry("", testUser, path.ExchangeService, path.EmailCategory),
},
test.inputCollections(), test.inputCollections(),
progress, progress,
) )
@ -987,10 +1013,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
// - file3 // - file3
getBaseSnapshot := func() fs.Entry { getBaseSnapshot := func() fs.Entry {
return baseWithChildren( return baseWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]fs.Entry{ []fs.Entry{
virtualfs.NewStaticDirectory( virtualfs.NewStaticDirectory(
encodeElements(testInboxDir)[0], encodeElements(testInboxDir)[0],
@ -1046,10 +1074,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
return []data.Collection{mc} return []data.Collection{mc}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testInboxDir + "2", name: testInboxDir + "2",
@ -1104,10 +1134,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
return []data.Collection{inbox, work} return []data.Collection{inbox, work}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testInboxDir + "2", name: testInboxDir + "2",
@ -1158,10 +1190,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
return []data.Collection{inbox, work} return []data.Collection{inbox, work}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: workDir, name: workDir,
@ -1189,10 +1223,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
return []data.Collection{personal, work} return []data.Collection{personal, work}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testInboxDir, name: testInboxDir,
@ -1229,10 +1265,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
return []data.Collection{personal, work} return []data.Collection{personal, work}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testInboxDir, name: testInboxDir,
@ -1280,10 +1318,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
return []data.Collection{personal} return []data.Collection{personal}
}, },
expected: expectedTreeWithChildren( expected: expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testInboxDir, name: testInboxDir,
@ -1335,7 +1375,9 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeMultipleSubdirecto
dirTree, err := inflateDirTree( dirTree, err := inflateDirTree(
ctx, ctx,
msw, msw,
[]*snapshot.Manifest{{}}, []*ManifestEntry{
mockSnapshotEntry("", testUser, path.ExchangeService, path.EmailCategory),
},
test.inputCollections(t), test.inputCollections(t),
progress, progress,
) )
@ -1375,10 +1417,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSkipsDeletedSubtre
// - file4 // - file4
getBaseSnapshot := func() fs.Entry { getBaseSnapshot := func() fs.Entry {
return baseWithChildren( return baseWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]fs.Entry{ []fs.Entry{
virtualfs.NewStaticDirectory( virtualfs.NewStaticDirectory(
encodeElements(testInboxDir)[0], encodeElements(testInboxDir)[0],
@ -1435,10 +1479,12 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSkipsDeletedSubtre
} }
expected := expectedTreeWithChildren( expected := expectedTreeWithChildren(
testTenant, []string{
service, testTenant,
testUser, service,
category, testUser,
category,
},
[]*expectedNode{ []*expectedNode{
{ {
name: testArchiveDir, name: testArchiveDir,
@ -1489,7 +1535,254 @@ func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSkipsDeletedSubtre
dirTree, err := inflateDirTree( dirTree, err := inflateDirTree(
ctx, ctx,
msw, msw,
[]*snapshot.Manifest{{}}, []*ManifestEntry{
mockSnapshotEntry("", testUser, path.ExchangeService, path.EmailCategory),
},
collections,
progress,
)
require.NoError(t, err)
expectTree(t, ctx, expected, dirTree)
}
type mockMultiSnapshotWalker struct {
snaps map[string]fs.Entry
}
func (msw *mockMultiSnapshotWalker) SnapshotRoot(man *snapshot.Manifest) (fs.Entry, error) {
if snap := msw.snaps[string(man.ID)]; snap != nil {
return snap, nil
}
return nil, errors.New("snapshot not found")
}
func (suite *HierarchyBuilderUnitSuite) TestBuildDirectoryTreeSelectsCorrectSubtrees() {
tester.LogTimeOfTest(suite.T())
t := suite.T()
ctx, flush := tester.NewContext()
defer flush()
const contactsDir = "contacts"
inboxPath := makePath(
suite.T(),
[]string{testTenant, service, testUser, category, testInboxDir},
)
inboxFileName1 := testFileName
inboxFileName2 := testFileName2
inboxFileData1 := testFileData
inboxFileData1v2 := testFileData5
inboxFileData2 := testFileData2
contactsFileName1 := testFileName3
contactsFileData1 := testFileData3
eventsFileName1 := testFileName5
eventsFileData1 := testFileData
// Must be a function that returns a new instance each time as StreamingFile
// can only return its Reader once.
// baseSnapshot with the following layout:
// - a-tenant
// - exchange
// - user1
// - email
// - Inbox
// - file1
// - contacts
// - contacts
// - file2
getBaseSnapshot1 := func() fs.Entry {
return baseWithChildren(
[]string{
testTenant,
service,
testUser,
},
[]fs.Entry{
virtualfs.NewStaticDirectory(
encodeElements(category)[0],
[]fs.Entry{
virtualfs.NewStaticDirectory(
encodeElements(testInboxDir)[0],
[]fs.Entry{
virtualfs.StreamingFileWithModTimeFromReader(
encodeElements(inboxFileName1)[0],
time.Time{},
bytes.NewReader(inboxFileData1),
),
},
),
},
),
virtualfs.NewStaticDirectory(
encodeElements(path.ContactsCategory.String())[0],
[]fs.Entry{
virtualfs.NewStaticDirectory(
encodeElements(contactsDir)[0],
[]fs.Entry{
virtualfs.StreamingFileWithModTimeFromReader(
encodeElements(contactsFileName1)[0],
time.Time{},
bytes.NewReader(contactsFileData1),
),
},
),
},
),
},
)
}
// Must be a function that returns a new instance each time as StreamingFile
// can only return its Reader once.
// baseSnapshot with the following layout:
// - a-tenant
// - exchange
// - user1
// - email
// - Inbox
// - file1 <- has different data version
// - events
// - events
// - file3
getBaseSnapshot2 := func() fs.Entry {
return baseWithChildren(
[]string{
testTenant,
service,
testUser,
},
[]fs.Entry{
virtualfs.NewStaticDirectory(
encodeElements(category)[0],
[]fs.Entry{
virtualfs.NewStaticDirectory(
encodeElements(testInboxDir)[0],
[]fs.Entry{
virtualfs.StreamingFileWithModTimeFromReader(
encodeElements(inboxFileName1)[0],
time.Time{},
// Wrap with a backup reader so it gets the version injected.
newBackupStreamReader(
serializationVersion,
io.NopCloser(bytes.NewReader(inboxFileData1v2)),
),
),
},
),
},
),
virtualfs.NewStaticDirectory(
encodeElements(path.EventsCategory.String())[0],
[]fs.Entry{
virtualfs.NewStaticDirectory(
encodeElements("events")[0],
[]fs.Entry{
virtualfs.StreamingFileWithModTimeFromReader(
encodeElements(eventsFileName1)[0],
time.Time{},
bytes.NewReader(eventsFileData1),
),
},
),
},
),
},
)
}
// Check the following:
// * contacts pulled from base1 unchanged even if no collections reference
// it
// * email pulled from base2
// * new email added
// * events not pulled from base2 as it's not listed as a Reason
//
// Expected output:
// - a-tenant
// - exchange
// - user1
// - email
// - Inbox
// - file1 <- version of data from second base
// - file2
// - contacts
// - contacts
// - file2
expected := expectedTreeWithChildren(
[]string{
testTenant,
service,
testUser,
},
[]*expectedNode{
{
name: category,
children: []*expectedNode{
{
name: testInboxDir,
children: []*expectedNode{
{
name: inboxFileName1,
children: []*expectedNode{},
data: inboxFileData1v2,
},
{
name: inboxFileName2,
children: []*expectedNode{},
data: inboxFileData2,
},
},
},
},
},
{
name: path.ContactsCategory.String(),
children: []*expectedNode{
{
name: contactsDir,
children: []*expectedNode{
{
name: contactsFileName1,
children: []*expectedNode{},
},
},
},
},
},
},
)
progress := &corsoProgress{pending: map[string]*itemDetails{}}
mc := mockconnector.NewMockExchangeCollection(inboxPath, 1)
mc.PrevPath = mc.FullPath()
mc.ColState = data.NotMovedState
mc.Names[0] = inboxFileName2
mc.Data[0] = inboxFileData2
msw := &mockMultiSnapshotWalker{
snaps: map[string]fs.Entry{
"id1": getBaseSnapshot1(),
"id2": getBaseSnapshot2(),
},
}
collections := []data.Collection{mc}
dirTree, err := inflateDirTree(
ctx,
msw,
[]*ManifestEntry{
mockSnapshotEntry("id1", testUser, path.ExchangeService, path.ContactsCategory),
mockSnapshotEntry("id2", testUser, path.ExchangeService, path.EmailCategory),
},
collections, collections,
progress, progress,
) )

View File

@ -360,12 +360,13 @@ func (pb Builder) ToServiceCategoryMetadataPath(
}, nil }, nil
} }
func (pb Builder) ToDataLayerExchangePathForCategory( func (pb Builder) ToDataLayerPath(
tenant, user string, tenant, user string,
service ServiceType,
category CategoryType, category CategoryType,
isItem bool, isItem bool,
) (Path, error) { ) (Path, error) {
if err := validateServiceAndCategory(ExchangeService, category); err != nil { if err := validateServiceAndCategory(service, category); err != nil {
return nil, err return nil, err
} }
@ -376,35 +377,29 @@ func (pb Builder) ToDataLayerExchangePathForCategory(
return &dataLayerResourcePath{ return &dataLayerResourcePath{
Builder: *pb.withPrefix( Builder: *pb.withPrefix(
tenant, tenant,
ExchangeService.String(), service.String(),
user, user,
category.String(), category.String(),
), ),
service: ExchangeService, service: service,
category: category, category: category,
hasItem: isItem, hasItem: isItem,
}, nil }, nil
} }
func (pb Builder) ToDataLayerExchangePathForCategory(
tenant, user string,
category CategoryType,
isItem bool,
) (Path, error) {
return pb.ToDataLayerPath(tenant, user, ExchangeService, category, isItem)
}
func (pb Builder) ToDataLayerOneDrivePath( func (pb Builder) ToDataLayerOneDrivePath(
tenant, user string, tenant, user string,
isItem bool, isItem bool,
) (Path, error) { ) (Path, error) {
if err := pb.verifyPrefix(tenant, user); err != nil { return pb.ToDataLayerPath(tenant, user, OneDriveService, FilesCategory, isItem)
return nil, err
}
return &dataLayerResourcePath{
Builder: *pb.withPrefix(
tenant,
OneDriveService.String(),
user,
FilesCategory.String(),
),
service: OneDriveService,
category: FilesCategory,
hasItem: isItem,
}, nil
} }
func (pb Builder) ToDataLayerSharePointPath( func (pb Builder) ToDataLayerSharePointPath(
@ -412,21 +407,7 @@ func (pb Builder) ToDataLayerSharePointPath(
category CategoryType, category CategoryType,
isItem bool, isItem bool,
) (Path, error) { ) (Path, error) {
if err := pb.verifyPrefix(tenant, site); err != nil { return pb.ToDataLayerPath(tenant, site, SharePointService, category, isItem)
return nil, err
}
return &dataLayerResourcePath{
Builder: *pb.withPrefix(
tenant,
SharePointService.String(),
site,
category.String(),
),
service: SharePointService,
category: category,
hasItem: isItem,
}, nil
} }
// FromDataLayerPath parses the escaped path p, validates the elements in p // FromDataLayerPath parses the escaped path p, validates the elements in p