Use proc macro for optional optional user.
This commit is contained in:
parent
e310e547f6
commit
695f450c64
18 changed files with 98 additions and 110 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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"
|
||||||
|
|
40
optional_optional_user/src/lib.rs
Normal file
40
optional_optional_user/src/lib.rs
Normal 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)
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>,
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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) %}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
{% block title %}Sign Up for Witch Watch, Bish{% endblock %}
|
{% block title %}Sign Up for Witch Watch, Bish{% endblock %}
|
||||||
|
|
||||||
{% block header %}{% endblock %}
|
{% block header %} {% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue