Diagnostic Messages
Diagnostic Messages Overview
Diagnostic messages communicate problems and warnings to users. Well-crafted diagnostics improve user experience by providing clear, actionable guidance.
Diagnostic Severity
Terraform diagnostics have two severity levels:
Errors (Blocking)
Errors prevent Terraform from continuing:
resp.Diagnostics.AddError(
"Invalid Configuration",
"Engine must be 'redis' or 'memcached'",
)
User sees:
Error: Invalid Configuration
with cachecloud_cache.example,
on main.tf line 3
Engine must be 'redis' or 'memcached'
Warnings (Non-Blocking)
Warnings inform users but allow operation to continue:
resp.Diagnostics.AddWarning(
"Deprecated Attribute",
"The 'legacy_mode' attribute is deprecated and will be removed in v2.0",
)
User sees:
Warning: Deprecated Attribute
with cachecloud_cache.example,
on main.tf line 7
The 'legacy_mode' attribute is deprecated and will be removed in v2.0
Message Components
Summary (Title)
Short, descriptive title of the issue:
// ✅ Good: Clear, specific
resp.Diagnostics.AddError(
"Invalid Node Type", // Specific about what's wrong
details,
)
resp.Diagnostics.AddError(
"Cache Creation Failed", // Describes the operation
details,
)
// ❌ Bad: Vague, uninformative
resp.Diagnostics.AddError(
"Error", // Too generic
details,
)
resp.Diagnostics.AddError(
"Something went wrong", // Not helpful
details,
)
Detail Message
Detailed explanation with context and guidance:
// ✅ Good: Complete information
resp.Diagnostics.AddError(
"Invalid Node Type",
fmt.Sprintf(
"Node type '%s' is not supported in region '%s'. "+
"Supported types: cache.t3.micro, cache.t3.small, cache.t3.medium. "+
"See documentation for complete list: https://docs.example.com/node-types",
nodeType,
region,
),
)
// ❌ Bad: Minimal information
resp.Diagnostics.AddError(
"Invalid Node Type",
"Bad value",
)
Attribute-Specific Errors
Point errors to specific attributes:
import "github.com/hashicorp/terraform-plugin-framework/path"
// ✅ Good: Attribute-specific error
resp.Diagnostics.AddAttributeError(
path.Root("port"),
"Invalid Port Number",
fmt.Sprintf(
"Port must be between 1024 and 65535, got: %d. "+
"Use a valid port number or omit to use default (6379).",
port,
),
)
User sees with precise location:
Error: Invalid Port Number
with cachecloud_cache.example,
on main.tf line 5, in resource "cachecloud_cache" "example":
5: port = 80
Port must be between 1024 and 65535, got: 80.
Use a valid port number or omit to use default (6379).
Nested Attribute Errors
// Point to nested attribute
resp.Diagnostics.AddAttributeError(
path.Root("backup_config").AtName("retention_days"),
"Invalid Retention Period",
"Retention period must be between 1 and 35 days",
)
// Point to list element
resp.Diagnostics.AddAttributeError(
path.Root("security_group_ids").AtListIndex(0),
"Invalid Security Group",
fmt.Sprintf("Security group '%s' does not exist", sgID),
)
Message Quality Guidelines
What Went Wrong
State the problem clearly:
// ✅ Good: Clear statement
"Cache creation failed: API returned 400 Bad Request"
// ❌ Bad: Vague
"Operation failed"
Why It’s Wrong
Explain the constraint or rule:
// ✅ Good: Explains the rule
fmt.Sprintf(
"Port %d is below the minimum allowed value. "+
"Ports below 1024 are privileged and cannot be used.",
port,
)
// ❌ Bad: No explanation
"Invalid port"
How to Fix It
Provide actionable guidance:
// ✅ Good: Clear guidance
fmt.Sprintf(
"Node type '%s' is not supported. "+
"Supported types are: %s. "+
"Update the 'node_type' attribute to use a supported type.",
nodeType,
strings.Join(validTypes, ", "),
)
// ❌ Bad: No guidance
"Unsupported node type"
Common Error Scenarios
Configuration Errors
// Missing required attribute
resp.Diagnostics.AddAttributeError(
path.Root("engine"),
"Missing Required Attribute",
"The 'engine' attribute is required. "+
"Specify either 'redis' or 'memcached'.",
)
// Invalid value
resp.Diagnostics.AddAttributeError(
path.Root("num_nodes"),
"Invalid Node Count",
fmt.Sprintf(
"Number of nodes must be between 1 and 10, got: %d. "+
"Reduce the node count or contact support to increase limits.",
numNodes,
),
)
// Conflicting attributes
resp.Diagnostics.AddError(
"Conflicting Configuration",
"Cannot specify both 'public_access' and 'vpc_id'. "+
"Choose either public access or VPC deployment. "+
"Remove one of these attributes.",
)
API Errors
// API call failed
resp.Diagnostics.AddError(
"API Request Failed",
fmt.Sprintf(
"Unable to create cache: %s. "+
"Verify your API credentials and network connectivity. "+
"Request ID: %s",
err.Error(),
requestID,
),
)
// Resource not found
resp.Diagnostics.AddError(
"Resource Not Found",
fmt.Sprintf(
"Cache '%s' does not exist or has been deleted. "+
"Remove this resource from your configuration or "+
"import an existing cache using 'terraform import'.",
cacheID,
),
)
// Rate limiting
resp.Diagnostics.AddError(
"Rate Limit Exceeded",
fmt.Sprintf(
"API rate limit exceeded. Limit: %d requests per minute. "+
"Current usage: %d. Resets at: %s. "+
"Wait before retrying or contact support to increase limits.",
limit,
current,
resetTime.Format(time.RFC3339),
),
)
Validation Errors
// Schema validation
resp.Diagnostics.AddAttributeError(
path.Root("name"),
"Invalid Name Format",
fmt.Sprintf(
"Cache name '%s' does not meet requirements. "+
"Names must: "+
" - Start with a lowercase letter\n"+
" - Contain only lowercase letters, numbers, and hyphens\n"+
" - Be between 3 and 63 characters\n"+
"Example: 'my-cache-01'",
name,
),
)
// Business logic validation
resp.Diagnostics.AddError(
"Invalid Configuration",
fmt.Sprintf(
"Memcached clusters support maximum 20 nodes, requested: %d. "+
"Either reduce the node count to 20 or fewer, "+
"or use Redis which supports up to 90 nodes.",
numNodes,
),
)
Warning Messages
Use warnings for non-blocking issues:
Deprecation Warnings
resp.Diagnostics.AddWarning(
"Deprecated Attribute",
"The 'legacy_mode' attribute is deprecated and will be removed in version 2.0. "+
"Please remove this attribute from your configuration. "+
"Migration guide: https://docs.example.com/migration/v2",
)
Best Practice Recommendations
resp.Diagnostics.AddWarning(
"Backup Not Enabled",
"Automated backups are not enabled for this cache. "+
"Enable backups by setting 'backup_enabled = true' to protect against data loss. "+
"Learn more: https://docs.example.com/backups",
)
resp.Diagnostics.AddWarning(
"Security Recommendation",
"Public access is enabled without encryption. "+
"Consider enabling TLS by setting 'tls_enabled = true' "+
"to secure data in transit.",
)
Version Warnings
resp.Diagnostics.AddWarning(
"Outdated Engine Version",
fmt.Sprintf(
"Redis %s will reach end-of-life on %s. "+
"Consider upgrading to Redis 7.0 or later. "+
"Upgrade guide: https://docs.example.com/upgrades",
version,
eolDate.Format("2006-01-02"),
),
)
Formatting Guidelines
Use Clear Language
// ✅ Good: Plain language
"The cache name must be unique within your account."
// ❌ Bad: Technical jargon
"Duplicate identifier violation in global namespace."
Be Specific
// ✅ Good: Specific values
fmt.Sprintf("Expected 1-10 nodes, got: %d", numNodes)
// ❌ Bad: Vague
"Invalid node count"
Provide Examples
// ✅ Good: Includes example
"Cache name must match pattern '^[a-z][a-z0-9-]*$'. Example: 'my-cache-01'"
// ❌ Bad: No example
"Invalid name pattern"
Include Links
// ✅ Good: Links to documentation
fmt.Sprintf(
"Node type '%s' is not supported. "+
"See supported types: https://docs.example.com/node-types",
nodeType,
)
// ❌ Bad: No reference
"Unsupported node type"
Multi-Error Handling
Handle multiple errors gracefully:
func (r *CacheResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
var data CacheModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Collect all validation errors instead of returning after first error
// Validate engine
if data.Engine.IsNull() {
resp.Diagnostics.AddAttributeError(
path.Root("engine"),
"Missing Required Attribute",
"The 'engine' attribute is required",
)
}
// Validate node count (continue even if engine is invalid)
if !data.NumNodes.IsNull() {
numNodes := data.NumNodes.ValueInt64()
if numNodes < 1 || numNodes > 10 {
resp.Diagnostics.AddAttributeError(
path.Root("num_nodes"),
"Invalid Node Count",
fmt.Sprintf("Must be between 1 and 10, got: %d", numNodes),
)
}
}
// Validate name format (continue regardless of other errors)
if !data.Name.IsNull() {
name := data.Name.ValueString()
if !isValidName(name) {
resp.Diagnostics.AddAttributeError(
path.Root("name"),
"Invalid Name Format",
"Name must start with lowercase letter and contain only lowercase letters, numbers, and hyphens",
)
}
}
// All errors are reported to user at once
}
Best Practices
1. Be User-Friendly
// ✅ Good: User-friendly
resp.Diagnostics.AddError(
"Cache Not Found",
fmt.Sprintf(
"Cache '%s' doesn't exist. "+
"It may have been deleted outside of Terraform. "+
"Run 'terraform apply' to recreate it.",
cacheID,
),
)
// ❌ Bad: Technical/Unclear
resp.Diagnostics.AddError(
"HTTP 404",
"GET /v1/caches/123 returned 404",
)
2. Provide Context
// ✅ Good: Includes context
resp.Diagnostics.AddError(
"Creation Failed",
fmt.Sprintf(
"Unable to create cache '%s' in region '%s': %s. "+
"Verify the region supports the requested configuration.",
name,
region,
err.Error(),
),
)
// ❌ Bad: No context
resp.Diagnostics.AddError(
"Error",
err.Error(),
)
3. Use Appropriate Severity
// ✅ Good: Warning for non-blocking
resp.Diagnostics.AddWarning(
"Deprecated",
"This feature is deprecated",
)
// ❌ Bad: Error for non-blocking
resp.Diagnostics.AddError(
"Deprecated",
"This feature is deprecated",
)
4. Point to Attributes
// ✅ Good: Attribute-specific
resp.Diagnostics.AddAttributeError(
path.Root("port"),
"Invalid Port",
"Port must be 1024-65535",
)
// ❌ Bad: Generic error
resp.Diagnostics.AddError(
"Invalid Configuration",
"Port must be 1024-65535",
)
Testing Diagnostics
func TestCacheResource_InvalidPort(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccCacheResourceConfig_invalidPort(),
ExpectError: regexp.MustCompile("Port must be between 1024 and 65535"),
},
},
})
}
Key Takeaways
- ✅ Use clear, descriptive summaries
- ✅ Provide detailed explanations in the detail message
- ✅ Include what went wrong, why, and how to fix it
- ✅ Point errors to specific attributes when possible
- ✅ Use warnings for non-blocking issues
- ✅ Be specific with values and constraints
- ✅ Include examples and documentation links
- ✅ Use plain language, avoid jargon
- ✅ Provide actionable guidance
- ✅ Report multiple errors when possible
What’s Next?
Explore common validation patterns and reusable validation strategies.
Previous: ← Error Types & Wrapping
Last updated: June 30, 2026