XeroAPI / Xero-NetStandard

A wrapper of the Xero API in the .NetStandard 2.0 framework. Supports Accounting, Payroll AU/US, and Files
MIT License
127 stars 124 forks source link

Validation in .NET Standard #370

Open craigvn opened 3 years ago

craigvn commented 3 years ago

We are using OAuth2 v3.1 and OAuth2Client 1.3.3

I am in the process of upgrading to OAuth2.0. In older framework we did validation in way similar to this.

try
{
  xeroInvoice = api.Create(xeroInvoice);
}
catch (ValidationException ex)
{
  ex.ValidationErrors.ForEach(error =>
  {
    LogError(error.Message, ex);
  });
};

In OAuth2.0 the ValidationException no longer exists. So we have code similar to this.

try
{
  var xeroInvoices = new Xr.Invoices(); 
  xeroInvoices._Invoices = new List<Xr.Invoice>() {
    xeroInvoice
  };
  api.CreateInvoicesAsync(_accessToken, _xeroTenantId, xeroInvoices, true);
}
catch (Exception ex)
{
  // How do I capture validation errors
}

Is there any way to capture the validation issues easily in the new framework?

Tyf0x commented 3 years ago

Is this a dup' of https://github.com/XeroAPI/Xero-NetStandard/issues/346 ?

bryanallott commented 3 years ago

I've found that if I catch an ApiException, I get this object returned in the ErrorContent property (in json format) Then it's just a case of parsing that to get similar to the previous SDK (also currently migrating some code)

Xero.NetStandard.OAuth2.Client.ApiException: Xero API 400 error calling CreatePayment :{ "ErrorNumber": 10, "Type": "ValidationException", "Message": "A validation exception occurred", "Elements": [ { "Date": "\/Date(1626912000000)\/", "Amount": 5000.00, "Reference": "TestReference", "Status": "AUTHORISED", "HasAccount": true, "Account": { "AccountID": "d1ebb97b-d207-4ccb-9ab6-8a466a8b4d39", "Code": "200", "Name": "Sales", "Status": "ACTIVE", "Type": "REVENUE", "TaxType": "OUTPUT", "Description": "Income from any normal business activity", "Class": "REVENUE", "SystemAccount": "", "EnablePaymentsToAccount": false, "ShowInExpenseClaims": false, "BankAccountType": "NONE", "ReportingCode": "REV.TRA.GOO", "ReportingCodeName": "Sale of goods", "UpdatedDateUTC": "\/Date(1625138692100+0000)\/", "AddToWatchlist": true }, "Invoice": { "InvoiceID": "a3d09691-15f3-47f4-8ab9-241e7120e230", "Payments": [], "CreditNotes": [], "Prepayments": [], "Overpayments": [], "IsDiscounted": false, "HasErrors": false, "LineItems": [], "ValidationErrors": [] }, "HasValidationErrors": true, "ValidationErrors": [ { "Message": "Account type is invalid for making a payment to/from" } ] } ] }

nganbread commented 3 years ago

Is there an upgrade path document somewhere? Having to deal with OAuth2 and a client migration has been pretty difficult and Xero docs don't seem to explain anything except the bare minimum

m-guillemette commented 2 years ago

I'd like to second @nganbread's question on if there is anything documented for this. I'm trying to catch ApiException like @bryanallott stated but I don't seem to get that exception type returned back to me when trying to intentionally induce an error.

TOuhrouche commented 1 year ago

Here is our utility to extract error messages from Xero Exceptions:

 private IEnumerable<string> GetErrors(ApiException apiException) {
    if (apiException.ErrorContent == null) 
        return new List<string>(){"Xero failed with no specific error code"};
    try {
        string errorContent = Convert.ToString(apiException.ErrorContent);
        if (errorContent.Contains("504 Gateway Time-out") ||
            errorContent.Contains("Internal Server Error") ||
            errorContent.Contains("An error occurred in Xero") ||
            errorContent.Contains("An error occurred while processing your request") ||
            errorContent.Contains("An error has occurred during the authentication process") ||
            errorContent.Contains("NoDataProcessedException") ||
            errorContent.Contains("consumer_key_unknown")) {
            return new List<string>() { "An error occurred in Xero. Check the <a target='_blank' href='http://status.developer.xero.com '>API Status page</a> for current service status. Contact the API support team at api@xero.com for more assistance" };
        }
        if (errorContent.Contains("Forbidden") || errorContent.Contains("Unauthorized")) {
            return new List<string>() { "API access is forbidden. You might want to reinstall your Xero integration"};
        }
        var json = JsonConvert.DeserializeObject<ErrorResponse>(errorContent);
        var messages = json.Elements.SelectMany(e => e.ValidationErrors.Select(v => v.Message)).Distinct();
        return messages;
    } catch (Exception e) {
        return new List<string>(){$"Error parsing ApiException {apiException.ErrorContent}"};
    }
  }
pellet commented 11 months ago

@TOuhrouche thanks for posting this code for parsing out xero api exceptions, what is the "ErrorResponse" type which you deserialize the errorContent to here? var json = JsonConvert.DeserializeObject<ErrorResponse>(errorContent);

pellet commented 11 months ago

I ended up parsing out the validation error like this:


if (ex is not ApiException apiException) throw;

var deserializedObject = JObject.Parse(apiException.ErrorContent) as JObject;
var type = (string)deserializedObject["Type"];

if (type is not "ValidationException") throw;

var message = (string)deserializedObject.SelectToken("Elements[0].ValidationErrors[0].Message");
TOuhrouche commented 11 months ago

@pellet ErrorResponse is an object we use to deserialize the dynamic content.

 public class ErrorResponse { public Element[] Elements { get; set; } }
 public class Element { public ValidationError[] ValidationErrors { get; set; } }
 public class ValidationError { public string Message { get; set; } }