• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stephenafamo / scan / 14337148496

08 Apr 2025 03:10PM UTC coverage: 87.639% (-3.2%) from 90.805%
14337148496

push

github

stephenafamo
Add StructMapperColumns() to list columns that can be mapped

0 of 22 new or added lines in 1 file covered. (0.0%)

553 of 631 relevant lines covered (87.64%)

41.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.9
/mapper_struct.go
1
package scan
2

3
import (
4
        "context"
5
        "fmt"
6
        "reflect"
7
)
8

9
// CtxKeyAllowUnknownColumns makes it possible to allow unknown columns using the context
10
var CtxKeyAllowUnknownColumns contextKey = "allow unknown columns"
11

12
// Uses reflection to create a mapping function for a struct type
13
// using the default options
14
func StructMapper[T any](opts ...MappingOption) Mapper[T] {
20✔
15
        return CustomStructMapper[T](defaultStructMapper, opts...)
20✔
16
}
20✔
17

NEW
18
func StructMapperColumns[T any](opts ...MappingOption) ([]string, error) {
×
NEW
19
        return CustomStructMapperColumns[T](defaultStructMapper, opts...)
×
NEW
20
}
×
21

22
// Uses reflection to create a mapping function for a struct type
23
// using with custom options
24
func CustomStructMapper[T any](src StructMapperSource, optMod ...MappingOption) Mapper[T] {
25✔
25
        opts := mappingOptions{}
25✔
26
        for _, o := range optMod {
35✔
27
                o(&opts)
10✔
28
        }
10✔
29

30
        mod := func(ctx context.Context, c cols) (func(*Row) (any, error), func(any) (T, error)) {
71✔
31
                return structMapperFrom[T](ctx, c, src, opts)
46✔
32
        }
46✔
33

34
        if len(opts.mapperMods) > 0 {
27✔
35
                mod = Mod(mod, opts.mapperMods...)
2✔
36
        }
2✔
37

38
        return mod
25✔
39
}
40

NEW
41
func CustomStructMapperColumns[T any](src StructMapperSource, optMod ...MappingOption) ([]string, error) {
×
NEW
42
        opts := mappingOptions{}
×
NEW
43
        for _, o := range optMod {
×
NEW
44
                o(&opts)
×
NEW
45
        }
×
46

NEW
47
        if len(opts.mapperMods) > 0 {
×
NEW
48
                return nil, fmt.Errorf("Mapper mods are not supported in CustomStructMapperColumns")
×
NEW
49
        }
×
50

NEW
51
        typ := typeOf[T]()
×
NEW
52

×
NEW
53
        _, err := checks(typ)
×
NEW
54
        if err != nil {
×
NEW
55
                return nil, err
×
NEW
56
        }
×
57

NEW
58
        mapping, err := src.getMapping(typ)
×
NEW
59
        if err != nil {
×
NEW
60
                return nil, err
×
NEW
61
        }
×
62

NEW
63
        return mapping.cols(), nil
×
64
}
65

66
func structMapperFrom[T any](ctx context.Context, c cols, s StructMapperSource, opts mappingOptions) (func(*Row) (any, error), func(any) (T, error)) {
46✔
67
        typ := typeOf[T]()
46✔
68

46✔
69
        isPointer, err := checks(typ)
46✔
70
        if err != nil {
46✔
71
                return ErrorMapper[T](err)
×
72
        }
×
73

74
        mapping, err := s.getMapping(typ)
46✔
75
        if err != nil {
46✔
76
                return ErrorMapper[T](err)
×
77
        }
×
78

79
        return mapperFromMapping[T](mapping, typ, isPointer, opts)(ctx, c)
46✔
80
}
81

82
// Check if there are any errors, and returns if it is a pointer or not
83
func checks(typ reflect.Type) (bool, error) {
46✔
84
        if typ == nil {
46✔
85
                return false, fmt.Errorf("Nil type passed to StructMapper")
×
86
        }
×
87

88
        var isPointer bool
46✔
89

46✔
90
        switch {
46✔
91
        case typ.Kind() == reflect.Struct:
40✔
92
        case typ.Kind() == reflect.Pointer:
6✔
93
                isPointer = true
6✔
94

6✔
95
                if typ.Elem().Kind() != reflect.Struct {
6✔
96
                        return false, fmt.Errorf("Type %q is not a struct or pointer to a struct", typ.String())
×
97
                }
×
98
        default:
×
99
                return false, fmt.Errorf("Type %q is not a struct or pointer to a struct", typ.String())
×
100
        }
101

102
        return isPointer, nil
46✔
103
}
104

105
type mappingOptions struct {
106
        typeConverter   TypeConverter
107
        rowValidator    RowValidator
108
        mapperMods      []MapperMod
109
        structTagPrefix string
110
}
111

112
// MappingeOption is a function type that changes how the mapper is generated
113
type MappingOption func(*mappingOptions)
114

115
// WithRowValidator sets the [RowValidator] for the struct mapper
116
// after scanning all values in a row, they are passed to the RowValidator
117
// if it returns false, the zero value for that row is returned
118
func WithRowValidator(rv RowValidator) MappingOption {
2✔
119
        return func(opt *mappingOptions) {
4✔
120
                opt.rowValidator = rv
2✔
121
        }
2✔
122
}
123

124
// TypeConverter sets the [TypeConverter] for the struct mapper
125
// it is called to modify the type of a column and get the original value back
126
func WithTypeConverter(tc TypeConverter) MappingOption {
4✔
127
        return func(opt *mappingOptions) {
8✔
128
                opt.typeConverter = tc
4✔
129
        }
4✔
130
}
131

132
// WithStructTagPrefix should be used when every column from the database has a prefix.
133
func WithStructTagPrefix(prefix string) MappingOption {
2✔
134
        return func(opt *mappingOptions) {
4✔
135
                opt.structTagPrefix = prefix
2✔
136
        }
2✔
137
}
138

139
// WithMapperMods accepts mods used to modify the mapper
140
func WithMapperMods(mods ...MapperMod) MappingOption {
2✔
141
        return func(opt *mappingOptions) {
4✔
142
                opt.mapperMods = append(opt.mapperMods, mods...)
2✔
143
        }
2✔
144
}
145

146
func mapperFromMapping[T any](m mapping, typ reflect.Type, isPointer bool, opts mappingOptions) func(context.Context, cols) (func(*Row) (any, error), func(any) (T, error)) {
46✔
147
        return func(ctx context.Context, c cols) (func(*Row) (any, error), func(any) (T, error)) {
92✔
148
                // Filter the mapping so we only ask for the available columns
46✔
149
                filtered, err := filterColumns(ctx, c, m, opts.structTagPrefix)
46✔
150
                if err != nil {
46✔
151
                        return ErrorMapper[T](err)
×
152
                }
×
153

154
                mapper := regular[T]{
46✔
155
                        typ:       typ,
46✔
156
                        isPointer: isPointer,
46✔
157
                        filtered:  filtered,
46✔
158
                        converter: opts.typeConverter,
46✔
159
                        validator: opts.rowValidator,
46✔
160
                }
46✔
161
                switch {
46✔
162
                case opts.typeConverter == nil && opts.rowValidator == nil:
37✔
163
                        return mapper.regular()
37✔
164

165
                default:
9✔
166
                        return mapper.allOptions()
9✔
167
                }
168
        }
169
}
170

171
type regular[T any] struct {
172
        isPointer bool
173
        typ       reflect.Type
174
        filtered  mapping
175
        converter TypeConverter
176
        validator RowValidator
177
}
178

179
func (s regular[T]) regular() (func(*Row) (any, error), func(any) (T, error)) {
37✔
180
        return func(v *Row) (any, error) {
86✔
181
                        var row reflect.Value
49✔
182
                        if s.isPointer {
57✔
183
                                row = reflect.New(s.typ.Elem()).Elem()
8✔
184
                        } else {
49✔
185
                                row = reflect.New(s.typ).Elem()
41✔
186
                        }
41✔
187

188
                        for _, info := range s.filtered {
171✔
189
                                for _, v := range info.init {
156✔
190
                                        pv := row.FieldByIndex(v)
34✔
191
                                        if !pv.IsZero() {
50✔
192
                                                continue
16✔
193
                                        }
194

195
                                        pv.Set(reflect.New(pv.Type().Elem()))
18✔
196
                                }
197

198
                                fv := row.FieldByIndex(info.position)
122✔
199
                                v.ScheduleScanx(info.name, fv.Addr())
122✔
200
                        }
201

202
                        return row, nil
49✔
203
                }, func(v any) (T, error) {
41✔
204
                        row := v.(reflect.Value)
41✔
205

41✔
206
                        if s.isPointer {
49✔
207
                                row = row.Addr()
8✔
208
                        }
8✔
209

210
                        return row.Interface().(T), nil
41✔
211
                }
212
}
213

214
func (s regular[T]) allOptions() (func(*Row) (any, error), func(any) (T, error)) {
9✔
215
        return func(v *Row) (any, error) {
21✔
216
                        row := make([]reflect.Value, len(s.filtered))
12✔
217

12✔
218
                        for i, info := range s.filtered {
38✔
219
                                var ft reflect.Type
26✔
220
                                if s.isPointer {
28✔
221
                                        ft = s.typ.Elem().FieldByIndex(info.position).Type
2✔
222
                                } else {
26✔
223
                                        ft = s.typ.FieldByIndex(info.position).Type
24✔
224
                                }
24✔
225

226
                                if s.converter != nil {
48✔
227
                                        row[i] = s.converter.TypeToDestination(ft)
22✔
228
                                } else {
26✔
229
                                        row[i] = reflect.New(ft)
4✔
230
                                }
4✔
231

232
                                v.ScheduleScanx(info.name, row[i])
26✔
233
                        }
234

235
                        return row, nil
12✔
236
                }, func(v any) (T, error) {
12✔
237
                        vals := v.([]reflect.Value)
12✔
238

12✔
239
                        if s.validator != nil && !s.validator(s.filtered.cols(), vals) {
13✔
240
                                var t T
1✔
241
                                return t, nil
1✔
242
                        }
1✔
243

244
                        var row reflect.Value
11✔
245
                        if s.isPointer {
12✔
246
                                row = reflect.New(s.typ.Elem()).Elem()
1✔
247
                        } else {
11✔
248
                                row = reflect.New(s.typ).Elem()
10✔
249
                        }
10✔
250

251
                        for i, info := range s.filtered {
35✔
252
                                for _, v := range info.init {
32✔
253
                                        pv := row.FieldByIndex(v)
8✔
254
                                        if !pv.IsZero() {
12✔
255
                                                continue
4✔
256
                                        }
257

258
                                        pv.Set(reflect.New(pv.Type().Elem()))
4✔
259
                                }
260

261
                                var val reflect.Value
24✔
262
                                if s.converter != nil {
46✔
263
                                        val = s.converter.ValueFromDestination(vals[i])
22✔
264
                                } else {
24✔
265
                                        val = vals[i].Elem()
2✔
266
                                }
2✔
267

268
                                fv := row.FieldByIndex(info.position)
24✔
269
                                if info.isPointer {
27✔
270
                                        fv.Elem().Set(val)
3✔
271
                                } else {
24✔
272
                                        fv.Set(val)
21✔
273
                                }
21✔
274
                        }
275

276
                        if s.isPointer {
12✔
277
                                row = row.Addr()
1✔
278
                        }
1✔
279

280
                        return row.Interface().(T), nil
11✔
281
                }
282
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc