use anyhow::Result;
#[cfg(feature = "proptest")]
use proptest::{strategy::Strategy, string::string_regex};
use semver::Version;
use serde::{
de::Deserializer,
ser::{SerializeSeq, Serializer},
Deserialize, Serialize,
};
use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, str::FromStr, sync::Arc};
#[cfg(feature = "proptest")]
use test_strategy::Arbitrary;
use thiserror::Error;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "proptest", derive(Arbitrary))]
#[serde(try_from = "String")]
pub struct CrateName(
#[cfg_attr(feature = "proptest", strategy(string_regex("[a-zA-Z_-]+").unwrap().prop_map(|p| p.into())))]
Arc<str>,
);
#[derive(Error, Debug)]
pub enum CrateNameError {
#[error("identifier cannot be empty")]
Empty,
#[error("identifier too long ({length} bytes)")]
TooLong { length: usize },
#[error("identifier contains illegal character {character:?} at {index}")]
InvalidCharacter { character: char, index: usize },
}
impl CrateName {
pub fn new<S: Into<Arc<str>>>(name: S) -> Result<Self, CrateNameError> {
let name = name.into();
if name.len() == 0 {
return Err(CrateNameError::Empty);
}
if name.len() > 64 {
return Err(CrateNameError::TooLong { length: name.len() });
}
for (index, character) in name.chars().enumerate() {
match character {
'0'..='9' => continue,
'a'..='z' | 'A'..='Z' => continue,
'-' | '_' => continue,
_ => return Err(CrateNameError::InvalidCharacter { index, character }),
}
}
Ok(Self(name))
}
}
impl std::fmt::Display for CrateName {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(fmt)
}
}
impl Borrow<str> for CrateName {
fn borrow(&self) -> &str {
&self.0
}
}
impl TryFrom<String> for CrateName {
type Error = CrateNameError;
fn try_from(input: String) -> Result<Self, Self::Error> {
Self::new(input)
}
}
impl Deref for CrateName {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for CrateName {
type Err = CrateNameError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "proptest", derive(Arbitrary))]
pub struct CrateVersion {
pub krate: CrateName,
#[cfg_attr(feature = "proptest", strategy(crate::proptest::version()))]
pub version: Version,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "proptest", derive(Arbitrary))]
pub struct CrateMetadata {
pub name: CrateName,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CrateInfo {
pub metadata: CrateMetadata,
pub versions: BTreeMap<Version, VersionInfo>,
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "proptest", derive(Arbitrary))]
pub struct Checksum(#[serde(with = "hex")] [u8; 32]);
impl Checksum {
pub fn sha2_256(bytes: &[u8]) -> Self {
use sha2::Digest;
let mut hasher = sha2::Sha256::new();
hasher.update(bytes);
Self(hasher.finalize().into())
}
}
#[derive(Error, Debug)]
pub enum ChecksumParseError {
#[error(transparent)]
HexDecode(#[from] hex::FromHexError),
#[error("length mismatch, data was {0:} bytes")]
LengthMismatch(usize),
}
impl FromStr for Checksum {
type Err = ChecksumParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let result = hex::decode(input)?;
Ok(Self(result.try_into().map_err(|e: Vec<u8>| {
ChecksumParseError::LengthMismatch(e.len())
})?))
}
}
impl std::fmt::Debug for Checksum {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "Checksum(\"{}\")", hex::encode(self.0))
}
}
impl std::fmt::Display for Checksum {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "{}", hex::encode(self.0))
}
}
impl From<[u8; 32]> for Checksum {
fn from(raw: [u8; 32]) -> Self {
Self(raw)
}
}
impl From<&[u8; 32]> for Checksum {
fn from(raw: &[u8; 32]) -> Self {
Self(*raw)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "proptest", derive(Arbitrary))]
pub struct VersionInfo {
#[serde(rename = "crate")]
pub krate: CrateName,
#[cfg_attr(feature = "proptest", strategy(crate::proptest::version()))]
pub version: Version,
pub yanked: bool,
pub checksum: Checksum,
}
#[cfg(all(test, feature = "proptest"))]
mod proptests {
#[allow(unused)]
use super::CrateName;
use test_strategy::proptest;
#[proptest]
fn identifier(identifier: CrateName) {
CrateName::new(identifier.0).unwrap();
}
}