stellation_backend_cli/
cli.rs

1use std::env;
2use std::net::ToSocketAddrs;
3use std::path::PathBuf;
4
5use anyhow::{anyhow, Context};
6use clap::Parser;
7use stellation_backend::ServerAppProps;
8use stellation_backend_tower::{Frontend, Server, TowerEndpoint, TowerRenderRequest};
9use stellation_bridge::links::{Link, PhantomLink};
10use stellation_core::dev::StctlMetadata;
11use typed_builder::TypedBuilder;
12use yew::BaseComponent;
13
14#[derive(Parser)]
15struct Arguments {
16    /// The address to listen to.
17    #[arg(long, default_value = "localhost:5000", env = "STELLATION_LISTEN_ADDR")]
18    listen_addr: String,
19    /// The ditectory that contains the frontend artifact.
20    #[arg(long, env = "STELLATION_FRONTEND_DIR")]
21    frontend_dir: Option<PathBuf>,
22}
23
24/// The default command line instance for the backend server.
25#[derive(Debug, TypedBuilder)]
26pub struct Cli<COMP, CTX = (), L = PhantomLink>
27where
28    COMP: BaseComponent,
29{
30    endpoint: TowerEndpoint<COMP, CTX, L>,
31}
32
33impl<COMP, CTX, L> Cli<COMP, CTX, L>
34where
35    COMP: BaseComponent<Properties = ServerAppProps<CTX, TowerRenderRequest<CTX>>>,
36    CTX: 'static,
37    L: 'static + Link,
38{
39    /// Parses the arguments and runs the server.
40    pub async fn run(self) -> anyhow::Result<()> {
41        let Self { mut endpoint } = self;
42
43        let args = Arguments::parse();
44
45        // Prioritise information from stctl.
46        let meta = match env::var(StctlMetadata::ENV_NAME) {
47            Ok(m) => Some(StctlMetadata::from_json(&m).context("failed to load metadata")?),
48            Err(_) => None,
49        };
50
51        let addr = meta
52            .as_ref()
53            .map(|m| m.listen_addr.as_str())
54            .unwrap_or_else(|| args.listen_addr.as_str());
55
56        if let Some(ref p) = args.frontend_dir {
57            endpoint = endpoint.with_frontend(Frontend::new_path(p));
58        }
59
60        if let Some(ref meta) = meta {
61            endpoint = endpoint
62                .with_frontend(Frontend::new_path(&meta.frontend_dev_build_dir))
63                .with_auto_refresh();
64        }
65
66        let listen_addr = addr
67            .to_socket_addrs()
68            .context("failed to parse address")
69            .and_then(|m| {
70                m.into_iter()
71                    .next()
72                    .ok_or_else(|| anyhow!("failed to parse address"))
73            })?;
74
75        tracing::info!("Listening at: http://{}/", addr);
76
77        Server::<()>::bind(listen_addr)
78            .serve_service(endpoint.into_tower_service())
79            .await?;
80
81        Ok(())
82    }
83}