mikros 0.3.0

An optionated crate to help building multi-purpose applications.
Documentation
use std::fmt::Formatter;

use serde::de::{Deserializer, Error as SerdeError, Visitor};
use serde::{Deserialize, de::IntoDeserializer};

use crate::definition::ServiceKind;

#[derive(Debug, Clone)]
pub struct Service(pub ServiceKind, pub Option<i32>);

impl<'a> Deserialize<'a> for Service {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'a>,
    {
        struct ServiceVisitor;

        impl Visitor<'_> for ServiceVisitor {
            type Value = Service;

            fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
                formatter.write_str("a service type in the format `name` or `name:port`")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: SerdeError,
            {
                let parts: Vec<&str> = v.split(':').collect();
                let mut port: Option<i32> = None;

                if parts.len() > 1 {
                    port = Some(
                        parts[1]
                            .parse::<i32>()
                            .map_err(|_| E::custom("invalid port number"))?,
                    );
                }

                let service_kind = match parts[0] {
                    "grpc" => ServiceKind::Grpc,
                    "http" => ServiceKind::Http,
                    "native" => ServiceKind::Native,
                    "script" => ServiceKind::Script,
                    _ => ServiceKind::Custom(parts[0].to_string()),
                };

                Ok(Service(service_kind, port))
            }
        }

        deserializer.deserialize_str(ServiceVisitor)
    }
}

// Custom deserialization function for Vec<Service>
pub(crate) fn deserialize_services<'de, D>(deserializer: D) -> Result<Vec<Service>, D::Error>
where
    D: Deserializer<'de>,
{
    let services_as_strings: Vec<String> = Vec::deserialize(deserializer)?; // Fully qualify Vec::deserialize
    services_as_strings
        .into_iter()
        .map(|s| Service::deserialize(s.into_deserializer()))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    // Wrapper struct to deserialize from TOML
    #[derive(Debug, serde_derive::Deserialize)]
    struct Config {
        #[serde(deserialize_with = "deserialize_services")]
        types: Vec<Service>,
    }

    #[test]
    fn test_service_type_with_port() {
        let data =
            "types = [\"grpc:9090\", \"http:8080\", \"native\", \"subscriber\", \"consumer:7070\"]";
        let config: Config = toml::from_str(data).unwrap();
        assert_eq!(config.types.len(), 5);
    }

    #[test]
    fn test_service_type_without_port() {}

    #[test]
    fn test_custom_service_type_with_port() {}

    #[test]
    fn test_custom_service_type_without_port() {}
}