Repository: getkin/kin-openapi (go)

Issue:
Bug: Default values still count as properties even if setting them is disabled.
https://github.com/getkin/kin-openapi/blob/ecb06bc5c05b010b9c1322486ff74f2178f7927c/openapi3/schema.go#L1774-L1816

In `visitJSONObject`, if `value[propName]` is `nil`, it is assigned with the default value regardless of the setting (line 1794).

This counts towards `lenValue` (line 1813) and can falsely pass validations like `minProperties` that way.

I will add a PR to fix this bug.

Pull request description:
openapi3: fix default values count even when disabled (#767)
fixes #767 

Patch:
diff --git a/openapi3/schema.go b/openapi3/schema.go
index b6be8c1..4bfbca0 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -1789,12 +1789,10 @@ func (schema *Schema) visitJSONObject(settings *schemaValidationSettings, value
 			reqRO := settings.asreq && propSchema.Value.ReadOnly && !settings.readOnlyValidationDisabled
 			repWO := settings.asrep && propSchema.Value.WriteOnly && !settings.writeOnlyValidationDisabled
 
-			if value[propName] == nil {
-				if dlft := propSchema.Value.Default; dlft != nil && !reqRO && !repWO {
-					value[propName] = dlft
-					if f := settings.defaultsSet; f != nil {
-						settings.onceSettingDefaults.Do(f)
-					}
+			if f := settings.defaultsSet; f != nil && value[propName] == nil {
+				if dflt := propSchema.Value.Default; dflt != nil && !reqRO && !repWO {
+					value[propName] = dflt
+					settings.onceSettingDefaults.Do(f)
 				}
 			}
 


Test patch:
diff --git a/openapi3/issue767_test.go b/openapi3/issue767_test.go
new file mode 100644
index 0000000..d498877
--- /dev/null
+++ b/openapi3/issue767_test.go
@@ -0,0 +1,90 @@
+package openapi3_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func TestIssue767(t *testing.T) {
+	t.Parallel()
+
+	tests := [...]struct {
+		name     string
+		schema   *openapi3.Schema
+		value    map[string]interface{}
+		opts     []openapi3.SchemaValidationOption
+		checkErr require.ErrorAssertionFunc
+	}{
+		{
+			name: "default values disabled should fail with minProps 1",
+			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
+				"foo": {Type: "boolean", Default: true}}).WithMinProperties(1),
+			value: map[string]interface{}{},
+			opts: []openapi3.SchemaValidationOption{
+				openapi3.VisitAsRequest(),
+			},
+			checkErr: require.Error,
+		},
+		{
+			name: "default values enabled should pass with minProps 1",
+			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
+				"foo": {Type: "boolean", Default: true}}).WithMinProperties(1),
+			value: map[string]interface{}{},
+			opts: []openapi3.SchemaValidationOption{
+				openapi3.VisitAsRequest(),
+				openapi3.DefaultsSet(func() {}),
+			},
+			checkErr: require.NoError,
+		},
+		{
+			name: "default values enabled should pass with minProps 2",
+			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
+				"foo": {Type: "boolean", Default: true},
+				"bar": {Type: "boolean"},
+			}).WithMinProperties(2),
+			value: map[string]interface{}{"bar": false},
+			opts: []openapi3.SchemaValidationOption{
+				openapi3.VisitAsRequest(),
+				openapi3.DefaultsSet(func() {}),
+			},
+			checkErr: require.NoError,
+		},
+		{
+			name: "default values enabled should fail with maxProps 1",
+			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
+				"foo": {Type: "boolean", Default: true},
+				"bar": {Type: "boolean"},
+			}).WithMaxProperties(1),
+			value: map[string]interface{}{"bar": false},
+			opts: []openapi3.SchemaValidationOption{
+				openapi3.VisitAsRequest(),
+				openapi3.DefaultsSet(func() {}),
+			},
+			checkErr: require.Error,
+		},
+		{
+			name: "default values disabled should pass with maxProps 1",
+			schema: openapi3.NewSchema().WithProperties(map[string]*openapi3.Schema{
+				"foo": {Type: "boolean", Default: true},
+				"bar": {Type: "boolean"},
+			}).WithMaxProperties(1),
+			value: map[string]interface{}{"bar": false},
+			opts: []openapi3.SchemaValidationOption{
+				openapi3.VisitAsRequest(),
+			},
+			checkErr: require.NoError,
+		},
+	}
+
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
+			err := test.schema.VisitJSON(test.value, test.opts...)
+			test.checkErr(t, err)
+		})
+	}
+}