diff --git a/pkg/cloudcommon/validators/errors.go b/pkg/cloudcommon/validators/errors.go index e91d036a44..c5a48f6a3d 100644 --- a/pkg/cloudcommon/validators/errors.go +++ b/pkg/cloudcommon/validators/errors.go @@ -33,6 +33,7 @@ const ( ERR_MISSING_KEY ERR_INVALID_TYPE ERR_INVALID_CHOICE + ERR_INVALID_LENGTH ERR_NOT_IN_RANGE ERR_INVALID_VALUE ERR_MODEL_MANAGER @@ -45,6 +46,7 @@ var errTypeToString = map[ErrType]string{ ERR_MISSING_KEY: "Missing key error", ERR_INVALID_TYPE: "Invalid type error", ERR_INVALID_CHOICE: "Invalid choice error", + ERR_INVALID_LENGTH: "Invalid length error", ERR_NOT_IN_RANGE: "Not in range error", ERR_INVALID_VALUE: "Invalid value error", ERR_MODEL_MANAGER: "Model manager error", @@ -85,6 +87,14 @@ func newInvalidChoiceError(key string, choices choices.Choices, choice string) e return newError(ERR_INVALID_CHOICE, "invalid %q, want %s, got %s", key, choices, choice) } +func newStringTooShortError(key string, got, want int) error { + return newError(ERR_INVALID_LENGTH, "%q too short, got %d, min %s", key, got, want) +} + +func newStringTooLongError(key string, got, want int) error { + return newError(ERR_INVALID_LENGTH, "%q too long, got %d, max %s", key, got, want) +} + func newNotInRangeError(key string, value, lower, upper int64) error { return newError(ERR_NOT_IN_RANGE, "invalid %q: %d, want [%d,%d]", key, value, lower, upper) } diff --git a/pkg/cloudcommon/validators/validators_string.go b/pkg/cloudcommon/validators/validators_string.go new file mode 100644 index 0000000000..d7411e9baa --- /dev/null +++ b/pkg/cloudcommon/validators/validators_string.go @@ -0,0 +1,81 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validators + +import ( + "yunion.io/x/jsonutils" +) + +type ValidatorStringLen struct { + Validator + Value string + minLen int + maxLen int +} + +func NewStringLenRangeValidator(key string, minLen, maxLen int) *ValidatorStringLen { + v := &ValidatorStringLen{ + Validator: Validator{Key: key}, + minLen: minLen, + maxLen: maxLen, + } + v.SetParent(v) + return v +} + +func NewMinStringLenValidator(key string, minLen int) *ValidatorStringLen { + return NewStringLenRangeValidator(key, minLen, -1) +} + +func NewMaxStringLenValidator(key string, maxLen int) *ValidatorStringLen { + return NewStringLenRangeValidator(key, -1, maxLen) +} + +func NewStringNonEmptyValidator(key string) *ValidatorStringLen { + return NewMinStringLenValidator(key, 1) +} + +func (v *ValidatorStringLen) Default(s string) IValidator { + if v.minLen >= 0 && len(s) < v.minLen { + panic("invalid default string: shorter than validator requirement") + } + if v.maxLen >= 0 && len(s) > v.maxLen { + panic("invalid default string: shorter than validator requirement") + } + return v.Validator.Default(s) +} + +func (v *ValidatorStringLen) getValue() interface{} { + return v.Value +} + +func (v *ValidatorStringLen) Validate(data *jsonutils.JSONDict) error { + if err, isSet := v.Validator.validateEx(data); err != nil || !isSet { + return err + } + s, err := v.value.GetString() + if err != nil { + return newGeneralError(v.Key, err) + } + if v.minLen >= 0 && len(s) < v.minLen { + return newStringTooShortError(v.Key, len(s), v.minLen) + } + if v.maxLen >= 0 && len(s) > v.maxLen { + return newStringTooLongError(v.Key, len(s), v.maxLen) + } + data.Set(v.Key, jsonutils.NewString(s)) + v.Value = s + return nil +} diff --git a/pkg/cloudcommon/validators/validators_string_test.go b/pkg/cloudcommon/validators/validators_string_test.go new file mode 100644 index 0000000000..3fff30a1bd --- /dev/null +++ b/pkg/cloudcommon/validators/validators_string_test.go @@ -0,0 +1,99 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validators + +import ( + "testing" +) + +func TestStringLenValidator(t *testing.T) { + cases := []*C{ + { + Name: "missing non-optional", + In: `{}`, + Out: `{}`, + Optional: false, + Err: ERR_MISSING_KEY, + ValueWant: "", + }, + { + Name: "missing optional", + In: `{}`, + Out: `{}`, + Optional: true, + ValueWant: "", + }, + { + Name: "missing with default", + In: `{}`, + Out: `{s: "12345"}`, + Default: "12345", + ValueWant: "12345", + }, + { + Name: "stringified", + In: `{"s": 100}`, + Out: `{s: "100"}`, + ValueWant: "100", + }, + { + Name: "stringified too long", + In: `{"s": 9876543210}`, + Out: `{"s": 9876543210}`, + Err: ERR_INVALID_LENGTH, + ValueWant: "", + }, + { + Name: "stringified too short", + In: `{"s": 0}`, + Out: `{"s": 0}`, + Err: ERR_INVALID_LENGTH, + ValueWant: "", + }, + { + Name: "good length", + In: `{"s": "abcde"}`, + Out: `{"s": "abcde"}`, + ValueWant: "abcde", + }, + { + Name: "bad length (too short)", + In: `{"s": "0"}`, + Out: `{"s": "0"}`, + Err: ERR_INVALID_LENGTH, + ValueWant: "", + }, + { + Name: "bad length (too long)", + In: `{"s": "9876543210"}`, + Out: `{"s": "9876543210"}`, + Err: ERR_INVALID_LENGTH, + ValueWant: "", + }, + } + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + v := NewStringLenRangeValidator("s", 2, 5) + if c.Default != nil { + s := c.Default.(string) + v.Default(s) + } + if c.Optional { + v.Optional(true) + } + testS(t, v, c) + }) + } +}