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:
Keepers 2022-06-21 18:26:39 -06:00 committed by GitHub
parent 3a3303a817
commit 0e261fb96a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 331 additions and 0 deletions

129
src/pkg/source/exchange.go Normal file
View 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]
}

View 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())
}

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

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