rs-zero 0.2.10

Rust-first microservice framework inspired by go-zero engineering practices
Documentation
use std::collections::BTreeMap;

use crate::core::ConfigFeatureWarning;
#[cfg(any(feature = "rpc", feature = "discovery-etcd"))]
use crate::core::CoreError;

mod database;
mod etcd;
mod rpc;

pub use database::{DatabaseKindSection, DatabaseSection};
pub use etcd::{EtcdAuthSection, EtcdBackoffSection, EtcdDiscoverySection};
pub use rpc::{
    RpcClientProvider, RpcClientSection, RpcDeadlineSection, RpcLoadBalancePolicySection,
    RpcLoadBalanceSection, RpcRetrySection, RpcStreamingSection,
};

/// Framework-owned RPC client dependencies keyed by logical client name.
pub type RpcClientsConfig = BTreeMap<String, RpcClientSection>;

/// Returns feature warnings for dependency sections shared by REST/RPC configs.
pub fn dependency_feature_warnings(
    rpc_clients: &RpcClientsConfig,
    database: Option<&DatabaseSection>,
) -> Vec<ConfigFeatureWarning> {
    let mut warnings = Vec::new();
    if !rpc_clients.is_empty() && !cfg!(feature = "rpc") {
        warnings.push(ConfigFeatureWarning::ignored("rpc_clients", "rpc"));
    }
    if rpc_clients
        .values()
        .any(|client| client.provider == RpcClientProvider::Etcd)
        && !cfg!(feature = "discovery-etcd")
    {
        warnings.push(ConfigFeatureWarning::ignored(
            "rpc_clients.*.etcd",
            "discovery-etcd",
        ));
    }
    if let Some(database) = database {
        let required_feature = match database.kind {
            DatabaseKindSection::Sqlite if !cfg!(feature = "db-sqlite") => Some("db-sqlite"),
            DatabaseKindSection::Postgres if !cfg!(feature = "db-postgres") => Some("db-postgres"),
            DatabaseKindSection::Mysql if !cfg!(feature = "db-mysql") => Some("db-mysql"),
            _ => None,
        };
        if let Some(required_feature) = required_feature {
            warnings.push(ConfigFeatureWarning::ignored(
                "database.kind",
                required_feature,
            ));
        }
    }
    warnings
}

#[cfg(any(feature = "rpc", feature = "discovery-etcd"))]
fn config_error(message: impl Into<String>) -> CoreError {
    config::ConfigError::Message(message.into()).into()
}

#[cfg(test)]
mod tests {
    use super::{DatabaseKindSection, DatabaseSection, RpcClientProvider, RpcClientSection};

    #[test]
    fn default_rpc_client_is_static_without_endpoint() {
        let client = RpcClientSection::default();
        assert_eq!(client.provider, RpcClientProvider::Static);
        assert!(client.endpoint.is_empty());
        assert!(client.retry.enabled);
    }

    #[test]
    fn database_defaults_to_sqlite_memory() {
        let database = DatabaseSection::default();
        assert_eq!(database.kind, DatabaseKindSection::Sqlite);
        assert_eq!(database.url, "sqlite::memory:");
    }

    #[cfg(feature = "rpc")]
    #[test]
    fn maps_rpc_client_runtime_config() {
        let client = RpcClientSection {
            endpoint: "http://127.0.0.1:50051".to_string(),
            service: "hello-rpc".to_string(),
            ..RpcClientSection::default()
        };
        let config = client.to_rpc_client_config().expect("rpc client config");
        assert_eq!(config.endpoint, "http://127.0.0.1:50051");
        assert_eq!(config.discovery.service.as_deref(), Some("hello-rpc"));
        assert!(config.retry.enabled);
    }
}