Go Struct Fields Undefined - go

Learning Go as I work through creating a REST API. Getting the error undefined: authDetails when I use it the two times at the bottom to access the fields. I understand that it's set in a branch that may not be executed. I've tried moving it outside of the branch, or declaring it above with var authDetails auth.AccessDetails and then setting the values afterwards. I've also tried moving the assignment outside of the braces, but no matter what I do it doesn't seem to carry the values from within the conditional block out to where I try to use them.
I think I'm misunderstanding something regarding scope, if someone could assist me in understanding what the problem is it would be a huge help.
The Struct definition is:
type AccessDetails struct {
AccessUuid string
UserId uint64
}
and the code is:
func (server *Server) GetTokenUserIDFromRequest(r *http.Request) (uint64, error) {
var authDetails auth.AccessDetails
tokenString := auth.ExtractToken(r)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return 0, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("API_SECRET")), nil
})
if err != nil {
return 0, err
}
claims, ok := token.Claims.(jwt.MapClaims)
if ok && token.Valid {
accessUuid, ok := claims["access_uuid"].(string)
if !ok {
return 0, errors.New("token not valid, cannot retrieve access_uuid")
}
userid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 64)
if err != nil {
return 0, err
}
authDetails := &auth.AccessDetails{
AccessUuid: accessUuid,
UserId: userid,
}
}
redisuserid, err := server.RedisClient.Get(authDetails.AccessUuid).Result()
if err != nil {
return 0, err
}
userID, _ := strconv.ParseUint(redisuserid, 10, 64)
if userID != authDetails.UserId {
return 0, errors.New("userid from token does not match userid from database")
}
return userID, nil
}

You are redeclaring authDetails in the if-block instead of setting the value declared at the outer scope. The relevant section in the language specification is:
https://golang.org/ref/spec#Short_variable_declarations
Instead of authDetails:=... use authDetails=..., so it becomes an assignment, not a declaration.

Related

Check if any variable conforms any interface using generics in Go

I am writing an API using go-fiber, and I want to check, if passed JSON conforms an interface that I want to see. So I decided to use 1.18's feature - generics. Here is what I did, but it does not work due to type problem.
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) (I, error) {
if err := c.BodyParser(&format); err != nil {
return nil, err
}
return c.JSON(format), nil
}
The errors say
src/endpoints/v1/tasks.go:36:10: cannot use nil as I value in return statement
src/endpoints/v1/tasks.go:39:9: cannot use c.JSON(format) (value of type error) as type I in return statement
And I want to call the function like this:
type CreateTaskDF struct {
Target string `json:"target"`
Deepness int `json:"deepness"`
}
func CreateTask(c *fiber.Ctx) error {
data, err := checkDataConformsInterface[CreateTaskDF](&CreateTaskDF{}, c)
if err != nil {
log.Fatal(err)
}
// work with data here
...
How should I convert the return value in the function to make it work? Thanks!
It probably could work like this(if you do not consider any lib-based payload validators, which exist in almost every golang routing lib or web framework). So, to just validate your data you can use this:
func checkDataConformsInterface[I any](format I, c *fiber.Ctx) bool {
if err := c.BodyParser(&format); err != nil {
return false
}
return true
}
So I came up with the following solution
func checkDataConformsInterface[I any](format *I, c *fiber.Ctx) error {
if err := c.BodyParser(&format); err != nil {
return err
}
err := c.JSON(format)
if err != nil {
return err
}
return nil
}
which can be called like
func CreateTask(c *fiber.Ctx) error {
parsedData := CreateTaskDF{}
err := checkDataConformsInterface[CreateTaskDF](&parsedData, c)
if err != nil {
c.SendStatus(400)
return c.SendString("Wrong data")
}
Please, point me the problems if any

What is the best practice to unlock an already-deferred read-unlock RWMutex before the end of the function?

I'm working on a database and I want to know the best way to solve this issue.
Basically, I would like to unlock the read mutex earlier in the function because the last bit is concurrent safe.
func (s *Structure) Get(key interface{}, object interface{}) (found bool, err error ){
s.RLock()
defer s.RUnlock()
// Concurrent-dangerous stuff begins
ref, err := s.Index.GetOneEquals(string(s.StructName), key)
if err != nil {
return false, err
}
path := ref.ToPath(s.StructName)
if (path == nil) {
return false, nil
}
value, err := dbdrivers.DB.Get(path)
if err != nil {
return false, err
}
// Concurrent-dangerous stuff ends
// RUnlock should technically be here
err = encoding.Marshaler.Unmarshal(value, object)
if err != nil {
return false, err
}
}
If I just add an s.RUnlocked at the bottom part (where it says RUnlocked should technically be here) while keeping the deferred statement, if I understand correctly, it will cause issues. As I understand it, RUnlock works via a counter. So if my program reaches beyond that point ("RUnlocked should technically be here"), it will call the s.RUnlocked and also the deferred s.RUnlocked as well when the function ends. So the counter will decrement two times which might cause a disaster.
Therefore, the only solution I can think of is this - which is begging for gotchas down the line because I need to think of everywhere the function can end:
func (s *Structure) Get(key interface{}, object interface{}) (found bool, err error ){
s.RLock()
// Concurrent-dangerous stuff begins
ref, err := s.Index.GetOneEquals(string(s.StructName), key)
if err != nil {
s.RUnlock() // <----
return false, err
}
path := ref.ToPath(s.StructName)
if (path == nil) {
s.RUnlock() // <----
return false, nil
}
value, err := dbdrivers.DB.Get(path)
if err != nil {
s.RUnlock() // <----
return false, err
}
// Concurrent-dangerous stuff ends
// RUnlock should technically be here
s.RUnlock() // <----
err = encoding.Marshaler.Unmarshal(value, object)
if err != nil {
return false, err
}
}
Is there a safer way to do this?
You can still use defer if you put the inner block into its own function:
func (s *Structure) Get(key interface{}, object interface{}) (found bool, err error ){
s.RLock()
failed, found, err:= func() {
defer s.RUnlock()
ref, err := s.Index.GetOneEquals(string(s.StructName), key)
if err != nil {
return true, false, err
}
// Do stuff
return false, found, nil
}()
if failed {
return found, err
}
// continue function, lock is unlocked
}

How to list all the items in a table with pagination

I'm trying to list all the items in a DynamoDB table with pagination, and here below is my attempt:
const tableName = "RecordingTable"
type Recording struct {
ID string `dynamodbav:"id"`
CreatedAt string `dynamodbav:"createdAt"`
UpdatedAt string `dynamodbav:"updatedAt"`
Duration int `dynamodbav:"duration"`
}
type RecordingRepository struct {
ctx context.Context
svc *dynamodb.Client
}
func NewRecordingRepository(ctx context.Context) (*RecordingRepository, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}
return &RecordingRepository{ctx, dynamodb.NewFromConfig(cfg)}, nil
}
func (r *RecordingRepository) List(page int, size int) ([]Recording, error) {
size32 := int32(size)
queryInput := &dynamodb.QueryInput{
TableName: aws.String(tableName),
Limit: &size32,
}
recordings := []Recording{}
queryPaginator := dynamodb.NewQueryPaginator(r.svc, queryInput)
for i := 0; queryPaginator.HasMorePages(); i++ {
result, err := queryPaginator.NextPage(r.ctx)
if err != nil {
return nil, err
}
if i == page {
if result.Count > 0 {
for _, v := range result.Items {
recording := Recording{}
if err := attributevalue.UnmarshalMap(v, &recording); err != nil {
return nil, err
}
recordings = append(recordings, recording)
}
}
break
}
}
return recordings, nil
}
When I run the code above, I get the following error message:
api error ValidationException: Either the KeyConditions or KeyConditionExpression parameter must be specified in the request.
But why should I specify a KeyConditionExpression when I want to get all the items? Is there another way to go or a workaround this?
Query does need your keys. It is meant to find specific items in your DynamoDB. To get all items in your DynamoDB, you need to use the Scan operation.
This should be easily fixed in your code.
Instead of QueryInput use ScanInput and instead of NewQueryPaginator use NewScanPaginator.
Just replaced QueryInput with ScanInput and QueryPaginator with ScanPaginator.

Why I've got "multiple-value in single-value context" error though I've received there by multiple variable at the left hand side

I've written 2 functions v1GetSessionID and v1SessionIDforBind.
/*
* v1GetSessionID
*/
func v1GetSessionID(c *gin.Context,
app *firebase.App,
client *firestore.Client,
stripcallGetSession func() func(internalUserID string, customerID string) (sessionid string, err error)) (sessionid string, err error) {
defer erapse.ShowErapsedTIme(time.Now())
idToken := c.Param("idToken")
user, err := idToken2User(app, idToken)
if err != nil {return}
customerID, err := user.getCustomerID()
if err != nil {return}
sessionid, err = stripcallGetSession()(user.getInternalID(), customerID)
return
}
/*
* v1SessionIDforBind
*/
func v1SessionIDforBind(c *gin.Context, app *firebase.App, client *firestore.Client) {
defer erapse.ShowErapsedTIme(time.Now())
var requestBody JsonPurchaseBindSessionRequest
if err := c.ShouldBindJSON(&requestBody); err != nil {
okngErrorOut(c, err)
return
}
stripcallGetSession := func() func(internalUserID string, customerID string) (sessionid string, err error) {
return func(internalUserID string, customerID string)(sessionid string, err error){return stripeGetSessionIDforBind(requestBody, internalUserID, customerID)}
}
sessionid, err := v1GetSessionID(c, app, client, stripcallGetSession)()
if err != nil {
log.Println(err)
okngErrorOut(c, err)
} else {
c.JSON(http.StatusOK, gin.H{
"SessionID": sessionid,
})
}
}
In the middle of the latter function, I've called the former as follows;
sessionid, err := v1GetSessionID(c, app, client, stripcallGetSession)()
But go compiler indicate this as multiple-value in single-value context.
go run *.go
# command-line-arguments
./v1handler.go:332:34: multiple-value v1GetSessionID() in single-value context
v1GetSessionID returns 2 value of sessionid string, err error and I've received it by sessionid, err. So why I've got the error mentioned above?
go version is: go1.14.4 linux/arm.
You have an extra set of parens in the call to v1GetSessionID(). You call it with 4 parameters, and it will return two values like that. But by putting the extra parenthesis after that initial call you're asking for Go to then execute the return value as if it was a function, and that's what the compiler seems to be complaining about. Simple example: https://play.golang.org/p/ZX9kp6rxA09
Just drop the second set of parentheses and it should compile:
sessionid, err := v1GetSessionID(c, app, client, stripcallGetSession)

How to write clean code without all these cascading error Christmas trees? [duplicate]

This question already has answers here:
Go Error Handling Techniques [closed]
(11 answers)
Closed 2 years ago.
I wrote a function that should do a simple thing:
look up for a specific address in a table and return the ID, if
already existing
if not, create a new record for this particular address
return the ID of this newly created record
As RDMS I use mysql here. I put everything in a transaction to avoid race conditions in my concurrent go-routines that makes calls to this function.
However, the tons of constant checks for err makes the code ugly and full test coverage hard to get.
Is there anything I can improve here in terms of better code quality?
func getAddressId(db *sql.DB, address string) (int64, error) {
tx, err := db.Begin()
if err != nil {
tx.Rollback()
return 0, err
}
stmt, err := tx.Prepare("SELECT id FROM address WHERE `address`=?")
if err != nil {
tx.Rollback()
return 0, err
}
defer stmt.Close()
var result sql.NullInt64
err = stmt.QueryRow(address).Scan(&result)
if err != nil && err != sql.ErrNoRows {
tx.Rollback()
return 0, err
}
if result.Valid {
tx.Commit()
return result.Int64, nil
}
stmt, err = tx.Prepare("INSERT INTO address (address) VALUES (?)")
if err != nil {
tx.Rollback()
return 0, err
}
var res sql.Result = nil
res, err = stmt.Exec(address)
if err != nil {
tx.Rollback()
return 0, err
}
tx.Commit()
var id int64 = 0
id, err = res.LastInsertId()
return id, err
}
First, and most importantly, there's very little wrong with the above code. There are a few pieces I'd adjust (and will below), but generally it is very clear, straightforward, and (almost) hard to get wrong. There is nothing ugly about that.
Second, see Error Handling and Go for thoughts on error handling Go, though I won't be using those techniques here because they're not necessary.
Now there is one thing that's a bit bad, which is that it's easy to forget to call tx.Rollback() or tx.Commit() in the right places. In my opinion, that's reasonable to fix (but it's really more style than substance). The below isn't tested.
// Name your return values so that we can use bare returns.
func getAddressId(db *sql.DB, address string) (id int64, err error) {
tx, err := db.Begin()
if err != nil {
return // This is a bare return. No need to write "0, err" everywhere.
}
// From this point on, if we exit with an error, then rollback, otherwise commit.
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
stmt, err := tx.Prepare("SELECT id FROM address WHERE `address`=?")
if err != nil {
return
}
defer stmt.Close() // I'm not sure this is correct, because you reuse stmt
// This is purely style, but you can tighten up `err = ...; if err` logic like this:
var result sql.NullInt64
if err = stmt.QueryRow(address).Scan(&result); err != nil && err != sql.ErrNoRows {
return
}
if result.Valid {
id = result.Int64
return
}
if stmt, err = tx.Prepare("INSERT INTO address (address) VALUES (?)"); err != nil {
return
}
res, err := stmt.Exec(address)
if err != nil {
return
}
id = res.LastInsertId()
}
That said, I think this function is doing way too much, and if you break it up, it becomes easier to understand. For example (again, untested):
func getExistingAddressId(tx *sql.Tx, address string) (id int64, err error) {
stmt, err := tx.Prepare("SELECT id FROM address WHERE `address`=?")
if err != nil {
return
}
// I believe you need to close both statements, and splitting it up makes that clearer
defer stmt.Close()
var result sql.NullInt64
if err = stmt.QueryRow(address).Scan(&result); err != nil && err != sql.ErrNoRows {
return
}
// This is probably over-complicated. If !Valid, then .Int64 is 0.
if result.Valid {
return result.Int64, nil
}
return 0, nil
}
func insertNewAddress(tx *sql.Tx, address string) (id int64, err error) {
stmt, err := tx.Prepare("INSERT INTO address (address) VALUES (?)")
if err != nil {
return
}
defer stmt.Close()
res, err := stmt.Exec(address)
if err != nil {
return
}
return res.LastInsertId()
}
func getAddressId(db *sql.DB, address string) (id int64, err error) {
tx, err := db.Begin()
if err != nil {
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
if id, err = getExistingAddressId(tx, address); err != nil || id != 0 {
return
}
return insertNewAddress(tx, address)
}
Using named return values like this is a matter of style, and you could certainly not do it that way and it would be just as clear. But the point (a) defer is a powerful way to avoid duplicating logic that must always run and (b) if a function becomes a mess of error handling, it probably is doing too much.
As a side note, I strongly suspect you could get rid of the Prepare calls here, would would simplify things significantly. You only use the Statements one time. If you cached that Statements and reused them, then it would make sense to Prepare them. If you do that, then the code simplifies to:
func getExistingAddressId(tx *sql.Tx, address string) (int64, error) {
var result sql.NullInt64
if err := tx.QueryRow("SELECT id FROM address WHERE `address`=?", address).
Scan(&result); err != nil && err != sql.ErrNoRows {
return 0, err
}
return result.Int64, nil
}
func insertNewAddress(tx *sql.Tx, address string) (int64, error) {
res, err := tx.Exec("INSERT INTO address (address) VALUES (?)", address)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
func getAddressId(db *sql.DB, address string) (id int64, err error) {
tx, err := db.Begin()
if err != nil {
return 0, err
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
if id, err = getExistingAddressId(tx, address); err != nil || id != 0 {
return
}
return insertNewAddress(tx, address)
}
Rather than trying to simplify Go syntax, this simplifies the operation, which as a side effect makes the syntax simpler.
A small subtlety that may go overlooked if you're not very familiar with named return values. In return insertNewAddress(...), the return value of the function call gets assigned to id and err before the defer runs, so the if err != nil check will correctly reflect the returned value. This can be a bit tricky, so you may prefer to write this all more explicitly, especially now that the function is so much shorter.
func getAddressId(db *sql.DB, address string) (int64, error) {
tx, err := db.Begin()
if err != nil {
return 0, err
}
var id Int64
id, err = getExistingAddressId(tx, address)
if err == nil && id == 0 {
id, err = insertNewAddress(tx, address)
}
if err != nil {
tx.Rollback()
return 0, err
}
tx.Commit()
return id, nil
}
And now the code is very straightforward, with no tricks, which IMO is Go at its best.

Resources