Fixing struct generation

- ready to test
- slice size bugfixes
- appending empty struct before filling it out
This commit is contained in:
2025-09-30 17:09:19 -04:00
parent 053f5b5fd8
commit 2415af38b2
2 changed files with 44 additions and 30 deletions

2
.gitignore vendored
View File

@@ -25,3 +25,5 @@ go.work.sum
# env file # env file
.env .env
#ignore test files
cmd/

72
lazy.go
View File

@@ -20,7 +20,6 @@ const (
var ( var (
ErrBadExample = errors.New("example field must be a non-nil struct") ErrBadExample = errors.New("example field must be a non-nil struct")
ErrMissingKeys = errors.New("there must be a key for each row requested with RowCount") ErrMissingKeys = errors.New("there must be a key for each row requested with RowCount")
ErrTypeCast = errors.New("unable to type cast the mock structs")
//contains placeholder to inject type on runtime //contains placeholder to inject type on runtime
ErrUnsupportedType = errors.New("type: %s is not yet supported") ErrUnsupportedType = errors.New("type: %s is not yet supported")
) )
@@ -33,13 +32,19 @@ type RowMock struct {
} }
// configuration for RandomGenerate // configuration for RandomGenerate
type Config struct { type RowConfig struct {
Query string Query string
Example any Example any
Keys []any Keys []any
RowCount int RowCount int
} }
type StructConfig struct {
Query string
Example any
RowCount int
}
type StructMock struct { type StructMock struct {
Query string Query string
Args []driver.Value Args []driver.Value
@@ -48,35 +53,35 @@ type StructMock struct {
} }
// generates mock data based on configuration to be used for sqlmock // generates mock data based on configuration to be used for sqlmock
func RandomRows(m Config) (*RowMock, error) { func RandomRows(r RowConfig) (*RowMock, error) {
//example struct cannot be nil and must be a struct //example struct cannot be nil and must be a struct
if m.Example == nil { if r.Example == nil {
return nil, ErrBadExample return nil, ErrBadExample
} }
if reflect.ValueOf(m.Example).Kind() != reflect.Struct { if reflect.ValueOf(r.Example).Kind() != reflect.Struct {
return nil, ErrBadExample return nil, ErrBadExample
} }
//any weirdness, just pull one row //any weirdness, just pull one row
if m.RowCount <= 0 { if r.RowCount <= 0 {
m.RowCount = 1 r.RowCount = 1
} }
//if keys are set, ensure there are enough to populate requested rows //if keys are set, ensure there are enough to populate requested rows
primaryKey := false primaryKey := false
if len(m.Keys) > 0 { if len(r.Keys) > 0 {
primaryKey = true primaryKey = true
if len(m.Keys) != m.RowCount { if len(r.Keys) != r.RowCount {
return nil, ErrMissingKeys return nil, ErrMissingKeys
} }
} }
retType := reflect.TypeOf(m.Example) retType := reflect.TypeOf(r.Example)
maxFieldCount := retType.NumField() maxFieldCount := retType.NumField()
columns := make([]string, 0, maxFieldCount) columns := make([]string, 0, maxFieldCount)
rows := make([][]driver.Value, 0) rows := make([][]driver.Value, 0)
for y := 0; y < m.RowCount; y++ { for y := 0; y < r.RowCount; y++ {
rows = append(rows, make([]driver.Value, 0)) rows = append(rows, make([]driver.Value, 0))
for x := 0; x < maxFieldCount; x++ { for x := 0; x < maxFieldCount; x++ {
field := retType.Field(x) field := retType.Field(x)
@@ -92,7 +97,7 @@ func RandomRows(m Config) (*RowMock, error) {
//if field has lazy:"key" tag and tags are present inject primary key //if field has lazy:"key" tag and tags are present inject primary key
if field.Tag.Get(LAZY_TAG) == KEY_VALUE && primaryKey { if field.Tag.Get(LAZY_TAG) == KEY_VALUE && primaryKey {
rows[y] = append(rows[y], m.Keys[y]) rows[y] = append(rows[y], r.Keys[y])
continue continue
} }
@@ -108,16 +113,16 @@ func RandomRows(m Config) (*RowMock, error) {
return &RowMock{ return &RowMock{
//sql is rebound and escaped for sqlmock.ExpectQuery //sql is rebound and escaped for sqlmock.ExpectQuery
Query: sqlx.Rebind(sqlx.AT, regexp.QuoteMeta(m.Query)), Query: sqlx.Rebind(sqlx.AT, regexp.QuoteMeta(r.Query)),
Columns: columns, Columns: columns,
Rows: rows, Rows: rows,
}, nil }, nil
} }
// we are close to something here but the mock structs aren't it chief. Maybe the users can make() them first? // generates mock data for insert statements to be used for sqlmock, it also returns a slice of
// I don't love that either tho tbh // example structs with mock data
// hmmmmm func RandomStruct(c StructConfig) (*StructMock, error) {
func RandomStruct(c Config) (*StructMock, error) { //make sure we have an example struct
if c.Example == nil { if c.Example == nil {
return nil, ErrBadExample return nil, ErrBadExample
} }
@@ -125,50 +130,56 @@ func RandomStruct(c Config) (*StructMock, error) {
return nil, ErrBadExample return nil, ErrBadExample
} }
//we need at least one row
if c.RowCount <= 0 { if c.RowCount <= 0 {
c.RowCount = 1 c.RowCount = 1
} }
//get example type and number of fields
retType := reflect.TypeOf(c.Example) retType := reflect.TypeOf(c.Example)
maxFieldCount := retType.NumField() maxFieldCount := retType.NumField()
//create slice of structs
//create slice of example structs
ft := reflect.SliceOf(retType) ft := reflect.SliceOf(retType)
filled := reflect.MakeSlice(ft, 0, c.RowCount) filled := reflect.MakeSlice(ft, 0, c.RowCount)
// filled := reflect.MakeSlice(ft, 0, c.RowCount).Elem() args := make([]driver.Value, 0) //args for sqlmock WithArgs
args := make([]driver.Value, 0, maxFieldCount)
for x := 0; x < c.RowCount; x++ { for x := 0; x < c.RowCount; x++ {
//add empty example struct
filled = reflect.Append(filled, reflect.ValueOf(c.Example))
for y := 0; y < maxFieldCount; y++ { for y := 0; y < maxFieldCount; y++ {
field := retType.Field(y) field := retType.Field(y)
dbTag := field.Tag.Get(DB_TAG) dbTag := field.Tag.Get(DB_TAG)
// no db tag, we skip
if dbTag == "" { if dbTag == "" {
continue continue
} }
//get random value
nv := kindToRandom(field) nv := kindToRandom(field)
if nv == nil { if nv == nil {
return nil, fmt.Errorf(ErrUnsupportedType.Error(), field.Type.Name()) return nil, fmt.Errorf(ErrUnsupportedType.Error(), field.Type.Name())
} }
args[y] = nv //add to sqlmock args
// if x == 0 { args = append(args, nv)
nf := filled.Index(x).Field(y)
if nf.CanSet() { if filled.Index(x).Field(y).CanSet() {
filled.Index(x).Field(y).Set(reflect.ValueOf(nv)) filled.Index(x).Field(y).Set(reflect.ValueOf(nv))
} }
// }
} }
} }
ms, ok := reflect.ValueOf(filled).Interface().([]any) //convert reflect.Value to []any
if !ok { retStructs := make([]any, 0)
return nil, ErrTypeCast for x := 0; x < filled.Len(); x++ {
retStructs = append(retStructs, filled.Index(x))
} }
return &StructMock{ return &StructMock{
Query: sqlx.Rebind(sqlx.AT, regexp.QuoteMeta(c.Query)), Query: sqlx.Rebind(sqlx.AT, regexp.QuoteMeta(c.Query)),
Args: args, Args: args,
MockStructs: ms, MockStructs: retStructs,
Result: &sqlResult{ Result: &sqlResult{
rowsAffected: int64(c.RowCount), rowsAffected: int64(c.RowCount),
err: nil, err: nil,
@@ -176,7 +187,8 @@ func RandomStruct(c Config) (*StructMock, error) {
}, nil }, nil
} }
// converts basic reflect Kind's to psuedo-random data, slices are given a random amount of entries // converts basic reflect Kind's to psuedo-random data, it is super basic and supports very basic types. Slices and arrays
// are supported, slices will get 1-10 entries and arrays will fill length
func kindToRandom(field reflect.StructField) any { func kindToRandom(field reflect.StructField) any {
kind := field.Type.Kind() kind := field.Type.Kind()
switch kind { switch kind {