smarterr

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2025 License: MPL-2.0 Imports: 10 Imported by: 0

README

smarterr

Declarative, layered, and maintainable error messages for Go.

With a single line of code:

return smarterr.Append(ctx, diags, err, "id", "r-1234567890")

smarterr uses configuration—not code changes—to split an incoming error into three output channels:

  1. Error summary (for users)

    creating CloudWatch Composite Alarm
    
  2. Error detail with a suggested fix (for users)

    ID: r-1234567890
    Cause: operation error CloudWatch: ModifyServerlessCache
    If you are trying to modify a serverless cache, please use the
    `aws_cloudwatch_serverless_cache` resource instead of
    `aws_cloudwatch_log_group`.
    
  3. Log (tflog or Go log)

    creating CloudWatch Composite Alarm (ID r-1234567890): operation error CloudWatch: ModifyServerlessCache, https response error StatusCode: 400, RequestID: 9c9c8b2c-d71b-4717-b62a-68cfa4b18aa9, InvalidParameterCombination: No
    

smarterr lets you define, update, and standardize error output for thousands of call sites—using Config, not code. Evolve your error messages and formatting without cross-codebase refactors. Both developers and users get clean diagnostics.

smarterr: Library and command line interface

smarterr consists of two components:

  • Go Library: Integrate into your application or provider code to format errors, emit diagnostics, and control runtime logging.
  • Command-Line Tool (CLI): Use during development or CI to check configuration files and inspect merged output to catch issues.

Use the library to power smarterr behavior at runtime. Use the CLI to debug and verify configs before they ship.


Command-line installation

You can install the smarterr CLI with Go:

go install github.com/YakDriver/smarterr/cmd/smarterr@latest

This will install the smarterr binary in your $GOPATH/bin or $HOME/go/bin.


GitHub action

You can use the GitHub Action to check Config during CI checks.

For example:

- uses: YakDriver/check-smarterr-config@latest
  name: Check smarterr config
  with:
    base-dir: './internal'

Template types and usage

smarterr supports two main template types for customizing diagnostic output:

  • Error templates: error_summary and error_detail
    • Used when formatting diagnostics from Go errors (for example, via AddError or Append).
  • Diagnostic templates: diagnostic_summary and diagnostic_detail
    • Used when enriching framework-generated diagnostics (for example, via EnrichAppend).

Note: smarterr outputs diagnostics. The template name refers to the input type (error vs. diagnostic).

Function-to-template mapping:

  • AddError and Append use error_summary and error_detail.
  • EnrichAppend uses diagnostic_summary and diagnostic_detail.

smarterr falls back to the original error or diagnostic content if you don't define templates.

Why smarterr

  • For developers:
    • No more hunting down and updating error messages in thousands of places.
    • Consistent, high-quality error output everywhere, driven by Config.
    • Layered, declarative configuration—like you expect from modern tools—applied to error handling as a library.
    • Evolve your error messages and formatting without cross-codebase refactors.
  • For users:
    • Cleaner, more helpful, and consistent error messages.
    • Easier to understand, context-aware diagnostics to act on.

In a large project (like the Terraform AWS Provider, with ~15,000 error sites), smarterr lets you manage error output centrally and declaratively—an evolutionary step toward declarative development.


Quickstart

Library usage
  1. Embed your Config:
//go:embed service/smarterr.hcl
//go:embed service/*/smarterr.hcl
var SmarterrFS embed.FS

var smarterrInitOnce sync.Once

func init() {
  smarterrInitOnce.Do(func() {
    smarterr.SetLogger(smarterr.TFLogLogger{})
    smarterr.SetFS(&smarterr.WrappedFS{FS: &SmarterrFS}, "dir/where/files/are/embedded/such/as/internal")
  })
}
  1. Call smarterr in your error handling:

    smarterr.AddError(ctx, diags, err, "id", id) // uses error_summary/error_detail
    // or for SDK diagnostics:
    diags = smarterr.Append(ctx, diags, err, "id", id) // uses error_summary/error_detail
    // or to enrich framework diagnostics:
    smarterr.EnrichAppend(ctx, &diags, incoming, "id", id) // uses diagnostic_summary/diagnostic_detail
    
Command-line usage

The smarterr CLI lets you check and inspect your configuration files.

Commands
  • Show the effective merged configuration:

    smarterr config --base-dir /path/to/project --start-dir /path/to/project/internal/service
    # or with short flags:
    smarterr config -b /path/to/project -d /path/to/project/internal/service
    

    This prints the merged Config (after layering/merging) that would apply at the given directory.

  • Check your configuration:

    smarterr check --base-dir /path/to/project --start-dir /path/to/project/internal/service
    # or with short flags:
    smarterr check -b /path/to/project -d /path/to/project/internal/service
    

    This checks for parse errors, missing fields, schema issues, and other problems. smarterr returns a non-zero exit code if it finds errors.

    Flags:

    • --quiet, -q: Output errors (suppresses merged Config and warnings)
    • --silent, -S: No output, just an exit code (non-zero if errors)
    • --debug, -D: Enable debug output

Example Configuration

Here’s an example smarterr.hcl for a Terraform provider. For details, see the full Config schema.

#
template "error_summary" {
  format = "{{.happening}} {{.service}} {{.resource}} ({{.identifier}}): {{.error}}"
}

template "diagnostic_summary" {
  format = "{{.happening}} {{.service}} {{.resource}}: {{.diag.summary}}"
}

token "happening" {
  stack_matches = [
    "create",
  ]
}

token "service" {
  parameter = "service"
}

token "resource" {
  context = "resource_name"
}

token "identifier" {
  arg = "id"
}

token "error" {
  source = "error"
  transforms = [
    "clean_aws_error"
  ]
}

token "diag" {
  source = "diagnostic"
  field_transforms = {
    summary = ["upper"]
    detail  = ["lower"]
  }
}

stack_match "create" {
  called_from = "resource[a-zA-Z0-9]*Create"
  display     = "creating"
}

parameter "service" {
  value = "CloudWatch"
}

transform "clean_aws_error" {
  step "remove" {
    regex = "RequestID: [a-z0-9-]+,"
  }
  step "remove" {
    value = "InvalidParameterCombination: No"
  }
  step "remove" {
    value = "https response error StatusCode: 400"
  }
  step "strip_suffix" {
    value = ","
    recurse = true
  }
}

Diagnostic enrichment & structured tokens

smarterr supports Config-driven enrichment of both errors and framework-generated diagnostics (such as value conversion errors in Terraform Plugin Framework) using a structured diagnostic token.

Diagnostic token usage
  • Define a token with source = "diagnostic" to expose a structured token with fields (for example, .diag.summary, .diag.detail, .diag.severity).
  • Use field_transforms to apply transforms to individual fields of the diagnostic token.

Example:

token "diag" {
  source = "diagnostic"
  field_transforms = {
    summary = ["upper"]
    detail  = ["lower"]
  }
}

In your template, access fields as {{.diag.summary}}, {{.diag.detail}}, etc.

Example template:

template "diagnostic_summary" {
  format = "{{.happening}} {{.service}} {{.resource}}: {{.diag.summary}}"
}

template "diagnostic_detail" {
  format = "ID: {{.identifier}}\nCause: {{.diag.detail}}"
}

This enables context-rich diagnostics for both errors and framework-generated issues, all managed declaratively via Config.


Learn more


FAQ

Q: Do end users need to configure anything? A: No. Developers of host applications embed the Config. End users get better errors.

Q: Can you update error messages without code changes? A: Yes! Just update your Config and re-embed.

Q: What about missing or broken Config? A: smarterr falls back to the original error, never panics. Use smarterr check and enable debug mode to find issues.

For more, see the Diagnostics & Fallbacks doc.

Documentation

Index

Constants

View Source
const (
	ID           = "id"
	ResourceName = "resource_name"
	ServiceName  = "service_name"

	DiagnosticSummaryKey = "diagnostic_summary"
	DiagnosticDetailKey  = "diagnostic_detail"
	ErrorSummaryKey      = "error_summary"
	ErrorDetailKey       = "error_detail"
	LogErrorKey          = "log_error"
	LogWarnKey           = "log_warn"
	LogInfoKey           = "log_info"

	SeverityError   = internal.SeverityError
	SeverityWarning = internal.SeverityWarning
	SeverityInfo    = internal.SeverityInfo
)

Variables

View Source
var Debugf = internal.Debugf

Re-export internal.Debugf for internal debugging

View Source
var NewWrappedFS = internal.NewWrappedFS

Functions

func AddEnrich added in v0.7.0

func AddEnrich(ctx context.Context, existing *fwdiag.Diagnostics, incoming fwdiag.Diagnostics, keyvals ...any)

AddEnrich is a plugin Framework helper function that enriches diagnostics with smarterr information. This will not change the severity of either incoming or existing diagnostics, but will change the summary and detail of _incoming_ diagnostics only with smarterr information. Mutates the diagnostics in place via pointer, matching the framework pattern.

Template usage:

  • If you want to customize the output for framework-generated diagnostics (e.g., value conversion errors), define `diagnostic_summary` and `diagnostic_detail` templates in your config. These will be used by AddEnrich.
  • If these templates are not defined, the original diagnostic summary and detail are used.
  • Note: All output is a diagnostic; the template name refers to the input type (error vs. diagnostic).

func AddError added in v0.2.0

func AddError(ctx context.Context, diags *fwdiag.Diagnostics, err error, keyvals ...any)

AddError adds a formatted error to Terraform Plugin Framework diagnostics. Mutates the diagnostics in place via pointer, matching the framework pattern.

Template usage:

  • To customize the output for errors (Go error values), define `error_summary` and `error_detail` templates in your config.
  • These templates control the summary and detail for diagnostics created from errors via AddError.
  • If these templates are not defined, a fallback using the original error is used.
  • Note: All output is a diagnostic; the template name refers to the input type (error vs. diagnostic).

func AddOne added in v0.7.0

func AddOne(ctx context.Context, existing *fwdiag.Diagnostics, incoming fwdiag.Diagnostic, keyvals ...any)

AddOne appends a single diagnostic to existing Framework diagnostics with enrichment

func Append added in v0.2.0

func Append(ctx context.Context, diags sdkdiag.Diagnostics, err error, keyvals ...any) sdkdiag.Diagnostics

Append adds a formatted error to Terraform Plugin SDK diagnostics and returns the updated diagnostics slice.

Template usage:

  • To customize the output for errors (Go error values), define `error_summary` and `error_detail` templates in your config.
  • These templates control the summary and detail for diagnostics created from errors via Append.
  • If these templates are not defined, a fallback using the original error is used.
  • Note: All output is a diagnostic; the template name refers to the input type (error vs. diagnostic).

func AppendEnrich added in v0.7.0

func AppendEnrich(ctx context.Context, existing sdkdiag.Diagnostics, incoming sdkdiag.Diagnostics, keyvals ...any) sdkdiag.Diagnostics

AppendEnrich appends incoming SDK diagnostics to existing SDK diagnostics with enrichment

func AppendOne added in v0.7.0

func AppendOne(ctx context.Context, existing sdkdiag.Diagnostics, incoming sdkdiag.Diagnostic, keyvals ...any) sdkdiag.Diagnostics

AppendOne appends a single diagnostic to existing SDK diagnostics with enrichment

func Assert added in v0.2.0

func Assert[T any](val T, err error) (T, error)

Assert wraps a call returning (T, error) with smarterr.NewError on failure. Go doesn't yet support generics-based tuple unpacking, so this form works well for now.

func EnrichAppend deprecated added in v0.2.0

func EnrichAppend(ctx context.Context, existing *fwdiag.Diagnostics, incoming fwdiag.Diagnostics, keyvals ...any)

EnrichAppend is an alias for AddEnrich to maintain backward compatibility.

Deprecated: Use AddEnrich instead to align with Framework "Add" verb convention

func Errorf added in v0.2.0

func Errorf(format string, args ...any) error

Errorf formats according to a format specifier and returns a smarterr-enriched error. It behaves like fmt.Errorf, but also captures contextual metadata based on the call site. This ensures consistent DX and structured diagnostics with minimal developer effort.

Example:

return smarterr.Errorf("unexpected result for alarm %q", name)

func NewError added in v0.2.0

func NewError(err error) error

NewError wraps an existing error with smarterr metadata derived from the call stack. It automatically annotates the error with context-aware information (e.g., sub-action) without requiring developer input, reducing fragility and promoting consistent error enrichment.

Use NewError at the site where an error is first returned or recognized. The resulting error can be passed directly to smarterr.Append or smarterr.AddError without needing manual WithField-style annotation.

Example:

return nil, smarterr.NewError(err)

func SetFS

func SetFS(fs FileSystem, baseDir string)

SetFS allows the host application to provide a FileSystem implementation and the base directory for path normalization.

func SetLogger

func SetLogger(logger Logger)

SetLogger sets the global user-facing logger for smarterr.

Types

type ContextKey added in v0.4.0

type ContextKey internal.ContextKey

type Error added in v0.2.0

type Error struct {
	Err           error             // The original or wrapped error
	Message       string            // Optional developer-provided message (from Errorf)
	Annotations   map[string]string // Arbitrary key-value annotations (e.g., subaction, resource_id)
	CapturedStack []runtime.Frame   // Captured call stack for stack matching
}

Error is the enriched smarterr error type. It wraps a base error and includes structured annotations that can be used by Append or AddError to construct clear, user-friendly diagnostics.

func (*Error) Error added in v0.2.0

func (e *Error) Error() string

Error implements the error interface.

func (*Error) Stack added in v0.2.0

func (e *Error) Stack() []runtime.Frame

Stack returns the captured call stack frames.

func (*Error) Unwrap added in v0.2.0

func (e *Error) Unwrap() error

Unwrap returns the underlying error.

type FileSystem

type FileSystem = internal.FileSystem

type Logger

type Logger interface {
	Debug(ctx context.Context, msg string, keyvals map[string]any)
	Info(ctx context.Context, msg string, keyvals map[string]any)
	Warn(ctx context.Context, msg string, keyvals map[string]any)
	Error(ctx context.Context, msg string, keyvals map[string]any)
}

Logger is the interface for user-facing logs emitted by smarterr. These logs are intended for consumers of smarterr, not for internal debugging.

type StdLogger

type StdLogger struct{}

StdLogger is an adapter that emits user-facing logs using the standard Go log package.

func (StdLogger) Debug

func (l StdLogger) Debug(ctx context.Context, msg string, keyvals map[string]any)

func (StdLogger) Error

func (l StdLogger) Error(ctx context.Context, msg string, keyvals map[string]any)

func (StdLogger) Info

func (l StdLogger) Info(ctx context.Context, msg string, keyvals map[string]any)

func (StdLogger) Warn

func (l StdLogger) Warn(ctx context.Context, msg string, keyvals map[string]any)

type TFLogLogger

type TFLogLogger struct{}

TFLogLogger is an adapter that emits user-facing logs using Terraform's tflog package.

func (TFLogLogger) Debug

func (l TFLogLogger) Debug(ctx context.Context, msg string, keyvals map[string]any)

func (TFLogLogger) Error

func (l TFLogLogger) Error(ctx context.Context, msg string, keyvals map[string]any)

func (TFLogLogger) Info

func (l TFLogLogger) Info(ctx context.Context, msg string, keyvals map[string]any)

func (TFLogLogger) Warn

func (l TFLogLogger) Warn(ctx context.Context, msg string, keyvals map[string]any)

type WrappedFS

type WrappedFS = internal.WrappedFS

Directories

Path Synopsis
cmd
smarterr command
discovery.go Config discovery and loading logic for smarterr
discovery.go Config discovery and loading logic for smarterr

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL