Re-add logic for getting Exchange mail paths (#648)
* Re-add logic for getting Exchange mail paths Paths can either be for an item (email) or a folder. Returned struct provides safe access to underlying information in the path once it is created. * Refine tests, add struct comment Add test to make sure Item() and Folder() work properly for a resource that has no parent folder, just the prefix stuff. * Rework resource-specific paths based on comments * use a single type to represent resource specific paths * use service/category enums to denote the resource the path represents * update tests to check for service/category of the path * rename exchange_path*.go -> resource_path*.go
This commit is contained in:
parent
67bc038c55
commit
d1bf2b90a6
24
src/internal/path/categorytype_string.go
Normal file
24
src/internal/path/categorytype_string.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Code generated by "stringer -type=CategoryType -linecomment"; DO NOT EDIT.
|
||||
|
||||
package path
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[UnknownCategory-0]
|
||||
_ = x[EmailCategory-1]
|
||||
}
|
||||
|
||||
const _CategoryType_name = "UnknownCategoryemail"
|
||||
|
||||
var _CategoryType_index = [...]uint8{0, 15, 20}
|
||||
|
||||
func (i CategoryType) String() string {
|
||||
if i < 0 || i >= CategoryType(len(_CategoryType_index)-1) {
|
||||
return "CategoryType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _CategoryType_name[_CategoryType_index[i]:_CategoryType_index[i+1]]
|
||||
}
|
||||
@ -51,16 +51,17 @@ var charactersToEscape = map[rune]struct{}{
|
||||
escapeCharacter: {},
|
||||
}
|
||||
|
||||
// TODO(ashmrtn): Getting the category should either be through type-switches or
|
||||
// through a function, but if it's a function it should re-use existing enums
|
||||
// for resource types.
|
||||
var errMissingSegment = errors.New("missing required path element")
|
||||
|
||||
// For now, adding generic functions to pull information from segments.
|
||||
// Resources that don't have the requested information should return an empty
|
||||
// string.
|
||||
type Path interface {
|
||||
String() string
|
||||
Service() ServiceType
|
||||
Category() CategoryType
|
||||
Tenant() string
|
||||
User() string
|
||||
ResourceOwner() string
|
||||
Folder() string
|
||||
Item() string
|
||||
}
|
||||
@ -150,6 +151,72 @@ func (pb Builder) join(start, end int) string {
|
||||
return join(pb.elements[start:end])
|
||||
}
|
||||
|
||||
func (pb Builder) verifyPrefix(tenant, resourceOwner string) error {
|
||||
if len(tenant) == 0 {
|
||||
return errors.Wrap(errMissingSegment, "tenant")
|
||||
}
|
||||
|
||||
if len(resourceOwner) == 0 {
|
||||
return errors.Wrap(errMissingSegment, "user")
|
||||
}
|
||||
|
||||
if len(pb.elements) == 0 {
|
||||
return errors.New("missing path beyond prefix")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pb Builder) withPrefix(elements ...string) *Builder {
|
||||
res := Builder{}.Append(elements...)
|
||||
res.elements = append(res.elements, pb.elements...)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// ToDataLayerExchangeMailFolder returns a Path for an Exchange mail folder
|
||||
// resource with information useful to the data layer. This includes prefix
|
||||
// elements of the path such as the tenant ID, user ID, service, and service
|
||||
// category.
|
||||
func (pb Builder) ToDataLayerExchangeMailFolder(tenant, user string) (Path, error) {
|
||||
if err := pb.verifyPrefix(tenant, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dataLayerResourcePath{
|
||||
Builder: *pb.withPrefix(
|
||||
tenant,
|
||||
ExchangeService.String(),
|
||||
user,
|
||||
EmailCategory.String(),
|
||||
),
|
||||
service: ExchangeService,
|
||||
category: EmailCategory,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToDataLayerExchangeMailFolder returns a Path for an Exchange mail item
|
||||
// resource with information useful to the data layer. This includes prefix
|
||||
// elements of the path such as the tenant ID, user ID, service, and service
|
||||
// category.
|
||||
func (pb Builder) ToDataLayerExchangeMailItem(tenant, user string) (Path, error) {
|
||||
if err := pb.verifyPrefix(tenant, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dataLayerResourcePath{
|
||||
Builder: *pb.withPrefix(
|
||||
tenant,
|
||||
ExchangeService.String(),
|
||||
user,
|
||||
EmailCategory.String(),
|
||||
),
|
||||
service: ExchangeService,
|
||||
category: EmailCategory,
|
||||
hasItem: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// escapeElement takes a single path element and escapes all characters that
|
||||
// require an escape sequence. If there are no characters that need escaping,
|
||||
// the input is returned unchanged.
|
||||
|
||||
78
src/internal/path/resource_path.go
Normal file
78
src/internal/path/resource_path.go
Normal file
@ -0,0 +1,78 @@
|
||||
package path
|
||||
|
||||
type ServiceType int
|
||||
|
||||
//go:generate stringer -type=ServiceType -linecomment
|
||||
const (
|
||||
UnknownService ServiceType = iota
|
||||
ExchangeService // exchange
|
||||
)
|
||||
|
||||
type CategoryType int
|
||||
|
||||
//go:generate stringer -type=CategoryType -linecomment
|
||||
const (
|
||||
UnknownCategory CategoryType = iota
|
||||
EmailCategory // email
|
||||
)
|
||||
|
||||
// dataLayerResourcePath allows callers to extract information from a
|
||||
// resource-specific path. This struct is unexported so that callers are
|
||||
// forced to use the pre-defined constructors, making it impossible to create a
|
||||
// dataLayerResourcePath with invalid service/category combinations.
|
||||
//
|
||||
// All dataLayerResourcePaths start with the same prefix:
|
||||
// <tenant ID>/<service>/<resource owner ID>/<category>
|
||||
// which allows extracting high-level information from the path. The path
|
||||
// elements after this prefix represent zero or more folders and, if the path
|
||||
// refers to a file or item, an item ID. A valid dataLayerResourcePath must have
|
||||
// at least one folder or an item so that the resulting path has at least one
|
||||
// element after the prefix.
|
||||
type dataLayerResourcePath struct {
|
||||
Builder
|
||||
category CategoryType
|
||||
service ServiceType
|
||||
hasItem bool
|
||||
}
|
||||
|
||||
// Tenant returns the tenant ID embedded in the dataLayerResourcePath.
|
||||
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
|
||||
}
|
||||
|
||||
// Category returns the CategoryType embedded in the dataLayerResourcePath.
|
||||
func (rp dataLayerResourcePath) Category() CategoryType {
|
||||
return rp.category
|
||||
}
|
||||
|
||||
// ResourceOwner returns the user ID or group ID embedded in the
|
||||
// dataLayerResourcePath.
|
||||
func (rp dataLayerResourcePath) ResourceOwner() string {
|
||||
return rp.Builder.elements[2]
|
||||
}
|
||||
|
||||
// Folder returns the folder segment embedded in the dataLayerResourcePath.
|
||||
func (rp dataLayerResourcePath) Folder() string {
|
||||
endIdx := len(rp.Builder.elements)
|
||||
|
||||
if rp.hasItem {
|
||||
endIdx--
|
||||
}
|
||||
|
||||
return rp.Builder.join(4, endIdx)
|
||||
}
|
||||
|
||||
// Item returns the item embedded in the dataLayerResourcePath if the path
|
||||
// refers to an item.
|
||||
func (rp dataLayerResourcePath) Item() string {
|
||||
if rp.hasItem {
|
||||
return rp.Builder.elements[len(rp.Builder.elements)-1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
189
src/internal/path/resource_path_test.go
Normal file
189
src/internal/path/resource_path_test.go
Normal file
@ -0,0 +1,189 @@
|
||||
package path_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/internal/path"
|
||||
)
|
||||
|
||||
const (
|
||||
testTenant = "aTenant"
|
||||
testUser = "aUser"
|
||||
)
|
||||
|
||||
var (
|
||||
// Purposely doesn't have characters that need escaping so it can be easily
|
||||
// computed using strings.Join().
|
||||
rest = []string{"some", "folder", "path", "with", "possible", "item"}
|
||||
|
||||
missingInfo = []struct {
|
||||
name string
|
||||
tenant string
|
||||
user string
|
||||
rest []string
|
||||
}{
|
||||
{
|
||||
name: "NoTenant",
|
||||
tenant: "",
|
||||
user: testUser,
|
||||
rest: rest,
|
||||
},
|
||||
{
|
||||
name: "NoResourceOwner",
|
||||
tenant: testTenant,
|
||||
user: "",
|
||||
rest: rest,
|
||||
},
|
||||
{
|
||||
name: "NoFolderOrItem",
|
||||
tenant: testTenant,
|
||||
user: testUser,
|
||||
rest: nil,
|
||||
},
|
||||
}
|
||||
|
||||
modes = []struct {
|
||||
name string
|
||||
builderFunc func(b path.Builder, tenant, user string) (path.Path, error)
|
||||
expectedFolder string
|
||||
expectedItem string
|
||||
expectedService path.ServiceType
|
||||
expectedCategory path.CategoryType
|
||||
}{
|
||||
{
|
||||
name: "ExchangeMailFolder",
|
||||
builderFunc: path.Builder.ToDataLayerExchangeMailFolder,
|
||||
expectedFolder: strings.Join(rest, "/"),
|
||||
expectedItem: "",
|
||||
expectedService: path.ExchangeService,
|
||||
expectedCategory: path.EmailCategory,
|
||||
},
|
||||
{
|
||||
name: "ExchangeMailItem",
|
||||
builderFunc: path.Builder.ToDataLayerExchangeMailItem,
|
||||
expectedFolder: strings.Join(rest[0:len(rest)-1], "/"),
|
||||
expectedItem: rest[len(rest)-1],
|
||||
expectedService: path.ExchangeService,
|
||||
expectedCategory: path.EmailCategory,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type DataLayerResourcePath struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestDataLayerResourcePath(t *testing.T) {
|
||||
suite.Run(t, new(DataLayerResourcePath))
|
||||
}
|
||||
|
||||
func (suite *DataLayerResourcePath) TestMissingInfoErrors() {
|
||||
for _, m := range modes {
|
||||
suite.T().Run(m.name, func(tOuter *testing.T) {
|
||||
for _, test := range missingInfo {
|
||||
tOuter.Run(test.name, func(t *testing.T) {
|
||||
b := path.Builder{}.Append(test.rest...)
|
||||
|
||||
_, err := m.builderFunc(*b, test.tenant, test.user)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *DataLayerResourcePath) TestMailItemNoFolder() {
|
||||
t := suite.T()
|
||||
item := "item"
|
||||
b := path.Builder{}.Append(item)
|
||||
|
||||
p, err := b.ToDataLayerExchangeMailItem(testTenant, testUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, p.Folder())
|
||||
assert.Equal(t, item, p.Item())
|
||||
}
|
||||
|
||||
type PopulatedDataLayerResourcePath struct {
|
||||
suite.Suite
|
||||
b *path.Builder
|
||||
}
|
||||
|
||||
func TestPopulatedDataLayerResourcePath(t *testing.T) {
|
||||
suite.Run(t, new(PopulatedDataLayerResourcePath))
|
||||
}
|
||||
|
||||
func (suite *PopulatedDataLayerResourcePath) SetupSuite() {
|
||||
suite.b = path.Builder{}.Append(rest...)
|
||||
}
|
||||
|
||||
func (suite *PopulatedDataLayerResourcePath) TestTenant() {
|
||||
for _, m := range modes {
|
||||
suite.T().Run(m.name, func(t *testing.T) {
|
||||
p, err := m.builderFunc(*suite.b, testTenant, testUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, testTenant, p.Tenant())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *PopulatedDataLayerResourcePath) TestService() {
|
||||
for _, m := range modes {
|
||||
suite.T().Run(m.name, func(t *testing.T) {
|
||||
p, err := m.builderFunc(*suite.b, testTenant, testUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, m.expectedService, p.Service())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *PopulatedDataLayerResourcePath) TestCategory() {
|
||||
for _, m := range modes {
|
||||
suite.T().Run(m.name, func(t *testing.T) {
|
||||
p, err := m.builderFunc(*suite.b, testTenant, testUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, m.expectedCategory, p.Category())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *PopulatedDataLayerResourcePath) TestResourceOwner() {
|
||||
for _, m := range modes {
|
||||
suite.T().Run(m.name, func(t *testing.T) {
|
||||
p, err := m.builderFunc(*suite.b, testTenant, testUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, testUser, p.ResourceOwner())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *PopulatedDataLayerResourcePath) TestFolder() {
|
||||
for _, m := range modes {
|
||||
suite.T().Run(m.name, func(t *testing.T) {
|
||||
p, err := m.builderFunc(*suite.b, testTenant, testUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, m.expectedFolder, p.Folder())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *PopulatedDataLayerResourcePath) TestItem() {
|
||||
for _, m := range modes {
|
||||
suite.T().Run(m.name, func(t *testing.T) {
|
||||
p, err := m.builderFunc(*suite.b, testTenant, testUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, m.expectedItem, p.Item())
|
||||
})
|
||||
}
|
||||
}
|
||||
24
src/internal/path/servicetype_string.go
Normal file
24
src/internal/path/servicetype_string.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Code generated by "stringer -type=ServiceType -linecomment"; DO NOT EDIT.
|
||||
|
||||
package path
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[UnknownService-0]
|
||||
_ = x[ExchangeService-1]
|
||||
}
|
||||
|
||||
const _ServiceType_name = "UnknownServiceexchange"
|
||||
|
||||
var _ServiceType_index = [...]uint8{0, 14, 22}
|
||||
|
||||
func (i ServiceType) String() string {
|
||||
if i < 0 || i >= ServiceType(len(_ServiceType_index)-1) {
|
||||
return "ServiceType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _ServiceType_name[_ServiceType_index[i]:_ServiceType_index[i+1]]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user