Golang generics - go

trying to create auth service that is abstract by using generic type. I wanted to create package Auth that will have New() method for initialization but currently facing the issue that I can resolve. Anyone have idea if this is even possible and if this is even go way of solving the issue?
type Register[T any] interface {
SignUp(T) (*AuthEntity, error)
}
type AuthEntity struct {
ID string
UpdatedAt time.Time
CreatedAt time.Time
}
type AuthService[T any] struct {
Register[T]
}
type PasswordStrategy struct{}
type SignUpViaPassword struct {
Email string
Password string
}
func (s PasswordStrategy) SignUp(signUpData SignUpViaPassword) (*AuthEntity, error) {
return &AuthEntity{ID: "email#gmail.com"}, nil
}
func New[T any](register Register[T]) AuthService[T] {
return AuthService[T]{
Register: register,
}
}
type GoogleStrategy struct{}
type SignUpViaGoogle struct {
Token string
}
func (s GoogleStrategy) SignUp(signUpData SignUpViaGoogle) (*AuthEntity, error) {
return &AuthEntity{}, nil
}
func main() {
// This works as expected
authService := AuthService[SignUpViaPassword]{
Register: PasswordStrategy{},
}
entity, err := authService.SignUp(SignUpViaPassword{Password: "Kako ide", Email: "email#gmail.com"})
if err != nil {
log.Println(err)
}
log.Println(entity)
// This also works as expected
googleAuthService := AuthService[SignUpViaGoogle]{
Register: GoogleStrategy{},
}
entity2, err := authService.SignUp(SignUpViaPassword{Password: "Kako ide", Email: "email#gmail.com"})
if err != nil {
log.Println(err)
}
log.Println(entity2)
// this doesn't work and returns error type PasswordStrategy of PasswordStrategy{} does not match Register[T] (cannot infer T)
New(PasswordStrategy{})
New(GoogleStrategy{})
}

It cannot infer what T should be, so you can explicitly specify it:
New[SignUpViaPassword](PasswordStrategy{})

Related

Unmarshaling YAML into different struct based off YAML field

I'm trying to unmarshal the following YAML data into Go structures.
The data is the in the following format:
fetchers:
- type: "aws"
config:
omega: "lul"
- type: "kubernetes"
config:
foo: "bar"
Based of the type field, I want to determine wether to unmarshal the config field into awsConfig or kubernetesConfig struct.
My current code looks like this (using "gopkg.in/yaml.v2"):
type kubernetesConfig struct {
foo string `yaml:"foo"`
}
type awsConfig struct {
omega string `yaml:"omega"`
}
var c struct {
Fetchers []struct {
Type string `yaml:"type"`
Config interface{} `yaml:"config"`
} `yaml:"fetchers"`
}
err := yaml.Unmarshal(data, &c)
if err != nil {
log.Fatal(err)
}
for _, val := range c.Fetchers {
switch val.Type {
case "kubernetes":
conf := val.Config.(kubernetesConfig)
fmt.Println(conf.foo)
case "aws":
conf := val.Config.(awsConfig)
fmt.Println(conf.omega)
default:
log.Fatalf("No matching type, was type %v", val.Type)
}
}
Code in playground: https://go.dev/play/p/klxOoHMCtnG
Currently it gets unmarshalled as map[interface {}]interface {}, which can't be converted to one of the structs above.
Error:
panic: interface conversion: interface {} is map[interface {}]interface {}, not main.awsConfig \
Do I have to implemented the Unmarshaler Interface of the YAML package with a custom UnmarshalYAML function to get this done?
Found the solution by implementing Unmarshaler Interface:
type Fetcher struct {
Type string `yaml:"type"`
Config interface{} `yaml:"config"`
}
// Interface compliance
var _ yaml.Unmarshaler = &Fetcher{}
func (f *Fetcher) UnmarshalYAML(unmarshal func(interface{}) error) error {
var t struct {
Type string `yaml:"type"`
}
err := unmarshal(&t)
if err != nil {
return err
}
f.Type = t.Type
switch t.Type {
case "kubernetes":
var c struct {
Config kubernetesConfig `yaml:"config"`
}
err := unmarshal(&c)
if err != nil {
return err
}
f.Config = c.Config
case "aws":
var c struct {
Config awsConfig `yaml:"config"`
}
err := unmarshal(&c)
if err != nil {
return err
}
f.Config = c.Config
}
return nil
}
This type of task - where you want to delay the unmarshaling - is very similar to how json.RawMessage works with examples like this.
The yaml package does not have a similar mechanism for RawMessage - but this technique can easily be replicated as outlined here:
type RawMessage struct {
unmarshal func(interface{}) error
}
func (msg *RawMessage) UnmarshalYAML(unmarshal func(interface{}) error) error {
msg.unmarshal = unmarshal
return nil
}
// call this method later - when we know what concrete type to use
func (msg *RawMessage) Unmarshal(v interace{}) error {
return msg.unmarshal(v)
}
So to leverage this in your case:
var fs struct {
Configs []struct {
Type string `yaml:"type"`
Config RawMessage `yaml:"config"` // delay unmarshaling
} `yaml:"fetchers"`
}
err = yaml.Unmarshal([]byte(data), &fs)
if err != nil {
return
}
and based on the config "Type" (aws or kubernetes), you can finally unmarshal the RawMessage into the correct concrete type:
aws := awsConfig{} // concrete type
err = c.Config.Unmarshal(&aws)
or:
k8s := kubernetesConfig{} // concrete type
err = c.Config.Unmarshal(&k8s)
Working example here: https://go.dev/play/p/wsykOXNWk3H

Go interface inheritance

I am new to Go and don't understand one thing. Let's take one code which works:
package main
import "fmt"
type User struct {
Name string
Email string
}
type Admin struct {
User
Level string
}
type Notifier interface {
notify()
}
func (u *User) notify() {
fmt.Println("Notified", u.Name)
}
func SendNotification(notify Notifier) {
notify.notify()
}
func main() {
admin := Admin{
User: User{
Name: "john smith",
Email: "john#email.com",
},
Level: "super",
}
SendNotification(&admin)
admin.User.notify()
admin.notify()
}
Here function SendNotification recognises admin struct as Notifier, because admin struct has access to embedded user struct which implements the interface via pointer receiver. Ok.
Why then the code below doesn't work. Why norgateMathError needs to implement the interface and not use implementation from err error (for me it is the same situation):
package main
import (
"fmt"
"log"
)
type norgateMathError struct {
lat string
long string
err error
}
// func (n norgateMathError) Error() string {
// return fmt.Sprintf("a norgate math error occured: %v %v %v", n.lat, n.long, n.err)
// }
func main() {
_, err := sqrt(-10.23)
if err != nil {
log.Println(err)
}
}
func sqrt(f float64) (float64, error) {
if f < 0 {
nme := fmt.Errorf("norgate math redux: square root of negative number: %v", f)
return 0, &norgateMathError{"50.2289 N", "99.4656 W", nme}
}
return 42, nil
}
.\custom_error.go:28:13: cannot use &norgateMathError{...} (type *norgateMathError) as type error in return argument:
*norgateMathError does not implement error (missing Error method)
In the first case User is embedded inside Admin, thus Admin gain access to all methods defined on the User type.
In the second case, norgateMathError has a field err of type Error, thus do not automatically gain access to it's methods.
If you want norgateMathError to have an Error() method you have to define it manually
func (n norgateMathError) Error() string {
return n.err.Error()
}
There is a different between embedding a field and just having a field. More information can be found in the reference

how can i mock specific embedded method inside interface

I have this code and I wanna write a unit tests for update function.
how can i mock FindByUsername function ?
I try to overwrite u.FindByUsername but it's doesn't work.
also, I can write some function to give u *UserLogic and userName string as input parameters and execute u.FindByUsername() and mock this function but it's not a clean solution I need a better solution for mocking methods inside UserOperation interface.
package logic
import (
"errors"
"fmt"
)
var (
dataStore = map[string]*User{
"optic": &User{
Username: "bla",
Password: "ola",
},
}
)
//UserOperation interface
type UserOperation interface {
Update(info *User) error
FindByUsername(userName string) (*User, error)
}
//User struct
type User struct {
Username string
Password string
}
//UserLogic struct
type UserLogic struct {
UserOperation
}
//NewUser struct
func NewUser() UserOperation {
return &UserLogic{}
}
//Update method
func (u *UserLogic) Update(info *User) error {
userInfo, err := u.FindByUsername(info.Username)
if err != nil {
return err
}
fmt.Println(userInfo.Username, userInfo.Password)
fmt.Println("do some update logic !!!")
return nil
}
//FindByUsername method
func (u *UserLogic) FindByUsername(userName string) (*User, error) {
userInfo := &User{}
var exist bool
if userInfo, exist = dataStore[userName]; !exist {
return nil, errors.New("user not found")
}
return userInfo, nil
}
Update
I try to mock function with this code
func TestUpdate2(t *testing.T) {
var MockFunc = func(userName string) (*User, error) {
return &User{Username:"foo", Password:"bar"},nil
}
user := NewUser()
user.FindByUsername = MockFunc
user.Update(&User{Username:"optic", Password:"ola"})
}
You're mixing two levels of abstraction in your UserOperation interface: Update depends on FindByUsername. To make Update testable you need to inject the UserFinder functionality into your Update method. You can do this e.g. by defining a field in the UserLogic struct:
type UserOperation interface {
Update(info *User) error
}
type UserFinder func(userName string) (*User, error)
type UserLogic struct {
UserOperation
FindByUsername UserFinder
}
//NewUser struct
func NewUser() *UserLogic { // return structs, accept interfaces!
return &UserLogic{
findByUsername: FindByUsername
}
}
func (u *UserLogic) Update(info *User) error {
userInfo, err := u.findByUsername(info.Username)
if err != nil {
return err
}
fmt.Println(userInfo.Username, userInfo.Password)
fmt.Println("do some update logic !!!")
return nil
}
func FindByUsername(userName string) (*User, error) {
userInfo := &User{}
var exist bool
if userInfo, exist = dataStore[userName]; !exist {
return nil, errors.New("user not found")
}
return userInfo, nil
}

How correctly use UUID4 in Golang application?

I am new in Golang and need some help! I have several questions.
In PostgreSQL database I have table called surveys.
CREATE TABLE SURVEYS(
SURVEY_ID UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
SURVEY_NAME VARCHAR NOT NULL,
SURVEY_DESCRIPTION TEXT,
START_PERIOD TIMESTAMP,
END_PERIOD TIMESTAMP
);
As you can see SURVEY_ID column is PRIMARY KEY and it's type is UUID4.
In Golang application I create such struct to this table:
type Survey struct {
ID string `json:"survey_id"`
Name string `json:"survey_name"`
Description utils.NullString `json:"survey_description"`
StartPeriod utils.NullTime `json:"start_period"`
EndPeriod utils.NullTime `json:"end_period"`
}
As you can see type of ID field is string. Is it correct? I am not sure that it's best practice.
My second question about strange result which I have when make GET request to specific survey by it's ID.
For example when I make such request:
http://localhost:8000/api/survey/0cf1cf18-d5fd-474e-a8be-754fbdc89720
As response I have this:
{
"survey_id": "0cf1cf18-d5fd-474e-a8be-754fbdc89720",
"survey_name": "NAME",
"survey_description": {
"String": "DESCRIPTION",
"Valid": true
},
"start_period": {
"Time": "2019-01-01T00:00:00Z",
"Valid": false
},
"end_period": {
"Time": "0001-01-01T00:00:00Z",
"Valid": false
}
}
As you can see something wrong with last 3 field: survey_description, start_period and end_period. I want to see key and value in one line. For example as here:
{
"survey_id": "0cf1cf18-d5fd-474e-a8be-754fbdc89720",
"survey_name": "NAME",
"survey_description": "DESCRIPTION",
"start_period": "2019-01-01 00:00:00",
"end_period": null
}
Where exactly I make mistake in my code?
utils.go:
package utils
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/lib/pq"
"time"
)
// NullTime is an alias for pq.NullTime data type.
type NullTime struct {
pq.NullTime
}
// MarshalJSON for NullTime.
func (nt *NullTime) MarshalJSON() ([]byte, error) {
if !nt.Valid {
return []byte("null"), nil
}
val := fmt.Sprintf("\"%s\"", nt.Time.Format(time.RFC3339))
return []byte(val), nil
}
// UnmarshalJSON for NullTime.
func (nt *NullTime) UnmarshalJSON(b []byte) error {
err := json.Unmarshal(b, &nt.Time)
nt.Valid = err == nil
return err
}
// NullInt64 is an alias for sql.NullInt64 data type.
type NullInt64 struct {
sql.NullInt64
}
// MarshalJSON for NullInt64.
func (ni *NullInt64) MarshalJSON() ([]byte, error) {
if !ni.Valid {
return []byte("null"), nil
}
return json.Marshal(ni.Int64)
}
// UnmarshalJSON for NullInt64.
func (ni *NullInt64) UnmarshalJSON(b []byte) error {
err := json.Unmarshal(b, &ni.Int64)
ni.Valid = err == nil
return err
}
// NullString is an alias for sql.NullString data type.
type NullString struct {
sql.NullString
}
// MarshalJSON for NullString.
func (ns *NullString) MarshalJSON() ([]byte, error) {
if !ns.Valid {
return []byte("null"), nil
}
return json.Marshal(ns.String)
}
// UnmarshalJSON for NullString.
func (ns *NullString) UnmarshalJSON(b []byte) error {
err := json.Unmarshal(b, &ns.String)
ns.Valid = err == nil
return err
}
routes.go:
router.HandleFunc("/api/survey/{survey_id:[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}}", controllers.GetSurvey).Methods("GET")
controllers/survey.go:
var GetSurvey = func(responseWriter http.ResponseWriter, request *http.Request) {
// Initialize variables.
survey := models.Survey{}
var err error
vars := mux.Vars(request)
// Execute SQL statement.
err = database.DB.QueryRow("SELECT * FROM surveys WHERE survey_id = $1;", vars["survey_id"]).Scan(&survey.ID, &survey.Name, &survey.Description, &survey.StartPeriod, &survey.EndPeriod)
// Shape the response depending on the result of the previous command.
if err != nil {
log.Println(err)
switch err {
case sql.ErrNoRows:
utils.ResponseWithError(responseWriter, http.StatusNotFound, "The entry not found.")
default:
utils.ResponseWithError(responseWriter, http.StatusInternalServerError, err.Error())
}
return
}
utils.Response(responseWriter, http.StatusOK, survey)
}
Well, finally I found the result.
I changed the struct for the table:
type Survey struct {
ID string `json:"survey_id"`
Name string `json:"survey_name"`
Description *string `json:"survey_description", sql:"index"`
StartPeriod *time.Time `json:"start_period", sql:"index"`
EndPeriod *time.Time `json:"end_period", sql:"index"`
}
I don't see any issue with using a string for a UUID.
As for the MarshalJSON not working, I think I know what's going on. Your null types don't implement MarshalJSON, only the pointers to them. The fix would be to either change the function to use a non-pointer receiver, or make the fields pointers in your struct.
func (ns *NullString) MarshalJSON() ([]byte, error)
If you did make them pointers, then you could just keep them like that, since they're nullable.

golang - Save enum type to SQL database "panic: sql: converting Exec argument #1's type: non-Value type int returned from Value"

In my current go project (~5K LOC), I am using sqlite3 as my underlying database layer, and I am using gorm as my ORM engine. One of the models is a Platform with a field of PlatformType enum type. Here's a code snippet to demonstrate my problem.
package main
import (
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/jinzhu/gorm"
"database/sql/driver"
"fmt"
)
/****************************\
Object Layer
\****************************/
// Platform ID
type PlatformID string
func (u *PlatformID) Scan(value interface{}) error { *u = PlatformID(value.([]byte)); return nil }
func (u PlatformID) Value() (driver.Value, error) { return string(u), nil }
// Platform Type enumeration
type PlatformType int
const (
PLATFORM_TYPE_NOT_A_VALUE PlatformType = iota
PLATFORM_TYPE_TYPE1
PLATFORM_TYPE_TYPE2
)
var types = [...]string {
"Not a type",
"Type1",
"Type2",
}
func (platform_type PlatformType) String() string {
return types[platform_type]
}
func (u *PlatformType) Scan(value interface{}) error { *u = PlatformType(value.(int)); return nil }
func (u PlatformType) Value() (driver.Value, error) { return int(u), nil }
// Platform descriptor.
type Platform struct {
ID PlatformID `json:"ID" gorm:"type:varchar(100);unique;not null"` // Assigned by LCBO.
Type PlatformType `json:"Type" gorm:"type:integer"`
}
type PlatformStore interface {
Init() error
Save(platform *Platform) error
}
/****************************\
Persist Layer
\****************************/
func NewSqlite3Store(dbname string) *gorm.DB {
db, err := gorm.Open("sqlite3", dbname)
if err != nil {
panic("failed to connect database")
}
return db
}
type DBPlatformStore struct {
db *gorm.DB
}
func NewDBPlatformStore(db *gorm.DB) PlatformStore {
return &DBPlatformStore{
db: db,
}
}
func (store *DBPlatformStore) Init() error {
err := store.db.AutoMigrate(&Platform{}).Error
if err != nil {
panic(err)
}
return err
}
func (store *DBPlatformStore) Save(platform *Platform) error {
err := store.db.Create(platform).Error
if err != nil {
panic(err)
}
return err
}
/****************************\
Application Layer
\****************************/
func main() {
db := NewSqlite3Store("enum_test.db")
platformStore := NewDBPlatformStore(db)
fmt.Println("Initialize Database")
err := platformStore.Init()
if err != nil {
panic(err)
}
platform := new(Platform)
platform.ID = "12345"
platform.Type = PLATFORM_TYPE_TYPE1
platformStore.Save(platform)
}
After running the code above, I got a runtime error "sql: converting Exec argument #1's type: non-Value type int returned from Value"
]# go run enumtest.go
Initialize Database
panic: sql: converting Exec argument #1's type: non-Value type int returned from Value
goroutine 1 [running]:
panic(0x66d380, 0xc8203ae350)
/*/panic.go:481 +0x3e6
main.(*DBPlatformStore).Save(0xc820020b20, 0xc820304500, 0x0, 0x0)
/*/enumtest.go:84 +0x9f
main.main()
/*/enumtest.go:106 +0x247
exit status 2
And I checked my database, the platforms table has been created successfully.
]# sqlite3 enum_test.db
sqlite> .schema platforms
CREATE TABLE "platforms" ("id" varchar(100) NOT NULL UNIQUE,"type" integer , PRIMARY KEY ("id"));
The (not-so) trivial question is how do I modify my code so that I can correctly save the entry to database.
My bigger question is: How to save a customized GO enum type to a sql database?(with a ORM engine hopefully)
According to current database/sql docs, the sql has four builtin functions that returns driver.Value, and the underlying types are int64, float64, string and bool. So I guess that's the only four types supported.
I just changed the underlying type of my enum from int to int64 and things are working.
The problematic section is updated to the following snippet:
// Platform Type enumeration
type PlatformType int64
const (
PLATFORM_TYPE_NOT_A_VALUE PlatformType = iota
PLATFORM_TYPE_TYPE1
PLATFORM_TYPE_TYPE2
)
var types = [...]string {
"Not a type",
"Type1",
"Type2",
}
func (platform_type PlatformType) String() string {
return types[platform_type]
}
func (u *PlatformType) Scan(value interface{}) error { *u = PlatformType(value.(int64)); return nil }
func (u PlatformType) Value() (driver.Value, error) { return int64(u), nil }

Resources