by-macros

Crates.ioby-macros
lib.rsby-macros
version0.6.15
created_at2025-01-16 20:33:39.253168+00
updated_at2025-11-19 04:41:26.840138+00
descriptionBiyard Macros
homepage
repositoryhttps://github.com/biyard/rust-sdk/tree/main/packages/by-macros
max_upload_size
id1519879
size621,695
hackartist (hackartists)

documentation

README

Biyard Macros

The api_model macro generates an API model with database and repository support.

Features

  • Generates a database table based on the struct definition.
  • Creates repository functions (insert, update, delete, find, find_one, and functions to create tables).
  • Auto-generates API client functions for RESTful interactions.
  • Supports action-based API calls (action and action_by_id).
  • Integrates with SQLx for efficient database queries.

📌 Key Structures

1. Original Structure

The main struct includes functions to obtain the Client (for web interactions) and Repository (for server-side operations).

  • Given ExampleModel as an api_model, the following methods are available:
    • ExampleModel::get_client(endpoint) → Returns ExampleModelClient
    • ExampleModel::get_repository(postgres_pool) → Returns ExampleModelRepository (only for server feature)

2. API Client Structure

  • Named with the suffix Client (e.g., ExampleModelClient).
  • Provides API actions based on the base attribute.

3. Repository Structure

  • Named with the suffix Repository (e.g., ExampleModelRepository).
  • Used for direct database interactions (currently supports PostgreSQL).

4. Summary Model

  • Named with the suffix Summary (e.g., ExampleModelSummary).
  • Used for bulk data retrieval in query actions.
  • Includes only fields marked with summary.

📌 Client Structure

Actions

Types of Actions

  • Query Action (query_action, queryable)

    • Describes bulk data listing.
    • Defined as a field attribute.
  • Read Action (read_action)

    • Fetches a single entity by a unique identifier.
  • Action (action)

    • Supports custom REST API actions.
  • Action by ID (action_by_id)

    • Defines API actions that require an ID parameter.

Query Action (query_action, queryable)

Query action is used to retrieve multiple records from the database in a paginated manner. It is automatically generated for models that have the queryable or query_action attribute defined in a field.

  • It returns a paginated response wrapped in a QueryResponse<T> structure where iter_type is QueryResponse.
    • At this, iter_type must implement From<(i64, T)> trait.
  • The query parameters such as size and page are supported to control the data retrieval.
  • The response contains both the data and metadata (e.g., total count, pagination details).
  • Query param structure, which is named by suffix of Query is composed with fields described as queryable or query_action

Example Usage:

let client = ExampleModel::get_client("https://api.example.com");
let query_params = ExampleModelQuery::new(10).with_page(2);
let response = client.query(query_params).await?;
println!("Total Count: {}", response.total_count);
for item in response.items {
    println!("ID: {}, Name: {}", item.id, item.name);
}
Differences between queryable and `query_action**
  • queryable only makes a field to Query structure.
  • query_action makes a field and a function to `Query** structure

Example Usage:

#[cfg(feature = "server")]
use by_axum::aide;
use by_macros::{api_model, ApiModel};

pub type Result<T> = std::result::Result<T, by_types::ApiError<String>>;

#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct QueryResponse<T> {
    pub items: Vec<T>,
    pub total_count: i64,
}

impl<T> From<(Vec<T>, i64)> for QueryResponse<T> {
    fn from((items, total_count): (Vec<T>, i64)) -> Self {
        QueryResponse { items, total_count }
    }
}

#[api_model(base = "/examples", iter_type = QueryResponse)]
pub struct ExampleModel {
    #[api_model(summary, primary_key)]
    pub id: String,
    #[api_model(summary, auto = [insert])]
    pub created_at: i64,
    #[api_model(summary, auto = [insert, update])]
    pub updated_at: i64,
    #[api_model(summary, query_action = list_by_status)]
    pub status: i32,
    #[api_model(summary, queryable)]
    pub area: String,
}

async fn test_query() {
   let cli = ExampleModel::get_client("http://localhost:3000");

   // it makes an API call to GET http://localhost:3000/examples?param-type=query&area=test_area&size=10
   // res will be `Result<QueryResponse<ExampleModel>>`. If `iter_type` is described as and other generic response, it will be the described type. Don't forget implementing `From<(i64,T)>` trait for the reponse type.
   // And also abbreviated Result must be defined or imported in this file.
   let res = cli.query(ExampleModelQuery::new(10).with_area("test_area".to_string())).await;

   // it makes an API call to GET http://localhost:3000/examples?param-type=query&area=test_area&size=10&page=2
   cli.query(ExampleModelQuery::new(10).with_page(2).with_area("test_area".to_string())).await;

   // `list_by_status` is generated by `query_action` attribute.
   // it makes an API call to GET http://localhost:3000/examples?param-type=query&status=3&size=10&page=2
   cli.list_by_status(10, Some(2.to_string()), 3).await;
}
Principle of query

For composition of query url, It uses Param structure. ex) ExampleModelParam. It will declared as below:

#[serde(rename = "kebab-case", tag = "param-type")]
pub struct ExampleModelParam {
    Query(ExampleModelQuery)
    Read(ExampleModelReadAction)
}

If no read_action, Param is only declared with Query. At this, Query is always defined even if no queryable or query_action. If no query* is described, ExampleModelQuery only has size and bookmark fields. Note that page will be described in bookmark as string in SQL.

Handling requests in server side

Server side handle can handle Param or Query structure. If server side handler handle only Query like ExampleModelQuery, it ignores param-type query argument. If you don't have a plan to support read_action, you can only hanle Query instead of Param.

Read Action (read_action)

Read action(read_action) can be described as an structure attribute or a field attribute.

Structure attribute

It can be formed by three types; a function only, multiple functions, functions with additional parameters. Structure attribute can be utilized in two circumstances;

  • defining a API call without any parameter
  • adding a custom field to a specific read action.

The below explains how to use read_action as structure attribute.

  • The simplest definition defines only a function.
    • read_action = get_data
  • Multiple functions define two or more functions.
    • read_action = [get_data, verify]
  • Functions with additional parameter define functions with additional parameter, which this structure does not have.
    • read_action = [get_data, verify(code = String, email = String)]
Field attribute

It supports a single function and multiple functions form. Also it allows you to change to a custom parameter type. In contrast of structure attiribute, field

  • a single and multiple functions definition are same with structure attribute.
    • read_action = get_data, read_action = [get_data, verify]
    • Basically, read_action in field attribute makes a field with the same type to ReadAction structure.
  • If you want to change action type, you can use related attiribute.
    • related = CustomActionRequest makes Client use CustomActionRequest

Action (action)

Action by id (action_by_id)

📌 Repository Structure

  • Provides functions to interact with the database.
  • Implements CRUD operations via SQLx.
  • Automatically generates SQL table creation scripts.

Example Usage

let repo = ExampleModel::get_repository(pool);
let new_item = repo.insert("Example Name").await?;
let updated_item = repo.update("123", ExampleModelRepositoryUpdateRequest { name: Some("Updated") }).await?;
let deleted = repo.delete("123").await?;

📌 Example

#[api_model(
    base = "/examples",
    table = example_table,
    iter_type = QueryResponse,
    action_by_id = delete,
    action = [no_param, empty]
)]
pub struct ExampleModel {
    #[api_model(summary, primary_key, read_action = find_by_id)]
    pub id: String,
    #[api_model(summary, auto = [insert])]
    pub created_at: i64,
    #[api_model(summary, auto = [insert, update])]
    pub updated_at: i64,
    #[api_model(summary)]
    pub name: String,
}

📌 Conclusion

The #[api_model] macro automatically generates database models, API request structures, clients, and action enums.
This enables efficient RESTful API and database interactions.

Postgres Setup

Add set_updated_at function

  • Below function will be used by auto updated TIMESTAMP like updated_at
  CREATE OR REPLACE FUNCTION set_created_at()
    RETURNS TRIGGER AS $$
    BEGIN
      NEW.created_at := EXTRACT(EPOCH FROM now()) * 1000;

      RETURN NEW;
    END;
  $$ LANGUAGE plpgsql;
  CREATE OR REPLACE FUNCTION set_updated_at()
    RETURNS TRIGGER AS $$
    BEGIN
      NEW.updated_at := EXTRACT(EPOCH FROM now()) * 1000;

      RETURN NEW;
    END;
  $$ LANGUAGE plpgsql;
  • You can check if the function was created as below
  SELECT proname FROM pg_proc WHERE proname = 'set_created_at;

API Model

Usage

  • crate::Result must be declared.
  • For using ApiError as result error type, you should implement From<String> into inner error type
  • If you want to use fully customized error type, you should implement From<reqwest::Error>.
#[cfg(feature = "server")]
use by_axum::aide;

use by_macros::api_model;
use serde::{Deserialize, Serialize};

type Result<T> = std::result::Result<T, by_types::ApiError<String>>;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
#[api_model(base = "/topics/v1", iter_type=Vec, read_action = no_param_action)]
pub struct Topic {
    #[api_model(summary)]
    pub id: String,
    #[api_model(read_action = user_info)]
    pub wallet_address: String,
    #[api_model(read_action = [check_email,user_info])]
    pub email: String,
    #[api_model(summary, action = create)]
    pub title: String,
    #[api_model(summary, queryable, query_action = search_by, action = create, action_by_id = update)]
    pub description: String,
    #[api_model(summary, queryable, action_by_id = update, read_action = user_info)]
    pub status: i32,
    #[api_model(summary, query_action = [search_by, date_from])]
    pub created_at: i64,
    pub is_liked: bool,

    pub updated_at: i64,
    #[api_model(action_by_id = like, related = CommentRequest)]
    #[api_model(action = comment, related = Comment)]
    pub comments: Vec<Comment>,

    #[api_model(action_by_id = update)]
    pub tags: Vec<String>,
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Comment {
    pub id: String,
    pub content: String,
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct CommentRequest {
    pub comment_id: String,
    pub is_liked: bool,
}

Expanded code

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct Topic {
    pub id: String,
    pub wallet_address: String,
    pub email: String,
    pub title: String,
    pub description: String,
    pub status: i32,
    pub created_at: i64,
    pub is_liked: bool,
    pub updated_at: i64,
    pub comments: Vec<Comment>,
    pub tags: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub enum TopicAction {
    Comment(Comment),
    Create(TopicCreateRequest),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, Eq, PartialEq)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct TopicCreateRequest {
    pub title: String,
    pub description: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub enum TopicByIdAction {
    Update(TopicUpdateRequest),
    Like(CommentRequest),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, Eq, PartialEq)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct TopicUpdateRequest {
    pub description: String,
    pub status: i32,
    pub tags: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, Eq, PartialEq)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct TopicSummary {
    pub id: String,
    pub title: String,
    pub description: String,
    pub status: i32,
    pub created_at: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq, by_macros::QueryDisplay)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct TopicQuery {
    pub size: usize,
    pub bookmark: Option<String>,
    pub action: Option<TopicQueryActionType>,
    pub description: Option<String>,
    pub status: Option<i32>,
    pub created_at: Option<i64>,
}
impl TopicQuery {
    pub fn new(size: usize) -> Self {
        Self {
            size,
            ..Self::default()
        }
    }
    pub fn with_bookmark(mut self, bookmark: String) -> Self {
        self.bookmark = Some(bookmark);
        self
    }
    pub fn with_description(mut self, description: String) -> Self {
        self.description = Some(description);
        self
    }
    pub fn with_status(mut self, status: i32) -> Self {
        self.status = Some(status);
        self
    }
    pub fn search_by(mut self, description: String, created_at: i64) -> Self {
        self.description = Some(description);
        self.created_at = Some(created_at);
        self.action = Some(TopicQueryActionType::SearchBy);
        self
    }
    pub fn date_from(mut self, created_at: i64) -> Self {
        self.created_at = Some(created_at);
        self.action = Some(TopicQueryActionType::DateFrom);
        self
    }
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub enum TopicQueryActionType {
    SearchBy,
    DateFrom,
}

impl Topic {
    pub fn get_client(endpoint: &str) -> TopicClient {
        TopicClient {
            endpoint: endpoint.to_string(),
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
pub struct TopicClient {
    pub endpoint: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq, by_macros::QueryDisplay)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct TopicReadAction {
    pub action: Option<TopicReadActionType>,
    pub wallet_address: Option<String>,
    pub email: Option<String>,
    pub status: Option<i32>,
}
impl TopicReadAction {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn user_info(mut self, wallet_address: String, email: String, status: i32) -> Self {
        self.wallet_address = Some(wallet_address);
        self.email = Some(email);
        self.status = Some(status);
        self.action = Some(TopicReadActionType::UserInfo);
        self
    }
    pub fn check_email(mut self, email: String) -> Self {
        self.email = Some(email);
        self.action = Some(TopicReadActionType::CheckEmail);
        self
    }

    pub fn no_param_action(mut self) -> Self {
        self.action = Some(TopicReadActionType::NoParamAction);
        self
    }
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub enum TopicReadActionType {
    UserInfo,
    CheckEmail,
}
impl TopicClient {
    pub async fn no_param_action(
        &self,
    ) -> crate::Result<Topic> {
        let endpoint = format!("{}{}", self.endpoint, "/topics/v1");
        let params = TopicReadAction::new().no_param_action();
        let query = format!("{}?{}", endpoint, params);
        rest_api::get(&query).await
    }
    pub async fn user_info(
        &self,
        wallet_address: String,
        email: String,
        status: i32,
    ) -> crate::Result<Topic> {
        let endpoint = format!("{}{}", self.endpoint, "/topics/v1");
        let params = TopicReadAction::new().user_info(wallet_address, email, status);
        let query = format!("{}?{}", endpoint, params);
        rest_api::get(&query).await
    }
    pub async fn check_email(&self, email: String) -> crate::Result<Topic> {
        let endpoint = format!("{}{}", self.endpoint, "/topics/v1");
        let params = TopicReadAction::new().check_email(email);
        let query = format!("{}?{}", endpoint, params);
        rest_api::get(&query).await
    }
    pub async fn query(&self, params: TopicQuery) -> crate::Result<Vec<TopicSummary>> {
        let endpoint = format!("{}{}", self.endpoint, "/topics/v1");
        let query = format!("{}?{}", endpoint, params);
        rest_api::get(&query).await
    }
    pub async fn get(&self, id: &str) -> crate::Result<Topic> {
        let endpoint = format!("{}{}/{}", self.endpoint, "/topics/v1", id);
        rest_api::get(&endpoint).await
    }
    pub async fn search_by(
        &self,
        size: usize,
        bookmark: Option<String>,
        description: String,
        created_at: i64,
    ) -> crate::Result<Vec<TopicSummary>> {
        let endpoint = format!("{}{}", self.endpoint, "/topics/v1");
        let params = TopicQuery {
            size,
            bookmark,
            action: Some(TopicQueryActionType::SearchBy),
            description: Some(description),
            created_at: Some(created_at),
            ..TopicQuery::default()
        };
        let query = format!("{}?{}", endpoint, params);
        rest_api::get(&query).await
    }
    pub async fn date_from(
        &self,
        size: usize,
        bookmark: Option<String>,
        created_at: i64,
    ) -> crate::Result<Vec<TopicSummary>> {
        let endpoint = format!("{}{}", self.endpoint, "/topics/v1");
        let params = TopicQuery {
            size,
            bookmark,
            action: Some(TopicQueryActionType::DateFrom),
            created_at: Some(created_at),
            ..TopicQuery::default()
        };
        let query = format!("{}?{}", endpoint, params);
        rest_api::get(&query).await
    }
    pub async fn act(&self, params: TopicAction) -> crate::Result<Topic> {
        let endpoint = format!("{}{}", self.endpoint, "/topics/v1");
        rest_api::post(&endpoint, params).await
    }
    pub async fn create(&self, title: String, description: String) -> crate::Result<Topic> {
        let endpoint = format!("{}{}", self.endpoint, "/topics/v1");
        let req = TopicAction::Create(TopicCreateRequest { title, description });
        rest_api::post(&endpoint, req).await
    }
    pub async fn act_by_id(&self, id: &str, params: TopicByIdAction) -> crate::Result<Topic> {
        let endpoint = format!("{}{}/{}", self.endpoint, "/topics/v1", id);
        rest_api::post(&endpoint, params).await
    }
    pub async fn update(
        &self,
        id: &str,
        description: String,
        status: i32,
        tags: Vec<String>,
    ) -> crate::Result<Topic> {
        let endpoint = format!("{}{}/{}", self.endpoint, "/topics/v1", id);
        let req = TopicByIdAction::Update(TopicUpdateRequest {
            description,
            status,
            tags,
        });
        rest_api::post(&endpoint, req).await
    }
}

Define a model with parent ID

  • If a resource is based on a specific parent resource, you can specify a parent ID with :.
    • Usually, the ID should be kebab-case by REST API convention.
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
#[api_model(base = "/topics/v1/:topic-id/comments", iter_type=Vec)]
pub struct Comment {
    pub id: String,
    #[api_model(action = comment, related = String, read_action = search_by)]
    pub content: String,
    #[api_model(action_by_id = update, related = i64)]
    pub updated_at: i64,
}

Expanded code

pub struct Comment {
    pub id: String,
    pub content: String,
    pub updated_at: i64,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub enum CommentAction {
    Comment(String),
}
impl CommentClient {
    pub async fn act(&self, topic_id: &str, params: CommentAction) -> crate::Result<Comment> {
        let path = format!("/topics/v1/{}/comments", topic_id,);
        let endpoint = format!("{}{}", self.endpoint, path);
        rest_api::post(&endpoint, params).await
    }
    pub async fn comment(&self, topic_id: &str, request: String) -> crate::Result<Comment> {
        let path = format!("/topics/v1/{}/comments", topic_id,);
        let endpoint = format!("{}{}", self.endpoint, path);
        rest_api::post(&endpoint, request).await
    }
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub enum CommentByIdAction {
    Update(i64),
}
impl CommentClient {
    pub async fn act_by_id(
        &self,
        topic_id: &str,
        id: &str,
        params: CommentByIdAction,
    ) -> crate::Result<Comment> {
        let path = format!("/topics/v1/{}/comments", topic_id,);
        let endpoint = format!("{}{}/{}", self.endpoint, path, id);
        rest_api::post(&endpoint, params).await
    }
    pub async fn update(&self, topic_id: &str, id: &str, request: i64) -> crate::Result<Comment> {
        let path = format!("/topics/v1/{}/comments", topic_id,);
        let endpoint = format!("{}{}/{}", self.endpoint, path, id);
        rest_api::post(&endpoint, request).await
    }
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, Eq, PartialEq)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct CommentSummary {}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq, by_macros::QueryDisplay)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct CommentQuery {
    pub size: usize,
    pub bookmark: Option<String>,
}
impl CommentQuery {
    pub fn new(size: usize) -> Self {
        Self {
            size,
            ..Self::default()
        }
    }
    pub fn with_bookmark(mut self, bookmark: String) -> Self {
        self.bookmark = Some(bookmark);
        self
    }
}
impl CommentClient {}
impl Comment {
    pub fn get_client(endpoint: &str) -> CommentClient {
        CommentClient {
            endpoint: endpoint.to_string(),
        }
    }
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq)]
pub struct CommentClient {
    pub endpoint: String,
}
impl CommentClient {
    pub async fn query(
        &self,
        topic_id: &str,
        params: CommentQuery,
    ) -> crate::Result<Vec<CommentSummary>> {
        let path = format!("/topics/v1/{}/comments", topic_id,);
        let endpoint = format!("{}{}", self.endpoint, path);
        let query = format!("{}?{}", endpoint, params);
        rest_api::get(&query).await
    }
    pub async fn get(&self, topic_id: &str, id: &str) -> crate::Result<Comment> {
        let path = format!("/topics/v1/{}/comments", topic_id,);
        let endpoint = format!("{}{}/{}", self.endpoint, path, id);
        rest_api::get(&endpoint).await
    }
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Eq, PartialEq, by_macros::QueryDisplay)]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub struct CommentReadAction {
    pub action: Option<CommentReadActionType>,
    pub content: Option<String>,
}
impl CommentReadAction {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn search_by(mut self, content: String) -> Self {
        self.content = Some(content);
        self.action = Some(CommentReadActionType::SearchBy);
        self
    }
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "server", derive(schemars::JsonSchema, aide::OperationIo))]
pub enum CommentReadActionType {
    SearchBy,
}
impl CommentClient {
    pub async fn search_by(&self, topic_id: &str, content: String) -> crate::Result<Comment> {
        let path = format!("/topics/v1/{}/comments", topic_id,);
        let endpoint = format!("{}{}", self.endpoint, path);
        let params = CommentReadAction::new().search_by(content);
        let query = format!("{}?{}", endpoint, params);
        rest_api::get(&query).await
    }
}

Troublshooting

Panic with parent route

Assume Panel model is a child endpoint of organizations. It means that Panel model has a field mapping to organizations in many_to_one. At this, org_id is the foreign reference key of id of organizations table. And implicitly, :org-id in base will be org_id at the last field.

The below code generates Client having all function with the first parent id(org_id, which is snake_case of :org-id).

If action was described in org_id field, it generates create function in Client structure as pub fn create(&self, org_id: &str, name: String, user_count: u64, org_id: String). At this, the first parameter(org_id: &str) and the last parameter(org_id: String) are conflict.

In order to overcome this issue, there are two options.

  • rename :org-id to :organization-id.
    • It generate create function as pub fn create(&self, organization_id: &str, name: String, user_count: u64, org_id: String)
  • However, usually you might not need to make a additional parameter or org_id because all Client functions have org-id as their first argument. And it may indicate the org_id: String.
    • This means usually, many_to_one relationship will be expressed as a child endpoint in HTTP API concept.
#[derive(validator::Validate)]
#[api_model(base = "organizations/v2/:org-id/panels", table = panels, iter_type=QueryResponse)]
pub struct Panel {
    #[api_model(summary, primary_key, action = delete, read_action = [get_panel, find_by_id])]
    pub id: String,
    #[api_model(summary, auto = insert)]
    pub created_at: i64,
    #[api_model(auto = [insert, update])]
    pub updated_at: i64,

    #[api_model(summary, action = [create], action_by_id = update)]
    pub name: String,
    #[api_model(summary, action = [create], action_by_id = update)]
    pub user_count: u64,
    #[api_model(summary, many_to_one = organizations, action = [create])]
    pub org_id: String,
}
Commit count: 608

cargo fmt