git-lfs-spec 0.1.0

Rust types for Git LFS
Documentation
use lazy_static::lazy_static;
use regex::Regex;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

use std::fmt;

const VERSION: &str = "https://git-lfs.github.com/spec/v1";
const KEY_REGEX_STR: &str = "[a-z0-9.-]+";

lazy_static! {
    static ref KEY_REGEX: Regex = Regex::new(KEY_REGEX_STR).unwrap();
}

#[derive(Debug)]
pub struct Pointer {
    lines: Vec<Line>,
}

impl Default for Pointer {
    fn default() -> Self {
        Self {
            lines: vec![Line {
                key: "version".to_string(),
                value: VERSION.to_string(),
            }],
        }
    }
}

impl Serialize for Pointer {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(
            self.lines
                .iter()
                .fold(String::new(), |mut acc, line| {
                    acc.push_str(format!("{} {}\n", line.key, line.value).as_str());
                    acc
                })
                .as_str(),
        )
    }
}

impl<'de> Deserialize<'de> for Pointer {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct PointerVisitor;
        impl<'de> de::Visitor<'de> for PointerVisitor {
            type Value = Pointer;
            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                write!(formatter, "a string in the format <key> <value>")
            }

            fn visit_str<E>(self, lines_str: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                let lines: Vec<Line> =
                    lines_str
                        .split_terminator('\n')
                        .try_fold(vec![], |mut acc, line_str| {
                            let mut it = line_str.split_whitespace();
                            let key = it.next().ok_or_else(|| de::Error::missing_field("key"))?;
                            if !KEY_REGEX.is_match(key) {
                                return Err(de::Error::invalid_value(
                                    de::Unexpected::Str(key),
                                    &format!("expected key to match regex {}", KEY_REGEX.as_str())
                                        .as_str(),
                                ));
                            }
                            let value =
                                it.next().ok_or_else(|| de::Error::missing_field("value"))?;
                            let count = it.count();
                            if count != 2 {
                                return Err(de::Error::invalid_length(
                                    count,
                                    &"expected 2 elements",
                                ));
                            }
                            acc.push(Line {
                                key: key.to_string(),
                                value: value.to_string(),
                            });
                            Ok(acc)
                        })?;
                Ok(Pointer { lines })
            }
        }
        deserializer.deserialize_string(PointerVisitor)
    }
}

#[derive(Debug)]
pub struct Line {
    key: String,
    value: String,
}

impl Serialize for Line {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(format!("{} {}", self.key, self.value).as_str())
    }
}

impl<'de> Deserialize<'de> for Line {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct LineVisitor;
        impl<'de> de::Visitor<'de> for LineVisitor {
            type Value = Line;
            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                write!(formatter, "a string in the format <key> <value>")
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                let mut it = v.split_whitespace();
                let key = it.next().ok_or_else(|| de::Error::missing_field("key"))?;
                if !KEY_REGEX.is_match(key) {
                    return Err(de::Error::invalid_value(
                        de::Unexpected::Str(key),
                        &format!("expected key to match regex {}", KEY_REGEX.as_str()).as_str(),
                    ));
                }
                let value = it.next().ok_or_else(|| de::Error::missing_field("value"))?;
                let count = it.count();
                if count != 2 {
                    return Err(de::Error::invalid_length(count, &"expected 2 elements"));
                }
                Ok(Line {
                    key: key.to_string(),
                    value: value.to_string(),
                })
            }
        }
        deserializer.deserialize_string(LineVisitor)
    }
}