Files
snack/aur/rpc.go
Tai Groot 1410e4888c chore: update Go to 1.26.1, fix goimports formatting, add tests
- Update go.mod from Go 1.26.0 to 1.26.1
- Update dependencies: golang.org/x/sync, golang.org/x/sys,
  charmbracelet/x/exp/charmtone, mattn/go-runewidth
- Fix goimports formatting in 10 files
- Add apk/normalize_test.go: tests for normalizeName and
  parseArchNormalize with all known arch suffixes
- Add rpm/parse_test.go: tests for parseList, parseInfo,
  parseArchSuffix, and normalizeName (all at 100% coverage)
- All tests pass with -race, staticcheck and go vet clean
2026-03-08 12:47:30 +00:00

118 lines
3.2 KiB
Go

package aur
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"github.com/gogrlx/snack"
)
const rpcBaseURL = "https://aur.archlinux.org/rpc/v5"
// rpcResponse is the top-level AUR RPC response.
type rpcResponse struct {
ResultCount int `json:"resultcount"`
Results []rpcResult `json:"results"`
Type string `json:"type"`
Error string `json:"error,omitempty"`
Version int `json:"version"`
}
// rpcResult is a single package from the AUR RPC API.
type rpcResult struct {
Name string `json:"Name"`
Version string `json:"Version"`
Description string `json:"Description"`
URL string `json:"URL"`
URLPath string `json:"URLPath"`
PackageBase string `json:"PackageBase"`
PackageBaseID int `json:"PackageBaseID"`
NumVotes int `json:"NumVotes"`
Popularity float64 `json:"Popularity"`
OutOfDate *int64 `json:"OutOfDate"`
Maintainer string `json:"Maintainer"`
FirstSubmitted int64 `json:"FirstSubmitted"`
LastModified int64 `json:"LastModified"`
Depends []string `json:"Depends"`
MakeDepends []string `json:"MakeDepends"`
OptDepends []string `json:"OptDepends"`
License []string `json:"License"`
Keywords []string `json:"Keywords"`
}
func (r *rpcResult) toPackage() snack.Package {
return snack.Package{
Name: r.Name,
Version: r.Version,
Description: r.Description,
Repository: "aur",
}
}
// rpcGet performs an AUR RPC API request.
func rpcGet(ctx context.Context, endpoint string) (*rpcResponse, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, fmt.Errorf("aur rpc: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("aur rpc: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("aur rpc: HTTP %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("aur rpc: reading response: %w", err)
}
var result rpcResponse
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("aur rpc: parsing response: %w", err)
}
if result.Error != "" {
return nil, fmt.Errorf("aur rpc: %s", result.Error)
}
return &result, nil
}
// rpcSearch queries the AUR for packages matching the query string.
func rpcSearch(ctx context.Context, query string) ([]snack.Package, error) {
endpoint := rpcBaseURL + "/search/" + url.PathEscape(query)
resp, err := rpcGet(ctx, endpoint)
if err != nil {
return nil, err
}
pkgs := make([]snack.Package, 0, len(resp.Results))
for _, r := range resp.Results {
pkgs = append(pkgs, r.toPackage())
}
return pkgs, nil
}
// rpcInfo returns info about a specific AUR package.
func rpcInfo(ctx context.Context, pkg string) (*snack.Package, error) {
endpoint := rpcBaseURL + "/info?arg[]=" + url.QueryEscape(pkg)
resp, err := rpcGet(ctx, endpoint)
if err != nil {
return nil, err
}
if resp.ResultCount == 0 {
return nil, fmt.Errorf("aur info %s: %w", pkg, snack.ErrNotFound)
}
p := resp.Results[0].toPackage()
return &p, nil
}