1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use askama::Template;
use futures_lite::prelude::*;
use std::time::Duration;
use trillium::{Conn, Handler};
use trillium_askama::AskamaConnExt;
use trillium_caching_headers::{
    CacheControlDirective::{MaxAge, Public},
    CachingHeadersExt,
};
use trillium_conn_id::log_formatter::conn_id;
use trillium_logger::apache_common;
use trillium_proxy::Proxy;
use trillium_router::{Router, RouterConnExt};
use trillium_rustls::RustlsConfig;
use trillium_sessions::{MemoryStore, SessionConnExt};
use trillium_smol::ClientConfig;
use trillium_static_compiled::static_compiled;
use trillium_websockets::{Message, WebSocket, WebSocketConn};

#[derive(Template)]
#[template(path = "hello.html")]
struct HelloTemplate<'a> {
    name: &'a str,
}

async fn request_count(conn: Conn) -> Conn {
    let count = conn.session().get::<usize>("count").unwrap_or_default();
    conn.with_response_header("request-count", count.to_string())
        .with_session("count", count + 1)
}

async fn with_cache_control(conn: Conn) -> Conn {
    conn.with_cache_control([MaxAge(Duration::from_secs(604800)), Public])
        .with_vary([trillium::KnownHeaderName::UserAgent])
}

fn app() -> impl Handler {
    (
        with_cache_control,
        trillium_logger::logger().with_formatter(apache_common(conn_id, "-")),
        trillium_compression::compression(),
        trillium_conn_id::conn_id(),
        trillium_method_override::method_override(),
        trillium_head::head(),
        trillium_caching_headers::caching_headers(),
        trillium_cookies::cookies(),
        trillium_sessions::sessions(MemoryStore::new(), b"01234567890123456789012345678901123"),
        request_count,
        router(),
        static_compiled!("$CARGO_MANIFEST_DIR/public").with_index_file("index.html"),
    )
}

fn router() -> impl Handler {
    Router::new()
        .get("/hello", "hi")
        .post("/", |mut conn: Conn| async move {
            let body = conn.request_body_string().await.unwrap();
            conn.ok(format!("request body: {body}"))
        })
        .get("/template/:name", |conn: Conn| async move {
            if let Some(name) = conn.param("name").map(String::from) {
                conn.render(HelloTemplate { name: &name })
            } else {
                conn
            }
        })
        .get("/hello/:planet", |conn: Conn| async move {
            if let Some(planet) = conn.param("planet") {
                let response = format!("hello, {planet}");
                conn.ok(response)
            } else {
                conn
            }
        })
        .get(
            "/ws",
            WebSocket::new(|mut ws: WebSocketConn| async move {
                while let Some(Ok(Message::Text(input))) = ws.next().await {
                    log::info!("received message {:?}", &input);
                    let output: String = input.chars().rev().collect();
                    let _ = ws.send_string(format!("{} | {}", &input, &output)).await;
                }
            }),
        )
        .get(
            "/httpbin/*",
            Proxy::new(
                RustlsConfig::default().with_tcp_config(ClientConfig::default()),
                "https://httpbin.org",
            ),
        )
}

fn main() {
    env_logger::init();
    trillium_smol::run(app());
}

#[cfg(test)]
mod test {
    use trillium_testing::prelude::*;

    use super::app;

    #[test]
    fn test_index() {
        let app = app();
        let mut conn = get("/").on(&app);
        assert_ok!(&conn);
        assert_body_contains!(&mut conn, "<h1>Welcome to trillium!</h1>");
    }

    #[test]
    fn test_hello_hi() {
        let app = app();
        assert_ok!(get("/hello").on(&app), "hi");
    }

    #[test]
    fn test_post_index() {
        let app = app();
        assert_ok!(
            post("/").with_request_body("hey").on(&app),
            "request body: hey"
        );
    }

    #[test]
    fn test_askama_templating() {
        let app = app();
        assert_body_contains!(
            get("/template/trillium").on(&app),
            "<h1>hi there, trillium</h1>"
        );

        assert_body_contains!(
            get("/template/dear-reader").on(&app),
            "<h1>hi there, dear-reader</h1>"
        );
    }
}