Zaid-Ajaj / Hawaii

dotnet CLI tool to generate type-safe F# and Fable clients from OpenAPI/Swagger or OData services
MIT License
140 stars 15 forks source link
dotnet-cli fsharp openapi openapi-documents openapi-generator swagger

Hawaii Nuget

A dotnet CLI tool to generate type-safe F# and Fable clients from OpenAPI/Swagger/OData services.

Features

Install

dotnet tool install -g hawaii

Configuration

Create a configuration file called hawaii.json with the following shape:

{
    "schema": <schema>,
    "project": <project>,
    "output": <output>,
    ["target"]: <"fsharp" | "fable">
    ["synchronous"]: <true | false>,
    ["asyncReturnType"]: <"async" | "task">,
    ["resolveReferences"]: <true | false>,
    ["emptyDefinitions"]: <"ignore" | "free-form">,
    ["overrideSchema"]: <JSON schema subset>,
    ["filterTags"]: <list of tags>
}

Where

Example (PetStore Schema)

Here is an example configuration for the pet store API:

{
    "schema": "https://petstore3.swagger.io/api/v3/openapi.json",
    "output": "./output",
    "project": "PetStore",
    "synchronous": true
}

After you have the configuration ready, run hawaii in the directory where you have the hawaii.json file:

hawaii

You can also tell hawaii where to find the configuration file if it wasn't named hawaii.json, for example

hawaii --config ./petstore-hawaii.json

Using the generated project

Once hawaii has finished running, you find a fully generated F# project inside of the <output> directory. This project can be referenced from your application so you can start using it.

You can reference the project like this from your app like this:

<ItemGroup>
  <ProjectReference Include="..\path\to\output\PetStore.fsproj" />
</ItemGroup>

Then from your code:

open System
open System.Net.Http
open PetStore
open PetStore.Types

let petStoreUri = Uri "https://petstore3.swagger.io/api/v3"
let httpClient = new HttpClient(BaseAddress=petStoreUri)
let petStore = PetStoreClient(httpClient)

let availablePets() =
    let status = PetStatus.Available.Format()
    match petStore.findPetsByStatus(status) with
    | FindPetsByStatus.OK pets -> for pet in pets do printfn $"{pet.name}"
    | FindPetsByStatus.BadRequest -> printfn "Bad request"

availablePets()

// inventory : Map<string, int>
let (GetInventory.OK(inventory)) = petStore.getInventory()

for (status, quantity) in Map.toList inventory do
    printfn $"There are {quantity} pet(s) {status}"

Notice that you have to provide your own HttpClient to the PetStoreClient and set the BaseAddress to the base path of the service.

Example with OData (TripPinService schema)

{
  "schema": "https://services.odata.org/V4/(S(s3lb035ptje4a1j0bvkmqqa0))/TripPinServiceRW/$metadata",
  "project": "TripPinService",
  "output": "./output"
}

Generate OpenAPI specs from the OData schemas

Sometimes you want to see how Hawaii generated a client from an OData schema.

You use the following command to generate the intermediate OpenAPI specs file in the form of JSON.

Then you can inspect it but also modify then use it as your <schema> when you need to make corrections to the generated client

hawaii --from-odata-schema {schema} --output {output}

where

Example of such command

hawaii --from-odata-schema "https://services.odata.org/V4/(S(s3lb035ptje4a1j0bvkmqqa0))/TripPinServiceRW/$metadata" --output ./TripPin.json

Sample Applications

Version

You can ask hawaii which version it is currently on:

hawaii --version

No Logo

If you don't want the logo to show up in your CI or local machine, add --no-logo as the last parameter

hawaii --no-logo
hawaii --config ./hawaii.json --no-logo

Advanced - Overriding The Schema

OpenAPI schemas can be very loose and not always typed. Sometimes they will be missing operation IDs on certain paths. Although Hawaii will attempt to derive valid operation IDs from the path, name collisions can sometimes happen. Hawaii provides the overrideSchema option to allow you to "fix" the source schema or add more information when its missing.

Here is an example for how you can override operation IDs for certain paths

{
  "overrideSchema": {
    "paths": {
      "/consumer/v1/services/{id}/allocations": {
        "get": {
          "operationId": "getAllocationsForCustomerByServiceId"
        }
      },
      "/consumer/v1/services/allocations/{id}": {
        "get": {
            "operationId": "getAllocationIdFromCustomerServices"
        }
      }
    }
  }
}

The overrideSchema property basically takes a subset of another schema and merges it with the source schema.

You can go a step further by overriding the return types of certain responses. The following example shows how you can get the raw text output from the default response of a path instead of getting a typed response:

{
  "overrideSchema": {
    "paths": {
      "/bin/querybuilder.json": {
        "get": {
          "responses": {
            "default": {
              "content": {
                "application/json": {
                  "schema": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Limitations

These are the very early days of Hawaii as a tool to generate F# clients and there are some known limitations and rough edges that I will be working on:

You can watch the live coding sessions as a playlist published on YouTube here

Running integration tests

cd ./build
# run hawaii against multiple config permutations
dotnet run -- generate-and-build
# run hawaii against 10 schemas
dotnet run -- integration
# run hawaii agains the first {n} schemas out of ~2000 and see the progress
dotnet run -- rate {n}