Initial commit
This commit is contained in:
commit
bb9084dfed
13 changed files with 1934 additions and 0 deletions
13
.editorconfig
Normal file
13
.editorconfig
Normal file
|
@ -0,0 +1,13 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
11
.rustfmt.toml
Normal file
11
.rustfmt.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
condense_wildcard_suffixes = true
|
||||
edition = "2021"
|
||||
fn_single_line = true
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_matchers = true
|
||||
hard_tabs = true
|
||||
match_block_trailing_comma = true
|
||||
imports_granularity = "Crate"
|
||||
newline_style = "Unix"
|
||||
group_imports = "StdExternalCrate"
|
||||
tab_spaces = 4
|
1653
Cargo.lock
generated
Normal file
1653
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
29
Cargo.toml
Normal file
29
Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
|||
[workspace]
|
||||
members = ["macros", "."]
|
||||
|
||||
[package]
|
||||
name = "leptos_reactive_axum"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "reactive context for axum handlers"
|
||||
authors = ["davidontop <me@davidon.top>"]
|
||||
readme = "README.md"
|
||||
documentation = "https://docs.rs/leptos_reactive_axum"
|
||||
license = "MIT"
|
||||
repository = "https://git.davidon.top/public/leptos_reactive_axum.git"
|
||||
|
||||
[dependencies]
|
||||
leptos_reactive_axum_macros = { path = "./macros" }
|
||||
leptos_reactive = { version = "0.6", features = ["ssr"] }
|
||||
axum = "0.7"
|
||||
scopeguard = "1.2.0"
|
||||
thiserror = "1.0.61"
|
||||
http = "1.1.0"
|
||||
|
||||
[features]
|
||||
nightly = ["leptos_reactive/nightly"]
|
||||
|
||||
[dev-dependencies]
|
||||
axum-test = "15.1.0"
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 DavidOnTop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# ctxt
|
12
macros/Cargo.toml
Normal file
12
macros/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "leptos_reactive_axum_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "2"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
9
macros/src/lib.rs
Normal file
9
macros/src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
mod reactive;
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn reactive(
|
||||
attr: proc_macro::TokenStream,
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
reactive::reactive(attr.into(), input.into()).into()
|
||||
}
|
80
macros/src/reactive.rs
Normal file
80
macros/src/reactive.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::punctuated::Punctuated;
|
||||
|
||||
fn map_fnarg(fnarg: &syn::FnArg) -> Option<TokenStream> {
|
||||
match fnarg {
|
||||
syn::FnArg::Receiver(_rec) => None,
|
||||
syn::FnArg::Typed(pat_typed) => {
|
||||
let pat = &pat_typed.pat;
|
||||
let ty = match pat_typed.ty.as_ref().to_owned() {
|
||||
syn::Type::Infer(_) => None,
|
||||
_ => Some(pat_typed.ty.as_ref()),
|
||||
};
|
||||
let ty = ty.map(|t| quote! {: #t});
|
||||
Some(quote! {
|
||||
let #pat #ty = ::leptos_reactive_axum::extract().await.unwrap();
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reactive(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let syn::ItemFn {
|
||||
attrs,
|
||||
vis,
|
||||
mut sig,
|
||||
block,
|
||||
} = match syn::parse2(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return err.to_compile_error(),
|
||||
};
|
||||
|
||||
let extract_body = match syn::parse2::<syn::LitBool>(attr) {
|
||||
Ok(attr) => attr,
|
||||
Err(err) => return err.to_compile_error(),
|
||||
}
|
||||
.value();
|
||||
|
||||
let prepend = quote! {
|
||||
let __runtime = leptos_reactive::create_runtime();
|
||||
scopeguard::defer!(__runtime.dispose());
|
||||
leptos_reactive::provide_context(__parts);
|
||||
};
|
||||
let stmts = &block.stmts;
|
||||
|
||||
let mut extractors = Vec::new();
|
||||
|
||||
let body = if extract_body {
|
||||
if let Some(fnarg) = sig.inputs.pop() {
|
||||
Some(fnarg.into_value())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
sig.inputs.iter().for_each(|fnarg| {
|
||||
extractors.push(map_fnarg(fnarg));
|
||||
});
|
||||
|
||||
let extractors = extractors.iter().filter(|e| e.is_some());
|
||||
|
||||
sig.inputs = Punctuated::new();
|
||||
sig.inputs
|
||||
.push(syn::parse2(quote! {__parts: ::http::request::Parts}).unwrap());
|
||||
if let Some(body) = body {
|
||||
sig.inputs.push(body);
|
||||
}
|
||||
|
||||
quote! {
|
||||
#(#attrs)* #vis #sig {
|
||||
#prepend
|
||||
|
||||
#(#extractors)*
|
||||
|
||||
#(#stmts)*
|
||||
}
|
||||
}
|
||||
}
|
11
src/error.rs
Normal file
11
src/error.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ExtractionError {
|
||||
#[error("axum extracting information failed")]
|
||||
AxumError(String),
|
||||
#[error("leptos_reactive error occured")]
|
||||
LeptosError(String),
|
||||
#[error("the body of the request has been extracted more then once this isn't allowed")]
|
||||
MultipleBodyExtractor,
|
||||
}
|
28
src/lib.rs
Normal file
28
src/lib.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
pub mod error;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use axum::{extract::FromRequestParts, http::request::Parts};
|
||||
use error::ExtractionError;
|
||||
pub use leptos_reactive_axum_macros::reactive;
|
||||
|
||||
pub async fn extract<T>() -> Result<T, ExtractionError>
|
||||
where
|
||||
T: FromRequestParts<()>,
|
||||
T::Rejection: Debug,
|
||||
{
|
||||
extract_with_state::<T, ()>(&()).await
|
||||
}
|
||||
|
||||
pub async fn extract_with_state<T, S>(state: &S) -> Result<T, ExtractionError>
|
||||
where
|
||||
T: FromRequestParts<S>,
|
||||
T::Rejection: Debug,
|
||||
{
|
||||
let mut parts = leptos_reactive::use_context::<Parts>().ok_or_else(|| {
|
||||
ExtractionError::LeptosError("failed to extract Parts from leptos_reactive's Runtime defined by leptos_reactive_axum".to_string())
|
||||
})?;
|
||||
T::from_request_parts(&mut parts, state)
|
||||
.await
|
||||
.map_err(|e| ExtractionError::AxumError(format!("{e:?}")))
|
||||
}
|
64
tests/test.rs
Normal file
64
tests/test.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use axum::{
|
||||
http::{HeaderMap, HeaderValue},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use axum_test::TestServer;
|
||||
use http::HeaderName;
|
||||
use serde_json::json;
|
||||
|
||||
async fn demo_expanded_handler(
|
||||
__parts: ::http::request::Parts,
|
||||
Json(payload): Json<serde_json::Value>,
|
||||
) -> impl IntoResponse {
|
||||
let __runtime = leptos_reactive::create_runtime();
|
||||
scopeguard::defer!(__runtime.dispose());
|
||||
{
|
||||
leptos_reactive::provide_context(__parts);
|
||||
}
|
||||
let headers: HeaderMap = ::leptos_reactive_axum::extract().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
headers.get("macrosareawesome"),
|
||||
Some(&HeaderValue::from_static("YehTheyAre"))
|
||||
);
|
||||
assert_eq!(payload.as_str(), Some("hello there"));
|
||||
|
||||
""
|
||||
}
|
||||
|
||||
#[leptos_reactive_axum::reactive(true)]
|
||||
async fn handler(headers: HeaderMap, Json(payload): Json<serde_json::Value>) -> impl IntoResponse {
|
||||
assert_eq!(
|
||||
headers.get("macrosareawesome"),
|
||||
Some(&HeaderValue::from_static("YehTheyAre"))
|
||||
);
|
||||
assert_eq!(payload.as_str(), Some("hello there"));
|
||||
|
||||
""
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handlers() {
|
||||
let router = Router::new()
|
||||
.route("/demo", get(demo_expanded_handler))
|
||||
.route("/", get(handler));
|
||||
|
||||
let server = TestServer::new(router).unwrap();
|
||||
|
||||
let _ = server
|
||||
.get("/")
|
||||
.add_header(
|
||||
HeaderName::from_static("macrosareawesome"),
|
||||
HeaderValue::from_static("YehTheyAre"),
|
||||
)
|
||||
.json(&json!("hello there"));
|
||||
let _ = server
|
||||
.get("/demo")
|
||||
.add_header(
|
||||
HeaderName::from_static("macrosareawesome"),
|
||||
HeaderValue::from_static("YehTheyAre"),
|
||||
)
|
||||
.json(&json!("hello there"));
|
||||
}
|
Loading…
Reference in a new issue