diff --git a/engine.go b/engine.go index acb89568..b171ac7c 100644 --- a/engine.go +++ b/engine.go @@ -1,16 +1,144 @@ package fuego -func NewEngine() *Engine { - return &Engine{ - OpenAPI: NewOpenAPI(), - ErrorHandler: ErrorHandler, +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "os" + "path/filepath" +) + +// NewEngine creates a new Engine with the given options. +// For example: +// +// engine := fuego.NewEngin( +// WithOpenAPIConfig( +// OpenAPIConfig{ +// PrettyFormatJSON: true, +// }, +// ), +// ) +// +// Options all begin with `With`. +func NewEngine(options ...func(*Engine)) *Engine { + e := &Engine{ + OpenAPI: NewOpenAPI(), + OpenAPIConfig: defaultOpenAPIConfig, + ErrorHandler: ErrorHandler, + } + for _, option := range options { + option(e) } + return e } // The Engine is the main struct of the framework. type Engine struct { - OpenAPI *OpenAPI - ErrorHandler func(error) error + OpenAPI *OpenAPI + ErrorHandler func(error) error + OpenAPIConfig OpenAPIConfig acceptedContentTypes []string } + +type OpenAPIConfig struct { + // If true, the server will not serve nor generate any OpenAPI resources + Disabled bool + // If true, the engine will not print messages + DisableMessages bool + // If true, the engine will not save the OpenAPI JSON spec locally + DisableLocalSave bool + // Local path to save the OpenAPI JSON spec + JSONFilePath string + // Pretty prints the OpenAPI spec with proper JSON indentation + PrettyFormatJSON bool +} + +var defaultOpenAPIConfig = OpenAPIConfig{ + JSONFilePath: "doc/openapi.json", +} + +func WithOpenAPIConfig(config OpenAPIConfig) func(*Engine) { + return func(e *Engine) { + if config.JSONFilePath != "" { + e.OpenAPIConfig.JSONFilePath = config.JSONFilePath + } + + e.OpenAPIConfig.Disabled = config.Disabled + e.OpenAPIConfig.DisableLocalSave = config.DisableLocalSave + e.OpenAPIConfig.PrettyFormatJSON = config.PrettyFormatJSON + } +} + +// WithErrorHandler sets a customer error handler for the server +func WithErrorHandler(errorHandler func(err error) error) func(*Engine) { + return func(e *Engine) { + if errorHandler == nil { + panic("errorHandler cannot be nil") + } + + e.ErrorHandler = errorHandler + } +} + +// OutputOpenAPISpec takes the OpenAPI spec and outputs it to a JSON file +func (e *Engine) OutputOpenAPISpec() []byte { + e.OpenAPI.computeTags() + + // Validate + err := e.OpenAPI.Description().Validate(context.Background()) + if err != nil { + slog.Error("Error validating spec", "error", err) + } + + // Marshal spec to JSON + jsonSpec, err := e.marshalSpec() + if err != nil { + slog.Error("Error marshaling spec to JSON", "error", err) + } + + if !e.OpenAPIConfig.DisableLocalSave { + err := e.saveOpenAPIToFile(e.OpenAPIConfig.JSONFilePath, jsonSpec) + if err != nil { + slog.Error("Error saving spec to local path", "error", err, "path", e.OpenAPIConfig.JSONFilePath) + } + } + return jsonSpec +} + +func (e *Engine) saveOpenAPIToFile(jsonSpecLocalPath string, jsonSpec []byte) error { + jsonFolder := filepath.Dir(jsonSpecLocalPath) + + err := os.MkdirAll(jsonFolder, 0o750) + if err != nil { + return fmt.Errorf("error creating docs directory: %w", err) + } + + f, err := os.Create(jsonSpecLocalPath) // #nosec G304 (file path provided by developer, not by user) + if err != nil { + return fmt.Errorf("error creating file: %w", err) + } + defer f.Close() + + _, err = f.Write(jsonSpec) + if err != nil { + return fmt.Errorf("error writing file: %w", err) + } + + e.printOpenAPIMessage("JSON file: " + jsonSpecLocalPath) + return nil +} + +func (s *Engine) marshalSpec() ([]byte, error) { + if s.OpenAPIConfig.PrettyFormatJSON { + return json.MarshalIndent(s.OpenAPI.Description(), "", "\t") + } + return json.Marshal(s.OpenAPI.Description()) +} + +func (e *Engine) printOpenAPIMessage(msg string) { + if !e.OpenAPIConfig.DisableMessages { + slog.Info(msg) + } +} diff --git a/engine_test.go b/engine_test.go new file mode 100644 index 00000000..3962716f --- /dev/null +++ b/engine_test.go @@ -0,0 +1,40 @@ +package fuego + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestWithErrorHandler(t *testing.T) { + t.Run("default engine", func(t *testing.T) { + e := NewEngine() + err := NotFoundError{ + Err: errors.New("Not Found :c"), + } + errResponse := e.ErrorHandler(err) + require.ErrorAs(t, errResponse, &HTTPError{}) + }) + t.Run("custom handler", func(t *testing.T) { + e := NewEngine( + WithErrorHandler(func(err error) error { + return fmt.Errorf("%w foobar", err) + }), + ) + err := NotFoundError{ + Err: errors.New("Not Found :c"), + } + errResponse := e.ErrorHandler(err) + require.ErrorAs(t, errResponse, &HTTPError{}) + require.ErrorContains(t, errResponse, "Not Found :c foobar") + }) + t.Run("should be fatal", func(t *testing.T) { + require.Panics(t, func() { + NewEngine( + WithErrorHandler(nil), + ) + }) + }) +} diff --git a/examples/generate-opengraph-image/main.go b/examples/generate-opengraph-image/main.go index d04757c8..0679b4e5 100644 --- a/examples/generate-opengraph-image/main.go +++ b/examples/generate-opengraph-image/main.go @@ -21,9 +21,11 @@ var optionReturnsPNG = func(br *fuego.BaseRoute) { func main() { s := fuego.NewServer( - fuego.WithOpenAPIConfig(fuego.OpenAPIConfig{ - PrettyFormatJson: true, - }), + fuego.WithEngineOptions( + fuego.WithOpenAPIConfig(fuego.OpenAPIConfig{ + PrettyFormatJSON: true, + }), + ), ) fuego.GetStd(s, "/{title}", controller.OpenGraphHandler, diff --git a/examples/petstore/lib/server_test.go b/examples/petstore/lib/server_test.go index 9d0b8914..699e7377 100644 --- a/examples/petstore/lib/server_test.go +++ b/examples/petstore/lib/server_test.go @@ -14,10 +14,12 @@ import ( func TestPetstoreOpenAPIGeneration(t *testing.T) { server := NewPetStoreServer( fuego.WithoutStartupMessages(), - fuego.WithOpenAPIConfig(fuego.OpenAPIConfig{ - JsonFilePath: "testdata/doc/openapi.json", - PrettyFormatJson: true, - }), + fuego.WithEngineOptions( + fuego.WithOpenAPIConfig(fuego.OpenAPIConfig{ + JSONFilePath: "testdata/doc/openapi.json", + PrettyFormatJSON: true, + }), + ), ) server.OutputOpenAPISpec() diff --git a/openapi.go b/openapi.go index 5b4b27e5..5739bd09 100644 --- a/openapi.go +++ b/openapi.go @@ -1,13 +1,9 @@ package fuego import ( - "context" - "encoding/json" "fmt" "log/slog" "net/http" - "os" - "path/filepath" "reflect" "regexp" "slices" @@ -111,97 +107,45 @@ func (s *Server) OutputOpenAPISpec() openapi3.T { Description: "local server", }) - s.OpenAPI.computeTags() - - // Validate - err := s.OpenAPI.Description().Validate(context.Background()) - if err != nil { - slog.Error("Error validating spec", "error", err) - } - - // Marshal spec to JSON - jsonSpec, err := s.marshalSpec() - if err != nil { - slog.Error("Error marshaling spec to JSON", "error", err) - } - - if !s.OpenAPIConfig.DisableSwagger { - s.registerOpenAPIRoutes(jsonSpec) - } - - if !s.OpenAPIConfig.DisableLocalSave { - err := s.saveOpenAPIToFile(s.OpenAPIConfig.JsonFilePath, jsonSpec) - if err != nil { - slog.Error("Error saving spec to local path", "error", err, "path", s.OpenAPIConfig.JsonFilePath) - } + if !s.OpenAPIConfig.Disabled { + s.registerOpenAPIRoutes(s.Engine.OutputOpenAPISpec()) } return *s.OpenAPI.Description() } -func (s *Server) marshalSpec() ([]byte, error) { - if s.OpenAPIConfig.PrettyFormatJson { - return json.MarshalIndent(s.OpenAPI.Description(), "", "\t") - } - return json.Marshal(s.OpenAPI.Description()) -} - -func (s *Server) saveOpenAPIToFile(jsonSpecLocalPath string, jsonSpec []byte) error { - jsonFolder := filepath.Dir(jsonSpecLocalPath) - - err := os.MkdirAll(jsonFolder, 0o750) - if err != nil { - return fmt.Errorf("error creating docs directory: %w", err) - } - - f, err := os.Create(jsonSpecLocalPath) // #nosec G304 (file path provided by developer, not by user) - if err != nil { - return fmt.Errorf("error creating file: %w", err) - } - defer f.Close() - - _, err = f.Write(jsonSpec) - if err != nil { - return fmt.Errorf("error writing file: %w", err) - } - - s.printOpenAPIMessage("JSON file: " + jsonSpecLocalPath) - return nil -} - // Registers the routes to serve the OpenAPI spec and Swagger UI. func (s *Server) registerOpenAPIRoutes(jsonSpec []byte) { - GetStd(s, s.OpenAPIConfig.JsonUrl, func(w http.ResponseWriter, r *http.Request) { + GetStd(s, s.OpenAPIServerConfig.SpecURL, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write(jsonSpec) }) - s.printOpenAPIMessage(fmt.Sprintf("JSON spec: %s%s", s.url(), s.OpenAPIConfig.JsonUrl)) + s.printOpenAPIMessage(fmt.Sprintf("JSON spec: %s%s", s.url(), s.OpenAPIServerConfig.SpecURL)) - if !s.OpenAPIConfig.DisableSwaggerUI { - Register(s, Route[any, any]{ + if s.OpenAPIServerConfig.DisableSwaggerUI { + return + } + Registers(s.Engine, netHttpRouteRegisterer[any, any]{ + s: s, + route: Route[any, any]{ BaseRoute: BaseRoute{ Method: http.MethodGet, - Path: s.OpenAPIConfig.SwaggerUrl + "/", + Path: s.OpenAPIServerConfig.SwaggerURL + "/", }, - }, s.OpenAPIConfig.UIHandler(s.OpenAPIConfig.JsonUrl)) - s.printOpenAPIMessage(fmt.Sprintf("OpenAPI UI: %s%s/index.html", s.url(), s.OpenAPIConfig.SwaggerUrl)) - } -} - -func (s *Server) printOpenAPIMessage(msg string) { - if !s.disableStartupMessages { - slog.Info(msg) - } + }, + controller: s.OpenAPIServerConfig.UIHandler(s.OpenAPIServerConfig.SpecURL), + }) + s.printOpenAPIMessage(fmt.Sprintf("OpenAPI UI: %s%s/index.html", s.url(), s.OpenAPIServerConfig.SwaggerURL)) } -func validateJsonSpecUrl(jsonSpecUrl string) bool { - jsonSpecUrlRegexp := regexp.MustCompile(`^\/[\/a-zA-Z0-9\-\_]+(.json)$`) - return jsonSpecUrlRegexp.MatchString(jsonSpecUrl) +func validateSpecURL(specURL string) bool { + specURLRegexp := regexp.MustCompile(`^\/[\/a-zA-Z0-9\-\_]+(.json)$`) + return specURLRegexp.MatchString(specURL) } -func validateSwaggerUrl(swaggerUrl string) bool { - swaggerUrlRegexp := regexp.MustCompile(`^\/[\/a-zA-Z0-9\-\_]+[a-zA-Z0-9\-\_]$`) - return swaggerUrlRegexp.MatchString(swaggerUrl) +func validateSwaggerURL(swaggerURL string) bool { + swaggerURLRegexp := regexp.MustCompile(`^\/[\/a-zA-Z0-9\-\_]+[a-zA-Z0-9\-\_]$`) + return swaggerURLRegexp.MatchString(swaggerURL) } // RegisterOpenAPIOperation registers the route to the OpenAPI description. diff --git a/openapi_handler_test.go b/openapi_handler_test.go index 0e1bfe59..2e3e6323 100644 --- a/openapi_handler_test.go +++ b/openapi_handler_test.go @@ -23,7 +23,7 @@ func TestUIHandler(t *testing.T) { s.OutputOpenAPISpec() - require.NotNil(t, s.OpenAPIConfig.UIHandler) + require.NotNil(t, s.OpenAPIServerConfig.UIHandler) w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/swagger/index.html", nil) @@ -37,7 +37,7 @@ func TestUIHandler(t *testing.T) { t.Run("wrap DefaultOpenAPIHandler behind a middleware", func(t *testing.T) { s := NewServer( - WithOpenAPIConfig(OpenAPIConfig{ + WithOpenAPIServerConfig(OpenAPIServerConfig{ UIHandler: func(specURL string) http.Handler { return dummyMiddleware(DefaultOpenAPIHandler(specURL)) }, @@ -45,7 +45,7 @@ func TestUIHandler(t *testing.T) { ) s.OutputOpenAPISpec() - require.NotNil(t, s.OpenAPIConfig.UIHandler) + require.NotNil(t, s.OpenAPIServerConfig.UIHandler) w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/swagger/index.html", nil) @@ -59,7 +59,7 @@ func TestUIHandler(t *testing.T) { t.Run("disabling UI", func(t *testing.T) { s := NewServer( - WithOpenAPIConfig(OpenAPIConfig{ + WithOpenAPIServerConfig(OpenAPIServerConfig{ DisableSwaggerUI: true, }), ) diff --git a/openapi_test.go b/openapi_test.go index 0cdfbd09..9cbe7e60 100644 --- a/openapi_test.go +++ b/openapi_test.go @@ -313,10 +313,12 @@ func TestServer_OutputOpenApiSpec(t *testing.T) { docPath := "doc/openapi.json" t.Run("base", func(t *testing.T) { s := NewServer( - WithOpenAPIConfig( - OpenAPIConfig{ - JsonFilePath: docPath, - }, + WithEngineOptions( + WithOpenAPIConfig( + OpenAPIConfig{ + JSONFilePath: docPath, + }, + ), ), ) Get(s, "/", func(ContextNoBody) (MyStruct, error) { @@ -334,11 +336,11 @@ func TestServer_OutputOpenApiSpec(t *testing.T) { }) t.Run("do not print file", func(t *testing.T) { s := NewServer( - WithOpenAPIConfig( - OpenAPIConfig{ - JsonFilePath: docPath, + WithEngineOptions( + WithOpenAPIConfig(OpenAPIConfig{ + JSONFilePath: docPath, DisableLocalSave: true, - }, + }), ), ) Get(s, "/", func(ContextNoBody) (MyStruct, error) { @@ -354,12 +356,14 @@ func TestServer_OutputOpenApiSpec(t *testing.T) { }) t.Run("swagger disabled", func(t *testing.T) { s := NewServer( - WithOpenAPIConfig( - OpenAPIConfig{ - JsonFilePath: docPath, - DisableLocalSave: true, - DisableSwagger: true, - }, + WithEngineOptions( + WithOpenAPIConfig( + OpenAPIConfig{ + JSONFilePath: docPath, + DisableLocalSave: true, + Disabled: true, + }, + ), ), ) Get(s, "/", func(ContextNoBody) (MyStruct, error) { @@ -376,11 +380,13 @@ func TestServer_OutputOpenApiSpec(t *testing.T) { }) t.Run("pretty format json file", func(t *testing.T) { s := NewServer( - WithOpenAPIConfig( - OpenAPIConfig{ - JsonFilePath: docPath, - PrettyFormatJson: true, - }, + WithEngineOptions( + WithOpenAPIConfig( + OpenAPIConfig{ + JSONFilePath: docPath, + PrettyFormatJSON: true, + }, + ), ), ) Get(s, "/", func(ContextNoBody) (MyStruct, error) { @@ -453,26 +459,26 @@ func BenchmarkServer_generateOpenAPI(b *testing.B) { } } -func TestValidateJsonSpecUrl(t *testing.T) { - require.Equal(t, true, validateJsonSpecUrl("/path/to/jsonSpec.json")) - require.Equal(t, true, validateJsonSpecUrl("/spec.json")) - require.Equal(t, true, validateJsonSpecUrl("/path_/jsonSpec.json")) - require.Equal(t, false, validateJsonSpecUrl("path/to/jsonSpec.json")) - require.Equal(t, false, validateJsonSpecUrl("/path/to/jsonSpec")) - require.Equal(t, false, validateJsonSpecUrl("/path/to/jsonSpec.jsn")) +func TestValidateJsonSpecURL(t *testing.T) { + require.Equal(t, true, validateSpecURL("/path/to/jsonSpec.json")) + require.Equal(t, true, validateSpecURL("/spec.json")) + require.Equal(t, true, validateSpecURL("/path_/jsonSpec.json")) + require.Equal(t, false, validateSpecURL("path/to/jsonSpec.json")) + require.Equal(t, false, validateSpecURL("/path/to/jsonSpec")) + require.Equal(t, false, validateSpecURL("/path/to/jsonSpec.jsn")) } func TestValidateSwaggerUrl(t *testing.T) { - require.Equal(t, true, validateSwaggerUrl("/path/to/jsonSpec")) - require.Equal(t, true, validateSwaggerUrl("/swagger")) - require.Equal(t, true, validateSwaggerUrl("/Super-useful_swagger-2000")) - require.Equal(t, true, validateSwaggerUrl("/Super-useful_swagger-")) - require.Equal(t, true, validateSwaggerUrl("/Super-useful_swagger__")) - require.Equal(t, true, validateSwaggerUrl("/Super-useful_swaggeR")) - require.Equal(t, false, validateSwaggerUrl("/spec.json")) - require.Equal(t, false, validateSwaggerUrl("/path_/swagger.json")) - require.Equal(t, false, validateSwaggerUrl("path/to/jsonSpec.")) - require.Equal(t, false, validateSwaggerUrl("path/to/jsonSpec%")) + require.Equal(t, true, validateSwaggerURL("/path/to/jsonSpec")) + require.Equal(t, true, validateSwaggerURL("/swagger")) + require.Equal(t, true, validateSwaggerURL("/Super-useful_swagger-2000")) + require.Equal(t, true, validateSwaggerURL("/Super-useful_swagger-")) + require.Equal(t, true, validateSwaggerURL("/Super-useful_swagger__")) + require.Equal(t, true, validateSwaggerURL("/Super-useful_swaggeR")) + require.Equal(t, false, validateSwaggerURL("/spec.json")) + require.Equal(t, false, validateSwaggerURL("/path_/swagger.json")) + require.Equal(t, false, validateSwaggerURL("path/to/jsonSpec.")) + require.Equal(t, false, validateSwaggerURL("path/to/jsonSpec%")) } func TestLocalSave(t *testing.T) { @@ -493,10 +499,12 @@ func TestLocalSave(t *testing.T) { func TestAutoGroupTags(t *testing.T) { s := NewServer( - WithOpenAPIConfig(OpenAPIConfig{ - DisableLocalSave: true, - DisableSwagger: true, - }), + WithEngineOptions( + WithOpenAPIConfig(OpenAPIConfig{ + DisableLocalSave: true, + Disabled: true, + }), + ), ) Get(s, "/a", func(ContextNoBody) (MyStruct, error) { return MyStruct{}, nil diff --git a/server.go b/server.go index 6183fee3..53413bae 100644 --- a/server.go +++ b/server.go @@ -14,22 +14,21 @@ import ( "github.com/golang-jwt/jwt/v5" ) -type OpenAPIConfig struct { - DisableSwagger bool // If true, the server will not serve the Swagger UI nor the OpenAPI JSON spec - DisableSwaggerUI bool // If true, the server will not serve the Swagger UI - DisableLocalSave bool // If true, the server will not save the OpenAPI JSON spec locally - SwaggerUrl string // URL to serve the swagger UI - UIHandler func(specURL string) http.Handler // Handler to serve the OpenAPI UI from spec URL - JsonUrl string // URL to serve the OpenAPI JSON spec - JsonFilePath string // Local path to save the OpenAPI JSON spec - PrettyFormatJson bool // Pretty prints the OpenAPI spec with proper JSON indentation +type OpenAPIServerConfig struct { + // If true, the server will not serve the Swagger UI + DisableSwaggerUI bool + // URL to serve the swagger UI + SwaggerURL string + // Handler to serve the OpenAPI UI from spec URL + UIHandler func(specURL string) http.Handler + // URL to serve the OpenAPI JSON spec + SpecURL string } -var defaultOpenAPIConfig = OpenAPIConfig{ - SwaggerUrl: "/swagger", - JsonUrl: "/swagger/openapi.json", - JsonFilePath: "doc/openapi.json", - UIHandler: DefaultOpenAPIHandler, +var defaultOpenAPIServerConfig = OpenAPIServerConfig{ + SwaggerURL: "/swagger", + SpecURL: "/swagger/openapi.json", + UIHandler: DefaultOpenAPIHandler, } type Server struct { @@ -75,14 +74,16 @@ type Server struct { startTime time.Time - OpenAPIConfig OpenAPIConfig - loggingConfig LoggingConfig + OpenAPIServerConfig OpenAPIServerConfig + isTLS bool } // NewServer creates a new server with the given options. +// Fuego's [Server] is built on top of the standard library's [http.Server]. +// The OpenAPI and data flow is handled by the [Engine], a lightweight abstraction available for all kind of routers (net/http, Gin, Echo). // For example: // // app := fuego.NewServer( @@ -90,7 +91,8 @@ type Server struct { // fuego.WithoutLogger(), // ) // -// Option all begin with `With`. +// Options all begin with `With`. +// Some options are at engine level, and can be set with [WithEngineOptions]. // Some default options are set in the function body. func NewServer(options ...func(*Server)) *Server { s := &Server{ @@ -103,7 +105,7 @@ func NewServer(options ...func(*Server)) *Server { Mux: http.NewServeMux(), Engine: NewEngine(), - OpenAPIConfig: defaultOpenAPIConfig, + OpenAPIServerConfig: defaultOpenAPIServerConfig, Security: NewSecurity(), @@ -352,14 +354,12 @@ func WithErrorSerializer(serializer ErrorSender) func(*Server) { return func(c *Server) { c.SerializeError = serializer } } -// WithErrorHandler sets a customer error handler for the server -func WithErrorHandler(errorHandler func(err error) error) func(*Server) { - return func(c *Server) { c.ErrorHandler = errorHandler } -} - // WithoutStartupMessages disables the startup message func WithoutStartupMessages() func(*Server) { - return func(c *Server) { c.disableStartupMessages = true } + return func(c *Server) { + c.disableStartupMessages = true + c.OpenAPIConfig.DisableMessages = true + } } // WithoutLogger disables the default logger. @@ -369,41 +369,54 @@ func WithoutLogger() func(*Server) { } } -func WithOpenAPIConfig(openapiConfig OpenAPIConfig) func(*Server) { +func WithOpenAPIServerConfig(config OpenAPIServerConfig) func(*Server) { return func(s *Server) { - if openapiConfig.JsonUrl != "" { - s.OpenAPIConfig.JsonUrl = openapiConfig.JsonUrl + if config.SpecURL != "" { + s.OpenAPIServerConfig.SpecURL = config.SpecURL } - - if openapiConfig.SwaggerUrl != "" { - s.OpenAPIConfig.SwaggerUrl = openapiConfig.SwaggerUrl + if config.SwaggerURL != "" { + s.OpenAPIServerConfig.SwaggerURL = config.SwaggerURL } - - if openapiConfig.JsonFilePath != "" { - s.OpenAPIConfig.JsonFilePath = openapiConfig.JsonFilePath - } - - if openapiConfig.UIHandler != nil { - s.OpenAPIConfig.UIHandler = openapiConfig.UIHandler + if config.UIHandler != nil { + s.OpenAPIServerConfig.UIHandler = config.UIHandler } - s.OpenAPIConfig.DisableSwagger = openapiConfig.DisableSwagger - s.OpenAPIConfig.DisableSwaggerUI = openapiConfig.DisableSwaggerUI - s.OpenAPIConfig.DisableLocalSave = openapiConfig.DisableLocalSave - s.OpenAPIConfig.PrettyFormatJson = openapiConfig.PrettyFormatJson + s.OpenAPIServerConfig.DisableSwaggerUI = config.DisableSwaggerUI - if !validateJsonSpecUrl(s.OpenAPIConfig.JsonUrl) { - slog.Error("Error serving openapi json spec. Value of 's.OpenAPIConfig.JsonSpecUrl' option is not valid", "url", s.OpenAPIConfig.JsonUrl) + if !validateSpecURL(s.OpenAPIServerConfig.SpecURL) { + slog.Error("Error serving openapi json spec. Value of 's.OpenAPIServerConfig.SpecURL' option is not valid", "url", s.OpenAPIServerConfig.SpecURL) return } - if !validateSwaggerUrl(s.OpenAPIConfig.SwaggerUrl) { - slog.Error("Error serving swagger ui. Value of 's.OpenAPIConfig.SwaggerUrl' option is not valid", "url", s.OpenAPIConfig.SwaggerUrl) + if !validateSwaggerURL(s.OpenAPIServerConfig.SwaggerURL) { + slog.Error("Error serving swagger ui. Value of 's.OpenAPIServerConfig.SwaggerURL' option is not valid", "url", s.OpenAPIServerConfig.SwaggerURL) return } } } +// WithEngineOptions allows for setting of Engine options +// +// app := fuego.NewServer( +// fuego.WithAddr(":8080"), +// fuego.WithEngineOptions( +// WithOpenAPIConfig( +// OpenAPIConfig{ +// PrettyFormatJSON: true, +// }, +// ), +// ), +// ) +// +// Engine Options all begin with `With`. +func WithEngineOptions(options ...func(*Engine)) func(*Server) { + return func(s *Server) { + for _, option := range options { + option(s.Engine) + } + } +} + // WithValidator sets the validator to be used by the fuego server. // If no validator is provided, a default validator will be used. // diff --git a/server_test.go b/server_test.go index c263fbed..2aa85634 100644 --- a/server_test.go +++ b/server_test.go @@ -74,62 +74,79 @@ func TestWithXML(t *testing.T) { func TestWithOpenAPIConfig(t *testing.T) { t.Run("with default values", func(t *testing.T) { s := NewServer( - WithOpenAPIConfig(OpenAPIConfig{}), + WithOpenAPIServerConfig(OpenAPIServerConfig{}), ) - require.Equal(t, "/swagger", s.OpenAPIConfig.SwaggerUrl) - require.Equal(t, "/swagger/openapi.json", s.OpenAPIConfig.JsonUrl) - require.Equal(t, "doc/openapi.json", s.OpenAPIConfig.JsonFilePath) - require.False(t, s.OpenAPIConfig.PrettyFormatJson) + require.Equal(t, "/swagger", s.OpenAPIServerConfig.SwaggerURL) + require.Equal(t, "/swagger/openapi.json", s.OpenAPIServerConfig.SpecURL) + require.Equal(t, "doc/openapi.json", s.OpenAPIConfig.JSONFilePath) + require.False(t, s.OpenAPIConfig.PrettyFormatJSON) }) t.Run("with custom values", func(t *testing.T) { s := NewServer( - WithOpenAPIConfig(OpenAPIConfig{ - SwaggerUrl: "/api", - JsonUrl: "/api/openapi.json", - JsonFilePath: "openapi.json", - DisableSwagger: true, - DisableLocalSave: true, - PrettyFormatJson: true, + WithOpenAPIServerConfig(OpenAPIServerConfig{ + SwaggerURL: "/api", + SpecURL: "/api/openapi.json", }), + WithEngineOptions( + WithOpenAPIConfig( + OpenAPIConfig{ + JSONFilePath: "openapi.json", + DisableLocalSave: true, + PrettyFormatJSON: true, + Disabled: true, + }), + ), ) - require.Equal(t, "/api", s.OpenAPIConfig.SwaggerUrl) - require.Equal(t, "/api/openapi.json", s.OpenAPIConfig.JsonUrl) - require.Equal(t, "openapi.json", s.OpenAPIConfig.JsonFilePath) - require.True(t, s.OpenAPIConfig.DisableSwagger) + require.Equal(t, "/api", s.OpenAPIServerConfig.SwaggerURL) + require.Equal(t, "/api/openapi.json", s.OpenAPIServerConfig.SpecURL) + require.Equal(t, "openapi.json", s.OpenAPIConfig.JSONFilePath) + require.True(t, s.Engine.OpenAPIConfig.Disabled) require.True(t, s.OpenAPIConfig.DisableLocalSave) - require.True(t, s.OpenAPIConfig.PrettyFormatJson) + require.True(t, s.OpenAPIConfig.PrettyFormatJSON) }) t.Run("with invalid local path values", func(t *testing.T) { t.Run("with invalid path", func(t *testing.T) { NewServer( - WithOpenAPIConfig(OpenAPIConfig{ - JsonFilePath: "path/to/jsonSpec", - SwaggerUrl: "p i", - JsonUrl: "pi/op enapi.json", + WithOpenAPIServerConfig(OpenAPIServerConfig{ + SwaggerURL: "p i", + SpecURL: "pi/op enapi.json", }), + WithEngineOptions( + WithOpenAPIConfig(OpenAPIConfig{ + JSONFilePath: "path/to/jsonSpec", + }), + ), ) }) t.Run("with invalid url", func(t *testing.T) { NewServer( - WithOpenAPIConfig(OpenAPIConfig{ - JsonFilePath: "path/to/jsonSpec.json", - JsonUrl: "pi/op enapi.json", - SwaggerUrl: "p i", + WithOpenAPIServerConfig(OpenAPIServerConfig{ + SpecURL: "pi/op enapi.json", + SwaggerURL: "p i", }), + WithEngineOptions( + WithOpenAPIConfig(OpenAPIConfig{ + JSONFilePath: "path/to/jsonSpec.json", + }), + ), ) }) t.Run("with invalid url", func(t *testing.T) { NewServer( - WithOpenAPIConfig(OpenAPIConfig{ - JsonFilePath: "path/to/jsonSpec.json", - JsonUrl: "/api/openapi.json", - SwaggerUrl: "invalid path", + WithOpenAPIServerConfig(OpenAPIServerConfig{ + SpecURL: "/api/openapi.json", + SwaggerURL: "invalid path", }), + WithEngineOptions( + WithOpenAPIConfig(OpenAPIConfig{ + JSONFilePath: "path/to/jsonSpec.json", + }), + ), ) }) }) @@ -272,6 +289,7 @@ func TestWithoutStartupMessages(t *testing.T) { ) require.True(t, s.disableStartupMessages) + require.True(t, s.Engine.OpenAPIConfig.DisableMessages) } func TestWithoutAutoGroupTags(t *testing.T) {