Need to validate two fields in go-playground/validator/v10 - go

need to check atleast one of field should be present email, phone atleast one is mandatory
currently I have custom validator
func validateEmailPhone(fl validator.FieldLevel) bool {
user := fl.Top().Interface().(models.User)
validate := validator.Validate{}
if user.Email == "" && user.Phone == "" {
return false
}
if user.Email != "" {
if err := validate.Var(user.Email, "email"); err != nil {
return false
}
}
if user.Phone != "" {
if err := validate.Var(user.Phone, "e164"); err != nil {
return false
}
}
return true
}
but its panic
the user is gorm model
in this scenario how to check these two fields

There are plenty of conditional required tags in validator.
In your case, you probably want required_without_all + omitempty.
required_without_all - makes a field required if all fields in the list are empty.
omitempty - simply allows the field to be empty.
Please keep in mind that the required tag must come before omitempty.
Example

Related

What will happen if I doesn't give column name or commented it in DropIndex method of gorm v2?

In the below code, I have commented the column name. What will happen here. Does it remove the index from all columns if they have?
// Drop index for Name field
//db.Migrator().DropIndex(&User{}, "Name")
db.Migrator().DropIndex(&User{}, "idx_name")
Test the User under PostgreSQL
type User struct {
Id uint64
Avatar string
Nickname string `gorm:"index:idx_name"`
Password string
}
Your first question
I doesn't give column name in DropIndex method of gorm v2?
Test codes
err = db.Migrator().DropIndex(&User{}, "")
if err != nil {
fmt.Println(err)
}
error
unterminated quoted identifier at or near """ (SQLSTATE 42601)
Second question
commented it in DropIndex method of gorm v2
Test codes
//err = db.Migrator().DropIndex(&User{}, "Nickname")
//if err != nil {
// fmt.Println(err)
//}
err = db.Migrator().DropIndex(&User{}, "idx_name")
if err != nil {
fmt.Println(err)
}
Result: the index idx_name is deleted from Postgresql successfully
Per the source code of DropIndex, the DROP INDEX command will be executed here.
func (m Migrator) DropIndex(value interface{}, name string) error {
return m.RunWithValue(value, func(stmt *gorm.Statement) error {
if idx := stmt.Schema.LookIndex(name); idx != nil {
name = idx.Name
}
return m.DB.Exec("DROP INDEX ?", clause.Column{Name: name}).Error
})
}
Even if the db.Migrator().DropIndex(&User{}, "Nickname") is comment, the index will be deleted.

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?

Empty string for DynamoDB index key in Golang

I've been using Protobuf for object definitions to be used when communicating with DynamoDB. Until now, such objects would look like this:
type Record struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,2,opt,name=id,json=id,proto3" dynamodbav:"id,omitempty" json:"id,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name,json=name,proto3" dynamodbav:"name,omitempty" json:"name,omitempty"`
OrgId string `protobuf:"bytes,4,opt,name=org_id,json=orgId,proto3" dynamodbav:"org_id,omitempty" json:"org_id,omitempty"`
AccountId string `protobuf:"bytes,7,opt,name=account_id,json=accountId,proto3" dynamodbav:"account_id,omitempty" json:"account_id,omitempty"`
Address string `protobuf:"bytes,9,opt,name=address,proto3" dynamodbav:"address,omitempty" json:"address,omitempty"`
BillingTitle string `protobuf:"bytes,10,opt,name=billing_title,json=billingTitle,proto3" dynamodbav:"billing_title,omitempty" json:"billing_title,omitempty"`
Language string `protobuf:"bytes,11,opt,name=language,proto3" dynamodbav:"language,omitempty" json:"language,omitempty"`
Personal string `protobuf:"bytes,12,opt,name=personal,proto3" dynamodbav:"personal,omitempty" json:"personal,omitempty"`
Phonenumber string `protobuf:"bytes,13,opt,name=phonenumber,proto3" dynamodbav:"phonenumber,omitempty" json:"phonenumber,omitempty"`
Postalcode string `protobuf:"bytes,14,opt,name=postalcode,proto3" dynamodbav:"postalcode,omitempty" json:"postalcode,omitempty"`
ProjectId string `protobuf:"bytes,15,opt,name=project_id,json=projectId,proto3" dynamodbav:"project_id,omitempty" json:"project_id,omitempty"`
Remarks string `protobuf:"bytes,16,opt,name=remarks,proto3" dynamodbav:"remarks,omitempty" json:"remarks,omitempty"`
}
The table I'm adding these objects to has a global secondary index with project_id as the sort-key but this field isn't always populated. So, when I went to do a PutObject operation on the table, like this:
record := Record {
Id: "A000",
Name: "Some Record",
OrgId: "O000"
}
attrs, err := dynamodbattribute.MarshalMap(&record)
if err != nil {
panic(err)
}
if _, err := conn.PutItem(&dynamodb.PutItemInput{
TableName: aws.String(tableName),
Item: attrs
}); err != nil {
panic(err)
}
this code wouldn't panic and I'd see the value in DynamoDB. However, after removing the omitempty clause from all the dynamodbav tags, I notice that this now fails with the following error:
ValidationException: Invalid attribute value type
status code: 400, request id: 6a626232-fcd4-4999-afe4-3df5769ce1b2
After some further investigation, I see that the object is serialized as:
map[account_id:{
NULL: true
} address:{
NULL: true,
} billing_title:{
NULL: true
} id:{
S: "A000"
} name:{
S: "Some Record"
} language:{
NULL: true
} org_id:{
S: "O000"
} personal:{
NULL: true
} phonenumber:{
NULL: true
} postalcode:{
NULL: true
} project_id:{
NULL: true
} remarks:{
NULL: true
}
when omitempty is not included but serializes to:
map[id:{
S: "A000"
} name:{
S: "Some Record"
} org_id:{
S: "O000"
}
when omitempty is included.
I'd like to get the former with default values rather than NULL: true but I don't see any serialization options for this. Is there a way to do this without implementing the DynamoDB marshaler interface?
After some investigation, I found the source code for the Marshal function, here. According to this, there's a field called NullEmptyString which I can set to tell the encoder that I want empty strings to be sent as strings, rather than as null attributes. However, if I set this I cannot use Marshal, MarshalMap and MarshalList directly. So, I copied them and wrote my own:
func MarshalMap(in interface{}) (map[string]*dynamodb.AttributeValue, error) {
av, err := getEncoder().Encode(in)
if err != nil || av == nil || av.M == nil {
return map[string]*dynamodb.AttributeValue{}, err
}
return av.M, nil
}
func getEncoder() *dynamodbattribute.Encoder {
encoder := dynamodbattribute.NewEncoder()
encoder.NullEmptyString = false
return encoder
}

Scan a dynamodb table and using contains on a list with go sdk

I have a dynamodb field that looks like this:
[ { "S" : "test#gmail.com" }, { "S" : "test2#gmail.com" } ]
I am trying to run a scan to return any record that e.g. contain test#gmail.com . I am not sure I should use contains to do this, it's currently not returning any records, any pointers as to what I should use?
My go is setup like this:
type Site struct {
ID string `json:"id"`
Site string `json:"site"`
Emails []string `json:"emails,omitempty"`
}
func (ds *datastore) GetEmail(email string, out interface{}) error {
filt := expression.Name("emails").Contains(strings.ToLower(email))
fmt.Println("Get Email", filt)
//filt := expression.Contains(expression.Name("emails"), expression.Value(email))
proj := expression.NamesList(
expression.Name("emails"),
expression.Name("site"),
)
expr, err := expression.NewBuilder().
WithFilter(filt).
WithProjection(proj).
Build()
if err != nil {
fmt.Println(err)
}
scanInput := &dynamodb.ScanInput{
ExpressionAttributeNames: expr.Names(),
ExpressionAttributeValues: expr.Values(),
FilterExpression: expr.Filter(),
ProjectionExpression: expr.Projection(),
TableName: aws.String(ds.TableName),
}
result, err := ds.DDB.Scan(scanInput)
if err != nil {
fmt.Println("what is the err", err)
return err
}
if len(result.Items) == 0 {
fmt.Println("No Email found")
return errors.New(http.StatusText(http.StatusNotFound))
}
err = ds.Marshaler.UnmarshalMap(result.Items[0], out)
return err
}
If you're doing a contains on a partial email it won't match when the filter is applied to a set. It will have to be an exact email match.
{
"Email": "test#gmail.com"
}
// This will match a contains on "test#g"
{
"Emails": ["test#gmail.com", "another#gmail.com"]
}
// this will not match a contains on "test#g" but will match a contains of "test#gmail.com"
See contains: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html
Also note you're doing a scan. Scans perform poorly in Dynamodb as soon as your data is of any significant size. Think of storing your data in a different format so you query it via partition keys or use an AWS RDS as an alternative.

Go: Represent path without query string

How do I represent a path without query string?
Eg.:
www.example.com/user instead of
www.example.com/user?id=1
The following code didn't work:
Go:
if r.URL.Path[4:] != "" {
//do something
}
func main() {
req, err := http.NewRequest("GET", "http://www.example.com/user?id=1", nil)
if err != nil {
log.Fatal(err)
}
// get host
fmt.Printf("%v\n", req.Host) // Output: www.example.com
// path without query string
fmt.Printf("%v\n", req.URL.Path) // Output: /user
// get query string value by key
fmt.Printf("%v\n", req.URL.Query().Get("id")) // Output: 1
// raw query string
fmt.Printf("%v\n", req.URL.RawQuery) // Output: id=1
}
Go play
To add parameters to an url, you would use Values().
That means, an URL without any parameters would have its 'Values' length set to 0:
if len(r.URL.Query()) == 0 {
}
That should be the same as the r.URL.RawQuery suggested by Dewy Broto in the comments:
if r.URL.RawQuery == "" {
}
Or you can check for the presence if the key 'id' in the Values() map.
if r.URL.Query().Get("id") == "" {
//do something here
}

Resources