use modelx;
use modelx::{FieldType, Modelx};
use proc_macro2::TokenStream;
use quote::quote;
pub trait Module {
fn options(_modelx: &Modelx) -> TokenStream {
quote! {}
}
fn options_embed(_modelx: &Modelx) -> TokenStream {
quote! {}
}
fn init(_modelx: &Modelx) -> TokenStream {
quote! {}
}
fn boot(_modelx: &Modelx) -> TokenStream {
quote! {}
}
}
pub struct Model {}
pub struct Http {}
impl Module for Model {
fn options(_modelx: &Modelx) -> TokenStream {
quote! {}
}
fn options_embed(_modelx: &Modelx) -> TokenStream {
quote! {}
}
fn boot(_modelx: &Modelx) -> TokenStream {
quote! {}
}
fn init(modelx: &Modelx) -> TokenStream {
let types = modelx.definitions.iter().map(|(name, definition)| {
let struct_ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
let fields = definition.fields.iter().map(|(field_name, field)| {
let field_name = syn::Ident::new(field_name, proc_macro2::Span::call_site());
let field_type = match field.field_type {
FieldType::String => quote! { String },
FieldType::UUID => quote! { String },
FieldType::Integer => quote! { i32 },
};
if field.is_key {
quote! {
pub #field_name: #field_type
}
} else {
quote! {
pub #field_name: Option<#field_type>
}
}
});
quote! {
#[derive(Debug, serde::Serialize, serde::Deserialize, Default, sqlx::FromRow)]
pub struct #struct_ident {
#(#fields),*
}
}
});
quote! {
pub mod Model {
#(#types)*
}
}
}
}
impl Module for Http {
fn options(_modelx: &Modelx) -> TokenStream {
quote! {
pub struct OptHttp {
pub port: u16
}
impl Default for OptHttp {
fn default() -> Self {
OptHttp {
port: 8080,
}
}
}
}
}
fn options_embed(_modelx: &Modelx) -> TokenStream {
quote! {
pub http: OptHttp,
}
}
fn init(_modelx: &Modelx) -> TokenStream {
quote! {}
}
fn boot(modelx: &Modelx) -> TokenStream {
let http_routes = modelx
.definitions
.iter()
.filter(|(_name, definition)| definition.annotations.iter().any(|anno| anno == "@http"))
.map(|(name, _definition)| {
let endpoint = format!("/{}", name);
let fn_name = format!("axum_get_{}", name);
let fn_ident = syn::Ident::new(&fn_name, proc_macro2::Span::call_site());
quote! {
router = router.route(#endpoint, axum::routing::get(Self::#fn_ident));
}
});
let mut deploy_statements: Vec<String> = vec![];
modelx
.definitions
.iter()
.filter(|(_name, definition)| definition.annotations.iter().any(|anno| anno == "@http"))
.for_each(|(name, definition)| {
let mut deploy_sql = "".to_string();
deploy_sql.push_str(&format!("\nCREATE TABLE IF NOT EXISTS {} (", &name));
definition.fields.iter().for_each(|(field_name, field)| {
let sql_type = match &field.field_type {
FieldType::String => "text",
FieldType::UUID => "text",
FieldType::Integer => "integer",
};
let sql_key = if field.is_key { "PRIMARY KEY" } else { "" };
deploy_sql
.push_str(&format!("\n {} {} {},", &field_name, &sql_type, &sql_key));
});
deploy_sql.pop();
deploy_sql.push_str("\n);");
deploy_statements.push(deploy_sql);
});
let fn_name = "axum_deploy";
let fn_ident = syn::Ident::new(&fn_name, proc_macro2::Span::call_site());
let execute_deploy_statements = deploy_statements.iter().map(|statement| {
quote! {
sqlx::query(#statement).execute(&mut tx).await.unwrap();
}
});
let axum_deploy_endpoint = quote! {
pub async fn #fn_ident(axum::Extension(pool): axum::Extension<sqlx::PgPool>) -> impl axum::response::IntoResponse {
println!("Deploy");
let mut tx = pool.begin().await.unwrap();
#(#execute_deploy_statements)*
tx.commit().await.unwrap();
(axum::http::StatusCode::OK, "success")
}
};
let axum_endpoints = modelx.definitions.iter().filter(|(_name, definition)| definition.annotations.iter().any(|anno| anno == "@http")).map(|(name, _definition)| {
let fn_name = format!("axum_get_{}", name);
let struct_ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
let fn_ident = syn::Ident::new(&fn_name, proc_macro2::Span::call_site());
quote! {
pub async fn #fn_ident(axum::Extension(pool): axum::Extension<sqlx::PgPool>) -> impl axum::response::IntoResponse {
println!("GET for {}", #name);
let result = sqlx::query_as::<_, Model::#struct_ident>(&format!("SELECT * FROM {}", #name)).fetch_all(&pool).await.unwrap();
(axum::http::StatusCode::OK, axum::Json(result))
}
}
});
quote! {
#[derive(Debug)]
pub struct Http {
pub router: axum::Router,
}
impl Http {
pub fn new() -> Self {
let mut router = axum::Router::new().route("/health", axum::routing::get(Self::health));
router = router.route("/deploy", axum::routing::get(Self::axum_deploy));
#(#http_routes)*
Self {
router
}
}
#(#axum_endpoints)*
#axum_deploy_endpoint
pub async fn health() -> impl axum::response::IntoResponse {
(axum::http::StatusCode::OK, "healthy")
}
pub async fn boot(mut self, port: u16) {
let db = sqlx::postgres::PgPoolOptions::new().max_connections(5).connect("postgres://newuser:newUser@localhost:5432/postgres").await.expect("Cannot connect to Postgres database");
self.router = self.router.layer(axum::extract::Extension(db));
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
println!("Listening on http://localhost:{}", &port);
axum::Server::bind(&addr)
.serve(self.router.into_make_service())
.await
.unwrap();
}
}
let http = Http::new();
http.boot(*&options.http.port).await;
}
}
}