├── .gitignore ├── README.md ├── code_structure ├── mvc │ ├── app │ │ ├── application.go │ │ ├── url_mappings.go │ │ └── url_mappings_test.go │ ├── controllers │ │ └── users_controller.go │ ├── domain │ │ ├── datasource.go │ │ ├── user_dao.go │ │ └── user_dto.go │ ├── main.go │ └── services │ │ └── users_service.go └── orc │ └── main.go ├── gin_microservice ├── app │ ├── application.go │ └── url_mappings.go ├── controllers │ └── users_controller.go ├── domain │ ├── httperrors │ │ └── httperror.go │ └── users │ │ └── user.go ├── main.go └── services │ └── users_service.go ├── go.mod ├── go.sum ├── http_calls ├── example.go └── example_test.go ├── pointers └── main.go └── testeable_code ├── controllers ├── ping_controller.go └── ping_controller_test.go ├── main.go └── services └── ping_service.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang-examples 2 | Different examples on how to use the Go programming language 3 | -------------------------------------------------------------------------------- /code_structure/mvc/app/application.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | var ( 8 | router = gin.Default() 9 | ) 10 | 11 | func StartApplication() { 12 | mapUrls() 13 | 14 | router.Run(":8080") 15 | } 16 | -------------------------------------------------------------------------------- /code_structure/mvc/app/url_mappings.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/federicoleon/golang-examples/code_structure/mvc/controllers" 5 | ) 6 | 7 | func mapUrls() { 8 | router.GET("/users/:id", controllers.UsersController.Get) 9 | router.POST("/users", controllers.UsersController.Save) 10 | } 11 | -------------------------------------------------------------------------------- /code_structure/mvc/app/url_mappings_test.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestMappings(t *testing.T) { 10 | 11 | assert.EqualValues(t, 0, len(router.Routes())) 12 | 13 | mapUrls() 14 | 15 | routes := router.Routes() 16 | 17 | assert.EqualValues(t, 2, len(routes)) 18 | 19 | assert.EqualValues(t, http.MethodGet, routes[0].Method) 20 | assert.EqualValues(t, "/users/:id", routes[0].Path) 21 | 22 | assert.EqualValues(t, http.MethodPost, routes[1].Method) 23 | assert.EqualValues(t, "/users", routes[1].Path) 24 | } 25 | -------------------------------------------------------------------------------- /code_structure/mvc/controllers/users_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/federicoleon/golang-examples/code_structure/mvc/domain" 5 | "github.com/federicoleon/golang-examples/code_structure/mvc/services" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "strconv" 9 | ) 10 | 11 | var ( 12 | UsersController = usersController{} 13 | ) 14 | 15 | type usersController struct{} 16 | 17 | func (controller usersController) Get(c *gin.Context) { 18 | id, err := strconv.ParseInt(c.Param("id"), 10, 64) 19 | if err != nil { 20 | c.JSON(http.StatusBadRequest, map[string]string{"message": "invalid user id"}) 21 | return 22 | } 23 | 24 | user, err := services.UsersService.Get(id) 25 | if err != nil { 26 | c.JSON(http.StatusNotFound, map[string]string{"message": err.Error()}) 27 | return 28 | } 29 | c.JSON(http.StatusOK, user) 30 | } 31 | 32 | func (controller usersController) Save(c *gin.Context) { 33 | var user domain.User 34 | if err := c.ShouldBindJSON(&user); err != nil { 35 | c.JSON(http.StatusBadRequest, map[string]string{"message": "invalid json body"}) 36 | return 37 | } 38 | 39 | if err := services.UsersService.Save(&user); err != nil { 40 | c.JSON(http.StatusInternalServerError, map[string]string{"message": err.Error()}) 41 | return 42 | } 43 | c.JSON(http.StatusCreated, user) 44 | } 45 | -------------------------------------------------------------------------------- /code_structure/mvc/domain/datasource.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | _ "github.com/go-sql-driver/mysql" 7 | ) 8 | 9 | var ( 10 | dbClient *sql.DB 11 | ) 12 | 13 | func init() { 14 | var err error 15 | dbClient, err = sql.Open("mysql", fmt.Sprintf("%s:%s@(%s)/%s", 16 | "root", "root", "127.0.0.1:3306", "users_db")) 17 | if err != nil { 18 | panic(err) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /code_structure/mvc/domain/user_dao.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | func (user *User) Save() error { 8 | if user == nil { 9 | return errors.New("invalid user to save") 10 | } 11 | stmt, err := dbClient.Prepare("INSERT INTO users(email) VALUES(?);") 12 | if err != nil { 13 | return err 14 | } 15 | defer stmt.Close() 16 | 17 | result, err := stmt.Exec(user.Email) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | userId, err := result.LastInsertId() 23 | if err != nil { 24 | return err 25 | } 26 | user.Id = userId 27 | return nil 28 | } 29 | 30 | func (user *User) Get() error { 31 | stmt, err := dbClient.Prepare("SELECT id, email FROM users WHERE id=?;") 32 | if err != nil { 33 | return err 34 | } 35 | defer stmt.Close() 36 | 37 | rows, err := stmt.Query(user.Id) 38 | if err != nil { 39 | return err 40 | } 41 | defer rows.Close() 42 | 43 | for rows.Next() { 44 | if err := rows.Scan(&user.Id, &user.Email); err != nil { 45 | return err 46 | } 47 | return nil 48 | } 49 | return errors.New("user not found") 50 | } 51 | -------------------------------------------------------------------------------- /code_structure/mvc/domain/user_dto.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | type User struct { 4 | Id int64 `json:"id"` 5 | Email string `json:"email"` 6 | } -------------------------------------------------------------------------------- /code_structure/mvc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/federicoleon/golang-examples/code_structure/mvc/app" 5 | ) 6 | 7 | func main() { 8 | app.StartApplication() 9 | } 10 | -------------------------------------------------------------------------------- /code_structure/mvc/services/users_service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/federicoleon/golang-examples/code_structure/mvc/domain" 5 | ) 6 | 7 | var ( 8 | UsersService = usersService{} 9 | ) 10 | 11 | type usersService struct{} 12 | 13 | func (service usersService) Get(id int64) (*domain.User, error) { 14 | user := domain.User{Id: id} 15 | if err := user.Get(); err != nil { 16 | return nil, err 17 | } 18 | return &user, nil 19 | } 20 | 21 | func (service usersService) Save(user *domain.User) error { 22 | if err := user.Save(); err != nil { 23 | return err 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /code_structure/orc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | _ "github.com/go-sql-driver/mysql" 9 | "net/http" 10 | "strconv" 11 | ) 12 | 13 | var ( 14 | dbClient *sql.DB 15 | ) 16 | 17 | func init() { 18 | var err error 19 | dbClient, err = sql.Open("mysql", fmt.Sprintf("%s:%s@(%s)/%s", 20 | "root", "root", "127.0.0.1:3306", "users_db")) 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | 26 | type User struct { 27 | Id int64 `json:"id"` 28 | Email string `json:"email"` 29 | } 30 | 31 | func main() { 32 | router := gin.Default() 33 | 34 | router.GET("/users/:id", handlerGetUser) 35 | router.POST("/users", handlerCreateUser) 36 | 37 | router.Run(":8080") 38 | } 39 | 40 | func handlerGetUser(c *gin.Context) { 41 | id, err := strconv.ParseInt(c.Param("id"), 10, 64) 42 | if err != nil { 43 | c.JSON(http.StatusBadRequest, map[string]string{"message": "invalid user id"}) 44 | return 45 | } 46 | 47 | user, err := GetUser(id) 48 | if err != nil { 49 | c.JSON(http.StatusNotFound, map[string]string{"message": err.Error()}) 50 | return 51 | } 52 | 53 | c.JSON(http.StatusOK, user) 54 | } 55 | 56 | func handlerCreateUser(c *gin.Context) { 57 | var user User 58 | if err := c.ShouldBindJSON(&user); err != nil { 59 | c.JSON(http.StatusBadRequest, map[string]string{"message": "invalid json body"}) 60 | return 61 | } 62 | 63 | if err := SaveUser(&user); err != nil { 64 | c.JSON(http.StatusInternalServerError, map[string]string{"message": err.Error()}) 65 | return 66 | } 67 | 68 | c.JSON(http.StatusCreated, user) 69 | } 70 | 71 | func SaveUser(user *User) error { 72 | if user == nil { 73 | return errors.New("invalid user to save") 74 | } 75 | stmt, err := dbClient.Prepare("INSERT INTO users(email) VALUES(?);") 76 | if err != nil { 77 | return err 78 | } 79 | defer stmt.Close() 80 | 81 | result, err := stmt.Exec(user.Email) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | userId, err := result.LastInsertId() 87 | if err != nil { 88 | return err 89 | } 90 | user.Id = userId 91 | return nil 92 | } 93 | 94 | func GetUser(id int64) (*User, error) { 95 | stmt, err := dbClient.Prepare("SELECT id, email FROM users WHERE id=?;") 96 | if err != nil { 97 | return nil, err 98 | } 99 | defer stmt.Close() 100 | 101 | rows, err := stmt.Query(id) 102 | if err != nil { 103 | return nil, err 104 | } 105 | defer rows.Close() 106 | 107 | for rows.Next() { 108 | var user User 109 | if err := rows.Scan(&user.Id, &user.Email); err != nil { 110 | return nil, err 111 | } 112 | return &user, nil 113 | } 114 | return nil, errors.New("user not found") 115 | } 116 | -------------------------------------------------------------------------------- /gin_microservice/app/application.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | var ( 8 | router = gin.Default() 9 | ) 10 | 11 | func StartApplication() { 12 | mapUrls() 13 | 14 | router.Run(":8080") 15 | } 16 | -------------------------------------------------------------------------------- /gin_microservice/app/url_mappings.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/federicoleon/golang-examples/gin_microservice/controllers" 5 | ) 6 | 7 | func mapUrls() { 8 | router.POST("/users", controllers.UsersController.Create) 9 | router.GET("/users/:id", controllers.UsersController.Get) 10 | } 11 | -------------------------------------------------------------------------------- /gin_microservice/controllers/users_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "strconv" 6 | "github.com/federicoleon/golang-examples/gin_microservice/domain/httperrors" 7 | "net/http" 8 | "github.com/federicoleon/golang-examples/gin_microservice/domain/users" 9 | "github.com/federicoleon/golang-examples/gin_microservice/services" 10 | ) 11 | 12 | var ( 13 | UsersController = usersController{} 14 | ) 15 | 16 | type usersController struct{} 17 | 18 | func respond(c *gin.Context, isXml bool, httpCode int, body interface{}) { 19 | if isXml { 20 | c.XML(httpCode, body) 21 | return 22 | } 23 | c.JSON(httpCode, body) 24 | } 25 | 26 | func (controller usersController) Create(c *gin.Context) { 27 | var user users.User 28 | if err := c.ShouldBindJSON(&user); err != nil { 29 | httpErr := httperrors.NewBadRequestError("invalid json body") 30 | c.JSON(httpErr.Code, httpErr) 31 | return 32 | } 33 | createdUser, err := services.UsersService.Create(user) 34 | if err != nil { 35 | c.JSON(err.Code, err) 36 | return 37 | } 38 | // return created user 39 | c.JSON(http.StatusCreated, createdUser) 40 | } 41 | 42 | func (controller usersController) Get(c *gin.Context) { 43 | isXml := c.GetHeader("Accept") == "application/xml" 44 | 45 | userId, err := strconv.ParseInt(c.Param("id"), 10, 64) 46 | if err != nil { 47 | httpErr := httperrors.NewBadRequestError("invalid user id") 48 | respond(c, isXml, httpErr.Code, httpErr) 49 | return 50 | } 51 | 52 | user, getErr := services.UsersService.Get(userId) 53 | if getErr != nil { 54 | respond(c, isXml, getErr.Code, getErr) 55 | return 56 | } 57 | respond(c, isXml, http.StatusOK, user) 58 | } 59 | -------------------------------------------------------------------------------- /gin_microservice/domain/httperrors/httperror.go: -------------------------------------------------------------------------------- 1 | package httperrors 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type HttpError struct { 8 | Message string `json:"message"` 9 | Code int `json:"code"` 10 | Error string `json:"error"` 11 | } 12 | 13 | func NewBadRequestError(message string) *HttpError { 14 | return &HttpError{ 15 | Message: message, 16 | Code: http.StatusBadRequest, 17 | Error: "bad_request", 18 | } 19 | } 20 | 21 | func NewNotFoundError(message string) *HttpError { 22 | return &HttpError{ 23 | Message: message, 24 | Code: http.StatusNotFound, 25 | Error: "not_found", 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gin_microservice/domain/users/user.go: -------------------------------------------------------------------------------- 1 | package users 2 | 3 | import ( 4 | "github.com/federicoleon/golang-examples/gin_microservice/domain/httperrors" 5 | ) 6 | 7 | type User struct { 8 | Id int64 `json:"id"` 9 | FirstName string `json:"first_name"` 10 | LastName string `json:"last_name"` 11 | Email string `json:"email"` 12 | } 13 | 14 | func (user User) Validate() *httperrors.HttpError { 15 | if user.FirstName == "" { 16 | return httperrors.NewBadRequestError("invalid first name") 17 | } 18 | if user.LastName == "" { 19 | return httperrors.NewBadRequestError("invalid last name") 20 | } 21 | if user.Email == "" { 22 | return httperrors.NewBadRequestError("invalid email address") 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /gin_microservice/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/federicoleon/golang-examples/gin_microservice/app" 5 | ) 6 | 7 | func main() { 8 | app.StartApplication() 9 | } 10 | -------------------------------------------------------------------------------- /gin_microservice/services/users_service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/federicoleon/golang-examples/gin_microservice/domain/users" 5 | "github.com/federicoleon/golang-examples/gin_microservice/domain/httperrors" 6 | "fmt" 7 | ) 8 | 9 | var ( 10 | UsersService = usersService{} 11 | 12 | registeredUsers = map[int64]*users.User{} 13 | currentUserId int64 = 1 14 | ) 15 | 16 | type usersService struct{} 17 | 18 | func (service usersService) Create(user users.User) (*users.User, *httperrors.HttpError) { 19 | if err := user.Validate(); err != nil { 20 | return nil, err 21 | } 22 | 23 | user.Id = currentUserId 24 | currentUserId ++ 25 | 26 | registeredUsers[user.Id] = &user 27 | 28 | return &user, nil 29 | } 30 | 31 | func (service usersService) Get(userId int64) (*users.User, *httperrors.HttpError) { 32 | if user := registeredUsers[userId]; user != nil { 33 | return user, nil 34 | } 35 | return nil, httperrors.NewNotFoundError(fmt.Sprintf("user %d not found", userId)) 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/federicoleon/golang-examples 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/federicoleon/go-httpclient v0.1.0 7 | github.com/gin-gonic/gin v1.6.3 8 | github.com/go-sql-driver/mysql v1.5.0 9 | github.com/stretchr/testify v1.6.1 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/federicoleon/go-httpclient v0.1.0 h1:OFHRF9PXaI90rwVIBXR9wBHcAwQM1SYpSK/zfDPoBEE= 5 | github.com/federicoleon/go-httpclient v0.1.0/go.mod h1:8ZZxae4FhilK75s4ESjx6AjsPm43xBd1wj5BEQDDGkg= 6 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 7 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 8 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= 9 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= 10 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 11 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 12 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 13 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 14 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 15 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 16 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= 17 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= 18 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 19 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 20 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 21 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 22 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 23 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 24 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 25 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 26 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 27 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 28 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 29 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 30 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 31 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 32 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 33 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 36 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 37 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 38 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 39 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 40 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= 41 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 42 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 43 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 44 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 45 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 47 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 49 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 50 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 51 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 52 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 54 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 55 | -------------------------------------------------------------------------------- /http_calls/example.go: -------------------------------------------------------------------------------- 1 | package http_calls 2 | 3 | import ( 4 | "errors" 5 | "github.com/federicoleon/go-httpclient/gohttp" 6 | ) 7 | 8 | var ( 9 | httpClient = gohttp.NewBuilder().Build() 10 | ) 11 | 12 | type Endpoints struct { 13 | EventsUrl string `json:"events_url"` 14 | } 15 | 16 | func GetEndpoints() (*Endpoints, error) { 17 | response, err := httpClient.Get("https://api.github.com", nil) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | if response.StatusCode() > 299 { 23 | return nil, errors.New("error when trying to fetch github endpoints") 24 | } 25 | 26 | var endpoints Endpoints 27 | if err := response.UnmarshalJson(&endpoints); err != nil { 28 | return nil, err 29 | } 30 | return &endpoints, nil 31 | } 32 | -------------------------------------------------------------------------------- /http_calls/example_test.go: -------------------------------------------------------------------------------- 1 | package http_calls 2 | 3 | import ( 4 | "errors" 5 | "github.com/federicoleon/go-httpclient/gohttp" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestMain(m *testing.M) { 13 | gohttp.StartMockServer() 14 | 15 | os.Exit(m.Run()) 16 | } 17 | 18 | func TestGetEndpointsErrorGettingFromApi(t *testing.T) { 19 | // Initialization: 20 | gohttp.FlushMocks() 21 | gohttp.AddMock(gohttp.Mock{ 22 | Method: http.MethodGet, 23 | Url: "https://api.github.com", 24 | Error: errors.New("timeout getting the endpoints"), 25 | }) 26 | 27 | // Execution: 28 | endpoints, err := GetEndpoints() 29 | 30 | // Validation: 31 | 32 | assert.Nil(t, endpoints) 33 | assert.NotNil(t, err) 34 | assert.EqualValues(t, "timeout getting the endpoints", err.Error()) 35 | } 36 | 37 | func TestGetEndpointsNotFound(t *testing.T) { 38 | // Initialization: 39 | gohttp.FlushMocks() 40 | gohttp.AddMock(gohttp.Mock{ 41 | Method: http.MethodGet, 42 | Url: "https://api.github.com", 43 | ResponseStatusCode: http.StatusNotFound, 44 | ResponseBody: `{"message": "endpoint not found"}`, 45 | }) 46 | 47 | // Execution: 48 | endpoints, err := GetEndpoints() 49 | 50 | // Validation: 51 | assert.Nil(t, endpoints) 52 | assert.NotNil(t, err) 53 | assert.EqualValues(t, "error when trying to fetch github endpoints", err.Error()) 54 | } 55 | 56 | func TestGetEndpointsInvalidJsonResponse(t *testing.T) { 57 | // Initialization: 58 | gohttp.FlushMocks() 59 | gohttp.AddMock(gohttp.Mock{ 60 | Method: http.MethodGet, 61 | Url: "https://api.github.com", 62 | ResponseStatusCode: http.StatusOK, 63 | ResponseBody: `{"events_url": `, 64 | }) 65 | 66 | // Execution: 67 | endpoints, err := GetEndpoints() 68 | 69 | // Validation: 70 | 71 | assert.Nil(t, endpoints) 72 | assert.NotNil(t, err) 73 | assert.EqualValues(t, "unexpected end of JSON input", err.Error()) 74 | } 75 | 76 | func TestGetEndpointsNoError(t *testing.T) { 77 | // Initialization: 78 | gohttp.FlushMocks() 79 | gohttp.AddMock(gohttp.Mock{ 80 | Method: http.MethodGet, 81 | Url: "https://api.github.com", 82 | ResponseStatusCode: http.StatusOK, 83 | ResponseBody: `{"events_url": "https://api.github.com/events"}`, 84 | }) 85 | 86 | // Execution: 87 | endpoints, err := GetEndpoints() 88 | 89 | // Validation: 90 | 91 | assert.Nil(t, err) 92 | assert.NotNil(t, endpoints) 93 | assert.EqualValues(t, "https://api.github.com/events", endpoints.EventsUrl) 94 | } 95 | -------------------------------------------------------------------------------- /pointers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "errors" 6 | "encoding/json" 7 | ) 8 | 9 | type User struct { 10 | Status string `json:"status"` 11 | } 12 | 13 | // Change u between pointer or plain struct to see 14 | // the difference when executing this code. 15 | func (u *User) updateStatus(status string) { 16 | u.Status = status 17 | } 18 | 19 | // Every time you receive a pointer parameter then 20 | // you MUST validate the pointer is not nil. 21 | func updateUser(user *User, status string) { 22 | if user == nil { 23 | return 24 | } 25 | user.Status = status 26 | } 27 | 28 | // Functions or methods that don't modify internal 29 | // fields could be defined without a pointer since we 30 | // only need to obtain a value but not modify anything. 31 | func (u User) GetUpdatedStatus() string { 32 | return u.Status 33 | } 34 | 35 | // Errors should be returned at last. When an error is 36 | // returned, you should return nil for all of the other 37 | // variables since no one is going to use those. 38 | // You can not return nil if the return type is not a pointer. 39 | func getUser(userId int64) (*User, error) { 40 | if userId <= 0 { 41 | return nil, errors.New("user not found") 42 | } 43 | // Fetch user and return 44 | user := User{} 45 | return &user, nil 46 | } 47 | 48 | func displayStringPointers(name string) { 49 | // Gets a pointer to name by using the 'reference' operator: 50 | pointer := &name 51 | 52 | fmt.Println(pointer) 53 | 54 | // Get the value this pointer is pointing to by using the 'dereference' operator: 55 | actualValue := *pointer 56 | 57 | fmt.Println(actualValue) 58 | } 59 | 60 | func main() { 61 | // Show how reference & dereference operators work: 62 | displayStringPointers("Alex") 63 | 64 | // Show the difference between using a pointer or a copy 65 | // when modifying internal fields of an struct: 66 | 67 | user := User{Status: "active"} 68 | 69 | fmt.Println(user.Status) 70 | 71 | user.updateStatus("inactive") 72 | 73 | updateUser(nil, "inactive") 74 | 75 | fmt.Println(user.Status) 76 | 77 | currentUser, userErr := getUser(1) 78 | if userErr != nil { 79 | // Handling error 80 | return 81 | } 82 | fmt.Println(currentUser.Status) 83 | 84 | // Maps & channels are pointer by default. 85 | // No need to pass pointers in these cases. 86 | allUsers := make(map[int64]User) 87 | 88 | fmt.Println(allUsers) 89 | 90 | processUsers(allUsers) 91 | 92 | fmt.Println(allUsers) 93 | 94 | // See the difference between passing pointer to user or just a copy of the user. 95 | if err := jsonUnmarshal(`{"status": "hardcoded"}`, user); err != nil { 96 | fmt.Println(err.Error()) 97 | } 98 | } 99 | 100 | // Since json.Unmarshal will attempt to update internal fields 101 | // in target based on input json, then target needs to be a pointer. 102 | func jsonUnmarshal(jsonString string, target interface{}) error { 103 | return json.Unmarshal([]byte(jsonString), target) 104 | } 105 | 106 | func processUsers(allUsers map[int64]User) { // map is an alias for *runtime.hmap 107 | // Since maps are pointers by default, the first thing 108 | // we should do is validate if the map is nil. 109 | if allUsers == nil { 110 | return 111 | } 112 | 113 | allUsers[1] = User{Status: "active"} 114 | } 115 | -------------------------------------------------------------------------------- /testeable_code/controllers/ping_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "github.com/federicoleon/golang-examples/testeable_code/services" 7 | ) 8 | 9 | var ( 10 | PingController = pingController{} 11 | ) 12 | 13 | type pingController struct{} 14 | 15 | func (controller pingController) Ping(c *gin.Context) { 16 | result, err := services.PingService.HandlePing() 17 | if err != nil { 18 | c.String(http.StatusInternalServerError, err.Error()) 19 | return 20 | } 21 | c.String(http.StatusOK, result) 22 | } 23 | -------------------------------------------------------------------------------- /testeable_code/controllers/ping_controller_test.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "testing" 5 | "github.com/gin-gonic/gin" 6 | "net/http/httptest" 7 | "net/http" 8 | "github.com/federicoleon/golang-examples/testeable_code/services" 9 | "errors" 10 | ) 11 | 12 | type pingServiceMock struct { 13 | handlePingFn func() (string, error) 14 | } 15 | 16 | func (mock pingServiceMock) HandlePing() (string, error) { 17 | return mock.handlePingFn() 18 | } 19 | 20 | func TestPingWithError(t *testing.T) { 21 | serviceMock := pingServiceMock{} 22 | serviceMock.handlePingFn = func() (string, error) { 23 | return "", errors.New("error executing ping") 24 | } 25 | services.PingService = serviceMock 26 | 27 | response := httptest.NewRecorder() 28 | context, _ := gin.CreateTestContext(response) 29 | 30 | PingController.Ping(context) 31 | 32 | if response.Code != http.StatusInternalServerError { 33 | t.Error("response code should be 500") 34 | } 35 | 36 | if response.Body.String() != "error executing ping" { 37 | t.Error("response body should say 'error'") 38 | } 39 | } 40 | 41 | func TestPingNoError(t *testing.T) { 42 | serviceMock := pingServiceMock{} 43 | serviceMock.handlePingFn = func() (string, error) { 44 | return "pong", nil 45 | } 46 | services.PingService = serviceMock 47 | 48 | response := httptest.NewRecorder() 49 | context, _ := gin.CreateTestContext(response) 50 | 51 | PingController.Ping(context) 52 | 53 | if response.Code != http.StatusOK { 54 | t.Error("response code should be 200") 55 | } 56 | 57 | if response.Body.String() != "pong" { 58 | t.Error("response body should say 'pong'") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /testeable_code/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/federicoleon/golang-examples/testeable_code/controllers" 6 | ) 7 | 8 | var ( 9 | router = gin.Default() 10 | ) 11 | 12 | func main() { 13 | router.GET("/ping", controllers.PingController.Ping) 14 | 15 | router.Run(":8080") 16 | } 17 | -------------------------------------------------------------------------------- /testeable_code/services/ping_service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | pong = "pong" 9 | ) 10 | 11 | var ( 12 | PingService pingService = pingServiceImpl{} 13 | ) 14 | 15 | type pingService interface { 16 | HandlePing() (string, error) 17 | } 18 | 19 | type pingServiceImpl struct{} 20 | 21 | func (service pingServiceImpl) HandlePing() (string, error) { 22 | fmt.Println("doing some complex things...") 23 | return pong, nil 24 | } 25 | --------------------------------------------------------------------------------