nmstate 2.2.3

Library for networking management in a declarative manner
Documentation
// SPDX-License-Identifier: Apache-2.0

use std::convert::TryFrom;
use std::marker::PhantomData;
use std::str::FromStr;

use serde::{de, de::Visitor, Deserialize, Deserializer};

use crate::{ErrorKind, NmstateError};

pub(crate) fn u8_or_string<'de, D>(deserializer: D) -> Result<u8, D::Error>
where
    D: Deserializer<'de>,
{
    option_u8_or_string(deserializer).and_then(|i| {
        if let Some(i) = i {
            Ok(i)
        } else {
            Err(de::Error::custom("Required filed undefined"))
        }
    })
}

pub(crate) fn u16_or_string<'de, D>(deserializer: D) -> Result<u16, D::Error>
where
    D: Deserializer<'de>,
{
    option_u16_or_string(deserializer).and_then(|i| {
        if let Some(i) = i {
            Ok(i)
        } else {
            Err(de::Error::custom("Required filed undefined"))
        }
    })
}

pub(crate) fn u32_or_string<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
    D: Deserializer<'de>,
{
    option_u32_or_string(deserializer).and_then(|i| {
        if let Some(i) = i {
            Ok(i)
        } else {
            Err(de::Error::custom("Required filed undefined"))
        }
    })
}

pub(crate) fn bool_or_string<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
    D: Deserializer<'de>,
{
    option_bool_or_string(deserializer).and_then(|i| {
        if let Some(i) = i {
            Ok(i)
        } else {
            Err(de::Error::custom("Required filed undefined"))
        }
    })
}

pub(crate) fn option_bool_or_string<'de, D>(
    deserializer: D,
) -> Result<Option<bool>, D::Error>
where
    D: Deserializer<'de>,
{
    struct IntegerOrString(PhantomData<fn() -> Option<bool>>);

    impl<'de> Visitor<'de> for IntegerOrString {
        type Value = Option<bool>;

        fn expecting(
            &self,
            formatter: &mut std::fmt::Formatter,
        ) -> std::fmt::Result {
            formatter.write_str(
                "Need to be boolean: 1|0|true|false|yes|no|on|off|y|n",
            )
        }

        fn visit_bool<E>(self, value: bool) -> Result<Option<bool>, E>
        where
            E: de::Error,
        {
            Ok(Some(value))
        }

        fn visit_str<E>(self, value: &str) -> Result<Option<bool>, E>
        where
            E: de::Error,
        {
            match value.to_lowercase().as_str() {
                "1" | "true" | "yes" | "on" | "y" => Ok(Some(true)),
                "0" | "false" | "no" | "off" | "n" => Ok(Some(false)),
                _ => Err(de::Error::custom(
                    "Need to be boolean: 1|0|true|false|yes|no|on|off|y|n",
                )),
            }
        }

        fn visit_u64<E>(self, value: u64) -> Result<Option<bool>, E>
        where
            E: de::Error,
        {
            match value {
                1 => Ok(Some(true)),
                0 => Ok(Some(false)),
                _ => Err(de::Error::custom(
                    "Need to be boolean: 1|0|true|false|yes|no|on|off|y|n",
                )),
            }
        }
    }

    deserializer.deserialize_any(IntegerOrString(PhantomData))
}

pub(crate) fn option_u8_or_string<'de, D>(
    deserializer: D,
) -> Result<Option<u8>, D::Error>
where
    D: Deserializer<'de>,
{
    option_u64_or_string(deserializer).and_then(|i| {
        if let Some(i) = i {
            match u8::try_from(i) {
                Ok(i) => Ok(Some(i)),
                Err(e) => Err(de::Error::custom(e)),
            }
        } else {
            Ok(None)
        }
    })
}

pub(crate) fn option_u16_or_string<'de, D>(
    deserializer: D,
) -> Result<Option<u16>, D::Error>
where
    D: Deserializer<'de>,
{
    option_u64_or_string(deserializer).and_then(|i| {
        if let Some(i) = i {
            match u16::try_from(i) {
                Ok(i) => Ok(Some(i)),
                Err(e) => Err(de::Error::custom(e)),
            }
        } else {
            Ok(None)
        }
    })
}

pub(crate) fn option_u32_or_string<'de, D>(
    deserializer: D,
) -> Result<Option<u32>, D::Error>
where
    D: Deserializer<'de>,
{
    option_u64_or_string(deserializer).and_then(|i| {
        if let Some(i) = i {
            match u32::try_from(i) {
                Ok(i) => Ok(Some(i)),
                Err(e) => Err(de::Error::custom(e)),
            }
        } else {
            Ok(None)
        }
    })
}

// This function is inspired by https://serde.rs/string-or-struct.html
pub(crate) fn option_u64_or_string<'de, D>(
    deserializer: D,
) -> Result<Option<u64>, D::Error>
where
    D: Deserializer<'de>,
{
    struct IntegerOrString(PhantomData<fn() -> Option<u64>>);

    impl<'de> Visitor<'de> for IntegerOrString {
        type Value = Option<u64>;

        fn expecting(
            &self,
            formatter: &mut std::fmt::Formatter,
        ) -> std::fmt::Result {
            formatter.write_str("unsigned integer or string")
        }

        fn visit_str<E>(self, value: &str) -> Result<Option<u64>, E>
        where
            E: de::Error,
        {
            if let Some(prefix_len) = value.strip_prefix("0x") {
                u64::from_str_radix(prefix_len, 16)
                    .map_err(de::Error::custom)
                    .map(Some)
            } else {
                FromStr::from_str(value)
                    .map_err(de::Error::custom)
                    .map(Some)
            }
        }

        fn visit_u64<E>(self, value: u64) -> Result<Option<u64>, E>
        where
            E: de::Error,
        {
            Ok(Some(value))
        }
    }

    deserializer.deserialize_any(IntegerOrString(PhantomData))
}

pub(crate) fn option_i64_or_string<'de, D>(
    deserializer: D,
) -> Result<Option<i64>, D::Error>
where
    D: Deserializer<'de>,
{
    struct IntegerOrString(PhantomData<fn() -> Option<i64>>);

    impl<'de> Visitor<'de> for IntegerOrString {
        type Value = Option<i64>;

        fn expecting(
            &self,
            formatter: &mut std::fmt::Formatter,
        ) -> std::fmt::Result {
            formatter.write_str("signed integer or string")
        }

        fn visit_str<E>(self, value: &str) -> Result<Option<i64>, E>
        where
            E: de::Error,
        {
            FromStr::from_str(value)
                .map_err(de::Error::custom)
                .map(Some)
        }

        fn visit_u64<E>(self, value: u64) -> Result<Option<i64>, E>
        where
            E: de::Error,
        {
            i64::try_from(value).map_err(de::Error::custom).map(Some)
        }

        fn visit_i64<E>(self, value: i64) -> Result<Option<i64>, E>
        where
            E: de::Error,
        {
            Ok(Some(value))
        }
    }

    deserializer.deserialize_any(IntegerOrString(PhantomData))
}

#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
#[serde(try_from = "serde_json::Value")]
pub(crate) struct NumberAsString {
    value: String,
}

impl NumberAsString {
    pub(crate) fn as_str(&self) -> &str {
        self.value.as_str()
    }
}

impl std::convert::TryFrom<serde_json::Value> for NumberAsString {
    type Error = NmstateError;
    fn try_from(s: serde_json::Value) -> Result<Self, NmstateError> {
        match s {
            serde_json::Value::Number(d) => Ok(Self {
                value: format!("{d}"),
            }),
            serde_json::Value::String(s) => Ok(Self { value: s }),
            _ => Err(NmstateError::new(
                ErrorKind::InvalidArgument,
                format!(
                    "Invalid data type: {s}, should be \
                     integer or string"
                ),
            )),
        }
    }
}