Repository: getkin/kin-openapi
Pull request #1068 (2025-05-08 07:56:51)

openapi3: replace bigfloat with decimal128 to fix rounding errors during validation
Fixes https://github.com/getkin/kin-openapi/issues/817

Following the suggestion here https://github.com/getkin/kin-openapi/issues/817#issuecomment-1823392667


Patch:
diff --git a/go.mod b/go.mod
index 975848f..6f53d9f 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@ require (
 	github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90
 	github.com/perimeterx/marshmallow v1.1.5
 	github.com/stretchr/testify v1.9.0
+	github.com/woodsbury/decimal128 v1.3.0
 )
 
 require (
diff --git a/go.sum b/go.sum
index 28e383b..34001ef 100644
--- a/go.sum
+++ b/go.sum
@@ -32,6 +32,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
+github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
+github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 75bcf12..5741067 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -17,6 +17,7 @@ import (
 
 	"github.com/go-openapi/jsonpointer"
 	"github.com/mohae/deepcopy"
+	"github.com/woodsbury/decimal128"
 )
 
 const (
@@ -1643,7 +1644,10 @@ func (schema *Schema) visitJSONNumber(settings *schemaValidationSettings, value
 	if v := schema.MultipleOf; v != nil {
 		// "A numeric instance is valid only if division by this keyword's
 		//    value results in an integer."
-		if bigFloat := big.NewFloat(value / *v); !bigFloat.IsInt() {
+		numParsed, _ := decimal128.Parse(fmt.Sprintf("%.10f", value))
+		denParsed, _ := decimal128.Parse(fmt.Sprintf("%.10f", *v))
+		_, remainder := numParsed.QuoRem(denParsed)
+		if !remainder.IsZero() {
 			if settings.failfast {
 				return errSchema
 			}


Test patch:
diff --git a/openapi3/schema_test.go b/openapi3/schema_test.go
index 4332983..afacd31 100644
--- a/openapi3/schema_test.go
+++ b/openapi3/schema_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/base64"
 	"encoding/json"
+	"fmt"
 	"math"
 	"reflect"
 	"strings"
@@ -1490,3 +1491,41 @@ func TestIssue751(t *testing.T) {
 	require.NoError(t, schema.VisitJSON(validData))
 	require.ErrorContains(t, schema.VisitJSON(invalidData), "duplicate items found")
 }
+
+func TestIssue817(t *testing.T) {
+	max := 999999999.99
+	min := -999999999.99
+	mulOf := 0.01
+	schema := &Schema{
+		Type:       &Types{"number"},
+		Max:        &max,
+		Min:        &min,
+		MultipleOf: &mulOf,
+	}
+	validData := []float64{2.07, 8.1, 19628.87, 323.39, 40428.2, 1.13}
+	for _, data := range validData {
+		require.NoError(t, schema.VisitJSON(data))
+	}
+
+	invalidData := []struct {
+		mulfOf float64
+		data   float64
+	}{
+		{
+			mulfOf: 0.01,
+			data:   0.005,
+		},
+		{
+			mulfOf: 3.0,
+			data:   5.0,
+		},
+		{
+			mulfOf: 5.0,
+			data:   2.0,
+		},
+	}
+	for _, data := range invalidData {
+		schema.MultipleOf = &data.mulfOf
+		require.ErrorContains(t, schema.VisitJSON(data.data), fmt.Sprintf("number must be a multiple of %+v", data.mulfOf))
+	}
+}