I wrote some code to attempt to get a table description in Go, using the AWS SDK V2 DynamoDB package:
// First, create a connection to our local DynamoDB
client := dynamodb.NewFromConfig(cfg)
// Next, attempt to get the table description associated with the table name
output, err := client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
TableName: table.TableName,
})
// Now, if we got an error then check if it was a resource-not-found exception. If
// it was then that means we should create the table; otherwise, it means that something
// isn't right so return it. If the description was nil, we'll also create the table
var create bool
if err != nil {
if _, ok := err.(*types.ResourceNotFoundException); !ok {
return err
} else {
create = true
}
} else if output == nil {
create = true
}
During testing, this code returned the following error:
operation error DynamoDB: DescribeTable, https response error StatusCode: 400, RequestID: 4b0bcb2b-c833-459f-9db2-54841aa1bbd3, ResourceNotFoundException
The problem I'm having is that this is clearly a ResourceNotFoundException but the cast is not working. Is there something else I need to do to get this to work?
I have found a solution. First, one of the issues I had was that I was importing github.com/aws/aws-sdk-go-v2/service/sso/types instead of github.com/aws/aws-sdk-go-v2/service/dynamodb/types so my cast would never have worked. That being said, making this change did not fix the issue.
After log-debugging, I discovered that the v2 SDK wraps the AWS errors in a *smithy.OperationError type. Therefore, direct-casting and errors.Is won't work.
What I did that actually worked here was to change my error checking code to this:
if temp := new(types.ResourceNotFoundException); !errors.As(err, &temp) {
return err
} else {
create = true
}
Related
I am relatively new to Golang and Gin(framework)
I am writing some really simple API endpoints . But I notice something really weird about Gin, if there is any codition or function within my code that returns false the rest of my code or conditions does not get executed and Gin just returns a JSON with empty status :
{"status":""}
Here is a very simple code to explain what I mean
In a functions.go file I have :
func VerifyUserLogin(username,password,userAgent string) (string, string) {
userData := Users{}
userQueryColumn := "username=?"
// if they are trying to login with email
if nEmailHelpers.EmailIsValid(username) == true{
userQueryColumn = "email=?"
}
if getUserData := db.Select("password").Where(userQueryColumn, strings.ToLower(username)).First(&userData); getUserData.Error != nil {
// Meaning there was an error, most likely no data found , so we just return false todo improve this error handling later to handle more specific errors
return "", feedback["InvalidLogin"]
} else {
if getUserData.RowsAffected == 1 {
if nSecurityHelpers.CheckPasswordHash(password, userData.Password)==true {
token, tokenError := CreateToken(username, userAgent, false, 60)
if tokenError == nil {
return token, feedback["ValidLogin"]
} else {
return "", feedback["TokenNotGenerated"]
}
} else {
return "", feedback["InvalidLogin"]
}
}
return "", feedback["InvalidLogin"]
}
}
In another go file that references the functions.go file I have :
func main(){
router := gin.Default()
router.POST ("login",loginUser)
router.Run()
}
var feedback = userFeedback.Users()
// loginUser function to login a user
func loginUser(c *gin.Context){
requestBody := neielRequestsHelpers.RequestBody(c)
username := requestBody["username"]
password := requestBody["password"]
userAgent := c.Request.Header.Get("User-Agent")
token, loginMessage := userFunctions.VerifyUserLogin(username,password,userAgent)
// todo come back and fix proper error message when user does not exist
fmt.Println(loginMessage)
if loginMessage==feedback["ValidLogin"]{
c.JSON(http.StatusOK, gin.H{"status":loginMessage,"token":token})
}else{
c.JSON(http.StatusUnauthorized, gin.H{"status":feedback["InvalidLogin"]})
}
}
If all my inputs are correct , all goes well (Username exists and password is correct). But I have to handle scenario where username or password is invalid .If for any reason getUserData or nSecurityHelpers.CheckPasswordHash() is false , or any function for that matter returns a boolean of false . The entire function just terminates and doesn't allow me handle the error the way I want and output custom JSON response. I just get this {"status":""}
I am 100% sure this issue is from Gin , but I don't know what to activate or deactivate to allow me handle errors on my own. I have read the docs, but its obvious I am missing something .
Kindly help me please .
I have resolved this, thanks to everyone that tried to help
It was a typo error causing the issue "InValidLogin" instead of "InvalidLogin" in another file.
It was really subtle
I am using oapi-codegen to generate my server code and Echo Labstack as the server.
When I pass a Group instance to Openapi.RegisterHandlers instead of an Echo instance, I always get a 400 error with {"message":"no matching operation was found"} for any request in that group:
swagger, err := Openapi.GetSwagger()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
os.Exit(1)
}
// Use oapi validation middleware to check all requests against the
// OpenAPI schema.
g := e.Group("/api", middleware.OapiRequestValidator(swagger))
Openapi.RegisterHandlers(g, &MyApi{})
If send request /api/foo, where foo is an API endpoint defined in the generated server code, I get a 400 error. If I do /api/<some undefined api> I also get 400. If I do send a request for /baz, I get 404 as expected, since that isn't a defined route. If I don't pass a prefix to Group(), I get a 400 error for every request. I get the same behavior if I use RegisterHandlersWithBaseURL()
There seems to be a bug where if you specify the a base path, either to the Group() function or to RegisterHandlersWithBaseURL(), theOapiRequestValidator middle ignores the base path when checking the request path against the routes. It uses the routes defined in the OpenAPI spec without the base path. To work around this, I overwrote the inline.tmpl template and hacked the GetSwagger() function to include this at the bottom:
func GetSwagger(pathPrefix string) (swagger *openapi3.T, err error) {
...
var updatedPaths openapi3.Paths = make(openapi3.Paths)
for key, value := range(swagger.Paths) {
updatedPaths[pathPrefix + key] = value
}
swagger.Paths = updatedPaths
}
The key in the Path map is the route. I just append the base path to every key.
I am trying to upload a CustomSchema to all Users of a company in GSuite. This Custom Schema contains their Github Usernames, which I extracted with the github API.
The problem is, after running the code, the account in Gsuite is not added.
Relevant code (A connection to GSuite with admin Authentication is established, the map has all user entries. If you still want more code, I can provide you with it - just trying to keep it simple):
for _, u := range allUsers.Users {
if u.CustomSchemas != nil {
log.Printf("%v", string(u.CustomSchemas["User_Names"]))
}else{
u.CustomSchemas = map[string]googleapi.RawMessage{}
}
nameFromGsuite := u.Name.FullName
if githubLogin, ok := gitHubAccs[nameFromGsuite]; ok {
userSchemaForGithub := GithubAcc{GitHub: githubLogin}
jsonRaw, err := json.Marshal(userSchemaForGithub)
if err != nil {
log.Fatalf("Something went wrong logging: %v", err)
}
u.CustomSchemas["User_Names"] = jsonRaw
adminService.Users.Update(u.Id, u)
} else {
log.Printf("User not found for %v\n", nameFromGsuite)
}
}
This is the struct for the json encoding:
type GithubAcc struct {
GitHub string `json:"GitHub"`
}
For anyone stumbling upon this.
Everything in the code snippet is correct. By the way the method is written, I expected that adminService.Users.Update() actually updates the user. Instead, it returns an UserUpdatesCall.
You need to execute that update by calling .Do()
From the API:
Do executes the "directory.users.update" call.
So the solution is to change adminService.Users.Update(u.Id, u)
into adminService.Users.Update(u.Id, u).Do()
According to the official docs https://docs.sentry.io/clients/go/ you can log errors in Sentry from a golang project with:
// For Errors
raven.CapturePanic(func() {
// do all of the scary things here
}, nil)
// For panic
if err != nil {
raven.CaptureErrorAndWait(err, nil)
log.Panic(err)
}
This works like a charm, the problem is in Sentry both functions are logged with level 'Error'. Anyone knows how can the logging level be specified for each call? In Python is very explicit, but I don't see it for Go.
Using the sentry-go SDK, the Level is set on the Scope.
Documentation:
https://pkg.go.dev/github.com/getsentry/sentry-go?tab=doc#Scope.SetLevel
https://pkg.go.dev/github.com/getsentry/sentry-go?tab=doc#Level
Example:
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetLevel(sentry.LevelFatal)
sentry.CaptureException(errors.New("example error"))
})
I followed the advice in the comments, and came up with this:
// sentryErrorCapture sends error data to Sentry asynchronously. Use for non-Fatal errors.
var sentryErrorCapture = func(err error, severity raven.Severity, tags map[string]string, interfaces ...raven.Interface) string {
packet := newSentryPackage(err, severity, tags, interfaces...)
eventID, _ := raven.Capture(packet, tags)
return eventID
}
func newSentryPackage(err error, severity raven.Severity, tags map[string]string, interfaces ...raven.Interface) (packet *raven.Packet) {
interfaces = append(interfaces,
raven.NewException(err, raven.GetOrNewStacktrace(err, 1, 3, raven.IncludePaths())))
packet = &raven.Packet{
Message: err.Error(),
Level: severity,
Interfaces: interfaces,
Extra: getSentryExtraInfo(),
}
return
}
When I want to log an error specifying the level I call: sentryErrorCapture(err, raven.ERROR, nil).
I'm trying to write a twitter reader that resolves the final URLs of link shorteners etc, but gives me a URL along the way for a list of manually defined host patterns. The reason to do this is that i don't want to end up with the paywall URL but the one before.
As far as i can tell the way to do this is write my own client based on the default RoundTripper because returning an error from a custom CheckRedirect function aborts the client without yielding a response.
Is there a way to use the default client and record a list of URLs/specific URL from a custom checkRedirect function?
The client request will actually still return the last valid Response in cases where your custom CheckResponse yields an error (As mentioned in the comments).
http://golang.org/pkg/net/http/#Client
If CheckRedirect returns an error, the Client's Get method returns both the previous Response and CheckRedirect's error (wrapped in a url.Error) instead of issuing the Request req.
If you maintain a list of "known" paywall-urls, you can abort the paywall-redirect in your CheckResponse with a custom error type (Paywalled in the example below).
Your error handling code later has to consider that error type as a special (non-erroneous) case.
Example:
package main
import (
"errors"
"fmt"
"net/http"
"net/url"
)
var Paywalled = errors.New("next redirect would hit a paywall")
var badHosts = map[string]error{
"registration.ft.com": Paywalled,
}
var client = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// N.B.: when used in production, also check for redirect loops
return badHosts[req.URL.Host]
},
}
func main() {
resp, err := client.Get("http://on.ft.com/14pQBYE")
// ignore non-nil err if it's a `Paywalled` wrapped in url.Error
if e, ok := err.(*url.Error); (ok && e.Err != Paywalled) || (!ok && err != nil) {
fmt.Println("error: ", err)
return
}
resp.Body.Close()
fmt.Println(resp.Request.URL)
}