Reduce code duplication with generics
Go 1.18 introduced generics. Generics allow for common patterns to be reused across types. Today in packngo, we see duplication between ServiceOp types (devices and vlans, for example), effectively copy/pasting the Get, Delete, Update, List, and Create functions.
For some (if not all of these operations) we could create generic handlers that simplify how we onboard new endpoints.
The following demonstrates this pattern: https://gotipplay.golang.org/p/LzqKaJ_aHoR (credit to @rogpeppe as discussed in Gopher's Slack)
This issue is academic in nature. The more pressing goal is a fully generated client per #215. Even in a generated client, generics could play a large role in avoiding duplication within generated code.
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"strings"
)
type CRUD[T any] struct {
baseURL string
}
func (g CRUD[T]) Get(id string) (T, *http.Response, error) {
resp, err := http.Get(g.baseURL + "/" + id)
if err != nil {
return *new(T), nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return *new(T), nil, err
}
var v T
if err := json.Unmarshal(data, &v); err != nil {
return *new(T), nil, err
}
return v, resp, nil
}
func (g CRUD[T]) Put(_ string, _ T) error { return nil }
func (g CRUD[T]) Delete(_ string) error { return nil }
type User struct {
UserName string `json:"user"`
}
type Org struct {
OrgName string `json:"org"`
}
type Service struct {
User CRUD[User]
Org CRUD[Org]
}
func main() {
server := httptest.NewServer(http.HandlerFunc(logsrv))
var svc Service
svc.User.baseURL = server.URL + "/users"
svc.Org.baseURL = server.URL + "/orgs"
user, _, err := svc.User.Get("rsc")
fmt.Println(user, err)
org, _, err := svc.Org.Get("golang")
fmt.Println(org, err)
}
func logsrv(w http.ResponseWriter, req *http.Request) {
log.Printf("got request on URL %v", req.URL)
switch {
case strings.HasPrefix(req.URL.Path, "/users/"):
writeJSON(w, User{"someuser"})
case strings.HasPrefix(req.URL.Path, "/orgs/"):
writeJSON(w, Org{"someorg"})
default:
http.NotFound(w, req)
}
}
func writeJSON(w io.Writer, x any) {
data, _ := json.Marshal(x)
w.Write(data)
}