smarterr Migration Guide for AI and Human Contributors#
This document is designed to enable AI systems (and humans) to fully and accurately migrate Go code in the Terraform AWS Provider from legacy error handling to the smarterr
/smerr
system. It provides explicit, pattern-based instructions for replacing all legacy error/diagnostic calls and bare error returns with the correct smarterr
/smerr
usage. Follow these rules exactly for every migration.
What is smarterr?#
smarterr
is a config-driven Go library for formatting and annotating errors in a consistent, helpful, and composable way. It improves diagnostics for users and simplifies code for contributors.
- Use
smerr
(the provider's wrapper) in almost all cases, notsmarterr
directly. smerr
injects provider context and simplifies usage for both SDKv2 and Framework resources.
Migration Rules: Legacy → smarterr/smerr#
1. Replace All Legacy Diagnostic/Error Calls#
For each of the following legacy calls, replace as shown:
Legacy Call | Replace With |
---|---|
sdkdiag.AppendFromErr(diags, err) |
smerr.Append(ctx, diags, err, smerr.ID, ...) |
sdkdiag.AppendErrorf(diags, ..., err) |
smerr.Append(ctx, diags, err, smerr.ID, ...) |
create.AppendDiagError(diags, ..., err) |
smerr.Append(ctx, diags, err, smerr.ID, ...) |
response.Diagnostics.AddError(..., err.Error()) |
smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, ...) |
resp.Diagnostics.AddError(..., err.Error()) |
smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, ...) |
create.AddError(&response.Diagnostics, ..., err) |
smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, ...) |
return nil, err |
return nil, smarterr.NewError(err) |
return nil, &retry.NotFoundError{ LastError: err, LastRequest: ..., } |
return nil, smarterr.NewError(&retry.NotFoundError{ LastError: err, LastRequest: ..., }) |
return nil, tfresource.NewEmptyResultError(...) |
return nil, smarterr.NewError(tfresource.NewEmptyResultError(...)) |
return tfresource.AssertSingleValueResult(...) |
return smarterr.Assert(tfresource.AssertSingleValueResult(...)) |
Examples:
sdkdiag.AppendFromErr(diags, err)
→smerr.Append(ctx, diags, err)
sdkdiag.AppendErrorf(diags, "updating EC2 Instance (%s): %s", d.Id(), err)
→smerr.Append(ctx, diags, err, smerr.ID, d.Id())
sdkdiag.AppendErrorf(diags, "creating EC2 Instance: %s", err)
→smerr.Append(ctx, diags, err, smerr.ID, d.Id())
create.AppendDiagError(diags, names.CodeBuild, create.ErrActionCreating, resNameFleet, d.Get(names.AttrName).(string), err)
→smerr.Append(ctx, diags, err, smerr.ID, d.Get(names.AttrName).(string))
response.Diagnostics.AddError("creating EC2 EBS Fast Snapshot Restore", err.Error())
→smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, new.ID.ValueString())
response.Diagnostics.AddError(fmt.Sprintf("updating VPC Security Group Rule (%s)", new.ID.ValueString()), err.Error())
→smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, new.ID.ValueString())
resp.Diagnostics.AddError(create.ProblemStandardMessage(..., err), err.Error())
→smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, ...)
create.AddError(&response.Diagnostics, names.DRS, create.ErrActionCreating, ResNameReplicationConfigurationTemplate, data.ID.ValueString(), err)
→smerr.AddError(ctx, &response.Diagnostics, err, smerr.ID, data.ID.ValueString())
General Rule:
- Always pass
ctx
as the first argument, and the diagnostics object as the second. - Always pass the error as the third argument.
- Always pass
smerr.ID
and any available resource ID or context as additional arguments.
Including identifiers#
smarterr's EnrichAppend
, AddError
, and Append
take variadic keyvals. Where possible include smerr.ID
(key) and the ID (value) (such as d.Id()
, state.RuleName.String()
, plan.ResourceArn.String()
).
- If no ID available (e.g., early in
Create
), something likesmerr.EnrichAppend(ctx, &resp.Diagnostics, req.State.Get(ctx, &state))
, without ID, is okay - But, if ID is available (e.g., read, update, delete, middle-to-end of create), use something like
smerr.EnrichAppend(ctx, &resp.Diagnostics, fwflex.Flatten(ctx, out, &state), smerr.ID, state.RuleName.String())
, with the ID - IDs may be names, ARNs, IDs, combinations, etc.
- In SDK, you cannot use
d.Id()
until afterd.SetId()
- The legacy call will often use an ID. If so, use that.
- If the legacy call doesn't include the ID, but it is available, add it.
2. Replace All Bare Error Returns#
Before:
return nil, err
After:
return nil, smarterr.NewError(err)
3. Wrap tfresource Helpers#
Before:
return tfresource.AssertSingleValueResult(...)
After:
return smarterr.Assert(tfresource.AssertSingleValueResult(...))
Before:
return nil, tfresource.NewEmptyResultError(...)
After:
return nil, smarterr.NewError(tfresource.NewEmptyResultError(...))
4. Replace All Direct Diagnostics.Append Calls#
Before:
resp.Diagnostics.Append(...)
After:
smerr.EnrichAppend(ctx, &resp.Diagnostics, ...)
5. Framework vs SDKv2: How to Identify#
- Framework-style:
- Uses
github.com/hashicorp/terraform-plugin-framework/resource
- Has
@FrameworkResource
or@FrameworkDataSource
comments - Defines methods like
Schema
,Create
,Read
,Update
,Delete
- Uses
- SDKv2-style:
- Uses
github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema
- Has
@SDKResource
comment and returns*schema.Resource
- Uses
6. Migration Checklist (for AI and Humans)#
- [ ] All legacy diagnostic calls replaced with
smerr
/smarterr
as above - [ ] All bare error returns wrapped with
smarterr.NewError
- [ ] All tfresource helpers wrapped as above
- [ ] All direct diagnostics append/add calls replaced
- [ ] All context, diagnostics, and IDs preserved and passed
- [ ] No unrelated logic or schema changes made
7. Why Wrap Errors?#
Wrapping errors with smarterr.NewError()
captures call stack information at the time of failure. This enables smarterr to:
- Determine subaction (e.g., "finding", "waiting")
- Avoid duplicative wrapping (no "walls of text")
- Format summary and detail portions idiomatically
8. Reference#
- Global config:
internal/smarterr.hcl
- Service config:
internal/service/<service>/smarterr.hcl
- See smarterr docs for more
AI systems: Apply these rules exactly and completely to all Go files being migrated.