modio 0.13.0

Rust interface for integrating https://mod.io - a modding API for game developers
Documentation
use serde::de::{DeserializeOwned, Error, MapAccess};

mod smallstr {
    use std::fmt;

    use serde::de::{Deserialize, Deserializer, Error, Visitor};
    use serde::ser::{Serialize, Serializer};

    #[derive(Clone, Copy, Eq, Hash, PartialEq)]
    pub struct SmallStr<const LENGTH: usize> {
        bytes: [u8; LENGTH],
    }

    impl<const LENGTH: usize> SmallStr<LENGTH> {
        pub(crate) const fn from_str(input: &str) -> Option<Self> {
            if input.len() > LENGTH {
                return None;
            }
            Some(Self::from_bytes(input.as_bytes()))
        }

        pub(crate) const fn from_bytes(input: &[u8]) -> Self {
            let mut bytes = [0; LENGTH];
            let mut idx = 0;

            while idx < input.len() {
                bytes[idx] = input[idx];
                idx += 1;
            }

            Self { bytes }
        }

        pub fn as_str(&self) -> &str {
            std::str::from_utf8(&self.bytes)
                .expect("invalid utf8 string")
                .trim_end_matches('\0')
        }
    }

    impl<const LENGTH: usize> fmt::Debug for SmallStr<LENGTH> {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str(self.as_str())
        }
    }

    impl<'de, const LENGTH: usize> Deserialize<'de> for SmallStr<LENGTH> {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            struct StrVisitor<const LENGTH: usize>;

            impl<const LENGTH: usize> Visitor<'_> for StrVisitor<LENGTH> {
                type Value = SmallStr<LENGTH>;

                fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                    formatter.write_str("string")
                }

                fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
                    SmallStr::from_str(v).ok_or_else(|| Error::custom("string is too long"))
                }

                fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
                    SmallStr::from_str(&v).ok_or_else(|| Error::custom("string is too long"))
                }
            }

            deserializer.deserialize_any(StrVisitor::<LENGTH>)
        }
    }

    impl<const LENGTH: usize> Serialize for SmallStr<LENGTH> {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            self.as_str().serialize(serializer)
        }
    }
}
pub use smallstr::SmallStr;

pub mod url {
    use std::fmt;

    use serde::de::{Deserializer, Error, Visitor};
    use url::Url;

    struct UrlVisitor;

    impl Visitor<'_> for UrlVisitor {
        type Value = Url;

        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
            formatter.write_str("a string representing an URL")
        }

        fn visit_str<E: Error>(self, s: &str) -> Result<Self::Value, E> {
            Url::parse(s).map_err(|err| Error::custom(format!("{err}: {s:?}")))
        }
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Url, D::Error> {
        deserializer.deserialize_any(UrlVisitor)
    }

    pub mod opt {
        use std::fmt;

        use serde::de::{Deserializer, Error, Visitor};
        use url::Url;

        struct UrlVisitor;

        impl<'de> Visitor<'de> for UrlVisitor {
            type Value = Option<Url>;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter.write_str("an optional string representing an URL")
            }

            fn visit_some<D: Deserializer<'de>>(self, d: D) -> Result<Self::Value, D::Error> {
                d.deserialize_any(super::UrlVisitor).map(Some)
            }

            fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
                Ok(None)
            }

            fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
                Ok(None)
            }
        }

        pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Url>, D::Error> {
            d.deserialize_option(UrlVisitor)
        }
    }
}

pub trait DeserializeField<T: DeserializeOwned> {
    fn deserialize_value<'de, A: MapAccess<'de>>(
        &mut self,
        name: &'static str,
        map: &mut A,
    ) -> Result<(), A::Error>;
}

impl<T: DeserializeOwned> DeserializeField<T> for Option<T> {
    fn deserialize_value<'de, A>(&mut self, name: &'static str, map: &mut A) -> Result<(), A::Error>
    where
        A: MapAccess<'de>,
    {
        if self.is_some() {
            return Err(A::Error::duplicate_field(name));
        }
        self.replace(map.next_value()?);
        Ok(())
    }
}

pub trait MissingField<T> {
    fn missing_field<E: Error>(self, name: &'static str) -> Result<T, E>;
}

impl<T> MissingField<T> for Option<T> {
    fn missing_field<E: Error>(self, name: &'static str) -> Result<T, E> {
        self.ok_or_else(|| Error::missing_field(name))
    }
}