Giraffe icon indicating copy to clipboard operation
Giraffe copied to clipboard

Question: How Swagger support is expected to eventually work

Open hvester opened this issue 4 years ago • 27 comments

@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?

hvester avatar Jun 06 '20 07:06 hvester

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!

slang25 avatar Jun 06 '20 12:06 slang25

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.

Krzysztof-Cieslak avatar Jun 06 '20 12:06 Krzysztof-Cieslak

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.

slang25 avatar Jun 06 '20 13:06 slang25

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?

hvester avatar Jun 06 '20 15:06 hvester

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

slang25 avatar Jun 06 '20 15:06 slang25

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.

hvester avatar Jun 06 '20 15:06 hvester

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.

isaacabraham avatar Jun 06 '20 15:06 isaacabraham

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 .

Krzysztof-Cieslak avatar Jun 06 '20 16:06 Krzysztof-Cieslak

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.

slang25 avatar Jun 06 '20 17:06 slang25

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?

slang25 avatar Jun 07 '20 20:06 slang25

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?

Krzysztof-Cieslak avatar Jun 07 '20 20:06 Krzysztof-Cieslak

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).

slang25 avatar Jun 07 '20 20:06 slang25

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!

dustinmoris avatar Jun 12 '20 08:06 dustinmoris

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 👍

jkone27 avatar Oct 25 '20 17:10 jkone27

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?

panesofglass avatar Dec 10 '20 14:12 panesofglass

Any movement on this or desire from anyone else to make some ground here? Would love to contribute if needed.

StephenStrickland avatar Jun 08 '21 03:06 StephenStrickland

@baronfel has worked on something in this area recently on stream

Krzysztof-Cieslak avatar Jun 09 '21 23:06 Krzysztof-Cieslak

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 avatar Jun 10 '21 14:06 baronfel

@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.

panesofglass avatar Jul 14 '21 17:07 panesofglass

related comment: https://github.com/giraffe-fsharp/Giraffe/issues/488#issuecomment-884217398

baronfel avatar Jul 22 '21 13:07 baronfel

@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.

kdblocher avatar Nov 16 '21 15:11 kdblocher

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!

baronfel avatar Apr 07 '22 20:04 baronfel

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!

reinux avatar Apr 23 '22 23:04 reinux

This has been merged by the aspnetcore team apparently! https://github.com/giraffe-fsharp/Giraffe/issues/428#issuecomment-1092184498 👍

jkone27 avatar Apr 28 '22 07:04 jkone27

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.

reinux avatar May 14 '22 05:05 reinux

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.

C0DK avatar Sep 16 '22 05:09 C0DK

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.

dangdennis avatar Dec 19 '22 18:12 dangdennis