@@ -7,6 +7,7 @@ package httpassert
77import (
88 "fmt"
99 "reflect"
10+ "regexp"
1011 "testing"
1112
1213 "github.com/stretchr/testify/assert"
@@ -21,68 +22,127 @@ type Extractor func(t *testing.T, actual any) any
2122// request.Expect().JsonPath("$.data.id", httpassert.ExtractTo(&id))
2223func ExtractTo (ptr any ) Extractor {
2324 return func (t * testing.T , actual any ) any {
24- targetVal := reflect .ValueOf (ptr )
25- if targetVal .Kind () != reflect .Ptr || targetVal .IsNil () {
26- assert .Fail (t , "ExtractTo requires a non-nil pointer" )
25+ return extractInto (t , ptr , actual , nil )
26+ }
27+ }
28+
29+ func ExtractRegexTo (value string , ptr any ) Extractor {
30+ return func (t * testing.T , actual any ) any {
31+ return extractInto (t , ptr , actual , func (t * testing.T , v any , dstType reflect.Type ) (any , bool ) {
32+ s , ok := v .(string )
33+ if ! ok {
34+ assert .Fail (t , fmt .Sprintf ("ExtractRegexTo expects actual to be string, got %T" , v ))
35+ return nil , false
36+ }
37+
38+ re := regexp .MustCompile (value )
39+
40+ m := re .FindStringSubmatch (s )
41+ if m == nil {
42+ assert .Fail (t , fmt .Sprintf ("ExtractRegexTo no match for %q in %q" , re .String (), s ))
43+ return nil , false
44+ }
45+
46+ // m[0] full match, m[1:] groups
47+ groups := m [1 :]
48+ if len (groups ) == 0 {
49+ groups = []string {m [0 ]}
50+ }
51+
52+ if dstType .Kind () == reflect .Slice {
53+ return groups , true
54+ }
55+ return groups [0 ], true
56+ })
57+ }
58+ }
59+
60+ func extractInto (
61+ t * testing.T ,
62+ ptr any ,
63+ actual any ,
64+ preprocess func (t * testing.T , actual any , dstType reflect.Type ) (any , bool ),
65+ ) any {
66+ target := reflect .ValueOf (ptr )
67+ if target .Kind () != reflect .Ptr || target .IsNil () {
68+ assert .Fail (t , "ExtractTo requires a non-nil pointer" )
69+ return nil
70+ }
71+ if actual == nil {
72+ assert .Fail (t , "ExtractTo actual value is nil" )
73+ return nil
74+ }
75+
76+ dst := target .Elem ()
77+ dstType := dst .Type ()
78+
79+ if preprocess != nil {
80+ var ok bool
81+ actual , ok = preprocess (t , actual , dstType )
82+ if ! ok {
2783 return nil
2884 }
29-
3085 if actual == nil {
3186 assert .Fail (t , "ExtractTo actual value is nil" )
3287 return nil
3388 }
89+ }
3490
35- outVal := reflect .ValueOf (actual )
36- targetType := targetVal .Elem ().Type ()
91+ src := reflect .ValueOf (actual )
3792
38- // Direct assign / convert for simple values (string, int, etc.)
39- if outVal .Type ().AssignableTo (targetType ) {
40- targetVal .Elem ().Set (outVal )
41- return ptr
42- }
93+ // unwrap interface{}
94+ if src .IsValid () && src .Kind () == reflect .Interface && ! src .IsNil () {
95+ src = reflect .ValueOf (src .Interface ())
96+ }
4397
44- if outVal .Type ().ConvertibleTo (targetType ) {
45- targetVal .Elem ().Set (outVal .Convert (targetType ))
46- return ptr
47- }
98+ // direct assign / convert
99+ if src .IsValid () && src .Type ().AssignableTo (dstType ) {
100+ dst .Set (src )
101+ return ptr
102+ }
103+ if src .IsValid () && src .Type ().ConvertibleTo (dstType ) {
104+ dst .Set (src .Convert (dstType ))
105+ return ptr
106+ }
48107
49- // Special handling for slices, e.g. []interface{} -> []string
50- if outVal .Kind () == reflect .Slice && targetType .Kind () == reflect .Slice {
51- elemType := targetType .Elem ()
52- n := outVal .Len ()
53- dst := reflect .MakeSlice (targetType , n , n )
54-
55- for i := 0 ; i < n ; i ++ {
56- src := outVal .Index (i )
57-
58- // If it's interface{}, unwrap to the underlying concrete value.
59- if src .Kind () == reflect .Interface && ! src .IsNil () {
60- src = reflect .ValueOf (src .Interface ())
61- }
62-
63- if src .Type ().AssignableTo (elemType ) {
64- dst .Index (i ).Set (src )
65- continue
66- }
67-
68- if src .Type ().ConvertibleTo (elemType ) {
69- dst .Index (i ).Set (src .Convert (elemType ))
70- continue
71- }
72-
73- assert .Fail (t ,
74- fmt .Sprintf ("ExtractTo slice element type mismatch at index %d: cannot assign %v to %v" ,
75- i , src .Type (), elemType ))
76- return nil
108+ // slice handling: e.g. []interface{} -> []string
109+ if src .IsValid () && src .Kind () == reflect .Slice && dstType .Kind () == reflect .Slice {
110+ elemType := dstType .Elem ()
111+ n := src .Len ()
112+ out := reflect .MakeSlice (dstType , n , n )
113+
114+ for i := 0 ; i < n ; i ++ {
115+ s := src .Index (i )
116+
117+ // unwrap interface{} elements
118+ if s .Kind () == reflect .Interface && ! s .IsNil () {
119+ s = reflect .ValueOf (s .Interface ())
120+ }
121+
122+ if s .Type ().AssignableTo (elemType ) {
123+ out .Index (i ).Set (s )
124+ continue
125+ }
126+ if s .Type ().ConvertibleTo (elemType ) {
127+ out .Index (i ).Set (s .Convert (elemType ))
128+ continue
77129 }
78130
79- targetVal .Elem ().Set (dst )
80- return ptr
131+ assert .Fail (t , fmt .Sprintf (
132+ "ExtractTo slice element type mismatch at index %d: cannot assign %v to %v" ,
133+ i , s .Type (), elemType ,
134+ ))
135+ return nil
81136 }
82137
83- assert .Fail (t ,
84- fmt .Sprintf ("ExtractTo type mismatch: cannot assign %v to %v" ,
85- outVal .Type (), targetType ))
86- return nil
138+ dst .Set (out )
139+ return ptr
140+ }
141+
142+ srcType := any ("<invalid>" )
143+ if src .IsValid () {
144+ srcType = src .Type ()
87145 }
146+ assert .Fail (t , fmt .Sprintf ("ExtractTo type mismatch: cannot assign %v to %v" , srcType , dstType ))
147+ return nil
88148}
0 commit comments