I'm trying to define a custom Go func to use in a template condition.
What I want to achieve is : if the given argument is an IPv4 address the template will output IPv4: [argument] else it will output IPv6: [argument].
To do that I have created a template like this:
{{ range .Addresses }}
{{ if IsIPv4 . }}
IPv4: {{ . }}
{{ else }}
IPv6: {{ . }}
{{ end }}
{{ end }}
As you can see I have create a new function called IsIPv4 which take a string argument and given the argument return true or false. Here is the code:
var (
errNotAnIPAddress = errors.New("The argument is not an IP address")
)
func isIPv4(address string) (bool, error) {
ip := net.ParseIP(address)
if ip == nil {
return false, errNotAnIPAddress
}
return (ip.To4() != nil), nil
}
When executing my template I have got no errors but the execution looks like to stop when trying to evaluate {{ if IsIPv4 . }}.
Of course the function is mapped before trying to parse and execute the template.
funcMap := template.FuncMap{
"IsIPv4": isIPv4,
}
I'm pretty new to Go and I have probably missed something (maybe obvious?).
To debug a little bit I have tried to remove the IsIPv4 call in my template giving a condition like {{ if . }}. And the result was to always output IPv4: [the IP address]. That makes sense to me.
I also took a look at the predefined Go funcs like eq and it looks like I'm trying to reproduce the same idea, without any success.
TL;DR; You must inspect the error value returned by template.Execute() or template.ExecuteTemplate() which will tell you why it doesn't work for you.
Things that could go wrong
First, your execution doesn't panic because it simply returns an error. Would you inspect that, you'll probably know what's going wrong immediately:
if err := t.Execute(os.Stdout, data); err != nil {
panic(err)
}
Next what could go wrong: you have to register your custom functions prior to parsing the template because the template engine needs to be able to statically analyze the template, and it needs to know prior that IsIPv4 is a function name:
t := template.Must(template.New("").Funcs(template.FuncMap{
"IsIPv4": isIPv4,
}).Parse(templ))
Another potential error is that isIPv4() expects a value of type string, and you may pass a value of different type.
And custom functions registered for templates should only return a non-nil error if you intend to stop the execution of the template. Because that's what happens if you return an error. template.FuncMap:
[...] if the second (error) return value evaluates to non-nil during execution, execution terminates and Execute returns that error.
Working example
Here's a working example using your template and your isIPv4() function:
t := template.Must(template.New("").Funcs(template.FuncMap{
"IsIPv4": isIPv4,
}).Parse(templ))
m := map[string]interface{}{
"Addresses": []string{"127.0.0.1", "0:0:0:0:0:0:0:1"},
}
if err := t.Execute(os.Stdout, m); err != nil {
panic(err)
}
Output (try it on the Go Playground):
IPv4: 127.0.0.1
IPv6: 0:0:0:0:0:0:0:1
Potential errors
The above program prints the following errors:
Passing an invalid IP:
"Addresses": []string{"127.0.0.1.9"},
Output:
panic: template: :2:6: executing "" at <IsIPv4 .>: error calling IsIPv4:
The argument is not an IP address
Passing a non-string value:
"Addresses": []interface{}{2},
Output:
panic: template: :2:13: executing "" at <.>: wrong type for value; expected string; got int
Attempting to register your custom function after the template has been parsed:
t := template.Must(template.New("").Parse(templ))
t.Funcs(template.FuncMap{
"IsIPv4": isIPv4,
})
Output:
panic: template: :2: function "IsIPv4" not defined
Related
Here is my minimal .proto file:
syntax = "proto3";
message getDhtParams {}
message DhtContents {
string dht_contents=1;
}
service MyApp {
rpc getDhtContent(getDhtParams) returns (DhtContents) {}
}
Two things to note related to the above proto file:
It is a minimal file. There is a lot more to it.
The server is already generated and running. The server is implemented in Python.
I am writing client in Go. And this is the fetching code I have come up with:
func fetchDht() (*pb.DhtContents, error) {
// Set up a connection to the server.
address := "localhost:9998"
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewMyAppClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := client.GetDhtContent(ctx, &pb.GetDhtParams{})
if err != nil {
return nil, errors.New("could not get dht contents")
}
return r, nil
}
For sake of simplicity, I have tripped down the output, but the output looks something like this:
dht_contents:"{'node_ids': ['dgxydhlqoopevxv'], 'peer_addrs': [['192.168.1.154', '41457']], 'peer_meta': [{'peer_id': {'nodeID': 'dgxydhlqoopevxv', 'key': 'kdlvjdictuvgxspwkdizqryr', 'mid': 'isocvavbtzkxeigkkrubzkcx', 'public_key': 'uhapwxnfeqqmnojsaijghhic', '_address': 'xklqlebqngpkxb'}, 'ip_addrs': ['192.168.1.154', '41457'], 'services': [{'service_input': '', 'service_output': '', 'price': 0}], 'timestamp': 1661319968}]}"
A few things to note about this response:
It starts with dht_contents: which I know is a field of DhtContents message.
This could be an issue from the server side; in that case I will inform the service developer. But the json enclosed is not a valid JSON as it uses single quotes.
My questions:
What is an elegant way to deal with that dht_contents? There must be the protobuf/grpc way. I aim to get the contents between double quotes.
How do I convert the content to JSON? I have already created the struct to unmarshal.
It would be enough if I am also able to convert the response which is of type *pb.DhtContents to []byte, from there I can convert it to JSON.
The generated code should have a method which will get rid of dht_contents:" from the start and " from the end.
In your case, that method should be called GetDhtContents().
You can modify your fetchDht function to something like this:
func fetchDht() (string, error) {
address := "localhost:9998"
// ...
if err != nil {
return nil, errors.New("could not get dht contents")
}
return r.GetDhtContents(), nil
}
From there on, you can work on making it a valid JSON by replacing single quotes to double quotes. Or it may be handled on the service end.
there is the methods generated by proto file to get the content from the result(the "r"), then use r.Get..., you could get the content.
convert string to the type you want.
suggest:
change proto type to bytes
then json.Unmarshal([r.Get...],[dst])
This seems to be an easy question, but I haven't figured out how to do it: I have a nested map item, which when printed out is like the following:
fmt.Println("s3Info:", s3Info)
printout:
s3Info: [[map[s3Config:map[bucket:testbucket-data s3Key:runs/6033fd684304200011ef3bc5/init/03a78d21-446a-41bc-b4c1-eb66e04f45e2/52c8a076-f6c4-4180-8625-38ca52482628] size:158971 type:s3 varType:File]]
I wonder how can I get the value of bucket and s3Key from the object s3Info?
I tried to use s3Info.s3Config to access s3Config, but then got the following error:
go/api_default_service_data_item.go:659:46: s3Info.s3Config undefined (type interface {} is interface with no methods)
I also tried to use s3Info["s3Config"] to access s3Config, but then got the following error:
go/api_default_service_data_item.go:660:46: invalid operation: s3Info["s3Config"] (type interface {} does not support indexing)
ADDED:
The code is part of a program which processes the query response from an API endpoint, the following is the code:
var runData map[string]interface{}
json.Unmarshal(body, &runData)
p := runData["p"].(map[string]interface{})
init := p["init"].(map[string]interface{})
outputs := init["outputs"].(map[string]interface{})
for key, s3Info := range outputs {
// printout s3Info
fmt.Println("s3Info:", s3Info)
// check type
switch c := s3Info.(type) {
case string:
fmt.Println("Key:", key, "=>", "s3Info:", s3Info)
default:
fmt.Printf("s3Info Type: %T\n", c)
}
// type assert to map
s3Info := outputs[key].(map[string]interface{})
fmt.Println("Key:", key, "=>", "s3Config:", s3Info["s3Config"])
}
The printout is as follows:
s3Info: [map[s3Config:map[bucket:testbucket-data s3Key:runs/6033fd684304200011ef3bc5/init/03a78d21-446a-41bc-b4c1-eb66e04f45e2/52c8a076-f6c4-4180-8625-38ca52482628] size:158971 type:s3 varType:File]]
s3Info Type: []interface {}
interface conversion: interface {} is []interface {}, not map[string]interface {}
The s3Info was unmarshalled by json.Unmarshal() into an array of interface{} but not a map. The contents inside can be retrieved through type assertion to []interface{}.
s3Config can be obtained through:
for _, s := range s3Info.([]interface{}) {
s3Config := s.(map[string]interface{})["s3Config"]
}
Thanks #brits for the useful link:
I'm a beginner in Go and Echo.
I am required to store a html template(email template) , which will also have some details passed as context. So that it can be stored into body column (text in MySQL) and will be triggered later.
if user.Email !=""{
visitingDetails := H{"user_name" : user.Fname,
"location" : location.Name,
"visitor_company": visitor.Company,
"visitor_name" : visitor.Fname +" "+visitor.Lname,
"visitor_phone" : visitor.Phone,
"visitor_email" : visitor.Email,
"visitor_fname" : visitor.Fname,
"visitor_image" : visitor.ProfilePicture,
}
subject := visitor.Fname +" has come to visit you at the reception"
body := c.Render(http.StatusOK,"email/user_notify_email.html",visitingDetails)
emailJob := models.EmailJob{Recipients: visitor.Email , Subject: subject, Body: body}
db.Create(&emailJob)
if db.NewRecord(emailJob){
fmt.Println("Unable to send email")
}
}
The EmailJob
type EmailJob struct {
Id int
Recipients string
Subject string
Body string
Sent int
Error string
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
body := c.Render(http.StatusOK,"email/user_notify_email.html",visitingDetails)
this line gives error as it returns an error for render.
I am not sure how I will I do it? I hope I made it clear. A little help will be much appreciated.
You are using context.Render method incorrectly.
https://github.com/labstack/echo/blob/master/context.go#L111
// Render renders a template with data and sends a text/html response with status
// code. Renderer must be registered using `Echo.Renderer`.
Render(code int, name string, data interface{}) error
The Render methods renders the template and sends it as a response.
This method returns an error value, if something unexpected happens, this error value is a description of it. Otherwise, it is equal to nil.
See: https://golang.org/pkg/errors/
In order to use Renderer, you must register it and you can use that registered renderer to get rendered template text and save it in DB.
You can see example renderer in unit tests of Echo framework: https://github.com/labstack/echo/blob/master/context_test.go#L23
Hope this helps.
After doing a few things and understanding template in golang.
I came up with a solution like this.
t, err := template.ParseFiles("templates/email/user_notify_email.html")
if err != nil {
fmt.Println("Error happend")
fmt.Println(err)
return c.JSON(http.StatusOK, data)
}
buf := new(bytes.Buffer)
if err = t.Execute(buf, visitingDetails); err != nil {
fmt.Println(err)
}
body := buf.String()
And now this body can be stored. Body has rendered template that I required.
Much credit goes to this article https://medium.com/#dhanushgopinath/sending-html-emails-using-templates-in-golang-9e953ca32f3d
I'm a beginner with Go, and I'm now writing a function which can call an API. The function receives a part of the url (/user, /account, etc) and the struct to convert the returned json to (the structs User or Account for example) as arguments.
So I now have this:
func (self *RestClient) request(action string, return_type interface{}) interface{} {
res, _ := goreq.Request{Uri:self.url + action}.Do()
var item return_type
res.Body.FromJsonTo(&item)
return item
}
And I try to call this function using (with Index being the struct for the return type):
self.request("/api/v1/public/index", Index)
But this doesn't work. I get the following errors:
return_type is not a type
type Index is not an expression
I guess I understand why this is. I think I have to somehow find out the type of return_type and then convert return_type to that type, before using it as the type for item. I have no idea how though.
About the second error I have no idea. I don't even understand what is meant by it.
Could anybody help me out? How can I make this work? Or should this be done in a completely different way all together? All tips are welcome!
A few hints based on this code:
Don't use self - use a meaningful identifier
Don't use interface{} to avoid dealing with the type system
Don't use reflection
Don't ignore errors returned (as from FromJsonTo or goreq.Request)
Don't use a library like goreq unless you are sure you need it (you don't) - you are pulling in lots of code you don't need and it is teaching you bad habits like attempting to use empty interface and reflection to solve simple problems.
Have a look at the definition of FromJsonTo - if you look through this library you'll see it isn't saving you much effort and is adding lots of complexity. Here is how you could do it without the library:
func (c *RestClient) Request(action string, resource interface{}) error {
res, err := http.Get(c.url + action)
if err != nil {
return err
}
defer res.Body.Close()
return json.NewDecoder(res.Body).Decode(resource)
}
Or use an interface and move the decoding to the resource (which could embed a default decoder):
type Decoder interface {
Decode(r io.Reader) error
}
// RequestDecode fetches a request and feeds it to the decoder
func (c *RestClient) RequestDecode(action string, resource Decoder) error {
res, err := http.Get(c.url + action)
if err != nil {
return err
}
defer res.Body.Close()
return resource.Decode(res.Body)
}
I'll first say that you should always check for errors from any function that possibly returns one.
The error you are seeing is because you are trying to declare a variable item as type return_type and that is the name of a function argument.
The other error is from Index being a type declaration and not a concrete value of the Index type.
I agree with Volker's comment but to put it in code, you could use something like this:
func (self *RestClient) request(action string, return_type interface{}) {
res, err := goreq.Request{Uri:self.url + action}.Do()
if err != nil {
// Do something with error here.
}
res.Body.FromJsonTo(return_type)
}
var index Index
rest_client.request("/some/path", &index)
This allows flexibility but could lead to strange cases if you forget to pass a pointer to the value return_type.
I am trying to use inline if condition as follows:
topDisplay.text!.rangeOfString(".") != nil ? call function A : call function B
The idea here is if there is "." in the topDisplay.text! then call function A, otherwise, call function B. The method, rangeOfString, returns nil if no "." is found. So I am wondering is it possible to check nil within inline condition expression and making function call at the same time.
Your code is correct, assuming you put the calls to actual functions in there, i.e.:
func functionA() -> String { return "A" }
func functionB() -> String { return "B" }
topDisplay.text?.rangeOfString(".") != nil ? functionA() : functionB()
If you’re getting an error message, the most likely reason is functionA and functionB return different types:
func functionA() -> String { return "A" }
func functionB() -> Int { return 1 }
// error: could not find an overload for '!=' that accepts the supplied arguments
topDisplay.text?.rangeOfString(".") != nil ? functionA() : functionB()
In classic Swift error message style, this gives you an error about the valid != comparison not about the problem with A and B returning incompatible types.
However, given you aren’t showing in your code the assignment of the result, I suspect you are actually wanting to run these functions for their side-effects not for the value they return. If this is the case do not do this. The ?: operator is there for evaluating two possibilities as an expression. If you want side effects, use an if:
if topDisplay.text?.rangeOfString(".") != nil {
sideEffectfulFunctionA()
}
else {
sideEffectfulFunctionB()
}