use crate::{
Map,
error::{Error, ErrorKind},
};
use platforms::target::{Arch, OS};
use semver::VersionReq;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
use std::{
fmt::{self, Display},
slice,
str::FromStr,
};
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Affected {
#[serde(default)]
pub arch: Vec<Arch>,
#[serde(default)]
pub os: Vec<OS>,
#[serde(default)]
pub functions: Map<FunctionPath, Vec<VersionReq>>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct FunctionPath(Vec<Identifier>);
impl FunctionPath {
pub fn crate_name(&self) -> &str {
self.iter()
.next()
.expect("path must have 2 or more segments")
.as_str()
}
pub fn into_vec(self) -> Vec<Identifier> {
self.0
}
pub fn iter(&self) -> slice::Iter<'_, Identifier> {
self.0.iter()
}
pub fn segments(&self) -> &[Identifier] {
self.0.as_slice()
}
}
impl Display for FunctionPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut segments = self.iter();
let crate_name = segments.next().expect("path must have 2 or more segments");
write!(f, "{}", crate_name.as_str())?;
for segment in segments {
write!(f, "::{}", segment.as_str())?;
}
Ok(())
}
}
impl FromStr for FunctionPath {
type Err = Error;
fn from_str(path: &str) -> Result<Self, Error> {
let mut segments = vec![];
for segment in path.split("::") {
segments.push(segment.parse()?);
}
if segments.len() >= 2 {
Ok(FunctionPath(segments))
} else {
fail!(
ErrorKind::Parse,
"paths must start with the crate name (i.e. minimum two segments): '{}'",
path
)
}
}
}
impl<'de> Deserialize<'de> for FunctionPath {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let string = String::deserialize(deserializer)?;
string.parse().map_err(|e| D::Error::custom(format!("{e}")))
}
}
impl Serialize for FunctionPath {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_string())
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Identifier(String);
impl Identifier {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for Identifier {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl FromStr for Identifier {
type Err = Error;
fn from_str(identifier: &str) -> Result<Self, Error> {
validate_identifier(identifier)?;
Ok(Identifier(identifier.into()))
}
}
fn validate_identifier(identifier: &str) -> Result<(), Error> {
let mut chars = identifier.chars();
if let Some(first_char) = chars.next() {
match first_char {
'A'..='Z' | 'a'..='z' | '_' | '<' => (),
_ => fail!(
ErrorKind::Parse,
"invalid character at start of ident: '{}'",
identifier
),
}
} else {
fail!(ErrorKind::Parse, "empty identifier in affected path");
}
for c in chars {
match c {
'A'..='Z' | 'a'..='z' | '0'..='9' | '_' | '<' | '>' | ',' => (),
'(' | ')' => fail!(
ErrorKind::Parse,
"omit parameters when specifying affected paths: '{}'",
identifier
),
_ => fail!(
ErrorKind::Parse,
"invalid character in identifier: '{}'",
identifier
),
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::FunctionPath;
use std::str::FromStr;
const EXAMPLE_PATH_STR: &str = "foo::bar::baz";
#[test]
fn crate_name_test() {
let path = FunctionPath::from_str(EXAMPLE_PATH_STR).unwrap();
assert_eq!(path.crate_name(), "foo");
}
#[test]
fn display_test() {
let path = FunctionPath::from_str(EXAMPLE_PATH_STR).unwrap();
assert_eq!(path.to_string(), EXAMPLE_PATH_STR)
}
#[test]
fn from_str_test() {
assert!(FunctionPath::from_str("foo::bar").is_ok());
assert!(FunctionPath::from_str("foo::bar::baz").is_ok());
assert!(FunctionPath::from_str("foo::Bar::baz").is_ok());
assert!(FunctionPath::from_str("foo::<Bar>::baz").is_ok());
assert!(FunctionPath::from_str("foo::<BarA,BarB>::baz").is_ok());
assert!(FunctionPath::from_str("foo::Bar<Baz>::quux").is_ok());
assert!(FunctionPath::from_str("foo::Bar::_baz").is_ok());
assert!(FunctionPath::from_str("foo::Bar::_baz_").is_ok());
assert!(FunctionPath::from_str("f00::B4r::_b4z_").is_ok());
assert!(FunctionPath::from_str("minimum_two_components").is_err());
assert!(FunctionPath::from_str("no-hyphens::foobar").is_err());
assert!(FunctionPath::from_str("no_leading_digits::0rly").is_err());
}
}