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
use crate::TestTransport;
use std::sync::Arc;
use url::Url;

/// a bridge between trillium servers and clients
#[derive(Debug)]
pub struct ServerConnector<H> {
    handler: Arc<H>,
}

impl<H> ServerConnector<H>
where
    H: trillium::Handler,
{
    /// builds a new ServerConnector
    pub fn new(handler: H) -> Self {
        Self {
            handler: Arc::new(handler),
        }
    }

    /// opens a new connection to this virtual server, returning the client transport
    pub async fn connect(&self, secure: bool) -> TestTransport {
        let (client_transport, server_transport) = TestTransport::new();

        let handler = Arc::clone(&self.handler);

        crate::spawn(async move {
            trillium_http::Conn::map(server_transport, Default::default(), |mut conn| {
                let handler = Arc::clone(&handler);
                async move {
                    conn.set_secure(secure);
                    let conn = handler.run(conn.into()).await;
                    let conn = handler.before_send(conn).await;
                    conn.into_inner()
                }
            })
            .await
            .unwrap();
        });

        client_transport
    }
}

#[trillium_server_common::async_trait]
impl<H: trillium::Handler> trillium_server_common::Connector for ServerConnector<H> {
    type Transport = TestTransport;
    async fn connect(&self, url: &Url) -> std::io::Result<Self::Transport> {
        Ok(self.connect(url.scheme() == "https").await)
    }

    fn spawn<Fut: std::future::Future<Output = ()> + Send + 'static>(&self, fut: Fut) {
        crate::spawn(fut);
    }
}

/// build a connector from this handler
pub fn connector(handler: impl trillium::Handler) -> impl trillium_server_common::Connector {
    ServerConnector::new(handler)
}

#[cfg(test)]
mod test {
    use crate::server_connector::ServerConnector;
    use trillium_client::Client;

    #[test]
    fn test() {
        crate::block_on(async {
            let client = Client::new(ServerConnector::new("test"));
            let mut conn = client.get("https://example.com/test").await.unwrap();
            assert_eq!(conn.response_body().read_string().await.unwrap(), "test");
        });
    }

    #[test]
    fn test_no_dns() {
        crate::block_on(async {
            let client = Client::new(ServerConnector::new("test"));
            let mut conn = client
                .get("https://not.a.real.tld.example/test")
                .await
                .unwrap();
            assert_eq!(conn.response_body().read_string().await.unwrap(), "test");
        });
    }

    #[test]
    fn test_post() {
        crate::block_on(async {
            let client = Client::new(ServerConnector::new(
                |mut conn: trillium::Conn| async move {
                    let body = conn.request_body_string().await.unwrap();
                    let response = format!(
                        "{} {}://{}{} with body \"{}\"",
                        conn.method(),
                        if conn.is_secure() { "https" } else { "http" },
                        conn.inner().host().unwrap_or_default(),
                        conn.path(),
                        body
                    );

                    conn.ok(response)
                },
            ));

            let body = client
                .post("https://example.com/test")
                .with_body("some body")
                .await
                .unwrap()
                .response_body()
                .read_string()
                .await
                .unwrap();

            assert_eq!(
                body,
                "POST https://example.com/test with body \"some body\""
            );
        });
    }
}