Language basics
Annotations
Overview
Annotations in Taxi provide a way to attach metadata to types, models, services, and operations. They enable you to add configuration, validation rules, formatting hints, and other metadata without polluting the core data structure.
// Define an annotation
annotation Deprecated {
reason: String
since: String
}
// Use the annotation
@Deprecated(reason = "Use CustomerV2 instead", since = "2024-01-01")
model Customer {
id: CustomerId
name: CustomerName
}
Defining Annotations
Annotations are defined using the annotation
keyword followed by a name and optional field declarations:
// Simple annotation with no fields
annotation Internal
// Annotation with fields
annotation Validation {
minLength: Int?
maxLength: Int?
pattern: String?
}
// Annotation with required and optional fields
annotation Documentation {
description: String
example: String?
deprecated: Boolean = false
}
Supported Field Types
Annotation fields support various types to provide flexibility while maintaining structure:
- Primitive types:
String
,Int
,Double
,Boolean
- Enums: Custom enum types
- Arrays: Arrays of the above types
- Other annotations: Annotations can contain other annotations as fields
- Objects: Model types and structured data
enum Severity {
LOW, MEDIUM, HIGH, CRITICAL
}
model ErrorDetails {
code: Int
description: String
timestamp: String?
}
annotation Alert {
message: String
severity: Severity
tags: String[]
details: ErrorDetails? // Object field
}
annotation Metadata {
alert: Alert?
internal: Boolean = false
}
// Usage with object parameter
@Alert(
message = "Validation failed",
severity = Severity.HIGH,
tags = ["validation", "input"],
details = {
code = 1001,
description = "Email format is invalid",
timestamp = "2024-01-15T10:30:00Z"
}
)
type EmailAddress inherits String
Object Parameters
Annotations can accept object parameters, allowing you to pass structured data as annotation values:
model ValidationError {
code: Int
message: String
field: String?
}
annotation Validate {
error: ValidationError
enabled: Boolean = true
}
// Using object literal syntax
@Validate(error = {
code = 1001,
message = "Invalid email format",
field = "email"
})
type EmailAddress inherits String
Object parameters are validated at compile time to ensure they match the expected structure:
model Error {
message: String
severity: Int
}
annotation Rule {
error: Error
}
// ✅ Valid - matches Error structure
@Rule(error = { message = "Required field", severity = 1 })
type RequiredField inherits String
// ❌ Invalid - field name mismatch
@Rule(error = { description = "Wrong field name" }) // Compilation error
type InvalidField inherits String
Annotation Inheritance
Annotations support inheritance, allowing you to create hierarchies of related annotations. This is useful for creating specialized versions of common annotations.
// Base annotation
annotation Rule {
message: String
}
// Specialized annotations inheriting from Rule
annotation ValidationRule inherits Rule {
errorCode: Int
}
annotation BusinessRule inherits Rule {
priority: Int
category: String
}
// Using inherited annotations
@ValidationRule(message = "Invalid email format", errorCode = 1001)
type EmailAddress inherits String
@BusinessRule(message = "Customer must be active", priority = 1, category = "customer")
service OrderService {
operation processOrder(customerId: CustomerId): Order
}
Inheritance Rules
When working with annotation inheritance:
- Annotations can only inherit from other annotations
- Types cannot inherit from annotations
- Inherited fields are available on child annotations
- Child annotations can add new fields
- No multiple inheritance - each annotation can inherit from only one parent
// ✅ Valid: Annotation inheriting from annotation
annotation BaseRule {
message: String
}
annotation ValidationRule inherits BaseRule {
errorCode: Int
}
// ❌ Invalid: Type inheriting from annotation
// type CustomerId inherits ValidationRule // Compilation error
// ❌ Invalid: Annotation inheriting from type
// annotation MyAnnotation inherits String // Compilation error`
Accessing Inherited Fields
When using annotations with inheritance, all fields from the inheritance hierarchy are available:
annotation Rule {
message: String
enabled: Boolean = true
}
annotation ValidationRule inherits Rule {
errorCode: Int
severity: String = "ERROR"
}
// Usage includes both inherited and declared fields
@ValidationRule(
message = "Value must be positive", // From Rule
enabled = true, // From Rule (optional)
errorCode = 1001, // From ValidationRule
severity = "ERROR" // From ValidationRule (optional)
)
type PositiveNumber inherits Int
Inheritance with Object Fields
Object fields are fully supported in inherited annotations:
model ErrorInfo {
code: Int
description: String
}
annotation Rule {
error: ErrorInfo
}
annotation ValidationRule inherits Rule {
pattern: String?
}
// Child annotation can use inherited object field
@ValidationRule(
error = {
code = 1001,
description = "Must match pattern"
},
pattern = "^[A-Z]+$"
)
type UppercaseCode inherits String
Using Annotations
On Types
@Documentation(description = "Unique identifier for customers")
@Validation(pattern = "CUST-\\d{6}")
type CustomerId inherits String
@Internal
@Deprecated(reason = "Use PersonName instead", since = "2024-01-01")
type CustomerName inherits String`
On Models
@Documentation(description = "Core customer data structure")
@JsonSerializable(camelCase = true)
model Customer {
@Validation(minLength = 1)
id: CustomerId
@Documentation(description = "Customer's display name")
name: CustomerName
@Internal
internalScore: Int?
}
On Services and Operations
@HttpService(baseUrl = "https://api.example.com")
@RateLimit(requestsPerMinute = 100)
service CustomerService {
@HttpOperation(method = "GET", url = "/customers/{id}")
@Cacheable(ttl = 300)
@Documentation(description = "Retrieve customer by ID")
operation getCustomer(id: CustomerId): Customer
@HttpOperation(method = "POST", url = "/customers")
@Validation(required = true)
@Audit(level = "HIGH")
operation createCustomer(customer: Customer): CustomerId
}
Default Values
Annotation fields can specify default values, making them optional when using the annotation:
annotation Cache {
ttl: Int = 300 // Default 5 minutes
enabled: Boolean = true // Default enabled
key: String? // No default, optional
region: String // No default, required
}
// Using with defaults
@Cache(region = "customer-data") // ttl=300, enabled=true
type CustomerId inherits String
// Overriding defaults
@Cache(region = "temp-data", ttl = 60, enabled = false)
type TemporaryId inherits String`