Compare commits
1 Commits
main
...
3993-multi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7aea6bab4 |
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||
)
|
||||
|
||||
func AnyValueToT[T any](k string, m map[string]any) (T, error) {
|
||||
@ -23,3 +24,21 @@ func AnyValueToT[T any](k string, m map[string]any) (T, error) {
|
||||
|
||||
return vt, nil
|
||||
}
|
||||
|
||||
func AnyToT[T any](a any) (T, error) {
|
||||
if a == nil {
|
||||
return *new(T), clues.New("missing value")
|
||||
}
|
||||
|
||||
pt, ok := a.(*T)
|
||||
if ok {
|
||||
return ptr.Val(pt), nil
|
||||
}
|
||||
|
||||
t, ok := a.(T)
|
||||
if ok {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
return *new(T), clues.New(fmt.Sprintf("unexpected type: %T", a))
|
||||
}
|
||||
|
||||
114
src/pkg/path/service_resource.go
Normal file
114
src/pkg/path/service_resource.go
Normal file
@ -0,0 +1,114 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"github.com/alcionai/clues"
|
||||
|
||||
"github.com/alcionai/corso/src/internal/common/str"
|
||||
"github.com/alcionai/corso/src/internal/common/tform"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tuple
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ServiceResource holds a service + resource tuple. The tuple implies
|
||||
// that the resource owns some data in the given service.
|
||||
type ServiceResource struct {
|
||||
Service ServiceType
|
||||
ProtectedResource string
|
||||
}
|
||||
|
||||
func MakeServiceResource(
|
||||
st ServiceType,
|
||||
protectedResource string,
|
||||
) ServiceResource {
|
||||
return ServiceResource{
|
||||
Service: st,
|
||||
ProtectedResource: protectedResource,
|
||||
}
|
||||
}
|
||||
|
||||
func (sr ServiceResource) validate() error {
|
||||
if len(sr.ProtectedResource) == 0 {
|
||||
return clues.Stack(errMissingSegment, clues.New("protected resource"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Collection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// NewServiceResources is a lenient constructor for building a
|
||||
// new []ServiceResource. It allows the caller to pass in any
|
||||
// number of arbitrary values, but will require the following:
|
||||
// 1. even values must be path.ServiceType typed
|
||||
// 2. odd values must be string typed
|
||||
// 3. a non-zero, even number of values must be provided
|
||||
func NewServiceResources(elems ...any) ([]ServiceResource, error) {
|
||||
if len(elems) == 0 {
|
||||
return nil, clues.New("missing service resources")
|
||||
}
|
||||
|
||||
if len(elems)%2 == 1 {
|
||||
return nil, clues.New("odd number of service resources")
|
||||
}
|
||||
|
||||
srs := make([]ServiceResource, 0, len(elems)/2)
|
||||
|
||||
for i, j := 0, 1; i < len(elems); i, j = i+2, j+2 {
|
||||
srv, err := tform.AnyToT[ServiceType](elems[i])
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "service")
|
||||
}
|
||||
|
||||
pr, err := str.AnyToString(elems[j])
|
||||
if err != nil {
|
||||
return nil, clues.Wrap(err, "protected resource")
|
||||
}
|
||||
|
||||
srs = append(srs, MakeServiceResource(srv, pr))
|
||||
}
|
||||
|
||||
return srs, nil
|
||||
}
|
||||
|
||||
func ServiceResourcesToElements(srs []ServiceResource) Elements {
|
||||
es := make(Elements, 0, len(srs)*2)
|
||||
|
||||
for _, tuple := range srs {
|
||||
es = append(es, tuple.Service.String())
|
||||
es = append(es, tuple.ProtectedResource)
|
||||
}
|
||||
|
||||
return es
|
||||
}
|
||||
|
||||
// checks for the following:
|
||||
// 1. each ServiceResource is valid
|
||||
// 2. if len(srs) > 1, srs[i], srs[i+1] pass subservice checks.
|
||||
func validateServiceResources(srs []ServiceResource) error {
|
||||
switch len(srs) {
|
||||
case 0:
|
||||
return clues.Stack(errMissingSegment, clues.New("service"))
|
||||
case 1:
|
||||
return srs[0].validate()
|
||||
}
|
||||
|
||||
for i, tuple := range srs {
|
||||
if err := tuple.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if i+1 >= len(srs) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := ValidateServiceAndSubService(tuple.Service, srs[i+1].Service); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
166
src/pkg/path/service_resource_test.go
Normal file
166
src/pkg/path/service_resource_test.go
Normal file
@ -0,0 +1,166 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/alcionai/corso/src/internal/tester"
|
||||
)
|
||||
|
||||
type ServiceResourceUnitSuite struct {
|
||||
tester.Suite
|
||||
}
|
||||
|
||||
func TestServiceResourceUnitSuite(t *testing.T) {
|
||||
suite.Run(t, &ServiceResourceUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||
}
|
||||
|
||||
func (suite *ServiceResourceUnitSuite) TestNewServiceResource() {
|
||||
table := []struct {
|
||||
name string
|
||||
input []any
|
||||
expectErr assert.ErrorAssertionFunc
|
||||
expectResult []ServiceResource
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: []any{},
|
||||
expectErr: assert.Error,
|
||||
expectResult: nil,
|
||||
},
|
||||
{
|
||||
name: "odd elems: 1",
|
||||
input: []any{ExchangeService},
|
||||
expectErr: assert.Error,
|
||||
expectResult: nil,
|
||||
},
|
||||
{
|
||||
name: "odd elems: 3",
|
||||
input: []any{ExchangeService, "mailbox", OneDriveService},
|
||||
expectErr: assert.Error,
|
||||
expectResult: nil,
|
||||
},
|
||||
{
|
||||
name: "non-service even index",
|
||||
input: []any{"foo", "bar"},
|
||||
expectErr: assert.Error,
|
||||
expectResult: nil,
|
||||
},
|
||||
{
|
||||
name: "non-string odd index",
|
||||
input: []any{ExchangeService, OneDriveService},
|
||||
expectErr: assert.Error,
|
||||
expectResult: nil,
|
||||
},
|
||||
{
|
||||
name: "valid single",
|
||||
input: []any{ExchangeService, "mailbox"},
|
||||
expectErr: assert.NoError,
|
||||
expectResult: []ServiceResource{{ExchangeService, "mailbox"}},
|
||||
},
|
||||
{
|
||||
name: "valid multiple",
|
||||
input: []any{ExchangeService, "mailbox", OneDriveService, "user"},
|
||||
expectErr: assert.NoError,
|
||||
expectResult: []ServiceResource{
|
||||
{ExchangeService, "mailbox"},
|
||||
{OneDriveService, "user"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
t := suite.T()
|
||||
|
||||
result, err := NewServiceResources(test.input...)
|
||||
test.expectErr(t, err, clues.ToCore(err))
|
||||
assert.Equal(t, test.expectResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ServiceResourceUnitSuite) TestValidateServiceResources() {
|
||||
table := []struct {
|
||||
name string
|
||||
srs []ServiceResource
|
||||
expect assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
srs: []ServiceResource{},
|
||||
expect: assert.Error,
|
||||
},
|
||||
{
|
||||
name: "invalid resource",
|
||||
srs: []ServiceResource{{ExchangeService, ""}},
|
||||
expect: assert.Error,
|
||||
},
|
||||
{
|
||||
name: "invalid subservice",
|
||||
srs: []ServiceResource{
|
||||
{ExchangeService, "mailbox"},
|
||||
{OneDriveService, "user"},
|
||||
},
|
||||
expect: assert.Error,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
srs: []ServiceResource{
|
||||
{GroupsService, "group"},
|
||||
{SharePointService, "site"},
|
||||
},
|
||||
expect: assert.NoError,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
t := suite.T()
|
||||
|
||||
err := validateServiceResources(test.srs)
|
||||
test.expect(t, err, clues.ToCore(err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ServiceResourceUnitSuite) TestServiceResourceElements() {
|
||||
table := []struct {
|
||||
name string
|
||||
srs []ServiceResource
|
||||
expect Elements
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
srs: []ServiceResource{},
|
||||
expect: Elements{},
|
||||
},
|
||||
{
|
||||
name: "single",
|
||||
srs: []ServiceResource{{ExchangeService, "user"}},
|
||||
expect: Elements{ExchangeService.String(), "user"},
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
srs: []ServiceResource{
|
||||
{ExchangeService, "mailbox"},
|
||||
{OneDriveService, "user"},
|
||||
},
|
||||
expect: Elements{
|
||||
ExchangeService.String(), "mailbox",
|
||||
OneDriveService.String(), "user",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.Run(test.name, func() {
|
||||
t := suite.T()
|
||||
|
||||
result := ServiceResourcesToElements(test.srs)
|
||||
|
||||
// not ElementsMatch, order matters
|
||||
assert.Equal(t, test.expect, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,8 @@ const (
|
||||
ExchangeMetadataService // exchangeMetadata
|
||||
OneDriveMetadataService // onedriveMetadata
|
||||
SharePointMetadataService // sharepointMetadata
|
||||
GroupsService // groups
|
||||
GroupsMetadataService // groupsMetadata
|
||||
)
|
||||
|
||||
func toServiceType(service string) ServiceType {
|
||||
@ -51,3 +53,36 @@ func toServiceType(service string) ServiceType {
|
||||
return UnknownService
|
||||
}
|
||||
}
|
||||
|
||||
// subServices is a mapping of all valid service/subService pairs.
|
||||
// a subService pair occurs when one service contains a reference
|
||||
// to a protected resource of another service type, and the resource
|
||||
// for that second service is the identifier which is used to discover
|
||||
// data. A subService relationship may imply that the subservice data
|
||||
// is wholly replicated/owned by the primary service, or it may not,
|
||||
// each case differs.
|
||||
//
|
||||
// Ex:
|
||||
// - groups/<gID>/sharepoint/<siteID> => each team in groups contains a
|
||||
// complete sharepoint site.
|
||||
// - groups/<gID>/member/<userID> => each user in a team can own one or
|
||||
// more Chats. But the group does not contain the complete user data.
|
||||
var subServices = map[ServiceType]map[ServiceType]struct{}{
|
||||
GroupsService: {
|
||||
SharePointService: {},
|
||||
},
|
||||
}
|
||||
|
||||
func ValidateServiceAndSubService(service, subService ServiceType) error {
|
||||
subs, ok := subServices[service]
|
||||
if !ok {
|
||||
return clues.New("unsupported service").With("service", service)
|
||||
}
|
||||
|
||||
if _, ok := subs[subService]; !ok {
|
||||
return clues.New("unknown service/subService combination").
|
||||
With("service", service, "subService", subService)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
54
src/pkg/path/service_type_test.go
Normal file
54
src/pkg/path/service_type_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/clues"
|
||||
"github.com/alcionai/corso/src/internal/tester"
|
||||
)
|
||||
|
||||
type ServiceTypeUnitSuite struct {
|
||||
tester.Suite
|
||||
}
|
||||
|
||||
func TestServiceTypeUnitSuite(t *testing.T) {
|
||||
suite.Run(t, &ServiceTypeUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||
}
|
||||
|
||||
var knownServices = []ServiceType{
|
||||
UnknownService,
|
||||
ExchangeService,
|
||||
OneDriveService,
|
||||
SharePointService,
|
||||
ExchangeMetadataService,
|
||||
OneDriveMetadataService,
|
||||
SharePointMetadataService,
|
||||
GroupsService,
|
||||
GroupsMetadataService,
|
||||
}
|
||||
|
||||
func (suite *ServiceTypeUnitSuite) TestValildateServiceAndSubService() {
|
||||
table := map[ServiceType]map[ServiceType]assert.ErrorAssertionFunc{}
|
||||
|
||||
for _, si := range knownServices {
|
||||
table[si] = map[ServiceType]assert.ErrorAssertionFunc{}
|
||||
for _, sj := range knownServices {
|
||||
table[si][sj] = assert.Error
|
||||
}
|
||||
}
|
||||
|
||||
// expected successful
|
||||
table[GroupsService][SharePointService] = assert.NoError
|
||||
|
||||
for srv, ti := range table {
|
||||
for sub, expect := range ti {
|
||||
suite.Run(srv.String()+"-"+sub.String(), func() {
|
||||
err := ValidateServiceAndSubService(srv, sub)
|
||||
expect(suite.T(), err, clues.ToCore(err))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user