small pixel drawing of a pufferfish gore

internal/module/module.go

package module

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path"
	"strings"

	"golang.org/x/mod/module"
	"golang.org/x/sync/errgroup"
)

type latestResp struct {
	Version string `json:"Version"`
}

type resolvedModule struct {
	Module  string
	Version string
	GoMod   []byte
}

func proxyRequest(importPath, suffix string) (*http.Request, error) {
	proxyBase := "https://proxy.golang.org"
	if gp := os.Getenv("GOPROXY"); gp != "" {
		if strings.ContainsRune(gp, ',') ||
			strings.ContainsRune(gp, '|') ||
			gp == "off" ||
			gp == "direct" {
			return nil, fmt.Errorf("only GOPROXY=<url> is supported by gore, not %q", gp)
		}
		proxyBase = strings.TrimSuffix(gp, "/")
	}
	escapedSuffix, err := module.EscapeVersion(suffix)
	if err != nil {
		return nil, err
	}
	if escapedSuffix != "@latest" {
		escapedSuffix = "@v/" + escapedSuffix
	}
	escapedImportPath, err := module.EscapePath(importPath)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", proxyBase+"/"+escapedImportPath+"/"+escapedSuffix, nil)
	if err != nil {
		return nil, err
	}
	// TODO: dynamic version here pls
	req.Header.Set("User-Agent", "gore runs everything alpha")
	return req, nil
}

func moduleInfo(ctx context.Context, importPath, version string) (*latestResp, error) {
	suffix := version + ".info"
	if version == "latest" {
		suffix = "@latest"
	}
	req, err := proxyRequest(importPath, suffix)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/json")
	resp, err := http.DefaultClient.Do(req.WithContext(ctx))
	if err != nil {
		return nil, err
	}
	defer func() {
		io.ReadAll(resp.Body)
		resp.Body.Close()
	}()
	if resp.StatusCode == http.StatusNotFound {
		return nil, nil
	}
	if got, want := resp.StatusCode, http.StatusOK; got != want {
		return nil, fmt.Errorf("unexpected HTTP status: got %v, want %v", resp.Status, want)
	}
	var latest latestResp
	b, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("reading HTTP response: %v", err)
	}
	if err := json.Unmarshal(b, &latest); err != nil {
		return nil, fmt.Errorf("decoding /@latest response: %v", err)
	}
	return &latest, nil
}

func resolveGoMod(ctx context.Context, importPath string, latest *latestResp) (*resolvedModule, error) {
	req, err := proxyRequest(importPath, latest.Version+".mod")
	if err != nil {
		return nil, err
	}
	resp, err := http.DefaultClient.Do(req.WithContext(ctx))
	if err != nil {
		return nil, err
	}
	defer func() {
		io.ReadAll(resp.Body)
		resp.Body.Close()
	}()
	if got, want := resp.StatusCode, http.StatusOK; got != want {
		return nil, fmt.Errorf("unexpected HTTP status: got %v, want %v", resp.Status, want)
	}
	b, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("reading HTTP response: %v", err)
	}
	return &resolvedModule{
		module:  importPath,
		version: latest.Version,
		goMod:   b,
	}, nil
}

func Resolve(ctx context.Context, importPath, version string) (*resolvedModule, error) {
	eg, latestctx := errgroup.WithContext(ctx)

	parts := strings.Split(path.Clean(importPath), "/")
	resps := make([]*latestResp, len(parts))
	for idx := len(parts); idx > 0; idx-- {
		idx := idx // copy
		importPath := strings.Join(parts[:idx], "/")
		eg.Go(func() error {
			if importPath == "github.com" {
				// Short-circuit: github.com is not a Go module :)
				return nil
			}
			if strings.HasPrefix(importPath, "github.com/") &&
				!strings.ContainsRune(strings.TrimPrefix(importPath, "github.com/"), '/') {
				// Short-circuit: github.com/<something> references an
				// organisation or user, not a repository.
				return nil
			}
			resp, err := moduleInfo(latestctx, importPath, version)
			if err != nil {
				return err
			}
			resps[idx-1] = resp
			return nil
		})
	}

	if err := eg.Wait(); err != nil {
		return nil, err
	}

	for idx := len(parts); idx > 0; idx-- {
		importPath := strings.Join(parts[:idx], "/")
		resp := resps[idx-1]
		if resp == nil {
			continue
		}
		return resolveGoMod(ctx, importPath, resp)
	}

	return nil, fmt.Errorf("could not resolve import path %q to any Go module", importPath)
}