|
open System |
|
open Microsoft.AspNetCore.Builder |
|
open Microsoft.Extensions.Hosting |
|
open Microsoft.Extensions.DependencyInjection |
|
open Microsoft.FSharp.Core |
|
open Microsoft.OpenApi.Models |
|
open Swashbuckle.AspNetCore.SwaggerGen |
|
|
|
type Child = { Text: string } |
|
type Parent = { Child: Child option } |
|
|
|
// Replaces all Option<'T> types and types with Properties that have Option<'T> with the corresponding 'T type |
|
type OptionSchemaFilter() = |
|
let isFSharpOption (t:Type) = |
|
// There's got to be a better way to test if it's the generic Option<'T> type, |
|
// but I couldn't figure it out and settled for this instead |
|
t.Name = "FSharpOption`1" && t.Namespace = "Microsoft.FSharp.Core" |
|
interface ISchemaFilter with |
|
member x.Apply(schema: OpenApiSchema, context: SchemaFilterContext) = |
|
if isFSharpOption context.Type then |
|
let argumentType = context.Type.GetGenericArguments()[0] |
|
let argumentSchema = context.SchemaGenerator.GenerateSchema(argumentType, context.SchemaRepository) |
|
schema.Reference <- argumentSchema.Reference |
|
else |
|
for propertyInfo in context.Type.GetProperties() do |
|
if isFSharpOption propertyInfo.PropertyType then |
|
let argumentType = propertyInfo.PropertyType.GetGenericArguments()[0] |
|
let argumentSchema = context.SchemaGenerator.GenerateSchema(argumentType, context.SchemaRepository) |
|
|
|
// There is probably a better way to generate the property name. |
|
// This seems like it could have edge cases |
|
let camelCasePropertyName = String [| |
|
yield Char.ToLower(propertyInfo.Name[0]) |
|
yield! propertyInfo.Name[1..] |
|
|] |
|
|
|
schema.Properties[camelCasePropertyName].Reference <- argumentSchema.Reference |
|
|
|
// Removes the "*FSharpOption" added types from the schema list |
|
type OptionDocumentFilter() = |
|
interface IDocumentFilter with |
|
member this.Apply(swaggerDoc, _) = |
|
for key in swaggerDoc.Components.Schemas.Keys do |
|
if key.EndsWith("FSharpOption") then |
|
swaggerDoc.Components.Schemas.Remove(key) |> ignore |
|
|
|
[<EntryPoint>] |
|
let main args = |
|
let builder = WebApplication.CreateBuilder(args) |
|
|
|
builder.Services.AddSwaggerGen(fun options -> |
|
options.SchemaFilter<OptionSchemaFilter>() |
|
options.DocumentFilter<OptionDocumentFilter>() |
|
) |> ignore |
|
|
|
builder.Services.AddEndpointsApiExplorer() |> ignore |
|
|
|
let app = builder.Build() |
|
|
|
app.UseSwagger() |> ignore |
|
app.UseSwaggerUI() |> ignore |
|
|
|
app.MapPost("/test", Func<_,_>( |
|
fun ([<FromBody>]s:Parent) -> |
|
System.Text.Json.JsonSerializer.Serialize(s)) |
|
) |> ignore |
|
|
|
app.Run() |
|
|
|
0 |
Improving some bits with resp.
Nullable <- true
printfn
for while-dev-debugging-purposesJsonNamingPolicy.CamelCase.ConvertName(propertyInfo.Name)
for consistent camel casing property namesInput Sample:
and the relevant debugging output sample: