npm_package_json/
lib.rs

1//! `serde` mappings for npm's `package.json` file format.
2//!
3//! This does only implement the fields defined [in the official npm documentation](https://docs.npmjs.com/files/package.json).
4//! It is common enough that packages define custom entries that are required by
5//! various tooling.
6
7#![warn(
8    missing_copy_implementations,
9    missing_debug_implementations,
10    missing_docs,
11    unsafe_code
12)]
13
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::{collections::BTreeMap, fmt, fs, io::Read, path::Path, str::FromStr};
17
18mod error;
19
20pub use crate::error::Error;
21
22/// An ordered map for `bin` entries.
23pub type BinSet = BTreeMap<String, String>;
24/// An ordered map for `dependencies` entries.
25pub type DepsSet = BTreeMap<String, String>;
26/// An ordered map for `engines` entries.
27pub type EnginesSet = BTreeMap<String, String>;
28/// An ordered map for `scripts` entries.
29pub type ScriptsSet = BTreeMap<String, String>;
30
31/// The result type of this crate.
32pub type Result<T> = std::result::Result<T, Error>;
33
34/// A bug contacting form.
35#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
36pub struct Bug {
37    /// The email to use for contact.
38    pub email: Option<String>,
39    /// The url to use to submit bugs.
40    pub url: Option<String>,
41}
42
43impl Bug {
44    /// Creates a new default bug.
45    pub fn new() -> Self {
46        Self::default()
47    }
48
49    /// Creates a new bug with the given values.
50    pub fn with(email: Option<String>, url: Option<String>) -> Self {
51        Self { email, url }
52    }
53}
54
55/// A person.
56#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
57pub struct Person {
58    /// The name of a person.
59    pub name: String,
60    /// The email of a person.
61    pub email: Option<String>,
62    /// The homepage of the person.
63    pub url: Option<String>,
64}
65
66impl Person {
67    /// Creates a default person.
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    /// Creates a person with a given name.
73    pub fn with_name(name: String) -> Self {
74        Self {
75            name,
76            ..Default::default()
77        }
78    }
79
80    /// Creates a person with the given values.
81    pub fn with(name: String, email: Option<String>, url: Option<String>) -> Self {
82        Self { name, email, url }
83    }
84}
85
86impl fmt::Display for Person {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        match (&self.name, self.email.as_ref(), self.url.as_ref()) {
89            (name, Some(email), None) => write!(f, "{} <{}>", name, email),
90            (name, None, Some(url)) => write!(f, "{} ({})", name, url),
91            (name, None, None) => write!(f, "{}", name),
92            (name, Some(email), Some(url)) => write!(f, "{} <{}> ({})", name, email, url),
93        }
94    }
95}
96
97/// A reference to a person.
98#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
99#[serde(untagged)]
100pub enum PersonReference {
101    /// A short reference.
102    ///
103    /// Short references have a fixed format of `John Doe <john@doe.dev> (https://john.doe.dev)`.
104    Short(String),
105    /// A full reference.
106    ///
107    /// This type of reference defines the parts using a struct instead of a
108    /// shorthand string format.
109    Full(Person),
110}
111
112/// A reference to a man page.
113#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
114#[serde(untagged)]
115pub enum ManReference {
116    /// A single man page reference. Points to one single file.
117    Single(String),
118    /// Multiple man pages, can contain anything from zero to n.
119    Multiple(Vec<String>),
120}
121
122/// A repository.
123#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
124pub struct Repository {
125    /// The version control system that the repository uses.
126    pub r#type: String,
127    /// The url to the repository.
128    pub url: String,
129    /// The directory that the repository is in. Often used for monorepos.
130    pub directory: Option<String>,
131}
132
133/// A repository reference.
134#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
135#[serde(untagged)]
136pub enum RepositoryReference {
137    /// A short reference to the repository. Has to have the syntax that `npm install` allows as well. For more information see [here](https://docs.npmjs.com/files/package.json#repository).
138    Short(String),
139    /// A full reference.
140    Full(Repository),
141}
142
143/// The top-level `package.json` structure.
144#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
145#[serde(rename_all = "camelCase")]
146pub struct Package {
147    /// The package name.
148    pub name: String,
149    /// The package version.
150    pub version: String,
151    /// The optional package description.
152    pub description: Option<String>,
153    /// The optional list of keywords.
154    #[serde(default)]
155    pub keywords: Vec<String>,
156    /// The optional package homepage.
157    pub homepage: Option<String>,
158    /// The optional bug contact form.
159    pub bugs: Option<Bug>,
160    /// The optional package license.
161    pub license: Option<String>,
162    /// The optional author.
163    pub author: Option<PersonReference>,
164    /// The optional list of contributors.
165    #[serde(default)]
166    pub contributors: Vec<PersonReference>,
167    /// The optional list of files to include. Each entry defines a regex
168    /// pattern.
169    #[serde(default)]
170    pub files: Vec<String>,
171    /// The optional package main entry file.
172    pub main: Option<String>,
173    /// The optional package browser entry file.
174    ///
175    /// This is usually defined in libraries that are meant to be consumed by
176    /// browsers. These Thoes can refer to objects that are not available inside
177    /// a `nodejs` environment (like `window`).
178    pub browser: Option<String>,
179    /// The optional set of binary definitions.
180    #[serde(default)]
181    pub bin: BinSet,
182    /// The optional list of man page references.
183    pub man: Option<ManReference>,
184    /// The optional repository reference.
185    //#[serde(flatten)]
186    pub repository: Option<RepositoryReference>,
187    /// The optional list of script entries.
188    #[serde(default)]
189    pub scripts: ScriptsSet,
190    /// The optional list of dependencies.
191    #[serde(default)]
192    pub dependencies: DepsSet,
193    /// The optional list of development dependencies.
194    #[serde(default)]
195    pub dev_dependencies: DepsSet,
196    /// The optional list of peer dependencies.
197    #[serde(default)]
198    pub peer_dependencies: DepsSet,
199    /// The optional list of bundled dependencies.
200    #[serde(default)]
201    pub bundled_dependencies: DepsSet,
202    /// The optional list of optional dependencies.
203    #[serde(default)]
204    pub optional_dependencies: DepsSet,
205    /// The optional list of engine entries.
206    #[serde(default)]
207    pub engines: EnginesSet,
208    /// The package privacy.
209    #[serde(default)]
210    pub private: bool,
211    /// The OS' that the package can run on.
212    #[serde(default)]
213    pub os: Vec<String>,
214    /// The CPU architectures that the package can run on.
215    #[serde(default)]
216    pub cpu: Vec<String>,
217    /// The optional config object.
218    pub config: Option<Value>,
219    /// Other custom fields that have been defined inside the `package.json`
220    /// file.
221    #[serde(flatten)]
222    pub others: BTreeMap<String, Value>,
223}
224
225impl Package {
226    /// Creates a new default package.
227    pub fn new() -> Self {
228        Self::default()
229    }
230
231    /// Deserializes a `Package` from a file path.
232    pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
233        let content = fs::read(path.as_ref())?;
234        Self::from_slice(content.as_slice())
235    }
236
237    /// Deserializes a `Package` from an IO stream.
238    pub fn from_reader<R: Read>(r: R) -> Result<Self> {
239        Ok(serde_json::from_reader(r)?)
240    }
241
242    /// Deserializes a `Package` from bytes.
243    pub fn from_slice(v: &[u8]) -> Result<Self> {
244        Ok(serde_json::from_slice(v)?)
245    }
246}
247
248impl FromStr for Package {
249    type Err = Error;
250
251    /// Deserializes a `Package` from a string.
252    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
253        Ok(serde_json::from_str(s)?)
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::Person;
260
261    #[test]
262    fn test_person_display() {
263        let person = Person {
264            name: "John Doe".to_string(),
265            email: Some("john@doe.dev".to_string()),
266            url: Some("https://john.doe.dev".to_string()),
267        };
268        let expected = "John Doe <john@doe.dev> (https://john.doe.dev)";
269        assert_eq!(expected, person.to_string());
270    }
271}