Using OpenAPI Client Generators

While working on my Stream Deck plugin for Clockify, I noticed that the API client package I was using, wasn’t up-to-date with the latest version of Clockfiy’s OpenAPI specification. Having played around with Swagger in the past, I thought it should be simple enough to generate a new C# API client from the specs, given that this is one of the selling points for OpenAPI.

Image showing on the left logos of the different API generators and on the right the logo of the generated ClockifyClient package

In short: None of the options I tried, could generate a fully usable client, though some of may come from the specification not being specific enough or the server not implementing it as specified. In the end Microsoft’s latest endeavor Kiota produced a client that compiled from the start and with a few tweaks to the spec actually became usable.

The Challenge

The goal is to fetch the OpenAPI specification for the Clockify API, generate a C# API client and ship it as standalone library in a NuGet package.

Kiota

Kiota is a command line tool for generating an API client to call any OpenAPI-described API you’re interested in. The goal is to eliminate the need to take a dependency on a different API client library for every API that you need to call. Kiota API clients provide a strongly typed experience with all the features you expect from a high quality API SDK, but without having to learn a new library for every HTTP API.

Install

dotnet tool install --global Microsoft.OpenApi.Kiota

Generate

kiota generate --language CSharp \
    --openapi clockify-openapi.json \
    --class-name ClockifyApiClient \
    --namespace-name ClockifyClient \
    --output ClockifyClient \
    --structured-mime-types application/json

Problems

Kiota was the only generator that produced in my opinion usable, isolated client that compiled from the start, but due to unclear specs or the server not adhering to the specs, there were still at least two issues:

  • Some enum types were defined as string types, but the server would return an empty array type
  • Some pagination APIs used */* as media type, which Kiota rightfully understands as binary stream and implements them with a Stream return type

NSwag

NSwag is a Swagger/OpenAPI 2.0 and 3.0 toolchain for .NET, .NET Core, Web API, ASP.NET Core, TypeScript (jQuery, AngularJS, Angular 2+, Aurelia, KnockoutJS and more) and other platforms, written in C#. The OpenAPI/Swagger specification uses JSON and JSON Schema to describe a RESTful web API. The NSwag project provides tools to generate OpenAPI specifications from existing ASP.NET Web API controllers and client code from these OpenAPI specifications.

Install

dotnet tool install --global NSwag.ConsoleCore
# or
npm install -g nswag

Generate

nswag openapi2csclient /input:clockify-openapi.json \
    /classname:ClockifyApiClient \
    /namespace:ClockifyClient \
    /output:ClockifyClient/ClockifyApiClient.cs \
    /HandleReferences:false \
    /InlineNamedArrays:false \
    /GenerateDefaultValues:true \
    /ExcludedTypeNames:[] \
    /JsonLibrary:NewtonsoftJson

Problems

NSwag also seems to struggle with enum types, but it resorted to numbering similarly named return types and then not actually implementing those types. Meaning, some APIs would return SomeModel5, but there was no class implementing SomeModel5. After a brief web search on how to fix this and not directly finding answers, I decided to move on.

AutoRest

The AutoRest tool generates client libraries for accessing RESTful web services. Input to AutoRest is a spec that describes the REST API using the OpenAPI Specification format.

Install

npm install -g autorest

Generate

autorest -v3 --csharp \
    --input-file=clockify-openapi.json \
    --output-folder=ClockifyClient \
    --namespace=ClockifyClient \
    --tag=v1

Problems

AutoRest seems to choke on */* as media type, saying it’s currently no supported. There are endpoints that return a file and for which */* may make sense, one could be more specific on the media type.

One factor that made me move on pretty quickly was, that AutoRest pulls in Azure.* packages, which as a dependency is a bit off-putting, given that the API client won‘t have anything to do with Microsoft’s commercial cloud offering.

OpenAPI Generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (both 2.0 and 3.0 are supported).

Install

npm install @openapitools/openapi-generator-cli -g

Generate

openapi-generator-cli generate -i clockify-openapi.json \
    -g csharp \
    -o ./ClockifyClient \
    --global-property "apis,apiDocs=false,apiTests=false,models,modelDocs=false,modelTests=false" \
    --additional-properties "nullableReferenceTypes=true,packageName=ClockifyClient,targetFramework=netstandard2.0;net8.0"

Problems

The generated client might actually be functional, but I never got to the stage of trying it, because the OpenAPI Generator doesn’t just create a simple client with the necessary models, but it also creates a complete client structure and if you leave out the —global-properties above, you get documentation and tests for the client. While this is quite useful if all you want is some CLI client, it’s not exactly useful to ship a client as a library.

The Result

Given that Kiota was the only generator that produced an at least compilable output and also is the latest effort by Microsoft to build a modern OpenAPI tooling, I focused on modifying the OpenAPI specs to also make it work at runtime.

The specification used */* for one of the media type, which leads Kiota to return a Stream instance, instead of a specific DTO. Changing this to application/json fixed the issue in the generation. I’d consider this a bug in the specification, as */* would essentially indicate “any” kind of (binary) content, but it’s returning just a paginated list of objects.

The API server returns [] as an empty enum value for some of the endpoints, which isn’t supported in the way the schema defines it. This lead to a runtime error, where the client expects a String but instead gets an Array. Wrapping the enum type correctly, made the generated client work as expected. An interesting discovery was, that the OpenAPI specification is quite messy when it comes to enum types and it seems to only be supported starting with OpenAPI 3. Before everyone just implemented their own version with x-* fields.

Adding an Authentication provider for the Clockify API key and a factory to wrap everything neatly, I was then finally able to publish a NuGet package ClockifyClient. You can find the whole project on GitHub.

Leave a Comment

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.