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