better parsing of graph odata errs (#4900)
parse the odataerr to a local struct that has a more complete view of the data. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🧹 Tech Debt/Cleanup #### Issue(s) * #4685 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
c65527e5c4
commit
8c0ac505a2
@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
|
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
|
||||||
@ -141,15 +142,15 @@ var (
|
|||||||
|
|
||||||
func IsErrApplicationThrottled(err error) bool {
|
func IsErrApplicationThrottled(err error) bool {
|
||||||
return errors.Is(err, ErrApplicationThrottled) ||
|
return errors.Is(err, ErrApplicationThrottled) ||
|
||||||
hasErrorCode(err, applicationThrottled)
|
parseODataErr(err).hasErrorCode(err, applicationThrottled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrAuthenticationError(err error) bool {
|
func IsErrAuthenticationError(err error) bool {
|
||||||
return hasErrorCode(err, AuthenticationError)
|
return parseODataErr(err).hasErrorCode(err, AuthenticationError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrInsufficientAuthorization(err error) bool {
|
func IsErrInsufficientAuthorization(err error) bool {
|
||||||
return hasErrorCode(err, AuthorizationRequestDenied)
|
return parseODataErr(err).hasErrorCode(err, AuthorizationRequestDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrDeletedInFlight(err error) bool {
|
func IsErrDeletedInFlight(err error) bool {
|
||||||
@ -157,7 +158,7 @@ func IsErrDeletedInFlight(err error) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasErrorCode(
|
if parseODataErr(err).hasErrorCode(
|
||||||
err,
|
err,
|
||||||
errorItemNotFound,
|
errorItemNotFound,
|
||||||
itemNotFound,
|
itemNotFound,
|
||||||
@ -169,56 +170,51 @@ func IsErrDeletedInFlight(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsErrItemNotFound(err error) bool {
|
func IsErrItemNotFound(err error) bool {
|
||||||
return hasErrorCode(err, itemNotFound, errorItemNotFound)
|
return parseODataErr(err).hasErrorCode(err, itemNotFound, errorItemNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrInvalidDelta(err error) bool {
|
func IsErrInvalidDelta(err error) bool {
|
||||||
return hasErrorCode(err, syncStateNotFound, resyncRequired, syncStateInvalid)
|
return parseODataErr(err).hasErrorCode(err, syncStateNotFound, resyncRequired, syncStateInvalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrDeltaNotSupported(err error) bool {
|
func IsErrDeltaNotSupported(err error) bool {
|
||||||
return hasErrorMessage(err, ParameterDeltaTokenNotSupported)
|
return parseODataErr(err).hasErrorMessage(err, ParameterDeltaTokenNotSupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrQuotaExceeded(err error) bool {
|
func IsErrQuotaExceeded(err error) bool {
|
||||||
return hasErrorCode(err, QuotaExceeded)
|
return parseODataErr(err).hasErrorCode(err, QuotaExceeded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrExchangeMailFolderNotFound(err error) bool {
|
func IsErrExchangeMailFolderNotFound(err error) bool {
|
||||||
// Not sure if we can actually see a resourceNotFound error here. I've only
|
// Not sure if we can actually see a resourceNotFound error here. I've only
|
||||||
// seen the latter two.
|
// seen the latter two.
|
||||||
return hasErrorCode(err, ResourceNotFound, errorItemNotFound, MailboxNotEnabledForRESTAPI)
|
return parseODataErr(err).hasErrorCode(err, ResourceNotFound, errorItemNotFound, MailboxNotEnabledForRESTAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrUserNotFound(err error) bool {
|
func IsErrUserNotFound(err error) bool {
|
||||||
if hasErrorCode(err, RequestResourceNotFound, invalidUser) {
|
ode := parseODataErr(err)
|
||||||
|
|
||||||
|
if ode.hasErrorCode(err, RequestResourceNotFound, invalidUser) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasErrorCode(err, ResourceNotFound) {
|
if ode.hasErrorCode(err, ResourceNotFound) {
|
||||||
var odErr odataerrors.ODataErrorable
|
return strings.Contains(strings.ToLower(ode.Main.Message), "user")
|
||||||
if !errors.As(err, &odErr) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
mainMsg, _, _ := errData(odErr)
|
|
||||||
|
|
||||||
return strings.Contains(strings.ToLower(mainMsg), "user")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrInvalidRecipients(err error) bool {
|
func IsErrInvalidRecipients(err error) bool {
|
||||||
return hasErrorCode(err, ErrorInvalidRecipients)
|
return parseODataErr(err).hasErrorCode(err, ErrorInvalidRecipients)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrCannotOpenFileAttachment(err error) bool {
|
func IsErrCannotOpenFileAttachment(err error) bool {
|
||||||
return hasErrorCode(err, cannotOpenFileAttachment)
|
return parseODataErr(err).hasErrorCode(err, cannotOpenFileAttachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrAccessDenied(err error) bool {
|
func IsErrAccessDenied(err error) bool {
|
||||||
return hasErrorCode(err, ErrorAccessDenied) ||
|
return parseODataErr(err).hasErrorCode(err, ErrorAccessDenied) ||
|
||||||
clues.HasLabel(err, LabelStatus(http.StatusForbidden))
|
clues.HasLabel(err, LabelStatus(http.StatusForbidden))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,17 +236,17 @@ func IsErrConnectionReset(err error) bool {
|
|||||||
|
|
||||||
func IsErrUnauthorizedOrBadToken(err error) bool {
|
func IsErrUnauthorizedOrBadToken(err error) bool {
|
||||||
return clues.HasLabel(err, LabelStatus(http.StatusUnauthorized)) ||
|
return clues.HasLabel(err, LabelStatus(http.StatusUnauthorized)) ||
|
||||||
hasErrorCode(err, invalidAuthenticationToken) ||
|
parseODataErr(err).hasErrorCode(err, invalidAuthenticationToken) ||
|
||||||
errors.Is(err, ErrTokenExpired)
|
errors.Is(err, ErrTokenExpired)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrBadJWTToken(err error) bool {
|
func IsErrBadJWTToken(err error) bool {
|
||||||
return hasErrorCode(err, invalidAuthenticationToken)
|
return parseODataErr(err).hasErrorCode(err, invalidAuthenticationToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrItemAlreadyExistsConflict(err error) bool {
|
func IsErrItemAlreadyExistsConflict(err error) bool {
|
||||||
return errors.Is(err, ErrItemAlreadyExistsConflict) ||
|
return errors.Is(err, ErrItemAlreadyExistsConflict) ||
|
||||||
hasErrorCode(err, nameAlreadyExists)
|
parseODataErr(err).hasErrorCode(err, nameAlreadyExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelStatus transforms the provided statusCode into
|
// LabelStatus transforms the provided statusCode into
|
||||||
@ -262,7 +258,7 @@ func LabelStatus(statusCode int) string {
|
|||||||
|
|
||||||
// IsMalware is true if the graphAPI returns a "malware detected" error code.
|
// IsMalware is true if the graphAPI returns a "malware detected" error code.
|
||||||
func IsMalware(err error) bool {
|
func IsMalware(err error) bool {
|
||||||
return hasErrorCode(err, malwareDetected)
|
return parseODataErr(err).hasErrorCode(err, malwareDetected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
|
func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
|
||||||
@ -274,22 +270,26 @@ func IsMalwareResp(ctx context.Context, resp *http.Response) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsErrFolderExists(err error) bool {
|
func IsErrFolderExists(err error) bool {
|
||||||
return hasErrorCode(err, folderExists)
|
return parseODataErr(err).hasErrorCode(err, folderExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrUsersCannotBeResolved(err error) bool {
|
func IsErrUsersCannotBeResolved(err error) bool {
|
||||||
return hasErrorCode(err, noResolvedUsers) || hasErrorMessage(err, usersCannotBeResolved)
|
ode := parseODataErr(err)
|
||||||
|
|
||||||
|
return ode.hasErrorCode(err, noResolvedUsers) || ode.hasErrorMessage(err, usersCannotBeResolved)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrSiteNotFound(err error) bool {
|
func IsErrSiteNotFound(err error) bool {
|
||||||
return hasErrorMessage(err, requestedSiteCouldNotBeFound)
|
return parseODataErr(err).hasErrorMessage(err, requestedSiteCouldNotBeFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrResourceLocked(err error) bool {
|
func IsErrResourceLocked(err error) bool {
|
||||||
|
ode := parseODataErr(err)
|
||||||
|
|
||||||
return errors.Is(err, ErrResourceLocked) ||
|
return errors.Is(err, ErrResourceLocked) ||
|
||||||
hasInnerErrorCode(err, ResourceLocked) ||
|
ode.hasInnerErrorCode(err, ResourceLocked) ||
|
||||||
hasErrorCode(err, NotAllowed) ||
|
ode.hasErrorCode(err, NotAllowed) ||
|
||||||
errMessageMatchesAllFilters(
|
ode.errMessageMatchesAllFilters(
|
||||||
err,
|
err,
|
||||||
filters.In([]string{"the service principal for resource"}),
|
filters.In([]string{"the service principal for resource"}),
|
||||||
filters.In([]string{"this indicate that a subscription within the tenant has lapsed"}),
|
filters.In([]string{"this indicate that a subscription within the tenant has lapsed"}),
|
||||||
@ -297,105 +297,7 @@ func IsErrResourceLocked(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsErrSharingDisabled(err error) bool {
|
func IsErrSharingDisabled(err error) bool {
|
||||||
return hasInnerErrorCode(err, sharingDisabled)
|
return parseODataErr(err).hasInnerErrorCode(err, sharingDisabled)
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// error parsers
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
func hasErrorCode(err error, codes ...errorCode) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var oDataError odataerrors.ODataErrorable
|
|
||||||
if !errors.As(err, &oDataError) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
code, ok := ptr.ValOK(oDataError.GetErrorEscaped().GetCode())
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
cs := make([]string, len(codes))
|
|
||||||
for i, c := range codes {
|
|
||||||
cs[i] = string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.Equal(cs).Compare(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasInnerErrorCode(err error, codes ...errorCode) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var oDataError odataerrors.ODataErrorable
|
|
||||||
if !errors.As(err, &oDataError) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
inner := oDataError.GetErrorEscaped().GetInnerError()
|
|
||||||
if inner == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
code, err := str.AnyValueToString("code", inner.GetAdditionalData())
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
cs := make([]string, len(codes))
|
|
||||||
for i, c := range codes {
|
|
||||||
cs[i] = string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.Equal(cs).Compare(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// only use this as a last resort. Prefer the code or statuscode if possible.
|
|
||||||
func hasErrorMessage(err error, msgs ...errorMessage) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var oDataError odataerrors.ODataErrorable
|
|
||||||
if !errors.As(err, &oDataError) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, ok := ptr.ValOK(oDataError.GetErrorEscaped().GetMessage())
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
cs := make([]string, len(msgs))
|
|
||||||
for i, c := range msgs {
|
|
||||||
cs[i] = string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.In(cs).Compare(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// only use this as a last resort. Prefer the code or statuscode if possible.
|
|
||||||
func errMessageMatchesAllFilters(err error, fs ...filters.Filter) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var oDataError odataerrors.ODataErrorable
|
|
||||||
if !errors.As(err, &oDataError) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, ok := ptr.ValOK(oDataError.GetErrorEscaped().GetMessage())
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters.Must(msg, fs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap is a helper function that extracts ODataError metadata from
|
// Wrap is a helper function that extracts ODataError metadata from
|
||||||
@ -405,20 +307,20 @@ func Wrap(ctx context.Context, e error, msg string) *clues.Err {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var oDataError odataerrors.ODataErrorable
|
ode := parseODataErr(e)
|
||||||
if !errors.As(e, &oDataError) {
|
if !ode.isODataErr {
|
||||||
return clues.WrapWC(ctx, e, msg).WithTrace(1)
|
return clues.StackWC(ctx, e).WithTrace(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
mainMsg, data, innerMsg := errData(oDataError)
|
if len(ode.Main.Message) > 0 {
|
||||||
|
e = clues.Stack(e, clues.New(ode.Main.Message))
|
||||||
if len(mainMsg) > 0 {
|
|
||||||
e = clues.Stack(e, clues.New(mainMsg))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ce := clues.WrapWC(ctx, e, msg).With(data...).WithTrace(1)
|
ce := clues.WrapWC(ctx, e, msg).
|
||||||
|
With("graph_api_err", ode).
|
||||||
|
WithTrace(1)
|
||||||
|
|
||||||
return setLabels(ce, innerMsg)
|
return setLabels(ce, ode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stack is a helper function that extracts ODataError metadata from
|
// Stack is a helper function that extracts ODataError metadata from
|
||||||
@ -428,20 +330,20 @@ func Stack(ctx context.Context, e error) *clues.Err {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var oDataError *odataerrors.ODataError
|
ode := parseODataErr(e)
|
||||||
if !errors.As(e, &oDataError) {
|
if !ode.isODataErr {
|
||||||
return clues.StackWC(ctx, e).WithTrace(1)
|
return clues.StackWC(ctx, e).WithTrace(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
mainMsg, data, innerMsg := errData(oDataError)
|
if len(ode.Main.Message) > 0 {
|
||||||
|
e = clues.Stack(e, clues.New(ode.Main.Message))
|
||||||
if len(mainMsg) > 0 {
|
|
||||||
e = clues.Stack(e, clues.New(mainMsg))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ce := clues.StackWC(ctx, e).With(data...).WithTrace(1)
|
ce := clues.StackWC(ctx, e).
|
||||||
|
With("graph_api_err", ode).
|
||||||
|
WithTrace(1)
|
||||||
|
|
||||||
return setLabels(ce, innerMsg)
|
return setLabels(ce, ode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stackReq is a helper function that extracts ODataError metadata from
|
// stackReq is a helper function that extracts ODataError metadata from
|
||||||
@ -467,12 +369,12 @@ func stackReq(
|
|||||||
// Checks for the following conditions and labels the error accordingly:
|
// Checks for the following conditions and labels the error accordingly:
|
||||||
// * mysiteNotFound | mysiteURLNotFound
|
// * mysiteNotFound | mysiteURLNotFound
|
||||||
// * malware
|
// * malware
|
||||||
func setLabels(err *clues.Err, msg string) *clues.Err {
|
func setLabels(err *clues.Err, ode oDataErr) *clues.Err {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
f := filters.Contains([]string{msg})
|
f := filters.Contains([]string{ode.Main.Message + ode.Main.Code})
|
||||||
|
|
||||||
if f.Compare(string(MysiteNotFound)) ||
|
if f.Compare(string(MysiteNotFound)) ||
|
||||||
f.Compare(string(MysiteURLNotFound)) {
|
f.Compare(string(MysiteURLNotFound)) {
|
||||||
@ -490,35 +392,6 @@ func setLabels(err *clues.Err, msg string) *clues.Err {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func errData(err odataerrors.ODataErrorable) (string, []any, string) {
|
|
||||||
data := make([]any, 0)
|
|
||||||
|
|
||||||
// Get MainError
|
|
||||||
mainErr := err.GetErrorEscaped()
|
|
||||||
mainMsg := ptr.Val(mainErr.GetMessage())
|
|
||||||
|
|
||||||
data = appendIf(data, "odataerror_code", mainErr.GetCode())
|
|
||||||
data = appendIf(data, "odataerror_message", mainErr.GetMessage())
|
|
||||||
data = appendIf(data, "odataerror_target", mainErr.GetTarget())
|
|
||||||
msgConcat := ptr.Val(mainErr.GetMessage()) + ptr.Val(mainErr.GetCode())
|
|
||||||
|
|
||||||
for i, d := range mainErr.GetDetails() {
|
|
||||||
pfx := fmt.Sprintf("odataerror_details_%d_", i)
|
|
||||||
data = appendIf(data, pfx+"code", d.GetCode())
|
|
||||||
data = appendIf(data, pfx+"message", d.GetMessage())
|
|
||||||
data = appendIf(data, pfx+"target", d.GetTarget())
|
|
||||||
msgConcat += ptr.Val(d.GetMessage())
|
|
||||||
}
|
|
||||||
|
|
||||||
inner := mainErr.GetInnerError()
|
|
||||||
if inner != nil {
|
|
||||||
data = appendIf(data, "odataerror_inner_cli_req_id", inner.GetClientRequestId())
|
|
||||||
data = appendIf(data, "odataerror_inner_req_id", inner.GetRequestId())
|
|
||||||
}
|
|
||||||
|
|
||||||
return mainMsg, data, strings.ToLower(msgConcat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reqData(req *http.Request) map[string]any {
|
func reqData(req *http.Request) map[string]any {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -547,14 +420,6 @@ func respData(resp *http.Response) map[string]any {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendIf(a []any, k string, v *string) []any {
|
|
||||||
if v == nil {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(a, k, *v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ItemInfo gathers potentially useful information about a drive item,
|
// ItemInfo gathers potentially useful information about a drive item,
|
||||||
// and aggregates that data into a map.
|
// and aggregates that data into a map.
|
||||||
func ItemInfo(item *custom.DriveItem) map[string]any {
|
func ItemInfo(item *custom.DriveItem) map[string]any {
|
||||||
@ -593,6 +458,162 @@ func ItemInfo(item *custom.DriveItem) map[string]any {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// error parsers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type oDataErr struct {
|
||||||
|
isODataErr bool
|
||||||
|
Details []oDataDeets `json:"details,omitempty"`
|
||||||
|
Inner innerErr `json:"inner,omitempty"`
|
||||||
|
Main mainErr `json:"main,omitempty"`
|
||||||
|
Resp apiResp `json:"resp,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type oDataDeets struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Target string `json:"target,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type innerErr struct {
|
||||||
|
ClientRequestID string `json:"clientRequestID,omitempty"`
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Date time.Time `json:"date,omitempty"`
|
||||||
|
OdataType string `json:"odataType,omitempty"`
|
||||||
|
RequestID string `json:"requestID,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mainErr struct {
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Target string `json:"target,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiResp struct {
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
StatusCode int `json:"statusCode,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// parses the odataerror.ODataError interface to a local
|
||||||
|
// struct for easy logging/usage.
|
||||||
|
func parseODataErr(err error) oDataErr {
|
||||||
|
if err == nil {
|
||||||
|
return oDataErr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ode oDataErr
|
||||||
|
graphODE *odataerrors.ODataError
|
||||||
|
)
|
||||||
|
|
||||||
|
if !errors.As(err, &graphODE) {
|
||||||
|
return oDataErr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
ode = oDataErr{
|
||||||
|
isODataErr: true,
|
||||||
|
Resp: apiResp{
|
||||||
|
Message: strings.Clone(graphODE.ApiError.Message),
|
||||||
|
StatusCode: graphODE.ApiError.ResponseStatusCode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
main = graphODE.GetErrorEscaped()
|
||||||
|
details []odataerrors.ErrorDetailsable
|
||||||
|
inner odataerrors.InnerErrorable
|
||||||
|
)
|
||||||
|
|
||||||
|
if main != nil {
|
||||||
|
ode.Main = mainErr{
|
||||||
|
Code: strings.Clone(ptr.Val(main.GetCode())),
|
||||||
|
Message: strings.Clone(ptr.Val(main.GetMessage())),
|
||||||
|
Target: strings.Clone(ptr.Val(main.GetTarget())),
|
||||||
|
}
|
||||||
|
|
||||||
|
inner = main.GetInnerError()
|
||||||
|
details = main.GetDetails()
|
||||||
|
}
|
||||||
|
|
||||||
|
if inner != nil {
|
||||||
|
ode.Inner = innerErr{
|
||||||
|
ClientRequestID: strings.Clone(ptr.Val(inner.GetClientRequestId())),
|
||||||
|
Date: ptr.Val(inner.GetDate()),
|
||||||
|
OdataType: strings.Clone(ptr.Val(inner.GetOdataType())),
|
||||||
|
RequestID: strings.Clone(ptr.Val(inner.GetRequestId())),
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := str.AnyValueToString("code", inner.GetAdditionalData())
|
||||||
|
if err == nil {
|
||||||
|
ode.Inner.Code = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ode.Details = make([]oDataDeets, 0, len(details))
|
||||||
|
|
||||||
|
for _, deet := range details {
|
||||||
|
d := oDataDeets{
|
||||||
|
Code: strings.Clone(ptr.Val(deet.GetCode())),
|
||||||
|
Message: strings.Clone(ptr.Val(deet.GetMessage())),
|
||||||
|
Target: strings.Clone(ptr.Val(deet.GetTarget())),
|
||||||
|
}
|
||||||
|
|
||||||
|
ode.Details = append(ode.Details, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ode oDataErr) hasErrorCode(err error, codes ...errorCode) bool {
|
||||||
|
if !ode.isODataErr {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := make([]string, len(codes))
|
||||||
|
for i, c := range codes {
|
||||||
|
cs[i] = string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.Equal(cs).Compare(ode.Main.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ode oDataErr) hasInnerErrorCode(err error, codes ...errorCode) bool {
|
||||||
|
if !ode.isODataErr {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := make([]string, len(codes))
|
||||||
|
for i, c := range codes {
|
||||||
|
cs[i] = string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.Equal(cs).Compare(ode.Inner.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// only use this as a last resort. Prefer the code or statuscode if possible.
|
||||||
|
func (ode oDataErr) hasErrorMessage(err error, msgs ...errorMessage) bool {
|
||||||
|
if !ode.isODataErr {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := make([]string, len(msgs))
|
||||||
|
for i, c := range msgs {
|
||||||
|
cs[i] = string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.In(cs).Compare(ode.Main.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// only use this as a last resort. Prefer the code or statuscode if possible.
|
||||||
|
func (ode oDataErr) errMessageMatchesAllFilters(err error, fs ...filters.Filter) bool {
|
||||||
|
if !ode.isODataErr {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters.Must(ode.Main.Message, fs...)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// other helpers
|
// other helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
@ -876,16 +875,6 @@ func (suite *GraphErrorsUnitSuite) TestIsErrItemNotFound() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
|
func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
|
||||||
innerMatch := graphTD.ODataErr("not-match")
|
|
||||||
merr := odataerrors.NewMainError()
|
|
||||||
inerr := odataerrors.NewInnerError()
|
|
||||||
inerr.SetAdditionalData(map[string]any{
|
|
||||||
"code": string(ResourceLocked),
|
|
||||||
})
|
|
||||||
merr.SetInnerError(inerr)
|
|
||||||
merr.SetCode(ptr.To("not-match"))
|
|
||||||
innerMatch.SetErrorEscaped(merr)
|
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
err error
|
err error
|
||||||
@ -913,7 +902,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "matching oDataErr inner code",
|
name: "matching oDataErr inner code",
|
||||||
err: innerMatch,
|
err: graphTD.ODataInner(string(ResourceLocked)),
|
||||||
expect: assert.True,
|
expect: assert.True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -941,16 +930,6 @@ func (suite *GraphErrorsUnitSuite) TestIsErrResourceLocked() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *GraphErrorsUnitSuite) TestIsErrSharingDisabled() {
|
func (suite *GraphErrorsUnitSuite) TestIsErrSharingDisabled() {
|
||||||
innerMatch := graphTD.ODataErr("not-match")
|
|
||||||
merr := odataerrors.NewMainError()
|
|
||||||
inerr := odataerrors.NewInnerError()
|
|
||||||
inerr.SetAdditionalData(map[string]any{
|
|
||||||
"code": string(sharingDisabled),
|
|
||||||
})
|
|
||||||
merr.SetInnerError(inerr)
|
|
||||||
merr.SetCode(ptr.To("not-match"))
|
|
||||||
innerMatch.SetErrorEscaped(merr)
|
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
err error
|
err error
|
||||||
@ -973,7 +952,7 @@ func (suite *GraphErrorsUnitSuite) TestIsErrSharingDisabled() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "matching oDataErr inner code",
|
name: "matching oDataErr inner code",
|
||||||
err: innerMatch,
|
err: graphTD.ODataInner(string(sharingDisabled)),
|
||||||
expect: assert.True,
|
expect: assert.True,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/microsoft/kiota-abstractions-go/serialization"
|
"github.com/microsoft/kiota-abstractions-go/serialization"
|
||||||
kjson "github.com/microsoft/kiota-serialization-json-go"
|
kjson "github.com/microsoft/kiota-serialization-json-go"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
|
"github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors"
|
||||||
@ -33,6 +35,27 @@ func ODataErrWithMsg(code, message string) *odataerrors.ODataError {
|
|||||||
return odErr
|
return odErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ODataInner(innerCode string) *odataerrors.ODataError {
|
||||||
|
odErr := odataerrors.NewODataError()
|
||||||
|
inerr := odataerrors.NewInnerError()
|
||||||
|
merr := odataerrors.NewMainError()
|
||||||
|
|
||||||
|
inerr.SetAdditionalData(map[string]any{
|
||||||
|
"code": innerCode,
|
||||||
|
})
|
||||||
|
inerr.SetClientRequestId(ptr.To(uuid.NewString()))
|
||||||
|
inerr.SetOdataType(ptr.To("@odata.type"))
|
||||||
|
inerr.SetRequestId(ptr.To("req-id"))
|
||||||
|
|
||||||
|
merr.SetInnerError(inerr)
|
||||||
|
merr.SetCode(ptr.To("main code"))
|
||||||
|
merr.SetMessage(ptr.To("main message"))
|
||||||
|
|
||||||
|
odErr.SetErrorEscaped(merr)
|
||||||
|
|
||||||
|
return odErr
|
||||||
|
}
|
||||||
|
|
||||||
func ParseableToMap(t *testing.T, thing serialization.Parsable) map[string]any {
|
func ParseableToMap(t *testing.T, thing serialization.Parsable) map[string]any {
|
||||||
sw := kjson.NewJsonSerializationWriter()
|
sw := kjson.NewJsonSerializationWriter()
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user