I'm trying to learn go so I decided to make a go module. It works but now I want the main program to get notified when changes happen. In this example I just monitor when connected, but later on for many more changes in the device controlled.
The code below works but I can't see changes that happend in the module, for example the function "onHpd1Connected" in the main program is called when connected but shows that my connection is nil (even if I can see it is set in the "emit" function). The code must be compatible with multiple devices connected (hpd1, hpd2 etc..). I've tried pointers in different ways but without any luck. What I'm I doing wrong, and is this the best way to do it? Or would channels be a better option?
package main
import (
"fmt"
"sandbox/hyperdeck"
)
type hdc struct {
client hyperdeck.Hyperdeck
}
var hpd1 = hdc{
client: hyperdeck.NewHyperdeck("10.0.20.5", 9993),
}
func (hd hdc) onHpd1Connected() {
fmt.Println("Connected 1")
fmt.Println(hd)
}
func main() {
hpd1.client.On("connected", hpd1.onHpd1Connected)
hpd1.client.Connect()
}
and the module
package hyperdeck
import (
"errors"
"fmt"
"net"
"reflect"
"strconv"
"time"
)
const (
Open ConnectionState = 1
Connecting ConnectionState = 2
Closed ConnectionState = 3
)
type ConnectionState int
type HdCallback func()
type Hyperdeck struct {
Ip string
Port int
connection net.Conn
state ConnectionState
initialized bool
listeners map[string][]HdCallback
}
func (hd *Hyperdeck) connect() error {
if hd.state != Closed {
return errors.New("already connected to server: " + hd.Ip)
}
hd.initialized = false
hd.state = Connecting
var err error
hd.connection, err = net.DialTimeout("tcp", hd.Ip+":"+strconv.Itoa(hd.Port), time.Duration(time.Second*2))
if err != nil {
hd.state = Closed
return err
} else {
hd.state = Open
hd.emit("connected")
}
return nil
}
func (hd Hyperdeck) Connect() {
for {
if !hd.Connected() {
err := hd.connect()
if err != nil {
fmt.Println(err)
}
}
time.Sleep(time.Second * 1)
}
}
func (hd *Hyperdeck) Connected() bool {
return hd.state == Open && hd.connection != nil
}
func (hd Hyperdeck) emit(event string, params ...interface{}) {
fmt.Println(hd)
if listeners, exists := hd.listeners[event]; exists {
in := make([]reflect.Value, len(params))
for k, param := range params {
in[k] = reflect.ValueOf(param)
}
for _, cb := range listeners {
reflect.ValueOf(cb).Call(in)
}
}
}
func NewHyperdeck(ip string, port int) Hyperdeck {
hd := Hyperdeck{
Ip: ip,
Port: port,
initialized: false,
connection: nil,
listeners: map[string][]HdCallback{},
state: Closed,
}
return hd
}
func (hd Hyperdeck) On(event string, callback func()) {
if _, exists := hd.listeners[event]; !exists {
hd.listeners[event] = make([]HdCallback, 0)
}
hd.listeners[event] = append(hd.listeners[event], callback)
}
I have a struct which contains a type based on an enum. I am trying to render it to a user friendly string. Here's minimum viable code:
package main
import (
"fmt"
"gopkg.in/yaml.v3"
)
type Job struct {
Engine Engine `json:"Engine" yaml:"Engine"`
}
//go:generate stringer -type=Engine --trimprefix=Engine
type Engine int
const (
engineUnknown Engine = iota // must be first
EngineDocker
engineDone // must be last
)
func main() {
j := Job{Engine: EngineDocker}
fmt.Printf("%+v\n\n", j)
out, _ := yaml.Marshal(j)
fmt.Println(string(out))
}
Here's the generated code:
// Code generated by "stringer -type=Engine --trimprefix=Engine"; DO NOT EDIT.
package main
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[engineUnknown-0]
_ = x[EngineDocker-1]
_ = x[engineDone-2]
}
const _Engine_name = "engineUnknownDockerengineDone"
var _Engine_index = [...]uint8{0, 13, 19, 29}
func (i Engine) String() string {
if i < 0 || i >= Engine(len(_Engine_index)-1) {
return "Engine(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Engine_name[_Engine_index[i]:_Engine_index[i+1]]
}
Here's the output:
{Engine:1}
Engine: 1
Here's what I'd like the output to be:
{Engine:Docker}
Engine: Docker
I thought the String() in the generated file would accomplish this. Is there any way to do this? Thanks!
yaml marshaler doesn't use String method. Instead YAML uses encoding.TextMarshaler and encoding.TextUnmarshaler interfaces. Actually, all other codec schemes - JSON, XML, TOML, etc. - use those interfaces to read/write the values. So, if you implement those methods for your type, you will receive all other codecs for free.
Here is an example how to make a human-readable encoding for your enum: https://go.dev/play/p/pEcBmAM-oZJ
type Engine int
const (
engineUnknown Engine = iota // must be first
EngineDocker
engineDone // must be last
)
var engineNames []string
var engineNameToValue map[string]Engine
func init() {
engineNames = []string{"Unknown", "Docker"}
engineNameToValue = make(map[string]Engine)
for i, name := range engineNames {
engineNameToValue[strings.ToLower(name)] = Engine(i)
}
}
func (e Engine) String() string {
if e < 0 || int(e) >= len(engineNames) {
panic(fmt.Errorf("Invalid engine code: %d", e))
}
return engineNames[e]
}
func ParseEngine(text string) (Engine, error) {
i, ok := engineNameToValue[strings.ToLower(text)]
if !ok {
return engineUnknown, fmt.Errorf("Invalid engine name: %s", text)
}
return i, nil
}
func (e Engine) MarshalText() ([]byte, error) {
return []byte(e.String()), nil
}
func (e *Engine) UnmarshalText(text []byte) (err error) {
name := string(text)
*e, err = ParseEngine(name)
return
}
How it works:
func main() {
j := Job{Engine: EngineDocker}
fmt.Printf("%#v\n\n", j)
out, err := yaml.Marshal(j)
if err != nil {
panic(err)
}
fmt.Printf("YAML: %s\n", string(out))
var jj Job
err = yaml.Unmarshal(out, &jj)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n\n", jj)
// == JSON ==
out, err = json.Marshal(j)
if err != nil {
panic(err)
}
fmt.Printf("JSON: %s\n", string(out))
var jjs Job
err = json.Unmarshal(out, &jjs)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n\n", jjs)
}
the output
main.Job{Engine:1}
YAML: Engine: Docker
main.Job{Engine:1}
JSON: {"Engine":"Docker"}
main.Job{Engine:1}
See? It writes and reads strings to both YAML and JSON without any extra effort.
During validation of user input I wanna set to nil some empty *strings in some structs.
E.g. I have this struct:
type User {
Username *string
}
If Username == "" I wanna set Username to nil before proceed.
But I wanna do this using tags, e.g.:
type User {
Username *string `mod:"nil_if_empty"`
}
because I'm already using this package: https://github.com/go-playground/mold in my project for sanitize.
So I'm creating a custom function to set empty strings to nil, but I'm stuck.
Reproduction: https://play.golang.org/p/_DlluqE2Y3k
package main
import (
"context"
"fmt"
"log"
"reflect"
"github.com/go-playground/mold/v3"
)
var tform *mold.Transformer
func main() {
tform = mold.New()
tform.Register("nilEmptyString", nilEmptyString)
type Test struct {
String *string `mold:"nilEmptyString"`
}
myEmptyString := ""
tt := Test{String: &myEmptyString}
err := tform.Struct(context.Background(), &tt)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", tt)
}
func nilEmptyString(_ context.Context, _ *mold.Transformer, v reflect.Value, _ string) error {
s, ok := v.Interface().(*string)
if !ok { // ok is always false, why?
return nil
}
if s != nil && *s != "" {
return nil
}
if s != nil && *s == "" {
// v.SetPointer(nil) // should I use this?
}
// v.Set(nil) // how to do this?
return nil
}
Why ok in s, ok := v.Interface().(*string) is always false?
How to set v to nil? Why is v.Set(nil) wrong?
I hope this is a good question to ask on StackOverflow. If not tell me how to improve.
Thanks.
I am trying to dynamically set a field that is of type interface{}. In all of the cases below the json unmarshals into the correct structure but in the "problem" case the json unmarshals to []interface{}. For that case I am expecting []Person. Why do I get the wrong type for "problem"?
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Employees struct {
Indicator string `json:"indicator"`
Items interface{} `json:"items"`
}
type Person struct {
Name string `json:"name"`
}
func main() {
simple()
easy()
moreDifficult()
problem()
}
var j = []byte(`{"name": "bob"}`)
var jj = []byte(`[{"name": "bob"}, {"name": "jim"}, {"name": "fred"}]`)
func simple() {
p := Person{}
if err := json.Unmarshal(j, &p); err != nil {
fmt.Println(err)
}
fmt.Println("easy:", p, reflect.TypeOf(p))
}
func easy() {
p := []Person{}
if err := json.Unmarshal(jj, &p); err != nil {
fmt.Println(err)
}
fmt.Println("easy:", p, reflect.TypeOf(p))
}
func moreDifficult() {
var j = []byte(`{"indicator": "more difficult"}`)
e := Employees{}
if err := json.Unmarshal(j, &e); err != nil {
fmt.Println(err)
}
fmt.Println("moreDifficult", e.Items, reflect.TypeOf(e.Items))
}
func problem() {
var j = []byte(`{"indicator:": "problem"}`)
e := Employees{}
if err := json.Unmarshal(j, &e); err != nil {
fmt.Println(err)
}
fmt.Println("problem:", e.Items, reflect.TypeOf(e.Items)) // why not []Person???
}
func (e *Employees) UnmarshalJSON(b []byte) error {
type alias Employees
a := &alias{}
if err := json.Unmarshal(b, &a); err != nil {
return err
}
e.Indicator = a.Indicator
var k = jj
if e.Indicator == "more difficult" {
k = j
e.Items = &Person{}
} else {
e.Items = []Person{}
}
return json.Unmarshal(k, &e.Items)
}
https://play.golang.org/p/xQvjMyLTk5i
The problem is that the interface's underlying value is not a pointer:
e.Items = []Person{}
While it's true that you're passing a pointer to the interface itself:
json.Unmarshal(k, &e.Items)
That does not fix the problem of the underlying value being a non-pointer.
var a interface{} = &T{}
var b interface{} = T{}
b = &b
The types of a and b are different and will be handled differently by the unmarshaler, in b's case the unmarshaler will elect to replace the pointed-to value with a map[string]interface{}.
So to fix your immediate problem you can do something like this:
if e.Indicator == "more difficult" {
k = j
e.Items = &Person{}
} else {
e.Items = &[]Person{}
}
return json.Unmarshal(k, e.Items)
Your current problem has absolutely nothing to do with JSON unmarshaling.
You define Items as type interface{}, so obviously, when you inspect it, it will be of type interface{}. If you want it to be of type []Person, simply define it as such:
type Employees struct {
Indicator string `json:"indicator"`
Items []Person `json:"items"`
}
Once you've done that, your test case will yield the expected results. But your Items will still be empty, as your input JSON doesn't have the items field.
i'm really new to Go, su just bear with me here. I'm trying to write a code for loading mysql data to redis cluster using following :redis-go-cluster,
load2redis
This is the code. Its a little bit long, just bear with me here.
package main
import (
"bytes"
"database/sql"
"flag"
// "github.com/garyburd/redigo/redis"
_ "github.com/go-sql-driver/mysql"
//"gopkg.in/redis.v4"
"github.com/chasex/redis-go-cluster"
"log"
"runtime"
// "strings"
"sync"
"time"
)
var client *redis.Cluster
type Task interface {
Execute()
}
type Pool struct {
mu sync.Mutex
size int
tasks chan Task
kill chan struct{}
wg sync.WaitGroup
}
func NewPool(size int) *Pool {
pool := &Pool{
tasks: make(chan Task, 128),
kill: make(chan struct{}),
}
pool.Resize(size)
return pool
}
func (p *Pool) worker() {
defer p.wg.Done()
for {
select {
case task, ok := <-p.tasks:
if !ok {
return
}
task.Execute()
case <-p.kill:
return
}
}
}
func (p *Pool) Resize(n int) {
p.mu.Lock()
defer p.mu.Unlock()
for p.size < n {
p.size++
p.wg.Add(1)
go p.worker()
}
for p.size > n {
p.size--
p.kill <- struct{}{}
}
}
func (p *Pool) Close() {
close(p.tasks)
}
func (p *Pool) Wait() {
p.wg.Wait()
}
func (p *Pool) Exec(task Task) {
p.tasks <- task
}
type RedisTask struct {
Index int
Command string
Key string
Value string
MapData map[string]string
}
func (e RedisTask) Execute() {
log.Println("executing:", e.Key, ",", e.Index)
if e.Command == "SET" {
_,err := redis.String(client.Do("SET", e.Key, e.Value))
checkErr(err, "set error:")
} else if e.Command == "SADD" {
_,err := redis.Strings(client.Do("SADD", e.Key, e.Value))
checkErr(err, "sadd error:")
} else if e.Command == "HMSET" {
_,err := redis.StringMap(client.Do("HMSET", e.Key, e.MapData))
checkErr(err, "hmset error:")
}
// TODO: clean data
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
startTime := time.Now().UnixNano() / int64(time.Millisecond)
host := flag.String("s", "localhost:3306", "mysql server host and port ,eg localhost:3306")
username := flag.String("u", "test", "username to login mysql")
password := flag.String("p", "test", "password for mysql")
database := flag.String("d", "test", "database you want to execute query")
query := flag.String("q", "select 1;", "your query sql")
ds := flag.String("ds", "key", "redis structure")
PK := flag.String("pk", "Rkey", "the redis Key in the fields of mysql query result")
//redisHost := flag.String("rs", "localhost:6379", "redis host and port ,eg localhost:6379")
//redisPassword := flag.String("rp", "test", "redis password")
poolSize := flag.Int("size", 10000, "redis pool size")
flag.Parse()
var buf bytes.Buffer = bytes.Buffer{}
buf.WriteString(*username)
buf.WriteString(":")
buf.WriteString(*password)
buf.WriteString("#tcp(")
buf.WriteString(*host)
buf.WriteString(")/")
buf.WriteString(*database)
db, err := sql.Open("mysql", buf.String())
checkErr(err, "connect to mysql error !")
defer db.Close()
poolWorker := NewPool(*poolSize)
// Execute the query
rows, err := db.Query(*query)
checkErr(err, "execute sql error!")
// pool = newPool(*redisHost, *redisPassword, *poolSize)
//client = redis.NewClient(&redis.Options{
// Addr: *redisHost,
// Password: *redisPassword, // no password set
// DB: 0, // use default DB
//})
client,_ = redis.NewCluster(&redis.Options{
StartNodes: []string{"10.x.x.x:6000", "10.x.x.x:6001", "10.x.x.x:6002"},
ConnTimeout: 50 * time.Millisecond,
ReadTimeout: 50 * time.Millisecond,
WriteTimeout: 50 * time.Millisecond,
KeepAlive: 16,
AliveTime: 60 * time.Second,
})
//checkErr(err, "client error:")
//pong, err := client.Ping().Result()
//checkErr(err, "redis client error:")
//log.Println(pong)
columns, err := rows.Columns()
checkErr(err, "get columns error!")
length := len(columns)
values := make([]sql.RawBytes, length)
scanArgs := make([]interface{}, len(values))
for i := range values {
scanArgs[i] = &values[i]
}
count := 0
for rows.Next() {
count += 1
err = rows.Scan(scanArgs...)
checkErr(err, "scan error")
var value string
var key string
var task RedisTask
if *ds == "key" {
key = getStringData(values[0])
value = getStringData(values[1])
if value != "" {
task = RedisTask{
Index: count,
Command: "SET",
Key: key,
Value: value,
}
}
} else if *ds == "set" {
key = getStringData(values[0])
value = getStringData(values[1])
if value != "" {
task = RedisTask{
Index: count,
Command: "SADD",
Key: key,
Value: value,
}
}
} else if *ds == "hash" {
key = getStringData(values[0])
// args := redis.Args{}.Add(key)
m := make(map[string]string)
for i, col := range values {
if col != nil && columns[i] != *PK {
value = getStringData(col)
m[columns[i]] = value
}
}
task = RedisTask{
Index: count,
Command: "HMSET",
Key: key,
MapData: m,
}
}
poolWorker.Exec(task)
}
if err = rows.Err(); err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
poolWorker.Close()
poolWorker.Wait()
EndTime := time.Now().UnixNano() / int64(time.Millisecond)
log.Println("======================================== executing time:", EndTime-startTime, " ms, total:", count)
}
func getStringData(data sql.RawBytes) string {
if data == nil {
return ""
}
value := string(data)
return clearBad(value)
}
func clearBad(str string) string {
// str = strings.Trim(str, "`")
// str = strings.Trim(str, "ï½€")
// str = strings.Trim(str, "-")
// str = strings.Trim(str, ".")
// str = strings.Trim(str, " ")
// str = strings.Trim(str, ";")
// str = strings.Trim(str, ",")
// str = strings.Trim(str, ":")
// str = strings.Trim(str, ";")
// str = strings.Trim(str, "'")
// str = strings.Trim(str, "!")
return str
}
func checkErr(err error, msg string) {
if err != nil {
log.Fatalln(msg, err)
}
}
When i'm executing it, i'm getting following exception:
./rak -u user -p user -s 10.X.X.X:8080 -d test -q "SELECT CONCAT( 'student:', c.sid ) Rkey, c.sname SNAME, c.saddress SADDRESS, c.sage SAGE FROM STUDENT c WHERE c.sid > 0;" -ds hash -size 1200
2017/07/21 10:29:09 rak.go:93: executing: student:2 , 2
2017/07/21 10:29:09 rak.go:93: executing: student:1 , 1
2017/07/21 10:29:09 rak.go:93: executing: student:3 , 3
2017/07/21 10:29:09 rak.go:268: hmset error: Do: unknown type map[string]string
$
Can somebody explain to me what i'm doing wrong here? I'd be very grateful.
As pointed out, Do does not work with maps. This is one way you could fix it.
} else if e.Command == "HMSET" {
// Build up a string slice to hold the key value pairs
args := make([]string, 0, len(e.MapData) * 2)
for k, v := range e.MapData {
args = append(args, k, v)
}
_,err := redis.StringMap(client.Do("HMSET", e.Key, args...))
checkErr(err, "hmset error:")
}
The Do method maps to the Redis command set and arguments are expected in the same way. For example.
127.0.0.1:6379> HMSET myKey foo bar baz boff
OK
127.0.0.1:6379> HGETALL myKey
1) "foo"
2) "bar"
3) "baz"
4) "boff"
127.0.0.1:6379>
The same map-set operation using the redis client in your code would be
client.Do("HMSET", "myKey", "foo", "bar", "baz", "boff")
When the arguments for keys and values of the map are dynamic, the most straight forward way is
client.Do("HMSET", "myKey", []string{"foo", "bar", "baz", "boff"}...)
which is exactly what the first code block above does.
Accepted values for Do function arguements are int64, float64, string or []byte .Since you passed a map it failed with error unknown type %T
Here is a reference to the respective code that does it
redis-go-cluster/node.go