package_json/schema/
mod.rs

1mod default;
2mod ignore;
3
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// A `package.json` is a JSON file that exists in the root of a JavaScript/Node.js project. It holds metadata relevant to the project and it's used for managing the project's dependencies, scripts, version and a whole lot more.
8///
9/// `package.json` schema from [official npm documentation](https://docs.npmjs.com/cli/v8/configuring-npm/package-json), see also [json-schemas repo](https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/package.json) and [json-schemas online](https://json.schemastore.org/package)
10#[derive(Serialize, Deserialize, Debug, Default)]
11#[serde(rename_all = "camelCase")]
12pub struct PackageJson {
13  /// The [name](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#name) for the npm package
14  pub name: String,
15  /// The [version](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#version) for the npm package
16  pub version: String,
17  /// The [description](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#description-1) helps people discover your package, as it's listed in `npm search`.
18  #[serde(skip_serializing_if = "Option::is_none")]
19  pub description: Option<String>,
20  /// The [keywords](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#keywords) helps people discover your package as it's listed in `npm search`.
21  #[serde(skip_serializing_if = "Option::is_none")]
22  pub keywords: Option<Vec<String>>,
23  /// The url to the project [homepage](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#homepage).
24  #[serde(skip_serializing_if = "Option::is_none")]
25  pub homepage: Option<String>,
26  /// The url to your project's issue tracker and / or the email address to which issues should be reported.
27  /// These are helpful for people who encounter issues with your package.
28  #[serde(skip_serializing_if = "Option::is_none")]
29  pub bugs: Option<PackageBugs>,
30  /// The [license](https://spdx.org/licenses/) of the package.
31  #[serde(skip_serializing_if = "Option::is_none")]
32  pub license: Option<String>,
33  /// The [author](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#people-fields-author-contributors) of the package.
34  #[serde(skip_serializing_if = "Option::is_none")]
35  pub author: Option<PackagePeople>,
36  /// A list of [people](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#people-fields-author-contributors) who contributed to this package.
37  #[serde(skip_serializing_if = "Option::is_none")]
38  pub contributors: Option<Vec<PackagePeople>>,
39  /// A list of [people](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#people-fields-author-contributors) who maintains this package.
40  #[serde(skip_serializing_if = "Option::is_none")]
41  pub maintainers: Option<Vec<PackagePeople>>,
42  /// Used to inform about ways to help [fund](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#funding) development of the package.
43  #[serde(skip_serializing_if = "Option::is_none")]
44  pub funding: Option<Vec<PackageFunding>>,
45  /// The [files](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#files) field is an array of files to include in your project. If you name a folder in the array, then it will also include the files inside that folder.
46  #[serde(skip_serializing_if = "Option::is_none")]
47  pub files: Option<Vec<String>>,
48  /// The [main](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#main) field is a module ID that is the primary entry point to your program. That is, if your package is named `foo`, and a user installs it, and then does `require("foo")`, then your main module's exports object will be returned.
49  ///
50  /// This should be a module relative to the root of your package folder.
51  ///
52  /// For most modules, it makes the most sense to have a main script and often not much else.
53  ///
54  /// If main is not set it defaults to `index.js` in the package's root folder.
55  #[serde(default = "default::main")]
56  pub main: String,
57  /// If your module is meant to be used client-side the [browser](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#browser) field should be used instead of the [main][PackageJson::main] field. This is helpful to hint users that it might rely on primitives that aren't available in Node.js modules. (e.g. window)
58  #[serde(skip_serializing_if = "Option::is_none")]
59  pub browser: Option<String>,
60  /// A lot of packages have one or more executable files that they'd like to install into the PATH. npm makes this pretty easy (in fact, it uses this feature to install the "npm" executable.)
61  ///
62  /// To use this, supply a [bin](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bin) field in your package.json which is a map of command name to local file name. When this package is installed globally, that file will be linked where global bins go so it is available to run by name. When this package is installed as a dependency in another package, the file will be linked where it will be available to that package either directly by npm exec or by name in other scripts when invoking them via npm run-script.
63  #[serde(skip_serializing_if = "Option::is_none")]
64  pub bin: Option<PackageBin>,
65  /// Specify either a single file or an array of filenames to put in place for the man program to find.
66  ///
67  /// If only a single file is provided, then it's installed such that it is the result from `man <pkgname>`, regardless of its actual filename.
68  #[serde(skip_serializing_if = "Option::is_none")]
69  pub man: Option<PackageMan>,
70  /// [The CommonJS Packages spec](http://wiki.commonjs.org/wiki/Packages/1.0) details a few ways that you can indicate the structure of your package using a directories object. If you look at npm's package.json, you'll see that it has directories for doc, lib, and man.
71  #[serde(skip_serializing_if = "Option::is_none")]
72  pub directories: Option<PackageDirectories>,
73  /// Specify the place where your code lives. This is helpful for people who want to contribute. If the git repo is on GitHub, then the npm docs command will be able to find you.
74  #[serde(skip_serializing_if = "Option::is_none")]
75  pub repository: Option<PackageRepository>,
76  /// A dictionary containing script commands that are run at various times in the lifecycle of your package. The key is the lifecycle event, and the value is the command to run at that point.
77  #[serde(default = "default::scripts")]
78  #[serde(skip_serializing_if = "ignore::ignore_scripts")]
79  pub scripts: HashMap<String, String>,
80  /// A [config](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#config) object can be used to set configuration parameters used in package scripts that persist across upgrades.
81  #[serde(skip_serializing_if = "Option::is_none")]
82  pub config: Option<HashMap<String, serde_json::Value>>,
83  /// [Dependencies](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#dependencies) are specified in a simple object that maps a package name to a version range. The version range is a string which has one or more space-separated descriptors. Dependencies can also be identified with a tarball or git URL.
84  ///
85  /// Please do not put test harnesses or transpilers or other "development" time tools in your dependencies object. See [devDependencies](PackageJson::dev_dependencies).
86  #[serde(skip_serializing_if = "Option::is_none")]
87  pub dependencies: Option<PackageDependencies>,
88  /// If someone is planning on downloading and using your module in their program, then they probably don't want or need to download and build the external test or documentation framework that you use.
89  ///
90  /// In this case, it's best to map these additional items in a [devDependencies](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#devdependencies) object.
91  #[serde(skip_serializing_if = "Option::is_none")]
92  pub dev_dependencies: Option<PackageDependencies>,
93  /// In some cases, you want to express the compatibility of your package with a host tool or library, while not necessarily doing a require of this host. This is usually referred to as a plugin. Notably, your module may be exposing a specific interface, expected and specified by the host documentation.
94  #[serde(skip_serializing_if = "Option::is_none")]
95  pub peer_dependencies: Option<PackageDependencies>,
96  /// When a user installs your package, npm will emit warnings if packages specified in peerDependencies are not already installed. The [peerDependenciesMeta](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#peerdependenciesmeta) field serves to provide npm more information on how your [peer dependencies][PackageJson::peer_dependencies] are to be used. Specifically, it allows peer dependencies to be marked as optional.
97  #[serde(skip_serializing_if = "Option::is_none")]
98  pub peer_dependencies_meta: Option<HashMap<String, HashMap<String, bool>>>,
99  /// An array of package names that will be bundled when publishing the package.
100  #[serde(skip_serializing_if = "Option::is_none")]
101  pub bundled_dependencies: Option<Vec<String>>,
102  /// If a dependency can be used, but you would like npm to proceed if it cannot be found or fails to install, then you may put it in the [optionalDependencies](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#optionaldependencies) object.
103  #[serde(skip_serializing_if = "Option::is_none")]
104  pub optional_dependencies: Option<PackageDependencies>,
105  /// If you need to make specific changes to dependencies of your dependencies, for example replacing the version of a dependency with a known security issue, replacing an existing dependency with a fork, or making sure that the same version of a package is used everywhere, then you may add an override.
106  ///
107  /// [Overrides](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides) provide a way to replace a package in your dependency tree with another version, or another package entirely. These changes can be scoped as specific or as vague as desired.
108  #[serde(skip_serializing_if = "Option::is_none")]
109  pub overrides: Option<HashMap<String, String>>,
110  /// Specify which [engines](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#engines) your module will run on.
111  #[serde(skip_serializing_if = "Option::is_none")]
112  pub engines: Option<HashMap<String, String>>,
113  /// Specify which [operating systems](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#os) your module will run on.
114  #[serde(skip_serializing_if = "Option::is_none")]
115  pub os: Option<Vec<String>>,
116  /// Specify which [cpu](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#cpu) your module will run on.
117  #[serde(skip_serializing_if = "Option::is_none")]
118  pub cpu: Option<Vec<String>>,
119  /// If set to true, then npm will refuse to publish it.
120  #[serde(default)]
121  pub private: bool,
122  /// This is a set of [config](https://docs.npmjs.com/cli/v8/using-npm/config) values that will be used at publish-time. It's
123  /// especially handy if you want to set the tag, registry or access, so that
124  /// you can ensure that a given package is not tagged with "latest", published
125  /// to the global public registry or that a scoped module is private by default.
126  #[serde(skip_serializing_if = "Option::is_none")]
127  pub publish_config: Option<HashMap<String, String>>,
128  /// The optional [workspace](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#workspaces)s
129  /// field is an array of file patterns that describes locations within the local
130  /// file system that the install client should look up to find each workspace
131  /// that needs to be symlinked to the top level node_modules folder.
132  #[serde(skip_serializing_if = "Option::is_none")]
133  pub workspaces: Option<Vec<String>>,
134  /// When set to "module", the type field allows a package to specify all .js files within are ES modules. If the "type" field is omitted or set to "commonjs", all .js files are treated as CommonJS.
135  #[serde(default = "default::r#type")]
136  pub r#type: String,
137  /// Set the [types](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#including-declarations-in-your-npm-package) property to point to your bundled declaration file. This is useful for packages that have a large number of types, but only a few of which are used.
138  #[serde(skip_serializing_if = "Option::is_none")]
139  pub types: Option<String>,
140  /// Note that the [typings](https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#including-declarations-in-your-npm-package) field is synonymous with "types", and could be used as well.
141  #[serde(skip_serializing_if = "Option::is_none")]
142  pub typings: Option<String>,
143
144  /// Any unknown fields should be placed in `unknown` field.
145  #[serde(flatten)]
146  pub unknowns: HashMap<String, serde_json::Value>,
147}
148
149/// see [PackageJson::bugs](PackageJson::bugs)
150#[derive(Serialize, Deserialize, Debug)]
151#[serde(untagged)]
152pub enum PackageBugs {
153  Url(String),
154  Record(PackageBugsRecord),
155}
156
157/// see [PackageJson::bugs](PackageJson::bugs)
158#[derive(Serialize, Deserialize, Debug, Default)]
159pub struct PackageBugsRecord {
160  pub url: Option<String>,
161  pub email: Option<String>,
162}
163
164#[derive(Serialize, Deserialize, Debug)]
165#[serde(untagged)]
166pub enum PackagePeople {
167  Literal(String),
168  Record(PackagePeopleRecord),
169}
170
171#[derive(Serialize, Deserialize, Debug, Default)]
172pub struct PackagePeopleRecord {
173  pub name: String,
174  pub email: Option<String>,
175  pub url: Option<String>,
176}
177
178/// see [PackageJson::funding](PackageJson::funding)
179#[derive(Serialize, Deserialize, Debug)]
180#[serde(untagged)]
181pub enum PackageFunding {
182  Url(String),
183  Record(PackageFundingRecord),
184  Slice(Vec<PackageFundingRecord>),
185}
186
187/// see [PackageJson::funding](PackageJson::funding)
188#[derive(Serialize, Deserialize, Debug, Default)]
189pub struct PackageFundingRecord {
190  pub r#type: String,
191  pub url: String,
192}
193
194/// see [PackageJson::bin](PackageJson::bin)
195#[derive(Serialize, Deserialize, Debug)]
196#[serde(untagged)]
197pub enum PackageBin {
198  Literal(String),
199  Record(HashMap<String, String>),
200}
201
202/// see [PackageJson::man](PackageJson::man)
203#[derive(Serialize, Deserialize, Debug)]
204#[serde(untagged)]
205pub enum PackageMan {
206  Literal(String),
207  Slice(Vec<String>),
208}
209
210/// see [PackageJson::directories](PackageJson::directories)
211#[derive(Serialize, Deserialize, Debug, Default)]
212pub struct PackageDirectories {
213  pub bin: Option<String>,
214  pub man: Option<String>,
215}
216
217/// see [PackageJson::repository](PackageJson::repository)
218#[derive(Serialize, Deserialize, Debug)]
219#[serde(untagged)]
220pub enum PackageRepository {
221  Url(String),
222  Record(PackageRepositoryRecord),
223}
224
225#[derive(Serialize, Deserialize, Debug, Default)]
226pub struct PackageRepositoryRecord {
227  pub r#type: String,
228  pub url: String,
229  pub directory: Option<String>,
230}
231
232pub type PackageDependencies = HashMap<String, String>;
233
234#[test]
235fn test_spec_fields() {
236  use self::default;
237  let package_json_raw = r#"
238    {
239      "name": "test",
240      "version": "1.0.0",
241      "description": "test",
242      "devDependencies": {
243        "typescript": "*"
244      }
245    }
246  "#;
247
248  let json = serde_json::from_str::<PackageJson>(package_json_raw).unwrap();
249  // test actual values
250  assert_eq!(json.name, "test");
251  assert_eq!(json.version, "1.0.0");
252  assert_eq!(json.description, Some("test".to_owned()));
253  assert_eq!(json.license, None);
254  assert_eq!(json.dependencies, None);
255  assert_eq!(
256    json.dev_dependencies,
257    Some(HashMap::from([("typescript".to_owned(), "*".to_owned())]))
258  );
259  assert_eq!(json.bundled_dependencies, None);
260
261  // test default values
262  assert!(!json.private, "json.private should be false");
263  assert_eq!(json.scripts, default::scripts());
264  assert_eq!(json.main, default::main());
265  assert_eq!(json.r#type, default::r#type());
266}
267
268#[test]
269fn test_unknown_fields() {
270  let json = r#"
271    {
272      "name": "test",
273      "version": "1.0.0",
274      "description": "test",
275      "foo": "bar",
276      "baz": "qux"
277    }"#;
278
279  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
280  assert_eq!(package_json.unknowns.len(), 2);
281  assert!(package_json.unknowns.contains_key("baz"));
282  assert!(package_json.unknowns.contains_key("baz"));
283  assert_eq!(package_json.unknowns.get("foo").unwrap(), &"bar".to_owned());
284  assert_eq!(package_json.unknowns.get("baz").unwrap(), &"qux".to_owned());
285}
286
287#[test]
288fn test_repository_string() {
289  let json = r#"
290  {
291    "name": "test",
292    "version": "1.0.0",
293    "description": "test",
294    "repository": "gitlab:user/repo"
295  }"#;
296  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
297  let expected = String::from("gitlab:user/repo");
298  match package_json.repository.unwrap() {
299    PackageRepository::Url(url) => {
300      assert_eq!(url, expected, "expected {} got {}", expected, url);
301    }
302    PackageRepository::Record(_) => {
303      panic!("expected a repository url, got a struct")
304    }
305  }
306}
307
308#[test]
309fn test_repository_record() {
310  let json = r#"
311  {
312    "name": "test",
313    "version": "1.0.0",
314    "description": "test",
315    "repository": {
316      "type": "git",
317      "url": "git+https://github.com/npm/cli.git"
318      }
319  }"#;
320  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
321  let expected = String::from("git+https://github.com/npm/cli.git");
322  match package_json.repository.unwrap() {
323    PackageRepository::Record(record) => {
324      assert_eq!(
325        record.url, expected,
326        "expected repository url {} got {}",
327        expected, record.url
328      );
329    }
330    PackageRepository::Url(_) => {
331      panic!("expected a repository structl, got a url")
332    }
333  }
334}
335
336#[test]
337fn test_repository_record_with_directory() {
338  let json = r#"
339  {
340    "name": "test",
341    "version": "1.0.0",
342    "description": "test",
343    "repository": {
344      "type": "git",
345      "url": "git+https://github.com/npm/cli.git",
346      "directory": "workspaces/libnpmpublish"
347    }
348  }"#;
349  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
350  let expected = String::from("workspaces/libnpmpublish");
351  match package_json.repository.unwrap() {
352    PackageRepository::Record(record) => {
353      let dir = record.directory.unwrap();
354      assert_eq!(
355        dir, expected,
356        "expected repository directory {} got {}",
357        expected, dir
358      );
359    }
360    PackageRepository::Url(_) => {
361      panic!("expected a repository struct, got a url")
362    }
363  }
364}
365
366#[test]
367fn test_author_string_serialization() {
368  let json = r#"
369 {
370	"name": "package-name",
371	"private": true,
372	"version": "1.0.0",
373	"description": "Something for everyone",
374	"author": "A string value",
375	"license": "Apache-2.0",
376	"workspaces": [
377		"packages/*"
378	]
379}"#;
380  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
381  let expected = String::from("A string value");
382  match package_json.author.unwrap() {
383    PackagePeople::Record(_) => {
384      panic!("expected a auhor string, got a struct");
385    }
386    PackagePeople::Literal(literal) => {
387      assert_eq!(literal, expected, "expected {} got {}", expected, literal);
388    }
389  }
390}
391
392#[test]
393fn test_author_object_serialization() {
394  let json = r#"
395 {
396	"name": "package-name",
397	"private": true,
398	"version": "1.0.0",
399	"description": "Something for everyone",
400	"author": {
401    "name": "Barney Rubble",
402    "email": "b@rubble.com",
403    "url": "http://barnyrubble.tumblr.com/"
404  },
405	"license": "Apache-2.0",
406	"workspaces": [
407		"packages/*"
408	]
409}"#;
410  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
411  let expected = String::from("http://barnyrubble.tumblr.com/");
412  match package_json.author.unwrap() {
413    PackagePeople::Record(record) => {
414      let author_url = record.url.unwrap();
415      assert_eq!(
416        author_url, expected,
417        "expected author url: {} got: {}",
418        expected, author_url
419      );
420    }
421    PackagePeople::Literal(_) => {
422      panic!("expected a auhor struct, got a string")
423    }
424  }
425}
426
427#[test]
428fn test_config_with_bool_serialization() {
429  let json = r#"
430   {
431    "name": "package-name",
432    "private": true,
433    "version": "1.0.0",
434    "description": "Something for everyone",
435    "author": {
436      "name": "Barney Rubble",
437      "email": "b@rubble.com",
438      "url": "http://barnyrubble.tumblr.com/"
439    },
440    "config": {
441      "foo": true
442    },
443    "license": "Apache-2.0",
444    "workspaces": [
445      "packages/*"
446    ]
447  }"#;
448  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
449  let expected: bool = true;
450  assert_eq!(package_json.config.unwrap().get("foo").unwrap(), &expected);
451}
452
453#[test]
454fn test_config_with_nested_serialization() {
455  let json = r#"
456   {
457    "name": "package-name",
458    "private": true,
459    "version": "1.0.0",
460    "description": "Something for everyone",
461    "author": {
462      "name": "Barney Rubble",
463      "email": "b@rubble.com",
464      "url": "http://barnyrubble.tumblr.com/"
465    },
466    "config": {
467      "commitizen": {
468        "path": "cz-conventional-changelog"
469      }
470    },
471    "license": "Apache-2.0",
472    "workspaces": [
473      "packages/*"
474    ]
475  }"#;
476  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
477  let expected: String = String::from("cz-conventional-changelog");
478  assert_eq!(
479    package_json
480      .config
481      .unwrap()
482      .get("commitizen")
483      .unwrap()
484      .get("path")
485      .unwrap(),
486    &expected
487  );
488}
489
490#[test]
491fn test_bugs_with_nested_serialization() {
492  let json = r#"
493   {
494    "name": "package-name",
495    "private": true,
496    "version": "1.0.0",
497    "description": "Something for everyone",
498    "author": {
499      "name": "Barney Rubble",
500      "email": "b@rubble.com",
501      "url": "http://barnyrubble.tumblr.com/"
502    },
503    "bugs": {
504      "url": "https://github.com/jquery/esprima/issues"
505    }
506  }"#;
507  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
508  let expected: String = String::from("https://github.com/jquery/esprima/issues");
509  match package_json.bugs.unwrap() {
510    PackageBugs::Url(_url) => {
511      panic!("expected a repository url, got a struct")
512    }
513    PackageBugs::Record(record) => {
514      let url = record.url.unwrap();
515      assert_eq!(url, expected, "expected {} got {}", expected, url);
516    }
517  }
518}
519
520#[test]
521fn test_scripts_serialization() {
522  let json = r#"
523    {
524        "name": "my-project",
525        "version": "1.0.0",
526        "scripts": {
527            "start": "node index.js",
528            "test": "jest",
529            "build": "webpack --mode production",
530            "lint": "eslint ."
531        }
532    }"#;
533  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
534  assert_eq!(package_json.scripts.get("start").unwrap(), "node index.js");
535  assert_eq!(package_json.scripts.get("test").unwrap(), "jest");
536  assert_eq!(
537    package_json.scripts.get("build").unwrap(),
538    "webpack --mode production"
539  );
540  assert_eq!(package_json.scripts.get("lint").unwrap(), "eslint .");
541}
542
543#[test]
544fn test_dependencies_and_dev_dependencies() {
545  let json = r#"
546    {
547        "name": "my-library",
548        "version": "2.1.0",
549        "dependencies": {
550            "lodash": "^4.17.21",
551            "axios": "^0.21.1"
552        },
553        "devDependencies": {
554            "jest": "^27.0.6",
555            "typescript": "^4.3.5"
556        }
557    }"#;
558  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
559  assert_eq!(
560    package_json
561      .dependencies
562      .as_ref()
563      .unwrap()
564      .get("lodash")
565      .unwrap(),
566    "^4.17.21"
567  );
568  assert_eq!(
569    package_json
570      .dependencies
571      .as_ref()
572      .unwrap()
573      .get("axios")
574      .unwrap(),
575    "^0.21.1"
576  );
577  assert_eq!(
578    package_json
579      .dev_dependencies
580      .as_ref()
581      .unwrap()
582      .get("jest")
583      .unwrap(),
584    "^27.0.6"
585  );
586  assert_eq!(
587    package_json
588      .dev_dependencies
589      .as_ref()
590      .unwrap()
591      .get("typescript")
592      .unwrap(),
593    "^4.3.5"
594  );
595}
596
597#[test]
598fn test_engines_and_os() {
599  let json = r#"
600    {
601        "name": "node-specific-package",
602        "version": "1.2.3",
603        "engines": {
604            "node": ">=14.0.0",
605            "npm": ">=6.0.0"
606        },
607        "os": ["darwin", "linux"]
608    }"#;
609  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
610  assert_eq!(
611    package_json.engines.as_ref().unwrap().get("node").unwrap(),
612    ">=14.0.0"
613  );
614  assert_eq!(
615    package_json.engines.as_ref().unwrap().get("npm").unwrap(),
616    ">=6.0.0"
617  );
618  assert_eq!(
619    package_json.os.as_ref().unwrap(),
620    &vec!["darwin".to_string(), "linux".to_string()]
621  );
622}
623
624#[test]
625fn test_bin_and_man() {
626  let json = r#"
627    {
628        "name": "cli-tool",
629        "version": "3.0.1",
630        "bin": {
631            "my-cli": "./bin/cli.js"
632        },
633        "man": [
634            "./man/doc.1",
635            "./man/doc.2"
636        ]
637    }"#;
638  let package_json = serde_json::from_str::<PackageJson>(json).unwrap();
639  match &package_json.bin {
640    Some(PackageBin::Record(bin_map)) => {
641      assert_eq!(bin_map.get("my-cli").unwrap(), "./bin/cli.js");
642    }
643    _ => panic!("Expected bin to be a Record"),
644  }
645  match &package_json.man {
646    Some(PackageMan::Slice(man_vec)) => {
647      assert_eq!(
648        man_vec,
649        &vec!["./man/doc.1".to_string(), "./man/doc.2".to_string()]
650      );
651    }
652    _ => panic!("Expected man to be a Slice"),
653  }
654}