Smuggle operator allows to change data contents or mutate it into
another type before stepping down in favor of generic comparison
process. Of course it is a smuggler operator. So fn is a function
that must take one parameter whose type must be convertible to the
type of the compared value.
As convenient shortcuts, fn can be a string specifying a
fields-path through structs, maps & slices, or any other type, in
this case a simple cast is done (see below for details).
fnsThenExpectedValue must have a length of 1 or more items. Its
last item corresponds to the expectedValue. The previous items, if
any, are like fn and are used sequentially after fn allowing to
implicitly chain Smuggle calls, so the following Cmp calls are
strictly equivalent:
Each fn can return a third string value which is used to describe
the test when a problem occurred (false second boolean value):
td.Cmp(t,"0029",td.Smuggle(func(valuestring)(int,bool,string){num,err:=strconv.Atoi(value)iferr!=nil{return0,false,"string must contain a number"}returnnum,true,""},td.Between(28,30)),)
Instead of returning (X, bool) or (X, bool, string), each fn can
return (X, error). When a problem occurs, the returned error is
non-nil, as in:
In this case the data location forwarded to next test will be
something like “DATA.MyTimeField”, but you can act on it
too by returning a SmuggledGot struct (by value or by address):
then the data location forwarded to next test will be something like
“DATA.MyTimeField.Year”. The “.” between the current path (here
“DATA.MyTimeField”) and the returned Name “Year” is automatically
added when Name starts with a Letter.
Note that SmuggledGot and *SmuggledGot returns are treated
equally, and they are only used when fn has only one returned value
or when the second boolean returned value is true.
Of course, all cases can go together:
// Accepts a "YYYY/mm/DD HH:MM:SS" string to produce a time.Time and tests// whether this date is contained between 2 hours before now and now.td.Cmp(t,"2020-01-25 12:13:14",td.Smuggle(func(datestring)(*SmuggledGot,bool,string){date,err:=time.Parse("2006/01/02 15:04:05",date)iferr!=nil{returnnil,false,`date must conform to "YYYY/mm/DD HH:MM:SS" format`}return&SmuggledGot{Name:"Date",Got:date,},true,""},td.Between(time.Now().Add(-2*time.Hour),time.Now())),)
or:
// Accepts a "YYYY/mm/DD HH:MM:SS" string to produce a time.Time and tests// whether this date is contained between 2 hours before now and now.td.Cmp(t,"2020-01-25 12:13:14",td.Smuggle(func(datestring)(*SmuggledGot,error){date,err:=time.Parse("2006/01/02 15:04:05",date)iferr!=nil{returnnil,err}return&SmuggledGot{Name:"Date",Got:date,},nil},td.Between(time.Now().Add(-2*time.Hour),time.Now())),)
Smuggle can also be used to access a struct field embedded in
several struct layers.
typeAstruct{Numint}// func (a *A) String() string { return fmt.Sprintf("Num is %d", a.Num) }typeBstruct{Asmap[string]*A}typeCstruct{BB}got:=C{B:B{As:map[string]*A{"foo":{Num:12}}}}// Tests that got.B.A.Num is 12td.Cmp(t,got,td.Smuggle(func(cC)int{returnc.B.As["foo"].Num},12))
As brought up above, a fields-path can be passed as fn value
instead of a function pointer. Using this feature, the Cmp
call in the above example can be rewritten as follows:
// Tests that got.B.As["foo"].Num is 12td.Cmp(t,got,td.Smuggle("B.As[foo].Num",12))
For convenience, if a map[string]… is addressed, it can be done
like a struct field as “foo” key in:
td.Cmp(t,got,td.Smuggle("B.As.foo.Num",12))
In addition, simple public methods can also be called like in:
td.Cmp(t,got,td.Smuggle("B.As[foo].String()","Num is 12"))
Allowed methods must not take any parameter and must return one
value or a value and an error. For the latter case, if the method
returns a non-nilerror, the comparison fails. The comparison also
fails if a panic occurs or if a method cannot be called. No private
fields should be traversed before calling the method. For fun,
consider a more complex example involving reflect and chaining
method calls:
got:=reflect.Valueof(&C{B:B{As:map[string]*A{"foo":{Num:12}}}})td.Cmp(t,got,td.Smuggle("Elem().Interface().B.As[foo].String()","Num is 12"))
Contrary to JSONPointer operator, private fields can be followed
and public methods on public fields can be called. Arrays, slices
and maps work using the index/key inside square brackets (e.g. [12]
or [foo]). Maps work only for simple key types (string or numbers),
without "" when using strings (e.g. [foo]).
Behind the scenes, a temporary function is automatically created to
achieve the same goal, but adds some checks against nil values and
auto-dereferences interfaces and pointers, even on several levels,
like in:
Last but not least, a simple type can be passed as fn to operate
a cast, handling specifically strings and slices of bytes:
td.Cmp(t,`{"foo":1}`,td.Smuggle(json.RawMessage{},td.JSON(`{"foo":1}`)))// or equallytd.Cmp(t,`{"foo":1}`,td.Smuggle(json.RawMessage(nil),td.JSON(`{"foo":1}`)))
converts on the fly a string to a json.RawMessage (alias to
jsontext.Value starting go1.27) so JSON operator can parse it
as JSON. This is mostly a shortcut for:
except that for strings and slices of bytes (like here), it accepts
io.Reader interface too:
varbodyio.Reader// …td.Cmp(t,body,td.Smuggle(json.RawMessage{},td.JSON(`{"foo":1}`)))// or equallytd.Cmp(t,body,td.Smuggle(json.RawMessage(nil),td.JSON(`{"foo":1}`)))
This last example allows to easily inject body content into JSON
operator.
The difference between Smuggle and Code operators is that Code
is used to do a final comparison while Smuggle transforms the data
and then steps down in favor of generic comparison
process. Moreover, the type accepted as input for the function is
more lax to facilitate the writing of tests (e.g. the function can
accept a float64 and the got value be an int). See examples. On the
other hand, the output type is strict and must match exactly the
expected value type. The fields-path stringfn shortcut and the
cast feature are not available with Code operator.
TypeBehind method returns the reflect.Type of only parameter of
fn. For the case where fn is a fields-path, it is always
any, as the type can not be known in advance.
t:=&testing.T{}got:=int64(123)ok:=td.Cmp(t,got,td.Smuggle(func(nint64)int{returnint(n)},123),"checks int64 got against an int value")fmt.Println(ok)ok=td.Cmp(t,"123",td.Smuggle(func(numStrstring)(int,bool){n,err:=strconv.Atoi(numStr)returnn,err==nil},td.Between(120,130)),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)ok=td.Cmp(t,"123",td.Smuggle(func(numStrstring)(int,bool,string){n,err:=strconv.Atoi(numStr)iferr!=nil{return0,false,"string must contain a number"}returnn,true,""},td.Between(120,130)),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)ok=td.Cmp(t,"123",td.Smuggle(func(numStrstring)(int,error){//nolint: gocriticreturnstrconv.Atoi(numStr)},td.Between(120,130)),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)// Short version :)ok=td.Cmp(t,"123",td.Smuggle(strconv.Atoi,td.Between(120,130)),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)// Output:// true// true// true// true// true
Lax example
t:=&testing.T{}// got is an int16 and Smuggle func input is an int64: it is OKgot:=int(123)ok:=td.Cmp(t,got,td.Smuggle(func(nint64)uint32{returnuint32(n)},uint32(123)))fmt.Println("got int16(123) → smuggle via int64 → uint32(123):",ok)// Output:// got int16(123) → smuggle via int64 → uint32(123): true
Auto_unmarshal example
t:=&testing.T{}// Automatically json.Unmarshal to comparegot:=[]byte(`{"a":1,"b":2}`)ok:=td.Cmp(t,got,td.Smuggle(func(bjson.RawMessage)(rmap[string]int,errerror){err=json.Unmarshal(b,&r)return},map[string]int{"a":1,"b":2,}))fmt.Println("JSON contents is OK:",ok)// Output:// JSON contents is OK: true
Cast example
t:=&testing.T{}// A string containing JSONgot:=`{ "foo": 123 }`// Automatically cast a string to a json.RawMessage so td.JSON can operateok:=td.Cmp(t,got,td.Smuggle(json.RawMessage{},td.JSON(`{"foo":123}`)))fmt.Println("JSON contents in string is OK:",ok)// Automatically read from io.Reader to a json.RawMessageok=td.Cmp(t,bytes.NewReader([]byte(got)),td.Smuggle(json.RawMessage{},td.JSON(`{"foo":123}`)))fmt.Println("JSON contents just read is OK:",ok)// Output:// JSON contents in string is OK: true// JSON contents just read is OK: true
Complex example
t:=&testing.T{}// No end date but a start date and a durationtypeStartDurationstruct{StartDatetime.TimeDurationtime.Duration}// Checks that end date is between 17th and 19th February both at 0h// for each of these durations in hoursfor_,duration:=range[]time.Duration{48*time.Hour,72*time.Hour,96*time.Hour}{got:=StartDuration{StartDate:time.Date(2018,time.February,14,12,13,14,0,time.UTC),Duration:duration,}// Simplest way, but in case of Between() failure, error will be bound// to DATA<smuggled>, not very clear...ok:=td.Cmp(t,got,td.Smuggle(func(sdStartDuration)time.Time{returnsd.StartDate.Add(sd.Duration)},td.Between(time.Date(2018,time.February,17,0,0,0,0,time.UTC),time.Date(2018,time.February,19,0,0,0,0,time.UTC))))fmt.Println(ok)// Name the computed value "ComputedEndDate" to render a Between() failure// more understandable, so error will be bound to DATA.ComputedEndDateok=td.Cmp(t,got,td.Smuggle(func(sdStartDuration)td.SmuggledGot{returntd.SmuggledGot{Name:"ComputedEndDate",Got:sd.StartDate.Add(sd.Duration),}},td.Between(time.Date(2018,time.February,17,0,0,0,0,time.UTC),time.Date(2018,time.February,19,0,0,0,0,time.UTC))))fmt.Println(ok)}// Output:// false// false// true// true// true// true
Interface example
t:=&testing.T{}gotTime,err:=time.Parse(time.RFC3339,"2018-05-23T12:13:14Z")iferr!=nil{t.Fatal(err)}// Do not check the struct itself, but its stringified formok:=td.Cmp(t,gotTime,td.Smuggle(func(sfmt.Stringer)string{returns.String()},"2018-05-23 12:13:14 +0000 UTC"))fmt.Println("stringified time.Time OK:",ok)// If got does not implement the fmt.Stringer interface, it fails// without calling the Smuggle functypeMyTimetime.Timeok=td.Cmp(t,MyTime(gotTime),td.Smuggle(func(sfmt.Stringer)string{fmt.Println("Smuggle func called!")returns.String()},"2018-05-23 12:13:14 +0000 UTC"))fmt.Println("stringified MyTime OK:",ok)// Output:// stringified time.Time OK: true// stringified MyTime OK: false
Field_path example
t:=&testing.T{}typeBodystruct{NamestringValueany}typeRequeststruct{Body*Body}typeTransactionstruct{Request}typeValueNumstruct{Numint}got:=&Transaction{Request:Request{Body:&Body{Name:"test",Value:&ValueNum{Num:123},},},}// Want to check whether Num is between 100 and 200?ok:=td.Cmp(t,got,td.Smuggle(func(tr*Transaction)(int,error){iftr.Body==nil||tr.Body.Value==nil{return0,errors.New("Request.Body or Request.Body.Value is nil")}ifv,ok:=tr.Body.Value.(*ValueNum);ok&&v!=nil{returnv.Num,nil}return0,errors.New("Request.Body.Value isn't *ValueNum or nil")},td.Between(100,200)))fmt.Println("check Num by hand:",ok)// Same, but automagically generated...ok=td.Cmp(t,got,td.Smuggle("Request.Body.Value.Num",td.Between(100,200)))fmt.Println("check Num using a fields-path:",ok)// And as Request is an anonymous field, can be simplified further// as it can be omittedok=td.Cmp(t,got,td.Smuggle("Body.Value.Num",td.Between(100,200)))fmt.Println("check Num using an other fields-path:",ok)// Note that maps and array/slices are supportedgot.Body.Value=map[string]any{"foo":[]any{3:map[int]string{666:"bar"},},}ok=td.Cmp(t,got,td.Smuggle("Body.Value[foo][3][666]","bar"))fmt.Println("check fields-path including maps/slices:",ok)// Output:// check Num by hand: true// check Num using a fields-path: true// check Num using an other fields-path: true// check fields-path including maps/slices: true
args… are optional and allow to name the test. This name is
used in case of failure to qualify the test. If len(args) > 1 and
the first item of args is a string and contains a ‘%’ rune then
fmt.Fprintf is used to compose the name, else args are passed to
fmt.Fprint. Do not forget it is the name of the test, not the
reason of a potential failure.
t:=&testing.T{}got:=int64(123)ok:=td.CmpSmuggle(t,got,func(nint64)int{returnint(n)},123,"checks int64 got against an int value")fmt.Println(ok)ok=td.CmpSmuggle(t,"123",func(numStrstring)(int,bool){n,err:=strconv.Atoi(numStr)returnn,err==nil},td.Between(120,130),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)ok=td.CmpSmuggle(t,"123",func(numStrstring)(int,bool,string){n,err:=strconv.Atoi(numStr)iferr!=nil{return0,false,"string must contain a number"}returnn,true,""},td.Between(120,130),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)ok=td.CmpSmuggle(t,"123",func(numStrstring)(int,error){//nolint: gocriticreturnstrconv.Atoi(numStr)},td.Between(120,130),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)// Short version :)ok=td.CmpSmuggle(t,"123",strconv.Atoi,td.Between(120,130),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)// Output:// true// true// true// true// true
Lax example
t:=&testing.T{}// got is an int16 and Smuggle func input is an int64: it is OKgot:=int(123)ok:=td.CmpSmuggle(t,got,func(nint64)uint32{returnuint32(n)},uint32(123))fmt.Println("got int16(123) → smuggle via int64 → uint32(123):",ok)// Output:// got int16(123) → smuggle via int64 → uint32(123): true
Auto_unmarshal example
t:=&testing.T{}// Automatically json.Unmarshal to comparegot:=[]byte(`{"a":1,"b":2}`)ok:=td.CmpSmuggle(t,got,func(bjson.RawMessage)(rmap[string]int,errerror){err=json.Unmarshal(b,&r)return},map[string]int{"a":1,"b":2,})fmt.Println("JSON contents is OK:",ok)// Output:// JSON contents is OK: true
Cast example
t:=&testing.T{}// A string containing JSONgot:=`{ "foo": 123 }`// Automatically cast a string to a json.RawMessage so td.JSON can operateok:=td.CmpSmuggle(t,got,json.RawMessage{},td.JSON(`{"foo":123}`))fmt.Println("JSON contents in string is OK:",ok)// Automatically read from io.Reader to a json.RawMessageok=td.CmpSmuggle(t,bytes.NewReader([]byte(got)),json.RawMessage{},td.JSON(`{"foo":123}`))fmt.Println("JSON contents just read is OK:",ok)// Output:// JSON contents in string is OK: true// JSON contents just read is OK: true
Complex example
t:=&testing.T{}// No end date but a start date and a durationtypeStartDurationstruct{StartDatetime.TimeDurationtime.Duration}// Checks that end date is between 17th and 19th February both at 0h// for each of these durations in hoursfor_,duration:=range[]time.Duration{48*time.Hour,72*time.Hour,96*time.Hour}{got:=StartDuration{StartDate:time.Date(2018,time.February,14,12,13,14,0,time.UTC),Duration:duration,}// Simplest way, but in case of Between() failure, error will be bound// to DATA<smuggled>, not very clear...ok:=td.CmpSmuggle(t,got,func(sdStartDuration)time.Time{returnsd.StartDate.Add(sd.Duration)},td.Between(time.Date(2018,time.February,17,0,0,0,0,time.UTC),time.Date(2018,time.February,19,0,0,0,0,time.UTC)))fmt.Println(ok)// Name the computed value "ComputedEndDate" to render a Between() failure// more understandable, so error will be bound to DATA.ComputedEndDateok=td.CmpSmuggle(t,got,func(sdStartDuration)td.SmuggledGot{returntd.SmuggledGot{Name:"ComputedEndDate",Got:sd.StartDate.Add(sd.Duration),}},td.Between(time.Date(2018,time.February,17,0,0,0,0,time.UTC),time.Date(2018,time.February,19,0,0,0,0,time.UTC)))fmt.Println(ok)}// Output:// false// false// true// true// true// true
Interface example
t:=&testing.T{}gotTime,err:=time.Parse(time.RFC3339,"2018-05-23T12:13:14Z")iferr!=nil{t.Fatal(err)}// Do not check the struct itself, but its stringified formok:=td.CmpSmuggle(t,gotTime,func(sfmt.Stringer)string{returns.String()},"2018-05-23 12:13:14 +0000 UTC")fmt.Println("stringified time.Time OK:",ok)// If got does not implement the fmt.Stringer interface, it fails// without calling the Smuggle functypeMyTimetime.Timeok=td.CmpSmuggle(t,MyTime(gotTime),func(sfmt.Stringer)string{fmt.Println("Smuggle func called!")returns.String()},"2018-05-23 12:13:14 +0000 UTC")fmt.Println("stringified MyTime OK:",ok)// Output:// stringified time.Time OK: true// stringified MyTime OK: false
Field_path example
t:=&testing.T{}typeBodystruct{NamestringValueany}typeRequeststruct{Body*Body}typeTransactionstruct{Request}typeValueNumstruct{Numint}got:=&Transaction{Request:Request{Body:&Body{Name:"test",Value:&ValueNum{Num:123},},},}// Want to check whether Num is between 100 and 200?ok:=td.CmpSmuggle(t,got,func(tr*Transaction)(int,error){iftr.Body==nil||tr.Body.Value==nil{return0,errors.New("Request.Body or Request.Body.Value is nil")}ifv,ok:=tr.Body.Value.(*ValueNum);ok&&v!=nil{returnv.Num,nil}return0,errors.New("Request.Body.Value isn't *ValueNum or nil")},td.Between(100,200))fmt.Println("check Num by hand:",ok)// Same, but automagically generated...ok=td.CmpSmuggle(t,got,"Request.Body.Value.Num",td.Between(100,200))fmt.Println("check Num using a fields-path:",ok)// And as Request is an anonymous field, can be simplified further// as it can be omittedok=td.CmpSmuggle(t,got,"Body.Value.Num",td.Between(100,200))fmt.Println("check Num using an other fields-path:",ok)// Note that maps and array/slices are supportedgot.Body.Value=map[string]any{"foo":[]any{3:map[int]string{666:"bar"},},}ok=td.CmpSmuggle(t,got,"Body.Value[foo][3][666]","bar")fmt.Println("check fields-path including maps/slices:",ok)// Output:// check Num by hand: true// check Num using a fields-path: true// check Num using an other fields-path: true// check fields-path including maps/slices: true
Returns true if the test is OK, false if it fails.
args… are optional and allow to name the test. This name is
used in case of failure to qualify the test. If len(args) > 1 and
the first item of args is a string and contains a ‘%’ rune then
fmt.Fprintf is used to compose the name, else args are passed to
fmt.Fprint. Do not forget it is the name of the test, not the
reason of a potential failure.
t:=td.NewT(&testing.T{})got:=int64(123)ok:=t.Smuggle(got,func(nint64)int{returnint(n)},123,"checks int64 got against an int value")fmt.Println(ok)ok=t.Smuggle("123",func(numStrstring)(int,bool){n,err:=strconv.Atoi(numStr)returnn,err==nil},td.Between(120,130),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)ok=t.Smuggle("123",func(numStrstring)(int,bool,string){n,err:=strconv.Atoi(numStr)iferr!=nil{return0,false,"string must contain a number"}returnn,true,""},td.Between(120,130),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)ok=t.Smuggle("123",func(numStrstring)(int,error){//nolint: gocriticreturnstrconv.Atoi(numStr)},td.Between(120,130),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)// Short version :)ok=t.Smuggle("123",strconv.Atoi,td.Between(120,130),"checks that number in %#v is in [120 .. 130]")fmt.Println(ok)// Output:// true// true// true// true// true
Lax example
t:=td.NewT(&testing.T{})// got is an int16 and Smuggle func input is an int64: it is OKgot:=int(123)ok:=t.Smuggle(got,func(nint64)uint32{returnuint32(n)},uint32(123))fmt.Println("got int16(123) → smuggle via int64 → uint32(123):",ok)// Output:// got int16(123) → smuggle via int64 → uint32(123): true
Auto_unmarshal example
t:=td.NewT(&testing.T{})// Automatically json.Unmarshal to comparegot:=[]byte(`{"a":1,"b":2}`)ok:=t.Smuggle(got,func(bjson.RawMessage)(rmap[string]int,errerror){err=json.Unmarshal(b,&r)return},map[string]int{"a":1,"b":2,})fmt.Println("JSON contents is OK:",ok)// Output:// JSON contents is OK: true
Cast example
t:=td.NewT(&testing.T{})// A string containing JSONgot:=`{ "foo": 123 }`// Automatically cast a string to a json.RawMessage so td.JSON can operateok:=t.Smuggle(got,json.RawMessage{},td.JSON(`{"foo":123}`))fmt.Println("JSON contents in string is OK:",ok)// Automatically read from io.Reader to a json.RawMessageok=t.Smuggle(bytes.NewReader([]byte(got)),json.RawMessage{},td.JSON(`{"foo":123}`))fmt.Println("JSON contents just read is OK:",ok)// Output:// JSON contents in string is OK: true// JSON contents just read is OK: true
Complex example
t:=td.NewT(&testing.T{})// No end date but a start date and a durationtypeStartDurationstruct{StartDatetime.TimeDurationtime.Duration}// Checks that end date is between 17th and 19th February both at 0h// for each of these durations in hoursfor_,duration:=range[]time.Duration{48*time.Hour,72*time.Hour,96*time.Hour}{got:=StartDuration{StartDate:time.Date(2018,time.February,14,12,13,14,0,time.UTC),Duration:duration,}// Simplest way, but in case of Between() failure, error will be bound// to DATA<smuggled>, not very clear...ok:=t.Smuggle(got,func(sdStartDuration)time.Time{returnsd.StartDate.Add(sd.Duration)},td.Between(time.Date(2018,time.February,17,0,0,0,0,time.UTC),time.Date(2018,time.February,19,0,0,0,0,time.UTC)))fmt.Println(ok)// Name the computed value "ComputedEndDate" to render a Between() failure// more understandable, so error will be bound to DATA.ComputedEndDateok=t.Smuggle(got,func(sdStartDuration)td.SmuggledGot{returntd.SmuggledGot{Name:"ComputedEndDate",Got:sd.StartDate.Add(sd.Duration),}},td.Between(time.Date(2018,time.February,17,0,0,0,0,time.UTC),time.Date(2018,time.February,19,0,0,0,0,time.UTC)))fmt.Println(ok)}// Output:// false// false// true// true// true// true
Interface example
t:=td.NewT(&testing.T{})gotTime,err:=time.Parse(time.RFC3339,"2018-05-23T12:13:14Z")iferr!=nil{t.Fatal(err)}// Do not check the struct itself, but its stringified formok:=t.Smuggle(gotTime,func(sfmt.Stringer)string{returns.String()},"2018-05-23 12:13:14 +0000 UTC")fmt.Println("stringified time.Time OK:",ok)// If got does not implement the fmt.Stringer interface, it fails// without calling the Smuggle functypeMyTimetime.Timeok=t.Smuggle(MyTime(gotTime),func(sfmt.Stringer)string{fmt.Println("Smuggle func called!")returns.String()},"2018-05-23 12:13:14 +0000 UTC")fmt.Println("stringified MyTime OK:",ok)// Output:// stringified time.Time OK: true// stringified MyTime OK: false
Field_path example
t:=td.NewT(&testing.T{})typeBodystruct{NamestringValueany}typeRequeststruct{Body*Body}typeTransactionstruct{Request}typeValueNumstruct{Numint}got:=&Transaction{Request:Request{Body:&Body{Name:"test",Value:&ValueNum{Num:123},},},}// Want to check whether Num is between 100 and 200?ok:=t.Smuggle(got,func(tr*Transaction)(int,error){iftr.Body==nil||tr.Body.Value==nil{return0,errors.New("Request.Body or Request.Body.Value is nil")}ifv,ok:=tr.Body.Value.(*ValueNum);ok&&v!=nil{returnv.Num,nil}return0,errors.New("Request.Body.Value isn't *ValueNum or nil")},td.Between(100,200))fmt.Println("check Num by hand:",ok)// Same, but automagically generated...ok=t.Smuggle(got,"Request.Body.Value.Num",td.Between(100,200))fmt.Println("check Num using a fields-path:",ok)// And as Request is an anonymous field, can be simplified further// as it can be omittedok=t.Smuggle(got,"Body.Value.Num",td.Between(100,200))fmt.Println("check Num using an other fields-path:",ok)// Note that maps and array/slices are supportedgot.Body.Value=map[string]any{"foo":[]any{3:map[int]string{666:"bar"},},}ok=t.Smuggle(got,"Body.Value[foo][3][666]","bar")fmt.Println("check fields-path including maps/slices:",ok)// Output:// check Num by hand: true// check Num using a fields-path: true// check Num using an other fields-path: true// check fields-path including maps/slices: true