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.
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 emptyarray
type - Some pagination APIs used
*/*
as media type, which Kiota rightfully understands as binary stream and implements them with aStream
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.