From 3f28428115cff30745e355a2879841df896a5af4 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:44:14 +0100 Subject: [PATCH 1/2] feat: Support minItems and maxItems --- README.md | 27 +++++++++++++++++++++++++++ pkg/schema/schema.go | 10 ++++++++++ pkg/schema/schema_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/README.md b/README.md index 170769b..173b389 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,8 @@ foo: bar | [`$ref`](#ref) | Accepts an URI to a valid `jsonschema`. Extend the schema for the current key | Takes an URI (or relative file) | | [`minLength`](#minlength) | Minimum string length. | Takes an `integer`. Must be smaller or equal than `maxLength` (if used) | | [`maxLength`](#maxlength) | Maximum string length. | Takes an `integer`. Must be greater or equal than `minLength` (if used) | +| [`minItems`](#minItems) | Minimum length of an array. | Takes an `integer`. Must be smaller or equal than `maxItems` (if used) | +| [`maxItems`](#maxItems) | Maximum length of an array. | Takes an `integer`. Must be greater or equal than `minItems` (if used) | ## Validation & completion @@ -716,6 +718,31 @@ The value must be an integer greater than zero and defines the maximum length of namespace: foo ``` +#### `minItems` + +The value must be an integer greater than zero and defines the minimum length of an array value. + +```yaml +# @schema +# minItems: 1 +# @schema +namespace: + - foo +``` + +#### `maxItems` + +The value must be an integer greater than zero and defines the maximum length of an array value. + +```yaml +# @schema +# maxItems: 2 +# @schema +namespace: + - foo + - bar +``` + #### `$ref` The value must be an URI or relative file. diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index d291136..9e25df8 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -247,6 +247,8 @@ type Schema struct { CustomAnnotations map[string]interface{} `yaml:"-" json:",omitempty"` MinLength *int `yaml:"minLength,omitempty" json:"minLength,omitempty"` MaxLength *int `yaml:"maxLength,omitempty" json:"maxLength,omitempty"` + MinItems *int `yaml:"minItems,omitempty" json:"minItems,omitempty"` + MaxItems *int `yaml:"maxItems,omitempty" json:"maxItems,omitempty"` } func NewSchema(schemaType string) *Schema { @@ -413,6 +415,14 @@ func (s Schema) Validate() error { return fmt.Errorf("cant use items if type is %s. Use type=array", s.Type) } + if (s.MinItems != nil || s.MaxItems != nil) && !s.Type.IsEmpty() && !s.Type.Matches("array") { + return fmt.Errorf("cant use minItems or maxItems if type is %s. Use type=array", s.Type) + } + + if (s.MinItems != nil && s.MaxItems != nil) && *s.MaxItems < *s.MinItems { + return errors.New("minItems cant be greater than maxItems") + } + if s.Const != nil && !s.Type.IsEmpty() { return errors.New("if your are using const, you can't use type") } diff --git a/pkg/schema/schema_test.go b/pkg/schema/schema_test.go index 5a28712..dc33fc5 100644 --- a/pkg/schema/schema_test.go +++ b/pkg/schema/schema_test.go @@ -147,6 +147,30 @@ func TestValidate(t *testing.T) { # @schema`, expectedValid: true, }, + { + comment: ` +# @schema +# minItems: 1 +# maxItems: 2 +# @schema`, + expectedValid: true, + }, + { + comment: ` +# @schema +# minItems: 2 +# maxItems: 1 +# @schema`, + expectedValid: false, + }, + { + comment: ` +# @schema +# type: string +# minItems: 1 +# @schema`, + expectedValid: false, + }, } for _, test := range tests { From 21ff86a188a510068bcb51d3283f9b5af26309c7 Mon Sep 17 00:00:00 2001 From: dadav <33197631+dadav@users.noreply.github.com> Date: Sat, 7 Dec 2024 21:44:44 +0100 Subject: [PATCH 2/2] fix: Check custom annotations more dynamically --- pkg/schema/schema.go | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 9e25df8..c44cdfd 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "reflect" "regexp" "slices" "strconv" @@ -262,6 +263,17 @@ func NewSchema(schemaType string) *Schema { } } +func (s Schema) getJsonKeys() []string { + result := []string{} + t := reflect.TypeOf(s) + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + result = append(result, field.Tag.Get("json")) + } + return result +} + // UnmarshalYAML custom unmarshal method func (s *Schema) UnmarshalYAML(node *yaml.Node) error { // Create an alias type to avoid recursion @@ -278,32 +290,27 @@ func (s *Schema) UnmarshalYAML(node *yaml.Node) error { // Initialize CustomAnnotations map alias.CustomAnnotations = make(map[string]interface{}) + knownKeys := s.getJsonKeys() + // Iterate through all node fields for i := 0; i < len(node.Content)-1; i += 2 { keyNode := node.Content[i] valueNode := node.Content[i+1] key := keyNode.Value - // Check if the key is a known field - switch key { - case "additionalProperties", "default", "then", "patternProperties", "properties", - "if", "minimum", "multipleOf", "exclusiveMaximum", "items", "exclusiveMinimum", - "maximum", "else", "pattern", "const", "$ref", "$schema", "$id", "format", - "description", "title", "type", "anyOf", "allOf", "oneOf", "requiredProperties", - "examples", "enum", "deprecated", "required", "not": - // Skip known fields + if slices.Contains(knownKeys, key) { continue - default: - // Unmarshal unknown fields into the CustomAnnotations map - if !strings.HasPrefix(key, CustomAnnotationPrefix) { - continue - } - var value interface{} - if err := valueNode.Decode(&value); err != nil { - return err - } - alias.CustomAnnotations[key] = value } + + // Unmarshal unknown fields into the CustomAnnotations map + if !strings.HasPrefix(key, CustomAnnotationPrefix) { + continue + } + var value interface{} + if err := valueNode.Decode(&value); err != nil { + return err + } + alias.CustomAnnotations[key] = value } // Copy alias to the main struct