entertainarr-adapter-http 0.1.0

HTTP adapter for entertainarr
Documentation
use anyhow::Context;
// used for publishing frontend;
pub use handler::client::prelude::ClientService;

mod extractor;
mod handler;
mod middleware;
mod prelude;

/// HTTP server configuration
#[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")
    }
}