How can I reduce redundant code of duplicate function in Golang? - function

I have a Rest API application to list all json data to browser. as long as I have more modules my code is more redundant. and complex.
func UserList(w http.ResponseWriter, r *http.Request) {
list := []models.User{}
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
func ProductList(w http.ResponseWriter, r *http.Request) {
list := []models.Product{}
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
func OrderList(w http.ResponseWriter, r *http.Request) {
list := []models.Order{}
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
Is there any better solution to make this code into just one function
Example
func List(w http.ResponseWriter, r *http.Request) {
list := ??? List of struct here ???
db.Find(&list)
json.NewEncoder(w).Encode(list)
}

you can do something like this:
func List(list interface{}, w http.ResponseWriter, r *http.Request,) {
db.Find(list)
json.NewEncoder(w).Encode(list)
}

If you pass the model type as a request param, this should do it (including error handling):
func List(w http.ResponseWriter, r *http.Request) {
var list interface{}
switch r.FormValue("model") {
case "user":
list = new([]models.User)
case "product":
list = new([]models.Product)
case "order":
list = new([]models.Order)
default:
http.Error(w, "invalid type", http.StatusBadRequest)
return
}
if err := db.Find(list); err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(list); err != nil {
log.Printf("json encoding error: %v", err)
}
}
Another option is to build a registry of types, and even the slice creation can be factored out, using the help of reflect:
var reg = map[string]reflect.Type{
"user": reflect.TypeOf((*models.User)(nil)).Elem(),
"product": reflect.TypeOf((*models.Product)(nil)).Elem(),
"order": reflect.TypeOf((*models.Order)(nil)).Elem(),
}
func List(w http.ResponseWriter, r *http.Request) {
etype := reg[r.FormValue("model")]
if etype == nil {
http.Error(w, "invalid type", http.StatusBadRequest)
return
}
list := reflect.New(reflect.SliceOf(etype)).Interface()
if err := db.Find(list); err != nil {
http.Error(w, "db error", http.StatusInternalServerError)
return
}
if err := json.NewEncoder(w).Encode(list); err != nil {
log.Printf("json encoding error: %v", err)
}
}

Given that you are calling db.Find(&list) I assume that they share a common interface. In which case you could wrap your handler calls like so;
func ListHandler(list <YOUR_INTERFACE>) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
db.Find(&list)
json.NewEncoder(w).Encode(list)
}
}
In your call;
http.HandleFunc("/user/list", ListHandler([]models.User{}))
http.HandleFunc("/product/list", ListHandler([]models.Product{}))
http.HandleFunc("/order/list", ListHandler([]models.Order{}))

Related

panic: open template/layout-main-page.html: The system cannot find the path specified

I'm new in Golang and I'm trying to run a simple test in my application but a panic occurs everytime. I can't find what is the problem.
I've looked in several places but couldn't find a solution.
Feel free to ask me anything about the code, if you have any questions, just ask.
The error mensage:
PS D:\projetos go\api-ranking-crypto> go test ./test/
panic: open template/layout-main-page.html: The system cannot find the path specified.
goroutine 1 [running]:
html/template.Must(...)
D:/Go/src/html/template/template.go:374
github.com/maickmachado/upvote-api/controllers.init()
D:/projetos go/api-ranking-crypto/controllers/controllers.go:15 +0x1d8
FAIL github.com/maickmachado/upvote-api/test 0.209s
FAIL
My test code:
func TestHealthCheck(t *testing.T) {
tt := []struct {
name string
method string
statusCode int
}{
{
name: "status ok",
method: http.MethodGet,
statusCode: http.StatusOK,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
request := httptest.NewRequest(tc.method, "/healthcheck", nil)
responseRecorder := httptest.NewRecorder()
controllers.HealthCheck(responseRecorder, request)
if responseRecorder.Code != tc.statusCode {
t.Errorf("Want status '%d', got '%d'", tc.statusCode, responseRecorder.Code)
}
})
}
}
The controller file:
var (
TmplMainPage = template.Must(template.ParseFiles("template/layout-main-page.html"))
TmplDetailPage = template.Must(template.ParseFiles("template/layout-detail-page.html"))
TmplError = template.Must(template.ParseFiles("template/layout-erro.html"))
TmplRanking = template.Must(template.ParseFiles("template/layout-ranking.html"))
)
func GetAllData(w http.ResponseWriter, r *http.Request) {...}
func GetRanking(w http.ResponseWriter, r *http.Request) {...}
func CryptoDetail(w http.ResponseWriter, r *http.Request) {...}
func VoteCrypto(w http.ResponseWriter, r *http.Request) {...}
func HealthCheck(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, `{"alive": true}`)}
My main file:
func init() {
database.CreateMongoBD()
}
func main() {
routes.HandleRequest()
}
My route file:
func HandleRequest() {
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/cryptocoins", controllers.GetAllData).Methods("GET")
myRouter.HandleFunc("/ranking", controllers.GetRanking).Methods("GET")
myRouter.HandleFunc("/cryptocoins/{name}", controllers.CryptoDetail).Methods("GET")
myRouter.HandleFunc("/healthcheck", controllers.HealthCheck).Methods("GET")
myRouter.HandleFunc("/cryptocoins/vote/{text}", controllers.VoteCrypto).Methods("POST")
myRouter.NotFoundHandler = http.Handler(http.HandlerFunc(controllers.ErrorHandler404))
log.Fatal(http.ListenAndServe(":8080", myRouter))
}
Thanks to Cerise Limón and her answer in another post.
I changed some things.
Put the template inside the Handle function and take out the template.Must from it:
func ErrorHandler404(w http.ResponseWriter, r *http.Request) {
TmplError, _ := template.ParseFiles("./template/layout-erro.html")
w.WriteHeader(http.StatusNotFound)
data := models.DetailPageData{
PageTitle: "Erro 404 - Not Found",
}
err := TmplError.Execute(w, data)
if err != nil {
log.Println(err)
}
}

json: cannot unmarshal object into Go value of type []*main.Config

I'm new to golang and json, we are using gorilla mux library and I'd like to do a post request in postman. In config struct entries needs to be a map like that and in post server I need to have an array of *Config in postServer struct. I have 3 go files.
Service.go file is this:
package main
import (
"errors"
"github.com/gorilla/mux"
"mime"
"net/http"
)
type Config struct {
Id string `json:"id"`
entries map[string]string `json:"entries"`
}
type postServer struct {
data map[string][]*Config
}
func (ts *postServer) createPostHandler(w http.ResponseWriter, req *http.Request) {
contentType := req.Header.Get("Content-Type")
mediatype, _, err := mime.ParseMediaType(contentType)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if mediatype != "application/json" {
err := errors.New("Expect application/json Content-Type")
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
return
}
rt, err := decodeBody(req.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
id := createId()
ts.data[id] = rt
renderJSON(w, rt)
}
func (ts *postServer) getAllHandler(w http.ResponseWriter, req *http.Request) {
allTasks := []*Config{}
for _, v := range ts.data {
allTasks = append(allTasks, v...)
}
renderJSON(w, allTasks)
}
func (ts *postServer) getPostHandler(w http.ResponseWriter, req *http.Request) {
id := mux.Vars(req)["id"]
task, ok := ts.data[id]
if !ok {
err := errors.New("key not found")
http.Error(w, err.Error(), http.StatusNotFound)
return
}
renderJSON(w, task)
}
func (ts *postServer) delPostHandler(w http.ResponseWriter, req *http.Request) {
id := mux.Vars(req)["id"]
if v, ok := ts.data[id]; ok {
delete(ts.data, id)
renderJSON(w, v)
} else {
err := errors.New("key not found")
http.Error(w, err.Error(), http.StatusNotFound)
}
}
I wanted to test createPostHandler.
Then I have helper.go file where I decoded json into go and rendered into json:
package main
import (
"encoding/json"
"github.com/google/uuid"
"io"
"net/http"
)
func decodeBody(r io.Reader) ([]*Config, error) {
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
var rt []*Config
if err := dec.Decode(&rt); err != nil {
return nil, err
}
return rt, nil
}
func renderJSON(w http.ResponseWriter, v interface{}) {
js, err := json.Marshal(v)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(js)
}
func createId() string {
return uuid.New().String()
}
and the last one go file is main.go where I have this:
package main
import (
"context"
"github.com/gorilla/mux"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
router := mux.NewRouter()
router.StrictSlash(true)
server := postServer{
data: map[string][]*Config{},
}
router.HandleFunc("/config/", server.createPostHandler).Methods("POST")
router.HandleFunc("/configs/", server.getAllHandler).Methods("GET")
router.HandleFunc("/config/{id}/", server.getPostHandler).Methods("GET")
router.HandleFunc("/config/{id}/", server.delPostHandler).Methods("DELETE")
// start server
srv := &http.Server{Addr: "0.0.0.0:8000", Handler: router}
go func() {
log.Println("server starting")
if err := srv.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
log.Fatal(err)
}
}
}()
<-quit
log.Println("service shutting down ...")
// gracefully stop server
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal(err)
}
log.Println("server stopped")
}
And JSON whad I did send is this:
{
"entries":["hello", "world"]
}
And error what I'm getting in postman is this:
json: cannot unmarshal object into Go value of type []*main.Config
I don't know what is a problem, maybe I'm sending wrong json or I just did something wrong in decodeBody, I needed to add [] in decodeBody in var rt []*Config because it wouldn't work otherwise.
Can someone help me to fix this please?
This is an example of how you can define a struct Config that you can parse your sample JSON into.
EDIT: field entries changed to map.
You can play with it on Playground.
package main
import (
"encoding/json"
"fmt"
)
type Config struct {
Id string `json:"id"`
Entries map[string]string `json:"entries"`
}
func main() {
str := `[{"id":"42", "entries":{"hello": "world"}}]`
var tmp []Config
err := json.Unmarshal([]byte(str), &tmp)
if err != nil {
fmt.Printf("error: %v", err)
}
var rt []*Config
for _, c := range tmp {
rt = append(rt, &c)
}
for _, c := range rt {
for k, v := range c.Entries {
fmt.Printf("id=%s key=%s value=%s\n", c.Id, k, v)
}
}
}

How to set up web server to perform POST Request in Go?

I want to set up a web server to perform a POST request. How does the post request get executed with the code below since only HandleFunc and ListenAndServe are defined in main function?
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
func post(w http.ResponseWriter, r *http.Request) {
const myurl string = "http://localhost:8000/"
request := strings.NewReader(`
{
"Name":"Tom",
"Age":"20"
}
`)
response, err := http.Post(myurl, "application/json", request)
content, err := ioutil.ReadAll(response.Body)
if err != nil {
panic(err)
}
fmt.Println(string(content))
defer response.Body.Close()
}
func main() {
http.HandleFunc("/", post)
log.Fatal(http.ListenAndServe(":8000", nil))
}
Here is a basic example of how you could go about it. I am using the same program to run both, the server and the client. This is just for demonstration purposes. You can of course make them separate programs.
// use struct to represent the data
// to recieve and send
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
// run the example
func main() {
// start the server in a goroutine
go server()
// wait 1 second to give the server time to start
time.Sleep(time.Second)
// make a post request
if err := client(); err != nil {
fmt.Println(err)
}
}
// basic web server to receive a request and
// decode the body into a user struct
func server() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
user := &Person{}
err := json.NewDecoder(r.Body).Decode(user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Println("got user:", user)
w.WriteHeader(http.StatusCreated)
})
if err := http.ListenAndServe(":8080", nil); err != http.ErrServerClosed {
panic(err)
}
}
// a simple client that posts a user to the server
func client() error {
user := &Person{
Name: "John",
Age: 30,
}
b := new(bytes.Buffer)
err := json.NewEncoder(b).Encode(user)
if err != nil {
return err
}
resp, err := http.Post("http://localhost:8080/", "application/json", b)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println(resp.Status)
return nil
}
Here is the working example: https://go.dev/play/p/34GT04jy_uA

How to stop the repetition of code when parsing files

I am repeating the same code of html files in every function. I just changed the name of one file in every function and that is ui/html/home.html in indexHandler() and ui/html/signup.html in signup()
Other file names are the same and the code of ParseFiles() is also the same in each function. How to prevent the code from repeating every time.
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
files := []string{"ui/html/home.html", "ui/html/footer.html", "ui/html/base.html",}
tmpl, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
tmpl.Execute(w, nil)
}
func signup(w http.ResponseWriter, r *http.Request) {
files := []string{"ui/html/signup.html", "ui/html/footer.html", "ui/html/base.html",}
tmpl, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
tmpl.Execute(w, nil)
}
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/signup", signup)
log.Println("Starting the server at :4000")
http.ListenAndServe(":4000", nil)
}
You can make it a package variable.
https://play.golang.org/p/haDmat_kzkg
package main
import (
"fmt"
)
var (
files = []string{"ui/html/home.html", "ui/html/footer.html", "ui/html/base.html"}
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
tmpl, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
tmpl.Execute(w, nil)
}
func signup(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles(files...)
if err != nil {
panic(err)
}
tmpl.Execute(w, nil)
}
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/signup", signup)
log.Println("Starting the server at :4000")
http.ListenAndServe(":4000", nil)
}
If you need to add a template for a specific handler, create a new slice out of the global variable and append to it.
tmpl, err := template.ParseFiles(append(files,[]string{"..."})...)
I think you have to read a bit about effective go in general, https://golang.org/doc/effective_go#initialization
or the go tour https://tour.golang.org/welcome/1
A tip to apply is to compile the template during initialization, rather during the request processing. This will speed up your program.
package main
//...
var (
indexTpml = template.Must(template.ParseFiles(files...))
)
https://play.golang.org/p/TgjZYCul9M6
Using some helper functions, one can write,
package main
import (
"html/template"
"log"
"net/http"
)
func makeTpl(base string) *template.Template {
files := []string{base, "ui/html/footer.html", "ui/html/base.html"}
return template.Must(template.ParseFiles(files...))
}
var (
indexTpml = makeTpl("ui/html/home.html")
signupTpml = makeTpl("ui/html/signup.html")
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
indexTpml.Execute(w, nil)
}
func signup(w http.ResponseWriter, r *http.Request) {
signupTpml.Execute(w, nil)
}
func main() {
http.HandleFunc("/", indexHandler)
http.HandleFunc("/signup", signup)
log.Println("Starting the server at :4000")
http.ListenAndServe(":4000", nil)
}

"data" field in JSON response using SuperGraph as library

Using SuperGraph as library I found JSON responses does not start with: "data".
Example:
{
"players": [...]
}
instead of:
{
"data": {
"players": [...]
}
}
I'm using this code:
func main() {
sg, err := core.NewSuperGraph(nil, sb)
r := chi.NewRouter()
r.Group(func(r chi.Router) {
r.Post(config.ApiEndpoint, myHandler(sg))
})
StartServer(r)
}
type reqBody struct {
Query string `json:"query"`
}
func sgHandler(sg *core.SuperGraph, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var rBody reqBody
bodyBytes, _ := ioutil.ReadAll(r.Body)
_ = r.Body.Close(
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
err := json.NewDecoder(bytes.NewBuffer(bodyBytes)).Decode(&rBody)
if err != nil {
http.Error(w, "Error parsing JSON request body", 400)
}
res, err := sg.GraphQL(ctx, rBody.Query, nil)
if err == nil {
result, _ := json.Marshal(res.Data) // IS THIS WRONG?
_, err = w.Write(result)
}
}
}
Is it my fault?
Is this wrong? result, _ := json.Marshal(res.Data)?
Maybe I need to Marshal all res? Isn't it heavier?