diff --git a/Cargo.lock b/Cargo.lock index 5d15bd0..19f09b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1166,6 +1166,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "optional_optional_user" +version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.18", +] + [[package]] name = "overload" version = "0.1.1" @@ -2373,6 +2381,7 @@ dependencies = [ "axum-macros", "axum-test", "justerror", + "optional_optional_user", "password-hash", "rand_core", "serde", diff --git a/Cargo.toml b/Cargo.toml index 27e5949..c97229e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,9 @@ axum-login = { version = "0.5", features = ["sqlite", "sqlx"] } unicode-segmentation = "1" async-session = "3" +# proc macros: +optional_optional_user = {path = "optional_optional_user"} + [dev-dependencies] axum-test = "9.0.0" diff --git a/optional_optional_user/Cargo.lock b/optional_optional_user/Cargo.lock new file mode 100644 index 0000000..c5f4677 --- /dev/null +++ b/optional_optional_user/Cargo.lock @@ -0,0 +1,46 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "maybe_optional_user" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" diff --git a/optional_optional_user/Cargo.toml b/optional_optional_user/Cargo.toml new file mode 100644 index 0000000..091ebcc --- /dev/null +++ b/optional_optional_user/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "optional_optional_user" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.28" +syn = "2.0.18" diff --git a/optional_optional_user/src/lib.rs b/optional_optional_user/src/lib.rs new file mode 100644 index 0000000..bc8c2f5 --- /dev/null +++ b/optional_optional_user/src/lib.rs @@ -0,0 +1,40 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemStruct}; + +#[proc_macro_derive(OptionalOptionalUser)] +pub fn derive_optional_optional_user(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemStruct); + + let name = &input.ident; + let has_user = input + .fields + .iter() + .find(|f| { + if let Some(ident) = f.ident.clone() { + ident == "user" + } else { + false + } + }) + .is_some(); + + let has_option = if has_user { + quote!( + ::std::any::TypeId::of::<::std::option::Option>() == self.user.type_id() + ) + } else { + quote!(false) + }; + + let output = quote!( + impl crate::templates::OptionalOptionalUser for #name { + fn has_optional_user(&self) -> bool { + use ::std::any::Any; + #has_user && #has_option + } + } + ); + + TokenStream::from(output) +} diff --git a/src/templates.rs b/src/templates.rs index 4d624f6..6128caf 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,9 +1,14 @@ use askama::Template; +use optional_optional_user::OptionalOptionalUser; use serde::{Deserialize, Serialize}; use crate::User; -#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] +pub trait OptionalOptionalUser { + fn has_optional_user(&self) -> bool; +} + +#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "signup.html")] pub struct CreateUser { pub username: String, @@ -13,7 +18,9 @@ pub struct CreateUser { pub pw_verify: String, } -#[derive(Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser, +)] #[template(path = "signup_success.html")] pub struct CreateUserSuccess(pub User); @@ -24,23 +31,38 @@ pub struct LoginPost { pub password: String, } -#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "login_get.html")] pub struct LoginGet { pub username: String, pub password: String, } -#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "logout_get.html")] pub struct LogoutGet; -#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "logout_post.html")] pub struct LogoutPost; -#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "index.html")] pub struct MainPage { pub user: Option, } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn main_page_has_optional_user() { + assert!(MainPage::default().has_optional_user()); + } + + #[test] + fn signup_success_has_no_optional_user() { + assert!(!CreateUserSuccess::default().has_optional_user()); + } +} diff --git a/src/watches/templates.rs b/src/watches/templates.rs index 5aa9629..10a62fb 100644 --- a/src/watches/templates.rs +++ b/src/watches/templates.rs @@ -1,15 +1,16 @@ use askama::Template; +use optional_optional_user::OptionalOptionalUser; use serde::{Deserialize, Serialize}; -use crate::{User, Watch}; +use crate::{templates::OptionalOptionalUser, User, Watch}; -#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "get_watches.html")] pub struct GetWatches { pub watches: Vec, pub user: Option, } -#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] +#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)] #[template(path = "get_search_watches.html")] pub struct GetSearchWatches {} diff --git a/templates/header_with_user.html b/templates/header_with_user.html index 38b61dc..e3e2fc7 100644 --- a/templates/header_with_user.html +++ b/templates/header_with_user.html @@ -1,6 +1,7 @@ +{% if self.has_optional_user() %} + {% match user %} {% when Some with (usr) %} -
Hello, {{ usr.username }}!
@@ -8,13 +9,14 @@ - {% else %} -
Heya, why don't you log in or sign up?
- {% endmatch %} +{% else %} + +{% endif %} +