Diagnostic Messages

7 minute read

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"
// ✅ 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.

Next: Validation Patterns →


Previous: ← Error Types & Wrapping

Last updated: June 30, 2026

Updated: