Validation & Error Handling
Module Overview
Validation and error handling are critical for creating reliable, user-friendly Terraform providers. This module covers comprehensive strategies for validating inputs, handling errors gracefully, and providing clear diagnostic messages to users.
Why Validation Matters
Proper validation provides several key benefits:
1. Early Error Detection
Catch configuration errors during plan phase rather than apply:
# Invalid configuration caught during plan
resource "cachecloud_cache" "example" {
name = "my-cache"
engine = "invalid" # Error: engine must be 'redis' or 'memcached'
node_type = "cache.t3.micro"
}
2. Clear Error Messages
Guide users to correct their configuration:
Error: Invalid Cache Engine
with cachecloud_cache.example,
on main.tf line 3, in resource "cachecloud_cache" "example":
3: engine = "invalid"
Engine must be either 'redis' or 'memcached', got: invalid
3. Prevent Invalid API Calls
Validate before making expensive API calls:
// Validate input before API call
if cache.NumNodes < 1 || cache.NumNodes > 10 {
resp.Diagnostics.AddError(
"Invalid Node Count",
fmt.Sprintf("num_nodes must be between 1 and 10, got: %d", cache.NumNodes),
)
return
}
// Only make API call with valid input
result, err := r.client.CreateCache(ctx, input)
Validation Levels
Terraform Plugin Framework provides multiple validation layers:
Schema-Level Validation
Built-in and custom validators attached to attributes:
Schema: schema.Schema{
Attributes: map[string]schema.Attribute{
"engine": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.OneOf("redis", "memcached"),
},
},
"port": schema.Int64Attribute{
Optional: true,
Validators: []validator.Int64{
int64validator.Between(1024, 65535),
},
},
},
}
Plan-Time Validation
Complex validation requiring multiple attributes:
func (r *CacheResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
var data CacheModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
// Validate engine-specific constraints
if data.Engine.ValueString() == "memcached" && data.NumNodes.ValueInt64() > 20 {
resp.Diagnostics.AddError(
"Invalid Configuration",
"Memcached clusters support maximum 20 nodes",
)
}
}
API-Level Validation
Server-side validation from API responses:
result, err := r.client.CreateCache(ctx, input)
if err != nil {
if client.IsValidationError(err) {
resp.Diagnostics.AddError(
"Configuration Rejected by API",
fmt.Sprintf("The cache service rejected the configuration: %s", err.Error()),
)
}
return
}
Error Handling Strategies
Error Types
Classify errors for appropriate handling:
// User configuration errors
resp.Diagnostics.AddAttributeError(
path.Root("engine"),
"Invalid Engine",
"Engine must be 'redis' or 'memcached'",
)
// API errors
resp.Diagnostics.AddError(
"API Request Failed",
fmt.Sprintf("Failed to create cache: %s", err.Error()),
)
// Warnings (non-blocking)
resp.Diagnostics.AddWarning(
"Deprecated Configuration",
"The 'legacy_mode' attribute is deprecated and will be removed in v2.0",
)
Error Context
Provide helpful context for troubleshooting:
resp.Diagnostics.AddError(
"Cache Creation Failed",
fmt.Sprintf(
"Unable to create cache '%s' with node type '%s'. "+
"Verify the node type is supported in your region. "+
"API Error: %s",
data.Name.ValueString(),
data.NodeType.ValueString(),
err.Error(),
),
)
Diagnostic Messages
Terraform uses diagnostics to communicate with users:
Error Severity Levels
// Error: Blocks operation, must be fixed
resp.Diagnostics.AddError("title", "details")
// Warning: Allows operation, should be addressed
resp.Diagnostics.AddWarning("title", "details")
// Attribute Error: Points to specific attribute
resp.Diagnostics.AddAttributeError(
path.Root("attribute_name"),
"title",
"details",
)
Message Quality
Good error messages include:
- What went wrong: Clear statement of the problem
- Why it’s wrong: Explanation of the constraint
- How to fix it: Guidance for resolution
// ❌ Bad
resp.Diagnostics.AddError("Error", "Invalid value")
// ✅ Good
resp.Diagnostics.AddAttributeError(
path.Root("port"),
"Invalid Port Number",
fmt.Sprintf(
"Port must be between 1024 and 65535, got: %d. "+
"Use a port number in the valid range or omit to use default (6379).",
port,
),
)
Module Chapters
This module covers validation and error handling in depth:
Chapter 1: Schema-Level Validation →
Learn to use built-in validators and create custom validators for attribute-level validation.
Topics:
- Built-in validators (OneOf, Between, RegexMatches)
- Custom validator implementation
- Combining multiple validators
- Conditional validation
- Validator testing
Chapter 2: Plan-Time Validation →
Implement complex validation requiring multiple attributes or external data.
Topics:
- ValidateConfig interface
- Cross-attribute validation
- Validating attribute combinations
- Conditional requirements
- Plan modifiers with validation
Chapter 3: Error Types & Wrapping →
Design error type hierarchies and wrap errors with context.
Topics:
- Custom error types
- Error wrapping patterns
- Error classification
- Sentinel errors
- Error inspection
Chapter 4: Diagnostic Messages →
Write clear, actionable diagnostic messages for users.
Topics:
- Error vs warning diagnostics
- Attribute-specific errors
- Error message formatting
- Providing resolution guidance
- Detail vs summary
Chapter 5: Validation Patterns →
Common validation scenarios and reusable patterns.
Topics:
- String validation (email, URL, regex)
- Number ranges and constraints
- List and map validation
- Conditional validation
- Multi-attribute validation
Chapter 6: API Error Handling →
Handle API errors gracefully and translate them for users.
Topics:
- Parsing API errors
- Rate limiting and retries
- Transient vs permanent errors
- Error recovery strategies
- Timeout handling
Chapter 7: Validation Lab →
Build comprehensive validation for a resource with hands-on exercises.
Topics:
- Implementing custom validators
- Multi-level validation
- Error message design
- Testing validation logic
- Complete validation suite
Chapter 8: Validation Practice →
Practice validation skills with exercises and challenges.
Topics:
- Validation exercises
- Error handling challenges
- Diagnostic message improvement
- Real-world scenarios
- Practice solutions
Best Practices
1. Validate Early
// ✅ Good: Validate at schema level
"engine": schema.StringAttribute{
Required: true,
Validators: []validator.String{
stringvalidator.OneOf("redis", "memcached"),
},
}
// ❌ Bad: Validate during API call
result, err := r.client.CreateCache(ctx, input)
if err != nil && strings.Contains(err.Error(), "invalid engine") {
// Too late - already made API call
}
2. Provide Clear Messages
// ✅ Good: Specific, actionable message
resp.Diagnostics.AddError(
"Invalid Node Type",
fmt.Sprintf(
"Node type '%s' is not supported. "+
"Supported types: cache.t3.micro, cache.t3.small, cache.t3.medium. "+
"Check documentation for complete list: https://docs.example.com/node-types",
nodeType,
),
)
// ❌ Bad: Vague message
resp.Diagnostics.AddError("Error", "Invalid node type")
3. Use Appropriate Severity
// ✅ Good: Error for blocking issues
if cache.Engine == "" {
resp.Diagnostics.AddError("Required", "Engine is required")
}
// ✅ Good: Warning for deprecation
if cache.LegacyMode {
resp.Diagnostics.AddWarning(
"Deprecated Attribute",
"legacy_mode is deprecated and will be removed in v2.0",
)
}
// ❌ Bad: Error for non-blocking issue
if cache.LegacyMode {
resp.Diagnostics.AddError("Deprecated", "Don't use legacy_mode")
}
4. Point to Specific Attributes
// ✅ Good: Attribute-specific error
resp.Diagnostics.AddAttributeError(
path.Root("num_nodes"),
"Invalid Node Count",
"num_nodes must be between 1 and 10",
)
// ❌ Bad: Generic error
resp.Diagnostics.AddError(
"Invalid Configuration",
"num_nodes must be between 1 and 10",
)
Validation Checklist
When implementing validation:
- ✅ Validate at the earliest possible stage
- ✅ Use schema validators for simple rules
- ✅ Implement ValidateConfig for complex rules
- ✅ Provide clear, actionable error messages
- ✅ Include context and resolution guidance
- ✅ Point errors to specific attributes
- ✅ Use appropriate diagnostic severity
- ✅ Test validation with invalid inputs
- ✅ Document validation rules
- ✅ Handle API errors gracefully
What You’ll Learn
By the end of this module, you’ll be able to:
- ✅ Implement validators at multiple levels
- ✅ Create custom validators for domain-specific rules
- ✅ Write clear, user-friendly error messages
- ✅ Handle API errors gracefully
- ✅ Classify and wrap errors appropriately
- ✅ Validate complex attribute combinations
- ✅ Test validation logic comprehensively
- ✅ Follow validation best practices
Quick Reference
Common Validators
import (
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
)
// String validators
stringvalidator.OneOf("value1", "value2")
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-z]+$`), "message")
stringvalidator.LengthBetween(1, 100)
// Number validators
int64validator.Between(1, 100)
int64validator.AtLeast(1)
int64validator.AtMost(100)
// List validators
listvalidator.SizeAtLeast(1)
listvalidator.SizeAtMost(10)
listvalidator.SizeBetween(1, 10)
Error Handling Template
func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// 1. Get configuration
var data Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// 2. Validate (if needed beyond schema)
if err := r.validate(&data); err != nil {
resp.Diagnostics.AddError("Validation Failed", err.Error())
return
}
// 3. Call API
result, err := r.client.Create(ctx, input)
if err != nil {
resp.Diagnostics.AddError(
"API Call Failed",
fmt.Sprintf("Unable to create resource: %s", err.Error()),
)
return
}
// 4. Save state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
Getting Started
Begin with Chapter 1: Schema-Level Validation to learn how to implement attribute-level validation using built-in and custom validators.
Previous: ← Testing Practice
Next: Schema-Level Validation →
Last updated: June 30, 2026