F# type provider for Open API specification
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.paket Adding OperationId on Operations #7 Nov 1, 2018
examples/NetCoreConsoleApp Fixing #9 Oct 16, 2018
src Adding OperationId on Operations #7 Nov 1, 2018
tests/OpenAPITypeProvider.Tests Adding OperationId on Operations #7 Nov 1, 2018
.gitattributes Added TP Starter Pack Feb 21, 2018
.gitignore Version 1.2 Sep 17, 2018
Directory.Build.targets
OpenAPITypeProvider.sln Refactored to follow official template Oct 12, 2018
README.md Version 2.0.0 done Oct 16, 2018
RELEASE_NOTES.md Adding OperationId on Operations #7 Nov 1, 2018
after.OpenAPITypeProvider.sln.targets Refactored to follow official template Oct 12, 2018
build.cmd Initial commit Jan 18, 2018
build.fsx Refactored to follow official template Oct 12, 2018
build.sh Initial commit Jan 18, 2018
logo.jpg Added logo Jul 24, 2018
netfx.props Refactored to follow official template Oct 12, 2018
paket.dependencies
paket.lock

README.md

drawing

OpenAPI Type Provider

Erased netstandard2.0 type provider for web API providers.

Why to use this type provider?

If you write F# backend for some application providing JSON API, you probably want to document this using OpenAPI v3 specification (previously called Swagger). This documentation can be created basically two ways:

  1. Generate it from code (code first approach)
  2. Write it manually (document first approach)

This type provider is focused on second option when you already got existing documentation (e.g. from frontend developer) and you want to be 100% sure that your API follows it correctly, which mostly means two things: Validation of requests payload is in correct form (as described in API documentation) and creating responses. Both things can be quite tedious and error-prone. It is common in web API development that after some time that server behavior is not what is written in documentation. This type provider is here to help with that.

Goals of OpenAPITypeProvider

When I started to think about writing this type provider, I set few goals:

  1. Netstandard2.0 support
  2. Erased type
  3. Tightly connected to Newtonsoft.Json
  4. Based on the latest OpenAPI specification (no support for Swagger)

Version 2.0.0 change

In versions < 2.0, the Schemas were created based on simplified logic: If the schema is the same (having same structure), it is considered to be the same schema no matter name you use (actually the first parsed named is used for all others). This approach had good intentions - to minimize amount of created Schemas - however shown to be wrong for complex scenarios. This could easily lead to situation where change in one Schema breaks your other Schema, even if they were not directly linked using $ref.

Since version 2.0.0 the Schemas are created based on logic:

  1. Root inline schemas are always created as separated one
  2. Root $ref schemas are created as separated one but with structure "copied" from referenced schema
  3. Nested inline schemas are created as separated one
  4. Nested $ref schemas are linked to referenced schema

Instalation

First install NuGet package

Install-Package OpenAPITypeProvider

or using Paket

nuget OpenAPITypeProvider

How to use

First open correct namespace and create basic type based on your YAML documentation

open OpenAPITypeProvider

type PetStore = OpenAPIV3Provider<"PetStore.yaml">

Now you can use defined Schemas in your specification like F# types.

Parsing from JSON

Each Schema type can be created from JSON string using static method Parse.

let json = """{"name":"Roman"}"""
let pet = PetStore.Schemas.NewPet.Parse(json)
let name = pet.Name // contains Roman
let tag = pet.Tag // contains Option value None

Sometimes you need to use parse JSON with custom date format.

let json = """{"date1":"31. 12. 2018 12:34:56","date2":"31. 12. 2017 12:34:56"}"""
let customDateFormat = "dd. MM. yyyy HH:mm:ss"
let twoDates = PetStore.Schemas.TwoDates.Parse(json, customDateFormat)

Method Parse throws an exception in case JSON does not fit to definition:

// fails with exception that property 'name' is not present,
// but should be based on Schema definition
let json = """{"notExistingProperty":"Roman"}"""
let pet = PetStore.Schemas.NewPet.Parse(json) 

// fails with exception that property 'name' not convertible
// to string value
let json = """{"name":123456}"""
let pet = PetStore.Schemas.NewPet.Parse(json) 

Instantiation of Schema types

Schema types has constructors based on definition so you can instantiate them as you need.

let pet = new PetStore.Schemas.NewPet(name = "Roman")

Converting to JSON

Each Schema instance has method ToJson with few overloads.

let pet = new PetStore.Schemas.NewPet(name = "Roman")
pet.ToJson() // returns '{"name":"Roman"}' - no indenting
pet.ToJson(Newtonsoft.Json.Formatting.Indented) // return json with fancy formatting

Converting to JSON with custom serializer

Each ToJson method has overload with supporting JsonSerializerSettings as parameter.

let pet = new PetStore.Schemas.NewPet(name = "Roman")

let settings = JsonSerializerSettings()
settings.NullValueHandling <- NullValueHandling.Include

pet.ToJson(settings, Formatting.None) // returns '{"name":"Roman","tag":null}'

Converting to Newtonsoft JToken

If you need JToken instead of string with JSON, method ToJToken is here for you.

let pet = new PetStore.Schemas.NewPet(name = "Roman")
let jtoken = pet.ToJToken()

Again, you can customize how to handle optional values.

let pet = new PetStore.Schemas.NewPet(name = "Roman")
let jtoken = pet.ToJToken(NullValueHandling.Include) // this now contains JNull value inside JObject

Simple values

By specification you are allowed to have Schema types not an objects, but simple values like strings or integers. This type provider supports them as well.

SimpleString:
    type: string    
    
SimpleArray:
    type: array
    items:
        type: string
let simpleString = PetStore.Schemas.SimpleString("ABC")
simpleString.Value // contains "ABC"

let simpleArray = PetStore.Schemas.SimpleArray(["A";"B"])
simpleArray.Values // contains List<string> ["A";"B"]

Requests & ResponseBodies

Using Schema types directly is quite handy and straightforward, but it doesn't say anything about routes, requests and responses. If you want to be 100% sure that you are fullfilling specification, go for Parse on Requests and ToJson / ToJToken methods on ResponseBodies.

let petStoreAPI = new PetStore() // Note! Instance of PetStore type is needed here.
let pet = new PetStore.Schemas.NewPet("Roman")

// this route returns NewPet schema by definition so ToJson allows only NewPet schema as parameter
petStoreAPI.Paths.``/pets/{id}``.Get.Responses.``200``.``application/json``.ToJson(pet)

// this route expects NewPet schema by definition so Parse method returns NewPet
let parsedPet = petStoreAPI.Paths.``/pets``.Post.RequestBody.``application/json``.Parse(jsonFromRequest)

In case you have any doubts, you can always have a look at unit tests

Known issues & Limitations

  1. No support for OneOf and ManyOf since they are basically union types which is quite difficult (or maybe impossible) to generate from type provider

Contribution

You are more than welcome to send a pull request if you find some bug or missing functionality.