use std::collections::HashMap;
use std::io::Cursor;
use std::str::FromStr;
use byteorder::{LittleEndian, ReadBytesExt};
use serde_derive::{Deserialize, Serialize};
use crate::utils::apierror::{error_invalid_request, specialize, ApiError};
use crate::utils::hashes::sha256;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SearchResultCrate {
pub name: String,
pub max_version: String,
#[serde(rename = "isDeprecated")]
pub is_deprecated: bool,
pub description: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SearchResultsMeta {
pub total: usize,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SearchResults {
pub crates: Vec<SearchResultCrate>,
pub meta: SearchResultsMeta,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ApiResponseErrors {
pub errors: Vec<ApiResponseError>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ApiResponseError {
pub detail: String,
}
impl From<ApiError> for ApiResponseErrors {
fn from(err: ApiError) -> Self {
ApiResponseErrors {
errors: vec![ApiResponseError { detail: err.to_string() }],
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct YesNoResult {
pub ok: bool,
}
impl YesNoResult {
#[must_use]
pub fn new() -> YesNoResult {
YesNoResult { ok: true }
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct YesNoMsgResult {
pub ok: bool,
pub msg: String,
}
impl YesNoMsgResult {
#[must_use]
pub fn new(msg: String) -> YesNoMsgResult {
YesNoMsgResult { ok: true, msg }
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct OwnersQueryResult {
pub users: Vec<RegistryUser>,
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct OwnersChangeQuery {
pub users: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RegistryUser {
pub id: i64,
#[serde(rename = "isActive")]
pub is_active: bool,
pub email: String,
pub login: String,
pub name: String,
pub roles: String,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct CrateMetadata {
pub name: String,
pub vers: String,
pub deps: Vec<CrateMetadataDependency>,
pub features: HashMap<String, Vec<String>>,
pub authors: Vec<String>,
pub description: Option<String>,
pub documentation: Option<String>,
pub homepage: Option<String>,
pub readme: Option<String>,
pub readme_file: Option<String>,
pub keywords: Vec<String>,
pub categories: Vec<String>,
pub license: Option<String>,
pub license_file: Option<String>,
pub repository: Option<String>,
pub badges: HashMap<String, serde_json::Value>,
pub links: Option<String>,
pub rust_version: Option<String>,
}
impl CrateMetadata {
pub fn validate(&self) -> Result<CrateUploadResult, ApiError> {
self.validate_name()?;
Ok(CrateUploadResult::default())
}
fn validate_name(&self) -> Result<(), ApiError> {
if self.name.is_empty() {
return validation_error("Name must not be empty");
}
if self.name.len() > 64 {
return validation_error("Name must not exceed 64 characters");
}
for (i, c) in self.name.chars().enumerate() {
match (i, c) {
(0, c) if !c.is_ascii_alphabetic() => {
return validation_error("Name must start with an ASCII letter");
}
(_, c) if !c.is_ascii_alphanumeric() && c != '-' && c != '_' => {
return validation_error("Name must only contain alphanumeric, -, _");
}
_ => { }
}
}
Ok(())
}
}
pub fn validation_error(details: &str) -> Result<(), ApiError> {
Err(specialize(error_invalid_request(), details.to_string()))
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DependencyKind {
#[default]
#[serde(rename = "normal")]
Normal,
#[serde(rename = "dev")]
Dev,
#[serde(rename = "build")]
Build,
}
impl FromStr for DependencyKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"normal" => Ok(DependencyKind::Normal),
"dev" => Ok(DependencyKind::Dev),
"build" => Ok(DependencyKind::Build),
_ => Err(()),
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct CrateMetadataDependency {
pub name: String,
pub version_req: String,
pub features: Vec<String>,
pub optional: bool,
pub default_features: bool,
pub target: Option<String>,
pub kind: DependencyKind,
pub registry: Option<String>,
pub explicit_name_in_toml: Option<String>,
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct CrateUploadResult {
pub warnings: CrateUploadWarnings,
}
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct CrateUploadWarnings {
pub invalid_categories: Vec<String>,
pub invalid_badges: Vec<String>,
pub other: Vec<String>,
}
pub struct CrateUploadData {
pub metadata: CrateMetadata,
pub content: Vec<u8>,
}
impl CrateUploadData {
#[allow(clippy::cast_possible_truncation)]
pub fn new(buffer: &[u8]) -> Result<CrateUploadData, ApiError> {
let mut cursor = Cursor::new(buffer);
let metadata_length = u64::from(cursor.read_u32::<LittleEndian>()?);
let metadata_buffer = &buffer[4..((4 + metadata_length) as usize)];
let metadata = serde_json::from_slice(metadata_buffer)?;
cursor.set_position(4 + metadata_length);
let content_length = cursor.read_u32::<LittleEndian>()? as usize;
let mut content = vec![0_u8; content_length];
content.copy_from_slice(&buffer[((4 + metadata_length + 4) as usize)..]);
Ok(CrateUploadData { metadata, content })
}
pub fn build_index_data(&self) -> IndexCrateMetadata {
let cksum = sha256(&self.content);
IndexCrateMetadata {
name: self.metadata.name.clone(),
vers: self.metadata.vers.clone(),
deps: self.metadata.deps.iter().map(IndexCrateDependency::from).collect(),
cksum,
features: HashMap::new(),
yanked: false,
links: self.metadata.links.clone(),
v: Some(2),
features2: Some(self.metadata.features.clone()),
rust_version: self.metadata.rust_version.clone(),
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct IndexCrateMetadata {
pub name: String,
pub vers: String,
pub deps: Vec<IndexCrateDependency>,
pub cksum: String,
pub features: HashMap<String, Vec<String>>,
pub yanked: bool,
pub links: Option<String>,
pub v: Option<u32>,
pub features2: Option<HashMap<String, Vec<String>>>,
pub rust_version: Option<String>,
}
impl IndexCrateMetadata {
pub fn get_feature(&self, feature: &str) -> Option<&[String]> {
self.features2
.as_ref()
.and_then(|r| r.get(feature))
.or_else(|| self.features.get(feature))
.map(Vec::as_slice)
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct IndexCrateDependency {
pub name: String,
pub req: String,
pub features: Vec<String>,
pub optional: bool,
pub default_features: bool,
pub target: Option<String>,
pub kind: DependencyKind,
pub registry: Option<String>,
pub package: Option<String>,
}
impl IndexCrateDependency {
#[must_use]
pub fn get_name(&self) -> &str {
self.package.as_deref().unwrap_or(&self.name)
}
#[must_use]
pub fn is_active_for(&self, active_targets: &[String], active_features: &[&str]) -> bool {
let is_in_targets = match self.target.as_ref() {
None => true,
Some(target_spec) => {
if let Some(rest) = target_spec.strip_prefix("cfg(") {
let _cfg_spec = &rest[..rest.len() - 1];
false
} else {
active_targets.contains(target_spec)
}
}
};
if !is_in_targets {
return false;
}
if !self.optional {
return true;
}
let name = self.get_name();
active_features.iter().any(|feature| {
feature.strip_prefix("dep:").is_some_and(|suffix| name == suffix)
|| feature.find('/').is_some_and(|i| &feature[..i] == name)
})
}
}
impl From<&CrateMetadataDependency> for IndexCrateDependency {
fn from(dep: &CrateMetadataDependency) -> Self {
Self {
name: dep.explicit_name_in_toml.as_ref().unwrap_or(&dep.name).clone(),
req: dep.version_req.clone(),
features: dep.features.clone(),
optional: dep.optional,
default_features: dep.default_features,
target: dep.target.clone(),
kind: dep.kind,
registry: dep.registry.clone(),
package: if dep.explicit_name_in_toml.is_some() {
Some(dep.name.clone())
} else {
None
},
}
}
}