Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce code duplication with generics #328

Open
displague opened this issue Apr 28, 2022 · 0 comments
Open

Reduce code duplication with generics #328

displague opened this issue Apr 28, 2022 · 0 comments

Comments

@displague
Copy link
Member

displague commented Apr 28, 2022

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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant