Skip to content

Szer/GiraffeGenerator

Repository files navigation

Giraffe Generator

.NET Core CI

This is first naive version of Giraffe server generator from OpenAPI specification

I believe in "contract-first" approach, and your OpenAPI spec is basically contract for your API

Backend code is just an implementation of this contract and client doesn't really want to know about it

Neither should you, so this library will help you with that

It follows Myriad approach and defines MSBuild target to generate code based on input

Example project is available to check here

Future feature list (basically TODO list):

  • Creating models from OpenAPI schemas
    • records generated from schema definitions with all data types from spec
    • handlers in generated webApp should support these types
    • default values support for primitive types
    • oneOf support
    • anyOf support
    • allOf support
    • discriminator support
    • not won't be supported
    • validation support (#33)
    • NodaTime support opt-in (#32)
  • Multiple responses from one endpoint
  • Creating endpoints with support for bindings
    • path
    • query
    • body
    • header (#35)
    • cookie (#35)
    • content-type negotiated body (#36)
    • binding error handling
  • Add XML comments on top of endpoint from OpenAPI descriptions
  • Support authentication (best efforts)
  • Support JSON/XML (de-)serialization
    • JSON
    • XML

How to use

  • Add nuget GiraffeGenerator.Sdk
  • Create OpenAPI spec file
  • Add generated file to your project file:
    <Compile Include="Generated.fs">
      <OpenApiSpecFile>spec.yaml</OpenApiSpecFile>
    </Compile>
  • Build project to generate the file
  • Implement interface defined in this file and register your implementation in AspNetCore DI
  • If you want to customize validation for some or all of your models, you have two extension points:
    • IGiraffeValidator<'model> - replaces all generated validation for the 'model type. Note: doesn't replace the validation for nested complex types (objects, arrays, options). Provide an implementation for them explicitely if you want to replace validation for them too.
    • IGiraffeAdditionalValidator<'model> - adds more validation to either IGiraffeValidator<'model> or generated validation
  • Note for people migrating from/to *nix: System.ComponentModel.DataAnnotations.RangeAttribute used by validation produces a different text representation for Double.*Infinity on *nix: "Infinity" instead of infinity unicode symbol (&#221E;)
  • May require serializer configuration to support mapping of absent and null values from/to Optionon<_>
  • May require serializer configuration to throw on absent required properties

Codegen configuration

All configuration is done by adding more child tags to Compile object. There are two types of configuration: parameterless (flags) and parameterfull (parameters).

flag is specified like <OpenApiFlag>true</OpenApiFlag>: absense of tag or any content except for true is treated as false

parameter is passed as tag content like <OpenApiValue>your value</OpenApiValue>

Example of both:

    <Compile Include="Generated.fs">
      <OpenApiSpecFile>spec.yaml</OpenApiSpecFile>
      <OpenApiSomeFlag>true</OpenApiSomeFlag>
    </Compile>

Generated module name customization

Defined as parameter OpenApiModuleName

Allowing non-qualified access

Defined as flag OpenApiAllowUnqualifiedAccess

NodaTime support

Enabled by OpenApiUseNodaTime flag.

Has optional parameter OpenApiMapDateTimeInto which controls generated type for date-time OpenAPI string format. Has four possible values:

  • instant
  • local-date-time
  • offset-date-time (default)
  • zoned-date-time

Adds support for the following custom string formats:

Format NodaTime type Supports default values (format)
local-date LocalDate [x] uuuu'-'MM'-'dd (c)
date LocalDate [x] (as above)
date-time Differs (see OpenApiMapDateTimeInto) [~] By configured format
instant Instant [x] uuuu'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFFF'Z'
local-time LocalTime [x] HH':'mm':'ss.FFFFFFFFF
time LocalTime [x] (as above)
local-date-time LocalDateTime [x] uuuu'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFFF (c)
offset-date-time OffsetDateTime [x] uuuu'-'MM'-'dd'T'HH':'mm':'ss;FFFFFFFFFo<Z+HH:mm> (c)
zoned-date-time ZonedDateTime [ ] No
offset Offset [x] general pattern, e.g. +05 or -03:30
time-offset Offset [x] (as above)
duration Duration [x] -H:mm:ss.FFFFFFFFF
period Interval [x] ISO8601 Duration (round-trip)
time-zone DateTimeZone [x] IANA Tzdb identifier
date-time-zone DateTimeZone [x] (as above)

Usage requires installation of NodaTime 3+ nuget at least. For some content types of bodies containing NodaTime types additional packages may be needed.

Note that zoned-date-time cannot be passed in query string or path parameters by default. Also note that (de)serialization duration format differs for query string binding and Json.NET serialization by default. See this test for more details.

How it works internally

  • It parses OpenAPI spec with package Microsoft.OpenApi.Readers to intermediate representation (IR)
  • Then it creates F# AST based on that IR
  • Finally it produces source code file with help of Fantomas

How to build and test

  1. Restore tools: dotnet tool restore
  2. dotnet pwsh build.ps1

At this stage there is no NuGet package publishing and packages are being published locally

To consume them in Example project there is local NuGet.Config with local repo added

After first full build&pack you could delete Generated.fs file from Example project and build it again to see that it actually generates on build

How to publish

  1. Make sure you have nuget API key set in GIRAFFE_GENERATOR_NUGET env
  2. Update version in Directory.build.props
  3. Put whatever you haven't put into Unreleased section of CHANGELOG
  4. Run ./publish.ps1
    • It will ask you for random number (as protection from accidental runs)
    • It will ask you whether you changed the version and updated CHANGELOG
    • It will parse new version from the Directory.build.props
    • Hard reset with git clean to latest master (stashing and poping props and CHANGELOG)
    • Run ./build.ps1 (compilation + test)
    • Check that tag with that version doesn't exist
    • Check that last version in changelog is lower
    • Will update CHANGELOG on major and minor (not patch) updates
      • It will replace Unreleased section with new version and will put a date on it
      • Put link to the bottom section called Changes (as git diff)
    • Will commit "release vX.Y.Z" with a tag
    • Will push artifacts to nuget at the end