| Crates.io | by-macros |
| lib.rs | by-macros |
| version | 0.6.15 |
| created_at | 2025-01-16 20:33:39.253168+00 |
| updated_at | 2025-11-19 04:41:26.840138+00 |
| description | Biyard Macros |
| homepage | |
| repository | https://github.com/biyard/rust-sdk/tree/main/packages/by-macros |
| max_upload_size | |
| id | 1519879 |
| size | 621,695 |
The api_model macro generates an API model with database and repository support.
insert, update, delete, find, find_one, and functions to create tables).action and action_by_id).The main struct includes functions to obtain the Client (for web interactions) and Repository (for server-side operations).
ExampleModel as an api_model, the following methods are available:
ExampleModel::get_client(endpoint) → Returns ExampleModelClientExampleModel::get_repository(postgres_pool) → Returns ExampleModelRepository (only for server feature)Client (e.g., ExampleModelClient).base attribute.Repository (e.g., ExampleModelRepository).Summary (e.g., ExampleModelSummary).summary.Query Action (query_action, queryable)
Read Action (read_action)
Action (action)
Action by ID (action_by_id)
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.
QueryResponse<T> structure where iter_type is QueryResponse.
iter_type must implement From<(i64, T)> trait.size and page are supported to control the data retrieval.Query param structure, which is named by suffix of Query is composed with fields described as queryable or query_actionExample 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);
}
queryable and `query_action**queryable only makes a field to Query structure.query_action makes a field and a function to `Query** structureExample 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;
}
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.
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) can be described as an structure attribute or a field 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;
The below explains how to use read_action as structure attribute.
read_action = get_dataread_action = [get_data, verify]read_action = [get_data, verify(code = String, email = String)]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
structure attribute.
read_action = get_data, read_action = [get_data, verify]read_action in field attribute makes a field with the same type to ReadAction structure.related attiribute.
related = CustomActionRequest makes Client use CustomActionRequest
action)action_by_id)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?;
#[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,
}
The #[api_model] macro automatically generates database models, API request structures, clients, and action enums.
This enables efficient RESTful API and database interactions.
set_updated_at functionupdated_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;
SELECT proname FROM pg_proc WHERE proname = 'set_created_at;
crate::Result must be declared.ApiError as result error type, you should implement From<String> into inner error typeFrom<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,
}
#[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
}
}
:.
#[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,
}
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
}
}
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.
:org-id to :organization-id.
create function as pub fn create(&self, organization_id: &str, name: String, user_count: u64, org_id: String)org_id because all Client functions have org-id as their first argument. And it may indicate the org_id: String.
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,
}