How can I remove a property from a object - go

I have a User struct:
type User struct {
gorm.Model
Email string
Password string
AccountType int
CompanyId int
FirstName string
LastName string
PhoneNumber string
RecoveryEmail string
Contractor bool `gorm:"sql:'not null' default:'false'"`
}
I'm using this struct to get a row from the database using gorm:
// Get a specific user from the database.
func getUser(id uint) (*User, error) {
var user User
if err := database.Connection.Select("id, created_at, email, account_type, company_id, first_name, last_name").Where("id = ? ", id).First(&user).Error; err != nil {
return nil, err
}
fmt.Println(&user)
return &user, nil
}
My Gin hanlder:
// #Summary Attempts to get a existing user by id
// #tags users
// #Router /api/users/getUserById [get]
func HandleGetUserById(c *gin.Context) {
// Were using delete params as it shares the same interface.
var json deleteParams
if err := c.Bind(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "No user ID found, please try again."})
return
}
outcome, err := getUser(json.Id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Something went wrong while trying to process that, please try again.", "error": err.Error()})
log.Println(err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Successfully found user",
"user": outcome,
})
}
It returns back everything fine, but when I return &user the fields not selected are returned back with default values:
{
"message": "Successfully found user",
"user": {
"ID": 53,
"CreatedAt": "2018-06-24T00:05:49.761736+01:00",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Email": "jack#jackner.com",
"Password": "",
"AccountType": 0,
"CompanyId": 2,
"FirstName": "",
"LastName": "",
"PhoneNumber": "",
"RecoveryEmail": "",
"Contractor": false
}
}
Is there a way in go to remove empty or null properties from an object? Or will I have to send back an object instead with the values mapped to said new object? If there's a simple way of doing the former with a helper function I'd like to know how.

You can specify the omitempty tag in your object's fields definitions.
Example:
Email string `json:",omitempty"`
If you define the fields that way, empty values will not be present in the JSON output:
https://golang.org/pkg/encoding/json/#Marshal
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

Related

Gin - struct param is validated as null

I have a function here to create post requests and add a new user of type struct into a slice (the data for the API is just running in memory, so therefore no database):
type user struct {
ID string `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
}
var users = []user{
{ID: "1", FirstName: "John", LastName: "Doe", Email: "john.doe#email.com"},
}
func createUser(c *gin.Context) {
var newUser user
if len(newUser.ID) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": "user id is null"})
return
}
users = append(users, newUser)
c.JSON(http.StatusCreated, newUser)
}
Eveything worked fine, until i tried to make an 'if statement' that checks if an id for a user being sent with a post request is null.
The problem is that the if statement returns true, when it should return false. So if i for example try to make a post request with the following data:
{
"id": "2",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe#email.com"
}
The if statement that checks for an id with the length of 0 will be true, and therefore return a StatusBadRequest. If have also tried this way:
if newUser.ID == "" {
}
But this also returns true when it shouldn't.
If i remove this check and create the POST request it works just fine, and the new added data will appear when i make a new GET request.
Why do these if statements return true?
When you create a new user object with var newUser user statement, you are just creating an empty user object. You still have to bind the JSON string you are sending into that object. for that, what you need to do is: c.BindJSON(&newUser). full code will be like:
func createUser(c *gin.Context) {
var newUser user
err := c.BindJSON(&newUser)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
if len(newUser.ID) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": "user id is null"})
return
}
users = append(users, newUser)
c.JSON(http.StatusCreated, newUser)
}
you can check this link for an example provided by Gin: https://github.com/gin-gonic/examples/blob/master/basic/main.go#L61

How to sanitize strings in Golang striping html, javascript, sql etc

I'm writing a REST API, through which users can register/login and a number of other things that would require them to send JSON in the request body. The JSON sent in the request body will look something like this for registering:
{
"name": "luke",
"email": "l#g.com",
"date_of_birth": "2012-04-23T18:25:43.511Z",
"location": "Galway",
"profile_image_id": 1,
"password": "password",
"is_subscribee_account": false,
"broker_id": 1
}
This data will then be decoded into a struct and the individual items inserted into the database. I'm not familiar with how I should sanitize the data to remove any Javascript, HTML, SQL etc that may interfere with the correct running of the application and possibly result in a compromised system.
The below code is what I have currently to read in and validate the data but it doesn't sanitize the data for html, javascript, sql etc.
Any input would be appreciated on how I should go about sanitizing it. The REST API is written in Go.
//The below code decodes the JSON into the dst variable.
func (app *application) readJSON(w http.ResponseWriter, r *http.Request, dst interface{}) error {
maxBytes := 1_048_576
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
err := dec.Decode(dst)
if err != nil {
var syntaxError *json.SyntaxError
var unmarshalTypeError *json.UnmarshalTypeError
var invalidUnmarshalError *json.InvalidUnmarshalError
switch {
case errors.As(err, &syntaxError):
return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset)
case errors.Is(err, io.ErrUnexpectedEOF):
return errors.New("body contains badly-formed JSON")
case errors.As(err, &unmarshalTypeError):
if unmarshalTypeError.Field != "" {
return fmt.Errorf("body contains incorrect JSON type for field %q", unmarshalTypeError.Field)
}
return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset)
case errors.Is(err, io.EOF):
return errors.New("body must not be empty")
case strings.HasPrefix(err.Error(), "json: unknown field "):
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
return fmt.Errorf("body contains unknown key %s", fieldName)
case err.Error() == "http: request body too large":
return fmt.Errorf("body must not be larger than %d bytes", maxBytes)
case errors.As(err, &invalidUnmarshalError):
panic(err)
default:
return err
}
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must only contain a single JSON value")
}
return nil
}
//The below code validates the email, password and user. If the first parameter in V.Check() is false the following two parameter are returned in the response to the user.
func ValidateEmail(v *validator.Validator, email string) {
v.Check(email != "", "email", "must be provided")
v.Check(validator.Matches(email, validator.EmailRX), "email", "must be a valid email address")
}
func ValidatePasswordPlaintext(v *validator.Validator, password string) {
v.Check(password != "", "password", "must be provided")
v.Check(len(password) >= 8, "password", "must be at least 8 bytes long")
v.Check(len(password) <= 72, "password", "must not be more than 72 bytes long")
}
func ValidateUser(v *validator.Validator, user *User) {
v.Check(user.Name != "", "name", "must be provided")
v.Check(len(user.Name) <= 500, "name", "must not be more than 500 bytes long")
ValidateEmail(v, user.Email)
if user.Password.plaintext != nil {
ValidatePasswordPlaintext(v, *user.Password.plaintext)
}
if user.Password.hash == nil {
panic("missing password hash for user")
}
}
How do I serialize the struct data with this in mind? So it wont cause issues if later inserted in a webpage. Is there a package function that I can pass the string data to that will return safe data?

Golang Gorm dynamically create user with map string interface not working

I am building a map[string]interface{} dynamically to create users from an imported CSV file. The map looks alright when I print it, but when I want to create it (add to my database) with Gorm I get an error.
for i := range results {
userMap := map[string]interface{}{}
for c := range choices {
if choices[c] != "Unspecified" {
userMap[choices[c]] = results[i][c]
}
}
fmt.Printf("%+v\n", userMap)
err = db.Model(&models.User{}).Create(userMap).Error
if err != nil {
fmt.Println(err)
}
}
The error I receive is:
zero-length delimited identifier at or near """"
I also activated the Gorm debugger and that outputs:
←[33m[2022-01-28 12:05:04]←[0m ←[36;1m[22.72ms]←[0m INSERT INTO ""
DEFAULT VALUES RETURNING "".* ←[36;31m[0 rows affected or returned
]←[0m
I checked the official documentation and it states that a create should be possible when using a map[string]interface{}.
I am clueless on why it is not working. Hope someone can help me out.
P.S. The outcome of the printf is:
map[CreatedAt:1/28/2022 8:25 Email:name#privder.nl FirstName:John
LastName:Doe
Password:$2a$23$nZWRc/UD9swTuoeF7XC2mdOrIEMCJzr3H1qrkaNQiE0AUcJrhWmCC]
My User model:
package models
import (
"time"
)
type User struct {
Id int `gorm:"AUTO_INCREMENT"`
CompanyId int `gorm:""`
FirstName string `gorm:"size:24"`
LastName string `gorm:"size:24"`
Email string `gorm:"size:64"`
Password string `gorm:"size:128"`
Activation Activation `gorm:"size:64"`
CreatedAt time.Time `gorm:"DEFAULT:current_timestamp"`
UpdatedAt time.Time `gorm:"DEFAULT:null"`
DeletedAt time.Time `gorm:"DEFAULT:null"`
}
If I would do this, it works perfectly fine:
err = db.Model(&models.User{}).Create(&models.User{
FirstName: "123",
LastName: "456",
Email: "789#nl.nl",
Password: "sdfsdfnsdfsjdkfh",
}).Error
if err != nil {
fmt.Println(err)
}
But when I do this, it doesnt work:
err = db.Model(&models.User{}).Create(map[string]interface{}{
"FirstName": "AAAAAA",
"LastName": "AAAAAA",
"Email": "aaa#bbbb.com",
"Password": "sdfsdfnsdfsjdkfh",
}).Error
if err != nil {
fmt.Println(err)
}
That results in the error:
INSERT INTO "" DEFAULT VALUES RETURNING "".*
I am using the latest (as far as I know) version of Gorm too. In my go.mod file it states:
github.com/jinzhu/gorm v1.9.16

gorm: populating related field of newly created data

I have the following related tables:
type Person struct {
ID uint64 `json:"id" gorm:"primary_key;auto_increment"`
Name string `json:"name"`
Surname string `json:"surname"`
}
type Book struct {
ID uint64 `json:"id" gorm:"primary_key;auto_increment"`
Title string `json:"title" binding:"required,min=2,max=100" gorm:"type:varchar(100)"`
Author Person `json:"author" binding:"required" gorm:"foreignkey:AuthorID"` // * here
AuthorID uint64 `json:"-"` // * here
WrittenIn string `json:"written_in" gorm:"type:varchar(5)"`
CreatedAt time.Time `json:"created_at" gorm:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:CURRENT_TIMESTAMP"`
}
I can successfully create data with Create() method of gorm using this function:
func CreateBook(ctx *gin.Context) {
// validating input
var inputData CreateBookInput
if err := ctx.ShouldBindJSON(&inputData); err != nil {
ctx.JSON(401, gin.H{"status": "fail", "error": err.Error()})
}
// create book
book := models.Book{Title: inputData.Title, AuthorID: inputData.AuthorID, WrittenIn: inputData.WrittenIn}
database.DB.Create(&book).Preload("Author")
// database.DB.Preload("Author").Create(&book)
// database.DB.Set("gorm:auto_preload", true).Create(&book)
ctx.JSON(201, gin.H{"status": "success", "book": book})
}
I want to return the newly created book with its author. Expected response:
"book": {
"id": 10,
"title": "Chinor ostidagi duel",
"author": {
"id": 3,
"name": "John",
"surname": "Smith"
},
"written_in": "1983",
"created_at": "2022-01-07T17:07:50.84473824+05:00",
"updated_at": "2022-01-07T17:07:50.84473824+05:00"
}
But I couldn't find a way to populate related 'author'. So what I get is:
"book": {
"id": 10,
"title": "Chinor ostidagi duel",
"author": {
"id": 0, // empty
"name": "", // empty
"surname": "" // empty
},
"written_in": "1983",
"created_at": "2022-01-07T17:07:50.84473824+05:00",
"updated_at": "2022-01-07T17:07:50.84473824+05:00"
}
Itried these methods with no success:
database.DB.Create(&book).Preload("Author")
database.DB.Preload("Author").Create(&book)
database.DB.Set("gorm:auto_preload", true).Create(&book)
database.DB.Create(&book).Set("gorm:auto_preload", true)
How can I populate related field of newly created data?
One possible solution that you could try is to use the AfterCreate hook.
func (b *Book) AfterCreate(tx *gorm.DB) (err error) {
return tx.Model(b).Preload("Author").Error
}
You can find more info about hooks here.
Preload is chain method, Create is finisher method. only finisher method will generate and execute SQL.
So...
1 Find author by id after create book
if err ;= database.DB.Create(&book).Error; err != nil {
return err
}
// will not throw not found error
database.DB.Limit(1).Find(&book.Author, book.AuthorID)
ctx.JSON(201, gin.H{"status": "success", "book": book})
2 Load author data every times, use hook
// create and update
// func (b *Book) AfterSave(tx *gorm.DB) (err error) {
// just create
func (b *Book) AfterCreate(tx *gorm.DB) (err error) {
// handle error if you want
tx.Limit(1).Find(&b.Author, b.AuthorID)
return
}

Test fails to capture logging output

I am trying to test my UserRegister functionality, it takes http request.
If user enters already existing email, UserRegister returns an error log (using logrus).
logs "github.com/sirupsen/logrus"
func UserRegister(res http.ResponseWriter, req *http.Request) {
requestID := req.FormValue("uid")
email := req.FormValue("email")
logs.WithFields(logs.Fields{
"Service": "User Service",
"package": "register",
"function": "UserRegister",
"uuid": requestID,
"email": email,
}).Info("Received data to insert to users table")
// check user entered new email address
hasAccount := checkemail.Checkmail(email, requestID) // returns true/false
if hasAccount != true { // User doesn't have an account
db := dbConn()
// Inserting token to login_token table
insertUser, err := db.Prepare("INSERT INTO users (email) VALUES(?)")
if err != nil {
logs.WithFields(logs.Fields{
"Service": "User Service",
"package": "register",
"function": "UserRegister",
"uuid": requestID,
"Error": err,
}).Error("Couldnt prepare insert statement for users table")
}
insertUser.Exec(email)
defer db.Close()
return
} // user account created
logs.WithFields(logs.Fields{
"Service": "User Service",
"package": "register",
"function": "UserRegister",
"uuid": requestID,
"email": email,
}).Error("User has an account for this email")
}
In my test module, I used following.
func TestUserRegister(t *testing.T) {
rec := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "http://localhost:7071/register?email=sachit45345h#gmail.com&uid=sjfkjsdkf9w89w83490w", nil)
UserRegister(rec, req)
expected := "User has an account for this email"
res := rec.Result()
content, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error("Couldnt read body", err)
}
val, err := strconv.Atoi(string(bytes.TrimSpace(content)))
if err != nil {
log.Println("Error parsing response", err)
}
if string(val) != expected {
t.Errorf("Expected %s, got %s", expected, string(content))
}
}
Result : Error parsing response strconv.Atoi: parsing "": invalid syntax
Why response can not be converted?
Checked threads:
Why is this Golang code to convert a string to an integer failing.
Edit : after #chmike answer.
This is a part of microservice. All the responses are written to API-Gateway. Using a function.
But here I just want to perform unit test and check whether my UserRegister works as expected.
The function UserRegister never writes to res or sets the status. As a consequence you get an empty string from res in TestUserRegister. content is an empty string and the conversion of an empty string to an integer with Atoi fails since there is no integer to convert.
I can only explain what happens. I can’t tell you what to do to fix the problem because you don’t explain what you want to do or get in the question.
The http response you are reading is not the response to your request. You create a response and expect it to have something in it. it will not. So you end up trying to create an integer from an empty string.
Look at some examples to see where the real response would come from. https://golang.org/pkg/net/http

Resources