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 7224edd6f6
commit c84b1b12ba
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
// 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,82 +253,69 @@ 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
}
// TODO: remove this. https://github.com/alcionai/corso/issues/4025
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)
}
// TODO: remove this. https://github.com/alcionai/corso/issues/4025
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)
}
// TODO: remove this. https://github.com/alcionai/corso/issues/4025
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
// ---------------------------------------------------------------------------

View File

@ -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

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 {
cats, ok := serviceCategories[service]
if !ok {

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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)
})
}
}

View File

@ -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
}

View File

@ -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)
})
}
}