use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
pub struct Endpoints {
pub enable_openapi: bool,
pub struct_name: syn::Ident,
pub vis: syn::Visibility,
}
impl Endpoints {
fn create_entity_path(&self) -> TokenStream {
if self.enable_openapi {
quote! {
#[::utoipa::path(
put,
path = "/",
tag = "entity",
request_body = Entity,
responses(
(status = 200, description = "Entity created successfully", body = OperationResponse),
(status = 500, description = "Internal server error", body = ::stately::ApiError)
)
)]
}
} else {
quote! {}
}
}
fn list_all_entities_path(&self) -> TokenStream {
if self.enable_openapi {
quote! {
#[::utoipa::path(
get,
path = "/list",
tag = "entity",
responses(
(status = 200, description = "List all entities", body = ListResponse),
(status = 500, description = "Internal server error", body = ::stately::ApiError)
)
)]
}
} else {
quote! {}
}
}
fn list_entities_path(&self) -> TokenStream {
if self.enable_openapi {
quote! {
#[::utoipa::path(
get,
path = "/list/{type}",
tag = "entity",
params(("type" = StateEntry, Path, description = "Entity type to list")),
responses(
(status = 200, description = "List entities by type", body = ListResponse),
(status = 500, description = "Internal server error", body = ::stately::ApiError)
),
)]
}
} else {
quote! {}
}
}
fn get_entity_by_id_path(&self) -> TokenStream {
if self.enable_openapi {
quote! {
#[::utoipa::path(
get,
path = "/{id}",
tag = "entity",
params(
("id" = String, Path, description = "Entity ID"),
GetEntityQuery
),
responses(
(status = 200, description = "Successfully retrieved entity", body = GetEntityResponse),
(status = 404, description = "Entity not found", body = ::stately::ApiError),
(status = 500, description = "Internal server error", body = ::stately::ApiError)
)
)]
}
} else {
quote! {}
}
}
fn update_entity_path(&self) -> TokenStream {
if self.enable_openapi {
quote! {
#[::utoipa::path(
post,
path = "/{id}",
tag = "entity",
params(("id" = String, Path, description = "Entity ID")),
request_body = Entity,
responses(
(status = 200, description = "Entity updated successfully", body = OperationResponse),
(status = 404, description = "Entity not found", body = ::stately::ApiError),
(status = 500, description = "Internal server error", body = ::stately::ApiError)
)
)]
}
} else {
quote! {}
}
}
fn patch_entity_by_id_path(&self) -> TokenStream {
if self.enable_openapi {
quote! {
#[::utoipa::path(
patch,
path = "/{id}",
tag = "entity",
params(("id" = String, Path, description = "Entity ID")),
request_body = Entity,
responses(
(status = 200, description = "Entity patched successfully", body = OperationResponse),
(status = 404, description = "Entity not found", body = ::stately::ApiError),
(status = 500, description = "Internal server error", body = ::stately::ApiError)
)
)]
}
} else {
quote! {}
}
}
fn remove_entity_path(&self) -> TokenStream {
if self.enable_openapi {
quote! {
#[::utoipa::path(
delete,
path = "/{entry}/{id}",
tag = "entity",
params(
("entry" = StateEntry, Path, description = "Entity type"),
("id" = String, Path, description = "Entity ID")
),
responses(
(status = 200, description = "Entity removed successfully", body = OperationResponse),
(status = 404, description = "Entity not found", body = ::stately::ApiError),
(status = 500, description = "Internal server error", body = ::stately::ApiError)
)
)]
}
} else {
quote! {}
}
}
fn get_entities_path(&self) -> TokenStream {
if self.enable_openapi {
quote! {
#[::utoipa::path(
get,
path = "/",
tag = "entity",
params(
("name" = Option<String>, Query, description = "Identifier of entity, ie id or name"),
("type" = Option<StateEntry>, Query, description = "Type of entity")
),
responses(
(status = 200, description = "Get entities with filters", body = EntitiesResponse),
(status = 500, description = "Internal server error", body = ::stately::ApiError)
),
)]
}
} else {
quote! {}
}
}
}
impl ToTokens for Endpoints {
fn to_tokens(&self, tokens: &mut TokenStream) {
let struct_name = &self.struct_name;
let vis = &self.vis;
let create_entity_path = self.create_entity_path();
let update_entity_path = self.update_entity_path();
let patch_entity_by_id_path = self.patch_entity_by_id_path();
let remove_entity_path = self.remove_entity_path();
let list_all_entities_path = self.list_all_entities_path();
let list_entities_path = self.list_entities_path();
let get_entities_path = self.get_entities_path();
let get_entity_by_id_path = self.get_entity_by_id_path();
tokens.extend(quote! {
#create_entity_path
#vis async fn create_entity(
::axum::extract::State(stately): ::axum::extract::State<#struct_name>,
::axum::Json(entity): ::axum::Json<Entity>,
) -> ::axum::response::Response {
use ::axum::response::IntoResponse;
let mut state = stately.state.write().await;
let id = state.create_entity(entity.clone());
let mut response = ::axum::Json(OperationResponse {
id: id.clone(),
message: format!("Entity created")
}).into_response();
response.extensions_mut().insert(ResponseEvent::Created { id, entity });
response
}
#update_entity_path
pub async fn update_entity(
::axum::extract::State(stately): ::axum::extract::State<#struct_name>,
::axum::extract::Path(id): ::axum::extract::Path<String>,
::axum::Json(entity): ::axum::Json<Entity>,
) -> ::axum::response::Response {
use ::axum::response::IntoResponse;
let mut state = stately.state.write().await;
match state.update_entity(&id, entity.clone()) {
Ok(_) => {
let entity_id: ::stately::EntityId = id.into();
let mut response = ::axum::Json(OperationResponse {
id: entity_id.clone(),
message: format!("Entity updated")
}).into_response();
response.extensions_mut().insert(ResponseEvent::Updated { id: entity_id, entity });
response
}
Err(e) => e.into_response()
}
}
#patch_entity_by_id_path
pub async fn patch_entity_by_id(
::axum::extract::State(stately): ::axum::extract::State<#struct_name>,
::axum::extract::Path(id): ::axum::extract::Path<String>,
::axum::Json(entity): ::axum::Json<Entity>,
) -> ::axum::response::Response {
use ::axum::response::IntoResponse;
let mut state = stately.state.write().await;
match state.update_entity(&id, entity.clone()) {
Ok(_) => {
let entity_id: ::stately::EntityId = id.into();
let mut response = ::axum::Json(OperationResponse {
id: entity_id.clone(),
message: format!("Entity patched")
}).into_response();
response.extensions_mut().insert(ResponseEvent::Updated { id: entity_id, entity });
response
}
Err(e) => e.into_response()
}
}
#remove_entity_path
pub async fn remove_entity(
::axum::extract::State(stately): ::axum::extract::State<#struct_name>,
::axum::extract::Path((entry, id)): ::axum::extract::Path<(StateEntry, String)>,
) -> ::axum::response::Response {
use ::axum::response::IntoResponse;
let mut state = stately.state.write().await;
if let Err(e) = state.remove_entity(&id, entry) {
return e.into_response();
};
let entity_id: ::stately::EntityId = id.into();
let mut response = ::axum::Json(OperationResponse {
id: entity_id.clone(),
message: format!("Entity removed")
}).into_response();
response.extensions_mut().insert(ResponseEvent::Deleted { id: entity_id, entry });
response
}
#list_all_entities_path
pub async fn list_all_entities(
::axum::extract::State(stately): ::axum::extract::State<#struct_name>,
) -> ::stately::Result<::axum::Json<ListResponse>> {
let state = stately.state.read().await;
let entities = state.list_entities(None);
Ok(::axum::Json(ListResponse { entities }))
}
#list_entities_path
pub async fn list_entities(
::axum::extract::State(stately): ::axum::extract::State<#struct_name>,
::axum::extract::Path(entity_type): ::axum::extract::Path<StateEntry>,
) -> ::stately::Result<::axum::Json<ListResponse>> {
let state = stately.state.read().await;
let entities = state.list_entities(Some(entity_type));
Ok(::axum::Json(ListResponse { entities }))
}
#get_entities_path
pub async fn get_entities(
::axum::extract::State(stately): ::axum::extract::State<#struct_name>,
) -> ::stately::Result<::axum::Json<EntitiesResponse>> {
let state = stately.state.read().await;
let entities = state.search_entities("");
Ok(::axum::Json(EntitiesResponse { entities: EntitiesMap { entities } }))
}
#get_entity_by_id_path
pub async fn get_entity_by_id(
::axum::extract::State(stately): ::axum::extract::State<#struct_name>,
::axum::extract::Path(id): ::axum::extract::Path<String>,
::axum::extract::Query(query): ::axum::extract::Query<GetEntityQuery>,
) -> ::stately::Result<::axum::Json<GetEntityResponse>> {
let state = stately.state.read().await;
let Some((id, entity)) = state.get_entity(&id, query.entity_type) else {
return Err(::stately::Error::NotFound(format!("Entity with ID {id} not found")))
};
Ok(::axum::Json(GetEntityResponse { id, entity }))
}
});
}
}