Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ type Config struct {
OpenAPIDocsPath string // Path for docs UI (default: "/docs")
OpenAPIJSONPath string // Path for JSON spec (default: "/openapi.json")
OpenAPIYamlPath string // Path for YAML spec (default: "/openapi.yaml")
OpenAPITitle string // Spec title (default: "Fiber OpenAPI")
OpenAPIDescription string // Spec description (default: "API documentation generated by fiber-oapi")
OpenAPIVersion string // Spec version (default: "1.0.0")
AuthService AuthorizationService // Service for handling auth
SecuritySchemes map[string]SecurityScheme // OpenAPI security schemes
DefaultSecurity []map[string][]string // Default security requirements
Expand All @@ -83,6 +86,19 @@ Default config when none is provided:
- Docs path: `/docs`
- JSON spec path: `/openapi.json`
- YAML spec path: `/openapi.yaml`
- Spec title: `Fiber OpenAPI`
- Spec description: `API documentation generated by fiber-oapi`
- Spec version: `1.0.0`

Override the `info` block of the generated spec via `Config`:

```go
fiberoapi.New(app, fiberoapi.Config{
OpenAPITitle: "My Service",
OpenAPIDescription: "Public REST API for the My Service platform",
OpenAPIVersion: "2024.10.1",
})
```
Comment thread
moutonjeremy marked this conversation as resolved.

## HTTP Methods

Expand Down
13 changes: 8 additions & 5 deletions _examples/simple/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,14 @@ func main() {

// Example 1: Using custom configuration
customConfig := fiberoapi.Config{
EnableValidation: true,
EnableOpenAPIDocs: true,
OpenAPIDocsPath: "/documentation", // Custom docs path
OpenAPIJSONPath: "/api-spec.json", // Custom spec path
OpenAPIYamlPath: "/api-spec.yaml", // Custom YAML spec path
EnableValidation: true,
EnableOpenAPIDocs: true,
OpenAPIDocsPath: "/documentation", // Custom docs path
OpenAPIJSONPath: "/api-spec.json", // Custom spec path
OpenAPIYamlPath: "/api-spec.yaml", // Custom YAML spec path
OpenAPITitle: "Simple Example API", // Spec title
OpenAPIDescription: "A minimal fiber-oapi demo for the README", // Spec description
OpenAPIVersion: "0.1.0", // Spec version
}
appOApi := fiberoapi.New(app, customConfig)
v1 := fiberoapi.Group(appOApi, "/api/v1")
Expand Down
138 changes: 137 additions & 1 deletion config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package fiberoapi

import (
"encoding/json"
"io"
"net/http/httptest"
"testing"

"github.com/gofiber/fiber/v3"
Expand Down Expand Up @@ -71,7 +74,7 @@ func TestConfigMerging(t *testing.T) {
AuthService: authService,
})
config := oapi.Config()

// Should respect explicit values when auth service is provided
if config.EnableValidation {
t.Error("Expected EnableValidation to be false when explicitly configured")
Expand All @@ -83,4 +86,137 @@ func TestConfigMerging(t *testing.T) {
t.Error("Expected AuthService to be set")
}
})
}

func TestOpenAPIInfoConfig(t *testing.T) {
t.Run("Defaults populate info block", func(t *testing.T) {
oapi := New(fiber.New())
cfg := oapi.Config()
if cfg.OpenAPITitle != "Fiber OpenAPI" {
t.Errorf("default OpenAPITitle = %q, want %q", cfg.OpenAPITitle, "Fiber OpenAPI")
}
if cfg.OpenAPIDescription != "API documentation generated by fiber-oapi" {
t.Errorf("default OpenAPIDescription = %q", cfg.OpenAPIDescription)
}
if cfg.OpenAPIVersion != "1.0.0" {
t.Errorf("default OpenAPIVersion = %q, want %q", cfg.OpenAPIVersion, "1.0.0")
}

spec := oapi.GenerateOpenAPISpec()
info := spec["info"].(map[string]interface{})
if info["title"] != "Fiber OpenAPI" {
t.Errorf("spec title = %v", info["title"])
}
if info["description"] != "API documentation generated by fiber-oapi" {
t.Errorf("spec description = %v", info["description"])
}
if info["version"] != "1.0.0" {
t.Errorf("spec version = %v", info["version"])
}
})

t.Run("Custom title/description/version override defaults", func(t *testing.T) {
oapi := New(fiber.New(), Config{
OpenAPITitle: "My Service",
OpenAPIDescription: "Public REST API",
OpenAPIVersion: "2024.10.1",
})

spec := oapi.GenerateOpenAPISpec()
info := spec["info"].(map[string]interface{})
if info["title"] != "My Service" {
t.Errorf("title = %v, want %q", info["title"], "My Service")
}
if info["description"] != "Public REST API" {
t.Errorf("description = %v", info["description"])
}
if info["version"] != "2024.10.1" {
t.Errorf("version = %v, want %q", info["version"], "2024.10.1")
}
})

t.Run("Partial override preserves other defaults", func(t *testing.T) {
oapi := New(fiber.New(), Config{
OpenAPITitle: "Only Title Set",
})
cfg := oapi.Config()
if cfg.OpenAPITitle != "Only Title Set" {
t.Errorf("title not overridden: %q", cfg.OpenAPITitle)
}
if cfg.OpenAPIDescription != "API documentation generated by fiber-oapi" {
t.Errorf("description should keep default, got %q", cfg.OpenAPIDescription)
}
if cfg.OpenAPIVersion != "1.0.0" {
t.Errorf("version should keep default, got %q", cfg.OpenAPIVersion)
}
})

t.Run("Info field + explicit disable + core signal propagates booleans", func(t *testing.T) {
// With a core signal (AuthService here) booleans from `provided` are honored,
// so the explicit EnableOpenAPIDocs:false actually disables auto docs even
// though the user also customized the title.
authService := &struct{ AuthorizationService }{}
oapi := New(fiber.New(), Config{
OpenAPITitle: "Custom",
EnableOpenAPIDocs: false,
AuthService: authService,
})
cfg := oapi.Config()
if cfg.EnableOpenAPIDocs {
t.Error("EnableOpenAPIDocs should be false when explicitly disabled alongside a core signal")
}
if cfg.OpenAPITitle != "Custom" {
t.Errorf("title = %q", cfg.OpenAPITitle)
}
})

t.Run("Info-only config keeps boolean defaults", func(t *testing.T) {
// Without any core signal, info-only customization must not silently flip
// EnableValidation / EnableOpenAPIDocs to false via Go's zero values.
oapi := New(fiber.New(), Config{OpenAPITitle: "Just Title"})
cfg := oapi.Config()
if !cfg.EnableValidation {
t.Error("EnableValidation should stay true when only cosmetic fields are set")
}
if !cfg.EnableOpenAPIDocs {
t.Error("EnableOpenAPIDocs should stay true when only cosmetic fields are set")
}
})

t.Run("Auto-served /openapi.json reflects custom info", func(t *testing.T) {
app := fiber.New()
New(app, Config{
OpenAPITitle: "Auto Served",
OpenAPIDescription: "Served via auto docs route",
OpenAPIVersion: "9.9.9",
})

req := httptest.NewRequest("GET", "/openapi.json", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Fatalf("status = %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("read response body: %v", err)
}
var spec map[string]interface{}
Comment thread
moutonjeremy marked this conversation as resolved.
if err := json.Unmarshal(body, &spec); err != nil {
t.Fatalf("invalid JSON: %v", err)
}
info := spec["info"].(map[string]interface{})
if info["title"] != "Auto Served" {
t.Errorf("title = %v", info["title"])
}
if info["description"] != "Served via auto docs route" {
t.Errorf("description = %v", info["description"])
}
if info["version"] != "9.9.9" {
t.Errorf("version = %v", info["version"])
}
})
}
67 changes: 47 additions & 20 deletions fiberoapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ import (
// DefaultConfig returns the default configuration
func DefaultConfig() Config {
return Config{
EnableValidation: true,
EnableOpenAPIDocs: true,
OpenAPIDocsPath: "/docs",
OpenAPIJSONPath: "/openapi.json",
OpenAPIYamlPath: "/openapi.yaml",
EnableValidation: true,
EnableOpenAPIDocs: true,
OpenAPIDocsPath: "/docs",
OpenAPIJSONPath: "/openapi.json",
OpenAPIYamlPath: "/openapi.yaml",
OpenAPITitle: "Fiber OpenAPI",
OpenAPIDescription: "API documentation generated by fiber-oapi",
OpenAPIVersion: "1.0.0",
}
}

Expand All @@ -35,13 +38,19 @@ func New(app *fiber.App, config ...Config) *OApiApp {
// We'll only override if the provided config appears to be intentionally configured

// Simple approach: if the provided config has any non-default values,
// we assume the user intended to configure it explicitly
// we assume the user intended to configure it explicitly.
// Info fields (title/description/version) participate in this signal too so that
// e.g. `Config{OpenAPITitle: "X", EnableOpenAPIDocs: false}` propagates the
// explicit `false` to the resolved config (consistency with path fields).
hasExplicitConfig := provided.EnableAuthorization ||
provided.AuthService != nil ||
provided.SecuritySchemes != nil ||
provided.OpenAPIDocsPath != "" ||
provided.OpenAPIJSONPath != "" ||
provided.OpenAPIYamlPath != "" ||
provided.OpenAPITitle != "" ||
provided.OpenAPIDescription != "" ||
provided.OpenAPIVersion != "" ||
provided.ValidationErrorHandler != nil ||
provided.AuthErrorHandler != nil

Expand All @@ -52,23 +61,32 @@ func New(app *fiber.App, config ...Config) *OApiApp {
}
// If no explicit config, keep the defaults (true, true, false)

// Heuristic: when only handler(s) are provided and all booleans are at zero value,
// assume the caller didn't intend to disable validation/docs — restore defaults.
otherExplicitConfig := provided.EnableAuthorization ||
// Heuristic: when only "metadata" fields (paths, info, handlers — i.e. cosmetic
// customization that does not signal intent about validation/docs themselves)
// are provided and all booleans are at their zero value, restore boolean defaults.
// This avoids surprising users who only want to override the spec title/path but
// end up with validation silently disabled because Go zero values can't disambiguate
// "not set" from "set to false". To actually disable validation or docs, set at least
// one core signal field too (EnableAuthorization / AuthService / SecuritySchemes).
hasCoreSignal := provided.EnableAuthorization ||
provided.AuthService != nil ||
provided.SecuritySchemes != nil ||
provided.OpenAPIDocsPath != "" ||
provided.SecuritySchemes != nil

hasMetadataOnly := !hasCoreSignal && (provided.OpenAPIDocsPath != "" ||
provided.OpenAPIJSONPath != "" ||
provided.OpenAPIYamlPath != ""
provided.OpenAPIYamlPath != "" ||
provided.OpenAPITitle != "" ||
provided.OpenAPIDescription != "" ||
provided.OpenAPIVersion != "" ||
provided.ValidationErrorHandler != nil ||
provided.AuthErrorHandler != nil)

// Only restore defaults if ALL boolean fields are false (suggesting they weren't explicitly set)
allBooleansAreFalse := !provided.EnableValidation && !provided.EnableOpenAPIDocs && !provided.EnableAuthorization
hasOnlyHandlers := (provided.ValidationErrorHandler != nil || provided.AuthErrorHandler != nil) && !otherExplicitConfig

if hasOnlyHandlers && allBooleansAreFalse {
// Only handler(s) are set, so restore defaults for boolean fields
cfg.EnableValidation = true // Keep validation enabled - the handler needs it
cfg.EnableOpenAPIDocs = true // Keep docs enabled - default behavior
if hasMetadataOnly && allBooleansAreFalse {
cfg.EnableValidation = true // Restore — caller likely didn't mean to disable it
cfg.EnableOpenAPIDocs = true // Restore — caller likely didn't mean to disable it
}

// For EnableAuthorization: only set to true if explicitly provided
Expand All @@ -86,6 +104,15 @@ func New(app *fiber.App, config ...Config) *OApiApp {
if provided.OpenAPIYamlPath != "" {
cfg.OpenAPIYamlPath = provided.OpenAPIYamlPath
}
if provided.OpenAPITitle != "" {
cfg.OpenAPITitle = provided.OpenAPITitle
}
if provided.OpenAPIDescription != "" {
cfg.OpenAPIDescription = provided.OpenAPIDescription
}
if provided.OpenAPIVersion != "" {
cfg.OpenAPIVersion = provided.OpenAPIVersion
}
Comment thread
moutonjeremy marked this conversation as resolved.
if provided.AuthService != nil {
cfg.AuthService = provided.AuthService
}
Expand Down Expand Up @@ -160,9 +187,9 @@ func (o *OApiApp) GenerateOpenAPISpec() map[string]interface{} {
spec := map[string]interface{}{
"openapi": "3.0.0",
"info": map[string]interface{}{
"title": "Fiber OpenAPI",
"version": "1.0.0",
"description": "API documentation generated by fiber-oapi",
"title": o.config.OpenAPITitle,
"version": o.config.OpenAPIVersion,
"description": o.config.OpenAPIDescription,
},
"paths": make(map[string]interface{}),
"components": make(map[string]interface{}),
Expand Down
3 changes: 3 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ type Config struct {
OpenAPIDocsPath string // Path for documentation UI (default: "/docs")
OpenAPIJSONPath string // Path for OpenAPI JSON spec (default: "/openapi.json")
OpenAPIYamlPath string // Path for OpenAPI YAML spec (default: "/openapi.yaml")
OpenAPITitle string // Title of the OpenAPI spec (default: "Fiber OpenAPI")
OpenAPIDescription string // Description of the OpenAPI spec (default: "API documentation generated by fiber-oapi")
OpenAPIVersion string // Version of the OpenAPI spec (default: "1.0.0")
AuthService AuthorizationService // Service for handling authentication and authorization
SecuritySchemes map[string]SecurityScheme // OpenAPI security schemes
DefaultSecurity []map[string][]string // Default security requirements
Expand Down
Loading