GC Serialization fix: event message support (#304)

Support for eventMessage Request and Response capabilities added for serialization.
This commit is contained in:
Danny 2022-07-11 13:04:20 -04:00 committed by GitHub
parent 608d19e821
commit b9652c66e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 314 additions and 24 deletions

View File

@ -15,7 +15,6 @@ import (
msuser "github.com/microsoftgraph/msgraph-sdk-go/users"
"github.com/pkg/errors"
"github.com/alcionai/corso/internal/connector/exchange"
"github.com/alcionai/corso/internal/connector/support"
"github.com/alcionai/corso/pkg/account"
"github.com/alcionai/corso/pkg/logger"
@ -308,7 +307,6 @@ func (gc *GraphConnector) serializeMessages(ctx context.Context, user string) ([
return collections, errs
}
// messageToDataCollection transfers message objects to objects within DataCollection
func (gc *GraphConnector) messageToDataCollection(
ctx context.Context,
objectWriter *kw.JsonSerializationWriter,
@ -316,39 +314,49 @@ func (gc *GraphConnector) messageToDataCollection(
message models.Messageable,
user string,
) error {
if *message.GetHasAttachments() {
var err error
aMessage := message
adtl := message.GetAdditionalData()
if len(adtl) > 2 {
aMessage, err = support.ConvertFromMessageable(adtl, message)
if err != nil {
return err
}
}
if *aMessage.GetHasAttachments() {
// getting all the attachments might take a couple attempts due to filesize
var retriesErr error
for count := 0; count < numberOfRetries; count++ {
attached, err := gc.client.
UsersById(user).
MessagesById(*message.GetId()).
MessagesById(*aMessage.GetId()).
Attachments().
Get()
retriesErr = err
if err == nil && attached != nil {
message.SetAttachments(attached.GetValue())
aMessage.SetAttachments(attached.GetValue())
break
}
}
if retriesErr != nil {
logger.Ctx(ctx).Debug("exceeded maximum retries")
return support.WrapAndAppend(*message.GetId(), errors.Wrap(retriesErr, "attachment failed"), nil)
return support.WrapAndAppend(*aMessage.GetId(), errors.Wrap(retriesErr, "attachment failed"), nil)
}
}
defer objectWriter.Close()
err := objectWriter.WriteObjectValue("", message)
err = objectWriter.WriteObjectValue("", aMessage)
if err != nil {
return support.SetNonRecoverableError(errors.Wrapf(err, "%s", *message.GetId()))
return support.SetNonRecoverableError(errors.Wrapf(err, "%s", *aMessage.GetId()))
}
byteArray, err := objectWriter.GetSerializedContent()
objectWriter.Close()
if err != nil {
return support.WrapAndAppend(*message.GetId(), errors.Wrap(err, "serializing mail content"), nil)
return support.WrapAndAppend(*aMessage.GetId(), errors.Wrap(err, "serializing mail content"), nil)
}
if byteArray != nil {
edc.PopulateCollection(&ExchangeData{id: *message.GetId(), message: byteArray, info: exchange.MessageInfo(message)})
edc.PopulateCollection(&ExchangeData{id: *aMessage.GetId(), message: byteArray})
}
return nil
}

View File

@ -58,17 +58,12 @@ func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_setTenantUsers()
}
func (suite *GraphConnectorIntegrationSuite) TestGraphConnector_ExchangeDataCollection() {
t := suite.T()
sel := selectors.NewExchangeBackup()
sel.Include(sel.Users("lidiah@8qzvrj.onmicrosoft.com"))
collectionList, err := suite.connector.ExchangeDataCollection(context.Background(), sel.Selector)
require.NotNil(t, collectionList, "collection list")
assert.Error(t, err) // TODO Remove after https://github.com/alcionai/corso/issues/140
assert.NotNil(t, suite.connector.status, "connector status")
assert.NotContains(t, err.Error(), "attachment failed") // TODO Create Retry Exceeded Error
assert.NotNil(suite.T(), collectionList, "collection list")
assert.Nil(suite.T(), err)
assert.NotNil(suite.T(), suite.connector.status, "connector status")
exchangeData := collectionList[0]
suite.Greater(len(exchangeData.FullPath()), 2)
}

View File

@ -1,13 +1,19 @@
package support
import (
"fmt"
"strconv"
kw "github.com/microsoft/kiota-serialization-json-go"
"github.com/pkg/errors"
"github.com/microsoftgraph/msgraph-sdk-go/models"
)
// ToMessage transfers all data from old message to new
// message except for the messageId.
func ToMessage(orig models.Messageable) *models.Message {
message := models.NewMessage()
var eventResponsableFields = []string{"responseType"}
var eventRequestableFields = []string{"allowNewTimeProposals", "meetingRequestType", "responseRequested"}
func CloneMessageableFields(orig, message models.Messageable) models.Messageable {
message.SetSubject(orig.GetSubject())
message.SetBodyPreview(orig.GetBodyPreview())
message.SetBody(orig.GetBody())
@ -38,5 +44,286 @@ func ToMessage(orig models.Messageable) *models.Message {
message.SetUniqueBody(orig.GetUniqueBody())
message.SetWebLink(orig.GetWebLink())
return message
}
func ToMessage(orig models.Messageable) models.Messageable {
message := models.NewMessage()
temp := CloneMessageableFields(orig, message)
aMessage, ok := temp.(*models.Message)
if !ok {
return nil
}
return aMessage
}
func SetEventMessageRequest(orig models.Messageable, adtl map[string]any) (models.EventMessageRequestable, error) {
aMessage := models.NewEventMessageRequest()
temp := CloneMessageableFields(orig, aMessage)
message, ok := temp.(models.EventMessageRequestable)
if !ok {
return nil, errors.New(*orig.GetId() + " failed to convert to eventMessageRequestable")
}
newMessage, err := SetAdditionalDataToEventMessage(adtl, message)
if err != nil {
return nil, errors.Wrap(err, *orig.GetId()+" eventMessageRequest could not set additional data")
}
additional, err := buildMapFromAdditional(eventRequestableFields, adtl)
if err != nil {
return nil, errors.Wrap(err, *orig.GetId()+" eventMessageRequest failed on method buildMapFromAdditional")
}
message, ok = newMessage.(models.EventMessageRequestable)
if !ok {
return nil, errors.New(*orig.GetId() + " failed to convert to eventMessageRequestable")
}
eventMessage, err := setEventRequestableFields(message, additional)
if err != nil {
return nil, err
}
return eventMessage, nil
}
func SetEventMessageResponse(orig models.Messageable, adtl map[string]any) (models.EventMessageResponseable, error) {
aMessage := models.NewEventMessageResponse()
temp := CloneMessageableFields(orig, aMessage)
message, ok := temp.(models.EventMessageResponseable)
if !ok {
return nil, errors.New(*orig.GetId() + " failed to convert to eventMessageRequestable")
}
newMessage, err := SetAdditionalDataToEventMessage(adtl, message)
if err != nil {
return nil, errors.Wrap(err, *orig.GetId()+" eventMessageResponse could not set additional data")
}
message, ok = newMessage.(models.EventMessageResponseable)
if !ok {
return nil, errors.New("unable to create event message responseable from " + *orig.GetId())
}
additional, err := buildMapFromAdditional(eventResponsableFields, adtl)
if err != nil {
return nil, errors.Wrap(err, *orig.GetId()+" eventMessageResponse failed on method buildMapFromAdditional")
}
for key, val := range additional {
switch key {
case "responseType":
temp, err := models.ParseResponseType(*val)
if err != nil {
return nil, errors.Wrap(err, *orig.GetId()+"failure to parse response type")
}
rType, ok := temp.(*models.ResponseType)
if !ok {
return nil, fmt.Errorf("%s : responseType not returned from models.ParseResponseType: %v\t%T", *orig.GetId(), temp, temp)
}
message.SetResponseType(rType)
default:
return nil, errors.New(key + " not supported for setEventMessageResponse")
}
}
return message, nil
}
// ConvertFromMessageable temporary function. Converts incorrect cast of messageable object to known
// type until upstream can make the appropriate changes
func ConvertFromMessageable(adtl map[string]any, orig models.Messageable) (models.EventMessageable, error) {
var aType string
aPointer, ok := adtl["@odata.type"]
if !ok {
return nil, errors.New("unknown data type: no @odata.type field")
}
ptr, ok := aPointer.(*string)
if !ok {
return nil, errors.New("unknown map type encountered")
}
aType = *ptr
if aType == "#microsoft.graph.eventMessageRequest" {
eventRequest, err := SetEventMessageRequest(orig, adtl)
if err != nil {
return nil, err
}
eventRequest.SetId(orig.GetId())
return eventRequest, err
}
if aType == "#microsoft.graph.eventMessageResponse" {
eventMessage, err := SetEventMessageResponse(orig, adtl)
if err != nil {
return nil, err
}
eventMessage.SetId(orig.GetId())
return eventMessage, nil
}
return nil, errors.New("unknown data type: " + aType)
}
// buildMapFromAdditional returns a submap of map[string]*string from map[string]any
func buildMapFromAdditional(list []string, adtl map[string]any) (map[string]*string, error) {
returnMap := make(map[string]*string)
for _, entry := range list {
ptr, ok := adtl[entry]
if !ok {
continue
}
value, ok := ptr.(*string)
if !ok {
boolConvert, ok := ptr.(*bool)
if !ok {
return nil, errors.New("unsupported value type: key: " + entry + fmt.Sprintf(" with type: %T", ptr))
}
aBool := *boolConvert
boolString := strconv.FormatBool(aBool)
returnMap[entry] = &boolString
continue
}
returnMap[entry] = value
}
return returnMap, nil
}
func setEventRequestableFields(em models.EventMessageRequestable, adtl map[string]*string) (models.EventMessageRequestable, error) {
for key, value := range adtl {
switch key {
case "meetingRequestType":
temp, err := models.ParseMeetingRequestType(*value)
if err != nil {
return nil, errors.Wrap(err, *em.GetId()+": failed on models.ParseMeetingRequestType")
}
rType, ok := temp.(*models.MeetingRequestType)
if !ok {
return nil, errors.New(*em.GetId() + ": failed to set meeting request type")
}
em.SetMeetingRequestType(rType)
case "responseRequested":
boolValue, err := strconv.ParseBool(*value)
if err != nil {
return nil, errors.Wrap(err, *em.GetId()+": failed to set responseRequested")
}
em.SetResponseRequested(&boolValue)
case "allowNewTimeProposals":
boolValue, err := strconv.ParseBool(*value)
if err != nil {
return nil, errors.Wrap(err, *em.GetId()+": failed to set allowNewTimeProposals")
}
em.SetAllowNewTimeProposals(&boolValue)
}
}
return em, nil
}
// SetAdditionalDataToEventMessage sets shared fields for 2 types of EventMessage: Response and Request
func SetAdditionalDataToEventMessage(adtl map[string]any, newMessage models.EventMessageable) (models.EventMessageable, error) {
for key, entry := range adtl {
if key == "endDateTime" {
dateTime := models.NewDateTimeTimeZone()
mapped, ok := entry.(map[string]*kw.JsonParseNode)
if ok {
for key, val := range mapped {
node := *val
value, err := node.GetStringValue()
if err != nil {
return nil, err
}
switch key {
case "dateTime":
dateTime.SetDateTime(value)
case "timeZone":
dateTime.SetTimeZone(value)
default:
return nil, errors.New("key not supported DateTime")
}
newMessage.SetEndDateTime(dateTime)
}
continue
}
}
if key == "startDateTime" {
dateTime := models.NewDateTimeTimeZone()
mapped, ok := entry.(map[string]*kw.JsonParseNode)
if ok {
for key, val := range mapped {
node := *val
value, err := node.GetStringValue()
if err != nil {
return nil, err
}
switch key {
case "dateTime":
dateTime.SetDateTime(value)
case "timeZone":
dateTime.SetTimeZone(value)
default:
return nil, errors.New("key not supported DateTime")
}
newMessage.SetStartDateTime(dateTime)
}
continue
}
}
if key == "location" {
aLocation := models.NewLocation()
mapped, ok := entry.(map[string]*kw.JsonParseNode)
if ok {
for key, val := range mapped {
node := *val
value, err := node.GetStringValue()
if err != nil {
return nil, errors.New("map[string]*JsonParseNode conversion failure")
}
switch key {
case "displayName":
aLocation.SetDisplayName(value)
case "locationType":
temp, err := models.ParseLocationType(*value)
if err != nil {
return nil, errors.New("location type parse failure")
}
lType, ok := temp.(*models.LocationType)
if !ok {
return nil, errors.New("location type interface failure")
}
aLocation.SetLocationType(lType)
}
}
}
newMessage.SetLocation(aLocation)
}
value, ok := entry.(*string)
if ok {
switch key {
case "isAllDay":
boolValue, err := strconv.ParseBool(*value)
if err != nil {
return nil, err
}
newMessage.SetIsAllDay(&boolValue)
case "isDelegated":
boolValue, err := strconv.ParseBool(*value)
if err != nil {
return nil, err
}
newMessage.SetIsDelegated(&boolValue)
case "isOutOfDate":
boolValue, err := strconv.ParseBool(*value)
if err != nil {
return nil, err
}
newMessage.SetIsOutOfDate(&boolValue)
case "meetingMessageType":
temp, err := models.ParseMeetingMessageType(*value)
if err != nil {
return nil, err
}
mType, ok := temp.(*models.MeetingMessageType)
if !ok {
return nil, errors.New("failed to create meeting message type")
}
newMessage.SetMeetingMessageType(mType)
}
}
}
return newMessage, nil
}