diff --git a/src/pkg/path/builder.go b/src/pkg/path/builder.go index 1cf502079..2e0237a6c 100644 --- a/src/pkg/path/builder.go +++ b/src/pkg/path/builder.go @@ -203,28 +203,30 @@ func (pb Builder) withPrefix(elements ...string) *Builder { // verifyPrefix ensures that the tenant and resourceOwner are valid // values, and that the builder has some directory structure. -func (pb Builder) verifyPrefix(tenant, resourceOwner string) error { - if err := verifyInputValues(tenant, resourceOwner); err != nil { - return err - } +// func (pb Builder) verifyPrefix(tenant, resourceOwner string) error { +// if err := verifyPrefixValues(tenant, resourceOwner); err != nil { +// return err +// } - if len(pb.elements) == 0 { - return clues.New("missing path beyond prefix") - } +// if len(pb.elements) == 0 { +// return clues.New("missing path beyond prefix") +// } - return nil -} +// return nil +// } // --------------------------------------------------------------------------- // Data Layer Path Transformers // --------------------------------------------------------------------------- func (pb Builder) ToStreamStorePath( - tenant, purpose string, - service ServiceType, + tenant string, + srs []ServiceResource, isItem bool, ) (Path, error) { - if err := verifyInputValues(tenant, purpose); err != nil { + cat := DetailsCategory + + if err := verifyPrefixValues(tenant, srs, cat); err != nil { return nil, err } @@ -232,40 +234,18 @@ func (pb Builder) ToStreamStorePath( return nil, clues.New("missing path beyond prefix") } - metadataService := UnknownService + dlrp := newDataLayerResourcePath(pb, tenant, toMetadataServices(srs), cat, isItem) - switch service { - case ExchangeService: - metadataService = ExchangeMetadataService - case OneDriveService: - metadataService = OneDriveMetadataService - case SharePointService: - metadataService = SharePointMetadataService - } - - return &dataLayerResourcePath{ - Builder: *pb.withPrefix( - tenant, - metadataService.String(), - purpose, - DetailsCategory.String()), - service: metadataService, - category: DetailsCategory, - hasItem: isItem, - }, nil + return &dlrp, nil } func (pb Builder) ToServiceCategoryMetadataPath( - tenant, user string, - service ServiceType, - category CategoryType, + tenant string, + srs []ServiceResource, + cat CategoryType, isItem bool, ) (Path, error) { - if err := ValidateServiceAndCategory(service, category); err != nil { - return nil, err - } - - if err := verifyInputValues(tenant, user); err != nil { + if err := verifyPrefixValues(tenant, srs, cat); err != nil { return nil, err } @@ -273,79 +253,66 @@ func (pb Builder) ToServiceCategoryMetadataPath( return nil, clues.New("missing path beyond prefix") } - metadataService := UnknownService + dlrp := newDataLayerResourcePath(pb, tenant, toMetadataServices(srs), cat, isItem) - switch service { - case ExchangeService: - metadataService = ExchangeMetadataService - case OneDriveService: - metadataService = OneDriveMetadataService - case SharePointService: - metadataService = SharePointMetadataService - } - - return &dataLayerResourcePath{ - Builder: *pb.withPrefix( - tenant, - metadataService.String(), - user, - category.String(), - ), - service: metadataService, - category: category, - hasItem: isItem, - }, nil + return &dlrp, nil } func (pb Builder) ToDataLayerPath( - tenant, user string, - service ServiceType, - category CategoryType, + tenant string, + srs []ServiceResource, + cat CategoryType, isItem bool, ) (Path, error) { - if err := ValidateServiceAndCategory(service, category); err != nil { + if err := verifyPrefixValues(tenant, srs, cat); err != nil { return nil, err } - if err := pb.verifyPrefix(tenant, user); err != nil { - return nil, err - } + dlrp := newDataLayerResourcePath(pb, tenant, srs, cat, isItem) - return &dataLayerResourcePath{ - Builder: *pb.withPrefix( - tenant, - service.String(), - user, - category.String()), - service: service, - category: category, - hasItem: isItem, - }, nil + return &dlrp, nil } func (pb Builder) ToDataLayerExchangePathForCategory( - tenant, user string, + tenant, mailboxID string, category CategoryType, isItem bool, ) (Path, error) { - return pb.ToDataLayerPath(tenant, user, ExchangeService, category, isItem) + srs, err := NewServiceResources(ExchangeService, mailboxID) + if err != nil { + return nil, err + } + + return pb.ToDataLayerPath(tenant, srs, category, isItem) } func (pb Builder) ToDataLayerOneDrivePath( - tenant, user string, + tenant, userID string, isItem bool, ) (Path, error) { - return pb.ToDataLayerPath(tenant, user, OneDriveService, FilesCategory, isItem) + srs, err := NewServiceResources(OneDriveService, userID) + if err != nil { + return nil, err + } + + return pb.ToDataLayerPath(tenant, srs, FilesCategory, isItem) } func (pb Builder) ToDataLayerSharePointPath( - tenant, site string, + tenant, siteID string, category CategoryType, isItem bool, ) (Path, error) { - return pb.ToDataLayerPath(tenant, site, SharePointService, category, isItem) + srs, err := NewServiceResources(SharePointService, siteID) + if err != nil { + return nil, err + } + + return pb.ToDataLayerPath(tenant, srs, category, isItem) } +// TODO: ToDataLayerGroupsPath() + // --------------------------------------------------------------------------- // Stringers and PII Concealer Compliance // --------------------------------------------------------------------------- diff --git a/src/pkg/path/builder_test.go b/src/pkg/path/builder_test.go index cb483606d..a1833dbb1 100644 --- a/src/pkg/path/builder_test.go +++ b/src/pkg/path/builder_test.go @@ -46,7 +46,10 @@ func (suite *BuilderUnitSuite) TestAppend() { func (suite *BuilderUnitSuite) TestAppendItem() { t := suite.T() - p, err := Build("t", "ro", ExchangeService, EmailCategory, false, "foo", "bar") + srs, err := NewServiceResources(ExchangeService, "ro") + require.NoError(t, err, clues.ToCore(err)) + + p, err := Build("t", srs, EmailCategory, false, "foo", "bar") require.NoError(t, err, clues.ToCore(err)) pb := p.ToBuilder() @@ -339,8 +342,13 @@ func (suite *BuilderUnitSuite) TestFolder() { } func (suite *BuilderUnitSuite) TestPIIHandling() { - p, err := Build("t", "ro", ExchangeService, EventsCategory, true, "dir", "item") - require.NoError(suite.T(), err) + t := suite.T() + + srs, err := NewServiceResources(ExchangeService, "ro") + require.NoError(t, err, clues.ToCore(err)) + + p, err := Build("t", srs, EventsCategory, true, "dir", "item") + require.NoError(t, err) table := []struct { name string diff --git a/src/pkg/path/category_type.go b/src/pkg/path/category_type.go index 4a992176f..328d63094 100644 --- a/src/pkg/path/category_type.go +++ b/src/pkg/path/category_type.go @@ -75,24 +75,6 @@ var serviceCategories = map[ServiceType]map[CategoryType]struct{}{ }, } -func validateServiceAndCategoryStrings(s, c string) (ServiceType, CategoryType, error) { - service := toServiceType(s) - if service == UnknownService { - return UnknownService, UnknownCategory, clues.Stack(ErrorUnknownService).With("service", fmt.Sprintf("%q", s)) - } - - category := ToCategoryType(c) - if category == UnknownCategory { - return UnknownService, UnknownCategory, clues.Stack(ErrorUnknownService).With("category", fmt.Sprintf("%q", c)) - } - - if err := ValidateServiceAndCategory(service, category); err != nil { - return UnknownService, UnknownCategory, err - } - - return service, category, nil -} - func ValidateServiceAndCategory(service ServiceType, category CategoryType) error { cats, ok := serviceCategories[service] if !ok { diff --git a/src/pkg/path/drive_test.go b/src/pkg/path/drive_test.go index e457a4423..a4e149ce4 100644 --- a/src/pkg/path/drive_test.go +++ b/src/pkg/path/drive_test.go @@ -59,7 +59,10 @@ func (suite *OneDrivePathSuite) Test_ToOneDrivePath() { suite.Run(tt.name, func() { t := suite.T() - p, err := path.Build("tenant", "user", path.OneDriveService, path.FilesCategory, false, tt.pathElements...) + srs, err := path.NewServiceResources(path.OneDriveService, "user") + require.NoError(t, err, clues.ToCore(err)) + + p, err := path.Build("tenant", srs, path.FilesCategory, false, tt.pathElements...) require.NoError(suite.T(), err, clues.ToCore(err)) got, err := path.ToDrivePath(p) diff --git a/src/pkg/path/path.go b/src/pkg/path/path.go index 8f8463b42..8138f0f75 100644 --- a/src/pkg/path/path.go +++ b/src/pkg/path/path.go @@ -79,10 +79,12 @@ var ( // string. type Path interface { String() string - Service() ServiceType + // ServiceResources produces all of the services and subservices, along with + // the protected resource paired with the service, as contained in the path, + // in their order of appearance. + ServiceResources() []ServiceResource Category() CategoryType Tenant() string - ResourceOwner() string Folder(escaped bool) string Folders() Elements Item() string @@ -132,41 +134,29 @@ type RestorePaths struct { // --------------------------------------------------------------------------- func Build( - tenant, resourceOwner string, - service ServiceType, + tenant string, + srs []ServiceResource, category CategoryType, hasItem bool, elements ...string, ) (Path, error) { - b := Builder{}.Append(elements...) - - return b.ToDataLayerPath( - tenant, resourceOwner, - service, category, - hasItem) + return Builder{}. + Append(elements...). + ToDataLayerPath(tenant, srs, category, hasItem) } func BuildPrefix( - tenant, resourceOwner string, - s ServiceType, - c CategoryType, + tenant string, + srs []ServiceResource, + cat CategoryType, ) (Path, error) { - pb := Builder{} - - if err := ValidateServiceAndCategory(s, c); err != nil { + if err := verifyPrefixValues(tenant, srs, cat); err != nil { return nil, err } - if err := verifyInputValues(tenant, resourceOwner); err != nil { - return nil, err - } + dlrp := newDataLayerResourcePath(Builder{}, tenant, srs, cat, false) - return &dataLayerResourcePath{ - Builder: *pb.withPrefix(tenant, s.String(), resourceOwner, c.String()), - service: s, - category: c, - hasItem: false, - }, nil + return &dlrp, nil } // FromDataLayerPath parses the escaped path p, validates the elements in p @@ -186,24 +176,37 @@ func FromDataLayerPath(p string, isItem bool) (Path, error) { return nil, clues.Stack(errParsingPath, err).With("path_string", p) } + // initial check for minimum required elements: + // tenant, service, resource, category, container/item if len(pb.elements) < 5 { return nil, clues.New("path has too few segments").With("path_string", p) } - service, category, err := validateServiceAndCategoryStrings( - pb.elements[1], - pb.elements[3], - ) + srs, catIdx, err := ElementsToServiceResources(pb.elements[1:]) if err != nil { + return nil, clues.Stack(err) + } + + // follow-up check: if more than one service exists, revisit the len check. + if len(srs) > 1 && len(pb.elements) < 3+(2*len(srs)) { + return nil, clues.New("path has too few segments").With("path_string", p) + } + + // +1 to account for slicing the tenant when calling the transformer func. + category := ToCategoryType(pb.elements[catIdx+1]) + + if err := verifyPrefixValues(pb.elements[0], srs, category); err != nil { return nil, clues.Stack(errParsingPath, err).With("path_string", p) } - return &dataLayerResourcePath{ - Builder: *pb, - service: service, - category: category, - hasItem: isItem, - }, nil + dlrp := dataLayerResourcePath{ + Builder: *pb, + serviceResources: srs, + category: category, + hasItem: isItem, + } + + return &dlrp, nil } // TrimTrailingSlash takes an escaped path element and returns an escaped path @@ -290,16 +293,21 @@ func Split(segment string) []string { // Unexported Helpers // --------------------------------------------------------------------------- -func verifyInputValues(tenant, resourceOwner string) error { +func verifyPrefixValues( + tenant string, + srs []ServiceResource, + cat CategoryType, +) error { if len(tenant) == 0 { return clues.Stack(errMissingSegment, clues.New("tenant")) } - if len(resourceOwner) == 0 { - return clues.Stack(errMissingSegment, clues.New("resourceOwner")) + if err := validateServiceResources(srs); err != nil { + return err } - return nil + // only the final service is checked for its category validity + return ValidateServiceAndCategory(srs[len(srs)-1].Service, cat) } // escapeElement takes a single path element and escapes all characters that diff --git a/src/pkg/path/path_test.go b/src/pkg/path/path_test.go index ca3055983..faccdac0d 100644 --- a/src/pkg/path/path_test.go +++ b/src/pkg/path/path_test.go @@ -334,14 +334,12 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() { testUser, testElement1, testElement2, - testElement3, - ), + testElement3), expectedFolder: fmt.Sprintf( "%s/%s/%s", testElementTrimmed, testElement2, - testElement3, - ), + testElement3), expectedSplit: []string{ testElementTrimmed, testElement2, @@ -351,8 +349,7 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() { expectedItemFolder: fmt.Sprintf( "%s/%s", testElementTrimmed, - testElement2, - ), + testElement2), expectedItemSplit: []string{ testElementTrimmed, testElement2, @@ -366,14 +363,12 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() { testUser, testElementTrimmed, testElement2, - testElement3, - ), + testElement3), expectedFolder: fmt.Sprintf( "%s/%s/%s", testElementTrimmed, testElement2, - testElement3, - ), + testElement3), expectedSplit: []string{ testElementTrimmed, testElement2, @@ -383,8 +378,7 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() { expectedItemFolder: fmt.Sprintf( "%s/%s", testElementTrimmed, - testElement2, - ), + testElement2), expectedItemSplit: []string{ testElementTrimmed, testElement2, @@ -393,21 +387,27 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() { } for service, cats := range serviceCategories { + for cat := range cats { + for _, item := range isItem { suite.Run(fmt.Sprintf("%s-%s-%s", service, cat, item.name), func() { + for _, test := range table { suite.Run(test.name, func() { - t := suite.T() - testPath := fmt.Sprintf(test.unescapedPath, service, cat) + var ( + t = suite.T() + testPath = fmt.Sprintf(test.unescapedPath, service, cat) + sr = ServiceResource{service, testUser} + ) p, err := FromDataLayerPath(testPath, item.isItem) require.NoError(t, err, clues.ToCore(err)) - assert.Equal(t, service, p.Service(), "service") + assert.Len(t, p.ServiceResources(), 1, "service resources") + assert.Equal(t, sr, p.ServiceResources()[0], "service resource") assert.Equal(t, cat, p.Category(), "category") assert.Equal(t, testTenant, p.Tenant(), "tenant") - assert.Equal(t, testUser, p.ResourceOwner(), "resource owner") fld := p.Folder(false) escfld := p.Folder(true) @@ -435,44 +435,74 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() { func (suite *PathUnitSuite) TestBuildPrefix() { table := []struct { name string - service ServiceType - category CategoryType tenant string - owner string + srs []ServiceResource + category CategoryType expect string expectErr require.ErrorAssertionFunc }{ { name: "ok", - service: ExchangeService, - category: ContactsCategory, tenant: "t", - owner: "ro", - expect: join([]string{"t", ExchangeService.String(), "ro", ContactsCategory.String()}), + srs: []ServiceResource{{ExchangeService, "roo"}}, + category: ContactsCategory, + expect: join([]string{"t", ExchangeService.String(), "roo", ContactsCategory.String()}), + expectErr: require.NoError, + }, + { + name: "ok with subservice", + tenant: "t", + srs: []ServiceResource{ + {GroupsService, "roo"}, + {SharePointService, "oor"}, + }, + category: LibrariesCategory, + expect: join([]string{ + "t", + GroupsService.String(), "roo", + SharePointService.String(), "oor", + LibrariesCategory.String()}), expectErr: require.NoError, }, { name: "bad category", - service: ExchangeService, + srs: []ServiceResource{{ExchangeService, "roo"}}, category: FilesCategory, tenant: "t", - owner: "ro", expectErr: require.Error, }, { name: "bad tenant", - service: ExchangeService, - category: ContactsCategory, tenant: "", - owner: "ro", + srs: []ServiceResource{{ExchangeService, "roo"}}, + category: ContactsCategory, expectErr: require.Error, }, { - name: "bad owner", - service: ExchangeService, - category: ContactsCategory, + name: "bad resource", tenant: "t", - owner: "", + srs: []ServiceResource{{ExchangeService, ""}}, + category: ContactsCategory, + expectErr: require.Error, + }, + { + name: "bad subservice", + tenant: "t", + srs: []ServiceResource{ + {ExchangeService, "roo"}, + {OneDriveService, "oor"}, + }, + category: FilesCategory, + expectErr: require.Error, + }, + { + name: "bad subservice resource", + tenant: "t", + srs: []ServiceResource{ + {GroupsService, "roo"}, + {SharePointService, ""}, + }, + category: LibrariesCategory, expectErr: require.Error, }, } @@ -480,7 +510,7 @@ func (suite *PathUnitSuite) TestBuildPrefix() { suite.Run(test.name, func() { t := suite.T() - r, err := BuildPrefix(test.tenant, test.owner, test.service, test.category) + r, err := BuildPrefix(test.tenant, test.srs, test.category) test.expectErr(t, err, clues.ToCore(err)) if r == nil { diff --git a/src/pkg/path/resource_path.go b/src/pkg/path/resource_path.go index 10dd71eb7..37738131a 100644 --- a/src/pkg/path/resource_path.go +++ b/src/pkg/path/resource_path.go @@ -18,9 +18,28 @@ import ( // element after the prefix. type dataLayerResourcePath struct { Builder - category CategoryType - service ServiceType - hasItem bool + category CategoryType + serviceResources []ServiceResource + hasItem bool +} + +// performs no validation, assumes the caller has validated the inputs. +func newDataLayerResourcePath( + pb Builder, + tenant string, + srs []ServiceResource, + cat CategoryType, + isItem bool, +) dataLayerResourcePath { + pfx := append([]string{tenant}, ServiceResourcesToElements(srs)...) + pfx = append(pfx, cat.String()) + + return dataLayerResourcePath{ + Builder: *pb.withPrefix(pfx...), + serviceResources: srs, + category: cat, + hasItem: isItem, + } } // Tenant returns the tenant ID embedded in the dataLayerResourcePath. @@ -28,9 +47,8 @@ func (rp dataLayerResourcePath) Tenant() string { return rp.Builder.elements[0] } -// Service returns the ServiceType embedded in the dataLayerResourcePath. -func (rp dataLayerResourcePath) Service() ServiceType { - return rp.service +func (rp dataLayerResourcePath) ServiceResources() []ServiceResource { + return rp.serviceResources } // Category returns the CategoryType embedded in the dataLayerResourcePath. @@ -95,15 +113,18 @@ func (rp dataLayerResourcePath) Item() string { // Dir removes the last element from the path. If this would remove a // value that is part of the standard prefix structure, an error is returned. func (rp dataLayerResourcePath) Dir() (Path, error) { - if len(rp.elements) <= 4 { + // Dir is not allowed to slice off any prefix values. + // The prefix len is determined by the length of the number of + // service+resource tuples, plus 2 (tenant and category). + if len(rp.elements) <= 2+(2*len(rp.serviceResources)) { return nil, clues.New("unable to shorten path").With("path", rp) } return &dataLayerResourcePath{ - Builder: *rp.Builder.Dir(), - service: rp.service, - category: rp.category, - hasItem: false, + Builder: *rp.Builder.Dir(), + serviceResources: rp.serviceResources, + category: rp.category, + hasItem: false, }, nil } @@ -116,10 +137,10 @@ func (rp dataLayerResourcePath) Append( } return &dataLayerResourcePath{ - Builder: *rp.Builder.Append(elems...), - service: rp.service, - category: rp.category, - hasItem: isItem, + Builder: *rp.Builder.Append(elems...), + serviceResources: rp.serviceResources, + category: rp.category, + hasItem: isItem, }, nil } diff --git a/src/pkg/path/resource_path_test.go b/src/pkg/path/resource_path_test.go index e49f797e2..3a7dfd3c3 100644 --- a/src/pkg/path/resource_path_test.go +++ b/src/pkg/path/resource_path_test.go @@ -256,10 +256,10 @@ func (suite *DataLayerResourcePath) TestDir() { func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() { tenant := "a-tenant" - user := "a-user" + resource := "a-resource" table := []struct { name string - service path.ServiceType + srs []path.ServiceResource category path.CategoryType postfix []string expectedService path.ServiceType @@ -267,14 +267,14 @@ func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() { }{ { name: "NoPostfixPasses", - service: path.ExchangeService, + srs: []path.ServiceResource{{path.ExchangeService, resource}}, category: path.EmailCategory, expectedService: path.ExchangeMetadataService, check: assert.NoError, }, { name: "PostfixPasses", - service: path.ExchangeService, + srs: []path.ServiceResource{{path.ExchangeService, resource}}, category: path.EmailCategory, postfix: []string{"a", "b"}, expectedService: path.ExchangeMetadataService, @@ -282,48 +282,48 @@ func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() { }, { name: "Fails", - service: path.ExchangeService, + srs: []path.ServiceResource{{path.ExchangeService, resource}}, category: path.FilesCategory, check: assert.Error, }, { name: "Passes", - service: path.ExchangeService, + srs: []path.ServiceResource{{path.ExchangeService, resource}}, category: path.ContactsCategory, expectedService: path.ExchangeMetadataService, check: assert.NoError, }, { name: "Passes", - service: path.ExchangeService, + srs: []path.ServiceResource{{path.ExchangeService, resource}}, category: path.EventsCategory, expectedService: path.ExchangeMetadataService, check: assert.NoError, }, { name: "Passes", - service: path.OneDriveService, + srs: []path.ServiceResource{{path.OneDriveService, resource}}, category: path.FilesCategory, expectedService: path.OneDriveMetadataService, check: assert.NoError, }, { name: "Passes", - service: path.SharePointService, + srs: []path.ServiceResource{{path.SharePointService, resource}}, category: path.LibrariesCategory, expectedService: path.SharePointMetadataService, check: assert.NoError, }, { name: "Passes", - service: path.SharePointService, + srs: []path.ServiceResource{{path.SharePointService, resource}}, category: path.ListsCategory, expectedService: path.SharePointMetadataService, check: assert.NoError, }, { name: "Passes", - service: path.SharePointService, + srs: []path.ServiceResource{{path.SharePointService, resource}}, category: path.PagesCategory, expectedService: path.SharePointMetadataService, check: assert.NoError, @@ -331,27 +331,26 @@ func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() { } for _, test := range table { - suite.Run(strings.Join([]string{ + name := strings.Join([]string{ test.name, - test.service.String(), + test.srs[0].Service.String(), test.category.String(), - }, "_"), func() { + }, "_") + + suite.Run(name, func() { t := suite.T() pb := path.Builder{}.Append(test.postfix...) p, err := pb.ToServiceCategoryMetadataPath( tenant, - user, - test.service, + test.srs, test.category, false) test.check(t, err, clues.ToCore(err)) - if err != nil { - return + if err == nil { + assert.Equal(t, test.expectedService, p.ServiceResources()[0]) } - - assert.Equal(t, test.expectedService, p.Service()) }) } } @@ -402,9 +401,9 @@ func (suite *DataLayerResourcePath) TestToExchangePathForCategory() { } assert.Equal(t, testTenant, p.Tenant()) - assert.Equal(t, path.ExchangeService, p.Service()) + assert.Equal(t, path.ExchangeService, p.ServiceResources()[0].Service) assert.Equal(t, test.category, p.Category()) - assert.Equal(t, testUser, p.ResourceOwner()) + assert.Equal(t, testUser, p.ServiceResources()[0].ProtectedResource) assert.Equal(t, strings.Join(m.expectedFolders, "/"), p.Folder(false)) assert.Equal(t, path.Elements(m.expectedFolders), p.Folders()) assert.Equal(t, m.expectedItem, p.Item()) @@ -456,7 +455,10 @@ func (suite *PopulatedDataLayerResourcePath) TestService() { suite.Run(m.name, func() { t := suite.T() - assert.Equal(t, path.ExchangeService, suite.paths[m.isItem].Service()) + assert.Equal( + t, + path.ExchangeService, + suite.paths[m.isItem].ServiceResources()[0].Service) }) } } @@ -476,7 +478,10 @@ func (suite *PopulatedDataLayerResourcePath) TestResourceOwner() { suite.Run(m.name, func() { t := suite.T() - assert.Equal(t, testUser, suite.paths[m.isItem].ResourceOwner()) + assert.Equal( + t, + testUser, + suite.paths[m.isItem].ServiceResources()[0].ProtectedResource) }) } } diff --git a/src/pkg/path/service_category_test.go b/src/pkg/path/service_category_test.go index d2b19b244..37aa5774e 100644 --- a/src/pkg/path/service_category_test.go +++ b/src/pkg/path/service_category_test.go @@ -20,118 +20,76 @@ func TestServiceCategoryUnitSuite(t *testing.T) { suite.Run(t, s) } -func (suite *ServiceCategoryUnitSuite) TestValidateServiceAndCategoryBadStringErrors() { +func (suite *ServiceCategoryUnitSuite) TestVerifyPrefixValues() { table := []struct { name string - service string - category string - }{ - { - name: "Service", - service: "foo", - category: EmailCategory.String(), - }, - { - name: "Category", - service: ExchangeService.String(), - category: "foo", - }, - } - for _, test := range table { - suite.Run(test.name, func() { - _, _, err := validateServiceAndCategoryStrings(test.service, test.category) - assert.Error(suite.T(), err) - }) - } -} - -func (suite *ServiceCategoryUnitSuite) TestValidateServiceAndCategory() { - table := []struct { - name string - service string - category string - expectedService ServiceType - expectedCategory CategoryType - check assert.ErrorAssertionFunc + service ServiceType + category CategoryType + check assert.ErrorAssertionFunc }{ { name: "UnknownService", - service: UnknownService.String(), - category: EmailCategory.String(), + service: UnknownService, + category: EmailCategory, check: assert.Error, }, { name: "UnknownCategory", - service: ExchangeService.String(), - category: UnknownCategory.String(), + service: ExchangeService, + category: UnknownCategory, check: assert.Error, }, { - name: "BadServiceString", - service: "foo", - category: EmailCategory.String(), + name: "BadServiceType", + service: ServiceType(-1), + category: EmailCategory, check: assert.Error, }, { - name: "BadCategoryString", - service: ExchangeService.String(), - category: "foo", + name: "BadCategoryType", + service: ExchangeService, + category: CategoryType(-1), check: assert.Error, }, { - name: "ExchangeEmail", - service: ExchangeService.String(), - category: EmailCategory.String(), - expectedService: ExchangeService, - expectedCategory: EmailCategory, - check: assert.NoError, + name: "ExchangeEmail", + service: ExchangeService, + category: EmailCategory, + check: assert.NoError, }, { - name: "ExchangeContacts", - service: ExchangeService.String(), - category: ContactsCategory.String(), - expectedService: ExchangeService, - expectedCategory: ContactsCategory, - check: assert.NoError, + name: "ExchangeContacts", + service: ExchangeService, + category: ContactsCategory, + check: assert.NoError, }, { - name: "ExchangeEvents", - service: ExchangeService.String(), - category: EventsCategory.String(), - expectedService: ExchangeService, - expectedCategory: EventsCategory, - check: assert.NoError, + name: "ExchangeEvents", + service: ExchangeService, + category: EventsCategory, + check: assert.NoError, }, { - name: "OneDriveFiles", - service: OneDriveService.String(), - category: FilesCategory.String(), - expectedService: OneDriveService, - expectedCategory: FilesCategory, - check: assert.NoError, + name: "OneDriveFiles", + service: OneDriveService, + category: FilesCategory, + check: assert.NoError, }, { - name: "SharePointLibraries", - service: SharePointService.String(), - category: LibrariesCategory.String(), - expectedService: SharePointService, - expectedCategory: LibrariesCategory, - check: assert.NoError, + name: "SharePointLibraries", + service: SharePointService, + category: LibrariesCategory, + check: assert.NoError, }, } for _, test := range table { suite.Run(test.name, func() { t := suite.T() - s, c, err := validateServiceAndCategoryStrings(test.service, test.category) + srs := []ServiceResource{{test.service, "resource"}} + + err := verifyPrefixValues("tid", srs, test.category) test.check(t, err, clues.ToCore(err)) - - if err != nil { - return - } - - assert.Equal(t, test.expectedService, s) - assert.Equal(t, test.expectedCategory, c) }) } } diff --git a/src/pkg/path/service_resource.go b/src/pkg/path/service_resource.go index bbfc3f4c7..7b03fed82 100644 --- a/src/pkg/path/service_resource.go +++ b/src/pkg/path/service_resource.go @@ -85,6 +85,40 @@ func ServiceResourcesToElements(srs []ServiceResource) Elements { return es } +// ElementsToServiceResources turns as many pairs of elems as possible +// into ServiceResource tuples. Elems must begin with a service, but +// may contain more entries than there are service/resource pairs. +// This transformer will continue consuming elements until it finds an +// even-numbered index that cannot be cast to a ServiceType. +// Returns the serviceResource pairs, the first index containing element +// that is not part of a service/resource pair, and an error if elems is +// len==0 or contains no services. +func ElementsToServiceResources(elems Elements) ([]ServiceResource, int, error) { + if len(elems) == 0 { + return nil, -1, clues.Wrap(errMissingSegment, "service") + } + + var ( + srs = make([]ServiceResource, 0) + i int + ) + + for j := 1; i < len(elems); i, j = i+2, j+2 { + service := toServiceType(elems[i]) + if service == UnknownService { + if i == 0 { + return nil, -1, clues.Wrap(errMissingSegment, "service") + } + + break + } + + srs = append(srs, ServiceResource{service, elems[j]}) + } + + return srs, i, nil +} + // checks for the following: // 1. each ServiceResource is valid // 2. if len(srs) > 1, srs[i], srs[i+1] pass subservice checks. @@ -112,3 +146,29 @@ func validateServiceResources(srs []ServiceResource) error { return nil } + +// makes a copy of the slice with all of the Services swapped for their +// metadata service countterparts. +func toMetadataServices(srs []ServiceResource) []ServiceResource { + msrs := make([]ServiceResource, 0, len(srs)) + + for _, sr := range srs { + msr := sr + metadataService := UnknownService + + switch sr.Service { + case ExchangeService: + metadataService = ExchangeMetadataService + case OneDriveService: + metadataService = OneDriveMetadataService + case SharePointService: + metadataService = SharePointMetadataService + // TODO: add groups + } + + msr.Service = metadataService + msrs = append(msrs, msr) + } + + return msrs +} diff --git a/src/pkg/path/service_resource_test.go b/src/pkg/path/service_resource_test.go index 78dea7aab..29c9d964b 100644 --- a/src/pkg/path/service_resource_test.go +++ b/src/pkg/path/service_resource_test.go @@ -125,7 +125,7 @@ func (suite *ServiceResourceUnitSuite) TestValidateServiceResources() { } } -func (suite *ServiceResourceUnitSuite) TestServiceResourceElements() { +func (suite *ServiceResourceUnitSuite) TestServiceResourceToElements() { table := []struct { name string srs []ServiceResource @@ -164,3 +164,80 @@ func (suite *ServiceResourceUnitSuite) TestServiceResourceElements() { }) } } + +func (suite *ServiceResourceUnitSuite) TestElementsToServiceResource() { + table := []struct { + name string + elems Elements + expectErr assert.ErrorAssertionFunc + expectIdx int + expectSRS []ServiceResource + }{ + { + name: "empty", + elems: Elements{}, + expectErr: assert.Error, + expectIdx: -1, + expectSRS: nil, + }, + { + name: "nil", + elems: nil, + expectErr: assert.Error, + expectIdx: -1, + expectSRS: nil, + }, + { + name: "non-service 0th elem", + elems: Elements{"fnords"}, + expectErr: assert.Error, + expectIdx: -1, + expectSRS: nil, + }, + { + name: "non-service 2nd elem", + elems: Elements{ExchangeService.String(), "fnords", "smarf"}, + expectErr: assert.Error, + expectIdx: -1, + expectSRS: nil, + }, + { + name: "single serviceResource", + elems: Elements{ExchangeService.String(), "fnords"}, + expectErr: assert.NoError, + expectIdx: 2, + expectSRS: []ServiceResource{{ExchangeService, "fnords"}}, + }, + { + name: "single serviceResource and extra value", + elems: Elements{ExchangeService.String(), "fnords", "smarf"}, + expectErr: assert.NoError, + expectIdx: 2, + expectSRS: []ServiceResource{{ExchangeService, "fnords"}}, + }, + { + name: "multiple serviceResource", + elems: Elements{ExchangeService.String(), "fnords", OneDriveService.String(), "smarf"}, + expectErr: assert.NoError, + expectIdx: 4, + expectSRS: []ServiceResource{{ExchangeService, "fnords"}, {OneDriveService, "smarf"}}, + }, + { + name: "multiple serviceResource and extra value", + elems: Elements{ExchangeService.String(), "fnords", OneDriveService.String(), "smarf", "flaboigans"}, + expectErr: assert.NoError, + expectIdx: 4, + expectSRS: []ServiceResource{{ExchangeService, "fnords"}, {OneDriveService, "smarf"}}, + }, + } + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + srs, idx, err := ElementsToServiceResources(test.elems) + test.expectErr(t, err, clues.ToCore(err)) + assert.Equal(t, test.expectIdx, idx) + assert.Equal(t, test.expectSRS, srs) + }) + } +}