#![warn(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
unsafe_code
)]
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{collections::BTreeMap, fmt, fs, io::Read, path::Path, str::FromStr};
mod error;
pub use crate::error::Error;
pub type BinSet = BTreeMap<String, String>;
pub type DepsSet = BTreeMap<String, String>;
pub type EnginesSet = BTreeMap<String, String>;
pub type ScriptsSet = BTreeMap<String, String>;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Bug {
pub email: Option<String>,
pub url: Option<String>,
}
impl Bug {
pub fn new() -> Self {
Self::default()
}
pub fn with(email: Option<String>, url: Option<String>) -> Self {
Self { email, url }
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Person {
pub name: String,
pub email: Option<String>,
pub url: Option<String>,
}
impl Person {
pub fn new() -> Self {
Self::default()
}
pub fn with_name(name: String) -> Self {
Self {
name,
..Default::default()
}
}
pub fn with(name: String, email: Option<String>, url: Option<String>) -> Self {
Self { name, email, url }
}
}
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.name, self.email.as_ref(), self.url.as_ref()) {
(name, Some(email), None) => write!(f, "{} <{}>", name, email),
(name, None, Some(url)) => write!(f, "{} ({})", name, url),
(name, None, None) => write!(f, "{}", name),
(name, Some(email), Some(url)) => write!(f, "{} <{}> ({})", name, email, url),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum PersonReference {
Short(String),
Full(Person),
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum ManReference {
Single(String),
Multiple(Vec<String>),
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Repository {
pub r#type: String,
pub url: String,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum RepositoryReference {
Short(String),
Full(Repository),
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Package {
pub name: String,
pub version: String,
pub description: Option<String>,
#[serde(default)]
pub keywords: Vec<String>,
pub homepage: Option<String>,
pub bugs: Option<Bug>,
pub license: Option<String>,
pub author: Option<PersonReference>,
#[serde(default)]
pub contributors: Vec<PersonReference>,
#[serde(default)]
pub files: Vec<String>,
pub main: Option<String>,
pub browser: Option<String>,
#[serde(default)]
pub bin: BinSet,
pub man: Option<ManReference>,
pub repository: Option<RepositoryReference>,
#[serde(default)]
pub scripts: ScriptsSet,
#[serde(default)]
pub dependencies: DepsSet,
#[serde(default)]
pub dev_dependencies: DepsSet,
#[serde(default)]
pub peer_dependencies: DepsSet,
#[serde(default)]
pub bundled_dependencies: DepsSet,
#[serde(default)]
pub optional_dependencies: DepsSet,
#[serde(default)]
pub engines: EnginesSet,
#[serde(default)]
pub private: bool,
#[serde(flatten)]
pub others: BTreeMap<String, Value>,
}
impl Package {
pub fn new() -> Self {
Self::default()
}
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
let content = fs::read(path.as_ref())?;
Self::from_slice(content.as_slice())
}
pub fn from_reader<R: Read>(r: R) -> Result<Self> {
Ok(serde_json::from_reader(r)?)
}
pub fn from_slice(v: &[u8]) -> Result<Self> {
Ok(serde_json::from_slice(v)?)
}
}
impl FromStr for Package {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(serde_json::from_str(s)?)
}
}
#[cfg(test)]
mod tests {
use super::Person;
#[test]
fn test_person_display() {
let person = Person {
name: "John Doe".to_string(),
email: Some("john@doe.dev".to_string()),
url: Some("https://john.doe.dev".to_string()),
};
let expected = "John Doe <john@doe.dev> (https://john.doe.dev)";
assert_eq!(expected, person.to_string());
}
}