initial v0.8 pre-release

This commit is contained in:
Gani Georgiev
2022-10-30 10:28:14 +02:00
parent 9cbb2e750e
commit 90dba45d7c
388 changed files with 21580 additions and 13603 deletions
+11 -3
View File
@@ -1,7 +1,6 @@
package validators
import (
"encoding/binary"
"fmt"
"strings"
@@ -22,7 +21,7 @@ func UploadedFileSize(maxBytes int) validation.RuleFunc {
return nil // nothing to validate
}
if binary.Size(v.Bytes()) > maxBytes {
if int(v.Header().Size) > maxBytes {
return validation.NewError("validation_file_size_limit", fmt.Sprintf("Maximum allowed file size is %v bytes.", maxBytes))
}
@@ -47,7 +46,16 @@ func UploadedFileMimeType(validTypes []string) validation.RuleFunc {
return validation.NewError("validation_invalid_mime_type", "Unsupported file type.")
}
filetype := mimetype.Detect(v.Bytes())
f, err := v.Header().Open()
if err != nil {
return validation.NewError("validation_invalid_mime_type", "Unsupported file type.")
}
defer f.Close()
filetype, err := mimetype.DetectReader(f)
if err != nil {
return validation.NewError("validation_invalid_mime_type", "Unsupported file type.")
}
for _, t := range validTypes {
if filetype.Is(t) {
+7 -37
View File
@@ -28,7 +28,7 @@ var requiredErr = validation.NewError("validation_required", "Missing required v
func NewRecordDataValidator(
dao *daos.Dao,
record *models.Record,
uploadedFiles []*rest.UploadedFile,
uploadedFiles map[string][]*rest.UploadedFile,
) *RecordDataValidator {
return &RecordDataValidator{
dao: dao,
@@ -42,7 +42,7 @@ func NewRecordDataValidator(
type RecordDataValidator struct {
dao *daos.Dao
record *models.Record
uploadedFiles []*rest.UploadedFile
uploadedFiles map[string][]*rest.UploadedFile
}
// Validate validates the provided `data` by checking it against
@@ -88,7 +88,7 @@ func (validator *RecordDataValidator) Validate(data map[string]any) error {
// check unique constraint
if field.Unique && !validator.dao.IsRecordValueUnique(
validator.record.Collection(),
validator.record.Collection().Id,
key,
value,
validator.record.GetId(),
@@ -127,8 +127,6 @@ func (validator *RecordDataValidator) checkFieldValue(field *schema.SchemaField,
return validator.checkFileValue(field, value)
case schema.FieldTypeRelation:
return validator.checkRelationValue(field, value)
case schema.FieldTypeUser:
return validator.checkUserValue(field, value)
}
return nil
@@ -316,8 +314,8 @@ func (validator *RecordDataValidator) checkFileValue(field *schema.SchemaField,
}
// extract the uploaded files
files := make([]*rest.UploadedFile, 0, len(validator.uploadedFiles))
for _, file := range validator.uploadedFiles {
files := make([]*rest.UploadedFile, 0, len(validator.uploadedFiles[field.Name]))
for _, file := range validator.uploadedFiles[field.Name] {
if list.ExistInSlice(file.Name(), names) {
files = append(files, file)
}
@@ -351,8 +349,8 @@ func (validator *RecordDataValidator) checkRelationValue(field *schema.SchemaFie
options, _ := field.Options.(*schema.RelationOptions)
if len(ids) > options.MaxSelect {
return validation.NewError("validation_too_many_values", fmt.Sprintf("Select no more than %d", options.MaxSelect))
if options.MaxSelect != nil && len(ids) > *options.MaxSelect {
return validation.NewError("validation_too_many_values", fmt.Sprintf("Select no more than %d", *options.MaxSelect))
}
// check if the related records exist
@@ -374,31 +372,3 @@ func (validator *RecordDataValidator) checkRelationValue(field *schema.SchemaFie
return nil
}
func (validator *RecordDataValidator) checkUserValue(field *schema.SchemaField, value any) error {
ids := list.ToUniqueStringSlice(value)
if len(ids) == 0 {
if field.Required {
return requiredErr
}
return nil // nothing to check
}
options, _ := field.Options.(*schema.UserOptions)
if len(ids) > options.MaxSelect {
return validation.NewError("validation_too_many_values", fmt.Sprintf("Select no more than %d", options.MaxSelect))
}
// check if the related users exist
var total int
validator.dao.UserQuery().
Select("count(*)").
AndWhere(dbx.In("id", list.ToInterfaceSlice(ids)...)).
Row(&total)
if total != len(ids) {
return validation.NewError("validation_missing_users", "Failed to fetch all users with the provided ids")
}
return nil
}
+58 -171
View File
@@ -20,7 +20,7 @@ import (
type testDataFieldScenario struct {
name string
data map[string]any
files []*rest.UploadedFile
files map[string][]*rest.UploadedFile
expectedErrors []string
}
@@ -28,7 +28,7 @@ func TestRecordDataValidatorEmptyAndUnknown(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
collection, _ := app.Dao().FindCollectionByNameOrId("demo")
collection, _ := app.Dao().FindCollectionByNameOrId("demo2")
record := models.NewRecord(collection)
validator := validators.NewRecordDataValidator(app.Dao(), record, nil)
@@ -80,9 +80,9 @@ func TestRecordDataValidatorValidateText(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", "test")
dummy.SetDataValue("field2", "test")
dummy.SetDataValue("field3", "test")
dummy.Set("field1", "test")
dummy.Set("field2", "test")
dummy.Set("field3", "test")
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -196,9 +196,9 @@ func TestRecordDataValidatorValidateNumber(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", 123)
dummy.SetDataValue("field2", 123)
dummy.SetDataValue("field3", 123)
dummy.Set("field1", 123)
dummy.Set("field2", 123)
dummy.Set("field3", 123)
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -307,9 +307,9 @@ func TestRecordDataValidatorValidateBool(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", false)
dummy.SetDataValue("field2", true)
dummy.SetDataValue("field3", true)
dummy.Set("field1", false)
dummy.Set("field2", true)
dummy.Set("field3", true)
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -403,9 +403,9 @@ func TestRecordDataValidatorValidateEmail(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", "test@demo.com")
dummy.SetDataValue("field2", "test@test.com")
dummy.SetDataValue("field3", "test@example.com")
dummy.Set("field1", "test@demo.com")
dummy.Set("field2", "test@test.com")
dummy.Set("field3", "test@example.com")
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -519,9 +519,9 @@ func TestRecordDataValidatorValidateUrl(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", "http://demo.com")
dummy.SetDataValue("field2", "http://test.com")
dummy.SetDataValue("field3", "http://example.com")
dummy.Set("field1", "http://demo.com")
dummy.Set("field2", "http://test.com")
dummy.Set("field3", "http://example.com")
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -647,9 +647,9 @@ func TestRecordDataValidatorValidateDate(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", "2022-01-01 01:01:01")
dummy.SetDataValue("field2", "2029-01-01 01:01:01.123")
dummy.SetDataValue("field3", "2029-01-01 01:01:01.123")
dummy.Set("field1", "2022-01-01 01:01:01")
dummy.Set("field2", "2029-01-01 01:01:01.123")
dummy.Set("field3", "2029-01-01 01:01:01.123")
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -779,9 +779,9 @@ func TestRecordDataValidatorValidateSelect(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", "a")
dummy.SetDataValue("field2", []string{"a", "b"})
dummy.SetDataValue("field3", []string{"a", "b", "c"})
dummy.Set("field1", "a")
dummy.Set("field2", []string{"a", "b"})
dummy.Set("field3", []string{"a", "b", "c"})
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -909,9 +909,9 @@ func TestRecordDataValidatorValidateJson(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", `{"test":123}`)
dummy.SetDataValue("field2", `{"test":123}`)
dummy.SetDataValue("field3", `{"test":123}`)
dummy.Set("field1", `{"test":123}`)
dummy.Set("field2", `{"test":123}`)
dummy.Set("field3", `{"test":123}`)
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -1080,7 +1080,9 @@ func TestRecordDataValidatorValidateFile(t *testing.T) {
"field2": []string{"test1", testFiles[0].Name(), testFiles[3].Name()},
"field3": []string{"test1", "test2", "test3", "test4"},
},
[]*rest.UploadedFile{testFiles[0], testFiles[1], testFiles[2]},
map[string][]*rest.UploadedFile{
"field2": {testFiles[0], testFiles[3]},
},
[]string{"field2", "field3"},
},
{
@@ -1090,7 +1092,10 @@ func TestRecordDataValidatorValidateFile(t *testing.T) {
"field2": []string{"test1", testFiles[0].Name()},
"field3": []string{"test1", "test2", "test3"},
},
[]*rest.UploadedFile{testFiles[0], testFiles[1], testFiles[2]},
map[string][]*rest.UploadedFile{
"field1": {testFiles[0]},
"field2": {testFiles[0]},
},
[]string{"field1"},
},
{
@@ -1100,7 +1105,10 @@ func TestRecordDataValidatorValidateFile(t *testing.T) {
"field2": []string{"test1", testFiles[0].Name()},
"field3": []string{testFiles[1].Name(), testFiles[2].Name()},
},
[]*rest.UploadedFile{testFiles[0], testFiles[1], testFiles[2]},
map[string][]*rest.UploadedFile{
"field2": {testFiles[0], testFiles[1], testFiles[2]},
"field3": {testFiles[1], testFiles[2]},
},
[]string{"field3"},
},
{
@@ -1120,7 +1128,9 @@ func TestRecordDataValidatorValidateFile(t *testing.T) {
"field2": []string{testFiles[0].Name(), testFiles[1].Name()},
"field3": nil,
},
[]*rest.UploadedFile{testFiles[0], testFiles[1], testFiles[2]},
map[string][]*rest.UploadedFile{
"field2": {testFiles[0], testFiles[1]},
},
[]string{},
},
{
@@ -1130,7 +1140,9 @@ func TestRecordDataValidatorValidateFile(t *testing.T) {
"field2": []string{"test1", testFiles[0].Name()},
"field3": "test1", // will be casted
},
[]*rest.UploadedFile{testFiles[0], testFiles[1], testFiles[2]},
map[string][]*rest.UploadedFile{
"field2": {testFiles[0], testFiles[1], testFiles[2]},
},
[]string{},
},
}
@@ -1142,17 +1154,17 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
demo, _ := app.Dao().FindCollectionByNameOrId("demo4")
demo, _ := app.Dao().FindCollectionByNameOrId("demo3")
// demo4 rel ids
relId1 := "b8ba58f9-e2d7-42a0-b0e7-a11efd98236b"
relId2 := "df55c8ff-45ef-4c82-8aed-6e2183fe1125"
relId3 := "b84cd893-7119-43c9-8505-3c4e22da28a9"
relId4 := "054f9f24-0a0a-4e09-87b1-bc7ff2b336a2"
// demo3 rel ids
relId1 := "mk5fmymtx4wsprk"
relId2 := "7nwo8tuiatetxdm"
relId3 := "lcl9d87w22ml6jy"
relId4 := "1tmknxy2868d869"
// record rel ids from different collections
diffRelId1 := "63c2ab80-84ab-4057-a592-4604a731f78f"
diffRelId2 := "2c542824-9de1-42fe-8924-e57c86267760"
diffRelId1 := "0yxhwia2amd8gec"
diffRelId2 := "llvuca81nly1qls"
// create new test collection
collection := &models.Collection{}
@@ -1162,7 +1174,7 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
Name: "field1",
Type: schema.FieldTypeRelation,
Options: &schema.RelationOptions{
MaxSelect: 1,
MaxSelect: types.Pointer(1),
CollectionId: demo.Id,
},
},
@@ -1171,7 +1183,7 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
Required: true,
Type: schema.FieldTypeRelation,
Options: &schema.RelationOptions{
MaxSelect: 2,
MaxSelect: types.Pointer(2),
CollectionId: demo.Id,
},
},
@@ -1180,7 +1192,6 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
Unique: true,
Type: schema.FieldTypeRelation,
Options: &schema.RelationOptions{
MaxSelect: 3,
CollectionId: demo.Id,
},
},
@@ -1188,7 +1199,7 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
Name: "field4",
Type: schema.FieldTypeRelation,
Options: &schema.RelationOptions{
MaxSelect: 3,
MaxSelect: types.Pointer(3),
CollectionId: "", // missing or non-existing collection id
},
},
@@ -1199,9 +1210,9 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", relId1)
dummy.SetDataValue("field2", []string{relId1, relId2})
dummy.SetDataValue("field3", []string{relId1, relId2, relId3})
dummy.Set("field1", relId1)
dummy.Set("field2", []string{relId1, relId2})
dummy.Set("field3", []string{relId1, relId2, relId3})
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
@@ -1254,7 +1265,7 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
"field3": []string{relId1, relId2, relId3, relId4},
},
nil,
[]string{"field2", "field3"},
[]string{"field2"},
},
{
"check with ids from different collections",
@@ -1289,130 +1300,6 @@ func TestRecordDataValidatorValidateRelation(t *testing.T) {
checkValidatorErrors(t, app.Dao(), models.NewRecord(collection), scenarios)
}
func TestRecordDataValidatorValidateUser(t *testing.T) {
app, _ := tests.NewTestApp()
defer app.Cleanup()
userId1 := "97cc3d3d-6ba2-383f-b42a-7bc84d27410c"
userId2 := "7bc84d27-6ba2-b42a-383f-4197cc3d3d0c"
userId3 := "4d0197cc-2b4a-3f83-a26b-d77bc8423d3c"
missingUserId := "00000000-84ab-4057-a592-4604a731f78f"
// create new test collection
collection := &models.Collection{}
collection.Name = "validate_test"
collection.Schema = schema.NewSchema(
&schema.SchemaField{
Name: "field1",
Type: schema.FieldTypeUser,
Options: &schema.UserOptions{
MaxSelect: 1,
},
},
&schema.SchemaField{
Name: "field2",
Required: true,
Type: schema.FieldTypeUser,
Options: &schema.UserOptions{
MaxSelect: 2,
},
},
&schema.SchemaField{
Name: "field3",
Unique: true,
Type: schema.FieldTypeUser,
Options: &schema.UserOptions{
MaxSelect: 3,
},
},
)
if err := app.Dao().SaveCollection(collection); err != nil {
t.Fatal(err)
}
// create dummy record (used for the unique check)
dummy := models.NewRecord(collection)
dummy.SetDataValue("field1", userId1)
dummy.SetDataValue("field2", []string{userId1, userId2})
dummy.SetDataValue("field3", []string{userId1, userId2, userId3})
if err := app.Dao().SaveRecord(dummy); err != nil {
t.Fatal(err)
}
scenarios := []testDataFieldScenario{
{
"check required constraint - nil",
map[string]any{
"field1": nil,
"field2": nil,
"field3": nil,
},
nil,
[]string{"field2"},
},
{
"check required constraint - zero id",
map[string]any{
"field1": "",
"field2": "",
"field3": "",
},
nil,
[]string{"field2"},
},
{
"check unique constraint",
map[string]any{
"field1": nil,
"field2": userId1,
"field3": []string{userId1, userId2, userId3, userId3}, // repeating values are collapsed
},
nil,
[]string{"field3"},
},
{
"check MaxSelect constraint",
map[string]any{
"field1": []string{userId1, userId2}, // maxSelect is 1 and will be normalized to userId1 only
"field2": []string{userId1, userId2, userId3},
"field3": []string{userId1, userId3, userId2},
},
nil,
[]string{"field2"},
},
{
"check with mixed existing and nonexisting user ids",
map[string]any{
"field1": missingUserId,
"field2": []string{missingUserId, userId1},
"field3": []string{userId1, missingUserId},
},
nil,
[]string{"field1", "field2", "field3"},
},
{
"valid data - only required fields",
map[string]any{
"field2": []string{userId1, userId2},
},
nil,
[]string{},
},
{
"valid data - all fields with normalization",
map[string]any{
"field1": []string{userId1, userId2},
"field2": userId2,
"field3": []string{userId3, userId2, userId1}, // unique is not triggered because the order is different
},
nil,
[]string{},
},
}
checkValidatorErrors(t, app.Dao(), models.NewRecord(collection), scenarios)
}
func checkValidatorErrors(t *testing.T, dao *daos.Dao, record *models.Record, scenarios []testDataFieldScenario) {
for i, s := range scenarios {
validator := validators.NewRecordDataValidator(dao, record, s.files)