Type Safety
Azu's type safety system is built on Crystal's powerful static type system, providing compile-time guarantees that eliminate entire classes of runtime errors. This document explores how Azu leverages type safety throughout the framework.
Overview
Request Contracts
Type-Safe Parameter Extraction
struct UserRequest
include Request
getter id : Int32
getter name : String
getter email : String
getter age : Int32?
def initialize(@id, @name, @email, @age = nil)
end
# Compile-time validation
def self.from_params(params : Params) : self
new(
id: params.get_int("id"),
name: params.get_string("name"),
email: params.get_string("email"),
age: params.get_int?("age")
)
end
end
Schema Validation
struct CreateUserRequest
include Request
getter name : String
getter email : String
getter age : Int32
# Schema-based validation
def self.schema
Schema.new(
name: String,
email: String,
age: Int32
)
end
def self.from_params(params : Params) : self
validated = schema.validate(params.to_h)
new(
name: validated["name"].as(String),
email: validated["email"].as(String),
age: validated["age"].as(Int32)
)
end
end
Endpoint Type Safety
Compile-Time Route Registration
struct UserEndpoint
include Endpoint(UserRequest, UserResponse)
# Compile-time route definition
get "/users/:id"
def call : UserResponse
# Type-safe access to request data
user_id = request.id
user_name = request.name
# Compile-time guarantee that response matches contract
UserResponse.new(user: find_user(user_id))
end
end
Generic Endpoints
struct ApiEndpoint(T, U)
include Endpoint(T, U)
get "/api/:resource"
def call : U
# Type-safe generic handling
result = process_request(request)
U.new(result)
end
end
Response Type Safety
Structured Responses
struct UserResponse
include Response
getter user : User
getter status : Int32
def initialize(@user, @status = 200)
end
def render : String
# Type-safe JSON serialization
{
id: user.id,
name: user.name,
email: user.email
}.to_json
end
end
Error Response Types
struct ErrorResponse
include Response
getter error : String
getter code : Int32
getter details : Hash(String, String)?
def initialize(@error, @code = 400, @details = nil)
end
def render : String
{
error: error,
code: code,
details: details
}.to_json
end
end
WebSocket Type Safety
Typed Channel Messages
class ChatChannel < Azu::Channel
ws "/ws/chat/:room_id"
# Type-safe message handling
def on_message(message : String)
parsed = JSON.parse(message)
case parsed["type"].as(String)
when "message"
handle_message(parsed["content"].as(String))
when "join"
handle_join(parsed["user"].as(String))
else
# Compile-time exhaustive checking
raise "Unknown message type"
end
end
private def handle_message(content : String)
broadcast({
type: "message",
content: content,
timestamp: Time.utc.to_unix
}.to_json)
end
private def handle_join(user : String)
broadcast({
type: "join",
user: user
}.to_json)
end
end
Component Type Safety
Typed Component Props
class UserCard < Azu::Component
getter user : User
getter show_email : Bool
def initialize(@user, @show_email = false)
end
def content
div class: "user-card" do
h3 user.name
p "ID: #{user.id}"
if show_email
p "Email: #{user.email}"
end
end
end
def on_event(event : String, data : Hash(String, JSON::Any))
case event
when "toggle_email"
@show_email = !@show_email
update_content
when "edit"
# Type-safe event handling
handle_edit(data["field"].as(String), data["value"].as(String))
end
end
end
Compile-Time Optimizations
Method Resolution
# Compile-time method resolution
def process_user(user : User) : String
user.name
end
def process_user(user : AdminUser) : String
"Admin: #{user.name}"
end
# Crystal resolves the correct method at compile time
Type Inference
# Crystal infers types automatically
users = [] of User # Explicit type annotation
users << User.new("John", "john@example.com")
# Type inference in blocks
users.map(&.name) # Returns Array(String)
users.select(&.active?) # Returns Array(User)
Benefits of Type Safety
1. Compile-Time Error Detection
# This won't compile - type mismatch
def get_user_name(user : User) : String
user.id # Error: Int32 cannot be assigned to String
end
2. Refactoring Safety
# Renaming a field is safe - compiler catches all usages
struct User
getter full_name : String # Renamed from 'name'
end
# Compiler will show all places that need updating
3. API Contract Enforcement
# Endpoint contract is enforced at compile time
struct UserEndpoint
include Endpoint(UserRequest, UserResponse)
def call : UserResponse
# Must return UserResponse, not any other type
UserResponse.new(user: find_user(request.id))
end
end
Best Practices
1. Use Explicit Types for Public APIs
# Good: Explicit return type
def find_user(id : Int32) : User?
# Implementation
end
# Avoid: Implicit return type for public methods
def find_user(id)
# Implementation
end
2. Leverage Union Types
# Union types for flexible but type-safe APIs
def process_result(result : User | Error) : String
case result
when User
"User: #{result.name}"
when Error
"Error: #{result.message}"
end
end
3. Use Type Guards
def handle_data(data : JSON::Any) : String
case data
when String
data
when Int32
data.to_s
when Array
data.join(", ")
else
raise "Unsupported data type"
end
end
4. Prefer Structs Over Classes
# Good: Immutable value objects
struct Point
getter x : Float64
getter y : Float64
def initialize(@x, @y)
end
end
# Use classes only when you need inheritance or mutable state
Type Safety in Testing
Type-Safe Test Helpers
# Type-safe test data creation
def create_test_user(name : String = "Test User") : User
User.new(
id: Random.new.rand(1000),
name: name,
email: "#{name.downcase.gsub(/\s+/, ".")}@example.com"
)
end
# Type-safe assertions
def assert_user_response(response : UserResponse, expected_user : User)
assert response.user.id == expected_user.id
assert response.user.name == expected_user.name
end
Performance Impact
Type safety in Azu has minimal runtime overhead:
Compile-time checks: No runtime cost
Type annotations: Optimized away by compiler
Method resolution: Resolved at compile time
Memory safety: No garbage collection overhead
Next Steps
Request Contracts - Learn about type-safe request handling
Response Objects - Explore structured response patterns
Endpoints - Master endpoint type safety
Testing - Write type-safe tests
Last updated
Was this helpful?