distrubutes ServiceResource throughout path

Adds utilization of the ServiceResource struct to all
of the path package functionality.  In general, this
takes most instances where a service and resource
are requested, and replaces those values with a
slice of ServiceResource tuples.

To keep the review smaller, this change does not update
any packages outside of path.  Therefore the tests and
build are guaranteed to fail.  The next PR, which
updates all other packages with the changes, is
necessary for both PRs to move forward.
This commit is contained in:
ryanfkeepers 2023-08-10 14:57:04 -06:00
parent 5f7f1092ec
commit dfd486cd41
11 changed files with 416 additions and 297 deletions

View File

@ -203,28 +203,30 @@ func (pb Builder) withPrefix(elements ...string) *Builder {
// verifyPrefix ensures that the tenant and resourceOwner are valid // verifyPrefix ensures that the tenant and resourceOwner are valid
// values, and that the builder has some directory structure. // values, and that the builder has some directory structure.
func (pb Builder) verifyPrefix(tenant, resourceOwner string) error { // func (pb Builder) verifyPrefix(tenant, resourceOwner string) error {
if err := verifyInputValues(tenant, resourceOwner); err != nil { // if err := verifyPrefixValues(tenant, resourceOwner); err != nil {
return err // return err
} // }
if len(pb.elements) == 0 { // if len(pb.elements) == 0 {
return clues.New("missing path beyond prefix") // return clues.New("missing path beyond prefix")
} // }
return nil // return nil
} // }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Data Layer Path Transformers // Data Layer Path Transformers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func (pb Builder) ToStreamStorePath( func (pb Builder) ToStreamStorePath(
tenant, purpose string, tenant string,
service ServiceType, srs []ServiceResource,
isItem bool, isItem bool,
) (Path, error) { ) (Path, error) {
if err := verifyInputValues(tenant, purpose); err != nil { cat := DetailsCategory
if err := verifyPrefixValues(tenant, srs, cat); err != nil {
return nil, err return nil, err
} }
@ -232,40 +234,18 @@ func (pb Builder) ToStreamStorePath(
return nil, clues.New("missing path beyond prefix") return nil, clues.New("missing path beyond prefix")
} }
metadataService := UnknownService dlrp := newDataLayerResourcePath(pb, tenant, toMetadataServices(srs), cat, isItem)
switch service { return &dlrp, nil
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
} }
func (pb Builder) ToServiceCategoryMetadataPath( func (pb Builder) ToServiceCategoryMetadataPath(
tenant, user string, tenant string,
service ServiceType, srs []ServiceResource,
category CategoryType, cat CategoryType,
isItem bool, isItem bool,
) (Path, error) { ) (Path, error) {
if err := ValidateServiceAndCategory(service, category); err != nil { if err := verifyPrefixValues(tenant, srs, cat); err != nil {
return nil, err
}
if err := verifyInputValues(tenant, user); err != nil {
return nil, err return nil, err
} }
@ -273,79 +253,66 @@ func (pb Builder) ToServiceCategoryMetadataPath(
return nil, clues.New("missing path beyond prefix") return nil, clues.New("missing path beyond prefix")
} }
metadataService := UnknownService dlrp := newDataLayerResourcePath(pb, tenant, toMetadataServices(srs), cat, isItem)
switch service { return &dlrp, nil
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
} }
func (pb Builder) ToDataLayerPath( func (pb Builder) ToDataLayerPath(
tenant, user string, tenant string,
service ServiceType, srs []ServiceResource,
category CategoryType, cat CategoryType,
isItem bool, isItem bool,
) (Path, error) { ) (Path, error) {
if err := ValidateServiceAndCategory(service, category); err != nil { if err := verifyPrefixValues(tenant, srs, cat); err != nil {
return nil, err return nil, err
} }
if err := pb.verifyPrefix(tenant, user); err != nil { dlrp := newDataLayerResourcePath(pb, tenant, srs, cat, isItem)
return nil, err
}
return &dataLayerResourcePath{ return &dlrp, nil
Builder: *pb.withPrefix(
tenant,
service.String(),
user,
category.String()),
service: service,
category: category,
hasItem: isItem,
}, nil
} }
func (pb Builder) ToDataLayerExchangePathForCategory( func (pb Builder) ToDataLayerExchangePathForCategory(
tenant, user string, tenant, mailboxID string,
category CategoryType, category CategoryType,
isItem bool, isItem bool,
) (Path, error) { ) (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( func (pb Builder) ToDataLayerOneDrivePath(
tenant, user string, tenant, userID string,
isItem bool, isItem bool,
) (Path, error) { ) (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( func (pb Builder) ToDataLayerSharePointPath(
tenant, site string, tenant, siteID string,
category CategoryType, category CategoryType,
isItem bool, isItem bool,
) (Path, error) { ) (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 // Stringers and PII Concealer Compliance
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -46,7 +46,10 @@ func (suite *BuilderUnitSuite) TestAppend() {
func (suite *BuilderUnitSuite) TestAppendItem() { func (suite *BuilderUnitSuite) TestAppendItem() {
t := suite.T() 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)) require.NoError(t, err, clues.ToCore(err))
pb := p.ToBuilder() pb := p.ToBuilder()
@ -339,8 +342,13 @@ func (suite *BuilderUnitSuite) TestFolder() {
} }
func (suite *BuilderUnitSuite) TestPIIHandling() { func (suite *BuilderUnitSuite) TestPIIHandling() {
p, err := Build("t", "ro", ExchangeService, EventsCategory, true, "dir", "item") t := suite.T()
require.NoError(suite.T(), err)
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 { table := []struct {
name string name string

View File

@ -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 { func ValidateServiceAndCategory(service ServiceType, category CategoryType) error {
cats, ok := serviceCategories[service] cats, ok := serviceCategories[service]
if !ok { if !ok {

View File

@ -59,7 +59,10 @@ func (suite *OneDrivePathSuite) Test_ToOneDrivePath() {
suite.Run(tt.name, func() { suite.Run(tt.name, func() {
t := suite.T() 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)) require.NoError(suite.T(), err, clues.ToCore(err))
got, err := path.ToDrivePath(p) got, err := path.ToDrivePath(p)

View File

@ -79,10 +79,12 @@ var (
// string. // string.
type Path interface { type Path interface {
String() string 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 Category() CategoryType
Tenant() string Tenant() string
ResourceOwner() string
Folder(escaped bool) string Folder(escaped bool) string
Folders() Elements Folders() Elements
Item() string Item() string
@ -132,41 +134,29 @@ type RestorePaths struct {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func Build( func Build(
tenant, resourceOwner string, tenant string,
service ServiceType, srs []ServiceResource,
category CategoryType, category CategoryType,
hasItem bool, hasItem bool,
elements ...string, elements ...string,
) (Path, error) { ) (Path, error) {
b := Builder{}.Append(elements...) return Builder{}.
Append(elements...).
return b.ToDataLayerPath( ToDataLayerPath(tenant, srs, category, hasItem)
tenant, resourceOwner,
service, category,
hasItem)
} }
func BuildPrefix( func BuildPrefix(
tenant, resourceOwner string, tenant string,
s ServiceType, srs []ServiceResource,
c CategoryType, cat CategoryType,
) (Path, error) { ) (Path, error) {
pb := Builder{} if err := verifyPrefixValues(tenant, srs, cat); err != nil {
if err := ValidateServiceAndCategory(s, c); err != nil {
return nil, err return nil, err
} }
if err := verifyInputValues(tenant, resourceOwner); err != nil { dlrp := newDataLayerResourcePath(Builder{}, tenant, srs, cat, false)
return nil, err
}
return &dataLayerResourcePath{ return &dlrp, nil
Builder: *pb.withPrefix(tenant, s.String(), resourceOwner, c.String()),
service: s,
category: c,
hasItem: false,
}, nil
} }
// FromDataLayerPath parses the escaped path p, validates the elements in p // 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) 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 { if len(pb.elements) < 5 {
return nil, clues.New("path has too few segments").With("path_string", p) return nil, clues.New("path has too few segments").With("path_string", p)
} }
service, category, err := validateServiceAndCategoryStrings( srs, catIdx, err := ElementsToServiceResources(pb.elements[1:])
pb.elements[1],
pb.elements[3],
)
if err != nil { 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 nil, clues.Stack(errParsingPath, err).With("path_string", p)
} }
return &dataLayerResourcePath{ dlrp := dataLayerResourcePath{
Builder: *pb, Builder: *pb,
service: service, serviceResources: srs,
category: category, category: category,
hasItem: isItem, hasItem: isItem,
}, nil }
return &dlrp, nil
} }
// TrimTrailingSlash takes an escaped path element and returns an escaped path // TrimTrailingSlash takes an escaped path element and returns an escaped path
@ -290,16 +293,21 @@ func Split(segment string) []string {
// Unexported Helpers // Unexported Helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
func verifyInputValues(tenant, resourceOwner string) error { func verifyPrefixValues(
tenant string,
srs []ServiceResource,
cat CategoryType,
) error {
if len(tenant) == 0 { if len(tenant) == 0 {
return clues.Stack(errMissingSegment, clues.New("tenant")) return clues.Stack(errMissingSegment, clues.New("tenant"))
} }
if len(resourceOwner) == 0 { if err := validateServiceResources(srs); err != nil {
return clues.Stack(errMissingSegment, clues.New("resourceOwner")) 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 // escapeElement takes a single path element and escapes all characters that

View File

@ -334,14 +334,12 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() {
testUser, testUser,
testElement1, testElement1,
testElement2, testElement2,
testElement3, testElement3),
),
expectedFolder: fmt.Sprintf( expectedFolder: fmt.Sprintf(
"%s/%s/%s", "%s/%s/%s",
testElementTrimmed, testElementTrimmed,
testElement2, testElement2,
testElement3, testElement3),
),
expectedSplit: []string{ expectedSplit: []string{
testElementTrimmed, testElementTrimmed,
testElement2, testElement2,
@ -351,8 +349,7 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() {
expectedItemFolder: fmt.Sprintf( expectedItemFolder: fmt.Sprintf(
"%s/%s", "%s/%s",
testElementTrimmed, testElementTrimmed,
testElement2, testElement2),
),
expectedItemSplit: []string{ expectedItemSplit: []string{
testElementTrimmed, testElementTrimmed,
testElement2, testElement2,
@ -366,14 +363,12 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() {
testUser, testUser,
testElementTrimmed, testElementTrimmed,
testElement2, testElement2,
testElement3, testElement3),
),
expectedFolder: fmt.Sprintf( expectedFolder: fmt.Sprintf(
"%s/%s/%s", "%s/%s/%s",
testElementTrimmed, testElementTrimmed,
testElement2, testElement2,
testElement3, testElement3),
),
expectedSplit: []string{ expectedSplit: []string{
testElementTrimmed, testElementTrimmed,
testElement2, testElement2,
@ -383,8 +378,7 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() {
expectedItemFolder: fmt.Sprintf( expectedItemFolder: fmt.Sprintf(
"%s/%s", "%s/%s",
testElementTrimmed, testElementTrimmed,
testElement2, testElement2),
),
expectedItemSplit: []string{ expectedItemSplit: []string{
testElementTrimmed, testElementTrimmed,
testElement2, testElement2,
@ -393,21 +387,27 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() {
} }
for service, cats := range serviceCategories { for service, cats := range serviceCategories {
for cat := range cats { for cat := range cats {
for _, item := range isItem { for _, item := range isItem {
suite.Run(fmt.Sprintf("%s-%s-%s", service, cat, item.name), func() { suite.Run(fmt.Sprintf("%s-%s-%s", service, cat, item.name), func() {
for _, test := range table { for _, test := range table {
suite.Run(test.name, func() { suite.Run(test.name, func() {
t := suite.T() var (
testPath := fmt.Sprintf(test.unescapedPath, service, cat) t = suite.T()
testPath = fmt.Sprintf(test.unescapedPath, service, cat)
sr = ServiceResource{service, testUser}
)
p, err := FromDataLayerPath(testPath, item.isItem) p, err := FromDataLayerPath(testPath, item.isItem)
require.NoError(t, err, clues.ToCore(err)) 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, cat, p.Category(), "category")
assert.Equal(t, testTenant, p.Tenant(), "tenant") assert.Equal(t, testTenant, p.Tenant(), "tenant")
assert.Equal(t, testUser, p.ResourceOwner(), "resource owner")
fld := p.Folder(false) fld := p.Folder(false)
escfld := p.Folder(true) escfld := p.Folder(true)
@ -435,44 +435,74 @@ func (suite *PathUnitSuite) TestFromDataLayerPath() {
func (suite *PathUnitSuite) TestBuildPrefix() { func (suite *PathUnitSuite) TestBuildPrefix() {
table := []struct { table := []struct {
name string name string
service ServiceType
category CategoryType
tenant string tenant string
owner string srs []ServiceResource
category CategoryType
expect string expect string
expectErr require.ErrorAssertionFunc expectErr require.ErrorAssertionFunc
}{ }{
{ {
name: "ok", name: "ok",
service: ExchangeService,
category: ContactsCategory,
tenant: "t", tenant: "t",
owner: "ro", srs: []ServiceResource{{ExchangeService, "roo"}},
expect: join([]string{"t", ExchangeService.String(), "ro", ContactsCategory.String()}), 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, expectErr: require.NoError,
}, },
{ {
name: "bad category", name: "bad category",
service: ExchangeService, srs: []ServiceResource{{ExchangeService, "roo"}},
category: FilesCategory, category: FilesCategory,
tenant: "t", tenant: "t",
owner: "ro",
expectErr: require.Error, expectErr: require.Error,
}, },
{ {
name: "bad tenant", name: "bad tenant",
service: ExchangeService,
category: ContactsCategory,
tenant: "", tenant: "",
owner: "ro", srs: []ServiceResource{{ExchangeService, "roo"}},
category: ContactsCategory,
expectErr: require.Error, expectErr: require.Error,
}, },
{ {
name: "bad owner", name: "bad resource",
service: ExchangeService,
category: ContactsCategory,
tenant: "t", 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, expectErr: require.Error,
}, },
} }
@ -480,7 +510,7 @@ func (suite *PathUnitSuite) TestBuildPrefix() {
suite.Run(test.name, func() { suite.Run(test.name, func() {
t := suite.T() 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)) test.expectErr(t, err, clues.ToCore(err))
if r == nil { if r == nil {

View File

@ -18,9 +18,28 @@ import (
// element after the prefix. // element after the prefix.
type dataLayerResourcePath struct { type dataLayerResourcePath struct {
Builder Builder
category CategoryType category CategoryType
service ServiceType serviceResources []ServiceResource
hasItem bool 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. // Tenant returns the tenant ID embedded in the dataLayerResourcePath.
@ -28,9 +47,8 @@ func (rp dataLayerResourcePath) Tenant() string {
return rp.Builder.elements[0] return rp.Builder.elements[0]
} }
// Service returns the ServiceType embedded in the dataLayerResourcePath. func (rp dataLayerResourcePath) ServiceResources() []ServiceResource {
func (rp dataLayerResourcePath) Service() ServiceType { return rp.serviceResources
return rp.service
} }
// Category returns the CategoryType embedded in the dataLayerResourcePath. // 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 // 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. // value that is part of the standard prefix structure, an error is returned.
func (rp dataLayerResourcePath) Dir() (Path, error) { 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 nil, clues.New("unable to shorten path").With("path", rp)
} }
return &dataLayerResourcePath{ return &dataLayerResourcePath{
Builder: *rp.Builder.Dir(), Builder: *rp.Builder.Dir(),
service: rp.service, serviceResources: rp.serviceResources,
category: rp.category, category: rp.category,
hasItem: false, hasItem: false,
}, nil }, nil
} }
@ -116,10 +137,10 @@ func (rp dataLayerResourcePath) Append(
} }
return &dataLayerResourcePath{ return &dataLayerResourcePath{
Builder: *rp.Builder.Append(elems...), Builder: *rp.Builder.Append(elems...),
service: rp.service, serviceResources: rp.serviceResources,
category: rp.category, category: rp.category,
hasItem: isItem, hasItem: isItem,
}, nil }, nil
} }

View File

@ -256,10 +256,10 @@ func (suite *DataLayerResourcePath) TestDir() {
func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() { func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() {
tenant := "a-tenant" tenant := "a-tenant"
user := "a-user" resource := "a-resource"
table := []struct { table := []struct {
name string name string
service path.ServiceType srs []path.ServiceResource
category path.CategoryType category path.CategoryType
postfix []string postfix []string
expectedService path.ServiceType expectedService path.ServiceType
@ -267,14 +267,14 @@ func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() {
}{ }{
{ {
name: "NoPostfixPasses", name: "NoPostfixPasses",
service: path.ExchangeService, srs: []path.ServiceResource{{path.ExchangeService, resource}},
category: path.EmailCategory, category: path.EmailCategory,
expectedService: path.ExchangeMetadataService, expectedService: path.ExchangeMetadataService,
check: assert.NoError, check: assert.NoError,
}, },
{ {
name: "PostfixPasses", name: "PostfixPasses",
service: path.ExchangeService, srs: []path.ServiceResource{{path.ExchangeService, resource}},
category: path.EmailCategory, category: path.EmailCategory,
postfix: []string{"a", "b"}, postfix: []string{"a", "b"},
expectedService: path.ExchangeMetadataService, expectedService: path.ExchangeMetadataService,
@ -282,48 +282,48 @@ func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() {
}, },
{ {
name: "Fails", name: "Fails",
service: path.ExchangeService, srs: []path.ServiceResource{{path.ExchangeService, resource}},
category: path.FilesCategory, category: path.FilesCategory,
check: assert.Error, check: assert.Error,
}, },
{ {
name: "Passes", name: "Passes",
service: path.ExchangeService, srs: []path.ServiceResource{{path.ExchangeService, resource}},
category: path.ContactsCategory, category: path.ContactsCategory,
expectedService: path.ExchangeMetadataService, expectedService: path.ExchangeMetadataService,
check: assert.NoError, check: assert.NoError,
}, },
{ {
name: "Passes", name: "Passes",
service: path.ExchangeService, srs: []path.ServiceResource{{path.ExchangeService, resource}},
category: path.EventsCategory, category: path.EventsCategory,
expectedService: path.ExchangeMetadataService, expectedService: path.ExchangeMetadataService,
check: assert.NoError, check: assert.NoError,
}, },
{ {
name: "Passes", name: "Passes",
service: path.OneDriveService, srs: []path.ServiceResource{{path.OneDriveService, resource}},
category: path.FilesCategory, category: path.FilesCategory,
expectedService: path.OneDriveMetadataService, expectedService: path.OneDriveMetadataService,
check: assert.NoError, check: assert.NoError,
}, },
{ {
name: "Passes", name: "Passes",
service: path.SharePointService, srs: []path.ServiceResource{{path.SharePointService, resource}},
category: path.LibrariesCategory, category: path.LibrariesCategory,
expectedService: path.SharePointMetadataService, expectedService: path.SharePointMetadataService,
check: assert.NoError, check: assert.NoError,
}, },
{ {
name: "Passes", name: "Passes",
service: path.SharePointService, srs: []path.ServiceResource{{path.SharePointService, resource}},
category: path.ListsCategory, category: path.ListsCategory,
expectedService: path.SharePointMetadataService, expectedService: path.SharePointMetadataService,
check: assert.NoError, check: assert.NoError,
}, },
{ {
name: "Passes", name: "Passes",
service: path.SharePointService, srs: []path.ServiceResource{{path.SharePointService, resource}},
category: path.PagesCategory, category: path.PagesCategory,
expectedService: path.SharePointMetadataService, expectedService: path.SharePointMetadataService,
check: assert.NoError, check: assert.NoError,
@ -331,27 +331,26 @@ func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() {
} }
for _, test := range table { for _, test := range table {
suite.Run(strings.Join([]string{ name := strings.Join([]string{
test.name, test.name,
test.service.String(), test.srs[0].Service.String(),
test.category.String(), test.category.String(),
}, "_"), func() { }, "_")
suite.Run(name, func() {
t := suite.T() t := suite.T()
pb := path.Builder{}.Append(test.postfix...) pb := path.Builder{}.Append(test.postfix...)
p, err := pb.ToServiceCategoryMetadataPath( p, err := pb.ToServiceCategoryMetadataPath(
tenant, tenant,
user, test.srs,
test.service,
test.category, test.category,
false) false)
test.check(t, err, clues.ToCore(err)) test.check(t, err, clues.ToCore(err))
if err != nil { if err == nil {
return 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, 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, 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, strings.Join(m.expectedFolders, "/"), p.Folder(false))
assert.Equal(t, path.Elements(m.expectedFolders), p.Folders()) assert.Equal(t, path.Elements(m.expectedFolders), p.Folders())
assert.Equal(t, m.expectedItem, p.Item()) assert.Equal(t, m.expectedItem, p.Item())
@ -456,7 +455,10 @@ func (suite *PopulatedDataLayerResourcePath) TestService() {
suite.Run(m.name, func() { suite.Run(m.name, func() {
t := suite.T() 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() { suite.Run(m.name, func() {
t := suite.T() t := suite.T()
assert.Equal(t, testUser, suite.paths[m.isItem].ResourceOwner()) assert.Equal(
t,
testUser,
suite.paths[m.isItem].ServiceResources()[0].ProtectedResource)
}) })
} }
} }

View File

@ -20,118 +20,76 @@ func TestServiceCategoryUnitSuite(t *testing.T) {
suite.Run(t, s) suite.Run(t, s)
} }
func (suite *ServiceCategoryUnitSuite) TestValidateServiceAndCategoryBadStringErrors() { func (suite *ServiceCategoryUnitSuite) TestVerifyPrefixValues() {
table := []struct { table := []struct {
name string name string
service string service ServiceType
category string category CategoryType
}{ check assert.ErrorAssertionFunc
{
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
}{ }{
{ {
name: "UnknownService", name: "UnknownService",
service: UnknownService.String(), service: UnknownService,
category: EmailCategory.String(), category: EmailCategory,
check: assert.Error, check: assert.Error,
}, },
{ {
name: "UnknownCategory", name: "UnknownCategory",
service: ExchangeService.String(), service: ExchangeService,
category: UnknownCategory.String(), category: UnknownCategory,
check: assert.Error, check: assert.Error,
}, },
{ {
name: "BadServiceString", name: "BadServiceType",
service: "foo", service: ServiceType(-1),
category: EmailCategory.String(), category: EmailCategory,
check: assert.Error, check: assert.Error,
}, },
{ {
name: "BadCategoryString", name: "BadCategoryType",
service: ExchangeService.String(), service: ExchangeService,
category: "foo", category: CategoryType(-1),
check: assert.Error, check: assert.Error,
}, },
{ {
name: "ExchangeEmail", name: "ExchangeEmail",
service: ExchangeService.String(), service: ExchangeService,
category: EmailCategory.String(), category: EmailCategory,
expectedService: ExchangeService, check: assert.NoError,
expectedCategory: EmailCategory,
check: assert.NoError,
}, },
{ {
name: "ExchangeContacts", name: "ExchangeContacts",
service: ExchangeService.String(), service: ExchangeService,
category: ContactsCategory.String(), category: ContactsCategory,
expectedService: ExchangeService, check: assert.NoError,
expectedCategory: ContactsCategory,
check: assert.NoError,
}, },
{ {
name: "ExchangeEvents", name: "ExchangeEvents",
service: ExchangeService.String(), service: ExchangeService,
category: EventsCategory.String(), category: EventsCategory,
expectedService: ExchangeService, check: assert.NoError,
expectedCategory: EventsCategory,
check: assert.NoError,
}, },
{ {
name: "OneDriveFiles", name: "OneDriveFiles",
service: OneDriveService.String(), service: OneDriveService,
category: FilesCategory.String(), category: FilesCategory,
expectedService: OneDriveService, check: assert.NoError,
expectedCategory: FilesCategory,
check: assert.NoError,
}, },
{ {
name: "SharePointLibraries", name: "SharePointLibraries",
service: SharePointService.String(), service: SharePointService,
category: LibrariesCategory.String(), category: LibrariesCategory,
expectedService: SharePointService, check: assert.NoError,
expectedCategory: LibrariesCategory,
check: assert.NoError,
}, },
} }
for _, test := range table { for _, test := range table {
suite.Run(test.name, func() { suite.Run(test.name, func() {
t := suite.T() 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)) test.check(t, err, clues.ToCore(err))
if err != nil {
return
}
assert.Equal(t, test.expectedService, s)
assert.Equal(t, test.expectedCategory, c)
}) })
} }
} }

View File

@ -85,6 +85,40 @@ func ServiceResourcesToElements(srs []ServiceResource) Elements {
return es 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: // checks for the following:
// 1. each ServiceResource is valid // 1. each ServiceResource is valid
// 2. if len(srs) > 1, srs[i], srs[i+1] pass subservice checks. // 2. if len(srs) > 1, srs[i], srs[i+1] pass subservice checks.
@ -112,3 +146,29 @@ func validateServiceResources(srs []ServiceResource) error {
return nil 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
}

View File

@ -125,7 +125,7 @@ func (suite *ServiceResourceUnitSuite) TestValidateServiceResources() {
} }
} }
func (suite *ServiceResourceUnitSuite) TestServiceResourceElements() { func (suite *ServiceResourceUnitSuite) TestServiceResourceToElements() {
table := []struct { table := []struct {
name string name string
srs []ServiceResource 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)
})
}
}