add /pkg/source skeleton (#198)
* add /pkg/source skeleton Source acts as an intermediary between the client and internal packages (GraphConnector, Kopia) to specify the scope of data in a Backup or Restore operation request.
This commit is contained in:
parent
3a3303a817
commit
0e261fb96a
129
src/pkg/source/exchange.go
Normal file
129
src/pkg/source/exchange.go
Normal file
@ -0,0 +1,129 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ExchangeSource provides an api for scoping
|
||||
// data in the Exchange service.
|
||||
type ExchangeSource struct {
|
||||
Source
|
||||
}
|
||||
|
||||
// ToExchange transforms the generic source into an ExchangeSource.
|
||||
// Errors if the service defined by the source is not ServiceExchange.
|
||||
func (s Source) ToExchange() (*ExchangeSource, error) {
|
||||
if s.service != ServiceExchange {
|
||||
return nil, badCastErr(ServiceExchange, s.service)
|
||||
}
|
||||
src := ExchangeSource{s}
|
||||
return &src, nil
|
||||
}
|
||||
|
||||
// NewExchange produces a new Source with the service set to ServiceExchange.
|
||||
func NewExchange(tenantID string) *ExchangeSource {
|
||||
src := ExchangeSource{
|
||||
newSource(tenantID, ServiceExchange),
|
||||
}
|
||||
return &src
|
||||
}
|
||||
|
||||
// Scopes retrieves the list of exchangeScopes in the source.
|
||||
func (s *ExchangeSource) Scopes() []exchangeScope {
|
||||
scopes := []exchangeScope{}
|
||||
for _, v := range s.scopes {
|
||||
scopes = append(scopes, exchangeScope(v))
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
|
||||
// the following are called by the client to specify the constraints
|
||||
// each call appends one or more scopes to the source.
|
||||
|
||||
// Users selects the specified users. All of their data is included.
|
||||
func (s *ExchangeSource) Users(us ...string) {
|
||||
// todo
|
||||
}
|
||||
|
||||
// Contacts selects the specified contacts owned by the user.
|
||||
func (s *ExchangeSource) Contacts(u string, vs ...string) {
|
||||
// todo
|
||||
}
|
||||
|
||||
// Events selects the specified events owned by the user.
|
||||
func (s *ExchangeSource) Events(u string, vs ...string) {
|
||||
// todo
|
||||
}
|
||||
|
||||
// MailFolders selects the specified mail folders owned by the user.
|
||||
func (s *ExchangeSource) MailFolders(u string, vs ...string) {
|
||||
// todo
|
||||
}
|
||||
|
||||
// MailMessages selects the specified mail messages within the given folder,
|
||||
// owned by the user.
|
||||
func (s *ExchangeSource) MailMessages(u, f string, vs ...string) {
|
||||
// todo
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
|
||||
// exchangeScope specifies the data available
|
||||
// when interfacing with the Exchange service.
|
||||
type exchangeScope map[string]string
|
||||
|
||||
type exchangeCategory int
|
||||
|
||||
// exchangeCategory describes the type of data in scope.
|
||||
const (
|
||||
ExchangeCategoryUnknown exchangeCategory = iota
|
||||
ExchangeContact
|
||||
ExchangeEvent
|
||||
ExchangeFolder
|
||||
ExchangeMail
|
||||
ExchangeUser
|
||||
)
|
||||
|
||||
// String complies with the stringer interface, so that exchangeCategories
|
||||
// can be added into the scope map.
|
||||
func (ec exchangeCategory) String() string {
|
||||
return strconv.Itoa(int(ec))
|
||||
}
|
||||
|
||||
var (
|
||||
exchangeScopeKeyContactID = ExchangeContact.String()
|
||||
exchangeScopeKeyEventID = ExchangeEvent.String()
|
||||
exchangeScopeKeyFolderID = ExchangeFolder.String()
|
||||
exchangeScopeKeyMessageID = ExchangeMail.String()
|
||||
exchangeScopeKeyUserID = ExchangeUser.String()
|
||||
)
|
||||
|
||||
// Category describes the type of the data in scope.
|
||||
func (s exchangeScope) Category() exchangeCategory {
|
||||
return exchangeCategory(getIota(s, scopeKeyCategory))
|
||||
}
|
||||
|
||||
// Granularity describes the breadth of data in scope.
|
||||
func (s exchangeScope) Granularity() scopeGranularity {
|
||||
return granularityOf(s)
|
||||
}
|
||||
|
||||
func (s exchangeScope) UserID() string {
|
||||
return s[exchangeScopeKeyUserID]
|
||||
}
|
||||
|
||||
func (s exchangeScope) ContactID() string {
|
||||
return s[exchangeScopeKeyContactID]
|
||||
}
|
||||
|
||||
func (s exchangeScope) EventID() string {
|
||||
return s[exchangeScopeKeyEventID]
|
||||
}
|
||||
|
||||
func (s exchangeScope) FolderID() string {
|
||||
return s[exchangeScopeKeyFolderID]
|
||||
}
|
||||
|
||||
func (s exchangeScope) MessageID() string {
|
||||
return s[exchangeScopeKeyMessageID]
|
||||
}
|
||||
26
src/pkg/source/exchange_test.go
Normal file
26
src/pkg/source/exchange_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package source_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/pkg/source"
|
||||
)
|
||||
|
||||
type ExchangeSourceSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestExchangeSourceSuite(t *testing.T) {
|
||||
suite.Run(t, new(ExchangeSourceSuite))
|
||||
}
|
||||
|
||||
func (suite *ExchangeSourceSuite) TestNewExchangeSource() {
|
||||
t := suite.T()
|
||||
es := source.NewExchange("tid")
|
||||
assert.Equal(t, es.TenantID, "tid")
|
||||
assert.Equal(t, es.Service(), source.ServiceExchange)
|
||||
assert.NotZero(t, es.Scopes())
|
||||
}
|
||||
24
src/pkg/source/service_string.go
Normal file
24
src/pkg/source/service_string.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Code generated by "stringer -type=service -linecomment"; DO NOT EDIT.
|
||||
|
||||
package source
|
||||
|
||||
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[ServiceUnknown-0]
|
||||
_ = x[ServiceExchange-1]
|
||||
}
|
||||
|
||||
const _service_name = "Unknown ServiceExchange"
|
||||
|
||||
var _service_index = [...]uint8{0, 15, 23}
|
||||
|
||||
func (i service) String() string {
|
||||
if i < 0 || i >= service(len(_service_index)-1) {
|
||||
return "service(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _service_name[_service_index[i]:_service_index[i+1]]
|
||||
}
|
||||
87
src/pkg/source/source.go
Normal file
87
src/pkg/source/source.go
Normal file
@ -0,0 +1,87 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type service int
|
||||
|
||||
//go:generate stringer -type=service -linecomment
|
||||
const (
|
||||
ServiceUnknown service = iota // Unknown Service
|
||||
ServiceExchange // Exchange
|
||||
)
|
||||
|
||||
var ErrorBadSourceCast = errors.New("wrong source service type")
|
||||
|
||||
const (
|
||||
scopeKeyGranularity = "granularity"
|
||||
scopeKeyCategory = "category"
|
||||
)
|
||||
|
||||
const (
|
||||
// All is the wildcard value used to express "all data of <type>"
|
||||
// Ex: Events(u1, All) => all events for user u1.
|
||||
All = "*"
|
||||
)
|
||||
|
||||
// The core source. Has no api for setting or retrieving data.
|
||||
// Is only used to pass along more specific source instances.
|
||||
type Source struct {
|
||||
TenantID string // The tenant making the request.
|
||||
service service // The service scope of the data. Exchange, Teams, Sharepoint, etc.
|
||||
scopes []map[string]string // A slice of scopes. Expected to get cast to fooScope within each service handler.
|
||||
}
|
||||
|
||||
// helper for specific source instance constructors.
|
||||
func newSource(tenantID string, s service) Source {
|
||||
return Source{
|
||||
TenantID: tenantID,
|
||||
service: s,
|
||||
scopes: []map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
// Service return the service enum for the source.
|
||||
func (s Source) Service() service {
|
||||
return s.service
|
||||
}
|
||||
|
||||
func badCastErr(cast, is service) error {
|
||||
return errors.Wrapf(ErrorBadSourceCast, "%s service is not %s", cast, is)
|
||||
}
|
||||
|
||||
type scopeGranularity int
|
||||
|
||||
// granularity expresses the breadth of the request
|
||||
const (
|
||||
GranularityUnknown scopeGranularity = iota
|
||||
SingleItem
|
||||
AllIn
|
||||
)
|
||||
|
||||
// String complies with the stringer interface, so that granularities
|
||||
// can be added into the scope map.
|
||||
func (g scopeGranularity) String() string {
|
||||
return strconv.Itoa(int(g))
|
||||
}
|
||||
|
||||
func granularityOf(source map[string]string) scopeGranularity {
|
||||
return scopeGranularity(getIota(source, scopeKeyGranularity))
|
||||
}
|
||||
|
||||
// retrieves the iota, stored as a string, and transforms it to
|
||||
// an int. Any errors will return a 0 by default.
|
||||
func getIota(m map[string]string, key string) int {
|
||||
v, ok := m[key]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
i, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return i
|
||||
}
|
||||
65
src/pkg/source/source_test.go
Normal file
65
src/pkg/source/source_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type SourceSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestSourceSuite(t *testing.T) {
|
||||
suite.Run(t, new(SourceSuite))
|
||||
}
|
||||
|
||||
func (suite *SourceSuite) TestNewSource() {
|
||||
t := suite.T()
|
||||
s := newSource("tid", ServiceUnknown)
|
||||
assert.NotNil(t, s)
|
||||
assert.Equal(t, s.TenantID, "tid")
|
||||
assert.Equal(t, s.service, ServiceUnknown)
|
||||
assert.NotNil(t, s.scopes)
|
||||
}
|
||||
|
||||
func (suite *SourceSuite) TestSource_Service() {
|
||||
table := []service{
|
||||
ServiceUnknown,
|
||||
ServiceExchange,
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(fmt.Sprintf("testing %d", test), func(t *testing.T) {
|
||||
s := newSource("tid", test)
|
||||
assert.Equal(t, s.Service(), test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SourceSuite) TestGetIota() {
|
||||
table := []struct {
|
||||
name string
|
||||
val string
|
||||
expect int
|
||||
}{
|
||||
{"zero", "0", 0},
|
||||
{"positive", "1", 1},
|
||||
{"negative", "-1", -1},
|
||||
{"empty", "", 0},
|
||||
{"NaN", "fnords", 0},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
m := map[string]string{"test": test.val}
|
||||
result := getIota(m, "test")
|
||||
assert.Equal(t, result, test.expect)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *SourceSuite) TestBadCastErr() {
|
||||
err := badCastErr(ServiceUnknown, ServiceExchange)
|
||||
assert.Error(suite.T(), err)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user