Join arguments into one string - go

I have this:
if t.FieldName != "" {
if t.FieldName != item.FieldName {
panic(errors.New("FieldName does not match, see: ", t.FieldName, item.FieldName))
}
}
that won't compile because errors.New takes one string arg. So I need to do something like:
panic(errors.New(joinArgs("FieldName does not match, see: ", t.FieldName, item.FieldName)))
How can I implement joinArgs, so that it concatenates all it's strings arguments into one string?

The XY problem is asking about your attempted solution rather than your actual problem: The XY Problem. Your real problem is formatting panic error messages.
This is the normal solution to your real problem:
package main
import "fmt"
func main() {
t := struct{ FieldName string }{FieldName: "a t.FieldName"}
item := struct{ FieldName string }{FieldName: "an item.FieldName"}
panic(fmt.Sprintf("FieldName does not match, see: %v %v", t.FieldName, item.FieldName))
}
Playground: https://play.golang.org/p/DaOlcqUgV_H
Output:
panic: FieldName does not match, see: a t.FieldName an item.FieldName

This seemed to work, not sure if it's optimal tho
func joinArgs(strangs ...string) string {
buffer := bytes.NewBufferString("")
for _, s := range strangs {
buffer.WriteString(s)
}
return buffer.String()
}

Related

Participle is stating Unexpected Token "XXX"

I am playing with a participle to learn how to parse and I cannot determine why this is unexpected, code is here:
// nolint: golint, dupl
package main
import (
"fmt"
"io"
"github.com/alecthomas/participle/v2"
"github.com/alecthomas/participle/v2/lexer"
)
var htaccessLexer = lexer.MustSimple([]lexer.SimpleRule{
{"Comment", `^#[^\n]*`},
{"Ident", `^\w+`},
{"Int", `\d+`},
{"String", `("(\\"|[^"])*"|\S+)`},
{"EOL", `[\n\r]+`},
{"whitespace", `[ \t]+`},
})
type HTACCESS struct {
Directives []*Directive `##*`
}
type Directive struct {
Pos lexer.Position
ErrorDocument *ErrorDocument `##`
}
type ErrorDocument struct {
Code int `"ErrorDocument" #Int`
Path string `#String`
}
var htaccessParser = participle.MustBuild[HTACCESS](
participle.Lexer(htaccessLexer),
participle.CaseInsensitive("Ident"),
participle.Unquote("String"),
participle.Elide("whitespace"),
)
func Parse(r io.Reader) (*HTACCESS, error) {
program, err := htaccessParser.Parse("", r)
if err != nil {
return nil, err
}
return program, nil
}
func main() {
v, err := htaccessParser.ParseString("", `ErrorDocument 403 test`)
if err != nil {
panic(err)
}
fmt.Println(v)
}
From what I can tell, this seems to be correct, I expect 403 to be there, but I am not sure why it isn't recognizing it... Any help is appreciated!
Edit:
I changed my lexer to this:
var htaccessLexer = lexer.MustSimple([]lexer.SimpleRule{
{"dir", `^\w+`},
{"int", `\d+`},
{"str", `("(\\"|[^"])*"|\S+)`},
{"EOL", `[\n\r]+`},
{"whitespace", `\s+`},
})
And the error is gone, but it is still printing an empty array, not sure why. I am also not sure why using different values for the lexer fixed either....
Thank you!
I believe I found the issue, it is the order, Ident was finding numbers in my lexer via the \w tag, so this caused my integers to be marked as ident.
I found that I have to separate QuotedStrings and UnQuotedStrings otherwise unquoted strings was picking up integers. Alternatively I could ensure it only picks up non-numeric values, but that would miss things like stringwithnum2
Here is my solution
var htaccessLexer = lexer.MustSimple([]lexer.SimpleRule{
{"Comment", `(?i)#[^\n]*`},
{"QuotedString", `"(\\"|[^"])*"`},
{"Number", `[-+]?(\d*\.)?\d+`},
{"UnQuotedString", `[^ \t]+`},
{"Ident", `^[a-zA-Z_]`},
{"EOL", `[\n\r]+`},
{"whitespace", `[ \t]+`},
})
type ErrorDocument struct {
Pos lexer.Position
Code int `"ErrorDocument" #Number`
Path string `(#QuotedString | #UnQuotedString)`
}
This fixed my issue, because it now finds quoted strings, then looks for Numbers, then looks for unquoted strings.

Get a value from a known key in a dynamic nested YAML

I'm pretty new to Golang. I have a YAML file with dynamic keys but only one is known, and it's not necessarily the first one (after config). There is no other key at the level of config.
The yaml file :
config:
foo:bar: baz
bar:foo: baz
abs:getit: myvalue
I want to retrieve myvalue from the nested key config:abs:getit. This nested key name will never change, it will always be config:abs:getit. All other keys can be whatever, we don't care, with different types of content (arrays, int, strings, array of array).
What is the best way to recover the value ?
I worked with yaml package, but I have to fix every field in a struct to unmarshall it, but I don't know how many nested keys there can be so I cannot write a struct which works all the time.
I worked with a map, but I can figure out which map I have to use, because if I can have a field with 6 nested keys or 3 nested keys with array in it before the value I'm searching and it will fails.
I am pretty lost with those kind of things in a dynamic context.
Ideally, I want to do a cat myFile.yaml | yq '.config."abs:getit"', but in Golang...
Any ideas and best practices to do that ?
You can do:
func main() {
var obj struct {
Config struct {
AbsGetit string `yaml:"abs:getit"`
} `yaml:"config"`
}
err := yaml.Unmarshal(data, &obj)
if err != nil {
panic(err)
}
fmt.Printf("%q\n", obj.Config.AbsGetit)
}
https://go.dev/play/p/KJ_lzZxaZBy
I think you need this code. Just put the correct path to your "myFile.yaml" file. In main() function, you will see two different examples of how to use the code according to your needs.
getConfVal finds a node of a YAML tree with an arbitrary sequence in N-dimensional depth. If the node does not exist, the value will be nil.
myFile.yaml
config:
foo:bar: baz
bar:foo: baz
abs:getit: myvalue
foo:
bar: "conf-foo-bar"
bar:
foo: "conf-bar-foo"
abs:
getit: "conf-abs-getit"
one:
two:
three:
four:
five: 5
five2: [4, 7]
package main
import (
"fmt"
"os"
yaml "gopkg.in/yaml.v3"
)
func main() {
if err := readTConf("./path/to/myFile.yaml", &cfg); err != nil {
fmt.Printf("Read YAML Conf: %v\n", err)
return
}
// e.g.
getConfVal(cfg, []string{"foo:bar"})
// getConfVal(cfg, []string{"foo", "bar"})
// getConfVal(cfg, []string{"four", "five"})
// getConfVal(cfg, []string{"four", "five2"})
fmt.Printf("\nThis is the result you are looking for. (%v)\n", needleRes)
}
var needleRes interface{}
var cfg map[string]interface{}
func getConfVal(o map[string]interface{}, ns []string) (map[string]interface{}, bool) {
nsCnt := len(ns)
for kn, vn := range ns {
for ko, vo := range o {
if fmt.Sprintf("%T", vo) == "map[string]interface {}" {
res, ok := getConfVal(vo.(map[string]interface{}), ns)
if ok {
return res, true
break
}
}
if fmt.Sprintf("%T", vo) == "string" {
if ko == vn {
if kn+1 == nsCnt {
needleRes = vo
return map[string]interface{}{}, true
}
}
}
}
}
return map[string]interface{}{}, false
}
func readTConf(f string, c *map[string]interface{}) error {
yamlFile, err := os.ReadFile(f)
if err != nil {
return err
}
if err := yaml.Unmarshal([]byte(yamlFile), &c); err != nil {
return err
}
return nil
}
Thank you for your precise answer. I'm sorry but there is an error in the question, and I apologize for the mistake.
It's not a single flow scalar Yaml but a map, since there is space before the value :
config:
foo:bar: baz
bar:foo: baz
abs:getit: myvalue
The code above logically returns a conversion error like this :
panic: yaml: unmarshal errors:
line 2: cannot unmarshal !!map into string
My whole code is here. The file I read is a Pulumi config Yaml, which will be different for all projects, except for one common key ("abs:getit:"), only the value is different.
The original question file has been modified. Really sorry for that...

Struct field tag `name` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair

I read this, but it is different than my case, I've the below code:
package main
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
type User struct {
Name string `name`
Age int64 `age`
}
func main() {
var u User = User{"bob", 10}
res, err := JSONEncode(u)
if err != nil {
panic(err)
}
fmt.Println(string(res))
}
func JSONEncode(v interface{}) ([]byte, error) {
refObjVal := reflect.ValueOf(v)
refObjTyp := reflect.TypeOf(v)
buf := bytes.Buffer{}
if refObjVal.Kind() != reflect.Struct {
return buf.Bytes(), fmt.Errorf(
"val of kind %s is not supported",
refObjVal.Kind(),
)
}
buf.WriteString("{")
pairs := []string{}
for i := 0; i < refObjVal.NumField(); i++ {
structFieldRefObj := refObjVal.Field(i)
structFieldRefObjTyp := refObjTyp.Field(i)
switch structFieldRefObj.Kind() {
case reflect.String:
strVal := structFieldRefObj.Interface().(string)
pairs = append(pairs, `"`+string(structFieldRefObjTyp.Tag)+`":"`+strVal+`"`)
case reflect.Int64:
intVal := structFieldRefObj.Interface().(int64)
pairs = append(pairs, `"`+string(structFieldRefObjTyp.Tag)+`":`+strconv.FormatInt(intVal, 10))
default:
return buf.Bytes(), fmt.Errorf(
"struct field with name %s and kind %s is not supprted",
structFieldRefObjTyp.Name,
structFieldRefObj.Kind(),
)
}
}
buf.WriteString(strings.Join(pairs, ","))
buf.WriteString("}")
return buf.Bytes(), nil
}
It works perfectly, and give output as:
{"name":"bob","age":10}
But as VS code, it gives me the below problems:
What could be the issue?
Note that that's just a warning telling you that you're not following convention. The code, as you already know, compiles and runs and outputs the result you want: https://go.dev/play/p/gxcv8qPVZ6z.
To avoid the warning, disable your linter, or, better yet, follow the convention by using key:"value" in the struct tags and then extract the value by using the Get method: https://go.dev/play/p/u0VTGL48TjO.
https://pkg.go.dev/reflect#go1.18.3#StructTag
A StructTag is the tag string in a struct field.
By convention, tag strings are a concatenation of optionally
space-separated key:"value" pairs. Each key is a non-empty string
consisting of non-control characters other than space (U+0020 ' '),
quote (U+0022 '"'), and colon (U+003A ':'). Each value is quoted using
U+0022 '"' characters and Go string literal syntax.
Struct tag supposed to be a key:"value", field:"name" for example.
type User struct {
Name string `field:"name"`
Age int64 `field:"age"`
}
instead of field as in another answer you can use json:"keyname"
type User struct {
Name string `json:"name"`
Age int64 `json:"age"`
}

How can I convert a JSON string to a byte array?

I need some help with unmarshaling. I have this example code:
package main
import (
"encoding/json"
"fmt"
)
type Obj struct {
Id string `json:"id"`
Data []byte `json:"data"`
}
func main() {
byt := []byte(`{"id":"someID","data":["str1","str2"]}`)
var obj Obj
if err := json.Unmarshal(byt, &obj); err != nil {
panic(err)
}
fmt.Println(obj)
}
What I try to do here - convert bytes to the struct, where type of one field is []byte. The error I get:
panic: json: cannot unmarshal string into Go struct field Obj.data of
type uint8
That's probably because parser already sees that "data" field is already a slice and tries to represent "str1" as some char bytecode (type uint8?).
How do I store the whole data value as one bytes array? Because I want to unmarshal the value to the slice of strings later. I don't include a slice of strings into struct because this type can change (array of strings, int, string, etc), I wish this to be universal.
My first recommendation would be for you to just use []string instead of []byte if you know the input type is going to be an array of strings.
If data is going to be a JSON array with various types, then your best option is to use []interface{} instead - Go will happily unmarshal the JSON for you and you can perform checks at runtime to cast those into more specific typed variables on an as-needed basis.
If []byte really is what you want, use json.RawMessage, which is of type []byte, but also implements the methods for JSON parsing. I believe this may be what you want, as it will accept whatever ends up in data. Of course, you then have to manually parse Data to figure out just what actually IS in there.
One possible bonus is that this skips any heavy parsing because it just copies the bytes over. When you want to use this data for something, you use a []interface{}, then use a type switch to use individual values.
https://play.golang.org/p/og88qb_qtpSGJ
package main
import (
"encoding/json"
"fmt"
)
type Obj struct {
Id string `json:"id"`
Data json.RawMessage `json:"data"`
}
func main() {
byt := []byte(`{"id":"someID","data":["str1","str2", 1337, {"my": "obj", "id": 42}]}`)
var obj Obj
if err := json.Unmarshal(byt, &obj); err != nil {
panic(err)
}
fmt.Printf("%+v\n", obj)
fmt.Printf("Data: %s\n", obj.Data)
// use it
var d []interface{}
if err := json.Unmarshal(obj.Data, &d); err != nil {
panic(err)
}
fmt.Printf("%+v\n", d)
for _, v := range d {
// you need a type switch to deterine the type and be able to use most of these
switch real := v.(type) {
case string:
fmt.Println("I'm a string!", real)
case float64:
fmt.Println("I'm a number!", real)
default:
fmt.Printf("Unaccounted for: %+v\n", v)
}
}
}
Your question is:
convert bytes array to struct with a field of type []byte
But you do not have a bytearray but a string array. Your question is not the same as your example. So let answer your question, there are more solutions possible depending in how far you want to diverge from your original requirements.
One string can be converted to one byte-slice, two strings need first to be transformed to one string. So that is problem one. The second problem are the square-brackets in your json-string
This works fine, it implicitly converts the string in the json-string to a byte-slice:
byt := []byte(`{"id":"someID","data":"str1str2"}`)
var obj Obj
if err := json.Unmarshal(byt, &obj); err != nil {
panic(err)
}
fmt.Println(obj)

How to compare Go errors

I have an error value which when printed on console gives me Token is expired
How can I compare it with a specific error value? I tried this but it did not work:
if err == errors.New("Token is expired") {
log.Printf("Unauthorised: %s\n", err)
}
Declaring an error, and comparing it with '==' (as in err == myPkg.ErrTokenExpired) is no longer the best practice with Go 1.13 (Q3 2019)
The release notes mentions:
Go 1.13 contains support for error wrapping, as first proposed in the Error Values proposal and discussed on the associated issue.
An error e can wrap another error w by providing an Unwrap method that returns w.
Both e and w are available to programs, allowing e to provide additional context to w or to reinterpret it while still allowing programs to make decisions based on w.
To support wrapping, fmt.Errorf now has a %w verb for creating wrapped errors, and three new functions in the errors package ( errors.Unwrap, errors.Is and errors.As) simplify unwrapping and inspecting wrapped errors.
So the Error Value FAQ explains:
You need to be prepared that errors you get may be wrapped.
If you currently compare errors using ==, use errors.Is instead.
Example:
if err == io.ErrUnexpectedEOF
becomes
if errors.Is(err, io.ErrUnexpectedEOF)
Checks of the form if err != nil need not be changed.
Comparisons to io.EOF need not be changed, because io.EOF should never be wrapped.
If you check for an error type using a type assertion or type switch, use errors.As instead. Example:
if e, ok := err.(*os.PathError); ok
becomes
var e *os.PathError
if errors.As(err, &e)
Also use this pattern to check whether an error implements an interface. (This is one of those rare cases when a pointer to an interface is appropriate.)
Rewrite a type switch as a sequence of if-elses.
This answer is for Go 1.12 and earlier releases.
Define an error value in a library
package fruits
var NoMorePumpkins = errors.New("No more pumpkins")
Do not create errors with errors.New anywhere in the code but return the predefined value whenever error occurs and then you can do the following:
package shop
if err == fruits.NoMorePumpkins {
...
}
See io package errors for reference.
This can be improved by adding methods to hide the check implementation and make the client code more immune to changes in fruits package.
package fruits
func IsNoMorePumpkins(err error) bool {
return err == NoMorePumpkins
}
See os package errors for reference.
Try
err.Error() == "Token is expired"
Or create your own error by implementing the error interface.
It's idiomatic for packages to export error variables that they use so others can compare against them.
E.g. If an error would came from a package named myPkg and was defined as:
var ErrTokenExpired error = errors.New("Token is expired")
You could compare the errors directly as:
if err == myPkg.ErrTokenExpired {
log.Printf("Unauthorised: %s\n", err)
}
If the errors come from a third party package and that doesn't use exported error variables then what you can do is simply to compare against the string you get from err.Error() but be careful with this approach as changing an Error string might not be released in a major version and would break your business logic.
The error type is an interface type. An error variable represents any value that can describe itself as a string. Here is the interface's declaration:
type error interface {
Error() string
}
The most commonly-used error implementation is the errors package's unexported errorString type:
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
See this working code output (The Go Playground):
package main
import (
"errors"
"fmt"
"io"
)
func main() {
err1 := fmt.Errorf("Error")
err2 := errors.New("Error")
err3 := io.EOF
fmt.Println(err1) //Error
fmt.Printf("%#v\n", err1) // &errors.errorString{s:"Error"}
fmt.Printf("%#v\n", err2) // &errors.errorString{s:"Error"}
fmt.Printf("%#v\n", err3) // &errors.errorString{s:"EOF"}
}
output:
Error
&errors.errorString{s:"Error"}
&errors.errorString{s:"Error"}
&errors.errorString{s:"EOF"}
Also see: Comparison operators
Comparison operators compare two operands and yield an untyped boolean
value. In any comparison, the first operand must be assignable to the
type of the second operand, or vice versa.
The equality operators == and != apply to operands that are
comparable.
Pointer values are comparable. Two pointer values are equal if they
point to the same variable or if both have value nil. Pointers to
distinct zero-size variables may or may not be equal.
Interface values are comparable. Two interface values are equal if
they have identical dynamic types and equal dynamic values or if both
have value nil.
A value x of non-interface type X and a value t of interface type T
are comparable when values of type X are comparable and X implements
T. They are equal if t's dynamic type is identical to X and t's
dynamic value is equal to x.
Struct values are comparable if all their fields are comparable. Two
struct values are equal if their corresponding non-blank fields are
equal.
So:
1- You may use Error(), like this working code (The Go Playground):
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("Token is expired")
err2 := errors.New("Token is expired")
if err1.Error() == err2.Error() {
fmt.Println(err1.Error() == err2.Error()) // true
}
}
output:
true
2- Also you may compare it with nil, like this working code (The Go Playground):
package main
import (
"errors"
"fmt"
)
func main() {
err1 := errors.New("Token is expired")
err2 := errors.New("Token is expired")
if err1 != nil {
fmt.Println(err1 == err2) // false
}
}
output:
false
3- Also you may compare it with exact same error, like this working code
(The Go Playground):
package main
import (
"fmt"
"io"
)
func main() {
err1 := io.EOF
if err1 == io.EOF {
fmt.Println("err1 is : ", err1)
}
}
output:
err1 is : EOF
ref: https://blog.golang.org/error-handling-and-go
It's being discouraged to compare errors by strings. Instead you should compare errors by value.
package main
import "errors"
var NotFound = errors.New("not found")
func main() {
if err := doSomething(); errors.Is(err, NotFound) {
println(err)
}
}
func doSomething() error {
return NotFound
}
It is especially useful if you are library author and would like to export errors so users can act differently on different type of errors. Standard library does it as well.
Problem with this approach is that exported values can be changed by anyone as Go doesn't support immutable values. Nothing prevents you, though, to use string as an error and make it const.
package main
type CustomError string
func (ce CustomError) Error() string {
return string(ce)
}
const NotFound CustomError = "not found"
func main() {
if err := doSomething(); errors.Is(err, NotFound) {
println(err)
}
}
func doSomething() error {
return NotFound
}
It is more verbose but safer approach.
You should first consider comparing errors by value, as described in other solutions with:
if errors.Is(err1, err2) {
// do sth
}
However in some cases the error returned from a function is a bit complex, e.g. an error is being wrapped multiple times, with a context being added to it in each function call like fmt.Errorf("some context: %w", err), and you may simply just want to compare the error message of two errors. In such cases you can do this:
// SameErrorMessage checks whether two errors have the same messages.
func SameErrorMessage(err, target error) bool {
if target == nil || err == nil {
return err == target
}
return err.Error() == target.Error()
}
func main() {
...
if SameErrorMessage(err1, err2) {
// do sth
}
}
Note that if you simply use
if err1.Error() == err2.Error() {
// do sth
}
You might face nil pointer dereference runtime error if either of err1 or err2 be nil.
To add to #wst 's answer, in some cases, the errors.Is(err, NotFound) approach may not work for reasons I am trying to figure out too. If someone knows, please let me know in the comments.
But I found a better approach to use it in the following way which was working for me:
if NotFound.Is(err) {
// do something
}
Where var NotFound = errors.New("not found") is an exported common error declared.
In my case, the solution was
if models.GetUnAuthenticatedError().Is(err) {
// Do something
}
I want to post one case where errors.Is could work well for custom errors with non-comparable values.
type CustomError struct {
Meta map[string]interface{}
Message string
}
func (c CustomError) Error() string {
return c.Message
}
var (
ErrorA = CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}}
)
func DoSomething() error {
return ErrorA
}
func main() {
err := DoSomething()
if errors.Is(err, ErrorA) {
fmt.Println("error is errorA")
} else {
fmt.Println("error is NOT errorA")
}
}
Output
error is NOT errorA
Playground
The reason is errors.Is checks whether the target is comparable or not
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
The comparable type in Go are
booleans, numbers, strings, pointers, channels, arrays of comparable types, structs whose fields are all comparable types
Since the Meta map[string]interface{} of CustomError is NOT comparable, so errors.Is checks failed.
One workaround is declare the ErrorA = &CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}} as pointer.

Resources