From 58351939004ee087e21f1837822b5bafb9b27653 Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Fri, 10 Nov 2023 14:55:22 +0200 Subject: [PATCH] [#3735] fixed `text` field min/max validators to properly count multi-byte characters --- CHANGELOG.md | 2 ++ forms/validators/record_data.go | 7 ++++-- forms/validators/record_data_test.go | 34 +++++++++++++++++++++++----- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7652b05..47b2e897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ - Fixed TinyMCE source code viewer textarea styles ([#3715](https://github.com/pocketbase/pocketbase/issues/3715)). +- Fixed `text` field min/max validators to properly count multi-byte characters ([#3735](https://github.com/pocketbase/pocketbase/issues/3735)). + ## v0.19.3 diff --git a/forms/validators/record_data.go b/forms/validators/record_data.go index cf2d3cc0..8852b47c 100644 --- a/forms/validators/record_data.go +++ b/forms/validators/record_data.go @@ -132,11 +132,14 @@ func (validator *RecordDataValidator) checkTextValue(field *schema.SchemaField, options, _ := field.Options.(*schema.TextOptions) - if options.Min != nil && len(val) < *options.Min { + // note: casted to []rune to count multi-byte chars as one + length := len([]rune(val)) + + if options.Min != nil && length < *options.Min { return validation.NewError("validation_min_text_constraint", fmt.Sprintf("Must be at least %d character(s)", *options.Min)) } - if options.Max != nil && len(val) > *options.Max { + if options.Max != nil && length > *options.Max { return validation.NewError("validation_max_text_constraint", fmt.Sprintf("Must be less than %d character(s)", *options.Max)) } diff --git a/forms/validators/record_data_test.go b/forms/validators/record_data_test.go index 2de37d56..c511dc30 100644 --- a/forms/validators/record_data_test.go +++ b/forms/validators/record_data_test.go @@ -63,15 +63,17 @@ func TestRecordDataValidatorValidateText(t *testing.T) { Name: "field2", Required: true, Type: schema.FieldTypeText, + Options: &schema.TextOptions{ + Pattern: pattern, + }, }, &schema.SchemaField{ Name: "field3", Unique: true, Type: schema.FieldTypeText, Options: &schema.TextOptions{ - Min: &min, - Max: &max, - Pattern: pattern, + Min: &min, + Max: &max, }, }, ) @@ -109,6 +111,16 @@ func TestRecordDataValidatorValidateText(t *testing.T) { nil, []string{"field3"}, }, + { + "(text) check min constraint with multi-bytes char", + map[string]any{ + "field1": "test", + "field2": "test", + "field3": "𝌆", // 4 bytes should be counted as 1 char + }, + nil, + []string{"field3"}, + }, { "(text) check max constraint", map[string]any{ @@ -119,15 +131,25 @@ func TestRecordDataValidatorValidateText(t *testing.T) { nil, []string{"field3"}, }, + { + "(text) check max constraint with multi-bytes chars", + map[string]any{ + "field1": "test", + "field2": "test", + "field3": strings.Repeat("𝌆", max), // shouldn't exceed the max limit even though max*4bytes chars are used + }, + nil, + []string{}, + }, { "(text) check pattern constraint", map[string]any{ "field1": nil, - "field2": "test", - "field3": "test!", + "field2": "test!", + "field3": "test", }, nil, - []string{"field3"}, + []string{"field2"}, }, { "(text) valid data (only required)",