summaryrefslogtreecommitdiffstats
path: root/service-joiner/src/main.rs
blob: 7cd284401fecaf5e773938a37e4bb017a417850a (plain)
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
use std::str::FromStr;

use anyhow::Context;
use anyhow::Error;
use anyhow::Result;
use actix_web::{get, web, App, HttpServer, Responder};
use derive_more::{Display, Error};

#[derive(Clone)]
struct Endpoints {
    hello_service_endpoint: String,
    hello_service_port: u16,

    world_service_endpoint: String,
    world_service_port: u16,
}

#[derive(Debug, Display, Error)]
#[display(fmt = "error: {}", inner)]
struct AppError {
    inner: anyhow::Error,
}

impl actix_web::error::ResponseError for AppError {}

#[get("/")]
async fn joiner(state: web::Data<Endpoints>) -> impl Responder {
    async fn inner(state: web::Data<Endpoints>) -> Result<impl Responder> {
        let hello = {
            let url = reqwest::Url::parse(&format!("http://{host}:{port}/",
                host = state.hello_service_endpoint,
                port = state.hello_service_port))?;
            reqwest::get(url)
        };
        let world = {
            let url = reqwest::Url::parse(&format!("http://{host}:{port}/",
                host = state.world_service_endpoint,
                port = state.world_service_port))?;
            reqwest::get(url)
        };

        let (hello, world) = tokio::try_join!(hello, world)?;
        let (hello, world) = tokio::try_join!(hello.text(), world.text())?;

        Ok(format!("{} {}", hello, world)) as Result<String>
    }

    inner(state).await.map_err(|inner| AppError { inner })
}

#[actix_web::main]
async fn main() -> Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
    let bind = std::env::var("HOST").expect("environment: HOST variable not set");
    let port = std::env::var("PORT")
        .as_ref()
        .map(|p| u16::from_str(p))
        .expect("environment: HOST variable not set")
        .context("Failed to parse port as integer")?;

    let hello_service = std::env::var("HELLO_SERVICE")
        .as_ref()
        .map(std::ops::Deref::deref)
        .map(parse_endpoint)
        .expect("environment: HELLO_SERVICE variable not set")?;

    let world_service = std::env::var("WORLD_SERVICE")
        .as_ref()
        .map(std::ops::Deref::deref)
        .map(parse_endpoint)
        .expect("environment: WORLD_SERVICE variable not set")?;

    HttpServer::new(move || {
            App::new()
                .app_data(web::Data::new(Endpoints {
                    hello_service_endpoint: hello_service.0.clone(),
                    hello_service_port: hello_service.1,

                    world_service_endpoint: world_service.0.clone(),
                    world_service_port: world_service.1,
                }))
                .service(joiner)
        })
        .bind(format!("{}:{}", bind, port))?
        .run()
        .await
        .map_err(anyhow::Error::from)
}

fn parse_endpoint(s: &str) -> Result<(String, u16)> {
    let v = s.split(':').collect::<Vec<_>>();
    if v.len() != 2 {
        anyhow::bail!("Expected host:port, got: '{}'", s)
    }

    u16::from_str(v[1])
        .map(|port| (v[0].to_string(), port))
        .map_err(Error::from)
}