Jump to content

Paul Millard

Members
  • Posts

    5
  • Joined

  • Last visited

Reputation

0 Neutral

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. Hi there, I have been working on a solution that utilises the Sage Accounting API. I installed OpenAPI-Generator-CLI toolkit v6.6.0 to create the required API and Model classes for communicating with our test data, and I have come across some issues that may need to be addressed, or at least documented. I took the Swagger file which contains ALL of the API classes because the end goal of our solution, is to push Stock, Customer and Supplier contacts, and Sales Invoices into the accounts. So far, I have got Stock working, and pushing Contacts into the system. Not without difficulty though. First of all, literally every endpoint that returns a list of records, requires a new type to be defined in the Swagger file that supports the Page type. I have gone over this in another thread and a solution was presented to resolve this, but of course, the Swagger file should have been updated by Sage to reflect this need. Calling any GET endpoint that returns multiple records, causes an exception because of the strong requirement for a Page type to be deserialized. Secondly, many API endpoint functions return attributes within their respective model classes that have a 'NULL' value assigned. A good example, is the PostContacts endpoint. Passing a 'PostContactsContact' type as it's parameter, required the structure to be properly initialised. But the function would always fail, because the return type 'Contact' contained attributes such as 'credit_limit' and the submember '.TaxTreatment.IsImporter'. In order to fix this issue, I had to make manual changes to the model classes, and define the types for these attributes, to support the 'NULL' type. Typically, in CSharp, the property 'CreditLimit' has to be redefined as: public double? CreditLimit { get; set; } I do appreciate that OpenAPI v3.0, supported the "nullable" attribute for data members, but was deprecated in v3.1. But a replacement method was used to define whether a member supported a null, by using the 'oneOf' attribute: "type": { "oneOf": [ - "double" - "null" ] } Apart from this issue, the other problem is when actually generating the model classes using the OpenAPI-Generator-cli tool, but this is not an issue for this forum. Either way, it must be clear on the API documentation that return types which support NULL, must be shown to do so! Much time was wasted trying to figure not only why I was getting errors when calling the API, but to figure out which model structures, were causing the errors and to modify the classes respectively based on the response text from the API. I hope this information proves useful, and to help Sage improve their API documentation and Swagger file respectively. Regards, Paul.
  2. Hi Mark, Many, many thanks for this information! After examining the Swagger file that you sent to me via email, I was able to modify my Swagger file to accommodate the new Pagination type for the model records that I was testing with. Regenerating the Swagger CSharp project, I was able to see the new return types. The GET methods now work without error. As mentioned in my email, I think it's helpful that the community are aware of this, if they are generating their model/api classes from the Swagger file. I have attached the swagger file you sent via email. For the community, you need to look at the "BankAccountPage" and "LedgerAccountPage" types that are defined in the Swagger file. They inherit the "Page" type and define the array of subsequent "BankAccount" or "LedgerAccount" types. You will also need to examine the "Get" methods for these types, as they originally returned a list of "BankAccount" objects, but now return a single "BankAccountPage" object which is correctly deserialized from the JSON response message. Again, many thanks Mark, all the help is much appreciated. Paul. swagger.full.pagination-poc.json
  3. This is the function that causes the problem for me. I will try to modify this function myself, but presently, if the API development team, are able to provide an interim solution, it would be very much appreciated. /// <summary> /// Deserialize the JSON string into a proper object. /// </summary> /// <param name="response">The HTTP response.</param> /// <param name="type">Object type.</param> /// <returns>Object representation of the JSON string.</returns> public object Deserialize(IRestResponse response, Type type) { IList<Parameter> headers = response.Headers; if (type == typeof(byte[])) // return byte array { return response.RawBytes; } // TODO: ? if (type.IsAssignableFrom(typeof(Stream))) if (type == typeof(Stream)) { if (headers != null) { var filePath = String.IsNullOrEmpty(Configuration.TempFolderPath) ? Path.GetTempPath() : Configuration.TempFolderPath; var regex = new Regex(@"Content-Disposition=.*filename=['""]?([^'""\s]+)['""]?$"); foreach (var header in headers) { var match = regex.Match(header.ToString()); if (match.Success) { string fileName = filePath + SanitizeFilename(match.Groups[1].Value.Replace("\"", "").Replace("'", "")); File.WriteAllBytes(fileName, response.RawBytes); return new FileStream(fileName, FileMode.Open); } } } var stream = new MemoryStream(response.RawBytes); return stream; } if (type.Name.StartsWith("System.Nullable`1[[System.DateTime")) // return a datetime object { return DateTime.Parse(response.Content, null, System.Globalization.DateTimeStyles.RoundtripKind); } if (type == typeof(String) || type.Name.StartsWith("System.Nullable")) // return primitive type { return ConvertType(response.Content, type); } // at this point, it must be a model (json) try { return JsonConvert.DeserializeObject(response.Content, type, serializerSettings); } catch (Exception e) { throw new ApiException(500, e.Message); } }
  4. Continuing from my post, I have called the function, passing the parameter for 'attributes' with the value 'all', and clearly get all of the fields that are described in the API documentation. But the prefix part of the JSON data structure, still causes the function to throw an exception error. So the call is clearly returning data, but the function does not interpret the RESPONSE correctly.
  5. Good morning. I'm currently working on developing a software that utilises the Sage Accounting API v3.1. I downloaded the Swagger file, and generated a C# .NET Class library based on the 'swagger.full.json' file. I used the Swagger-Codegen utility: java -jar /projects/swagger-codegen/modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate -i swagger.full.json -l csharp -o .\CSharp This was a great start because I successfully wrote the authorisation token code and I'm able to communicate with the API, because it returns the exact same data that I get when calling the 'Products/Get all products' method in Postman: { "$total": 1, "$page": 1, "$next": null, "$back": null, "$itemsPerPage": 20, "$items": [ { "id": "33095baf0a6647de91c34117753c6a97", "displayed_as": "Standard A4 Notebook", "$path": "/products/33095baf0a6647de91c34117753c6a97" } ] } The API documentation describes the end-point for 'Products/GET - Returns all products' as returning a JSON response that follows this structure: [ { "id": "string", "displayed_as": "string", "$path": "string", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:15:22Z", "deleted_at": "2019-08-24T14:15:22Z", "deletable": true, "deactivatable": true, "used_on_recurring_invoice": true, "item_code": "string", "description": "string", "notes": "string", "sales_ledger_account": {}, "sales_tax_rate": {}, "purchase_ledger_account": {}, "usual_supplier": {}, "purchase_tax_rate": {}, "cost_price": 0, "sales_prices": [], "source_guid": "string", "purchase_description": "string", "active": true, "catalog_item_type": {} } ] So, the problem is this. The Swagger file has generated a class in my Class Library, called ProductsApi. The function I used to test the ability to call the API, is 'GetProducts': /// <summary> /// Returns all Products /// </summary> /// <remarks> /// ### Endpoint Availability * Accounting Plus: 🇨🇦, 🇩🇪, 🇪🇸, 🇫🇷, 🇬🇧, 🇮🇪, 🇺🇸 * Accounting Standard: 🇬🇧, 🇮🇪 * Accounting Start: 🇨🇦, 🇩🇪, 🇪🇸, 🇫🇷, 🇬🇧, 🇮🇪, 🇺🇸 ### Access Control Restrictions Requires the authenticated user to have any mentioned role in one of the listed areas: * Area: &#x60;Products &amp; Services&#x60;: Read Only, Restricted Access, Full Access * Area: &#x60;Sales&#x60;: Read Only, Restricted Access, Full Access * Area: &#x60;Purchases&#x60;: Read Only, Restricted Access, Full Access /// </remarks> /// <exception cref="IO.Swagger.Client.ApiException">Thrown when fails to make API call</exception> /// <param name="search">Use this to filter by the item code or description. (optional)</param> /// <param name="updatedOrCreatedSince">Use this to limit the response to Products changed since a given date (format: YYYY-MM-DDT(+|-)hh:mm) or date-time (format: YYYY-MM-DDThh:mm:ss(+|-)hh:mm). Inclusive of the passed timestamp. (optional)</param> /// <param name="deletedSince">Use this to limit the response to Products deleted since a given date (format: YYYY-MM-DDT(+|-)hh:mm) or date-time (format: YYYY-MM-DDThh:mm:ss(+|-)hh:mm). Not inclusive of the passed timestamp. (optional)</param> /// <param name="active">Use this to only return active or inactive items (optional)</param> /// <param name="itemsPerPage">Returns the given number of Products per request. (optional, default to 20)</param> /// <param name="page">Go to specific page of Products (optional, default to 1)</param> /// <param name="attributes">Specify the attributes that you want to expose for the Products (expose all attributes with &#39;all&#39;). These are in addition to the base attributes (name, path) (optional)</param> /// <returns>List&lt;Product&gt;</returns> List<Product> GetProducts (string search = null, DateTime? updatedOrCreatedSince = null, DateTime? deletedSince = null, bool? active = null, int? itemsPerPage = null, int? page = null, string attributes = null); As you can see, it returns a List<Product> object. When I call this function, I get an immediate exception error with the following details: Further investigation shows that the API is returning data in the exact format seen in Postman, which you can see further up. So, the issue is that the function I call, is not matching up with the expected data for the API documentation. Can anyone explain why this is the case, and is it possible to resolve?
×
×
  • Create New...