Skip to content

Commit

Permalink
feat: add initial session support
Browse files Browse the repository at this point in the history
This is based on the tower-sessions crate, but provides a slightly
simpler API (that should be extended in the following commits).
  • Loading branch information
m4tx committed Sep 25, 2024
1 parent c04e7e3 commit 54dcc43
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 8 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
# Examples
"examples/hello-world",
"examples/todo-list",
"examples/sessions",
]
resolver = "2"

Expand Down
11 changes: 11 additions & 0 deletions examples/sessions/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "example-sessions"
version = "0.1.0"
publish = false
description = "Sessions - Flareon example."
edition = "2021"

[dependencies]
askama = "0.12.1"
flareon = { path = "../../flareon" }
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
88 changes: 88 additions & 0 deletions examples/sessions/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use askama::Template;
use flareon::forms::Form;
use flareon::middleware::SessionMiddleware;
use flareon::request::{Request, RequestExt};
use flareon::response::{Response, ResponseExt};
use flareon::router::Route;
use flareon::{reverse, Body, Error, FlareonApp, FlareonProject, StatusCode};

#[derive(Debug, Template)]
#[template(path = "index.html")]
struct IndexTemplate<'a> {
request: &'a Request,
name: String,
}

#[derive(Debug, Template)]
#[template(path = "name.html")]
struct NameTemplate<'a> {
request: &'a Request,
}

#[derive(Debug, Form)]
struct NameForm {
#[form(opt(max_length = 100))]
name: String,
}

async fn hello(request: Request) -> Result<Response, Error> {
let name: String = request
.session()
.get("user_name")
.await
.expect("Invalid session value")
.unwrap_or_default();
if name.is_empty() {
return Ok(reverse!(request, "name"));
}

let template = IndexTemplate {
request: &request,
name,
};

Ok(Response::new_html(
StatusCode::OK,
Body::fixed(template.render()?),
))
}

async fn name(mut request: Request) -> Result<Response, Error> {
if request.method() == flareon::Method::POST {
let name_form = NameForm::from_request(&mut request).await.unwrap();
request
.session_mut()
.insert("user_name", name_form.name)
.await
.unwrap();

return Ok(reverse!(request, "index"));
}

let template = NameTemplate { request: &request };

Ok(Response::new_html(
StatusCode::OK,
Body::fixed(template.render()?),
))
}

#[tokio::main]
async fn main() {
let hello_app = FlareonApp::builder()
.urls([
Route::with_handler_and_name("/", hello, "index"),
Route::with_handler_and_name("/name", name, "name"),
])
.build()
.unwrap();

let flareon_project = FlareonProject::builder()
.register_app_with_views(hello_app, "")
.middleware(SessionMiddleware::new())
.build();

flareon::run(flareon_project, "127.0.0.1:8000")
.await
.unwrap();
}
14 changes: 14 additions & 0 deletions examples/sessions/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% let request = request %}

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sessions example</title>
</head>
<body>
<h1>Hello!</h1>
<p>Your name is: {{ name }}</p>
</body>
</html>
17 changes: 17 additions & 0 deletions examples/sessions/templates/name.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% let request = request %}

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sessions example</title>
</head>
<body>
<h1>Hello!</h1>
<form id="todo-form" action="{{ flareon::reverse_str!(request, "name") }}" method="post">
<input type="text" id="name" name="name" placeholder="Please enter your name" required>
<button type="submit">Submit</button>
</form>
</body>
</html>
5 changes: 1 addition & 4 deletions examples/todo-list/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ async fn index(request: Request) -> Result<Response, Error> {
};
let rendered = index_template.render()?;

Ok(Response::new_html(
StatusCode::OK,
Body::fixed(rendered.as_bytes().to_vec()),
))
Ok(Response::new_html(StatusCode::OK, Body::fixed(rendered)))
}

#[derive(Debug, Form)]
Expand Down
12 changes: 9 additions & 3 deletions flareon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod headers;
#[doc(hidden)]
#[path = "private.rs"]
pub mod __private;
pub mod middleware;
pub mod request;
pub mod response;
pub mod router;
Expand Down Expand Up @@ -53,6 +54,9 @@ pub type Result<T> = std::result::Result<T, Error>;
/// A type alias for an HTTP status code.
pub type StatusCode = http::StatusCode;

/// A type alias for an HTTP method.
pub type Method = http::Method;

#[async_trait]
pub trait RequestHandler {
async fn handle(&self, request: Request) -> Result<Response>;
Expand Down Expand Up @@ -279,7 +283,7 @@ pub struct FlareonProject<S> {
handler: S,
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct FlareonProjectBuilder {
apps: Vec<FlareonApp>,
urls: Vec<Route>,
Expand All @@ -304,10 +308,12 @@ impl FlareonProjectBuilder {

#[must_use]
pub fn middleware<M: tower::Layer<RouterService>>(
self,
&mut self,
middleware: M,
) -> FlareonProjectBuilderWithMiddleware<M::Service> {
self.to_builder_with_middleware().middleware(middleware)
self.clone()
.to_builder_with_middleware()
.middleware(middleware)
}

/// Builds the Flareon project instance.
Expand Down
23 changes: 23 additions & 0 deletions flareon/src/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use tower_sessions::{MemoryStore, SessionManagerLayer};

#[derive(Debug, Copy, Clone)]
pub struct SessionMiddleware {}

impl SessionMiddleware {
#[must_use]
pub fn new() -> Self {
Self {}
}
}

impl<S> tower::Layer<S> for SessionMiddleware {
type Service = <SessionManagerLayer<MemoryStore> as tower::Layer<S>>::Service;

fn layer(&self, inner: S) -> Self::Service {
let session_store = MemoryStore::default();
let session_layer = SessionManagerLayer::new(session_store);
session_layer.layer(inner)
}
}

// TODO: add Flareon ORM-based session store
2 changes: 1 addition & 1 deletion flareon/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl RequestExt for Request {
let content_type = self
.content_type()
.map_or("".into(), |value| String::from_utf8_lossy(value.as_bytes()));
if self.content_type() == Some(&http::HeaderValue::from_static(expected)) {
if content_type == expected {
Ok(())
} else {
Err(Error::InvalidContentType {
Expand Down

0 comments on commit 54dcc43

Please sign in to comment.