use anyhow::Context;
pub use handler::client::prelude::ClientService;
mod extractor;
mod handler;
mod middleware;
mod prelude;
#[derive(serde::Deserialize)]
pub struct Config {
#[serde(default = "Config::default_address")]
pub address: std::net::IpAddr,
#[serde(default = "Config::default_port")]
pub port: u16,
}
const DEFAULT_ADDRESS: std::net::IpAddr = std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED);
impl Default for Config {
fn default() -> Self {
Self {
address: Self::default_address(),
port: Self::default_port(),
}
}
}
impl Config {
pub const fn default_address() -> std::net::IpAddr {
DEFAULT_ADDRESS
}
pub const fn default_port() -> u16 {
3000
}
pub fn builder(self) -> anyhow::Result<HttpServerBuilder<(), (), (), (), ()>> {
Ok(HttpServerBuilder {
socket_address: std::net::SocketAddr::from((self.address, self.port)),
authentication_service: (),
client_service: (),
media_service: (),
podcast_service: (),
tvshow_service: (),
})
}
}
pub struct HttpServerBuilder<AS, CS, MS, PS, TVS> {
socket_address: std::net::SocketAddr,
authentication_service: AS,
client_service: CS,
media_service: MS,
podcast_service: PS,
tvshow_service: TVS,
}
impl<AS, CS, MS, PS, TVS> HttpServerBuilder<AS, CS, MS, PS, TVS> {
pub fn with_authentication_service<AS2>(
self,
service: AS2,
) -> HttpServerBuilder<AS2, CS, MS, PS, TVS>
where
AS2: entertainarr_domain::auth::prelude::AuthenticationService,
{
HttpServerBuilder {
socket_address: self.socket_address,
authentication_service: service,
client_service: self.client_service,
media_service: self.media_service,
podcast_service: self.podcast_service,
tvshow_service: self.tvshow_service,
}
}
pub fn with_client_service<CS2>(self, service: CS2) -> HttpServerBuilder<AS, CS2, MS, PS, TVS>
where
CS2: crate::server::handler::client::prelude::ClientService,
{
HttpServerBuilder {
socket_address: self.socket_address,
authentication_service: self.authentication_service,
client_service: service,
media_service: self.media_service,
podcast_service: self.podcast_service,
tvshow_service: self.tvshow_service,
}
}
pub fn with_media_service<MS2>(self, service: MS2) -> HttpServerBuilder<AS, CS, MS2, PS, TVS>
where
MS2: entertainarr_domain::media::prelude::MediaService,
{
HttpServerBuilder {
socket_address: self.socket_address,
authentication_service: self.authentication_service,
client_service: self.client_service,
media_service: service,
podcast_service: self.podcast_service,
tvshow_service: self.tvshow_service,
}
}
pub fn with_podcast_service<PS2>(self, service: PS2) -> HttpServerBuilder<AS, CS, MS, PS2, TVS>
where
PS2: entertainarr_domain::podcast::prelude::PodcastService,
{
HttpServerBuilder {
socket_address: self.socket_address,
authentication_service: self.authentication_service,
client_service: self.client_service,
media_service: self.media_service,
podcast_service: service,
tvshow_service: self.tvshow_service,
}
}
pub fn with_tvshow_service<TVS2>(self, service: TVS2) -> HttpServerBuilder<AS, CS, MS, PS, TVS2>
where
TVS2: entertainarr_domain::tvshow::prelude::TvShowService,
{
HttpServerBuilder {
socket_address: self.socket_address,
authentication_service: self.authentication_service,
client_service: self.client_service,
media_service: self.media_service,
podcast_service: self.podcast_service,
tvshow_service: service,
}
}
}
impl<AS, CS, MS, PS, TVS> HttpServerBuilder<AS, CS, MS, PS, TVS>
where
AS: entertainarr_domain::auth::prelude::AuthenticationService + Clone,
CS: crate::server::handler::client::prelude::ClientService + Clone,
MS: entertainarr_domain::media::prelude::MediaService + Clone,
PS: entertainarr_domain::podcast::prelude::PodcastService + Clone,
TVS: entertainarr_domain::tvshow::prelude::TvShowService + Clone,
{
pub fn router(self) -> axum::Router {
let state = ServerState {
authentication_service: self.authentication_service,
client_service: self.client_service,
media_service: self.media_service,
podcast_service: self.podcast_service,
tvshow_service: self.tvshow_service,
};
handler::create::<ServerState<AS, CS, MS, PS, TVS>>()
.layer(middleware::tracing::layer())
.with_state(state)
}
pub fn build(self) -> anyhow::Result<HttpServer> {
let socket_address = self.socket_address;
let router = self.router();
Ok(HttpServer {
router,
socket_address,
})
}
}
#[derive(Clone, Debug)]
pub struct ServerState<AS, CS, MS, PS, TVS> {
authentication_service: AS,
client_service: CS,
media_service: MS,
podcast_service: PS,
tvshow_service: TVS,
}
impl<AS, CS, MS, PS, TVS> prelude::ServerState for ServerState<AS, CS, MS, PS, TVS>
where
AS: entertainarr_domain::auth::prelude::AuthenticationService,
CS: crate::server::handler::client::prelude::ClientService,
MS: entertainarr_domain::media::prelude::MediaService,
PS: entertainarr_domain::podcast::prelude::PodcastService,
TVS: entertainarr_domain::tvshow::prelude::TvShowService,
{
fn authentication_service(
&self,
) -> &impl entertainarr_domain::auth::prelude::AuthenticationService {
&self.authentication_service
}
fn client_service(&self) -> &impl handler::client::prelude::ClientService {
&self.client_service
}
fn media_service(&self) -> &impl entertainarr_domain::media::prelude::MediaService {
&self.media_service
}
fn podcast_service(&self) -> &impl entertainarr_domain::podcast::prelude::PodcastService {
&self.podcast_service
}
fn tvshow_service(&self) -> &impl entertainarr_domain::tvshow::prelude::TvShowService {
&self.tvshow_service
}
}
pub struct HttpServer {
router: axum::Router,
socket_address: std::net::SocketAddr,
}
impl HttpServer {
pub async fn run(self) -> anyhow::Result<()> {
let listener = tokio::net::TcpListener::bind(self.socket_address)
.await
.context("unable to bind socket")?;
tracing::info!(address = ?self.socket_address, "starting server");
axum::serve(listener, self.router)
.await
.context("server shutdown")
}
}