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