invalid receiver error when using Gin Gonic framework in Go - go

I am trying to use an external (non anonymous) function in the routing of my Gin based web server as shown below:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/hi/", Hi)
router.Run(":8080")
}
func (c *gin.Context) Hi() {
c.String(http.StatusOK, "Hello")
}
But I get 2 errors:
./main.go:13:23: undefined: Hi
./main.go:18:6: cannot define new methods on non-local type gin.Context
I am wondering how I can use anonymous functions in my endpoint handlers with gin gonic? All the documentation I've found so far uses anonymous functions.
Thanks!

You can only define a new method for a type in the same package declaring that type. That is, you cannot add a new method to gin.Context.
You should do:
func Hi(c *gin.Context) {
...

package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/hi", hi)
var n Node
router.GET("/hello", n.hello)
router.GET("/extra", func(ctx *gin.Context) {
n.extra(ctx, "surprise~")
})
router.Run(":8080")
}
func hi(c *gin.Context) {
c.String(200, "hi")
}
type Node struct{}
func (n Node) hello(c *gin.Context) {
c.String(200, "world")
}
func (n Node) extra(c *gin.Context, data interface{}) {
c.String(200, "%v", data)
}

Related

cannot use mockDB (variable of type *MockDB) as *gorm.DB value in struct literal

I created a get function for get exercises from the postgres db. And I wrote mock testing but I got this error from the struct how can I fix it?
I used Handler struct It has *gorm.DB struct.
error:
cannot use mockDB (variable of type *MockDB) as *gorm.DB value in struct literal
// router
package exercises
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type Handlers struct {
DB *gorm.DB
}
func RegisterRoutes(router *gin.Engine, db *gorm.DB) {
h := &Handlers{
DB: db,
}
routes := router.Group("/exercises")
routes.POST("/", h.AddExercise)
routes.GET("/", h.GetExercises)
routes.GET("/:id", h.GetExercise)
routes.PUT("/:id", h.UpdateExercise)
routes.DELETE("/:id", h.DeleteExercise)
}
// test
package exercises
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/kayraberktuncer/sports-planner/pkg/common/models"
"github.com/stretchr/testify/mock"
"gorm.io/gorm"
)
type MockDB struct {
mock.Mock
}
func (m *MockDB) Find(value interface{}) *gorm.DB {
args := m.Called(value)
return args.Get(0).(*gorm.DB)
}
func (m *MockDB) Error() error {
args := m.Called()
return args.Error(0)
}
func TestGetExercises(t *testing.T) {
// Setup mock DB
mockDB := new(MockDB)
mockDB.On("Find", &[]models.Exercise{}).Return(mockDB).Once()
// Setup Gin router
router := gin.New()
router.GET("/", func(c *gin.Context) {
handlers := &Handlers{DB: mockDB} // error
handlers.GetExercises(c)
})
// Perform request
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
router.ServeHTTP(w, req)
// Assert response
if w.Code != http.StatusOK {
t.Errorf("Expected status code %d, got %d", http.StatusOK, w.Code)
}
// Assert mock DB was called correctly
mockDB.AssertExpectations(t)
}
I wanted to made mock testing with my handler struct
MockDB and gorm's DB are two different structs and you cannot use them interchangeably. They can be used in the same place if they implement the same interface. For example:
// router
package exercises
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// this interface will be implemented by gorm.DB struct
type Store interface {
Create(value interface{}) *gorm.DB
First(out interface{}, where ...interface{}) *gorm.DB
Model(value interface{}) *gorm.DB
Delete(value interface{}, where ...interface{}) *gorm.DB
Find(out interface{}, where ...interface{}) *gorm.DB
DB() *sql.DB
Raw(sql string, values ...interface{}) *gorm.DB
Exec(sql string, values ...interface{}) *gorm.DB
Where(query interface{}, args ...interface{}) *gorm.DB
//other method signatures
}
type Handlers struct {
DB Store
}
func RegisterRoutes(router *gin.Engine, db Store) {
h := &Handlers{
DB: db,
}
routes := router.Group("/exercises")
routes.POST("/", h.AddExercise)
routes.GET("/", h.GetExercises)
routes.GET("/:id", h.GetExercise)
routes.PUT("/:id", h.UpdateExercise)
routes.DELETE("/:id", h.DeleteExercise)
}
Now, you can pass the *gorm.DB to the RegisterRoutes function in your code. For testing, you can use your MockDB struct if it implements all of the methods from the Store interface.

How to use gin as a server to write prometheus exporter metrics

This is the official prometheus golang-client example:
package main
import (
"log"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "cpu_temperature_celsius",
Help: "Current temperature of the CPU.",
})
func init() {
// Metrics have to be registered to be exposed:
prometheus.MustRegister(cpuTemp)
}
func main() {
cpuTemp.Set(65.3)
// The Handler function provides a default handler to expose metrics
// via an HTTP server. "/metrics" is the usual endpoint for that.
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))
}
In this code, the http server uses the promhttp library.
How to modify the metrics handler when using the gin framework? I did not find answers in the documentation.
We just utilize promhttp handler.
package main
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "cpu_temperature_celsius",
Help: "Current temperature of the CPU.",
})
func init() {
prometheus.MustRegister(cpuTemp)
}
func prometheusHandler() gin.HandlerFunc {
h := promhttp.Handler()
return func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
}
}
func main() {
cpuTemp.Set(65.3)
r := gin.New()
r.GET("/", func(c *gin.Context) {
c.JSON(200, "Hello world!")
})
r.GET("/metrics", prometheusHandler())
r.Run()
}
Or we always can switch to Prometheus middleware - https://github.com/zsais/go-gin-prometheus
Use gin wrapper
router.GET("/metrics", gin.WrapH(promhttp.Handler()))
I am using prometheus with other library https://github.com/Depado/ginprom:
package main
import (
"github.com/Depado/ginprom"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
p := ginprom.New(
ginprom.Engine(r),
ginprom.Subsystem("gin"),
ginprom.Path("/metrics"),
)
r.Use(p.Instrument())
r.GET("/hello/:id", func(c *gin.Context) {})
r.GET("/world/:id", func(c *gin.Context) {})
r.Run("127.0.0.1:8080")
}

How can I pass a var between routes funcs and change it in every controller?

I want to pass var and change it globally in every controller between routes so if controller1 change var to "as" I want controller2 to use the new value of the var = "as". How can I do it?
something like this
main.go:
var test []string
func NewRouter() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.GET("/ping", gets(test))
r.Run(":6030")
return r
}
controller.go:
func gets(test) gin.HandlerFunc {
fn := func(c *gin.Context){
// Here I want to change my var (test) and I want to change it globally so if any another function use it I want to use the new value I just change it here
}
}
The comment of mkopriva already give you a solution on how to using mutex.
However, since golang promote share memory by communicating, I propose another solution using channel.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
r := NewRouter()
r.Run(":6030")
}
var test = []string{"abc"}
func NewRouter() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
updateChan := make(chan []string, 0)
r.GET("/", gets(updateChan))
r.GET("/mn", er(updateChan))
return r
}
func gets(updateChan chan []string) gin.HandlerFunc {
return func(c *gin.Context){
fmt.Println("before update in get, test=",test)
newTest := []string{"def"}
updateChan <- newTest
}
}
func er(updateChan chan []string) gin.HandlerFunc {
return func(c *gin.Context){
test = <-updateChan
fmt.Println("get a new value for test in er, test=",test)
}
}
With an unbuffered channel updateChan, test will always be update with the latest input pushed to updateChan channel. And no data race will happen here.
Of course this is just a simple demonstration and there are lot more stuff to make a complete HTTP server with gin. But I limit the scope of the demonstration to channel usage only.

Pass a reference to a Redis instance to a Gorilla/Mux Handler

I'm trying to get my hands dirty while playing with some Gorilla/Mux and Go-Redis but I'm facing a little implementation problem here.
Essentially I have a project structured like the following:
Where redismanager.go handles the initialization of a Redis Client:
package redismanager
import (
"fmt"
"github.com/go-redis/redis"
)
func InitRedisClient() redis.Client {
client := redis.NewClient(&redis.Options{
Addr : "localhost:6379",
Password: "",
DB : 0, //default
})
pong, err := client.Ping().Result()
if( err != nil ){
fmt.Println("Cannot Initialize Redis Client ", err)
}
fmt.Println("Redis Client Successfully Initialized . . .", pong)
return *client
}
Where main.go calls redismanager.InitRedisClient and initializes mux.Handlers:
package main
import (
"github.com/gorilla/mux"
"github.com/go-redis/redis"
"net/http"
"fmt"
"log"
"encoding/json"
"io/ioutil"
"../redismanager"
"../api"
)
type RedisInstance struct {
RInstance *redis.Client
}
func main() {
//Initialize Redis Client
client := redismanager.InitRedisClient()
//Get current redis instance to get passed to different Gorilla-Mux Handlers
redisHandler := &RedisInstance{RInstance:&client}
//Initialize Router Handlers
r := mux.NewRouter()
r.HandleFunc("/todo", redisHandler.AddTodoHandler).
Methods("POST")
fmt.Println("Listening on port :8000 . . .")
// Bind to a port and pass our router in
log.Fatal(http.ListenAndServe(":8000", r))
}
Now, I can easily define and let work properly AddTodoHandler in the same file like:
func (c *RedisInstance) AddTodoHandler(w http.ResponseWriter, r *http.Request) {
. . . doSomething
}
But, to make things a bit more modular, I'm trying to move all of these RouteHandlers inside their respective files in api package. In order to make that, I need to pass a reference to redisHandler but I'm having some difficulties when trying to make that with an Handler inside api package.
For instance, If in the main I add:
r.HandleFunc("/todo/{id}", api.GetTodoHandler(&client)).
Methods("GET")
with gettodo.go
package api
import (
"net/http"
"github.com/gorilla/mux"
"fmt"
"encoding/json"
"github.com/go-redis/redis"
)
func GetTodoHandler(c *RedisInstance) func (w http.ResponseWriter, r *http.Request) {
func (w http.ResponseWriter, r *http.Request) {
. . . doSomething
}
}
It works nicely.
I'm still pretty new to Go and haven't found any cleaner solution to that even after several researches and reads.
Is my approach correct or are there any better ones?
Write a function that converts a function with the Redis instance argument to an HTTP handler:
func redisHandler(c *RedisInstance,
f func(c *RedisInstance, w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { f(c, w, r) })
}
Write your API handlers like this:
func AddTodoHandler(c *RedisInstance, w http.ResponseWriter, r *http.Request) {
...
}
Add to the mux like this:
r.Handler("/todo", redisHandler(client, api.AddTodoHandler)).Methods("POST")
where client is the Redis instance.
I would recommend using an App struct which initializes DB and Routes. And all Redis methods will be called inside.
e.g. type App struct{Routes *mux.Router, DB *DB_TYPE}
And which will have App.initializeRoutes method.
type App struct {
Router *mux.Router
DB *redis.NewClient
}
func (a *App) Run(addr string) {
log.Fatal(http.ListenAndServe(":8000", a.Router))
}
func (a *App) Initialize(addr, password string, db int) error {
// Connect postgres
db, err := redis.NewClient(&redis.Options{
Addr: addr,
Password: password,
DB: db,
})
if err != nil {
return err
}
// Ping to connection
err = db.Ping()
if err != nil {
return err
}
// Set db in Model
a.DB = db
a.Router = mux.NewRouter()
a.initializeRoutes()
return nil
}
func (a *App) initializeRoutes() {
a.Router.HandleFunc("/todo", a.AddTodoHandler).Methods("POST")
a.Router.HandleFunc("/todo/{id}", a.GetTodoHandler).Methods("GET")
}
// AddTodoHandler has access to DB, in your case Redis
// you can replace the steps for Redis.
func (a *App) AddTodoHandler() {
//has access to DB
a.DB
}
Hope you get the point, you can even extract out the Model work into a separate Struct and then pass it inside func's
r.HandleFunc("/todo/{id}", redisHandler.api.GetTodoHandler).Methods("GET")
Your redisHandler, as defined in main, has no api field, so this naturally doesn't compile.
If you re-defined your RedisInstance type in the api package, and you defined the handler methods on that type in the method-specific files, then you can initialize your redisHandler using that api.RedisInstance type and you can delete the main.RedisInstance type definition:
package main
import (
"github.com/gorilla/mux"
"github.com/go-redis/redis"
"net/http"
"fmt"
"log"
"encoding/json"
"io/ioutil"
"../redismanager"
"../api"
)
func main() {
//Initialize Redis Client
client := redismanager.InitRedisClient()
//Get current redis instance to get passed to different Gorilla-Mux Handlers
redisHandler := &api.RedisInstance{RInstance:&client}
//Initialize Router Handlers
r := mux.NewRouter()
r.HandleFunc("/todo", redisHandler.AddTodoHandler).Methods("POST")
r.HandleFunc("/todo/{id}", redisHandler.GetTodoHandler).Methods("GET")
fmt.Println("Listening on port :8000 . . .")
// Bind to a port and pass our router in
log.Fatal(http.ListenAndServe(":8000", r))
}

Having trouble splitting go code in multiple files

I have two files main.go and group.go... it looks something like this
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
// Creates a gin router with default middlewares:
// logger and recovery (crash-free) middlewares
router := gin.Default()
v1 := router.Group("/v1")
{
v1.GET("/", func (c *gin.Context) {
c.JSON(http.StatusOK, "{'sup': 'dup'}")
})
groups := v1.Group("/groups")
{
groups.GET("/", groupIndex)
groups.GET("/:id", groupShow)
groups.POST("/", groupCreate)
groups.PUT("/:id", groupUpdate)
groups.DELETE("/:id", groupDelete)
}
}
// Listen and server on 0.0.0.0:8080
router.Run(":3000")
}
So the methods groupIndex, groupCreate, groupUpdate, etc are located in another file under routes/group.go
package main
import (
"strings"
"github.com/gin-gonic/gin"
)
func groupIndex(c *gin.Context) {
var group struct {
Name string
Description string
}
group.Name = "Famzz"
group.Description = "Jamzzz"
c.JSON(http.StatusOK, group)
}
func groupShow(c *gin.Context) {
c.JSON(http.StatusOK, "{'groupShow': 'someContent'}")
}
func groupCreate(c *gin.Context) {
c.JSON(http.StatusOK, "{'groupShow': 'someContent'}")
}
func groupUpdate(c *gin.Context) {
c.JSON(http.StatusOK, "{'groupUpdate': 'someContent'}")
}
func groupDelete(c *gin.Context) {
c.JSON(http.StatusOK, "{'groupDelete': 'someContent'}")
}
But when I try to compile I get the following error
stuff/main.go:21: undefined: groupIndex
stuff/main.go:23: undefined: groupShow
stuff/main.go:24: undefined: groupCreate
stuff/main.go:25: undefined: groupUpdate
stuff/main.go:26: undefined: groupDelete
I'm super new to go, but I thought if you put files in the same package, then they'll have access to each other. What am I doing wrong here?
There are two ways to fix this:
Move group.go to the same directory as main.go.
Import group.go as a package. Change the package declaration on group.go to:
package routes // or the name of your choice
Export the functions by starting them with a capital letter:
func GroupIndex(c *gin.Context) {
Import the package from main:
import "path/to/routes"
...
groups.GET("/", routes.GroupIndex)
The document How To Write Go Code explains this and more.

Resources