From cf6a81e805164109d585b529990f54ecb7e700ff Mon Sep 17 00:00:00 2001 From: Hein Date: Tue, 13 Jan 2026 12:18:13 +0200 Subject: [PATCH] =?UTF-8?q?feat(reflection):=20=E2=9C=A8=20add=20tests=20f?= =?UTF-8?q?or=20standard=20SQL=20null=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement tests for mapping standard library sql.Null* types to struct. * Verify handling of valid and nil values for sql.NullInt64, sql.NullString, sql.NullFloat64, sql.NullBool, and sql.NullTime. * Ensure correct error handling and type conversion in MapToStruct function. --- pkg/reflection/model_utils.go | 18 +-- .../model_utils_stdlib_sqltypes_test.go | 120 ++++++++++++++++++ 2 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 pkg/reflection/model_utils_stdlib_sqltypes_test.go diff --git a/pkg/reflection/model_utils.go b/pkg/reflection/model_utils.go index b07d4d2..58be9f8 100644 --- a/pkg/reflection/model_utils.go +++ b/pkg/reflection/model_utils.go @@ -1102,6 +1102,12 @@ func setFieldValue(field reflect.Value, value interface{}) error { } } + // If we can convert the type, do it + if valueReflect.Type().ConvertibleTo(field.Type()) { + field.Set(valueReflect.Convert(field.Type())) + return nil + } + // Handle struct types (like SqlTimeStamp, SqlDate, SqlTime which wrap SqlNull[time.Time]) if field.Kind() == reflect.Struct { @@ -1113,9 +1119,9 @@ func setFieldValue(field reflect.Value, value interface{}) error { // Call the Scan method with the value results := scanMethod.Call([]reflect.Value{reflect.ValueOf(value)}) if len(results) > 0 { - // Check if there was an error - if err, ok := results[0].Interface().(error); ok && err != nil { - return err + // The Scan method returns error - check if it's nil + if !results[0].IsNil() { + return results[0].Interface().(error) } return nil } @@ -1170,12 +1176,6 @@ func setFieldValue(field reflect.Value, value interface{}) error { } - // If we can convert the type, do it - if valueReflect.Type().ConvertibleTo(field.Type()) { - field.Set(valueReflect.Convert(field.Type())) - return nil - } - return fmt.Errorf("cannot convert %v to %v", valueReflect.Type(), field.Type()) } diff --git a/pkg/reflection/model_utils_stdlib_sqltypes_test.go b/pkg/reflection/model_utils_stdlib_sqltypes_test.go new file mode 100644 index 0000000..0c4891e --- /dev/null +++ b/pkg/reflection/model_utils_stdlib_sqltypes_test.go @@ -0,0 +1,120 @@ +package reflection_test + +import ( + "database/sql" + "testing" + "time" + + "github.com/bitechdev/ResolveSpec/pkg/reflection" +) + +func TestMapToStruct_StandardSqlNullTypes(t *testing.T) { + // Test model with standard library sql.Null* types + type TestModel struct { + ID int64 `bun:"id,pk" json:"id"` + Age sql.NullInt64 `bun:"age" json:"age"` + Name sql.NullString `bun:"name" json:"name"` + Score sql.NullFloat64 `bun:"score" json:"score"` + Active sql.NullBool `bun:"active" json:"active"` + UpdatedAt sql.NullTime `bun:"updated_at" json:"updated_at"` + } + + now := time.Now() + dataMap := map[string]any{ + "id": int64(100), + "age": int64(25), + "name": "John Doe", + "score": 95.5, + "active": true, + "updated_at": now, + } + + var result TestModel + err := reflection.MapToStruct(dataMap, &result) + if err != nil { + t.Fatalf("MapToStruct() error = %v", err) + } + + // Verify ID + if result.ID != 100 { + t.Errorf("ID = %v, want 100", result.ID) + } + + // Verify Age (sql.NullInt64) + if !result.Age.Valid { + t.Error("Age.Valid = false, want true") + } + if result.Age.Int64 != 25 { + t.Errorf("Age.Int64 = %v, want 25", result.Age.Int64) + } + + // Verify Name (sql.NullString) + if !result.Name.Valid { + t.Error("Name.Valid = false, want true") + } + if result.Name.String != "John Doe" { + t.Errorf("Name.String = %v, want 'John Doe'", result.Name.String) + } + + // Verify Score (sql.NullFloat64) + if !result.Score.Valid { + t.Error("Score.Valid = false, want true") + } + if result.Score.Float64 != 95.5 { + t.Errorf("Score.Float64 = %v, want 95.5", result.Score.Float64) + } + + // Verify Active (sql.NullBool) + if !result.Active.Valid { + t.Error("Active.Valid = false, want true") + } + if !result.Active.Bool { + t.Error("Active.Bool = false, want true") + } + + // Verify UpdatedAt (sql.NullTime) + if !result.UpdatedAt.Valid { + t.Error("UpdatedAt.Valid = false, want true") + } + if !result.UpdatedAt.Time.Equal(now) { + t.Errorf("UpdatedAt.Time = %v, want %v", result.UpdatedAt.Time, now) + } + + t.Log("All standard library sql.Null* types handled correctly!") +} + +func TestMapToStruct_StandardSqlNullTypes_WithNil(t *testing.T) { + // Test nil handling for standard library sql.Null* types + type TestModel struct { + ID int64 `bun:"id,pk" json:"id"` + Age sql.NullInt64 `bun:"age" json:"age"` + Name sql.NullString `bun:"name" json:"name"` + } + + dataMap := map[string]any{ + "id": int64(200), + "age": int64(30), + "name": nil, // Explicitly nil + } + + var result TestModel + err := reflection.MapToStruct(dataMap, &result) + if err != nil { + t.Fatalf("MapToStruct() error = %v", err) + } + + // Age should be valid + if !result.Age.Valid { + t.Error("Age.Valid = false, want true") + } + if result.Age.Int64 != 30 { + t.Errorf("Age.Int64 = %v, want 30", result.Age.Int64) + } + + // Name should be invalid (null) + if result.Name.Valid { + t.Error("Name.Valid = true, want false (null)") + } + + t.Log("Nil handling for sql.Null* types works correctly!") +}