From dd7253d8d84096ed1dc7a4cbf908f9fa751b19d3 Mon Sep 17 00:00:00 2001 From: EwenQuim Date: Wed, 6 Mar 2024 19:56:02 +0100 Subject: [PATCH] OpenAPI: parses the 'validate' struct tag --- .../full-app-gourmet/controller/ingredient.go | 2 +- openapi3/openapi3.go | 5 ++ openapi3/parse_validate.go | 29 +++++++++ openapi3/parse_validate_test.go | 62 +++++++++++++++++++ openapi3/to_schema.go | 6 +- 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 openapi3/parse_validate.go create mode 100644 openapi3/parse_validate_test.go diff --git a/examples/full-app-gourmet/controller/ingredient.go b/examples/full-app-gourmet/controller/ingredient.go index 93552e8..cf7c89a 100644 --- a/examples/full-app-gourmet/controller/ingredient.go +++ b/examples/full-app-gourmet/controller/ingredient.go @@ -28,7 +28,7 @@ func (rs ingredientRessource) getAllIngredients(c fuego.ContextNoBody) ([]store. } type CreateIngredient struct { - Name string `json:"name" validate:"required,min=3,max=20"` + Name string `json:"name" validate:"required,min=3,max=20" example:"Tomato"` Description string `json:"description"` } diff --git a/openapi3/openapi3.go b/openapi3/openapi3.go index 3128748..502531c 100644 --- a/openapi3/openapi3.go +++ b/openapi3/openapi3.go @@ -100,9 +100,14 @@ type Schema struct { Type string `json:"type,omitempty" yaml:"type"` Format string `json:"format,omitempty" yaml:"format,omitempty"` Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Nullable bool `json:"nullable,omitempty" yaml:"nullable,omitempty"` Example string `json:"example,omitempty" yaml:"example,omitempty"` Properties map[string]Schema `json:"properties,omitempty" yaml:"properties,omitempty"` Items *Schema `json:"items,omitempty" yaml:"items,omitempty"` + MinLength int `json:"minLength,omitempty" yaml:"minLength,omitempty"` + MaxLength int `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + Minimum int `json:"minimum,omitempty" yaml:"minimum,omitempty"` + Maximum int `json:"maximum,omitempty" yaml:"maximum,omitempty"` Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` } diff --git a/openapi3/parse_validate.go b/openapi3/parse_validate.go new file mode 100644 index 0000000..7217482 --- /dev/null +++ b/openapi3/parse_validate.go @@ -0,0 +1,29 @@ +package openapi3 + +import ( + "strconv" + "strings" +) + +// parses the "validate" tag used by go-playground/validator and sets the schema accordingly +func parseValidate(s *Schema, tag string) { + for _, item := range strings.Split(tag, ",") { + if strings.Contains(item, "min=") { + min := strings.Split(item, "min=")[1] + if s.Type == "integer" || s.Type == "number" { + s.Minimum, _ = strconv.Atoi(min) + } else if s.Type == "string" { + s.MinLength, _ = strconv.Atoi(min) + } + } + + if strings.Contains(item, "max=") { + max := strings.Split(item, "max=")[1] + if s.Type == "integer" || s.Type == "number" { + s.Maximum, _ = strconv.Atoi(max) + } else if s.Type == "string" { + s.MaxLength, _ = strconv.Atoi(max) + } + } + } +} diff --git a/openapi3/parse_validate_test.go b/openapi3/parse_validate_test.go new file mode 100644 index 0000000..6fdd15c --- /dev/null +++ b/openapi3/parse_validate_test.go @@ -0,0 +1,62 @@ +package openapi3 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseValidate(t *testing.T) { + t.Run("empty", func(t *testing.T) { + s := &Schema{} + parseValidate(s, "") + }) + + t.Run("required", func(t *testing.T) { + t.Skip("TODO") + s := &Schema{} + parseValidate(s, "required") + require.Equal(t, true, s.Required) + }) + + t.Run("min for int", func(t *testing.T) { + s := &Schema{ + Type: "integer", + } + parseValidate(s, "min=10") + require.Equal(t, 10, s.Minimum) + }) + + t.Run("max for int", func(t *testing.T) { + s := &Schema{ + Type: "integer", + } + parseValidate(s, "max=10") + require.Equal(t, 10, s.Maximum) + }) + + t.Run("min for string", func(t *testing.T) { + s := &Schema{ + Type: "string", + } + parseValidate(s, "min=10") + require.Equal(t, 10, s.MinLength) + }) + + t.Run("max for string", func(t *testing.T) { + s := &Schema{ + Type: "string", + } + parseValidate(s, "max=10") + require.Equal(t, 10, s.MaxLength) + }) + + t.Run("multiple", func(t *testing.T) { + s := &Schema{ + Type: "string", + } + parseValidate(s, "required,min=10,max=20") + require.Equal(t, 10, s.MinLength) + require.Equal(t, 20, s.MaxLength) + }) +} diff --git a/openapi3/to_schema.go b/openapi3/to_schema.go index 47abf93..10f4601 100644 --- a/openapi3/to_schema.go +++ b/openapi3/to_schema.go @@ -72,11 +72,15 @@ func ToSchema(v any) *Schema { if strings.Contains(fieldType.Tag.Get("validate"), "required") { s.Required = append(s.Required, fieldName) } - s.Properties[fieldName] = Schema{ + + fieldSchema := Schema{ Type: fieldTypeType, Example: fieldType.Tag.Get("example"), Format: format, } + parseValidate(&fieldSchema, fieldType.Tag.Get("validate")) + s.Properties[fieldName] = fieldSchema + } } }