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
$refschemas are created as separated one but with structure "copied" from referenced schema - Nested inline schemas are created as separated one
- Nested
$refschemas 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
openOpenAPITypeProvidertypePetStore= 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.
letjson="""{"name":"Roman"}"""letpet= PetStore.Schemas.NewPet.Parse(json)letname= pet.Name // contains Romanlettag= pet.Tag // contains Option value NoneSometimes you need to use parse JSON with custom date format.
letjson="""{"date1":"31. 12. 2018 12:34:56","date2":"31. 12. 2017 12:34:56"}"""letcustomDateFormat="dd. MM. yyyy HH:mm:ss"lettwoDates= 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 definitionletjson="""{"notExistingProperty":"Roman"}"""letpet= PetStore.Schemas.NewPet.Parse(json)// fails with exception that property 'name' not convertible// to string valueletjson="""{"name":123456}"""letpet= PetStore.Schemas.NewPet.Parse(json)Schema types has constructors based on definition so you can instantiate them as you need.
letpet=new PetStore.Schemas.NewPet(name ="Roman")Each Schema instance has method ToJson with few overloads.
letpet=new PetStore.Schemas.NewPet(name ="Roman")
pet.ToJson()// returns '{"name":"Roman"}' - no indenting
pet.ToJson(Newtonsoft.Json.Formatting.Indented)// return json with fancy formattingEach ToJson method has overload with supporting JsonSerializerSettings as parameter.
letpet=new PetStore.Schemas.NewPet(name ="Roman")letsettings= 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.
letpet=new PetStore.Schemas.NewPet(name ="Roman")letjtoken= pet.ToJToken()Again, you can customize how to handle optional values.
letpet=new PetStore.Schemas.NewPet(name ="Roman")letjtoken= pet.ToJToken(NullValueHandling.Include)// this now contains JNull value inside JObjectBy 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: arrayitems:
type: stringletsimpleString= PetStore.Schemas.SimpleString("ABC")
simpleString.Value // contains "ABC"letsimpleArray= 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.
letpetStoreAPI=new PetStore()// Note! Instance of PetStore type is needed here.letpet=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 NewPetletparsedPet= 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
OneOfandManyOfsince 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.
