Skip to content

Commit

Permalink
feat: implement flareon::main macro for easy framework bootstrapping (#…
Browse files Browse the repository at this point in the history
…77)

This is the first step in making Flareon an actual "framework" rather
than just a library. This macro currently doesn't do much now, but
eventually we'd like to have an entire CLI that would be automatically
generated for each Flareon project and would allow to do some common
operations (run server, run migrations, copy static files to a
directory, etc.), as well as allow Flareon users to define their own
ones.
  • Loading branch information
m4tx authored Nov 25, 2024
1 parent f3af778 commit 96260c8
Show file tree
Hide file tree
Showing 20 changed files with 161 additions and 103 deletions.
6 changes: 0 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions examples/admin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@ description = "Admin panel - Flareon example."
edition = "2021"

[dependencies]
env_logger = "0.11.5"
flareon = { path = "../../flareon" }
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
13 changes: 4 additions & 9 deletions examples/admin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@ impl FlareonApp for HelloApp {
}
}

#[tokio::main]
async fn main() {
env_logger::init();

#[flareon::main]
async fn main() -> flareon::Result<FlareonProject> {
let flareon_project = FlareonProject::builder()
.config(
ProjectConfig::builder()
Expand All @@ -57,10 +55,7 @@ async fn main() {
.middleware_with_context(StaticFilesMiddleware::from_app_context)
.middleware(SessionMiddleware::new())
.build()
.await
.unwrap();
.await?;

flareon::run(flareon_project, "127.0.0.1:8000")
.await
.unwrap();
Ok(flareon_project)
}
1 change: 0 additions & 1 deletion examples/hello-world/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ edition = "2021"

[dependencies]
flareon = { path = "../../flareon" }
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
11 changes: 4 additions & 7 deletions examples/hello-world/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,12 @@ impl FlareonApp for HelloApp {
}
}

#[tokio::main]
async fn main() {
#[flareon::main]
async fn main() -> flareon::Result<FlareonProject> {
let flareon_project = FlareonProject::builder()
.register_app_with_views(HelloApp, "")
.build()
.await
.unwrap();
.await?;

flareon::run(flareon_project, "127.0.0.1:8000")
.await
.unwrap();
Ok(flareon_project)
}
1 change: 0 additions & 1 deletion examples/sessions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ edition = "2021"
[dependencies]
askama = "0.12.1"
flareon = { path = "../../flareon" }
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
11 changes: 4 additions & 7 deletions examples/sessions/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,13 @@ impl FlareonApp for HelloApp {
}
}

#[tokio::main]
async fn main() {
#[flareon::main]
async fn main() -> flareon::Result<FlareonProject> {
let flareon_project = FlareonProject::builder()
.register_app_with_views(HelloApp, "")
.middleware(SessionMiddleware::new())
.build()
.await
.unwrap();
.await?;

flareon::run(flareon_project, "127.0.0.1:8000")
.await
.unwrap();
Ok(flareon_project)
}
2 changes: 0 additions & 2 deletions examples/todo-list/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,3 @@ edition = "2021"
[dependencies]
askama = "0.12.1"
flareon = { path = "../../flareon" }
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
env_logger = "0.11.5"
59 changes: 32 additions & 27 deletions examples/todo-list/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
mod migrations;

use askama::Template;
use flareon::db::migrations::MigrationEngine;
use flareon::db::{model, query, Database, Model};
use flareon::config::{DatabaseConfig, ProjectConfig};
use flareon::db::migrations::DynMigration;
use flareon::db::{model, query, Model};
use flareon::forms::Form;
use flareon::request::{Request, RequestExt};
use flareon::response::{Response, ResponseExt};
use flareon::router::{Route, Router};
use flareon::{reverse, Body, FlareonApp, FlareonProject, StatusCode};
use tokio::sync::OnceCell;

#[derive(Debug, Clone)]
#[model]
Expand All @@ -24,12 +24,8 @@ struct IndexTemplate<'a> {
todo_items: Vec<TodoItem>,
}

static DB: OnceCell<Database> = OnceCell::const_new();

async fn index(request: Request) -> flareon::Result<Response> {
let db = DB.get().unwrap();

let todo_items = TodoItem::objects().all(db).await?;
let todo_items = TodoItem::objects().all(request.db()).await?;
let index_template = IndexTemplate {
request: &request,
todo_items,
Expand All @@ -49,12 +45,11 @@ async fn add_todo(mut request: Request) -> flareon::Result<Response> {
let todo_form = TodoForm::from_request(&mut request).await?.unwrap();

{
let db = DB.get().unwrap();
TodoItem {
id: 0,
title: todo_form.title,
}
.save(db)
.save(request.db())
.await?;
}

Expand All @@ -69,8 +64,9 @@ async fn remove_todo(request: Request) -> flareon::Result<Response> {
let todo_id = todo_id.parse::<i32>().expect("todo_id is not a number");

{
let db = DB.get().unwrap();
query!(TodoItem, $id == todo_id).delete(db).await?;
query!(TodoItem, $id == todo_id)
.delete(request.db())
.await?;
}

Ok(reverse!(request, "index"))
Expand All @@ -83,6 +79,16 @@ impl FlareonApp for TodoApp {
"todo-app"
}

fn migrations(&self) -> Vec<Box<dyn DynMigration>> {
// TODO: this is way too complicated for the user-facing API
#[allow(trivial_casts)]
migrations::MIGRATIONS
.iter()
.copied()
.map(|x| Box::new(x) as Box<dyn DynMigration>)
.collect()
}

fn router(&self) -> Router {
Router::with_urls([
Route::with_handler_and_name("/", index, "index"),
Expand All @@ -92,23 +98,22 @@ impl FlareonApp for TodoApp {
}
}

#[tokio::main]
async fn main() {
env_logger::init();

let db = DB
.get_or_init(|| async { Database::new("sqlite::memory:").await.unwrap() })
.await;
MigrationEngine::new(migrations::MIGRATIONS.iter().copied())
.run(db)
.await
.unwrap();

#[flareon::main]
async fn main() -> flareon::Result<FlareonProject> {
let todo_project = FlareonProject::builder()
.config(
ProjectConfig::builder()
.database_config(
DatabaseConfig::builder()
.url("sqlite::memory:")
.build()
.unwrap(),
)
.build(),
)
.register_app_with_views(TodoApp, "")
.build()
.await
.unwrap();
.await?;

flareon::run(todo_project, "127.0.0.1:8080").await.unwrap();
Ok(todo_project)
}
10 changes: 10 additions & 0 deletions flareon-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod dbtest;
mod form;
mod main_fn;
mod model;
mod query;

Expand All @@ -12,6 +13,7 @@ use syn::{parse_macro_input, ItemFn};

use crate::dbtest::fn_to_dbtest;
use crate::form::impl_form_for_struct;
use crate::main_fn::fn_to_flareon_main;
use crate::model::impl_model_for_struct;
use crate::query::{query_to_tokens, Query};

Expand Down Expand Up @@ -122,6 +124,14 @@ pub fn dbtest(_args: TokenStream, input: TokenStream) -> TokenStream {
.into()
}

#[proc_macro_attribute]
pub fn main(_args: TokenStream, input: TokenStream) -> TokenStream {
let fn_input = parse_macro_input!(input as ItemFn);
fn_to_flareon_main(fn_input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}

pub(crate) fn flareon_ident() -> proc_macro2::TokenStream {
let flareon_crate = crate_name("flareon").expect("flareon is not present in `Cargo.toml`");
match flareon_crate {
Expand Down
40 changes: 40 additions & 0 deletions flareon-macros/src/main_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::ItemFn;

use crate::flareon_ident;

pub(super) fn fn_to_flareon_main(main_function_decl: ItemFn) -> syn::Result<TokenStream> {
let mut new_main_decl = main_function_decl.clone();
new_main_decl.sig.ident =
syn::Ident::new("__flareon_main", main_function_decl.sig.ident.span());

if !main_function_decl.sig.inputs.is_empty() {
return Err(syn::Error::new_spanned(
main_function_decl.sig.inputs,
"flareon::main function must have zero arguments",
));
}

let crate_name = flareon_ident();
let result = quote! {
fn main() {
let body = async {
let project: #crate_name::FlareonProject = __flareon_main().await.unwrap();
#crate_name::run_cli(project).await.unwrap();

#new_main_decl
};
#[allow(clippy::expect_used)]
{
return #crate_name::__private::tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(body);
}
}

};
Ok(result)
}
8 changes: 8 additions & 0 deletions flareon-macros/tests/compile_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ fn func_query() {
t.compile_fail("tests/ui/func_query_double_field.rs");
t.compile_fail("tests/ui/func_query_invalid_field.rs");
}

#[rustversion::attr(not(nightly), ignore)]
#[test]
fn attr_main() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/attr_main.rs");
t.compile_fail("tests/ui/attr_main_args.rs");
}
6 changes: 6 additions & 0 deletions flareon-macros/tests/ui/attr_main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use flareon::FlareonProject;

#[flareon::main]
async fn main() -> flareon::Result<FlareonProject> {
std::process::exit(0);
}
4 changes: 4 additions & 0 deletions flareon-macros/tests/ui/attr_main_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[flareon::main]
async fn main(arg: i32) -> flareon::Result<FlareonProject> {
std::process::exit(0);
}
11 changes: 11 additions & 0 deletions flareon-macros/tests/ui/attr_main_args.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
error: flareon::main function must have zero arguments
--> tests/ui/attr_main_args.rs:2:15
|
2 | async fn main(arg: i32) -> flareon::Result<FlareonProject> {
| ^^^^^^^^

error[E0601]: `main` function not found in crate `$CRATE`
--> tests/ui/attr_main_args.rs:4:2
|
4 | }
| ^ consider adding a `main` function to `$DIR/tests/ui/attr_main_args.rs`
2 changes: 1 addition & 1 deletion flareon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ sync_wrapper.workspace = true
thiserror.workspace = true
time.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
tower.workspace = true
tower = { workspace = true, features = ["util"] }
tower-sessions = { workspace = true, features = ["memory-store"] }

[dev-dependencies]
Expand Down
Loading

0 comments on commit 96260c8

Please sign in to comment.