Skip to content

Missing DU Support for OpenAPI Discriminator Schemas #60

@houstonhaynes

Description

@houstonhaynes

Hawaii doesn't natively generate F# discriminated unions from OpenAPI discriminator schemas (oneOf with mapping). These are semantically discriminated unions but Hawaii generates them as separate types without a union type.

OpenAPI Source

# From Cloudflare Workers OpenAPI spec
workersbindingitem:
  oneOf:
    - $ref: "#/components/schemas/workersbindingkindassets"
    - $ref: "#/components/schemas/workersbindingkindbrowser"
    - $ref: "#/components/schemas/workersbindingkindd1"
    - $ref: "#/components/schemas/workersbindingkindr2bucket"
    # ... 27 total binding types
  discriminator:
    propertyName: type
    mapping:
      assets: "#/components/schemas/workersbindingkindassets"
      browser: "#/components/schemas/workersbindingkindbrowser"
      # ... etc

Current Hawaii Output

Hawaii generates individual types but no discriminated union:

type workersbindingkindassets = { ... }
type workersbindingkindbrowser = { ... }
type workersbindingkindd1 = { ... }
// ... 27 more types

// But no:
// type workersbindingitem =
//     | Assets of workersbindingkindassets
//     | Browser of workersbindingkindbrowser
//     | D1 of workersbindingkindd1

CloudflareFS Workaround

We created a post-processing script that analyzes the generated code and automatically creates discriminated unions:

File: generators/hawaii/post-process-discriminators.fsx

// Auto-detects binding kind types and generates DU
let getBindingKindTypes (typesContent: string) =
    let pattern = @"type (workersbindingkind[a-z0-9]+)\s*="
    Regex.Matches(typesContent, pattern)
    |> Seq.cast<Match>
    |> Seq.map (fun m -> m.Groups.[1].Value)
    |> Seq.toList

let generateDiscriminatedUnion (typeName: string) (cases: (string * string) list) =
    let typeDecl = sprintf "type %s =" typeName
    let unionCases =
        cases
        |> List.map (fun (caseName, schemaType) ->
            sprintf "    | %s of %s" caseName schemaType)
        |> String.concat "\n"
    sprintf "%s\n%s\n" typeDecl unionCases

Generated Output:

// Discriminated union (auto-generated by post-processor)
type workersbindingitem =
    | Assets of workersbindingkindassets
    | Browser of workersbindingkindbrowser
    | D1 of workersbindingkindd1
    | R2bucket of workersbindingkindr2bucket
    // ... 27 cases total

Integration: generators/hawaii/generate-workers.ps1

# Step 1: Run Hawaii
dotnet run --project D:\repos\Hawaii\src\Hawaii.fsproj -- --config workers-hawaii.json

# Step 2: Post-process discriminated unions
dotnet fsi post-process-discriminators.fsx Generated-Workers\Types.fs

Proposed Enhancement

Hawaii should detect OpenAPI discriminator schemas and automatically generate F# discriminated unions:

  1. Detect oneOf with discriminator in OpenAPI schema
  2. Generate individual case types (current behavior)
  3. Additionally generate a discriminated union type
  4. Use the discriminator.propertyName for JSON serialization
  5. Handle discriminator.mapping for case name mapping

Benefits

  • Type safety: Single union type instead of loose collection of types
  • Pattern matching: F# exhaustiveness checking works
  • Semantic correctness: OpenAPI discriminators ARE discriminated unions
  • No post-processing: Works out of the box

Reference Implementation

CloudflareFS has a working post-processor that demonstrates the pattern. We're happy to contribute this logic to Hawaii if it would be helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions