use std::net::SocketAddr; use axum::{ extract::{MatchedPath, State}, http::Request, routing::get, Json, Router, }; use serde::{Deserialize, Serialize}; use tokio_util::sync::CancellationToken; use tower_http::trace::TraceLayer; use crate::state::SharedState; pub struct Api { state: SharedState, host: SocketAddr, } impl Api { pub fn new(state: impl Into, host: impl Into) -> Self { Self { state: state.into(), host: host.into(), } } pub async fn serve(&self) -> anyhow::Result<()> { let app = Router::new() .route("/", get(root)) .route("/discovery", get(discovery)) .with_state(self.state.clone()) .layer( TraceLayer::new_for_http().make_span_with(|request: &Request<_>| { // Log the matched route's path (with placeholders not filled in). // Use request.uri() or OriginalUri if you want the real path. let matched_path = request .extensions() .get::() .map(MatchedPath::as_str); tracing::info_span!( "http_request", method = ?request.method(), matched_path, some_other_field = tracing::field::Empty, ) }), // ... ); tracing::info!("listening on {}", self.host); let listener = tokio::net::TcpListener::bind(&self.host).await.unwrap(); axum::serve(listener, app.into_make_service()) .await .unwrap(); Ok(()) } } async fn root() -> &'static str { "Hello, churn!" } #[derive(Serialize, Deserialize)] pub struct Discovery { pub external_host: String, pub process_host: String, } impl Discovery { pub async fn get_from_host(host: &str) -> anyhow::Result { let resp = reqwest::get(format!("{}/discovery", host.trim_end_matches('/'))).await?; let s: Self = resp.json().await?; Ok(s) } } async fn discovery(State(state): State) -> Json { Json(Discovery { external_host: state.config.external_host.clone(), process_host: state.config.process_host.clone(), }) } #[async_trait::async_trait] impl notmad::Component for Api { async fn run(&self, _cancellation_token: CancellationToken) -> Result<(), notmad::MadError> { self.serve().await.map_err(notmad::MadError::Inner)?; Ok(()) } }