I am probably missing something really simple here:
package main
import (
"fmt"
"strconv"
"reflect"
)
func main() {
s := "abd"
fmt.Println(s)
_, err := strconv.Atoi(s)
if err != nil {
fmt.Println(err)
}
fmt.Println(reflect.TypeOf(err))
fmt.Println(err.Err)
}
I am trying to extract the error itself e.g. ErrSyntax or ErrRange, but I am not able to do so.
After looking at:
https://golang.org/src/strconv/atoi.go?s=3604:3671#L16
I see that err is a pointer to strconv.NumError
15 // A NumError records a failed conversion.
16 type NumError struct {
17 Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
18 Num string // the input
19 Err error // the reason the conversion failed (ErrRange, ErrSyntax)
20 }
And Err is the field that holds either ErrRange of ErrSyntax. Therefore, I thought that err.Err would work, but I get:
err.Err undefined (type error has no field or method Err
Err is public, am I missing something with visibility rules?
What am I missing?
Use a type assertion to get the *strconv.NumError value:
if e, ok := err.(*strconv.NumError); ok {
fmt.Println("e.Err", e.Err)
}
playground example
Related
I have a Gorm delete with the returning result:
expirationDate := time.Now().UTC().Add(-(48 * time.hour))
var deletedUsers Users
res := gormDB.WithContext(ctx).
Table("my_users").
Clauses(clause.Returning{Columns: []clause.Column{{Name: "email"}}}).
Where("created_at < ?", expirationDate).
Delete(&deletedUsers)
Now the test with clauses always fails. e.g. :
sqlMock.ExpectExec(`DELETE`)
.WithArgs(expirationDate)
.WillReturnResult(sqlmock.NewResult(1, 1))
Receiving error:
"call to Query 'DELETE FROM "my_users" WHERE created_at < $1 RETURNING "email"' with args [{Name: Ordinal:1 Value:2023-01-18 06:15:34.694274 +0000 UTC}], was not expected, next expectation is: ExpectedExec => expecting Exec or ExecContext which:\n - matches sql: 'DELETE'\n - is with arguments:\n 0 - 2023-01-18 06:15:34.694274 +0000 UTC\n - should return Result having:\n LastInsertId: 1\n RowsAffected: 1"
I tried many other sqlMock expectations, but they have a similar issue.
Also, we don't have a return value in ExpectExec, only in ExpectQuery...
Any chance someone has to test the Gorm query with the Clauses?
I was able to successfully manage what you need. First, let me share the files I wrote, and then I'll walk you through all of the relevant changes. The files are repo.go for production and repo_test.go for the test code.
repo.go
package gormdelete
import (
"context"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type Users struct {
Email string
}
func Delete(ctx context.Context, gormDB *gorm.DB) error {
expirationDate := time.Now().UTC().Add(-(48 * time.Hour))
var deletedUsers Users
res := gormDB.WithContext(ctx).Table("my_users").Clauses(clause.Returning{Columns: []clause.Column{{Name: "email"}}}).Where("created_at < ?", expirationDate).Delete(&deletedUsers)
if res.Error != nil {
return res.Error
}
return nil
}
As you didn't provide the full file I tried to guess what was missing.
repo_test.go
package gormdelete
import (
"context"
"database/sql/driver"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
// this is taken directly from the docs
// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
type AnyTime struct{}
// Match satisfies sqlmock.Argument interface
func (a AnyTime) Match(v driver.Value) bool {
_, ok := v.(time.Time)
return ok
}
func TestDelete(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error was not expected: %v", err)
}
conn, _ := db.Conn(context.Background())
gormDb, err := gorm.Open(postgres.New(postgres.Config{
Conn: conn,
}))
row := sqlmock.NewRows([]string{"email"}).AddRow("test#example.com")
mock.ExpectBegin()
mock.ExpectQuery("DELETE FROM \"my_users\" WHERE created_at < ?").WithArgs(AnyTime{}).WillReturnRows(row)
mock.ExpectCommit()
err = Delete(context.Background(), gormDb)
assert.Nil(t, err)
if err = mock.ExpectationsWereMet(); err != nil {
t.Errorf("not all expectations were met: %v", err)
}
}
Here, there are more changes that it's worth mentioning:
I instantiated the AnyTime as per the documentation (you can see the link in the comment).
Again, I guessed the setup of the db, mock, and gormDb but I think it should be more or less the same.
I switch the usage of ExpectExec to ExpectQuery as we'll have back a result set as specified by the Clauses method in your repo.go file.
You've to wrap the ExpectQuery within an ExpectBegin and an ExpectCommit.
Finally, pay attention to the difference in how the driver expects the parameters in the SQL statement. In the production code, you can choose to use ? or $1. However, in the test code, you can only use the ? otherwise it won't match the expectations.
Hope to help a little bit, otherwise, let me know!
I think is something that I miss theoretically from the passing by reference topic but I can't find a way to read the ID without using the support networkInterfaceReference
package main
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/profiles/preview/resources/mgmt/resources"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2021-03-01/compute"
"github.com/Azure/azure-sdk-for-go/services/subscription/mgmt/2020-09-01/subscription"
"github.com/Azure/go-autorest/autorest/azure/auth"
"github.com/ktr0731/go-fuzzyfinder"
)
var selectedSub subscription.Model
var selectedRG resources.Group
var selectedVM compute.VirtualMachine
func main() {
selectedSub = GetSubscription()
selectedRG = GetResourceGroup()
selectedVM = GetVM()
fmt.Printf("Sub: %s\nRG: %s\nVM: %s\n", *selectedSub.DisplayName, *selectedRG.Name, *selectedVM.Name)
// THIS WORK
networkInterfaceReference := *selectedVM.NetworkProfile.NetworkInterfaces
fmt.Printf("%s", *networkInterfaceReference[0].ID)
// THIS DOESN'T WORK
fmt.Printf("%s", *selectedVM.NetworkProfile.NetworkInterfaces[0].ID)
}
...
...
...
func GetVM() compute.VirtualMachine {
vmClient := compute.NewVirtualMachinesClient(*selectedSub.SubscriptionID)
authorizer, err := auth.NewAuthorizerFromCLI()
if err == nil {
vmClient.Authorizer = authorizer
}
vmList, err := vmClient.List(context.TODO(), *selectedRG.Name)
if err != nil {
panic(err)
}
idx, err := fuzzyfinder.Find(vmList.Values(), func(i int) string {
return *vmList.Values()[i].Name
})
if err != nil {
panic(err)
}
return vmList.Values()[idx]
}
Hovering to the error showed the following error message:
field NetworkProfile *[]compute.NetworkProfile
(compute.VirtualMachineProperties).NetworkProfile on pkg.go.dev
NetworkProfile - Specifies the network interfaces of the virtual machine.
invalid operation: cannot index selectedVM.NetworkProfile.NetworkInterfaces (variable of type *[]compute.NetworkInterfaceReference)compiler (NonIndexableOperand)
If you want the 2nd way to work:
// WORKS
networkInterfaceReference := *selectedVM.NetworkProfile.NetworkInterfaces
fmt.Printf("%s", *networkInterfaceReference[0].ID)
// THIS DOESN'T WORK
fmt.Printf("%s", *selectedVM.NetworkProfile.NetworkInterfaces[0].ID)
studying the compilation error you are getting (P.S. please don't post screen-shots of code/errors) - the error is failing because you are trying to index a pointer which is not allowed in Go. You can only index maps, arrays or slices.
The fix is simple, since you do two (2) pointer dereferences in the working version, you need to do two (2) same in the single expression - but also you need to ensure the lexical binding order so the indexing is done after the pointer dereference:
fmt.Printf("%s", *(*selectedVM.NetworkProfile.NetworkInterfaces)[0].ID)
Finally, there is no pass-by-reference in Go. Everything is by value. If you want to change a value, pass a pointer to it - but that pointer is a still just a value that is copied.
I have read through the piece of code bellow and I don't know what exactly the syntax of
d.()
in
if f, ok := d.(*ast.FuncDecl); mean.
Can anybody explain it for me?
package main
import (
"go/ast"
"go/parser"
"go/token"
"regexp"
"github.com/posener/complete"
)
func functionsInFile(path string, regexp *regexp.Regexp) (tests []string) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
complete.Log("Failed parsing %s: %s", path, err)
return nil
}
for _, d := range f.Decls {
if f, ok := d.(*ast.FuncDecl); ok {
name := f.Name.String()
if regexp == nil || regexp.MatchString(name) {
tests = append(tests, name)
}
}
}
return
}
As the comments say, this is a type assertion. Type assertions are described in the Go spec: https://golang.org/ref/spec#Type_assertions
Applied to your specific code sample, I think it's taken from the AST module documentation, which you should read if you want to work with Go ASTs.
ParseFile returns an ast.File, which is:
type File struct {
Doc *CommentGroup // associated documentation; or nil
Package token.Pos // position of "package" keyword
Name *Ident // package name
Decls []Decl // top-level declarations; or nil
Scope *Scope // package scope (this file only)
Imports []*ImportSpec // imports in this file
Unresolved []*Ident // unresolved identifiers in this file
Comments []*CommentGroup // list of all comments in the source file
}
So Decls is a slice of Decl, which is an interface. In brief, this means that at compile-time you don't know what the actual underlying type is (though you do know it satisfies the interface), so you do a run-time type assertion to verify it's something you expect.
if f, ok := d.(*ast.FuncDecl); ok {
name := f.Name.String()
if regexp == nil || regexp.MatchString(name) {
tests = append(tests, name)
}
}
This means "if d is actually a FuncDecl, do this thing".
One of the functions I am running: image.Decode()
The image.Decode function takes in an io.Reader && and the io.Reader function takes in a []byte.
When I pass in a []uint8, if gives me this error:
panic: image: unknown format
How do I convert the []uint8 to []byte?
UPDATE
The error is happening at the starred area because image.Decode can't read the variable xxx.
package main
import (
"github.com/nfnt/resize"
"image"
"image/jpeg"
"fmt"
"launchpad.net/goamz/aws"
"launchpad.net/goamz/s3"
"bytes"
"encoding/json"
"io/ioutil"
"os"
"reflect"
)
type Data struct {
Key string
}
func main() {
useast := aws.USEast
connection := s3.New(auth, useast)
mybucket := connection.Bucket("bucketName")
image_data, err := mybucket.Get("1637563605030")
if err != nil {
panic(err.Error())
} else {
fmt.Println("success")
}
xxx := []byte(image_data)
******* THIS IS WHERE THE ERROR OCCURS **************
original_image, _, err := image.Decode(bytes.NewReader(xxx))
******* THIS IS WHERE THE ERROR OCCURS END **************
if err != nil {
fmt.Println("Shit")
panic(err.Error())
} else {
fmt.Println("Another success")
}
new_image := resize.Resize(160, 0, original_image, resize.Lanczos3)
if new_image != nil {
fmt.Println("YAY")
}
}
The Go Programming Language Specification
Numeric types
uint8 the set of all unsigned 8-bit integers (0 to 255)
byte alias for uint8
package main
import "fmt"
func ByteSlice(b []byte) []byte { return b }
func main() {
b := []byte{0, 1}
u8 := []uint8{2, 3}
fmt.Printf("%T %T\n", b, u8)
fmt.Println(ByteSlice(b))
fmt.Println(ByteSlice(u8))
}
Output:
[]uint8 []uint8
[0 1]
[2 3]
You have misdiagnosed your problem.
As the other answers have explained, there's no problem passing a []uint8 where a []byte is required. If this was your problem, you'd be getting a compile time error. You aren't. A panic is a runtime error, and it's being thrown by the image library when it reads the data in the slice.
In fact, the image library is only partially your problem. See http://golang.org/src/pkg/image/format.go. It's returning an error message because it doesn't recognize the image format of the data in the slice. Your code, which calls image.Decode() is calling panic when image.Decode() returns the error message.
If you have a variable imageData that is []uint8 you may pass []byte(imageData)
See http://golang.org/ref/spec#Conversions
Using Go’s ast package, I am looping over a struct’s field list like so:
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}
// typ is a *ast.StructType representing the above
for _, fld := range typ.Fields.List {
// get fld.Type as string
}
…and would like to get a simple string representation of fld.Type, as it appears in the source code, e.g. []int or map[byte]float64.
The ast package field type Type property is an Expr, so I’ve found myself getting off into the weeds using type switches and handling every type specifically – when my only goal is to get out the plain string to the right of each field name, which seems like it should be simpler.
Is there a simple way?
There are two things you could be getting at here, one is the type of an expression as would ultimately be resolved during compilation and the other is the code which would determine that type.
Digging through the docs, I don't believe the first is at all available. You can get at the later, however, by using End() and Pos() on Node.
Quick example program:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
src := `
package foo
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
// hard coding looking these up
typeDecl := f.Decls[0].(*ast.GenDecl)
structDecl := typeDecl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType)
fields := structDecl.Fields.List
for _, field := range fields {
typeExpr := field.Type
start := typeExpr.Pos() - 1
end := typeExpr.End() - 1
// grab it in source
typeInSource := src[start:end]
fmt.Println(typeInSource)
}
}
This prints:
string
[]int
map[byte]float64
I through this together in the golang playground, if you want to mess with it.
You can use go/types ExprString
This works with complicated types like []string, []map[string]string, etc.
import (
...
"go/types"
...
)
...
// typ is a *ast.StructType representing the above
for _, fld := range typ.Fields.List {
...
typeExpr := fld.Type
typeString := types.ExprString(typeExpr)
...
}
https://golang.org/src/go/types/exprstring.go
This is exactly what Fprint in the go/printer package is for. It takes any AST node as an argument and writes its string representation to a io.Writer.
You can use it in your example as follows:
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"log"
)
func main() {
src := `
package foo
type Thing struct {
Field1 string
Field2 []int
Field3 map[byte]float64
}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
typeDecl := f.Decls[0].(*ast.GenDecl)
structDecl := typeDecl.Specs[0].(*ast.TypeSpec).Type.(*ast.StructType)
for i, fld := range structDecl.Fields.List {
// get fld.Type as string
var typeNameBuf bytes.Buffer
err := printer.Fprint(&typeNameBuf, fset, fld.Type)
if err != nil {
log.Fatalf("failed printing %s", err)
}
fmt.Printf("field %d has type %q\n", i, typeNameBuf.String())
}
}
Output:
field 0 has type "string"
field 1 has type "[]int"
field 2 has type "map[byte]float64"
Try it in playground: https://play.golang.org/p/cyrCLt_JEzQ
I found a way to do this without using the original source code as a reference for simple members (not slices, arrays or structs):
for _, field := range fields {
switch field.Type.(type) {
case *ast.Ident:
stype := field.Type.(*ast.Ident).Name // The type as a string
tag = ""
if field.Tag != nil {
tag = field.Tag.Value //the tag as a string
}
name := field.Names[0].Name //name as a string
...
For the non-simple members you just need another case statement (IE: case *ast.ArrayType:).