Go template can't call method on field - templates

I have a wrapper around net/mail.Address that provides some marshalling logic. I'm trying to use it in a template, but I keep getting can't evaluate field String in type EmailAddress. The template docs say:
The name of a niladic method of the data, preceded by a period,
such as
.Method
The result is the value of invoking the method with dot as the
receiver, dot.Method().
and
Method invocations may be chained and combined with fields and keys
to any depth:
.Field1.Key1.Method1.Field2.Key2.Method2
So with that in mind I've written this:
package main
import (
"bytes"
"fmt"
"html/template"
"net/mail"
"os"
)
type EmailAddress struct{ mail.Address }
type emailFormatter struct {
From EmailAddress
To EmailAddress
}
var tmpl = template.Must(template.New("Sample Text").Parse("From: {{.From.String}}\r" + `
To: {{.To.String}}` + "\r" + `
Content-Type: text/html` + "\r" + `
Subject: Sample Text` + "\r\n\r" + `
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sample Text</title>
<meta charset="utf-8"/>
</head>
<body>
Sample Text
</body>
</html>
`));
func main() {
to := EmailAddress{
mail.Address{
Address: "em#i.l",
Name: "",
},
}
from := EmailAddress{
mail.Address{
Address: "no-reply#test.quest",
Name: "",
},
}
fmt.Println(to.String()) //outputs (as expected) "<em#i.l>"
fmt.Println(from.String()) //outputs (as expected) "<no-reply#test.quest>"
f := emailFormatter{
To: to,
From: from,
}
var buff bytes.Buffer
if err := tmpl.Execute(&buff, f); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Println(buff.String())
}
I've separately verified that calling EmailAddress.String is totally legal, so I can't figure out why the only output of this is:
Error: template: Sample Text:1:13: executing "Sample Text" at <.From.String>: can't evaluate field String in type main.EmailAddress
EDIT
At a commenter's suggestion I changed the calls from .From.String and .To.String to .From.Address.String and .To.Address.String, because
"String isn't defined on EmailAddress, it's defined on EmailAddress.Address"
but the result is the same:
Error: template: Sample Text:1:13: executing "Sample Text" at <.From.Address.String>: can't evaluate field String in type mail.Address

Since String is defined with a pointer receiver you need to pass an "addressable" instance of mail.Address to the template to be able to execute that method.
You can do this by passing in a pointer to f.
if err := tmpl.Execute(&buff, &f); err != nil {
panic(err)
}
Or you can do it by passing in pointer to EmailAddress.
type emailFormatter struct {
From *EmailAddress
To *EmailAddress
}
// ...
f := emailFormatter{
To: &to,
From: &from,
}
// ...
if err := tmpl.Execute(&buff, f); err != nil {
panic(err)
}
Or by passing in a pointer to mail.Address.
type EmailAddress struct{ *mail.Address }
// ...
to := EmailAddress{
&mail.Address{
Address: "em#i.l",
Name: "",
},
}
from := EmailAddress{
&mail.Address{
Address: "no-reply#test.quest",
Name: "",
},
}
f := emailFormatter{
To: to,
From: from,
}
// ...
if err := tmpl.Execute(&buff, f); err != nil {
panic(err)
}
Note that the reason you don't need to do that in the Go code is because there the compiler does it for you.
For example:
fmt.Println(to.String())
becomes:
fmt.Println((&to).String())
A method call x.m() is valid if the method set of (the type of) x
contains m and the argument list can be assigned to the parameter list
of m. If x is addressable and &x's method set contains m, x.m() is
shorthand for (&x).m()

Related

assert: mock: I don't know what to return (even if I've declared the mock function & the return)

I use Testify to create a unit test for my golang app. I need to create a unit test for this function where it calls a variadic function (function with trailing arguments). I encountered an error when I test it. I'm actually not sure if the error is because of the trailing argument itself or not, but I feel like there's something wrong with the mock.
// svc/callThisFunction.go
// data type of args is []sqkit.SelectOption
func CallThisFunction(ctx context.Context, args ...sqkit.SelectFunctiom) (result string, err error) {
return result, nil
}
// svc/functionToTest.go
// This is the function that I wanna test
func FunctionToTest(ctx context.Context, id int64) (result string, err error) {
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", id),
},
}
newResult, err := callThisFunctionService.CallThisFunction(ctx, args)
if err != nil {
return newResult, err
}
return newResult, nil
}
// svc/functionToTest_test.go
func Test_FunctionToTest(t *testing.T) {
testCase := []struct {
name string
id int64
onCallThisFunctionMock func(callThisFunctionSvc *mocks.CallThisFunctionSvc)
expectedResult string
wantError bool
expectedError error
}{
{
name: "Success",
id: 1,
onCallThisFunctionMock: func(callThisFunctionSvc *mocks.CallThisFunctionSvc) {
// NOTE: I've created 2 different versions (used separately, not at the same), using mock.Anything() and using actual arguments
// Both of these give the same errors
// Using actual arguments
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", 1},
},
}
callThisFunctionSvc.On("CallThisFunction", context.Background(), args).Return("Success", nil)
// Using mock.Anything
callThisFunctionSvc.On("CallThisFunction", context.Background(), mock.Anything).Return("Success", nil)
}
}
}
for _, tc := range testCases {
var callThisFunctionSvc = new(mocks.CallThisFunctionSvc)
tc.onCallThisFunctionMock(callThisFunctionSvc)
svc := &svc.FunctionToTest{
CallThisFunction: callThisFunctionSvc,
}
actualResult, actualError := svc.FunctionToTest(context.Background(), tc.id)
if tc.wantEror {
require.Error(t, actualError, tc.expectedError)
} else {
require.NoError(t, actualError)
}
require.Equal(t, tc.expectedResult, actualResult)
}
}
This is the error it gives
=== RUN Test_GenerateDocument
--- FAIL: Test_GenerateDocument (0.00s)
panic:
assert: mock: I don't know what to return because the method call was unexpected.
Either do Mock.On("CallThisFunction").Return(...) first, or remove the GetTemplates() call.
This method was unexpected:
CallThisFunction(*context.emptyCtx,sqkit.Where)
0: (*context.emptyCtx)(0xc0000a4010)
1: sqkit.Where{"id = 1"}
Usually, when I encountered an error like this, it's because I haven't defined the return values of the function calls inside the function I wanna test. But this time I've created it, but it somehow can't read the return. Any idea why?
The error indicates you called CallThisFuncion with params (context.Context, sqkit.Where), but your example is using and setting the expectation for (context.Context, []sqkit.Option). The example with mock.Anything should work, but I believe it's failing because of the context. You'll need to set the expectation with the same context you're passing down. If FunctionToTest is going to be altering the context, I believe you'll need to use mock.Anything instead.
func Test_FunctionToTest(t *testing.T) {
testCase := []struct {
name string
id int64
onCallThisFunctionMock func(context.Context, *mocks.CallThisFunctionSvc)
expectedResult string
wantError bool
expectedError error
}{
{
name: "Success",
id: 1,
onCallThisFunctionMock: func(ctx context.Context, callThisFunctionSvc *mocks.CallThisFunctionSvc) {
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", 1},
},
}
callThisFunctionSvc.On("CallThisFunction", ctx, args).Return("Success", nil)
}
}
}
for _, tc := range testCases {
var callThisFunctionSvc = new(mocks.CallThisFunctionSvc)
var ctx = context.Background()
tc.onCallThisFunctionMock(ctx, callThisFunctionSvc)
svc := &svc.FunctionToTest{
CallThisFunction: callThisFunctionSvc,
}
actualResult, actualError := svc.FunctionToTest(ctx, tc.id)
if tc.wantEror {
require.Error(t, actualError, tc.expectedError)
} else {
require.NoError(t, actualError)
}
require.Equal(t, tc.expectedResult, actualResult)
}
}
If you want to ensure a context.Context was passed as the first parameter but don't care what context, you could use AnythingOfType.
callThisFunctionSvc.On("CallThisFunction", mock.AnythingOfType("context.Context"), args).Return("Success", nil)

How to create new object from interface without knowing the explicit type in Go? [duplicate]

This question already has answers here:
How do you create a new instance of a struct from its type at run time in Go?
(5 answers)
Closed 9 months ago.
Working example files included at the end.
I have a package to assist in testing api handlers by creating test http contexts.
The issue is in the AssertJSONResponseBody. The problem is that once the concrete type and value are extracted from the interface, it contains a pointer to the original object. Any changes to the extracted object affect the original object. This then makes the following equal comparison useless because essentially they are pointing to the same value.
Here is the struct:
type TestRequest struct {
Recorder *httptest.ResponseRecorder
Context *gin.Context
t *testing.T
}
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, mesgAndArgs ...interface{}) bool {
outObject := reflect.Indirect(reflect.ValueOf(expectedObj)).Addr().Interface()
// Set break point at next line and compare expectedObject with outObject.
// Then, step over the line and watch the values for both objects change.
// When the decoder unmarshals the json the original object is changed
// because of the pointer in the outobject.
err := json.NewDecoder(r.Recorder.Body).Decode(outObject)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, outObject, mesgAndArgs...)
}
How do I create a new instance of the underlying type without coupling it to the original value via a pointer?
Here are the working example files.
APIHandler/main.go
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
const (
JSONBindingError string = "An error occurred binding the request."
EmailRequiredError string = "Email is required."
)
func main() {
router := gin.Default()
v1 := router.Group("/api/v1/contacts")
{
v1.POST("/", CreateContactHandler)
}
router.Run()
fmt.Printf("hello, world\n")
}
func CreateContactHandler(c *gin.Context) {
request := CreateContactRequest{}
err := c.Bind(&request)
if err != nil {
log.Println("ERROR:", JSONBindingError, err)
apiError := APIError{StatusCode: http.StatusBadRequest, Message: JSONBindingError}
c.JSON(http.StatusBadRequest, apiError)
return
}
if request.Contact.Email == "" {
log.Println("ERROR:", http.StatusBadRequest, EmailRequiredError)
apiError := APIError{StatusCode: http.StatusBadRequest, Message: EmailRequiredError}
c.JSON(http.StatusBadRequest, apiError)
return
}
// Successful client request
// resp := h.Client.CreateContact(request)
// c.JSON(resp.StatusCode, resp.Body)
}
type CreateContactRequest struct {
Contact Contact
}
type Contact struct {
Name string
Email string
}
type CreateContactResponse struct {
Message string
}
type APIError struct {
StatusCode int `json:"status"` // Should match the response status code
Message string `json:"message"`
}
type APIResponse struct {
StatusCode int
Body interface{}
}
APIHandler/helpers/http.go
package helpers
import (
"bytes"
"encoding/json"
"net/http/httptest"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/gin-gonic/gin"
)
// TestRequest is a struct to facilitate
// HTTP Context testing with gin handlers
type TestRequest struct {
Recorder *httptest.ResponseRecorder
Context *gin.Context
t *testing.T
}
// NewTestRequest returns a new TestRequest
func NewTestRequest(t *testing.T) *TestRequest {
rec := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(rec)
ctx.Request = httptest.NewRequest("GET", "/", nil)
return &TestRequest{
Recorder: rec,
Context: ctx,
t: t,
}
}
// SetJSONRequestBody returns a new TestRequest where the request is a post.
// Takes an interface to marshal into JSON and set as the request body.
func (r *TestRequest) SetJSONRequestBody(obj interface{}) *TestRequest {
json, err := json.Marshal(obj)
assert.NoError(r.t, err)
r.Context.Request = httptest.NewRequest("POST", "/", bytes.NewBuffer(json))
r.Context.Request.Header.Add("Content-Type", "application/json")
return r
}
// AssertStatusCode asserts that the recorded status
// is the same as the submitted status.
// The message and the args are added to the message
// when the assertion fails.
func (r *TestRequest) AssertStatusCode(expectedCode int, msgAndArgs ...interface{}) bool {
return assert.Equal(r.t, expectedCode, r.Recorder.Code, msgAndArgs...)
}
// AssertJSONResponseBody asserts that the recorded
// response body unmarshals to the given interface.
// The message and the args are added to the message
// when the assertion fails.
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, masgAndArgs ...interface{}) bool {
out := reflect.Indirect(reflect.ValueOf(expectedObj)).Addr().Interface()
err := json.NewDecoder(r.Recorder.Body).Decode(out)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, out, masgAndArgs...)
}
APIHandler/main_test.go
package main
import (
"APIHandler/helpers"
"net/http"
"testing"
)
func TestSingleContactCreate(t *testing.T) {
tests := []struct {
toCreate interface{}
handlerExpected APIError
clientReturn APIResponse
statusCode int
}{
// when there is a JSON binding error
{toCreate: "",
handlerExpected: APIError{StatusCode: http.StatusBadRequest, Message: EmailRequiredError},
clientReturn: APIResponse{},
statusCode: http.StatusBadRequest},
// when email is missing
{toCreate: CreateContactRequest{
Contact: Contact{
Name: "test",
}},
handlerExpected: APIError{StatusCode: http.StatusBadRequest, Message: JSONBindingError},
clientReturn: APIResponse{},
statusCode: http.StatusBadRequest},
}
// act
for i, test := range tests {
req := helpers.NewTestRequest(t)
req.SetJSONRequestBody(test.toCreate)
CreateContactHandler(req.Context)
// assert
req.AssertStatusCode(test.statusCode, "Test %d", i)
req.AssertJSONResponseBody(&test.handlerExpected, "Test %d", i)
}
}
Here's the resolution per #mkopriva:
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, masgAndArgs ...interface{}) bool {
elem := reflect.TypeOf(expectedObj)
theType := elem.Elem()
newInstance := reflect.New(theType)
out := newInstance.Interface()
err := json.NewDecoder(r.Recorder.Body).Decode(out)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, out, masgAndArgs...)
}

How to access a struct slice element key directly in a template file

I am loading some template files and attempting to compile them with some structs I've defined.
The following example is working. I'd like to know if there is a better way of formatting my templateFile to directly access config.Servers[1].Ip1 without needing two sets of {{}}
templateFile:
{{$a := index .Servers 1 }}{{$a.Ip1}} some extra text
learn.go:
package main
import (
"html/template"
"os"
)
type Server struct {
Ip1 string
Ip2 string
}
type Configuration struct {
Servers []Server
}
func main() {
someServers := []Server{
{
Ip1: "1.1.1.1",
Ip2: "2.2.2.2",
},
{
Ip1: "3.3.3.3",
Ip2: "4.4.4.4",
},
}
config := Configuration{
Servers: someServers,
}
tmpl, err := template.ParseFiles("./templateFile")
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, config)
if err != nil {
panic(err)
}
}
Please refer this:
https://golang.org/pkg/html/template/
You have to use {{}} in your HTML template, if you wish to access any Struct variable.

Add to array from json and execute data in template

I have a little qiestion!
How add to array data from json and execute template. Simple. But Not working!
package main
import (
"fmt"
"html/template"
"os"
"encoding/json"
)
type Person struct {
Name string
Jobs []*Job
}
type Job struct {
Employer string
Role string
}
const templ = `The name is {{.Name}}.
{{with .Jobs}}
{{range .}}
An employer is {{.Employer}}
and the role is {{.Role}}
{{end}}
{{end}}
`
func main() {
job1 := Job{Employer: "Monash", Role: "Honorary"}
job2 := Job{Employer: "Box Hill", Role: "Head of HE"}
byt := []byte(`{"num":6.13,"Jobs":[{"Employer": "test1", "Role": "test1"},{"Employer": "test2", "Role": "test2"}]}`)
var dat map[string]interface{}
if err := json.Unmarshal(byt, &dat); err != nil {
panic(err)
}
fmt.Println(dat)
// HOW ADD FROM ARRAY 'dat' TO STRUCT 'Job'
// LINE 54
person := Person{
Name: "jan",
Jobs: []*Job{&job1, &job2},
}
t := template.New("Person template")
t, err := t.Parse(templ)
checkError(err)
err = t.Execute(os.Stdout, person)
checkError(err)
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
Here you can play/test code: http://play.golang.org/p/AB8hGLrLRy
Watch line 46.
Thank you very much!
First thing, your dat var isn't of the right type. You should have something like that:
dat := struct {
Jobs []*Job
}
That way, when unmarshalling your JSON string into dat, the Jobs key will be filled with a array of *Job initialized with your data's array.
I'm using an anonymous struct, but you could also have a named struct (see #RoninDev's comment for an example).
Then, just add them to the person array, something like that:
person.Jobs = append(person.Jobs, jobs.Jobs...)
Note the ... operator, which use an array as a variadic argument.
You can see the full working code on this playground.
You're unmarshalling the json fine, it's just into the most ambiguous type the language offers... You could unbox it with a series of type asserts but I don't recommend it. The json can easily be modeled in Go with a structure like the following;
type Wrapper struct {
Num float64 `json:"num"`
Jobs []*Job `json:"Jobs"`
}
Here's an updated example of your code using that struct; http://play.golang.org/p/aNLK_Uk2km
After you've deserialized them you can just use append to add them to the jobs array in the person object like person.Jobs = append(person.Jobs, dat.Jobs...)

Quoted string in templates

I want to put in my html template (actually JavaScript part) null or string with email:
var email = null;
// or a string:
var email = "somebody#somewhere.com";
But with template
var email = {{.Email}};
I get quoted string everytime:
var email = "null";
var email = "somebody#somewhere.com";
How to fix it?
EDIT: here is my code: http://play.golang.org/p/8k4s8dv2PE
You can see Go surrounds string with quotes and removes comment - there is pre or post processing.
Without seeing your code, the best I can do is provide an example that shows your required functionality.
If I had to take a guess, I'd say the data you were passing in was a string, not a string pointer. A string cannot have a value of nil/null, only a string pointer can. Though that would yield a "", not a "null", (as demonstrated here), so I can only imagine that this is something weird you're doing in your code.
To prevent link-rot, I've duplicated the code examples below, though it's worth clicking through the links to see their output.
Required Functionality:
package main
import (
"log"
"os"
"html/template"
)
type TemplateData struct {
Email *string
}
func main() {
const temp = "<script>var email = {{.Email}};</script>\n"
t := template.Must(template.New("email_template").Parse(temp))
email := "somebody#somewhere.com"
err := t.Execute(os.Stdout, TemplateData{
Email: &email,
})
if err != nil {
log.Println("executing template:", err)
}
err = t.Execute(os.Stdout, TemplateData{
Email: nil,
})
if err != nil {
log.Println("executing template:", err)
}
}
Trying to use a "null" string:
package main
import (
"log"
"os"
"html/template"
)
type TemplateData struct {
Email string
}
func main() {
const temp = "<script>var email = {{.Email}};</script>\n"
t := template.Must(template.New("email_template").Parse(temp))
email := "somebody#somewhere.com"
err := t.Execute(os.Stdout, TemplateData{
Email: email,
})
if err != nil {
log.Println("executing template:", err)
}
err = t.Execute(os.Stdout, TemplateData{
})
if err != nil {
log.Println("executing template:", err)
}
}
Using the least amount of editing to your code sample:
type TplCtx struct {
UserEmail *string
}
and
t.Execute(os.Stdout, &TplCtx{nil})