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

demdxx / gocast / 3771917395

pending completion
3771917395

push

github

Dmitry Ponomarev
Improve casting with context to make it more flexible

43 of 51 new or added lines in 2 files covered. (84.31%)

5 existing lines in 3 files now uncovered.

1060 of 1379 relevant lines covered (76.87%)

1.73 hits per line

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

75.1
/struct.go
1
// Copyright (c) 2014 Dmitry Ponomarev <demdxx@gmail.com>
2
//
3
// Permission is hereby granted, free of charge, to any person obtaining a copy of
4
// this software and associated documentation files (the "Software"), to deal in
5
// the Software without restriction, including without limitation the rights to
6
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
// the Software, and to permit persons to whom the Software is furnished to do so,
8
// subject to the following conditions:
9
//
10
// The above copyright notice and this permission notice shall be included in all
11
// copies or substantial portions of the Software.
12
//
13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19

20
package gocast
21

22
import (
23
        "context"
24
        "reflect"
25
        "strings"
26
        "time"
27
)
28

29
// TryCopyStruct convert any input type into the target structure
UNCOV
30
func TryCopyStruct(dst, src any, tags ...string) (err error) {
×
UNCOV
31
        return TryCopyStructContext(context.Background(), dst, src, tags...)
×
UNCOV
32
}
×
33

34
// TryCopyStructContext convert any input type into the target structure
35
func TryCopyStructContext(ctx context.Context, dst, src any, tags ...string) (err error) {
2✔
36
        if dst == nil || src == nil {
2✔
37
                return ErrInvalidParams
×
38
        }
×
39

40
        if sintf, ok := dst.(CastSetter); ok {
4✔
41
                if sintf.CastSet(ctx, src) == nil {
4✔
42
                        return nil
2✔
43
                }
2✔
44
        }
45

46
        switch dst.(type) {
2✔
47
        case time.Time, *time.Time:
2✔
48
                err = setFieldTimeValue(reflect.ValueOf(dst), src)
2✔
49
        default:
2✔
50
                destVal := reflectTarget(reflect.ValueOf(dst))
2✔
51
                destType := destVal.Type()
2✔
52

2✔
53
                srcVal := reflectTarget(reflect.ValueOf(src))
2✔
54

2✔
55
                switch srcVal.Kind() {
2✔
56
                case reflect.Map, reflect.Struct:
2✔
57
                        for i := 0; i < destVal.NumField(); i++ {
4✔
58
                                f := destVal.Field(i)
2✔
59
                                if !f.CanSet() {
4✔
60
                                        continue
2✔
61
                                }
62

63
                                // Get passable field names
64
                                names := fieldNames(destType.Field(i), tags...)
2✔
65
                                if len(names) < 1 {
2✔
66
                                        continue
×
67
                                }
68

69
                                // Get value from map
70
                                var v any
2✔
71

2✔
72
                                if srcVal.Kind() == reflect.Map {
4✔
73
                                        v = reflectMapValueByStringKeys(srcVal, names)
2✔
74
                                } else {
4✔
75
                                        v, _ = ReflectStructFieldValue(srcVal, names...)
2✔
76
                                }
2✔
77

78
                                // Set field value
79
                                if v == nil {
4✔
80
                                        err = setFieldValueReflect(ctx, f, reflect.Zero(f.Type()))
2✔
81
                                } else {
4✔
82
                                        switch f.Kind() {
2✔
83
                                        case reflect.Struct:
2✔
84
                                                if err = TryCopyStructContext(ctx, f.Addr().Interface(), v, tags...); err != nil {
2✔
85
                                                        return err
×
86
                                                }
×
87
                                        default:
2✔
88
                                                var vl any
2✔
89
                                                if vl, err = TryToType(v, f.Type(), tags...); err == nil {
4✔
90
                                                        val := reflect.ValueOf(vl)
2✔
91
                                                        if val.Kind() == reflect.Ptr && val.Kind() != f.Kind() {
2✔
92
                                                                val = val.Elem()
×
93
                                                        }
×
94
                                                        err = setFieldValueReflect(ctx, f, val)
2✔
95
                                                } else if setter, _ := f.Interface().(CastSetter); setter != nil {
×
96
                                                        err = setter.CastSet(ctx, v)
×
97
                                                } else if f.CanAddr() {
×
98
                                                        if setter, _ := f.Addr().Interface().(CastSetter); setter != nil {
×
99
                                                                err = setter.CastSet(ctx, v)
×
100
                                                        }
×
101
                                                }
102
                                        } // end switch
103
                                } // end else
104
                                if err != nil {
2✔
105
                                        break
×
106
                                }
107
                        }
108
                default:
×
109
                        err = wrapError(ErrUnsupportedType, destType.Name())
×
110
                }
111
        }
112
        return err
2✔
113
}
114

115
// Struct convert any input type into the target structure
116
func Struct[R any](src any, tags ...string) (R, error) {
2✔
117
        return StructContext[R](context.Background(), src, tags...)
2✔
118
}
2✔
119

120
// StructContext convert any input type into the target structure
121
func StructContext[R any](ctx context.Context, src any, tags ...string) (R, error) {
2✔
122
        var res R
2✔
123
        err := TryCopyStructContext(ctx, &res, src, tags...)
2✔
124
        return res, err
2✔
125
}
2✔
126

127
// ToStruct convert any input type into the target structure
128
//
129
// Deprecated: Use TryCopyStruct instead
130
func ToStruct(dst, src any, tags ...string) error {
×
131
        return TryCopyStruct(dst, src, tags...)
×
132
}
×
133

134
// StructFields returns the field names from the structure
135
func StructFields(st any, tag string) []string {
2✔
136
        s := reflectTarget(reflect.ValueOf(st))
2✔
137
        t := s.Type()
2✔
138

2✔
139
        fields := make([]string, 0, s.NumField())
2✔
140
        for i := 0; i < s.NumField(); i++ {
4✔
141
                fname, _ := fieldName(t.Field(i), tag)
2✔
142
                if fname != "" && fname != "-" {
4✔
143
                        fields = append(fields, fname)
2✔
144
                }
2✔
145
        }
146
        return fields
2✔
147
}
148

149
// StructFieldTags returns Map with key->tag matching
150
func StructFieldTags(st any, tag string) map[string]string {
2✔
151
        fields := map[string]string{}
2✔
152
        keys, values := StructFieldTagsUnsorted(st, tag)
2✔
153

2✔
154
        for i, k := range keys {
4✔
155
                fields[k] = values[i]
2✔
156
        }
2✔
157
        return fields
2✔
158
}
159

160
// StructFieldTagsUnsorted returns field names and tag targets separately
161
func StructFieldTagsUnsorted(st any, tag string) ([]string, []string) {
2✔
162
        keys := []string{}
2✔
163
        values := []string{}
2✔
164

2✔
165
        s := reflectTarget(reflect.ValueOf(st))
2✔
166
        t := s.Type()
2✔
167

2✔
168
        for i := 0; i < s.NumField(); i++ {
4✔
169
                f := t.Field(i)
2✔
170
                tag := fieldTag(f, tag)
2✔
171
                if len(tag) > 0 && tag != "-" {
4✔
172
                        keys = append(keys, f.Name)
2✔
173
                        values = append(values, tag)
2✔
174
                }
2✔
175
        }
176
        return keys, values
2✔
177
}
178

179
// StructFieldValue returns the value of the struct field
180
func StructFieldValue(st any, names ...string) (any, error) {
2✔
181
        structVal := reflectTarget(reflect.ValueOf(st))
2✔
182
        return ReflectStructFieldValue(structVal, names...)
2✔
183
}
2✔
184

185
// ReflectStructFieldValue returns the value of the struct field
186
func ReflectStructFieldValue(st reflect.Value, names ...string) (any, error) {
2✔
187
        structVal := reflectTarget(st)
2✔
188
        structType := structVal.Type()
2✔
189
        for _, name := range names {
4✔
190
                if _, ok := structType.FieldByName(name); ok {
4✔
191
                        return structVal.FieldByName(name).Interface(), nil
2✔
192
                }
2✔
193
        }
194
        return nil, wrapError(ErrStructFieldNameUndefined, strings.Join(names, ", "))
2✔
195
}
196

197
// SetStructFieldValue puts value into the struct field
198
func SetStructFieldValue(ctx context.Context, st any, name string, value any) (err error) {
2✔
199
        s := reflectTarget(reflect.ValueOf(st))
2✔
200
        t := s.Type()
2✔
201
        if _, ok := t.FieldByName(name); ok {
4✔
202
                field := s.FieldByName(name)
2✔
203
                if !field.CanSet() {
2✔
204
                        return wrapError(ErrStructFieldValueCantBeChanged, name)
×
205
                }
×
206
                err = setFieldValue(ctx, field, value)
2✔
207
                return err
2✔
208
        }
209
        return wrapError(ErrStructFieldNameUndefined, name)
2✔
210
}
211

212
///////////////////////////////////////////////////////////////////////////////
213
/// MARK: Helpers
214
///////////////////////////////////////////////////////////////////////////////
215

216
var fieldNameArr = []string{"field", "schema", "sql", "json", "xml", "yaml"}
217

218
func setFieldValue(ctx context.Context, field reflect.Value, value any) (err error) {
2✔
219
        switch field.Interface().(type) {
2✔
220
        case time.Time, *time.Time:
2✔
221
                err = setFieldTimeValue(field, value)
2✔
222
        default:
2✔
223
                if setter, _ := field.Interface().(CastSetter); setter != nil {
2✔
224
                        return setter.CastSet(ctx, value)
×
225
                } else if field.CanAddr() {
4✔
226
                        if setter, _ := field.Addr().Interface().(CastSetter); setter != nil {
4✔
227
                                return setter.CastSet(ctx, value)
2✔
228
                        } else if vl := reflect.ValueOf(value); field.Kind() == vl.Kind() || field.Kind() == reflect.Interface {
6✔
229
                                field.Set(vl)
2✔
230
                        } else {
2✔
231
                                return wrapError(ErrUnsupportedType, field.Type().String())
×
232
                        }
×
233
                } else if vl := reflect.ValueOf(value); field.Kind() == vl.Kind() || field.Kind() == reflect.Interface {
×
234
                        field.Set(vl)
×
235
                } else {
×
236
                        return wrapError(ErrUnsupportedType, field.Type().String())
×
237
                }
×
238
        }
239
        return err
2✔
240
}
241

242
func setFieldValueReflect(ctx context.Context, field, value reflect.Value) (err error) {
2✔
243
        switch field.Interface().(type) {
2✔
244
        case time.Time, *time.Time:
×
245
                err = setFieldTimeValue(field, value.Interface())
×
246
        default:
2✔
247
                if setter, _ := field.Interface().(CastSetter); setter != nil {
2✔
248
                        return setter.CastSet(ctx, value.Interface())
×
249
                } else if field.CanAddr() {
4✔
250
                        if setter, _ := field.Addr().Interface().(CastSetter); setter != nil {
4✔
251
                                return setter.CastSet(ctx, value.Interface())
2✔
252
                        } else if field.Kind() == value.Kind() || field.Kind() == reflect.Interface {
6✔
253
                                field.Set(value)
2✔
254
                        } else {
2✔
255
                                return wrapError(ErrUnsupportedType, field.Type().String())
×
256
                        }
×
257
                } else if field.Kind() == value.Kind() || field.Kind() == reflect.Interface {
×
258
                        field.Set(value)
×
259
                } else {
×
260
                        return wrapError(ErrUnsupportedType, field.Type().String())
×
261
                }
×
262
        }
263
        return err
2✔
264
}
265

266
func setFieldTimeValue(field reflect.Value, value any) (err error) {
2✔
267
        switch v := value.(type) {
2✔
268
        case nil:
×
269
                s := reflectTarget(field)
×
270
                s.Set(reflect.ValueOf(time.Time{}))
×
271
        case time.Time:
2✔
272
                s := reflectTarget(field)
2✔
273
                s.Set(reflect.ValueOf(v))
2✔
274
        case *time.Time:
×
275
                s := reflectTarget(field)
×
276
                s.Set(reflect.ValueOf(*v))
×
277
        case string:
2✔
278
                var tm time.Time
2✔
279
                if tm, err = ParseTime(v); err == nil {
4✔
280
                        s := reflectTarget(field)
2✔
281
                        s.Set(reflect.ValueOf(tm))
2✔
282
                }
2✔
283
        case int64:
2✔
284
                s := reflectTarget(field)
2✔
285
                s.Set(reflect.ValueOf(time.Unix(v, 0)))
2✔
286
        case uint64:
×
287
                s := reflectTarget(field)
×
288
                s.Set(reflect.ValueOf(time.Unix(int64(v), 0)))
×
289
        default:
×
290
                err = wrapError(ErrUnsupportedType, field.String())
×
291
        }
292
        return err
2✔
293
}
294

295
func fieldNames(f reflect.StructField, tags ...string) []string {
2✔
296
        if len(tags) > 0 {
4✔
297
                names := fieldTagArr(f, tags[0])
2✔
298
                switch names[0] {
2✔
299
                case "", "-":
×
300
                        return []string{f.Name}
×
301
                default:
2✔
302
                }
303
                return []string{names[0], f.Name}
2✔
304
        }
305
        return []string{f.Name, f.Name}
2✔
306
}
307

308
func fieldNameFromTags(f reflect.StructField, tags ...string) (name string, omitempty bool) {
2✔
309
        if len(tags) == 0 {
4✔
310
                return f.Name, false
2✔
311
        }
2✔
312
        return fieldName(f, tags[0])
2✔
313
}
314

315
func fieldName(f reflect.StructField, tag string) (name string, omitempty bool) {
2✔
316
        names := fieldTagArr(f, tag)
2✔
317
        name = names[0]
2✔
318
        if len(names) > 1 && names[len(names)-1] == "omitempty" {
2✔
319
                omitempty = true
×
320
        }
×
321
        if name == "" {
2✔
322
                name = f.Name
×
323
        }
×
324
        return name, omitempty
2✔
325
}
326

327
func fieldTagArr(f reflect.StructField, tag string) []string {
2✔
328
        return strings.Split(fieldTag(f, tag), ",")
2✔
329
}
2✔
330

331
func fieldTag(f reflect.StructField, tag string) string {
2✔
332
        if tag == "-" {
4✔
333
                return f.Name
2✔
334
        }
2✔
335
        var (
2✔
336
                fields string
2✔
337
                tags   []string
2✔
338
        )
2✔
339
        if tag != "" {
4✔
340
                tags = strings.Split(tag, ",")
2✔
341
        } else {
4✔
342
                tags = fieldNameArr
2✔
343
        }
2✔
344
        for _, k := range tags {
4✔
345
                fields = f.Tag.Get(k)
2✔
346
                if fields != "" {
4✔
347
                        break
2✔
348
                }
349
        }
350
        if fields != "" {
4✔
351
                if fields == "-" {
2✔
352
                        return ""
×
353
                }
×
354
                return fields
2✔
355
        }
356
        return f.Name
×
357
}
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