Giraffe
Giraffe copied to clipboard
Question: How Swagger support is expected to eventually work
@isaacabraham mentioned yesterday in fsharpConf that endpoint routing should be the enabler for Swagger support. After looking at the EndpointRouting.fs I am wondering how Swagger support could possibly work. Based on my understanding (I'm happy to be proven wrong) it is not possible to implement "Swagger support for endpoint routing" so that by using endpoint routing you would get Swagger support out of the box, because the apis that are used to register endpoints (such as MapMethods) take RequestDelegate from which it is not possible to know (without running it, which is not practical) for example what kind of json it returns or which HTTP codes the endpoint may return.
If Swagger support cannot be gotten out of the box then it means that the relevant metadata should be collected by Giraffe. EndPoint type seems to take a step in the correct direction, but at least at the moment it does not contain the metadata that would be necessary to generate Swagger documentation.
Any thoughts?
You're right @hvester, I think there has been a bit of misconception around swagger, I don't think we'll get it "for free" from endpoint routing, for the reasons you've described - we don't have any detail in the structure of our app other than the routes now.
There are some new options that the endpoint routing opens up though, I think the most realistic option would be to have an optional "descriptor" with each route.
To clarify, now that the overall application is a list of endpoints, it could be that there is an alternative top-level structure that also takes a description object for each endpoint. Each route is basically; an endpoint, and RequestDelegate (i.e. the composition of handlers) and an object describing what that RequestDelegate does, this description object would be optional but lights up swagger.
Another reasonable approach would be to change the signature of HttpHandler
, so that each handler describes itself, and the combinators (>=>
/choose
) could also work in a mode that aggregates the handlers and gives you that description I talked about in the previous option. However, I feel this pollutes the simplicity of Giraffe, and would be quite a heavy change.
I'd love to hear more thoughts on this and other options!
What we get "for free" is the fact that in the end, we have a flat list of routes in application - which is already a huge improvement from documentation generation point of view over the previous routing.
I believe that in case of Endpoint routing solution is using Metadata API. So we would have, for example, POST => withDocumentation<InputDto, outputDto> => route "/sadas" (myHttpHandler)
. We could possibly later use this metadata information to generate the swagger documentation.
Totally agree @Krzysztof-Cieslak , I may not have been clear, the endpoint routing is a big improvement.
What you have suggested is spot on, I suggested the metadata API a couple of years back, but couldn't find it. If swashbuckle or something else can pick that up then that takes a lot of the work off of us.
There are some new options that the endpoint routing opens up though, I think the most realistic option would be to have an optional "descriptor" with each route.
This descriptor/metadata should be present in the top-level data structure and probably should be thought out before this is released to avoid breaking backwards compatibility in the future.
Another reasonable approach would be to change the signature of
HttpHandler
, so that each handler describes itself, and the combinators (>=>
/choose
) could also work in a mode that aggregates the handlers and gives you that description I talked about in the previous option. However, I feel this pollutes the simplicity of Giraffe, and would be quite a heavy change.
I think this is something that should be considered now that a new top level data structure will be introduced. And yes, I very much agree this kind of change would undermine the simplicity of Giraffe to some extent. Have you @slang25 prototyped this approach?
No, but I could have a bash at what @Krzysztof-Cieslak has suggested, just as a proof of concept, it wouldn't be a breaking change, but it would be nice to prove it.
Update: this will be a breaking change
I believe that in case of Endpoint routing solution is using Metadata API. So we would have, for example,
POST => withDocumentation<InputDto, outputDto> => route "/sadas" (myHttpHandler)
. We could possibly later use this metadata information to generate the swagger documentation.
I don't think that the Metadata API solves much, because the metadata has to come from somewhere, which means that it has to be collected from the handler tree in one way or another.
Apologies for potentially misleading - I should have been clear that I wasn't suggesting Swagger directly but more the ability to generate automatic documentation for APIs through endpoint routing.
I don't think that the Metadata API solves much, because the metadata has to come from somewhere, which means that it has to be collected from the handler tree in one way or another.
This metadata, one way or another, will always need to be manually defined (for example with such helper function I've used in my pseudocode sample). There's absolutely no way to do that automatically with HttpHandler
abstraction - as content binding and output writing are happening on the runtime, not as part of static typing.
The only way to do it on the compile-time would be totally changing HttpHandler
abstraction... which would break nice abstraction, make things more complex and potentially cause problems with composability .
Yeah, metadata will be useful as we can attach it to the endpoint routes and then we are hoping for the rest of the aspnetcore eco-system to turn that into openapi/swagger.
Changing the HttpHandler
abstraction, or augmenting it with descriptions at that level would be too much IMO, for the reasons @Krzysztof-Cieslak mentions. I think the POST => withDocumentation<InputDto, outputDto> => route "/sadas" (myHttpHandler)
approach really is the pragmatic sweet spot.
There is the potential that the description of your endpoint does not match the reality, and I know this will be a pain point, but I feel like this is what it would need to look like.
I solved this mismatch issue for my previous client with some property-based tests, and documented the approach here: Property Based Testing your API Response Types with F# I'm sure there will be lots of solutions that will help keep things in sync.
I didn't mean to accuse you (@isaacabraham) of misleading, it was more that with the current discussions on GitHub, I worry that people are expecting the kind of magic where you just get swagger from existing handlers, and this likely won't be the case.
I've done a little investigation this evening.
It doesn't look like there's any progress made within Swashbuckle or ASP.NET Core with the Endpoint Metadata stuff, you can attach objects to the endpoint metadata, but at the moment there are only basic metadata types provided by ASP.NET Core today (like HttpMethodMetadata
).
Best case scenario is this gets designed and built in the .NET 6 timeframe, I think this is still unlikely.
Today in Giraffe endpoints look like this: https://github.com/giraffe-fsharp/Giraffe/blob/855b623aac88ce78d36170ae89c47765557587f9/src/Giraffe/EndpointRouting.fs#L182-L184
We'll likely want to add to the tuples, or the cases an extra object for metadata, so this will be a breaking change and perhaps something we should put into the structure now to keep the door open to reduce breaking changes, I'm always hesitant to second guess a design ahead of implementing something. It may be that adding cases to the Endpoint
DU (instead of adding to the existing tuples) isn't that big of a breaking change (I'm a little rusty on F# and backward binary compatibility).
Given the current situation, it may be worth looking at tackling the OpenAPI gen ourselves, using a similar approach to Carter (which @jchannon did here: https://github.com/CarterCommunity/Carter/pull/135)
@giraffe-fsharp/a-team What are your thoughts on this. I have 3 questions:
- Am I missing something?
- Do we want to make a change now to the
Endpoint
type to avoid a breaking change - Do we want to build out the OpenAPI gen ourselves?
basic metadata types provided by ASP.NET Core today
Keep in mind underlying API is basically IList<obj>
, so we can add there what we need (i.e custom types)
Do we want to make a change now to the Endpoint type to avoid a breaking change
As we are in alpha
phase for 5.0 release I think it's a good time to expand Endpoint
type without worrying about breaking changes - I've already proposed some changes to it in #423. I think we should definitely change the type to allow including Metadata - it may be useful not only for OpenAPI but potential other tooling - so it's orthogonal to this Swagger discussion.
Do we want to build out the OpenAPI gen ourselves?
This is a big question... and I guess, as always answer is - is there anyone who will take a lead on this and maintain this part?
I'm happy to have a bash over the next few weekends 😃 I'm thinking about streaming it on twitch, could be fun!
The thing with IList<obj>
is that we can add whatever we want in there, but our types aren't going to be touched by Swashbuckle or ASP.NET Core (we can dream!), so it kinda rules out a solution that leverages those libraries picking up some of the OpenAPI work. We could put our own metadata in there, but wouldn't need to for our own solution. Maybe in a year or so we'll find ourselves wanting to put some objects in there though if and when the eco-system starts to find uses for it.
We could do with getting the API nailed down, or perhaps I build something and we discuss it (the generation will be most of the work).
Do we want to make a change now to the Endpoint type to avoid a breaking change
Yes
Do we want to build out the OpenAPI gen ourselves?
Maybe have to :(
Keep in mind underlying API is basically IList
, so we can add there what we need (i.e custom types)
Agreed
As we are in alpha phase for 5.0 release I think it's a good time to expand Endpoint type without worrying about breaking changes - I've already proposed some changes to it in #423. I think we should definitely change the type to allow including Metadata - it may be useful not only for OpenAPI but potential other tooling - so it's orthogonal to this Swagger discussion.
Agreed
I'm happy to have a bash over the next few weekends 😃 I'm thinking about streaming it on twitch, could be fun!
I want to watch!
Hello guys, news on this one ? NET5 is RC2,
anyone able to find a workaround to make swagger work with endpoints for giraffe?
Cheers, have a wonderful week, and thank you so much for Giraffe 👍
In case it helps, I wrote https://github.com/panesofglass/FSharp.Data.JsonSchema to help with translating F# <-> JSON Schema. Perhaps it could be useful?
Any movement on this or desire from anyone else to make some ground here? Would love to contribute if needed.
@baronfel has worked on something in this area recently on stream
That's right, I have been. @StephenStrickland I started https://github.com/baronfel/Giraffe.EndpointRouting.OpenAPI to try my hand at the problem space. The intent of the library is to use Endpoint Metadata to store metadata descriptions about the routes, but to do it by shadowing the existing Giraffe combinators where appropriate so that it's less code churn for a user to start using the library. Of course a user could always add metadata explicitly via an addMetadata
function as well. Right now it's very barebones (I've only gotten as far as generating a scaffolded set of path objects and the httpmethod operations underneath the paths. I actually was looking at @panesofglass's library https://github.com/panesofglass/FSharp.Data.JsonSchema for the actual json schema generation.
If you clone the repo and run dotnet watch run
from the samples/OpenAPI.Sample
directory, you can go to https://localhost:5001/api-docs
to see the rendered openapi definition.
@baronfel I haven't touched it in awhile, and I know there are some outstanding issues. Let me know if you need me to jump on any.
related comment: https://github.com/giraffe-fsharp/Giraffe/issues/488#issuecomment-884217398
@slang25 This is a killer feature that I would love to have in order to move my entire microservice architecture at work over to Giraffe. Has there been any news about this topic recently? I would be happy to collaborate on this if there's a doable solution for it.
The aspnetcore team has something proposed for .NET 7 that would move the OpenAPi metadata from MVC down into a separate library, as well as expose endpoint extension methods to set metadata: https://github.com/dotnet/aspnetcore/issues/40676
This could be the shared core that we've been looking for to build on!
The aspnetcore team has something proposed for .NET 7 that would move the OpenAPi metadata from MVC down into a separate library, as well as expose endpoint extension methods to set metadata: https://github.com/dotnet/aspnetcore/issues/40676
Very much looking forward to this!
This has been merged by the aspnetcore team apparently! https://github.com/giraffe-fsharp/Giraffe/issues/428#issuecomment-1092184498 👍
Possibly slightly OT, but is there a way to use Swagger in the meantime?
Even if I have to do some extra typing, it would be useful if I can at least specify some F#/CLR types to generate Swagger descriptions from.
This has been merged by the aspnetcore team apparently! #428 (comment) 👍
Now 6 months later, when would we see the benefits of this? I'm trying different API frameworks and familiarizing myself with f#, however, an OpenAPI spec is a requirement.. :/ I don't want to use plain aspnetcore.
I want to add an edge case on top of the already difficult work to get swagger into f# land. However, maybe more advanced F# practitioners have already found a solution.
I've resorted to using asp.net directly with f# for the swagger integration. If you return an Option type from a controller, the swagger integration generates an <T>FSharpOption
in the spec.
Example. Not a perfect rendition but I think this example should make sense.
myTypeResponse: {
"type": "object",
"properties": {
"myCustomProperty": {
"type": StringFSharpOption { value: string, nullable: true } // I've inlined the openapi component for brevity
}
}
}
But instead, what we'd want is myCustomProperty: { type: "string", nullable: true }
without the nested FSharpOption object.