Code Standards
Comprehensive coding standards and guidelines for contributing to the Azu web framework.
Overview
This document outlines the coding standards, conventions, and best practices that all contributors should follow when working on the Azu framework. Consistent code style and patterns ensure maintainability and readability.
Crystal Language Standards
Code Style Guidelines
Indentation and Formatting
# Use 2 spaces for indentation
class ExampleClass
def example_method
if condition
do_something
else
do_something_else
end
end
end
# Align method parameters for readability
def complex_method(
param1 : String,
param2 : Int32,
param3 : Bool = false
)
# Method implementation
end
Naming Conventions
# Use snake_case for methods and variables
def calculate_user_score
user_name = "john_doe"
total_score = 0
end
# Use PascalCase for classes, structs, and modules
class UserManager
struct UserData
module DatabaseHelper
end
# Use UPPER_SNAKE_CASE for constants
MAX_RETRY_ATTEMPTS = 3
DEFAULT_TIMEOUT = 30.seconds
Type Annotations
# Use explicit type annotations for public APIs
def create_user(name : String, email : String) : User
User.new(name: name, email: email)
end
# Use type annotations for instance variables
class UserService
@database : Database::Connection
@cache : Cache::Store
def initialize(@database, @cache)
end
end
Code Organization
File Structure
# src/azu/handler/example_handler.cr
require "../http_request"
require "../response"
# Module documentation
module Azu
# Handler documentation
class ExampleHandler
include Handler
# Constants
MAX_RETRIES = 3
# Instance variables
@retry_count : Int32
# Constructor
def initialize(@retry_count = 0)
end
# Public methods
def call(request : HttpRequest, response : Response) : Response
# Implementation
end
# Private methods
private def handle_retry(request : HttpRequest) : Response
# Implementation
end
end
end
Module Organization
# Group related functionality in modules
module Azu
# Core framework functionality
module Core
# Core classes and structs
end
# HTTP handling
module Handler
# Handler implementations
end
# Template system
module Templates
# Template-related classes
end
end
Azu Framework Standards
Endpoint Patterns
# Standard endpoint structure
struct UserEndpoint
include Endpoint(UserRequest, UserResponse)
# Route definitions
get "/users/:id"
post "/users"
put "/users/:id"
delete "/users/:id"
# Implementation
def call : UserResponse
case @request.method
when "GET"
handle_get
when "POST"
handle_post
when "PUT"
handle_put
when "DELETE"
handle_delete
else
raise MethodNotAllowedError.new
end
end
# Private handler methods
private def handle_get : UserResponse
user_id = @request.params["id"]
user = User.find(user_id)
UserResponse.new(user)
end
private def handle_post : UserResponse
user = User.create(@request.data)
UserResponse.new(user, status: 201)
end
end
Request/Response Patterns
# Request contract
struct UserRequest
include Request
# Validations
validates :name, presence: true, length: {min: 2, max: 50}
validates :email, presence: true, format: :email
validates :age, numericality: {greater_than: 0, less_than: 150}
# Custom validations
validate :email_domain_allowed
private def email_domain_allowed
return unless email?
allowed_domains = ["example.com", "test.com"]
domain = email.not_nil!.split("@").last?
unless domain && allowed_domains.includes?(domain)
errors.add(:email, "domain not allowed")
end
end
end
# Response contract
struct UserResponse
include Response
def initialize(@user : User, @status : Int32 = 200)
end
def render : String
case @request.accept
when "application/json"
render_json
when "text/html"
render_html
else
render_json
end
end
private def render_json : String
{
id: @user.id,
name: @user.name,
email: @user.email,
created_at: @user.created_at
}.to_json
end
private def render_html : String
Templates.render("users/show.html", {user: @user})
end
end
Handler Patterns
# Standard handler structure
class AuthenticationHandler
include Handler
def initialize(@secret_key : String)
end
def call(request : HttpRequest, response : Response) : Response
# Pre-processing
token = extract_token(request)
unless valid_token?(token)
return Response.new(
status: 401,
body: {error: "Unauthorized"}.to_json,
headers: {"Content-Type" => "application/json"}
)
end
# Continue to next handler
@next.call(request, response)
rescue ex : Exception
# Error handling
handle_error(ex, request, response)
end
private def extract_token(request : HttpRequest) : String?
request.headers["Authorization"]?.try(&.gsub("Bearer ", ""))
end
private def valid_token?(token : String?) : Bool
return false unless token
# Token validation logic
JWT.decode(token, @secret_key, JWT::Algorithm::HS256)
true
rescue JWT::Error
false
end
private def handle_error(ex : Exception, request : HttpRequest, response : Response) : Response
Response.new(
status: 500,
body: {error: "Internal server error"}.to_json,
headers: {"Content-Type" => "application/json"}
)
end
end
Documentation Standards
Code Documentation
# Class documentation
# Represents a user in the system with authentication and profile management capabilities.
#
# @example Basic usage
# user = User.new(name: "John Doe", email: "john@example.com")
# user.save
#
# @example With validation
# user = User.new(name: "", email: "invalid-email")
# unless user.valid?
# puts user.errors.full_messages
# end
class User
# Creates a new user with the given attributes.
#
# @param name [String] The user's full name
# @param email [String] The user's email address
# @param password [String?] Optional password for authentication
# @raise [ArgumentError] If name or email is empty
def initialize(@name : String, @email : String, @password : String? = nil)
raise ArgumentError.new("Name cannot be empty") if @name.empty?
raise ArgumentError.new("Email cannot be empty") if @email.empty?
end
# Saves the user to the database.
#
# @return [Bool] true if saved successfully, false otherwise
# @raise [DatabaseError] If database connection fails
def save : Bool
# Implementation
end
end
API Documentation
# API endpoint documentation
# @api {get} /users/:id Get user by ID
# @apiName GetUser
# @apiGroup Users
# @apiVersion 1.0.0
#
# @apiParam {Number} id User's unique ID
#
# @apiSuccess {Object} user User object
# @apiSuccess {Number} user.id User ID
# @apiSuccess {String} user.name User's full name
# @apiSuccess {String} user.email User's email address
# @apiSuccess {String} user.created_at User creation timestamp
#
# @apiError {Object} 404 User not found
# @apiError {Object} 500 Internal server error
struct GetUserEndpoint
include Endpoint(GetUserRequest, UserResponse)
get "/users/:id"
def call : UserResponse
# Implementation
end
end
Testing Standards
Test Structure
# spec/azu/handler/example_handler_spec.cr
require "../spec_helper"
describe Azu::Handler::ExampleHandler do
describe "#call" do
it "processes valid requests" do
# Arrange
handler = Azu::Handler::ExampleHandler.new
request = create_test_request("/test")
response = create_test_response
# Act
result = handler.call(request, response)
# Assert
result.status.should eq(200)
end
it "handles invalid requests" do
# Arrange
handler = Azu::Handler::ExampleHandler.new
request = create_test_request("/invalid")
response = create_test_response
# Act & Assert
expect_raises(ArgumentError) do
handler.call(request, response)
end
end
it "maintains response headers" do
# Arrange
handler = Azu::Handler::ExampleHandler.new
request = create_test_request("/test")
response = create_test_response
# Act
result = handler.call(request, response)
# Assert
result.headers["Content-Type"].should eq("application/json")
end
end
describe "edge cases" do
it "handles nil parameters" do
# Test nil handling
end
it "handles empty parameters" do
# Test empty parameter handling
end
it "handles malformed data" do
# Test malformed data handling
end
end
end
Test Utilities
# spec/support/test_helpers.cr
module TestHelpers
def self.create_test_request(
path : String,
method : String = "GET",
params : Hash = {} of String => String,
headers : HTTP::Headers = HTTP::Headers.new
) : Azu::HttpRequest
Azu::HttpRequest.new(
method: method,
path: path,
params: params,
headers: headers
)
end
def self.create_test_response(
status : Int32 = 200,
body : String = "",
headers : HTTP::Headers = HTTP::Headers.new
) : Azu::Response
Azu::Response.new(
status: status,
body: body,
headers: headers
)
end
def self.create_test_user(attributes : Hash = {} of String => String) : User
default_attributes = {
"name" => "Test User",
"email" => "test@example.com"
}
User.new(**default_attributes.merge(attributes))
end
end
Performance Standards
Memory Management
# Use appropriate data structures
class OptimizedHandler
include Handler
# Use Array for small collections
@small_list = Array(String).new(10)
# Use Set for large collections with lookups
@large_set = Set(String).new
# Use Hash for key-value mappings
@cache = {} of String => String
def call(request : HttpRequest, response : Response) : Response
# Use String.build for string concatenation in loops
result = String.build do |str|
@small_list.each do |item|
str << item
str << "\n"
end
end
Response.new(body: result)
end
end
Resource Management
# Proper resource cleanup
class DatabaseHandler
include Handler
def call(request : HttpRequest, response : Response) : Response
connection = database_pool.checkout
begin
result = process_with_connection(connection, request)
Response.new(body: result)
ensure
database_pool.checkin(connection)
end
end
end
Security Standards
Input Validation
# Comprehensive input validation
struct SecureRequest
include Request
# Sanitize and validate all inputs
validates :username, presence: true, length: {min: 3, max: 20}, format: /^[a-zA-Z0-9_]+$/
validates :email, presence: true, format: :email
validates :password, presence: true, length: {min: 8}, format: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/
# Custom validation for security
validate :no_sql_injection
validate :no_xss_attempts
private def no_sql_injection
sql_patterns = ["SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "CREATE"]
sql_patterns.each do |pattern|
if username?.try(&.upcase.includes?(pattern)) || email?.try(&.upcase.includes?(pattern))
errors.add(:base, "Invalid input detected")
break
end
end
end
private def no_xss_attempts
xss_patterns = ["<script>", "javascript:", "onload=", "onerror="]
xss_patterns.each do |pattern|
if username?.try(&.downcase.includes?(pattern)) || email?.try(&.downcase.includes?(pattern))
errors.add(:base, "Invalid input detected")
break
end
end
end
end
Authentication and Authorization
# Secure authentication handler
class SecureAuthHandler
include Handler
def initialize(@secret_key : String, @rate_limiter : RateLimiter)
end
def call(request : HttpRequest, response : Response) : Response
# Rate limiting
unless @rate_limiter.allow?(request.ip)
return Response.new(status: 429, body: "Too many requests")
end
# Token validation with proper error handling
token = extract_token(request)
unless valid_token?(token)
# Log failed attempt
log_failed_attempt(request.ip)
return Response.new(
status: 401,
body: {error: "Invalid credentials"}.to_json,
headers: {"Content-Type" => "application/json"}
)
end
@next.call(request, response)
end
private def valid_token?(token : String?) : Bool
return false unless token
# Use constant-time comparison to prevent timing attacks
JWT.decode(token, @secret_key, JWT::Algorithm::HS256)
true
rescue JWT::Error
false
end
end
Error Handling Standards
Exception Handling
# Comprehensive error handling
class RobustHandler
include Handler
def call(request : HttpRequest, response : Response) : Response
@next.call(request, response)
rescue ex : DatabaseError
handle_database_error(ex, request, response)
rescue ex : ValidationError
handle_validation_error(ex, request, response)
rescue ex : AuthenticationError
handle_authentication_error(ex, request, response)
rescue ex : Exception
handle_generic_error(ex, request, response)
end
private def handle_database_error(ex : DatabaseError, request : HttpRequest, response : Response) : Response
# Log the error
Log.error { "Database error: #{ex.message}" }
# Return appropriate response
Response.new(
status: 503,
body: {error: "Service temporarily unavailable"}.to_json,
headers: {"Content-Type" => "application/json"}
)
end
private def handle_validation_error(ex : ValidationError, request : HttpRequest, response : Response) : Response
Response.new(
status: 422,
body: {errors: ex.errors}.to_json,
headers: {"Content-Type" => "application/json"}
)
end
end
Logging Standards
# Structured logging
class LoggingHandler
include Handler
def call(request : HttpRequest, response : Response) : Response
start_time = Time.monotonic
result = @next.call(request, response)
duration = Time.monotonic - start_time
# Structured logging
Log.info {
{
method: request.method,
path: request.path,
status: result.status,
duration: duration.total_milliseconds,
ip: request.ip,
user_agent: request.headers["User-Agent"]?
}
}
result
rescue ex : Exception
# Error logging
Log.error(exception: ex) {
{
method: request.method,
path: request.path,
error: ex.message,
ip: request.ip
}
}
raise ex
end
end
Code Review Standards
Review Checklist
## Code Review Checklist
### Functionality
- [ ] Does the code work as intended?
- [ ] Are all edge cases handled?
- [ ] Are error conditions properly managed?
- [ ] Does the code follow the established patterns?
### Code Quality
- [ ] Is the code readable and well-documented?
- [ ] Are there any code smells or anti-patterns?
- [ ] Is the code properly tested?
- [ ] Are there any performance concerns?
### Security
- [ ] Are inputs properly validated?
- [ ] Are there any security vulnerabilities?
- [ ] Is sensitive data properly handled?
- [ ] Are authentication/authorization checks in place?
### Testing
- [ ] Are unit tests comprehensive?
- [ ] Are integration tests included?
- [ ] Do tests cover edge cases?
- [ ] Are tests maintainable?
### Documentation
- [ ] Is the code properly documented?
- [ ] Are API changes documented?
- [ ] Are breaking changes noted?
- [ ] Is the README updated if needed?
Review Process
# Pull request template
# .github/pull_request_template.md
## Description
Brief description of the changes made.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No breaking changes (or breaking changes documented)
## Related Issues
Closes #(issue number)
Best Practices
1. Keep It Simple
# Good: Simple and readable
def calculate_total(items : Array(Item)) : Float64
items.sum(&.price)
end
# Avoid: Overly complex
def calculate_total(items : Array(Item)) : Float64
items.reduce(0.0) do |acc, item|
acc + item.price
end
end
2. Fail Fast
# Good: Fail fast with clear error messages
def process_user(user : User) : Bool
raise ArgumentError.new("User cannot be nil") if user.nil?
raise ArgumentError.new("User must be valid") unless user.valid?
# Process user
user.save
end
3. Be Explicit
# Good: Explicit type annotations
def create_user(name : String, email : String) : User
User.new(name: name, email: email)
end
# Avoid: Implicit types
def create_user(name, email)
User.new(name: name, email: email)
end
Next Steps
Development Setup - Setting up your development environment
Roadmap - Development roadmap and priorities
Contributing Guidelines - General contributing guidelines
Following these standards ensures consistent, maintainable, and high-quality code for the Azu framework.
Last updated
Was this helpful?