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"
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
[[package]]
name = "maybe_optional_user"
version = "0.1.0"
dependencies = [
"quote",
"syn 2.0.18",
]
[[package]]
name = "memchr"
version = "2.5.0"
@ -1174,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"
@ -2381,7 +2381,7 @@ dependencies = [
"axum-macros",
"axum-test",
"justerror",
"maybe_optional_user",
"optional_optional_user",
"password-hash",
"rand_core",
"serde",

View File

@ -26,7 +26,7 @@ unicode-segmentation = "1"
async-session = "3"
# proc macros:
maybe_optional_user = {path = "maybe_optional_user"}
optional_optional_user = {path = "optional_optional_user"}
[dev-dependencies]
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]
name = "maybe_optional_user"
name = "optional_optional_user"
version = "0.1.0"
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 resp = s.get("/logout").await;
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]
@ -174,7 +174,7 @@ mod test {
let resp = s.post("/logout").await;
resp.assert_status_ok();
let body = std::str::from_utf8(resp.bytes()).unwrap();
let default = LogoutPost::default().to_string();
let default = LogoutPost.to_string();
assert_eq!(body, &default);
}
@ -205,7 +205,7 @@ mod test {
let resp = s.post("/logout").await;
let body = std::str::from_utf8(resp.bytes()).unwrap();
let default = LogoutPost::default().to_string();
let default = LogoutPost.to_string();
assert_eq!(body, &default);
}
}

View File

@ -122,7 +122,7 @@ pub async fn handle_signup_success(
.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() {
// 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 body = std::str::from_utf8(resp.bytes()).unwrap();
let expected = CreateUserSuccess { user }.to_string();
let expected = CreateUserSuccess(user).to_string();
assert_eq!(&expected, body);
}

View File

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

View File

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

View File

@ -1,18 +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<Watch>,
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")]
pub struct GetSearchWatches {
pub watches: Vec<Watch>,
pub user: Option<User>,
}
pub struct GetSearchWatches {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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