Use proc macro for optional optional user.

This commit is contained in:
Joe Ardent 2023-06-13 15:56:15 -07:00
parent e310e547f6
commit 695f450c64
18 changed files with 98 additions and 110 deletions

18
Cargo.lock generated
View file

@ -1066,14 +1066,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
[[package]]
name = "maybe_optional_user"
version = "0.1.0"
dependencies = [
"quote",
"syn 2.0.18",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -1174,6 +1166,14 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "optional_optional_user"
version = "0.1.0"
dependencies = [
"quote",
"syn 2.0.18",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -2381,7 +2381,7 @@ dependencies = [
"axum-macros", "axum-macros",
"axum-test", "axum-test",
"justerror", "justerror",
"maybe_optional_user", "optional_optional_user",
"password-hash", "password-hash",
"rand_core", "rand_core",
"serde", "serde",

View file

@ -26,7 +26,7 @@ unicode-segmentation = "1"
async-session = "3" async-session = "3"
# proc macros: # proc macros:
maybe_optional_user = {path = "maybe_optional_user"} optional_optional_user = {path = "optional_optional_user"}
[dev-dependencies] [dev-dependencies]
axum-test = "9.0.0" axum-test = "9.0.0"

View file

@ -1,34 +0,0 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemStruct};
#[proc_macro_derive(ImplMaybeOptionalUser)]
pub fn derive_maybe_optional_user(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
let name = &input.ident;
let mut has_opt_user = false;
input.fields.iter().inspect(|f| {
if let Some(ident) = f.ident.clone() {
match &f.ty {
syn::Type::Path(path)
if !path.path.segments.is_empty()
&& path.path.segments.first().unwrap().ident == "Option" =>
{
has_opt_user = true && ident == "user";
}
_ => {}
}
}
});
let output = quote! {
impl crate::templates::MaybeOptionalUser for #name {
fn has_optional_user() -> bool {
#has_opt_user
}
}
};
TokenStream::from(output)
}

View file

@ -1,5 +1,5 @@
[package] [package]
name = "maybe_optional_user" name = "optional_optional_user"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"

View file

@ -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<crate::User>>() == 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)
}

View file

@ -165,7 +165,7 @@ mod test {
let s = server().await; let s = server().await;
let resp = s.get("/logout").await; let resp = s.get("/logout").await;
let body = std::str::from_utf8(resp.bytes()).unwrap().to_string(); let body = std::str::from_utf8(resp.bytes()).unwrap().to_string();
assert_eq!(body, LogoutGet::default().to_string()); assert_eq!(body, LogoutGet.to_string());
} }
#[tokio::test] #[tokio::test]
@ -174,7 +174,7 @@ mod test {
let resp = s.post("/logout").await; let resp = s.post("/logout").await;
resp.assert_status_ok(); resp.assert_status_ok();
let body = std::str::from_utf8(resp.bytes()).unwrap(); let body = std::str::from_utf8(resp.bytes()).unwrap();
let default = LogoutPost::default().to_string(); let default = LogoutPost.to_string();
assert_eq!(body, &default); assert_eq!(body, &default);
} }
@ -205,7 +205,7 @@ mod test {
let resp = s.post("/logout").await; let resp = s.post("/logout").await;
let body = std::str::from_utf8(resp.bytes()).unwrap(); let body = std::str::from_utf8(resp.bytes()).unwrap();
let default = LogoutPost::default().to_string(); let default = LogoutPost.to_string();
assert_eq!(body, &default); assert_eq!(body, &default);
} }
} }

View file

@ -122,7 +122,7 @@ pub async fn handle_signup_success(
.unwrap_or_default() .unwrap_or_default()
}; };
let mut resp = CreateUserSuccess { user: user.clone() }.into_response(); let mut resp = CreateUserSuccess(user.clone()).into_response();
if user.username.is_empty() || id.is_empty() { if user.username.is_empty() || id.is_empty() {
// redirect to front page if we got here without a valid witch ID // redirect to front page if we got here without a valid witch ID
@ -260,7 +260,7 @@ mod test {
let resp = server.get(&path).expect_success().await; let resp = server.get(&path).expect_success().await;
let body = std::str::from_utf8(resp.bytes()).unwrap(); let body = std::str::from_utf8(resp.bytes()).unwrap();
let expected = CreateUserSuccess { user }.to_string(); let expected = CreateUserSuccess(user).to_string();
assert_eq!(&expected, body); assert_eq!(&expected, body);
} }

View file

@ -1,15 +1,14 @@
use askama::Template; use askama::Template;
use optional_optional_user::OptionalOptionalUser;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::User; use crate::User;
pub trait MaybeOptionalUser { pub trait OptionalOptionalUser {
fn has_optional_user() -> bool { fn has_optional_user(&self) -> bool;
false
}
} }
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "signup.html")] #[template(path = "signup.html")]
pub struct CreateUser { pub struct CreateUser {
pub username: String, pub username: String,
@ -17,24 +16,13 @@ pub struct CreateUser {
pub email: Option<String>, pub email: Option<String>,
pub password: String, pub password: String,
pub pw_verify: String, pub pw_verify: String,
user: Option<User>,
}
impl MaybeOptionalUser for CreateUser {
fn has_optional_user() -> bool {
true
}
} }
#[derive(Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq)] #[derive(
Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser,
)]
#[template(path = "signup_success.html")] #[template(path = "signup_success.html")]
pub struct CreateUserSuccess { pub struct CreateUserSuccess(pub User);
pub user: User,
}
impl MaybeOptionalUser for CreateUserSuccess {
fn has_optional_user() -> bool {
false
}
}
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)]
#[template(path = "login_post.html")] #[template(path = "login_post.html")]
@ -42,49 +30,39 @@ pub struct LoginPost {
pub username: String, pub username: String,
pub password: String, pub password: String,
} }
impl MaybeOptionalUser for LoginPost {
fn has_optional_user() -> bool {
false
}
}
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "login_get.html")] #[template(path = "login_get.html")]
pub struct LoginGet { pub struct LoginGet {
pub username: String, pub username: String,
pub password: String, pub password: String,
} }
impl MaybeOptionalUser for LoginGet {
fn has_optional_user() -> bool {
false
}
}
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "logout_get.html")] #[template(path = "logout_get.html")]
pub struct LogoutGet; pub struct LogoutGet;
impl MaybeOptionalUser for LogoutGet {
fn has_optional_user() -> bool {
false
}
}
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "logout_post.html")] #[template(path = "logout_post.html")]
pub struct LogoutPost; pub struct LogoutPost;
impl MaybeOptionalUser for LogoutPost {
fn has_optional_user() -> bool {
false
}
}
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "index.html")] #[template(path = "index.html")]
pub struct MainPage { pub struct MainPage {
pub user: Option<User>, pub user: Option<User>,
} }
impl MaybeOptionalUser for MainPage {
fn has_optional_user() -> bool { #[cfg(test)]
true 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());
} }
} }

View file

@ -1,5 +1,4 @@
use std::{ use std::{
any::Any,
fmt::{Debug, Display}, fmt::{Debug, Display},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };

View file

@ -1,18 +1,16 @@
use askama::Template; use askama::Template;
use optional_optional_user::OptionalOptionalUser;
use serde::{Deserialize, Serialize}; 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")] #[template(path = "get_watches.html")]
pub struct GetWatches { pub struct GetWatches {
pub watches: Vec<Watch>, pub watches: Vec<Watch>,
pub user: Option<User>, pub user: Option<User>,
} }
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)] #[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "get_search_watches.html")] #[template(path = "get_search_watches.html")]
pub struct GetSearchWatches { pub struct GetSearchWatches {}
pub watches: Vec<Watch>,
pub user: Option<User>,
}

View file

@ -4,11 +4,11 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{{ title }} - Witch Watch{% endblock %}</title> <title>{% block title %}{{ title }} - Witch Watch{% endblock %}</title>
{% block head %}{% include "header.html" %}{% endblock %} {% block head %}{% endblock %}
</head> </head>
<body> <body>
<div id="header"> <div id="header">
{% block header %}{% endblock %} {% block header %}{% include "header_with_user.html" %}{% endblock %}
</div> </div>
<div id="content"> <div id="content">
{% block content %}{% endblock %} {% block content %}{% endblock %}

View file

@ -1,4 +1,4 @@
{% if Self::has_optional_user() %} {% if self.has_optional_user() %}
{% match user %} {% match user %}
{% when Some with (usr) %} {% when Some with (usr) %}

View file

@ -2,6 +2,8 @@
{% block title %}Login to Witch Watch, Bish{% endblock %} {% block title %}Login to Witch Watch, Bish{% endblock %}
{% block header %}{% endblock %}
{% block content %} {% block content %}
<p> <p>

View file

@ -2,6 +2,8 @@
{% block title %}Logout of Witch Watch, Bish{% endblock %} {% block title %}Logout of Witch Watch, Bish{% endblock %}
{% block header %}{% endblock %}
{% block content %} {% block content %}
<p> <p>

View file

@ -2,6 +2,8 @@
{% block title %}Thanks for Signing Up for Witch Watch, Bish{% endblock %} {% block title %}Thanks for Signing Up for Witch Watch, Bish{% endblock %}
{% block header %}{% endblock %}
{% block content %} {% block content %}
<h1>Goodbye</h1> <h1>Goodbye</h1>

View file

@ -3,11 +3,12 @@
{% block title %}Thanks for Signing Up for Witch Watch, Bish{% endblock %} {% block title %}Thanks for Signing Up for Witch Watch, Bish{% endblock %}
{% block content %} {% block content %}
{% block header %}{% endblock %}
<h1>You did it!</h1> <h1>You did it!</h1>
<div id="signup_success"><p> <div id="signup_success"><p>
{{ self.user }} {{ self.0 }}
</p> </p>
</div> </div>