diff --git a/Cargo.lock b/Cargo.lock index 5f6e762..ff71225 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5295,6 +5295,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.12", + "tracing", "utoipa", ] @@ -5324,8 +5325,10 @@ name = "shield-bootstrap" version = "0.0.4" dependencies = [ "dioxus", + "leptos", "shield", "shield-dioxus", + "shield-leptos", ] [[package]] diff --git a/examples/leptos-actix/Cargo.toml b/examples/leptos-actix/Cargo.toml index 39f3e97..380c546 100644 --- a/examples/leptos-actix/Cargo.toml +++ b/examples/leptos-actix/Cargo.toml @@ -48,6 +48,7 @@ leptos_actix = { workspace = true, optional = true } leptos_meta.workspace = true leptos_router.workspace = true shield.workspace = true +# shield-bootstrap = { workspace = true, features = ["leptos"] } shield-leptos.workspace = true shield-leptos-actix = { workspace = true, optional = true } shield-memory = { workspace = true, optional = true } diff --git a/examples/leptos-actix/src/home.rs b/examples/leptos-actix/src/home.rs index f304ac3..58d1a0c 100644 --- a/examples/leptos-actix/src/home.rs +++ b/examples/leptos-actix/src/home.rs @@ -1,10 +1,10 @@ use leptos::{either::Either, prelude::*}; use leptos_router::components::A; -use shield_leptos::integration::LeptosUser; +use shield_leptos::LeptosUser; #[server] pub async fn user() -> Result, ServerFnError> { - use shield_leptos::context::extract_user; + use shield_leptos::extract_user; Ok(extract_user().await) } diff --git a/examples/leptos-axum/Cargo.toml b/examples/leptos-axum/Cargo.toml index 1763c87..ea2a753 100644 --- a/examples/leptos-axum/Cargo.toml +++ b/examples/leptos-axum/Cargo.toml @@ -44,6 +44,7 @@ leptos_axum = { workspace = true, optional = true } leptos_meta.workspace = true leptos_router.workspace = true shield.workspace = true +# shield-bootstrap = { workspace = true, features = ["leptos"] } shield-leptos.workspace = true shield-leptos-axum = { workspace = true, features = [ "utoipa", diff --git a/examples/leptos-axum/src/home.rs b/examples/leptos-axum/src/home.rs index 1aeb617..c1a6a68 100644 --- a/examples/leptos-axum/src/home.rs +++ b/examples/leptos-axum/src/home.rs @@ -1,10 +1,10 @@ use leptos::{either::Either, prelude::*}; use leptos_router::components::A; -use shield_leptos::integration::LeptosUser; +use shield_leptos::LeptosUser; #[server] pub async fn user() -> Result, ServerFnError> { - use shield_leptos::context::extract_user; + use shield_leptos::extract_user; Ok(extract_user().await) } diff --git a/packages/core/shield/Cargo.toml b/packages/core/shield/Cargo.toml index 059a2bd..09bf050 100644 --- a/packages/core/shield/Cargo.toml +++ b/packages/core/shield/Cargo.toml @@ -20,4 +20,5 @@ futures.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true thiserror.workspace = true +tracing.workspace = true utoipa = { workspace = true, optional = true } diff --git a/packages/core/shield/src/action.rs b/packages/core/shield/src/action.rs index 665b9a7..8be7d2b 100644 --- a/packages/core/shield/src/action.rs +++ b/packages/core/shield/src/action.rs @@ -1,12 +1,21 @@ use std::any::Any; use async_trait::async_trait; +use serde::{Deserialize, Serialize}; use crate::{ error::ShieldError, form::Form, provider::Provider, request::Request, response::Response, session::Session, }; +// TODO: Think of a better name. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ActionForms { + pub id: String, + pub name: String, + pub forms: Vec
, +} + #[async_trait] pub trait Action: ErasedAction + Send + Sync { fn id(&self) -> String; diff --git a/packages/core/shield/src/shield.rs b/packages/core/shield/src/shield.rs index c6d327d..ba1d9ad 100644 --- a/packages/core/shield/src/shield.rs +++ b/packages/core/shield/src/shield.rs @@ -1,10 +1,11 @@ use std::{any::Any, collections::HashMap, sync::Arc}; use futures::future::try_join_all; +use tracing::warn; use crate::{ - Session, error::ShieldError, form::Form, method::ErasedMethod, options::ShieldOptions, - storage::Storage, user::User, + action::ActionForms, error::ShieldError, method::ErasedMethod, options::ShieldOptions, + session::Session, storage::Storage, user::User, }; #[derive(Clone)] @@ -68,7 +69,8 @@ impl Shield { &self, action_id: &str, session: Session, - ) -> Result, ShieldError> { + ) -> Result { + let mut action_name = None::; let mut forms = vec![]; for (_, method) in self.methods.iter() { @@ -76,6 +78,14 @@ impl Shield { continue; }; + let name = action.erased_name(); + if let Some(action_name) = &action_name { + if *action_name != name { + warn!("Action name mismatch `{}` != `{}`", action_name, name); + } + } + action_name = Some(name); + for provider in method.erased_providers().await? { if !action.erased_condition(&*provider, session.clone())? { continue; @@ -87,7 +97,11 @@ impl Shield { } } - Ok(forms) + Ok(ActionForms { + id: action_id.to_owned(), + name: action_name.unwrap_or(action_id.to_owned()), + forms, + }) } } diff --git a/packages/core/shield/src/shield_dyn.rs b/packages/core/shield/src/shield_dyn.rs index ae5ad2d..6f49655 100644 --- a/packages/core/shield/src/shield_dyn.rs +++ b/packages/core/shield/src/shield_dyn.rs @@ -2,7 +2,9 @@ use std::{any::Any, sync::Arc}; use async_trait::async_trait; -use crate::{Session, error::ShieldError, form::Form, shield::Shield, user::User}; +use crate::{ + action::ActionForms, error::ShieldError, session::Session, shield::Shield, user::User, +}; #[async_trait] pub trait DynShield: Send + Sync { @@ -12,7 +14,7 @@ pub trait DynShield: Send + Sync { &self, action_id: &str, session: Session, - ) -> Result, ShieldError>; + ) -> Result; } #[async_trait] @@ -25,7 +27,7 @@ impl DynShield for Shield { &self, action_id: &str, session: Session, - ) -> Result, ShieldError> { + ) -> Result { self.action_forms(action_id, session).await } } @@ -45,7 +47,7 @@ impl ShieldDyn { &self, action_id: &str, session: Session, - ) -> Result, ShieldError> { + ) -> Result { self.0.action_forms(action_id, session).await } } diff --git a/packages/integrations/shield-dioxus/src/routes/action.rs b/packages/integrations/shield-dioxus/src/routes/action.rs index 9efba43..f37f76e 100644 --- a/packages/integrations/shield-dioxus/src/routes/action.rs +++ b/packages/integrations/shield-dioxus/src/routes/action.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; -use shield::Form; +use shield::ActionForms; -use crate::{DioxusIntegrationDyn, form::ToRsx}; +use crate::{DioxusIntegrationDyn, ErasedDioxusStyle}; #[derive(Clone, PartialEq, Props)] pub struct ActionProps { @@ -15,20 +15,19 @@ pub fn Action(props: ActionProps) -> Element { move || forms(action_id.clone()) })?; + let style = use_context::(); let response_read = response.read(); let response = response_read.as_ref().unwrap(); match response { - Ok(forms) => rsx! { - {forms.iter().map(ToRsx::to_rsx)} - }, + Ok(forms) => style.render(forms), Err(err) => rsx! { "{err}" }, } } #[server] -async fn forms(action_id: String) -> Result, ServerFnError> { +async fn forms(action_id: String) -> Result { let FromContext(integration): FromContext = extract().await?; let shield = integration.extract_shield().await; let session = integration.extract_session().await; diff --git a/packages/integrations/shield-dioxus/src/style.rs b/packages/integrations/shield-dioxus/src/style.rs index 93a0ca8..d38667b 100644 --- a/packages/integrations/shield-dioxus/src/style.rs +++ b/packages/integrations/shield-dioxus/src/style.rs @@ -1,10 +1,10 @@ use std::sync::Arc; use dioxus::prelude::Element; -use shield::Form; +use shield::ActionForms; pub trait DioxusStyle: Send + Sync { - fn render(&self, forms: &[Form]) -> Element; + fn render(&self, action: &ActionForms) -> Element; } #[derive(Clone)] @@ -15,7 +15,7 @@ impl ErasedDioxusStyle { Self(Arc::new(integration)) } - pub fn render(&self, forms: &[Form]) -> Element { - self.0.render(forms) + pub fn render(&self, action: &ActionForms) -> Element { + self.0.render(action) } } diff --git a/packages/integrations/shield-leptos-actix/src/integration.rs b/packages/integrations/shield-leptos-actix/src/integration.rs index 5ac5a43..91c35f1 100644 --- a/packages/integrations/shield-leptos-actix/src/integration.rs +++ b/packages/integrations/shield-leptos-actix/src/integration.rs @@ -5,18 +5,18 @@ use leptos::prelude::provide_context; use leptos_actix::{extract, redirect}; use shield::{Session, ShieldDyn, User}; use shield_actix::{ExtractSession, ExtractShield, ExtractUser}; -use shield_leptos::integration::{LeptosIntegration, LeptosUser}; +use shield_leptos::{LeptosIntegration, LeptosUser}; -pub struct LeptosActixIntegration(PhantomData); +pub struct ActixLeptosIntegration(PhantomData); -impl Default for LeptosActixIntegration { +impl Default for ActixLeptosIntegration { fn default() -> Self { Self(Default::default()) } } #[async_trait] -impl LeptosIntegration for LeptosActixIntegration { +impl LeptosIntegration for ActixLeptosIntegration { async fn extract_shield(&self) -> ShieldDyn { let ExtractShield(shield) = extract::>().await.expect("TOD"); @@ -41,5 +41,5 @@ impl LeptosIntegration for LeptosActixIntegration } pub fn provide_actix_integration() { - provide_context::>(Arc::new(LeptosActixIntegration::::default())); + provide_context::>(Arc::new(ActixLeptosIntegration::::default())); } diff --git a/packages/integrations/shield-leptos-axum/src/integration.rs b/packages/integrations/shield-leptos-axum/src/integration.rs index 8edd721..dd04245 100644 --- a/packages/integrations/shield-leptos-axum/src/integration.rs +++ b/packages/integrations/shield-leptos-axum/src/integration.rs @@ -5,18 +5,18 @@ use leptos::prelude::provide_context; use leptos_axum::{extract, redirect}; use shield::{Session, ShieldDyn, User}; use shield_axum::{ExtractSession, ExtractShield, ExtractUser}; -use shield_leptos::integration::{LeptosIntegration, LeptosUser}; +use shield_leptos::{LeptosIntegration, LeptosUser}; -pub struct LeptosAxumIntegration(PhantomData); +pub struct AxumLeptosIntegration(PhantomData); -impl Default for LeptosAxumIntegration { +impl Default for AxumLeptosIntegration { fn default() -> Self { Self(Default::default()) } } #[async_trait] -impl LeptosIntegration for LeptosAxumIntegration { +impl LeptosIntegration for AxumLeptosIntegration { async fn extract_shield(&self) -> ShieldDyn { let ExtractShield(shield) = extract::>().await.expect("TODO"); @@ -41,5 +41,5 @@ impl LeptosIntegration for LeptosAxumIntegration { } pub fn provide_axum_integration() { - provide_context::>(Arc::new(LeptosAxumIntegration::::default())); + provide_context::>(Arc::new(AxumLeptosIntegration::::default())); } diff --git a/packages/integrations/shield-leptos/src/lib.rs b/packages/integrations/shield-leptos/src/lib.rs index 398c7f0..c1c91b6 100644 --- a/packages/integrations/shield-leptos/src/lib.rs +++ b/packages/integrations/shield-leptos/src/lib.rs @@ -1,2 +1,7 @@ -pub mod context; -pub mod integration; +mod context; +mod integration; +mod style; + +pub use context::*; +pub use integration::*; +pub use style::*; diff --git a/packages/integrations/shield-leptos/src/style.rs b/packages/integrations/shield-leptos/src/style.rs new file mode 100644 index 0000000..ef5f264 --- /dev/null +++ b/packages/integrations/shield-leptos/src/style.rs @@ -0,0 +1,21 @@ +use std::sync::Arc; + +use leptos::prelude::AnyView; +use shield::ActionForms; + +pub trait LeptosStyle: Send + Sync { + fn render(&self, action: &ActionForms) -> AnyView; +} + +#[derive(Clone)] +pub struct ErasedLeptosStyle(Arc); + +impl ErasedLeptosStyle { + pub fn new(integration: I) -> Self { + Self(Arc::new(integration)) + } + + pub fn render(&self, action: &ActionForms) -> AnyView { + self.0.render(action) + } +} diff --git a/packages/styles/shield-bootstrap/Cargo.toml b/packages/styles/shield-bootstrap/Cargo.toml index 020264a..6ab4c09 100644 --- a/packages/styles/shield-bootstrap/Cargo.toml +++ b/packages/styles/shield-bootstrap/Cargo.toml @@ -10,8 +10,11 @@ version.workspace = true [features] dioxus = ["dep:dioxus", "dep:shield-dioxus"] +leptos = ["dep:leptos", "dep:shield-leptos"] [dependencies] dioxus = { workspace = true, optional = true } +leptos = { workspace = true, optional = true } shield.workspace = true shield-dioxus = { workspace = true, optional = true } +shield-leptos = { workspace = true, optional = true } diff --git a/packages/styles/shield-bootstrap/src/dioxus.rs b/packages/styles/shield-bootstrap/src/dioxus.rs index db6b0eb..d8bd247 100644 --- a/packages/styles/shield-bootstrap/src/dioxus.rs +++ b/packages/styles/shield-bootstrap/src/dioxus.rs @@ -1,5 +1,5 @@ use dioxus::prelude::*; -use shield::Form; +use shield::ActionForms; use shield_dioxus::{DioxusStyle, ErasedDioxusStyle}; #[derive(Default)] @@ -12,17 +12,16 @@ impl BootstrapDioxusStyle { } impl DioxusStyle for BootstrapDioxusStyle { - fn render(&self, forms: &[Form]) -> Element { + fn render(&self, action: &ActionForms) -> Element { rsx! { div { class: "container", h1 { - // TODO: Get from action. - "Sign in" + "{action.name}" } - for form in forms { + for form in &action.forms { form { for input in &form.inputs { div { diff --git a/packages/styles/shield-bootstrap/src/leptos.rs b/packages/styles/shield-bootstrap/src/leptos.rs new file mode 100644 index 0000000..b4b30c0 --- /dev/null +++ b/packages/styles/shield-bootstrap/src/leptos.rs @@ -0,0 +1,59 @@ +use leptos::prelude::*; +use shield::{ActionForms, Input}; +use shield_leptos::{ErasedLeptosStyle, LeptosStyle}; + +#[derive(Default)] +pub struct BootstrapLeptosStyle {} + +impl BootstrapLeptosStyle { + pub fn context(self) -> ErasedLeptosStyle { + ErasedLeptosStyle::new(self) + } +} + +impl BootstrapLeptosStyle { + fn render_form_input(&self, input: &Input) -> impl IntoView { + view! { +
+ {self.render_label(input)} + {self.render_input(input)} +
+ } + } + + fn render_label(&self, input: &Input) -> Option { + input.label.as_ref().map(|label| { + view! { + + } + }) + } + + fn render_input(&self, input: &Input) -> impl IntoView { + view! { + + } + } +} + +impl LeptosStyle for BootstrapLeptosStyle { + fn render(&self, action: &ActionForms) -> AnyView { + view! { +
+

{action.name.clone()}

+ + {action.forms.iter().map(|form| view! { + + {form.inputs.iter().map(|input| self.render_form_input(input)).collect_view()} + + }).collect_view()} +
+ } + .into_any() + } +} diff --git a/packages/styles/shield-bootstrap/src/lib.rs b/packages/styles/shield-bootstrap/src/lib.rs index 33e2da1..c092ab1 100644 --- a/packages/styles/shield-bootstrap/src/lib.rs +++ b/packages/styles/shield-bootstrap/src/lib.rs @@ -1,5 +1,9 @@ #[cfg(feature = "dioxus")] mod dioxus; +#[cfg(feature = "leptos")] +mod leptos; #[cfg(feature = "dioxus")] pub use dioxus::*; +#[cfg(feature = "leptos")] +pub use leptos::*;