mirror of
https://github.com/aykhans/sarin.git
synced 2026-02-28 14:59:14 +00:00
Introduce structured error types and bump Go/linter versions
Replace ad-hoc fmt.Errorf/errors.New calls with typed error structs across config, sarin, and script packages to enable type-based error handling. Add script-specific error handlers in CLI entry point. Fix variable shadowing bug in Worker for scriptTransformer. Bump Go to 1.25.7 and golangci-lint to v2.8.0.
This commit is contained in:
@@ -638,10 +638,16 @@ func parseConfigFile(configFile types.ConfigFile, maxDepth int) (*Config, error)
|
||||
// - Escaped "@": strings starting with "@@" (literal "@" at start)
|
||||
// - File reference: "@/path/to/file" or "@./relative/path"
|
||||
// - URL reference: "@http://..." or "@https://..."
|
||||
//
|
||||
// It can return the following errors:
|
||||
// - types.ErrScriptEmpty
|
||||
// - types.ErrScriptSourceEmpty
|
||||
// - types.ErrScriptURLNoHost
|
||||
// - types.URLParseError
|
||||
func validateScriptSource(script string) error {
|
||||
// Empty script is invalid
|
||||
if script == "" {
|
||||
return errors.New("script cannot be empty")
|
||||
return types.ErrScriptEmpty
|
||||
}
|
||||
|
||||
// Not a file/URL reference - it's an inline script
|
||||
@@ -658,17 +664,17 @@ func validateScriptSource(script string) error {
|
||||
source := script[1:] // Remove the @ prefix
|
||||
|
||||
if source == "" {
|
||||
return errors.New("script source cannot be empty after @")
|
||||
return types.ErrScriptSourceEmpty
|
||||
}
|
||||
|
||||
// Check if it's a URL
|
||||
// Check if it's a http(s) URL
|
||||
if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") {
|
||||
parsedURL, err := url.Parse(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid URL: %w", err)
|
||||
return types.NewURLParseError(source, err)
|
||||
}
|
||||
if parsedURL.Host == "" {
|
||||
return errors.New("URL must have a host")
|
||||
return types.ErrScriptURLNoHost
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@ func (parser ConfigFileParser) Parse() (*Config, error) {
|
||||
}
|
||||
|
||||
// fetchFile retrieves file contents from a local path or HTTP/HTTPS URL.
|
||||
// It can return the following errors:
|
||||
// - types.FileReadError
|
||||
// - types.HTTPFetchError
|
||||
// - types.HTTPStatusError
|
||||
func fetchFile(ctx context.Context, src string) ([]byte, error) {
|
||||
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
|
||||
return fetchHTTP(ctx, src)
|
||||
@@ -57,25 +61,28 @@ func fetchFile(ctx context.Context, src string) ([]byte, error) {
|
||||
}
|
||||
|
||||
// fetchHTTP downloads file contents from an HTTP/HTTPS URL.
|
||||
// It can return the following errors:
|
||||
// - types.HTTPFetchError
|
||||
// - types.HTTPStatusError
|
||||
func fetchHTTP(ctx context.Context, url string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
return nil, types.NewHTTPFetchError(url, err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch file: %w", err)
|
||||
return nil, types.NewHTTPFetchError(url, err)
|
||||
}
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to fetch file: HTTP %d %s", resp.StatusCode, resp.Status)
|
||||
return nil, types.NewHTTPStatusError(url, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
return nil, types.NewHTTPFetchError(url, err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
@@ -83,19 +90,21 @@ func fetchHTTP(ctx context.Context, url string) ([]byte, error) {
|
||||
|
||||
// fetchLocal reads file contents from the local filesystem.
|
||||
// It resolves relative paths from the current working directory.
|
||||
// It can return the following errors:
|
||||
// - types.FileReadError
|
||||
func fetchLocal(src string) ([]byte, error) {
|
||||
path := src
|
||||
if !filepath.IsAbs(src) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get working directory: %w", err)
|
||||
return nil, types.NewFileReadError(src, err)
|
||||
}
|
||||
path = filepath.Join(pwd, src)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file: %w", err)
|
||||
return nil, types.NewFileReadError(path, err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"go.aykhans.me/sarin/internal/types"
|
||||
)
|
||||
|
||||
// It can return the following errors:
|
||||
// - types.TemplateParseError
|
||||
func validateTemplateString(value string, funcMap template.FuncMap) error {
|
||||
if value == "" {
|
||||
return nil
|
||||
@@ -15,7 +17,7 @@ func validateTemplateString(value string, funcMap template.FuncMap) error {
|
||||
|
||||
_, err := template.New("").Funcs(funcMap).Parse(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("template parse error: %w", err)
|
||||
return types.NewTemplateParseError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user