Merge pull request 'chore: Go 1.26 modernization' (#17) from chore/go-1.26-modernization into main

This commit is contained in:
Charon 2026-02-24 18:01:41 +00:00
commit c8b32cc1a1
26 changed files with 136 additions and 174 deletions

4
pkg/cache/cache.go vendored
View file

@ -67,7 +67,7 @@ func (c *Cache) Path(key string) string {
}
// Get retrieves a cached item if it exists and hasn't expired.
func (c *Cache) Get(key string, dest interface{}) (bool, error) {
func (c *Cache) Get(key string, dest any) (bool, error) {
path := c.Path(key)
dataStr, err := c.medium.Read(path)
@ -98,7 +98,7 @@ func (c *Cache) Get(key string, dest interface{}) (bool, error) {
}
// Set stores an item in the cache.
func (c *Cache) Set(key string, data interface{}) error {
func (c *Cache) Set(key string, data any) error {
path := c.Path(key)
// Ensure parent directory exists

View file

@ -2,6 +2,7 @@ package coredeno
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
@ -14,7 +15,7 @@ func (s *Sidecar) Start(ctx context.Context, args ...string) error {
defer s.mu.Unlock()
if s.cmd != nil {
return fmt.Errorf("coredeno: already running")
return errors.New("coredeno: already running")
}
// Ensure socket directory exists with owner-only permissions

View file

@ -2,6 +2,7 @@ package coredeno
import (
"path/filepath"
"slices"
"strings"
)
@ -25,20 +26,10 @@ func CheckPath(path string, allowed []string) bool {
// CheckNet returns true if the given host:port is in the allowed list.
func CheckNet(addr string, allowed []string) bool {
for _, a := range allowed {
if a == addr {
return true
}
}
return false
return slices.Contains(allowed, addr)
}
// CheckRun returns true if the given command is in the allowed list.
func CheckRun(cmd string, allowed []string) bool {
for _, a := range allowed {
if a == cmd {
return true
}
}
return false
return slices.Contains(allowed, cmd)
}

View file

@ -4,6 +4,7 @@ import (
"context"
"embed"
goio "io"
"slices"
"sync/atomic"
)
@ -29,12 +30,7 @@ type Features struct {
// IsEnabled returns true if the given feature is enabled.
func (f *Features) IsEnabled(feature string) bool {
for _, flag := range f.Flags {
if flag == feature {
return true
}
}
return false
return slices.Contains(f.Flags, feature)
}
// Option is a function that configures the Core.
@ -44,15 +40,15 @@ type Option func(*Core) error
// Message is the interface for all messages that can be sent through the Core's IPC system.
// Any struct can be a message, allowing for structured data to be passed between services.
// Used with ACTION for fire-and-forget broadcasts.
type Message interface{}
type Message any
// Query is the interface for read-only requests that return data.
// Used with QUERY (first responder) or QUERYALL (all responders).
type Query interface{}
type Query any
// Task is the interface for requests that perform side effects.
// Used with PERFORM (first responder executes).
type Task interface{}
type Task any
// TaskWithID is an optional interface for tasks that need to know their assigned ID.
// This is useful for tasks that want to report progress back to the frontend.

View file

@ -1,6 +1,7 @@
package core
import (
"errors"
"fmt"
"sync"
)
@ -27,7 +28,7 @@ func newServiceManager() *serviceManager {
// It also appends to startables/stoppables if the service implements those interfaces.
func (m *serviceManager) registerService(name string, svc any) error {
if name == "" {
return fmt.Errorf("core: service name cannot be empty")
return errors.New("core: service name cannot be empty")
}
m.mu.Lock()
defer m.mu.Unlock()

View file

@ -1,8 +1,9 @@
package help
import (
"cmp"
"regexp"
"sort"
"slices"
"strings"
"unicode"
)
@ -158,11 +159,11 @@ func (i *searchIndex) Search(query string) []*SearchResult {
}
// Sort by score (highest first)
sort.Slice(results, func(a, b int) bool {
if results[a].Score != results[b].Score {
return results[a].Score > results[b].Score
slices.SortFunc(results, func(a, b *SearchResult) int {
if a.Score != b.Score {
return cmp.Compare(b.Score, a.Score) // descending
}
return results[a].Topic.Title < results[b].Topic.Title
return cmp.Compare(a.Topic.Title, b.Topic.Title)
})
return results
@ -285,24 +286,15 @@ func extractSnippet(content string, res []*regexp.Regexp) string {
if matchPos == -1 {
// No match found, use start of content
start = 0
end = snippetLen
if end > runeLen {
end = runeLen
}
end = min(snippetLen, runeLen)
} else {
// Convert byte position to rune position
matchRunePos := len([]rune(content[:matchPos]))
// Extract snippet around match (rune-based)
start = matchRunePos - 50
if start < 0 {
start = 0
}
start = max(matchRunePos-50, 0)
end = start + snippetLen
if end > runeLen {
end = runeLen
}
end = min(start+snippetLen, runeLen)
}
snippet := string(runes[start:end])
@ -357,11 +349,11 @@ func highlight(text string, res []*regexp.Regexp) string {
}
// Sort matches by start position
sort.Slice(matches, func(i, j int) bool {
if matches[i].start != matches[j].start {
return matches[i].start < matches[j].start
slices.SortFunc(matches, func(a, b match) int {
if a.start != b.start {
return cmp.Compare(a.start, b.start)
}
return matches[i].end > matches[j].end
return cmp.Compare(b.end, a.end) // descending
})
// Merge overlapping or adjacent matches
@ -370,9 +362,7 @@ func highlight(text string, res []*regexp.Regexp) string {
curr := matches[0]
for i := 1; i < len(matches); i++ {
if matches[i].start <= curr.end {
if matches[i].end > curr.end {
curr.end = matches[i].end
}
curr.end = max(curr.end, matches[i].end)
} else {
merged = append(merged, curr)
curr = matches[i]

View file

@ -18,14 +18,16 @@
package main
import (
"cmp"
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"sort"
"slices"
"strings"
)
@ -101,7 +103,7 @@ func findProjectRoot() (string, error) {
}
parent := filepath.Dir(dir)
if parent == dir {
return "", fmt.Errorf("could not find go.mod in any parent directory")
return "", errors.New("could not find go.mod in any parent directory")
}
dir = parent
}
@ -507,11 +509,11 @@ func printReport(result ValidationResult) {
fmt.Printf("-------------\n")
// Sort by file then line
sort.Slice(result.MissingKeys, func(i, j int) bool {
if result.MissingKeys[i].File != result.MissingKeys[j].File {
return result.MissingKeys[i].File < result.MissingKeys[j].File
slices.SortFunc(result.MissingKeys, func(a, b KeyUsage) int {
if a.File != b.File {
return cmp.Compare(a.File, b.File)
}
return result.MissingKeys[i].Line < result.MissingKeys[j].Line
return cmp.Compare(a.Line, b.Line)
})
for _, usage := range result.MissingKeys {

View file

@ -4,6 +4,7 @@ package i18n
import (
"embed"
"encoding/json"
"errors"
"fmt"
"io/fs"
"path"
@ -118,7 +119,7 @@ func NewWithLoader(loader Loader, opts ...Option) (*Service, error) {
// Load all available languages
langs := loader.Languages()
if len(langs) == 0 {
return nil, fmt.Errorf("no languages available from loader")
return nil, errors.New("no languages available from loader")
}
for _, lang := range langs {
@ -223,7 +224,7 @@ func (s *Service) SetLanguage(lang string) error {
}
if len(s.availableLangs) == 0 {
return fmt.Errorf("no languages available")
return errors.New("no languages available")
}
matcher := language.NewMatcher(s.availableLangs)

View file

@ -7,11 +7,12 @@
package datanode
import (
"cmp"
goio "io"
"io/fs"
"os"
"path"
"sort"
"slices"
"strings"
"sync"
"time"
@ -359,8 +360,8 @@ func (m *Medium) List(p string) ([]fs.DirEntry, error) {
}
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
slices.SortFunc(entries, func(a, b fs.DirEntry) int {
return cmp.Compare(a.Name(), b.Name())
})
return entries, nil

View file

@ -6,11 +6,12 @@ package node
import (
"archive/tar"
"bytes"
"cmp"
goio "io"
"io/fs"
"os"
"path"
"sort"
"slices"
"strings"
"time"
@ -335,8 +336,8 @@ func (n *Node) ReadDir(name string) ([]fs.DirEntry, error) {
}
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
slices.SortFunc(entries, func(a, b fs.DirEntry) int {
return cmp.Compare(a.Name(), b.Name())
})
return entries, nil

View file

@ -103,10 +103,7 @@ func (x *XORObfuscator) deriveKeyStream(entropy []byte, length int) []byte {
h.Write(blockBytes[:])
block := h.Sum(nil)
copyLen := len(block)
if offset+copyLen > length {
copyLen = length - offset
}
copyLen := min(len(block), length-offset)
copy(stream[offset:], block[:copyLen])
offset += copyLen
blockNum++
@ -222,10 +219,7 @@ func (s *ShuffleMaskObfuscator) deriveMask(entropy []byte, length int) []byte {
h.Write(blockBytes[:])
block := h.Sum(nil)
copyLen := len(block)
if offset+copyLen > length {
copyLen = length - offset
}
copyLen := min(len(block), length-offset)
copy(mask[offset:], block[:copyLen])
offset += copyLen
blockNum++

View file

@ -1,11 +1,12 @@
package collector
import (
"cmp"
"context"
"encoding/json"
"fmt"
"net/http"
"sort"
"slices"
"strings"
"time"
@ -126,8 +127,11 @@ func (i *InfluxDB) Collect(ctx context.Context) error {
for _, r := range runSet {
data.Runs = append(data.Runs, r)
}
sort.Slice(data.Runs, func(i, j int) bool {
return data.Runs[i].Model < data.Runs[j].Model || (data.Runs[i].Model == data.Runs[j].Model && data.Runs[i].RunID < data.Runs[j].RunID)
slices.SortFunc(data.Runs, func(a, b lab.BenchmarkRun) int {
if c := cmp.Compare(a.Model, b.Model); c != 0 {
return c
}
return cmp.Compare(a.RunID, b.RunID)
})
i.store.SetBenchmarks(data)

View file

@ -1,9 +1,11 @@
package handler
import (
"cmp"
"fmt"
"html/template"
"math"
"slices"
"sort"
"strings"
@ -60,25 +62,15 @@ func LossChart(points []lab.LossPoint) template.HTML {
yMin, yMax := allPts[0].Loss, allPts[0].Loss
for _, p := range allPts {
x := float64(p.Iteration)
if x < xMin {
xMin = x
}
if x > xMax {
xMax = x
}
if p.Loss < yMin {
yMin = p.Loss
}
if p.Loss > yMax {
yMax = p.Loss
}
xMin = min(xMin, x)
xMax = max(xMax, x)
yMin = min(yMin, p.Loss)
yMax = max(yMax, p.Loss)
}
// Add padding to Y range.
yRange := yMax - yMin
if yRange < 0.1 {
yRange = 0.1
}
yRange = max(yRange, 0.1)
yMin = yMin - yRange*0.1
yMax = yMax + yRange*0.1
if xMax == xMin {
@ -102,13 +94,7 @@ func LossChart(points []lab.LossPoint) template.HTML {
}
// X axis labels.
nGridX := 6
if int(xMax-xMin) < nGridX {
nGridX = int(xMax - xMin)
}
if nGridX < 1 {
nGridX = 1
}
nGridX := max(min(6, int(xMax-xMin)), 1)
for i := 0; i <= nGridX; i++ {
xVal := xMin + float64(i)*(xMax-xMin)/float64(nGridX)
x := scaleX(xVal)
@ -118,7 +104,7 @@ func LossChart(points []lab.LossPoint) template.HTML {
// Draw train loss line (dimmed).
if len(trainPts) > 1 {
sort.Slice(trainPts, func(i, j int) bool { return trainPts[i].Iteration < trainPts[j].Iteration })
slices.SortFunc(trainPts, func(a, b lab.LossPoint) int { return cmp.Compare(a.Iteration, b.Iteration) })
sb.WriteString(`<polyline points="`)
for i, p := range trainPts {
if i > 0 {
@ -134,7 +120,7 @@ func LossChart(points []lab.LossPoint) template.HTML {
// Draw val loss line (accent).
if len(valPts) > 1 {
sort.Slice(valPts, func(i, j int) bool { return valPts[i].Iteration < valPts[j].Iteration })
slices.SortFunc(valPts, func(a, b lab.LossPoint) int { return cmp.Compare(a.Iteration, b.Iteration) })
sb.WriteString(`<polyline points="`)
for i, p := range valPts {
if i > 0 {
@ -232,7 +218,7 @@ func ContentChart(points []lab.ContentPoint) template.HTML {
if !ok || len(pts) < 2 {
continue
}
sort.Slice(pts, func(i, j int) bool { return pts[i].Iteration < pts[j].Iteration })
slices.SortFunc(pts, func(a, b lab.ContentPoint) int { return cmp.Compare(a.Iteration, b.Iteration) })
// Average duplicate iterations.
averaged := averageByIteration(pts)
@ -285,7 +271,7 @@ func CapabilityChart(points []lab.CapabilityPoint) template.HTML {
overall = append(overall, p)
}
}
sort.Slice(overall, func(i, j int) bool { return overall[i].Iteration < overall[j].Iteration })
slices.SortFunc(overall, func(a, b lab.CapabilityPoint) int { return cmp.Compare(a.Iteration, b.Iteration) })
if len(overall) == 0 {
return template.HTML(`<div class="empty">No overall capability data</div>`)
@ -532,21 +518,14 @@ func DomainChart(stats []lab.DomainStat) template.HTML {
if len(stats) == 0 {
return ""
}
limit := 25
if len(stats) < limit {
limit = len(stats)
}
limit := min(25, len(stats))
items := stats[:limit]
maxCount := 0
for _, d := range items {
if d.Count > maxCount {
maxCount = d.Count
}
}
if maxCount == 0 {
maxCount = 1
maxCount = max(maxCount, d.Count)
}
maxCount = max(maxCount, 1)
barH := 18
gap := 4
@ -561,10 +540,7 @@ func DomainChart(stats []lab.DomainStat) template.HTML {
for i, d := range items {
y := i*(barH+gap) + 5
barW := int(float64(d.Count) / float64(maxCount) * float64(barAreaW))
if barW < 2 {
barW = 2
}
barW := max(int(float64(d.Count)/float64(maxCount)*float64(barAreaW)), 2)
fmt.Fprintf(&b, `<text x="%d" y="%d" fill="var(--muted)" font-size="11" text-anchor="end" dominant-baseline="middle">%s</text>`,
labelW-8, y+barH/2, template.HTMLEscapeString(d.Domain))
fmt.Fprintf(&b, `<rect x="%d" y="%d" width="%d" height="%d" fill="var(--accent)" rx="2" opacity="0.8"/>`,
@ -585,13 +561,9 @@ func VoiceChart(stats []lab.VoiceStat) template.HTML {
maxCount := 0
for _, v := range stats {
if v.Count > maxCount {
maxCount = v.Count
}
}
if maxCount == 0 {
maxCount = 1
maxCount = max(maxCount, v.Count)
}
maxCount = max(maxCount, 1)
barW := 50
gap := 8
@ -607,10 +579,7 @@ func VoiceChart(stats []lab.VoiceStat) template.HTML {
for i, v := range stats {
x := i*(barW+gap) + gap + 5
barH := int(float64(v.Count) / float64(maxCount) * float64(chartHeight))
if barH < 2 {
barH = 2
}
barH := max(int(float64(v.Count)/float64(maxCount)*float64(chartHeight)), 2)
y := topPad + chartHeight - barH
fmt.Fprintf(&b, `<rect x="%d" y="%d" width="%d" height="%d" fill="var(--green)" rx="2" opacity="0.7"/>`,

View file

@ -1,11 +1,12 @@
package handler
import (
"cmp"
"embed"
"fmt"
"html/template"
"net/http"
"sort"
"slices"
"strings"
"time"
@ -72,10 +73,7 @@ func NewWebHandler(s *lab.Store) *WebHandler {
if cores <= 0 {
return "0"
}
pct := load / float64(cores) * 100
if pct > 100 {
pct = 100
}
pct := min(load/float64(cores)*100, 100)
return fmt.Sprintf("%.0f", pct)
},
"fmtGB": func(v float64) string {
@ -376,11 +374,14 @@ func buildModelGroups(runs []lab.TrainingRunStatus, benchmarks lab.BenchmarkData
}
result = append(result, *g)
}
sort.Slice(result, func(i, j int) bool {
if result[i].HasTraining != result[j].HasTraining {
return result[i].HasTraining
slices.SortFunc(result, func(a, b ModelGroup) int {
if a.HasTraining != b.HasTraining {
if a.HasTraining {
return -1
}
return 1
}
return result[i].Model < result[j].Model
return cmp.Compare(a.Model, b.Model)
})
return result
}

View file

@ -3,6 +3,7 @@ package manifest
import (
"crypto/ed25519"
"encoding/base64"
"errors"
"fmt"
"gopkg.in/yaml.v3"
@ -29,7 +30,7 @@ func Sign(m *Manifest, priv ed25519.PrivateKey) error {
// Verify checks the ed25519 signature in m.Sign against the public key.
func Verify(m *Manifest, pub ed25519.PublicKey) (bool, error) {
if m.Sign == "" {
return false, fmt.Errorf("manifest.Verify: no signature present")
return false, errors.New("manifest.Verify: no signature present")
}
sig, err := base64.StdEncoding.DecodeString(m.Sign)
if err != nil {

View file

@ -159,7 +159,7 @@ func (i *Installer) cloneRepo(ctx context.Context, org, repo, version, dest stri
cmd := exec.CommandContext(ctx, "gh", args...)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("%s: %s", err, strings.TrimSpace(string(output)))
return fmt.Errorf("%w: %s", err, strings.TrimSpace(string(output)))
}
return nil

View file

@ -1,9 +1,10 @@
package plugin
import (
"cmp"
"encoding/json"
"path/filepath"
"sort"
"slices"
core "forge.lthn.ai/core/go/pkg/framework/core"
"forge.lthn.ai/core/go/pkg/io"
@ -34,8 +35,8 @@ func (r *Registry) List() []*PluginConfig {
for _, cfg := range r.plugins {
result = append(result, cfg)
}
sort.Slice(result, func(i, j int) bool {
return result[i].Name < result[j].Name
slices.SortFunc(result, func(a, b *PluginConfig) int {
return cmp.Compare(a.Name, b.Name)
})
return result
}

View file

@ -2,7 +2,7 @@ package process
import (
"context"
"fmt"
"errors"
"sync"
"time"
)
@ -104,7 +104,7 @@ func (r *Runner) RunAll(ctx context.Context, specs []RunSpec) (*RunAllResult, er
Name: name,
Spec: remaining[name],
Skipped: true,
Error: fmt.Errorf("circular dependency or missing dependency"),
Error: errors.New("circular dependency or missing dependency"),
})
}
break
@ -136,7 +136,7 @@ func (r *Runner) RunAll(ctx context.Context, specs []RunSpec) (*RunAllResult, er
Name: spec.Name,
Spec: spec,
Skipped: true,
Error: fmt.Errorf("skipped due to dependency failure"),
Error: errors.New("skipped due to dependency failure"),
}
} else {
result = r.runSpec(ctx, spec)

View file

@ -4,6 +4,7 @@
package repos
import (
"errors"
"fmt"
"os"
"path/filepath"
@ -146,7 +147,7 @@ func FindRegistry(m io.Medium) (string, error) {
}
}
return "", fmt.Errorf("repos.yaml not found")
return "", errors.New("repos.yaml not found")
}
// ScanDirectory creates a Registry by scanning a directory for git repos.

View file

@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"time"
@ -54,7 +55,7 @@ type contentBlock struct {
Text string `json:"text,omitempty"`
Input json.RawMessage `json:"input,omitempty"`
ToolUseID string `json:"tool_use_id,omitempty"`
Content interface{} `json:"content,omitempty"`
Content any `json:"content,omitempty"`
IsError *bool `json:"is_error,omitempty"`
}
@ -156,8 +157,8 @@ func ListSessions(projectsDir string) ([]Session, error) {
sessions = append(sessions, s)
}
sort.Slice(sessions, func(i, j int) bool {
return sessions[i].StartTime.After(sessions[j].StartTime)
slices.SortFunc(sessions, func(a, b Session) int {
return b.StartTime.Compare(a.StartTime) // descending
})
return sessions, nil
@ -340,7 +341,7 @@ func extractToolInput(toolName string, raw json.RawMessage) string {
}
// Fallback: show raw JSON keys
var m map[string]interface{}
var m map[string]any
if json.Unmarshal(raw, &m) == nil {
var parts []string
for k := range m {
@ -353,21 +354,21 @@ func extractToolInput(toolName string, raw json.RawMessage) string {
return ""
}
func extractResultContent(content interface{}) string {
func extractResultContent(content any) string {
switch v := content.(type) {
case string:
return v
case []interface{}:
case []any:
var parts []string
for _, item := range v {
if m, ok := item.(map[string]interface{}); ok {
if m, ok := item.(map[string]any); ok {
if text, ok := m["text"].(string); ok {
parts = append(parts, text)
}
}
}
return strings.Join(parts, "\n")
case map[string]interface{}:
case map[string]any:
if text, ok := v["text"].(string); ok {
return text
}

View file

@ -1,6 +1,7 @@
package session
import (
"errors"
"fmt"
"os"
"os/exec"
@ -10,7 +11,7 @@ import (
// RenderMP4 generates an MP4 video from session events using VHS (charmbracelet).
func RenderMP4(sess *Session, outputPath string) error {
if _, err := exec.LookPath("vhs"); err != nil {
return fmt.Errorf("vhs not installed (go install github.com/charmbracelet/vhs@latest)")
return errors.New("vhs not installed (go install github.com/charmbracelet/vhs@latest)")
}
tape := generateTape(sess, outputPath)

View file

@ -2,6 +2,7 @@ package webview
import (
"context"
"errors"
"fmt"
"time"
)
@ -191,7 +192,7 @@ func (a HoverAction) Execute(ctx context.Context, wv *Webview) error {
}
if elem.BoundingBox == nil {
return fmt.Errorf("element has no bounding box")
return errors.New("element has no bounding box")
}
x := elem.BoundingBox.X + elem.BoundingBox.Width/2
@ -234,7 +235,7 @@ func (a DoubleClickAction) Execute(ctx context.Context, wv *Webview) error {
y := elem.BoundingBox.Y + elem.BoundingBox.Height/2
// Double click sequence
for i := 0; i < 2; i++ {
for i := range 2 {
for _, eventType := range []string{"mousePressed", "mouseReleased"} {
_, err := wv.client.Call(ctx, "Input.dispatchMouseEvent", map[string]any{
"type": eventType,
@ -495,7 +496,7 @@ func (wv *Webview) DragAndDrop(sourceSelector, targetSelector string) error {
return fmt.Errorf("source element not found: %w", err)
}
if source.BoundingBox == nil {
return fmt.Errorf("source element has no bounding box")
return errors.New("source element has no bounding box")
}
target, err := wv.querySelector(ctx, targetSelector)
@ -503,7 +504,7 @@ func (wv *Webview) DragAndDrop(sourceSelector, targetSelector string) error {
return fmt.Errorf("target element not found: %w", err)
}
if target.BoundingBox == nil {
return fmt.Errorf("target element has no bounding box")
return errors.New("target element has no bounding box")
}
// Calculate center points

View file

@ -2,6 +2,7 @@ package webview
import (
"context"
"errors"
"fmt"
"time"
)
@ -42,7 +43,7 @@ func (ah *AngularHelper) waitForAngular(ctx context.Context) error {
return err
}
if !isAngular {
return fmt.Errorf("not an Angular application")
return errors.New("not an Angular application")
}
// Wait for Zone.js stability
@ -278,13 +279,13 @@ func (ah *AngularHelper) GetRouterState() (*AngularRouterState, error) {
}
if result == nil {
return nil, fmt.Errorf("could not get router state")
return nil, errors.New("could not get router state")
}
// Parse result
resultMap, ok := result.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid router state format")
return nil, errors.New("invalid router state format")
}
state := &AngularRouterState{

View file

@ -3,6 +3,7 @@ package webview
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
@ -121,7 +122,7 @@ func NewCDPClient(debugURL string) (*CDPClient, error) {
}
if wsURL == "" {
return nil, fmt.Errorf("no WebSocket URL available")
return nil, errors.New("no WebSocket URL available")
}
// Connect to WebSocket
@ -306,7 +307,7 @@ func (c *CDPClient) NewTab(url string) (*CDPClient, error) {
}
if target.WebSocketDebuggerURL == "" {
return nil, fmt.Errorf("no WebSocket URL for new tab")
return nil, errors.New("no WebSocket URL for new tab")
}
// Connect to new tab

View file

@ -24,6 +24,7 @@ package webview
import (
"context"
"encoding/base64"
"errors"
"fmt"
"sync"
"time"
@ -122,7 +123,7 @@ func New(opts ...Option) (*Webview, error) {
if wv.client == nil {
cancel()
return nil, fmt.Errorf("no debug URL provided; use WithDebugURL option")
return nil, errors.New("no debug URL provided; use WithDebugURL option")
}
// Enable console capture
@ -222,7 +223,7 @@ func (wv *Webview) Screenshot() ([]byte, error) {
dataStr, ok := result["data"].(string)
if !ok {
return nil, fmt.Errorf("invalid screenshot data")
return nil, errors.New("invalid screenshot data")
}
data, err := base64.StdEncoding.DecodeString(dataStr)
@ -263,7 +264,7 @@ func (wv *Webview) GetURL() (string, error) {
url, ok := result.(string)
if !ok {
return "", fmt.Errorf("invalid URL result")
return "", errors.New("invalid URL result")
}
return url, nil
@ -281,7 +282,7 @@ func (wv *Webview) GetTitle() (string, error) {
title, ok := result.(string)
if !ok {
return "", fmt.Errorf("invalid title result")
return "", errors.New("invalid title result")
}
return title, nil
@ -306,7 +307,7 @@ func (wv *Webview) GetHTML(selector string) (string, error) {
html, ok := result.(string)
if !ok {
return "", fmt.Errorf("invalid HTML result")
return "", errors.New("invalid HTML result")
}
return html, nil
@ -520,7 +521,7 @@ func (wv *Webview) evaluate(ctx context.Context, script string) (any, error) {
return nil, fmt.Errorf("JavaScript error: %s", description)
}
}
return nil, fmt.Errorf("JavaScript error")
return nil, errors.New("JavaScript error")
}
// Extract result value
@ -541,12 +542,12 @@ func (wv *Webview) querySelector(ctx context.Context, selector string) (*Element
root, ok := docResult["root"].(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid document root")
return nil, errors.New("invalid document root")
}
rootID, ok := root["nodeId"].(float64)
if !ok {
return nil, fmt.Errorf("invalid root node ID")
return nil, errors.New("invalid root node ID")
}
// Query selector
@ -576,12 +577,12 @@ func (wv *Webview) querySelectorAll(ctx context.Context, selector string) ([]*El
root, ok := docResult["root"].(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid document root")
return nil, errors.New("invalid document root")
}
rootID, ok := root["nodeId"].(float64)
if !ok {
return nil, fmt.Errorf("invalid root node ID")
return nil, errors.New("invalid root node ID")
}
// Query selector all
@ -595,7 +596,7 @@ func (wv *Webview) querySelectorAll(ctx context.Context, selector string) ([]*El
nodeIDs, ok := queryResult["nodeIds"].([]any)
if !ok {
return nil, fmt.Errorf("invalid node IDs")
return nil, errors.New("invalid node IDs")
}
elements := make([]*ElementInfo, 0, len(nodeIDs))
@ -622,7 +623,7 @@ func (wv *Webview) getElementInfo(ctx context.Context, nodeID int) (*ElementInfo
node, ok := descResult["node"].(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid node description")
return nil, errors.New("invalid node description")
}
tagName, _ := node["nodeName"].(string)

View file

@ -47,6 +47,7 @@ package ws
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"sync"
@ -220,7 +221,7 @@ func (h *Hub) Broadcast(msg Message) error {
select {
case h.broadcast <- data:
default:
return fmt.Errorf("broadcast channel full")
return errors.New("broadcast channel full")
}
return nil
}
@ -424,7 +425,7 @@ func (c *Client) writePump() {
// Batch queued messages
n := len(c.send)
for i := 0; i < n; i++ {
for range n {
w.Write([]byte{'\n'})
w.Write(<-c.send)
}