loader revisited

This commit is contained in:
Ilia Denisov
2026-03-16 19:52:02 +02:00
committed by GitHub
parent e6c6970947
commit 3f1776aa5f
30 changed files with 1581 additions and 527 deletions
+63 -10
View File
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"galaxy/connector"
gerr "galaxy/error"
"galaxy/model/client"
"galaxy/model/report"
"io"
@@ -59,7 +60,7 @@ type httpConnector struct {
func NewHttpConnector(ctx context.Context, backendURL string) (*httpConnector, error) {
u, err := url.Parse(backendURL)
if err != nil {
return nil, err
return nil, gerr.WrapService(fmt.Errorf("parse backend URL %q: %w", backendURL, err))
}
h := &httpConnector{
ctx: ctx,
@@ -162,6 +163,58 @@ func isConnectTimeout(err error) bool {
return false
}
// isConnectionError reports transport-level connectivity failures that should
// be surfaced as connection errors instead of service contract errors.
func isConnectionError(err error) bool {
if err == nil {
return false
}
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return false
}
if isConnectTimeout(err) {
return true
}
var urlErr *url.Error
if errors.As(err, &urlErr) {
err = urlErr.Err
}
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
return true
}
var opErr *net.OpError
if errors.As(err, &opErr) {
return true
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
return true
}
return false
}
func classifyConnectorError(err error) error {
if err == nil {
return nil
}
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return err
}
if gerr.IsConnection(err) || gerr.IsService(err) {
return err
}
if isConnectionError(err) {
return gerr.WrapConnection(err)
}
return gerr.WrapService(err)
}
// CheckConnection probes backend status endpoint and reports whether server is reachable.
func (h *httpConnector) CheckConnection() bool {
resp, err := h.doRequest(h.requestContext(), checkConnectionPath)
@@ -177,17 +230,17 @@ func (h *httpConnector) CheckConnection() bool {
func (h *httpConnector) CheckVersion() ([]connector.VersionInfo, error) {
resp, err := h.doRequest(h.requestContext(), checkVersionPath)
if err != nil {
return nil, fmt.Errorf("request versions from backend: %w", err)
return nil, classifyConnectorError(fmt.Errorf("request versions from backend: %w", err))
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("request versions from backend: unexpected status code %d", resp.StatusCode)
return nil, classifyConnectorError(fmt.Errorf("request versions from backend: unexpected status code %d", resp.StatusCode))
}
var versions []connector.VersionInfo
if err := json.NewDecoder(resp.Body).Decode(&versions); err != nil {
return nil, fmt.Errorf("decode versions response: %w", err)
return nil, classifyConnectorError(fmt.Errorf("decode versions response: %w", err))
}
return versions, nil
@@ -198,17 +251,17 @@ func (h *httpConnector) CheckVersion() ([]connector.VersionInfo, error) {
func (h *httpConnector) DownloadVersion(urlOrPath string) ([]byte, error) {
resp, err := h.doRequest(h.requestContext(), urlOrPath)
if err != nil {
return nil, fmt.Errorf("download version artifact: %w", err)
return nil, classifyConnectorError(fmt.Errorf("download version artifact: %w", err))
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("download version artifact: unexpected status code %d", resp.StatusCode)
return nil, classifyConnectorError(fmt.Errorf("download version artifact: unexpected status code %d", resp.StatusCode))
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read version artifact body: %w", err)
return nil, classifyConnectorError(fmt.Errorf("read version artifact body: %w", err))
}
return body, nil
@@ -228,17 +281,17 @@ func (h *httpConnector) FetchReport(_ client.GameID, turn uint, callback func(re
func (h *httpConnector) fetchReport(turn uint) (report.Report, error) {
resp, err := h.doRequest(h.requestContext(), fetchReportRequestPath(turn))
if err != nil {
return report.Report{}, fmt.Errorf("request report from backend: %w", err)
return report.Report{}, classifyConnectorError(fmt.Errorf("request report from backend: %w", err))
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return report.Report{}, fmt.Errorf("request report from backend: unexpected status code %d", resp.StatusCode)
return report.Report{}, classifyConnectorError(fmt.Errorf("request report from backend: unexpected status code %d", resp.StatusCode))
}
var rep report.Report
if err := json.NewDecoder(resp.Body).Decode(&rep); err != nil {
return report.Report{}, fmt.Errorf("decode report response: %w", err)
return report.Report{}, classifyConnectorError(fmt.Errorf("decode report response: %w", err))
}
return rep, nil