feat(api): axum skeleton with health and OpenAPI (#53)
This commit was merged in pull request #53.
This commit is contained in:
@@ -4,15 +4,113 @@ mod config;
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match config::Config::load() {
|
||||
Ok(config) => {
|
||||
println!("arr starting, bind_addr={}", config.bind_addr);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
use arr_api::{AppState, Upstreams};
|
||||
use config::Config;
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
/// Dump the `OpenAPI` document and exit, instead of serving. `just gen-client`
|
||||
/// uses this so the TypeScript client can be regenerated without a port or a
|
||||
/// single upstream being up.
|
||||
const OPENAPI_FLAG: &str = "--openapi";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> ExitCode {
|
||||
if std::env::args().nth(1).as_deref() == Some(OPENAPI_FLAG) {
|
||||
return dump_openapi();
|
||||
}
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "info,tower_http=debug".into()),
|
||||
)
|
||||
.init();
|
||||
|
||||
match run().await {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
eprintln!("arr: {err}");
|
||||
tracing::error!("{err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_openapi() -> ExitCode {
|
||||
match arr_api::openapi().to_pretty_json() {
|
||||
Ok(json) => {
|
||||
println!("{json}");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("arr: openapi: {err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum Error {
|
||||
#[error("config: {0}")]
|
||||
Config(#[from] config::ConfigError),
|
||||
#[error("http client: {0}")]
|
||||
HttpClient(#[from] reqwest::Error),
|
||||
#[error("bind {addr}: {source}")]
|
||||
Bind {
|
||||
addr: std::net::SocketAddr,
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("serve: {0}")]
|
||||
Serve(std::io::Error),
|
||||
}
|
||||
|
||||
async fn run() -> Result<(), Error> {
|
||||
let config = Config::load()?;
|
||||
|
||||
let state = AppState::new(
|
||||
Upstreams::new(config.prowlarr_url, config.transmission_url)
|
||||
.with_prowlarr_api_key(config.prowlarr_api_key)
|
||||
.with_tmdb_api_key(config.tmdb_api_key),
|
||||
)?;
|
||||
|
||||
let app = arr_api::router(state).layer(TraceLayer::new_for_http());
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(config.bind_addr)
|
||||
.await
|
||||
.map_err(|source| Error::Bind {
|
||||
addr: config.bind_addr,
|
||||
source,
|
||||
})?;
|
||||
|
||||
tracing::info!(addr = %config.bind_addr, docs = arr_api::DOCS_PATH, "listening");
|
||||
|
||||
axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown())
|
||||
.await
|
||||
.map_err(Error::Serve)
|
||||
}
|
||||
|
||||
/// Stop accepting on Ctrl-C, or on the SIGTERM a service manager sends.
|
||||
async fn shutdown() {
|
||||
let interrupt = async {
|
||||
let _ = tokio::signal::ctrl_c().await;
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let terminate = async {
|
||||
match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
|
||||
Ok(mut signal) => {
|
||||
signal.recv().await;
|
||||
}
|
||||
Err(err) => tracing::warn!("no SIGTERM handler: {err}"),
|
||||
}
|
||||
};
|
||||
#[cfg(not(unix))]
|
||||
let terminate = std::future::pending::<()>();
|
||||
|
||||
tokio::select! {
|
||||
() = interrupt => {},
|
||||
() = terminate => {},
|
||||
}
|
||||
|
||||
tracing::info!("shutting down");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user