22 releases
| 0.6.15 | Nov 19, 2025 |
|---|---|
| 0.6.13 | Oct 23, 2025 |
| 0.6.7 | Jan 31, 2025 |
#286 in Procedural macros
395KB
9K
SLoC
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 (
actionandaction_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
ExampleModelas anapi_model, the following methods are available:ExampleModel::get_client(endpoint)→ ReturnsExampleModelClientExampleModel::get_repository(postgres_pool)→ ReturnsExampleModelRepository(only forserverfeature)
2. API Client Structure
- Named with the suffix
Client(e.g.,ExampleModelClient). - Provides API actions based on the
baseattribute.
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 whereiter_typeisQueryResponse.- At this,
iter_typemust implementFrom<(i64, T)>trait.
- At this,
- The query parameters such as
sizeandpageare supported to control the data retrieval. - The response contains both the data and metadata (e.g., total count, pagination details).
Query paramstructure, which is named by suffix ofQueryis composed with fields described asqueryableorquery_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**
queryableonly makes a field toQuerystructure.query_actionmakes 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_actionin field attribute makes a field with the same type toReadActionstructure.
- If you want to change action type, you can use
relatedattiribute.related = CustomActionRequestmakesClientuseCustomActionRequest
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::Resultmust be declared.- For using
ApiErroras result error type, you should implementFrom<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-idto:organization-id.- It generate
createfunction aspub fn create(&self, organization_id: &str, name: String, user_count: u64, org_id: String)
- It generate
- However, usually you might not need to make a additional parameter or
org_idbecause allClientfunctions haveorg-idas their first argument. And it may indicate theorg_id: String.- This means usually,
many_to_onerelationship will be expressed as a child endpoint in HTTP API concept.
- This means usually,
#[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,
}
Dependencies
~9–13MB
~224K SLoC