Skip to content

Commit b6c8ca4

Browse files
committed
openapi3filter: fix validation of absent optional properties in form-urlencoded requests
When decoding application/x-www-form-urlencoded request bodies, absent optional properties were incorrectly treated as null values and validated against the schema, causing "Value is not nullable" errors. Now, absent properties are skipped entirely during validation, which is the correct behavior for optional fields.
1 parent 4358c4a commit b6c8ca4

File tree

2 files changed

+103
-2
lines changed

2 files changed

+103
-2
lines changed

openapi3filter/issue1110_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package openapi3filter
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/getkin/kin-openapi/openapi3"
11+
"github.com/getkin/kin-openapi/routers/gorillamux"
12+
)
13+
14+
func TestIssue1110(t *testing.T) {
15+
// Test case: POST with application/x-www-form-urlencoded
16+
// Schema has two optional properties (not required)
17+
// Sending only one param should be valid since no fields are required
18+
spec := `
19+
openapi: 3.0.3
20+
info:
21+
version: 1.0.0
22+
title: Test API
23+
paths:
24+
/test:
25+
post:
26+
summary: Test endpoint with optional form parameters
27+
requestBody:
28+
content:
29+
application/x-www-form-urlencoded:
30+
schema:
31+
type: object
32+
properties:
33+
param1:
34+
type: string
35+
param2:
36+
type: string
37+
responses:
38+
'200':
39+
description: OK
40+
`[1:]
41+
42+
loader := openapi3.NewLoader()
43+
ctx := loader.Context
44+
45+
doc, err := loader.LoadFromData([]byte(spec))
46+
require.NoError(t, err)
47+
48+
err = doc.Validate(ctx)
49+
require.NoError(t, err)
50+
51+
router, err := gorillamux.NewRouter(doc)
52+
require.NoError(t, err)
53+
54+
for _, testcase := range []struct {
55+
name string
56+
data string
57+
shouldFail bool
58+
}{
59+
{
60+
name: "empty body should be valid (no required fields)",
61+
data: "",
62+
shouldFail: false,
63+
},
64+
{
65+
name: "only param1 present, param2 absent - should be valid",
66+
data: "param1=value1",
67+
shouldFail: false,
68+
},
69+
{
70+
name: "only param2 present, param1 absent - should be valid",
71+
data: "param2=value2",
72+
shouldFail: false,
73+
},
74+
{
75+
name: "both params present - should be valid",
76+
data: "param1=value1&param2=value2",
77+
shouldFail: false,
78+
},
79+
} {
80+
t.Run(testcase.name, func(t *testing.T) {
81+
req, err := http.NewRequest("POST", "/test", strings.NewReader(testcase.data))
82+
require.NoError(t, err)
83+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
84+
85+
route, pathParams, err := router.FindRoute(req)
86+
require.NoError(t, err)
87+
88+
validationInput := &RequestValidationInput{
89+
Request: req,
90+
PathParams: pathParams,
91+
Route: route,
92+
}
93+
err = ValidateRequest(ctx, validationInput)
94+
if testcase.shouldFail {
95+
require.Error(t, err, "This test should fail: "+testcase.name)
96+
} else {
97+
require.NoError(t, err, "This test should pass: "+testcase.name)
98+
}
99+
})
100+
}
101+
}

openapi3filter/req_resp_decoder.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,8 +1364,8 @@ func decodeSchemaConstructs(dec *urlValuesDecoder, schemas []*openapi3.SchemaRef
13641364
}
13651365

13661366
for name, prop := range schemaRef.Value.Properties {
1367-
value, _, err := decodeProperty(dec, name, prop, encFn)
1368-
if err != nil {
1367+
value, present, err := decodeProperty(dec, name, prop, encFn)
1368+
if err != nil || !present {
13691369
continue
13701370
}
13711371
if existingValue, exists := obj[name]; exists && !isEqual(existingValue, value) {

0 commit comments

Comments
 (0)