diff --git a/examples/petstore/controllers/pets.go b/examples/petstore/controllers/pets.go index e3bfecab..a407ae47 100644 --- a/examples/petstore/controllers/pets.go +++ b/examples/petstore/controllers/pets.go @@ -36,22 +36,24 @@ func (rs PetsResources) Routes(s *fuego.Server) { optionPagination, option.Query("name", "Filter by name", param.Example("cat name", "felix"), param.Nullable()), option.QueryInt("younger_than", "Only get pets younger than given age in years", param.Default(3)), - option.Description("Filter pets"), + option.AddDescription("Filter pets"), ) fuego.Get(petsGroup, "/all", rs.getAllPets, optionPagination, option.Tags("my-tag"), - option.Description("Get all pets"), + option.AddDescription("Get all pets"), ) - fuego.Get(petsGroup, "/by-age", rs.getAllPetsByAge, option.Description("Returns an array of pets grouped by age")) + fuego.Get(petsGroup, "/by-age", rs.getAllPetsByAge, option.AddDescription("Returns an array of pets grouped by age")) fuego.Post(petsGroup, "/", rs.postPets, option.DefaultStatusCode(201), option.AddError(409, "Conflict: Pet with the same name already exists", PetsError{}), ) fuego.Get(petsGroup, "/{id}", rs.getPets, + option.Description("Replace description with this sentence."), + option.OperationID("getPet"), option.Path("id", "Pet ID", param.Example("example", "123")), ) fuego.Get(petsGroup, "/by-name/{name...}", rs.getPetByName) diff --git a/examples/petstore/lib/testdata/doc/openapi.golden.json b/examples/petstore/lib/testdata/doc/openapi.golden.json index 16eaede4..f60d8c19 100644 --- a/examples/petstore/lib/testdata/doc/openapi.golden.json +++ b/examples/petstore/lib/testdata/doc/openapi.golden.json @@ -748,8 +748,8 @@ ] }, "get": { - "description": "controller: `github.com/go-fuego/fuego/examples/petstore/controllers.PetsResources.getPets`\n\n---\n\n", - "operationId": "GET_/pets/:id", + "description": "Replace description with this sentence.", + "operationId": "getPet", "parameters": [ { "in": "header", diff --git a/mux.go b/mux.go index 2cbc51b7..b4b2e21c 100644 --- a/mux.go +++ b/mux.go @@ -93,8 +93,9 @@ func Register[T, B any](s *Server, route Route[T, B], controller http.Handler, o o(&route.BaseRoute) } route.Handler = controller + route.Path = s.basePath + route.Path - fullPath := s.basePath + route.Path + fullPath := route.Path if route.Method != "" { fullPath = route.Method + " " + fullPath } @@ -107,27 +108,11 @@ func Register[T, B any](s *Server, route Route[T, B], controller http.Handler, o return &route } - route.Path = s.basePath + route.Path - err := route.RegisterOpenAPIOperation(s.OpenAPI) if err != nil { slog.Warn("error documenting openapi operation", "error", err) } - if route.FullName == "" { - route.FullName = route.Path - } - - if route.Operation.Summary == "" { - route.Operation.Summary = route.NameFromNamespace(camelToHuman) - } - - route.Operation.Description = "controller: `" + route.FullName + "`\n\n---\n\n" + route.Operation.Description - - if route.Operation.OperationID == "" { - route.Operation.OperationID = route.Method + "_" + strings.ReplaceAll(strings.ReplaceAll(route.Path, "{", ":"), "}", "") - } - return &route } @@ -176,12 +161,14 @@ func PatchStd(s *Server, path string, controller func(http.ResponseWriter, *http func registerFuegoController[T, B any, Contexted ctx[B]](s *Server, method, path string, controller func(Contexted) (T, error), options ...func(*BaseRoute)) *Route[T, B] { route := BaseRoute{ - Method: method, - Path: path, - Params: make(map[string]OpenAPIParam), - FullName: FuncName(controller), - Operation: openapi3.NewOperation(), - OpenAPI: s.OpenAPI, + Method: method, + Path: path, + Params: make(map[string]OpenAPIParam), + FullName: FuncName(controller), + Operation: &openapi3.Operation{ + Description: "controller: `" + FuncName(controller) + "`\n\n---\n\n", + }, + OpenAPI: s.OpenAPI, } acceptHeaderParameter := openapi3.NewHeaderParameter("Accept") @@ -197,11 +184,15 @@ func registerFuegoController[T, B any, Contexted ctx[B]](s *Server, method, path func registerStdController(s *Server, method, path string, controller func(http.ResponseWriter, *http.Request), options ...func(*BaseRoute)) *Route[any, any] { route := BaseRoute{ - Method: method, - Path: path, - FullName: FuncName(controller), - Operation: openapi3.NewOperation(), - OpenAPI: s.OpenAPI, + Method: method, + Path: path, + Params: make(map[string]OpenAPIParam), + FullName: FuncName(controller), + Operation: &openapi3.Operation{ + Description: "controller: `" + FuncName(controller) + "`\n\n---\n\n", + }, + Handler: http.HandlerFunc(controller), + OpenAPI: s.OpenAPI, } for _, o := range append(s.routeOptions, options...) { diff --git a/mux_test.go b/mux_test.go index 0d73a66e..0cb2987c 100644 --- a/mux_test.go +++ b/mux_test.go @@ -402,21 +402,21 @@ func TestRegister(t *testing.T) { Operation: &openapi3.Operation{ Tags: []string{"my-tag"}, Summary: "my-summary", - Description: "my-description", + Description: "my-description\n", OperationID: "my-operation-id", }, }, }, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), OptionOperationID("new-operation-id"), OptionSummary("new-summary"), - OptionDescription("new-description"), + OptionAddDescription("new-description"), OptionTags("new-tag"), ) require.NotNil(t, route) require.Equal(t, []string{"my-tag", "new-tag"}, route.Operation.Tags) require.Equal(t, "new-summary", route.Operation.Summary) - require.Equal(t, "controller: `/test`\n\n---\n\nnew-description", route.Operation.Description) + require.Equal(t, "my-description\nnew-description", route.Operation.Description) require.Equal(t, "new-operation-id", route.Operation.OperationID) }) } diff --git a/openapi.go b/openapi.go index e08ae26e..185f803c 100644 --- a/openapi.go +++ b/openapi.go @@ -204,6 +204,18 @@ func RegisterOpenAPIOperation[T, B any](openapi *OpenAPI, route Route[T, B]) (*o route.Operation = openapi3.NewOperation() } + if route.FullName == "" { + route.FullName = route.Path + } + + if route.Operation.Summary == "" { + route.Operation.Summary = route.NameFromNamespace(camelToHuman) + } + + if route.Operation.OperationID == "" { + route.Operation.OperationID = route.Method + "_" + strings.ReplaceAll(strings.ReplaceAll(route.Path, "{", ":"), "}", "") + } + // Request Body if route.Operation.RequestBody == nil { bodyTag := SchemaTagFromType(openapi, *new(B)) @@ -258,7 +270,7 @@ func RegisterOpenAPIOperation[T, B any](openapi *OpenAPI, route Route[T, B]) (*o for _, params := range route.Operation.Parameters { if params.Value.In == "path" { if !strings.Contains(route.Path, "{"+params.Value.Name) { - return nil, fmt.Errorf("path parameter '%s' is not declared in the path", params.Value.Name) + panic(fmt.Errorf("path parameter '%s' is not declared in the path", params.Value.Name)) } } } diff --git a/openapi_operations_test.go b/openapi_operations_test.go index c03e129f..85b67551 100644 --- a/openapi_operations_test.go +++ b/openapi_operations_test.go @@ -16,7 +16,7 @@ func TestTags(t *testing.T) { ) require.Equal(t, []string{"my-tag"}, route.Operation.Tags) - require.Equal(t, "controller: `github.com/go-fuego/fuego.testController`\n\n---\n\nmy description", route.Operation.Description) + require.Equal(t, "my description", route.Operation.Description) require.Equal(t, "my summary", route.Operation.Summary) require.Equal(t, true, route.Operation.Deprecated) } diff --git a/option.go b/option.go index 1c4c47e7..429f7033 100644 --- a/option.go +++ b/option.go @@ -273,7 +273,7 @@ func OptionDescription(description string) func(*BaseRoute) { // like the controller function name and the package name. func OptionAddDescription(description string) func(*BaseRoute) { return func(r *BaseRoute) { - r.Operation.Description += "\n\n" + description + r.Operation.Description += description } } diff --git a/option_test.go b/option_test.go index 61196994..1746d85e 100644 --- a/option_test.go +++ b/option_test.go @@ -11,6 +11,7 @@ import ( "github.com/thejerf/slogassert" "github.com/go-fuego/fuego" + "github.com/go-fuego/fuego/option" "github.com/go-fuego/fuego/param" ) @@ -232,7 +233,7 @@ func TestOpenAPI(t *testing.T) { route := fuego.Get(s, "/test", helloWorld, fuego.OptionSummary("test summary"), - fuego.OptionDescription("test description"), + fuego.OptionAddDescription("test description"), fuego.OptionTags("first-tag", "second-tag"), fuego.OptionDeprecated(), fuego.OptionOperationID("test-operation-id"), @@ -710,15 +711,25 @@ func TestSecurity(t *testing.T) { } func TestOptionAddDescription(t *testing.T) { + t.Run("Declare a description for the route", func(t *testing.T) { + s := fuego.NewServer() + + route := fuego.Get(s, "/test", helloWorld, + option.AddDescription("test description"), + ) + + require.Equal(t, "controller: `github.com/go-fuego/fuego_test.helloWorld`\n\n---\n\ntest description", route.Operation.Description) + }) + t.Run("Declare a description for the route with multiple descriptions", func(t *testing.T) { s := fuego.NewServer() route := fuego.Get(s, "/test", helloWorld, - fuego.OptionDescription("test description"), - fuego.OptionAddDescription("another description"), + option.Description("test description\n\n"), + option.AddDescription("another description"), ) - require.Equal(t, "controller: `github.com/go-fuego/fuego_test.helloWorld`\n\n---\n\ntest description\n\nanother description", route.Operation.Description) + require.Equal(t, "test description\n\nanother description", route.Operation.Description) }) }