Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 [Feature]: allow to automatically generate OpenAPI docs based on route details, without additional in-code comments #2347

Open
3 tasks done
loveyandex opened this issue Feb 27, 2023 · 10 comments

Comments

@loveyandex
Copy link

Feature Description

this swagger doc generation is so boring

Additional Context (optional)

No response

Code Snippet (optional)

package main

import "github.com/gofiber/fiber/v2"
import "log"

func main() {
  app := fiber.New()

  // An example to describe the feature

  log.Fatal(app.Listen(":3000"))
}

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have checked for existing issues that describe my suggestion prior to opening this one.
  • I understand that improperly formatted feature requests may be closed without explanation.
@welcome
Copy link

welcome bot commented Feb 27, 2023

Thanks for opening your first issue here! 🎉 Be sure to follow the issue template! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord

@ReneWerner87
Copy link
Member

Please revise your feature request and note how you imagine the interaction with fiber so that everyone has a concrete idea of the task to be done.

We need usage examples and the concrete generated output.

Please avoid personal impressions that will not help anyone.

generation is so boring

Since this is also optional and open-api is not supposed to be part of the core, this feature would have to be implemented in the swagger packages if i understand it correctly

@leonklingele leonklingele changed the title 🚀 [Feature]: new fast open api doc generator base on fiber.Ctx without commenting 🚀 [Feature]: allow to automatically generate OpenAPI docs based on route details, without additional in-code comments Mar 4, 2023
@leonklingele
Copy link
Member

leonklingele commented Mar 4, 2023

I suppose the idea behind this issue is to provide a similar functionality as done by emicklei/go-restful.
go-restful allows you to specify route options inside type-safe code which can be used to auto-generate an OpenAPI spec without using additional comments which are required for the current swagger package to work.

ws := new(restful.WebService)
ws.
	Path("/users").
	Consumes(restful.MIME_XML, restful.MIME_JSON).
	Produces(restful.MIME_JSON, restful.MIME_XML)

ws.Route(ws.GET("/{user-id}").To(u.findUser).
	Doc("get a user").
	Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
	Writes(User{}))		
...
	
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
	id := request.PathParameter("user-id")
	...
}

Basically, the whole API is documented inside go code without having to use any comments (which are not type-safe) at all.

Even though this OpenAPI spec generation itself should probably be done in a new middleware or tool, fiber itself needs to expose more data and add new route configuration options so this can be achieved.

@leonklingele leonklingele reopened this Mar 4, 2023
@mirusky
Copy link
Contributor

mirusky commented Mar 28, 2023

Maybe a middleware could achieve that, but we need some way to express "Consumes", "Produces", "Response 200", "Response 4xx", "Docs", "Params", "Auth" ... Since we don't know what user has writen inside the fiber.Handler it's difficult to produce an openapi spec.

Another approach could be some kind of code generator that reads the fiber.Handler code and then generate the specs.

Either way we would need some kind of annotation / more code to provide more details


Or it could have some kind of "decorator" function that extends fiber like:

package main

import (
    "github.com/gofiber/fiber/v2"
    "openapi"
)

func main() {
   app := openapi.New(fiber.New())

   // now here the router is extended
   app.Get("/users/:id", svc.FindUsers)
      Doc("get a user").
      Params("user-id", "identifier of the user", "string").
      Response200(User{}).
      Response400(FriendlyError{}).
      Response500(FriendlyError{})

   // After defining all routes we could call a method that exposes the spec based on what user has written
   app.Specs("/openapi")

   app.Listen(":3000")
}

@nesies
Copy link

nesies commented Jun 15, 2023

FastAPI offer automatic Swagger generator without adding comment to the Code.

https://fastapi.tiangolo.com/features/#automatic-docs
https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#add-regular-expressions

@gedw99
Copy link

gedw99 commented Aug 17, 2023

I use this

https://github.com/RussellLuo/kun#quick-start

It’s very clean and simple.

just put the annotation above the code line and it will gen it

The examples show it.

At the moment it uses go-kit, but I can work with Fiber by making simple adjustments to the go template.

i used kun to generate code for weaver . Weaver is the golang Kubernetes stack.

https://github.com/ServiceWeaver/weaver-kube

example: https://github.com/ServiceWeaver/weaver-kube/blob/main/examples/echo/echo.go

It would mean that Fiber users can deploy to k8 auto scale as well as tuning the exact same code on all desktops and servers without needing any docker or configuration.

on k8 you get scaling to zero for free

@leonklingele
Copy link
Member

leonklingele commented Jan 17, 2024

Some improvements to @mirusky suggestion (#2347 (comment)):

app := openapi.New(fiber.New())

app.Patch("/users/:id", handlers.UpdateUserHandler).
	Doc("Partially update a user by ID").

	// New: Ensure that really _all_ parameters in the URL from the
	//      route above ("/users/:id") have a corresponding "Params()"
	//      documentation. Panic or something otherwise.
	// New: Specify type using "fiber.UUIDv4" so we can
	//      retrieve the param later as a typed and verified UUID,
	//      similar to "<guid>" constraint.
	//      See https://docs.gofiber.io/guide/routing/#constraints.
	Param[":id", fiber.UUIDv4]("ID of the user").

	// New: Specify type using "model.User" so we can retrieve
	//      the body later as a typed and verified "model.User",
	Body[model.User, fiber.HeaderContentTypeJSON]("The updated user").

	// New: "Response()" method which accepts the status code and a
	//      content type (as well as other additional response headers).
	// New: Ensure that we really only return the specified object
	//      type (or types, plural?) for a given status code from our
	//      handler, similar to oapi-codegen's "strict" server.
	//      See https://github.com/deepmap/oapi-codegen/tree/66f9bb8d73111908bb20d30bae90e65eb49a6770#strict-server-generation
	Response[fiber.StatusNoContent, nil]("User was updated").
	Response[fiber.StatusNotFound, CustomError, fiber.ContentTypeJSON]("User not found").
	Response[fiber.StatusInternalServerError, CustomError, fiber.ContentTypeJSON]("Internal error")

app.Listen(":3000")
func UpdateUserHandler(c fiber.Ctx) error {
	id, err := c.Param(":id")
	if err != nil {
		// Abort handler if no ":id" param was provided.
		return fiber.Error(
			fiber.StatusInternalServerError,
			CustomError(err),
		)
	}

	// "id" var is of type fiber.UUIDv4

	user, err := c.Body[model.User]()
	if err != nil {
		// Abort handler if no "model.User" was provided.
		return fiber.Error(
			fiber.StatusInternalServerError,
			CustomError(err),
		)
	}

	// "model.NotProvided" was not specified in the
	// route above -> this will always abort the request.
	if _, err := c.Body[model.NotProvided](); err != nil {
		return fiber.Error(
			fiber.StatusInternalServerError,
			CustomError(err),
		)
	}

	if true {
		// This type of error was not specified in the
		// route above -> this will panic or something.
		return errors.New("unspecified error")
	}

	return svc.UpdateUserByID(id, user)
}

@nickajacks1
Copy link
Member

@leonklingele I think what you suggested looks very comfortable to use for simple cases.

I've observed some rather large challenges that I've seen in providing proper openapi support in looking for something to fit my org's use cases:

  • Keeping up with new versions of the OpenAPI spec. Quite a few libraries out there are stuck at 3.0 or even 2.0.
  • Providing a balance between rigorous adherence to OpenAPI and usability. The actual spec is quite deep and would take a lot of code to really get right. The more that is supported, the more complex the implementation will need to be and the less performant it will become.
  • There are a great deal of use cases to support. Spec generation, request response validation, interaction with json schema, authentication, and so on. Even if the scope was limited to only spec generation, someone's going to want validation.

I don't bring these challenges up as reasons not to pursue this, but rather to get people thinking about solutions (I'm very much interested in better openapi support in the go world!!)

I do worry that including something like this in core fiber may be a bit aggressive. An addon/extension/middleware of some kind might be an appropriate middle ground, unless a great deal of the scary implementation details can be outsourced to some external package.

@danielgtaylor
Copy link

danielgtaylor commented Mar 6, 2024

Just wanted to share that while it won't just automatically generate OpenAPI from your existing codebase, https://github.com/danielgtaylor/huma does provide a layer on top of Fiber that gives you OpenAPI 3.1 generation from your Go structs and handlers, just like FastAPI. Here's the "hello world" example for Fiber:

package main

import (
	"context"
	"fmt"

	"github.com/danielgtaylor/huma/v2"
	"github.com/danielgtaylor/huma/v2/adapters/humafiber"
	"github.com/gofiber/fiber/v2"
)

// GreetingOutput represents the greeting operation response.
type GreetingOutput struct {
	Body struct {
		Message string `json:"message" example:"Hello, world!" doc:"Greeting message"`
	}
}

func main() {
	// Create a new router & API
	app := fiber.New()
	api := humafiber.New(app, huma.DefaultConfig("My API", "1.0.0"))

	// Add the operation handler to the API.
	huma.Get(api, "/greeting/{name}", func(ctx context.Context, input *struct {
		Name string `path:"name" maxLength:"30" example:"world" doc:"Name to greet"`
	}) (*GreetingOutput, error) {
		resp := &GreetingOutput{}
		resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name)
		return resp, nil
	})

	app.Listen(":3000")
}

There's a full tutorial you can check out here that includes making an API with read & write calls, service options & config, using a generated CLI client and generating a Go SDK: https://huma.rocks/tutorial/installation/

That gives you a fully functional API with OpenAPI 3.1, JSON Schema, built-in validation, standard structured error responses, client-driven content negotiation, etc. You can use huma.Register to get full access to the OpenAPI operation to set things like the operation ID, description, tags, etc, including support for custom extensions. api.OpenAPI() provides full access to modify the spec as needed as well.

Hopefully you find some of this useful, and even if you don't support these features in Fiber core maybe you can suggest Huma as an option for you users.

@leonklingele
Copy link
Member

leonklingele commented Mar 7, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants