use std::cmp::Ordering;
use std::fmt;
use std::iter::{DoubleEndedIterator, FusedIterator};
use std::str::FromStr;
use std::vec::IntoIter;
use serde::de::{DeserializeOwned, Error as DeserError};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use url::Url;
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct Link {
pub href: Url,
pub rel: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Ref {
pub id: String,
pub links: Vec<Link>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct IdAndName {
pub id: String,
pub name: String,
}
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct XdotY<T>(pub T, pub T);
#[derive(Clone, Debug, Deserialize)]
pub struct Version {
pub id: XdotY<u16>,
#[serde(default)]
pub links: Vec<Link>,
#[serde(deserialize_with = "empty_as_default", default)]
pub status: Option<String>,
#[serde(deserialize_with = "empty_as_default", default)]
pub version: Option<XdotY<u16>>,
#[serde(deserialize_with = "empty_as_default", default)]
pub min_version: Option<XdotY<u16>>,
}
impl Version {
#[inline]
pub fn is_stable(&self) -> bool {
if let Some(ref status) = self.status {
let upper = status.to_uppercase();
upper == "STABLE" || upper == "CURRENT" || upper == "SUPPORTED"
} else {
true
}
}
}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Version {}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(&other))
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum Root {
MultipleVersions { versions: Vec<Version> },
OneVersion { version: Version },
}
#[derive(Debug, Clone)]
enum IntoStableIterInner {
Many(IntoIter<Version>),
One(Option<Version>),
}
#[derive(Debug)]
pub struct IntoStableIter(IntoStableIterInner);
impl Iterator for IntoStableIter {
type Item = Version;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
IntoStableIterInner::Many(ref mut inner) => {
for next in inner {
if next.is_stable() {
return Some(next);
}
}
None
}
IntoStableIterInner::One(ref mut opt) => opt.take(),
}
}
}
impl DoubleEndedIterator for IntoStableIter {
fn next_back(&mut self) -> Option<Self::Item> {
match self.0 {
IntoStableIterInner::Many(ref mut inner) => {
while let Some(next) = inner.next_back() {
if next.is_stable() {
return Some(next);
}
}
None
}
IntoStableIterInner::One(ref mut opt) => opt.take(),
}
}
}
impl FusedIterator for IntoStableIter {}
impl Root {
#[inline]
pub fn sort(&mut self) {
if let Root::MultipleVersions {
versions: ref mut vers,
} = self
{
vers.sort_unstable();
}
}
#[inline]
pub fn into_sorted(mut self) -> Self {
self.sort();
self
}
pub fn into_stable_iter(self) -> IntoStableIter {
match self {
Root::MultipleVersions { versions: vers } => {
IntoStableIter(IntoStableIterInner::Many(vers.into_iter()))
}
Root::OneVersion { version: ver } => {
let stable = if ver.is_stable() { Some(ver) } else { None };
IntoStableIter(IntoStableIterInner::One(stable))
}
}
}
}
impl<T> fmt::Display for XdotY<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}", self.0, self.1)
}
}
impl<T> fmt::Debug for XdotY<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("").field(&self.0).field(&self.1).finish()
}
}
impl<T> Serialize for XdotY<T>
where
T: fmt::Display,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<T> FromStr for XdotY<T>
where
T: FromStr + Default,
T::Err: fmt::Display,
{
type Err = String;
fn from_str(s: &str) -> Result<XdotY<T>, String> {
let mut parts = s.split('.');
if let Some(x_part) = parts.next() {
let x = x_part
.parse()
.map_err(|err| format!("cannot parse the first component: {}", err))?;
let y = if let Some(y_part) = parts.next() {
y_part
.parse()
.map_err(|err| format!("cannot parse the second component: {}", err))?
} else {
T::default()
};
if parts.next().is_some() {
Err(format!("expected X.Y, got {}", s))
} else {
Ok(XdotY(x, y))
}
} else {
Err(format!("expected X.Y, got {}", s))
}
}
}
impl<'de, T> Deserialize<'de> for XdotY<T>
where
T: FromStr + Default,
T::Err: fmt::Display,
{
fn deserialize<D>(deserializer: D) -> Result<XdotY<T>, D::Error>
where
D: Deserializer<'de>,
{
let value: String = Deserialize::deserialize(deserializer)?;
let version_part = if value.starts_with('v') {
&value[1..]
} else {
&value
};
XdotY::from_str(&version_part).map_err(D::Error::custom)
}
}
impl<T> From<(T, T)> for XdotY<T> {
fn from(value: (T, T)) -> XdotY<T> {
XdotY(value.0, value.1)
}
}
pub fn empty_as_default<'de, D, T>(des: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: DeserializeOwned + Default,
{
let value = Value::deserialize(des)?;
match value {
Value::String(ref s) if s.is_empty() => Ok(T::default()),
_ => serde_json::from_value(value).map_err(D::Error::custom),
}
}
#[cfg(test)]
pub mod test {
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use serde_json;
use super::{empty_as_default, Root, Version, XdotY};
pub fn compare<T: Serialize>(sample: &str, value: T) {
let converted: serde_json::Value = serde_json::from_str(sample).unwrap();
let result = serde_json::to_value(value).unwrap();
assert_eq!(result, converted);
}
#[derive(Debug, Deserialize)]
struct Custom(bool);
#[derive(Debug, Deserialize)]
struct EmptyAsDefault {
#[serde(deserialize_with = "empty_as_default")]
number: u8,
#[serde(deserialize_with = "empty_as_default")]
vec: Vec<String>,
#[serde(deserialize_with = "empty_as_default")]
opt: Option<Custom>,
#[serde(deserialize_with = "empty_as_default")]
string: Option<String>,
}
#[test]
fn test_empty_as_default_with_values() {
let s = "{\"number\": 42, \"vec\": [\"value\"], \"opt\": true, \"string\": \"value\"}";
let r: EmptyAsDefault = serde_json::from_str(s).unwrap();
assert_eq!(r.number, 42);
assert_eq!(r.vec, vec!["value".to_string()]);
assert!(r.opt.unwrap().0);
assert_eq!(r.string.unwrap(), "value");
}
#[test]
fn test_empty_as_default_with_empty_string() {
let s = "{\"number\": \"\", \"vec\": \"\", \"opt\": \"\", \"string\": \"\"}";
let r: EmptyAsDefault = serde_json::from_str(s).unwrap();
assert_eq!(r.number, 0);
assert!(r.vec.is_empty());
assert!(r.opt.is_none());
assert!(r.string.is_none());
}
#[test]
fn test_xdoty_debug() {
let xy = XdotY(1, 2);
let s = format!("{:?}", xy);
assert_eq!(s, "(1, 2)");
let s2 = format!("{:?}", (1, 2));
assert_eq!(s, s2);
}
#[test]
fn test_xdoty_display() {
let xy = XdotY(1, 2);
let s = format!("{}", xy);
assert_eq!(s, "1.2");
}
#[test]
fn test_xdoty_from_str() {
let xy: XdotY<u8> = XdotY::from_str("1.2").unwrap();
assert_eq!(xy.0, 1);
assert_eq!(xy.1, 2);
}
#[test]
fn test_xdoty_from_str_no_y() {
let xy: XdotY<u8> = XdotY::from_str("1").unwrap();
assert_eq!(xy.0, 1);
assert_eq!(xy.1, 0);
}
#[test]
fn test_xdoty_from_str_failure() {
for s in &["foo", "1.foo", "foo.2", "1.2.3"] {
let res: Result<XdotY<u8>, _> = XdotY::from_str(s);
assert!(res.is_err());
}
}
#[test]
fn test_xdoty_serde_serialize() {
let xy = XdotY(2u8, 27);
let ser = serde_json::to_string(&xy).unwrap();
assert_eq!(&ser, "\"2.27\"");
}
#[derive(Debug, Deserialize)]
struct Struct {
pub req: XdotY<u16>,
pub opt: Option<XdotY<u16>>,
}
#[test]
fn test_xdoty_serde_deserialize() {
let xy: XdotY<u8> = serde_json::from_str("\"2.27\"").unwrap();
assert_eq!(xy, XdotY(2, 27));
let xy2: XdotY<u16> =
serde_json::from_value(serde_json::Value::String("2.27".to_string())).unwrap();
assert_eq!(xy2, XdotY(2, 27));
let st: Struct = serde_json::from_str("{\"req\": \"2.27\", \"opt\": \"2.42\"}").unwrap();
assert_eq!(st.req, XdotY(2, 27));
assert_eq!(st.opt.unwrap(), XdotY(2, 42));
}
#[test]
fn test_xdoty_serde_deserialize_with_v() {
let xy: XdotY<u8> = serde_json::from_str("\"v2.27\"").unwrap();
assert_eq!(xy, XdotY(2, 27));
let xy2: XdotY<u16> =
serde_json::from_value(serde_json::Value::String("v2.27".to_string())).unwrap();
assert_eq!(xy2, XdotY(2, 27));
let st: Struct = serde_json::from_str("{\"req\": \"v2.27\", \"opt\": \"v2.42\"}").unwrap();
assert_eq!(st.req, XdotY(2, 27));
assert_eq!(st.opt.unwrap(), XdotY(2, 42));
}
#[test]
fn test_version_current_is_stable() {
let stable = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: Some("CURRENT".to_string()),
version: None,
min_version: None,
};
assert!(stable.is_stable());
}
#[test]
fn test_version_stable_is_stable() {
let stable = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: Some("Stable".to_string()),
version: None,
min_version: None,
};
assert!(stable.is_stable());
}
#[test]
fn test_version_supported_is_stable() {
let stable = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: Some("supported".to_string()),
version: None,
min_version: None,
};
assert!(stable.is_stable());
}
#[test]
fn test_version_no_status_is_stable() {
let stable = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: None,
version: None,
min_version: None,
};
assert!(stable.is_stable());
}
#[test]
fn test_version_deprecated_is_not_stable() {
let unstable = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: Some("DEPRECATED".to_string()),
version: None,
min_version: None,
};
assert!(!unstable.is_stable());
}
#[test]
fn test_root_sort() {
let vers: Vec<_> = [3, 1, 2]
.iter()
.map(|idx| Version {
id: XdotY(*idx, 0),
links: Vec::new(),
status: None,
version: None,
min_version: None,
})
.collect();
let mut root = Root::MultipleVersions { versions: vers };
root.sort();
if let Root::MultipleVersions { versions: res } = root {
let idx = res.into_iter().map(|v| v.id.0).collect::<Vec<_>>();
assert_eq!(idx, vec![1, 2, 3]);
} else {
unreachable!();
}
}
#[test]
fn test_root_sort_one() {
let ver = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: Some("supported".to_string()),
version: None,
min_version: None,
};
let mut root = Root::OneVersion { version: ver };
root.sort();
if let Root::OneVersion { version: res } = root {
assert_eq!(res.id.0, 2);
} else {
unreachable!();
}
}
#[test]
fn test_root_into_sorted() {
let vers: Vec<_> = [3, 1, 2]
.iter()
.map(|idx| Version {
id: XdotY(*idx, 0),
links: Vec::new(),
status: None,
version: None,
min_version: None,
})
.collect();
let mut root = Root::MultipleVersions { versions: vers };
root = root.into_sorted();
if let Root::MultipleVersions { versions: res } = root {
let idx = res.into_iter().map(|v| v.id.0).collect::<Vec<_>>();
assert_eq!(idx, vec![1, 2, 3]);
} else {
unreachable!();
}
}
#[test]
fn test_root_into_stable_iter() {
let vers: Vec<_> = [3, 1, 2]
.iter()
.map(|idx| Version {
id: XdotY(*idx, 0),
links: Vec::new(),
status: Some(if *idx > 1 { "CURRENT" } else { "DEPRECATED" }.to_string()),
version: None,
min_version: None,
})
.collect();
let root = Root::MultipleVersions { versions: vers };
let idx = root
.into_stable_iter()
.map(|ver| ver.id.0)
.collect::<Vec<_>>();
assert_eq!(idx, vec![3, 2]);
}
#[test]
fn test_root_into_stable_iter_reverse() {
let vers: Vec<_> = [3, 1, 2]
.iter()
.map(|idx| Version {
id: XdotY(*idx, 0),
links: Vec::new(),
status: Some(if *idx > 1 { "CURRENT" } else { "DEPRECATED" }.to_string()),
version: None,
min_version: None,
})
.collect();
let root = Root::MultipleVersions { versions: vers };
let mut idx = root.into_stable_iter().map(|ver| ver.id.0);
assert_eq!(idx.next_back(), Some(2));
assert_eq!(idx.next_back(), Some(3));
assert!(idx.next_back().is_none());
assert!(idx.next().is_none());
}
#[test]
fn test_root_into_stable_iter_one() {
let ver = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: Some("supported".to_string()),
version: None,
min_version: None,
};
let root = Root::OneVersion { version: ver };
let idx = root
.into_stable_iter()
.map(|ver| ver.id.0)
.collect::<Vec<_>>();
assert_eq!(idx, vec![2]);
}
#[test]
fn test_root_into_stable_iter_one_unstable() {
let ver = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: Some("deprecated".to_string()),
version: None,
min_version: None,
};
let root = Root::OneVersion { version: ver };
let mut idx = root.into_stable_iter().map(|ver| ver.id.0);
assert!(idx.next().is_none());
}
#[test]
fn test_root_into_stable_iter_one_reverse() {
let ver = Version {
id: XdotY(2, 0),
links: Vec::new(),
status: Some("supported".to_string()),
version: None,
min_version: None,
};
let root = Root::OneVersion { version: ver };
let mut idx = root.into_stable_iter().map(|ver| ver.id.0);
assert_eq!(idx.next_back(), Some(2));
assert!(idx.next_back().is_none());
}
const COMPUTE_ONE: &str = r#"{
"version": {
"status": "CURRENT",
"updated": "2013-07-23T11:33:21Z",
"links": [
{
"href": "https://example.org:13774/v2.1/",
"rel": "self"
},
{
"href": "http://docs.openstack.org/",
"type": "text/html",
"rel": "describedby"
}
],
"min_version": "2.1",
"version": "2.42",
"media-types": [
{
"base": "application/json",
"type": "application/vnd.openstack.compute+json;version=2.1"
}
],
"id": "v2.1"
}
}"#;
#[test]
fn test_parse_root_one_version() {
let root: Root = serde_json::from_str(COMPUTE_ONE).unwrap();
match root {
Root::OneVersion { version } => {
assert_eq!(version.id, XdotY(2, 1));
}
Root::MultipleVersions { .. } => panic!("Unexpected multiple versions"),
}
}
}