use {Error, serde, std};
use uuid::Uuid;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Revision {
sequence_number: u64,
digest: Uuid,
}
impl Revision {
pub fn parse(s: &str) -> Result<Self, Error> {
use std::str::FromStr;
Revision::from_str(s)
}
pub fn sequence_number(&self) -> u64 {
self.sequence_number
}
}
impl std::fmt::Display for Revision {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}-{}", self.sequence_number, self.digest.simple())
}
}
impl std::str::FromStr for Revision {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.splitn(2, '-');
let sequence_number_str = parts.next().ok_or(Error::BadRevision)?;
let sequence_number = u64::from_str_radix(sequence_number_str, 10).map_err(|_| {
Error::BadRevision
})?;
if sequence_number == 0 {
return Err(Error::BadRevision);
}
let digest_str = parts.next().ok_or(Error::BadRevision)?;
let digest = Uuid::parse_str(digest_str).map_err(|_| Error::BadRevision)?;
if digest_str.chars().any(|c| !c.is_digit(16)) {
return Err(Error::BadRevision);
}
Ok(Revision {
sequence_number: sequence_number,
digest: digest,
})
}
}
impl From<Revision> for String {
fn from(revision: Revision) -> Self {
revision.to_string()
}
}
impl serde::Serialize for Revision {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = self.to_string();
serializer.serialize_str(&s)
}
}
impl<'de> serde::Deserialize<'de> for Revision {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Revision;
fn expecting(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "a string specifying a CouchDB document revision")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Revision::parse(v).map_err(|_e| E::invalid_value(serde::de::Unexpected::Str(v), &self))
}
}
deserializer.deserialize_str(Visitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn parse_ok() {
let expected = Revision {
sequence_number: 42,
digest: "1234567890abcdeffedcba0987654321".parse().unwrap(),
};
let got = Revision::parse("42-1234567890abcdeffedcba0987654321").unwrap();
assert_eq!(expected, got);
}
#[test]
fn parse_nok() {
Revision::parse("bad_revision").unwrap_err();
}
#[test]
fn sequence_number() {
let rev = Revision::parse("999-1234567890abcdef1234567890abcdef").unwrap();
assert_eq!(999, rev.sequence_number());
}
#[test]
fn display() {
let expected = "42-1234567890abcdeffedcba0987654321";
let source = Revision {
sequence_number: 42,
digest: "1234567890abcdeffedcba0987654321".parse().unwrap(),
};
let got = format!("{}", source);
assert_eq!(expected, got);
}
#[test]
fn from_str_ok() {
use std::str::FromStr;
let expected = Revision {
sequence_number: 42,
digest: "1234567890abcdeffedcba0987654321".parse().unwrap(),
};
let got = Revision::from_str("42-1234567890abcdeffedcba0987654321").unwrap();
assert_eq!(expected, got);
}
#[test]
fn from_str_nok() {
macro_rules! expect_error {
($input: expr) => {
match Revision::from_str($input) {
Err(Error::RevisionParse{..}) => (),
x => panic!("Got unexpected result {:?}", x),
}
}
}
use std::str::FromStr;
Revision::from_str("12345678123456781234567812345678").unwrap_err();
Revision::from_str("-12345678123456781234567812345678").unwrap_err();
Revision::from_str("1-").unwrap_err();
Revision::from_str("1-1234567890abcdef1234567890abcdef-").unwrap_err();
Revision::from_str("-42-12345678123456781234567812345678").unwrap_err();
Revision::from_str("18446744073709551616-12345678123456781234567812345678").unwrap_err(); Revision::from_str("0-12345678123456781234567812345678").unwrap_err(); Revision::from_str("1-z2345678123456781234567812345678").unwrap_err();
Revision::from_str("1-1234567812345678123456781234567").unwrap_err();
Revision::from_str("bad_revision_blah_blah_blah").unwrap_err();
}
#[test]
fn string_from_revision() {
let expected = "42-1234567890abcdeffedcba0987654321";
let source = Revision {
sequence_number: 42,
digest: "1234567890abcdeffedcba0987654321".parse().unwrap(),
};
let got = format!("{}", source);
assert_eq!(expected, got);
}
#[test]
fn eq_same() {
let r1 = Revision::parse("1-1234567890abcdef1234567890abcdef").unwrap();
let r2 = Revision::parse("1-1234567890abcdef1234567890abcdef").unwrap();
assert!(r1 == r2);
}
#[test]
fn eq_different_numbers() {
let r1 = Revision::parse("1-1234567890abcdef1234567890abcdef").unwrap();
let r2 = Revision::parse("7-1234567890abcdef1234567890abcdef").unwrap();
assert!(r1 != r2);
}
#[test]
fn eq_different_digests() {
let r1 = Revision::parse("1-1234567890abcdef1234567890abcdef").unwrap();
let r2 = Revision::parse("1-9999567890abcdef1234567890abcdef").unwrap();
assert!(r1 != r2);
}
#[test]
fn eq_case_insensitive() {
let r1 = Revision::parse("1-1234567890abcdef1234567890ABCDEF").unwrap();
let r2 = Revision::parse("1-1234567890ABCDEf1234567890abcdef").unwrap();
assert!(r1 == r2);
}
#[test]
fn serialization_and_deserialization_ok() {
let source = r#""42-1234567890abcdeffedcba0987654321""#;
let expected = Revision::parse("42-1234567890abcdeffedcba0987654321").unwrap();
let got: Revision = serde_json::from_str(source).unwrap();
assert_eq!(got, expected);
}
#[test]
fn deserialization_enforces_revision_validity() {
let source = r#""obviously bad revision""#;
match serde_json::from_str::<Revision>(source) {
Err(ref e) if e.is_data() => {}
x => panic!("Got unexpected result {:?}", x),
}
}
}