Erased netstandard2.0 type provider for web API providers.
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:
- Generate it from code (code first approach)
- 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.
When I started to think about writing this type provider, I set few goals:
- Netstandard2.0 support
- Erased type
- Tightly connected to Newtonsoft.Json
- Based on the latest OpenAPI specification (no support for Swagger)
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:
- Root inline schemas are always created as separated one
- Root
$ref
schemas are created as separated one but with structure "copied" from referenced schema - Nested inline schemas are created as separated one
- Nested
$ref
schemas are linked to referenced schema
First install NuGet package
Install-Package OpenAPITypeProvider
or using Paket
nuget OpenAPITypeProvider
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.
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)
Schema types has constructors based on definition so you can instantiate them as you need.
let pet = new PetStore.Schemas.NewPet(name = "Roman")
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
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}'
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
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"]
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
- No support for
OneOf
andManyOf
since they are basically union types which is quite difficult (or maybe impossible) to generate from type provider
You are more than welcome to send a pull request if you find some bug or missing functionality.