Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Define a Structure

This document describes how to define structured data models within Biyard Rust projects using the custom api_model macro, streamlining serialization, validation, API interactions, and database integrations.

🛠️ Using the api_model Macro

The api_model macro supports two types of attributes:

  • Structure Attributes: Applied to the entire struct, specifying API endpoints, supported actions, database tables, and response behaviors.
  • Field Attributes: Applied individually to fields, specifying primary keys, relationships, and serialization rules.

This document specifically explains structure attributes.


📌 Structure Attributes

Structure attributes configure API endpoints, database associations, and model-specific actions:

  • api_model automatically attaches derives of Debug, Clone, serde::Deserialize, serde::Serialize, Default, PartialEq by default.
  • It also add schemars::JsonSchema and aide::OperationIo for server feature.

Syntax Example:

#![allow(unused)]
fn main() {
#[api_model(base = "/v1/users", table = "users")]
pub struct User {
    #[api_model(primary_key)]
    pub id: i64,
    pub username: String,
}
}

📖 Attribute Definitions

AttributeDescriptionExample
baseAPI path prefix for model-related endpoints.base = "/v1/users"
tableSpecifies the database table associated with the model.table = "users"
queryableEnables query functionality with optional sorting/filtering parameters.queryable = [(sort = Sorter)]
actionDefines actions (e.g., POST requests) with optional custom parameters.action = [signup(code = String, param = u64)]
read_actionDefines custom read-only actions without parameters.read_action = read_data
action_by_idDefines actions targeted by entity ID (e.g., update, delete).action_by_id = [get_data, update_data]
responseSpecifies custom response types for defined actions.response = [signup(UserResponse)]

📖 Field Attributes

Field attributes specify behaviors at the individual field level:

AttributeDescriptionExample
summaryAdds the field to UserSummary.#[api_model(summary)]
queryableAdds the field to UserQuery.#[api_model(queryable)]
actionDefines actions (e.g., POST requests) with optional custom parameters.#[api_model(action = signup)]
read_actionAdds the field to UserQuery.#[api_model(read_action = read_data)]
query_actionAdds the field to UserQuery.#[api_model(query_action = list_data)]
action_by_idDefines actions targeted by entity ID (e.g., update, delete).#[api_model(action_by_id = [get_data, update_data])]
primary_keyMarks the field as the primary key.#[api_model(primary_key)]
nullableAllows the database field to accept null values.#[api_model(nullable)]
skipExcludes the field from serialization and database handling.#[api_model(skip)]
typeDescribe type of SQL explicitly.#[api_model(type = INTEGER)]
nestedDefines nested data structures within the model.#[api_model(nested)]
one_to_manySpecifies a one-to-many relationship.#[api_model(one_to_many = "posts", foreign_key = "author_id")]
many_to_oneSpecifies a many-to-one relationship.#[api_model(many_to_one = "user")]
many_to_manyDefines a many-to-many relationship via a join table.#[api_model(many_to_many = "posts_tags", foreign_table_name = "tags")]
aggregatorEmbeds aggregation logic (sum, avg, etc.) into model fields.#[api_model(aggregator = sum(price))]
foreign_keyIndicates related key field name for one_to_many or many_to_one#[api_model(one_to_many = "posts", foreign_key = "author_id")]
foreign_table_nameMeans foreign table name via joined table#[api_model(many_to_many = "posts_tags", foreign_table_name = "tags")]
foreign_primary_keyIndicates field name mapped to id field of foreign table in joined table#[api_model(foreign_primary_key = tag_id)]
foreign_reference_keyIndicates field name mapped to id field of this table in joined table#[api_model(foreign_reference_key = post_id)]

⚙️ Practical Struct Example:

Here's a complete, clearly structured Rust model:

#![allow(unused)]
fn main() {
#[api_model(
    base = "/v1/users",
    table = "users",
    queryable = [(sort = Sorter)],
    action = [signup(code = String, param = u64), login(test = Vec<String>)],
    read_action = read_data,
    action_by_id = [get_data, update_data],
    response = [signup(UserResponse)]
)]
#[derive(Debug, Clone, Default)]
pub struct User {
    #[api_model(primary_key)]
    pub id: i64,

    pub username: String,

    #[api_model(nullable)]
    pub email: Option<String>,

    #[api_model(skip)]
    pub password_hash: String,
}
}

This setup ensures:

  • Clear alignment of API paths and database table structures.
  • Automatic generation of API clients and database CRUD operations.
  • Custom request and response handling for specified actions.

  • Clearly define your base attribute to reflect your API structure.
  • Always explicitly specify the associated table for clarity and database operations.
  • Define custom parameters and response types for actions explicitly when required.

Following these best practices ensures your models are consistent, maintainable, and intuitive within the Biyard Rust ecosystem.


📌 Generated Summary Structure (UserSummary):

The macro identifies fields marked with #[api_model(summary)] and generates a simplified struct called UserSummary:

🚩 Automatically Generated Struct:

#![allow(unused)]
fn main() {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct UserSummary {
    pub id: i64,
    pub created_at: i64,
    pub updated_at: i64,
    pub nickname: Option<String>,
}
}

🎯 Purpose of the Summary Struct:

The UserSummary structure is designed to provide concise, efficient, and safe data exchange:

  • API List Responses: Clearly optimized for list-based API responses (e.g., listing users without sensitive data).
  • Performance: Reduces payload size and overhead by excluding non-summary fields.
  • Security: Automatically excludes sensitive fields (e.g., email, password) not explicitly marked for summary.

📖 Rules for Generating Summary Structures:

  • Fields must explicitly include the summary attribute to appear in the summary structure.
  • The original model struct remains unaffected; the summary struct is an additional, simplified representation.
  • Useful for API endpoints designed specifically for listing or simplified views.

  • Clearly select summary fields carefully to exclude sensitive information.
  • Leverage summary structures for efficient API responses.
  • Regularly verify and adjust summary fields based on API usage patterns.

Following these best practices ensures safe, performant, and clear API data exchange within your Biyard Rust ecosystem.