ruskit 0.1.5

A modern web framework for Rust inspired by Laravel
Documentation
{% extends "docs_base.html" %}

{% block content %}
<div class="prose prose-slate max-w-none">
    <h1>Data Transfer Objects (DTOs)</h1>

    <p>In Ruskit, Data Transfer Objects (DTOs) are structures that define how data should be sent over the network. They help in validating incoming requests and structuring outgoing responses.</p>

    <h2>Overview</h2>

    <p>DTOs serve several purposes:</p>
    <ul>
        <li>Separate the API contract from internal data structures</li>
        <li>Provide input validation for requests</li>
        <li>Control what data is exposed in responses</li>
        <li>Enable versioning of API responses</li>
    </ul>

    <h2>Structure</h2>

    <p>A typical DTO module contains three main structs:</p>
    <ol>
        <li>Request DTO - For handling incoming data</li>
        <li>Response DTO - For sending data back to the client</li>
        <li>List Response DTO - For sending collections of data</li>
    </ol>

    <h3>Example</h3>
    <pre><code class="language-rust">use serde::{Serialize, Deserialize};
use validator::Validate;
use crate::app::models::User;

// Response DTO
#[derive(Serialize)]
pub struct UserResponse {
    pub id: i64,
    pub name: String,
    pub email: String,
    pub created_at: i64,
    pub updated_at: i64,
}

// Request DTO with validation
#[derive(Deserialize, Validate)]
pub struct CreateUserRequest {
    #[validate(length(min = 3, max = 50))]
    pub name: String,
    #[validate(email)]
    pub email: String,
    #[validate(length(min = 8))]
    pub password: String,
}

// List Response DTO
#[derive(Serialize)]
pub struct UserListResponse {
    pub data: Vec<UserResponse>,
}</code></pre>

    <h2>Creating DTOs</h2>

    <p>Use the <code>make:dto</code> command to generate a new DTO:</p>

    <pre><code class="language-bash">cargo kit make:dto User</code></pre>

    <p>This will create:</p>
    <ul>
        <li><code>src/app/dtos/user.rs</code> with basic DTO structures</li>
        <li>Update <code>src/app/dtos/mod.rs</code> to include the new module</li>
    </ul>

    <h2>Request DTOs</h2>

    <p>Request DTOs handle incoming data and validation:</p>

    <pre><code class="language-rust">#[derive(Deserialize, Validate)]
pub struct CreateUserRequest {
    #[validate(length(min = 3, max = 50))]
    pub name: String,
    #[validate(email)]
    pub email: String,
    #[validate(length(min = 8))]
    pub password: String,
    #[validate(range(min = 0, max = 150))]
    pub age: Option<i32>,
}</code></pre>

    <h3>Validation Rules</h3>

    <p>Common validation attributes:</p>
    <ul>
        <li><code>length(min = x, max = y)</code> - String length constraints</li>
        <li><code>range(min = x, max = y)</code> - Numeric range constraints</li>
        <li><code>email</code> - Email format validation</li>
        <li><code>url</code> - URL format validation</li>
        <li><code>contains(pattern = "x")</code> - String contains pattern</li>
        <li><code>regex(path = "REGEX")</code> - Regular expression matching</li>
    </ul>

    <h2>Response DTOs</h2>

    <p>Response DTOs control what data is sent to clients:</p>

    <pre><code class="language-rust">#[derive(Serialize)]
pub struct UserResponse {
    pub id: i64,
    pub name: String,
    pub email: String,
    // Note: password is not included in response
    pub created_at: i64,
    pub updated_at: i64,
}</code></pre>

    <h3>List Responses</h3>

    <p>For collections of data:</p>

    <pre><code class="language-rust">#[derive(Serialize)]
pub struct UserListResponse {
    pub data: Vec<UserResponse>,
    pub total: usize,
    pub page: usize,
    pub per_page: usize,
}</code></pre>

    <h2>Model Conversion</h2>

    <p>DTOs should implement <code>From</code> traits for conversion:</p>

    <pre><code class="language-rust">// Convert from Model to Response
impl From<User> for UserResponse {
    fn from(user: User) -> Self {
        Self {
            id: user.id,
            name: user.name,
            email: user.email,
            created_at: user.created_at,
            updated_at: user.updated_at,
        }
    }
}

// Convert from Request to Model
impl From<CreateUserRequest> for User {
    fn from(req: CreateUserRequest) -> Self {
        use std::time::{SystemTime, UNIX_EPOCH};
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64;
            
        Self {
            id: 0, // New user, ID will be set by database
            name: req.name,
            email: req.email,
            password_hash: hash_password(&req.password),
            created_at: now,
            updated_at: now,
        }
    }
}</code></pre>

    <h2>Best Practices</h2>

    <ol>
        <li>
            <strong>Separation of Concerns</strong>
            <ul>
                <li>Keep DTOs focused on data transfer</li>
                <li>Don't include business logic in DTOs</li>
                <li>Use separate DTOs for different API versions</li>
            </ul>
        </li>
        <li>
            <strong>Validation</strong>
            <ul>
                <li>Always validate incoming data</li>
                <li>Use appropriate validation rules</li>
                <li>Consider optional fields when appropriate</li>
            </ul>
        </li>
        <li>
            <strong>Security</strong>
            <ul>
                <li>Never expose sensitive data in responses</li>
                <li>Validate all incoming data</li>
                <li>Use appropriate serialization attributes</li>
            </ul>
        </li>
        <li>
            <strong>Naming Conventions</strong>
            <ul>
                <li>Use PascalCase for struct names</li>
                <li>End request DTOs with <code>Request</code></li>
                <li>End response DTOs with <code>Response</code></li>
                <li>Use descriptive names for fields</li>
            </ul>
        </li>
        <li>
            <strong>Documentation</strong>
            <ul>
                <li>Document all DTO fields</li>
                <li>Include validation requirements</li>
                <li>Explain any special formatting</li>
            </ul>
        </li>
    </ol>

    <h2>Usage in Controllers</h2>

    <pre><code class="language-rust">use axum::{
    response::Json,
    extract::Path,
};
use crate::app::dtos::user::{CreateUserRequest, UserResponse};

impl UserController {
    pub async fn store(
        Json(payload): Json<CreateUserRequest>
    ) -> Json<UserResponse> {
        // Payload is already validated due to DTO
        let user: User = payload.into();
        let created = User::create(user).await?;
        Json(UserResponse::from(created))
    }
}</code></pre>
</div>
{% endblock %}