switchgear-service 0.1.22

Service layer and API implementations for Switchgear LNURL load balancer
Documentation
use crate::api::discovery::{
    DiscoveryBackend, DiscoveryBackendPatch, DiscoveryBackendPatchSparse, DiscoveryBackendRest,
    DiscoveryBackendSparse, DiscoveryBackendStore, DiscoveryBackends,
};
use crate::axum::crud::error::CrudError;
use crate::axum::crud::response::JsonCrudResponse;
use crate::axum::extract::address::DiscoveryBackendAddressParam;
use crate::axum::header::no_cache_headers;
use crate::discovery::state::DiscoveryState;
use axum::http::{HeaderMap, HeaderValue};
use axum::{extract::State, Json};

pub struct DiscoveryHandlers;

impl DiscoveryHandlers {
    pub async fn get_backend<S>(
        DiscoveryBackendAddressParam { address }: DiscoveryBackendAddressParam,
        State(state): State<DiscoveryState<S>>,
    ) -> Result<JsonCrudResponse<DiscoveryBackendRest>, CrudError>
    where
        S: DiscoveryBackendStore,
    {
        let backend = state
            .store()
            .get(&address)
            .await
            .map_err(|e| crate::crud_error_from_service!(e))?
            .ok_or(CrudError::not_found())?;

        let backend = DiscoveryBackendRest {
            location: backend.address.encoded(),
            backend,
        };

        let headers = no_cache_headers();

        Ok(JsonCrudResponse::ok(backend, headers))
    }

    pub async fn get_backends<S>(
        headers: HeaderMap,
        State(state): State<DiscoveryState<S>>,
    ) -> Result<JsonCrudResponse<Vec<DiscoveryBackendRest>>, CrudError>
    where
        S: DiscoveryBackendStore,
    {
        let etag_request = headers
            .get(http::header::IF_NONE_MATCH)
            .map(|h| {
                h.to_str()
                    .map_err(|_| CrudError::bad())
                    .and_then(|etag_str| {
                        DiscoveryBackends::etag_from_str(etag_str).map_err(|_| CrudError::bad())
                    })
            })
            .transpose()?;

        let backends = state
            .store()
            .get_all(etag_request)
            .await
            .map_err(|e| crate::crud_error_from_service!(e))?;

        let mut headers = no_cache_headers();
        headers.insert(http::header::ETAG, backends.etag_string().try_into()?);

        match backends.backends {
            None => Ok(JsonCrudResponse::not_modified(headers)),
            Some(backends) => {
                let backends = backends
                    .into_iter()
                    .map(|backend| DiscoveryBackendRest {
                        location: backend.address.encoded(),
                        backend,
                    })
                    .collect();

                Ok(JsonCrudResponse::ok(backends, headers))
            }
        }
    }

    pub async fn post_backend<S>(
        State(state): State<DiscoveryState<S>>,
        Json(backend): Json<DiscoveryBackend>,
    ) -> Result<JsonCrudResponse<()>, CrudError>
    where
        S: DiscoveryBackendStore,
    {
        let result = state
            .store()
            .post(backend.clone())
            .await
            .map_err(|e| crate::crud_error_from_service!(e))?;

        let location = backend.address.encoded();
        let location = HeaderValue::from_str(&location)?;

        match result {
            Some(_) => Ok(JsonCrudResponse::created_location(location)),
            None => Err(CrudError::conflict(location)),
        }
    }

    pub async fn put_backend<S>(
        State(state): State<DiscoveryState<S>>,
        DiscoveryBackendAddressParam { address }: DiscoveryBackendAddressParam,
        Json(backend): Json<DiscoveryBackendSparse>,
    ) -> Result<JsonCrudResponse<()>, CrudError>
    where
        S: DiscoveryBackendStore,
    {
        let backend = DiscoveryBackend { address, backend };

        let was_created = state
            .store()
            .put(backend.clone())
            .await
            .map_err(|e| crate::crud_error_from_service!(e))?;

        if was_created {
            Ok(JsonCrudResponse::created())
        } else {
            Ok(JsonCrudResponse::no_content())
        }
    }

    pub async fn patch_backend<S>(
        State(state): State<DiscoveryState<S>>,
        DiscoveryBackendAddressParam { address }: DiscoveryBackendAddressParam,
        Json(backend): Json<DiscoveryBackendPatchSparse>,
    ) -> Result<JsonCrudResponse<()>, CrudError>
    where
        S: DiscoveryBackendStore,
    {
        let backend = DiscoveryBackendPatch { address, backend };

        let patched = state
            .store()
            .patch(backend.clone())
            .await
            .map_err(|e| crate::crud_error_from_service!(e))?;

        if patched {
            Ok(JsonCrudResponse::no_content())
        } else {
            Err(CrudError::not_found())
        }
    }

    pub async fn delete_backend<S>(
        DiscoveryBackendAddressParam { address }: DiscoveryBackendAddressParam,
        State(state): State<DiscoveryState<S>>,
    ) -> Result<JsonCrudResponse<()>, CrudError>
    where
        S: DiscoveryBackendStore,
    {
        if state
            .store()
            .delete(&address)
            .await
            .map_err(|e| crate::crud_error_from_service!(e))?
        {
            Ok(JsonCrudResponse::no_content())
        } else {
            Err(CrudError::not_found())
        }
    }
}