From 657affd67cd113a52cfdc04b86374973cb521a05 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 12:25:15 +0100 Subject: [PATCH 1/5] feat: Skip fields hidden from OpenAPI documentation in parameter extraction --- common.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common.go b/common.go index 79b3c16..baec2b2 100644 --- a/common.go +++ b/common.go @@ -365,6 +365,11 @@ func extractParametersFromStruct(inputType reflect.Type) []map[string]interface{ continue } + // Skip fields hidden from OpenAPI documentation + if field.Tag.Get("openapi") == "-" { + continue + } + // Process path parameters if pathTag := field.Tag.Get("path"); pathTag != "" { // Path parameters are always required regardless of type or validation tags. From 22b89b7ee4a8e3a6ec62b42faac06389472b2689 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 12:25:20 +0100 Subject: [PATCH 2/5] feat: Skip fields hidden from OpenAPI documentation in schema generation --- fiberoapi.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fiberoapi.go b/fiberoapi.go index 3c31b11..1c1ee36 100644 --- a/fiberoapi.go +++ b/fiberoapi.go @@ -384,6 +384,11 @@ func collectAllTypes(t reflect.Type, collected map[string]reflect.Type) { continue } + // Skip fields hidden from OpenAPI documentation + if field.Tag.Get("openapi") == "-" { + continue + } + // Skip fields with json:"-" tag if jsonTag := field.Tag.Get("json"); jsonTag == "-" { continue @@ -575,6 +580,11 @@ func generateSchema(t reflect.Type) map[string]interface{} { continue } + // Skip fields hidden from OpenAPI documentation + if field.Tag.Get("openapi") == "-" { + continue + } + // Skip fields that are path, query, or header parameters - they are handled separately if field.Tag.Get("path") != "" || field.Tag.Get("query") != "" || field.Tag.Get("header") != "" { continue From 9676de15c3be91c8c143318ab1a80f045056c1d9 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 12:25:25 +0100 Subject: [PATCH 3/5] feat: Add tests for hidden fields in OpenAPI schema and query parameters --- openapi_hidden_test.go | 113 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 openapi_hidden_test.go diff --git a/openapi_hidden_test.go b/openapi_hidden_test.go new file mode 100644 index 0000000..49844f1 --- /dev/null +++ b/openapi_hidden_test.go @@ -0,0 +1,113 @@ +package fiberoapi + +import ( + "encoding/json" + "io" + "net/http/httptest" + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Structs for testing openapi:"-" tag + +type HiddenFieldInput struct { + Name string `json:"name" validate:"required"` + Internal string `json:"internal" openapi:"-"` +} + +type HiddenFieldOutput struct { + ID int `json:"id"` + Name string `json:"name"` + Secret string `json:"secret" openapi:"-"` +} + +type HiddenFieldError struct { + StatusCode int `json:"statusCode"` + Message string `json:"message"` +} + +type HiddenQueryInput struct { + Name string `query:"name"` + Hidden string `query:"hidden" openapi:"-"` +} + +func TestOpenAPIHiddenField_ExcludedFromBodySchema(t *testing.T) { + app := fiber.New() + oapi := New(app) + + Post(oapi, "/items", func(c *fiber.Ctx, input *HiddenFieldInput) (*HiddenFieldOutput, *HiddenFieldError) { + return &HiddenFieldOutput{ID: 1, Name: input.Name}, nil + }, OpenAPIOptions{ + OperationID: "createItem", + Summary: "Create item", + Tags: []string{"items"}, + }) + + oapi.SetupDocs() + + req := httptest.NewRequest("GET", "/openapi.json", nil) + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + body, _ := io.ReadAll(resp.Body) + var spec map[string]interface{} + require.NoError(t, json.Unmarshal(body, &spec)) + + components := spec["components"].(map[string]interface{}) + schemas := components["schemas"].(map[string]interface{}) + + // Check input schema — "internal" field should be hidden + inputSchema := schemas["HiddenFieldInput"].(map[string]interface{}) + inputProps := inputSchema["properties"].(map[string]interface{}) + _, hasName := inputProps["name"] + _, hasInternal := inputProps["internal"] + assert.True(t, hasName, "visible field 'name' should be in schema") + assert.False(t, hasInternal, "hidden field 'internal' should NOT be in schema") + + // Check output schema — "secret" field should be hidden + outputSchema := schemas["HiddenFieldOutput"].(map[string]interface{}) + outputProps := outputSchema["properties"].(map[string]interface{}) + _, hasID := outputProps["id"] + _, hasSecret := outputProps["secret"] + assert.True(t, hasID, "visible field 'id' should be in schema") + assert.False(t, hasSecret, "hidden field 'secret' should NOT be in schema") +} + +func TestOpenAPIHiddenField_ExcludedFromQueryParams(t *testing.T) { + app := fiber.New() + oapi := New(app) + + Get(oapi, "/search", func(c *fiber.Ctx, input HiddenQueryInput) (*HiddenFieldOutput, *HiddenFieldError) { + return &HiddenFieldOutput{ID: 1, Name: input.Name}, nil + }, OpenAPIOptions{ + OperationID: "searchItems", + Summary: "Search items", + }) + + oapi.SetupDocs() + + req := httptest.NewRequest("GET", "/openapi.json", nil) + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + body, _ := io.ReadAll(resp.Body) + var spec map[string]interface{} + require.NoError(t, json.Unmarshal(body, &spec)) + + paths := spec["paths"].(map[string]interface{}) + searchPath := paths["/search"].(map[string]interface{}) + getOp := searchPath["get"].(map[string]interface{}) + + params, hasParams := getOp["parameters"].([]interface{}) + if hasParams { + for _, p := range params { + param := p.(map[string]interface{}) + assert.NotEqual(t, "hidden", param["name"], "hidden query param should NOT appear in OpenAPI parameters") + } + } +} From d29028155177b12df5994cc68d5dc7e16f4ed41f Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 12:25:29 +0100 Subject: [PATCH 4/5] feat: Add hidden fields to CreateUserInput and CreateUserOutput for OpenAPI --- _examples/simple/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/_examples/simple/main.go b/_examples/simple/main.go index d849a11..f69621b 100644 --- a/_examples/simple/main.go +++ b/_examples/simple/main.go @@ -32,6 +32,7 @@ type CreateUserInput struct { Email string `json:"email" validate:"required,email"` Age int `json:"age" validate:"required,min=13,max=120"` Bio string `json:"bio" validate:"omitempty,max=500"` + Internal string `json:"internal" openapi:"-"` // Hidden from OpenAPI docs, still parsed at runtime RequestContext ContextRequest `json:"requestContext" validate:"required,dive"` } @@ -41,6 +42,7 @@ type CreateUserOutput struct { Message string `json:"message"` Username string `json:"username"` Email string `json:"email"` + Token string `json:"token" openapi:"-"` // Hidden from OpenAPI docs, still serialized in response } type CreateUserError struct { From 92c8b6edc6d3f5355ba7e0427cba0783e4e4d7c3 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 15:17:53 +0100 Subject: [PATCH 5/5] test: Enhance OpenAPI hidden field tests for schema and query parameters --- openapi_hidden_test.go | 66 ++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/openapi_hidden_test.go b/openapi_hidden_test.go index 49844f1..714f13d 100644 --- a/openapi_hidden_test.go +++ b/openapi_hidden_test.go @@ -19,9 +19,9 @@ type HiddenFieldInput struct { } type HiddenFieldOutput struct { - ID int `json:"id"` - Name string `json:"name"` - Secret string `json:"secret" openapi:"-"` + ID int `json:"id"` + Name string `json:"name"` + Secret string `json:"secret" openapi:"-"` } type HiddenFieldError struct { @@ -51,26 +51,35 @@ func TestOpenAPIHiddenField_ExcludedFromBodySchema(t *testing.T) { req := httptest.NewRequest("GET", "/openapi.json", nil) resp, err := app.Test(req) require.NoError(t, err) - assert.Equal(t, 200, resp.StatusCode) + defer resp.Body.Close() + require.Equal(t, 200, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) - body, _ := io.ReadAll(resp.Body) var spec map[string]interface{} require.NoError(t, json.Unmarshal(body, &spec)) - components := spec["components"].(map[string]interface{}) - schemas := components["schemas"].(map[string]interface{}) + components, ok := spec["components"].(map[string]interface{}) + require.True(t, ok, "expected components section in OpenAPI spec") + schemas, ok := components["schemas"].(map[string]interface{}) + require.True(t, ok, "expected schemas section in components") // Check input schema — "internal" field should be hidden - inputSchema := schemas["HiddenFieldInput"].(map[string]interface{}) - inputProps := inputSchema["properties"].(map[string]interface{}) + inputSchema, ok := schemas["HiddenFieldInput"].(map[string]interface{}) + require.True(t, ok, "expected HiddenFieldInput schema") + inputProps, ok := inputSchema["properties"].(map[string]interface{}) + require.True(t, ok, "expected properties in HiddenFieldInput schema") _, hasName := inputProps["name"] _, hasInternal := inputProps["internal"] assert.True(t, hasName, "visible field 'name' should be in schema") assert.False(t, hasInternal, "hidden field 'internal' should NOT be in schema") // Check output schema — "secret" field should be hidden - outputSchema := schemas["HiddenFieldOutput"].(map[string]interface{}) - outputProps := outputSchema["properties"].(map[string]interface{}) + outputSchema, ok := schemas["HiddenFieldOutput"].(map[string]interface{}) + require.True(t, ok, "expected HiddenFieldOutput schema") + outputProps, ok := outputSchema["properties"].(map[string]interface{}) + require.True(t, ok, "expected properties in HiddenFieldOutput schema") _, hasID := outputProps["id"] _, hasSecret := outputProps["secret"] assert.True(t, hasID, "visible field 'id' should be in schema") @@ -93,21 +102,34 @@ func TestOpenAPIHiddenField_ExcludedFromQueryParams(t *testing.T) { req := httptest.NewRequest("GET", "/openapi.json", nil) resp, err := app.Test(req) require.NoError(t, err) - assert.Equal(t, 200, resp.StatusCode) + defer resp.Body.Close() + require.Equal(t, 200, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) - body, _ := io.ReadAll(resp.Body) var spec map[string]interface{} require.NoError(t, json.Unmarshal(body, &spec)) - paths := spec["paths"].(map[string]interface{}) - searchPath := paths["/search"].(map[string]interface{}) - getOp := searchPath["get"].(map[string]interface{}) - - params, hasParams := getOp["parameters"].([]interface{}) - if hasParams { - for _, p := range params { - param := p.(map[string]interface{}) - assert.NotEqual(t, "hidden", param["name"], "hidden query param should NOT appear in OpenAPI parameters") + paths, ok := spec["paths"].(map[string]interface{}) + require.True(t, ok, "expected paths section in OpenAPI spec") + searchPath, ok := paths["/search"].(map[string]interface{}) + require.True(t, ok, "expected /search path in spec") + getOp, ok := searchPath["get"].(map[string]interface{}) + require.True(t, ok, "expected get operation on /search") + + params, ok := getOp["parameters"].([]interface{}) + require.True(t, ok, "expected parameters array on /search get operation") + + // Verify "name" is present and "hidden" is absent + foundName := false + for _, p := range params { + param, ok := p.(map[string]interface{}) + require.True(t, ok, "expected parameter to be a map") + if param["name"] == "name" { + foundName = true } + assert.NotEqual(t, "hidden", param["name"], "hidden query param should NOT appear in OpenAPI parameters") } + assert.True(t, foundName, "visible query param 'name' should appear in OpenAPI parameters") }