#![allow(clippy::use_self)]
use std::fmt::Display;
use cfg_if::cfg_if;
use indexmap::IndexMap;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use typed_builder::TypedBuilder;
cfg_if! {
if #[cfg(feature = "validate")] {
use validator::Validate;
use validator::ValidationError;
use validator::ValidationErrors;
use crate::utils::validate_version;
use crate::utils::validate_email_or_url;
use crate::utils::validate_exports_path;
use crate::utils::PACKAGE_MANAGER_REGEX;
use crate::utils::PACKAGE_NAME_REGEX;
}
}
pub type AdditionalFields = IndexMap<String, Value>;
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct PackageJson {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(
feature = "validate",
validate(length(min = 1, max = 214), regex = "PACKAGE_NAME_REGEX")
)]
#[builder(default, setter(into, strip_option))]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validate", validate(custom = "validate_version"))]
#[builder(default, setter(into, strip_option))]
pub version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub keywords: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validate", validate(url))]
#[builder(default, setter(into, strip_option))]
pub homepage: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validate", validate)]
#[builder(default, setter(into, strip_option))]
pub bugs: Option<Bug>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub license: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validate", validate)]
#[builder(default, setter(into, strip_option))]
pub author: Option<Person>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validate", validate)]
#[builder(default, setter(into, strip_option))]
pub contributors: Option<Vec<Person>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validate", validate)]
#[builder(default, setter(into, strip_option))]
pub maintainers: Option<Vec<Person>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub files: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub main: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validate", validate)]
#[builder(default, setter(into, strip_option))]
pub exports: Option<Exports>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub bin: Option<Binary>,
#[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub type_: Option<Type>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub types: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub typings: Option<String>,
#[serde(
default,
rename = "typesVersions",
skip_serializing_if = "Option::is_none"
)]
#[builder(default, setter(into, strip_option))]
pub types_versions: Option<IndexMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub man: Option<Man>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub directories: Option<Directories>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub repository: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub scripts: Option<IndexMap<String, Option<String>>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub config: Option<IndexMap<String, Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub dependencies: Option<IndexMap<String, String>>,
#[serde(
default,
rename = "devDependencies",
skip_serializing_if = "Option::is_none"
)]
#[builder(default, setter(into, strip_option))]
pub dev_dependencies: Option<IndexMap<String, String>>,
#[serde(
default,
rename = "optionalDependencies",
skip_serializing_if = "Option::is_none"
)]
#[builder(default, setter(into, strip_option))]
pub optional_dependencies: Option<IndexMap<String, String>>,
#[serde(
default,
rename = "peerDependencies",
skip_serializing_if = "Option::is_none"
)]
#[builder(default, setter(into, strip_option))]
pub peer_dependencies: Option<IndexMap<String, String>>,
#[serde(
default,
rename = "bundledDependencies",
skip_serializing_if = "Option::is_none"
)]
#[builder(default, setter(into, strip_option))]
pub bundled_dependencies: Option<BundledDependencies>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub resolutions: Option<IndexMap<String, String>>,
#[serde(
default,
rename = "packageManager",
skip_serializing_if = "Option::is_none"
)]
#[builder(default, setter(into, strip_option))]
#[cfg_attr(feature = "validate", validate(regex = "PACKAGE_MANAGER_REGEX"))]
pub package_manager: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub engines: Option<IndexMap<String, String>>,
#[serde(
default,
rename = "engineStrict",
skip_serializing_if = "Option::is_none"
)]
#[builder(default, setter(into, strip_option))]
pub engine_strict: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub os: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub cpu: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub private: Option<Private>,
#[serde(
default,
rename = "publishConfig",
skip_serializing_if = "Option::is_none"
)]
#[builder(default, setter(into, strip_option))]
pub publish_config: Option<PublishConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub dist: Option<Dist>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub readme: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub module: Option<EsNext>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub browser: Option<EsNext>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub workspaces: Option<Workspaces>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub other: Option<AdditionalFields>,
}
impl TryFrom<&str> for PackageJson {
type Error = crate::error::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let package_json: Self = serde_json::from_str(value).map_err(crate::Error::ParsePackageJson)?;
Ok(package_json)
}
}
impl TryFrom<String> for PackageJson {
type Error = crate::error::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
let package_json: Self =
serde_json::from_str(value.as_str()).map_err(crate::Error::ParsePackageJson)?;
Ok(package_json)
}
}
impl PackageJson {
pub fn try_to_string(&self) -> Result<String, crate::error::Error> {
let content = serde_json::to_string(self).map_err(crate::Error::SerializePackageJson)?;
Ok(content)
}
}
impl TryFrom<PackageJson> for String {
type Error = crate::error::Error;
fn try_from(value: PackageJson) -> Result<Self, Self::Error> {
value.try_to_string()
}
}
impl Display for PackageJson {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.try_to_string().unwrap())
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Repository {
Path(String),
Object {
#[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
type_: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
directory: Option<String>,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Workspaces {
List(Vec<String>),
Object {
#[serde(default, skip_serializing_if = "Option::is_none")]
packages: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
nohoist: Option<Vec<String>>,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum PublishConfigAccess {
Public,
Restricted,
}
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct PublishConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub access: Option<PublishConfigAccess>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub tag: Option<String>,
#[cfg_attr(feature = "validate", validate(url))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub registry: Option<String>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub other: Option<IndexMap<String, String>>,
}
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct Dist {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub shasum: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub tarball: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Man {
Path(String),
Object(Vec<String>),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum EsNext {
Path(String),
Object(IndexMap<String, String>),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum BundledDependencies {
Bool(bool),
List(Vec<String>),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Private {
#[serde(rename = "true")]
True,
#[serde(rename = "false")]
False,
Bool(bool),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Binary {
Path(String),
Object(IndexMap<String, String>),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Type {
CommonJS,
Module,
}
impl Default for Type {
fn default() -> Self {
Self::CommonJS
}
}
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct PersonObject {
#[cfg_attr(feature = "validate", validate(length(min = 1)))]
#[builder(setter(into))]
pub name: String,
#[cfg_attr(feature = "validate", validate(url))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub url: Option<String>,
#[cfg_attr(feature = "validate", validate(email))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub email: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Person {
String(String),
Object(PersonObject),
}
cfg_if! {
if #[cfg(feature = "validate")] {
impl Validate for Person {
#[allow(unused_mut)]
fn validate(&self) -> Result<(), ValidationErrors> {
let mut errors = ValidationErrors::new();
let mut result = if errors.is_empty() {
Ok(())
} else {
Err(errors)
};
match self {
Person::Object(person) => ValidationErrors::merge(result, "Person", person.validate()),
Person::String(_) => result,
}
}
}
}
}
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct BugObject {
#[cfg_attr(feature = "validate", validate(url))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub url: Option<String>,
#[cfg_attr(feature = "validate", validate(email))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub email: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Bug {
EmailOrUrl(String),
Object(BugObject),
}
cfg_if! {
if #[cfg(feature = "validate")] {
impl Validate for Bug {
#[allow(unused_mut)]
fn validate(&self) -> Result<(), ValidationErrors> {
let mut errors = ValidationErrors::new();
match self {
Bug::EmailOrUrl(email_or_url) => {
let error = validate_email_or_url(email_or_url);
match error {
Ok(_) => (),
Err(e) => {
errors.add("Bug", e);
}
}
}
Bug::Object(bug) => {
if let Err(validation_errors) = bug.validate() {
errors = validation_errors;
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Exports {
Path(String),
Object(ExportsObject),
Nested(IndexMap<String, ExportsObject>),
}
cfg_if! {
if #[cfg(feature = "validate")] {
impl Validate for Exports {
#[allow(unused_mut)]
fn validate(&self) -> Result<(), ValidationErrors> {
let mut errors = ValidationErrors::new();
match self {
Exports::Path(path) => {
if let Err(validation_error) = validate_exports_path(path) {
errors.add("Exports", validation_error);
}
}
Exports::Object(object) => {
let mut validation_errors = ValidationErrors::new();
if let Some(additional_fields) = &object.other {
for (name, path) in additional_fields.iter() {
if name.starts_with('.') {
validation_errors.add("Exports", ValidationError::new("invalid field name"));
}
if let Err(validation_error) = validate_exports_path(path) {
validation_errors.add("Exports", validation_error);
}
}
}
let result = if validation_errors.is_empty() {
Ok(())
} else {
Err(validation_errors)
};
if let Err(nested_errors) = ValidationErrors::merge(result, "Exports", object.validate()) {
errors = nested_errors;
}
}
Exports::Nested(map) => {
for (name, object) in map.iter() {
if let Err(error) = validate_exports_path(name) {
errors.add("Exports", error);
}
if let Err(validation_errors) = object.validate() {
for (key, field_errors) in validation_errors.field_errors() {
for field_error in field_errors {
errors.add(key, field_error.clone());
}
}
}
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
}
}
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct ExportsObject {
#[cfg_attr(feature = "validate", validate(custom = "validate_exports_path"))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub require: Option<String>,
#[cfg_attr(feature = "validate", validate(custom = "validate_exports_path"))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub import: Option<String>,
#[cfg_attr(feature = "validate", validate(custom = "validate_exports_path"))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub node: Option<String>,
#[cfg_attr(feature = "validate", validate(custom = "validate_exports_path"))]
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub default: Option<String>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub other: Option<IndexMap<String, String>>,
}
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct Directories {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub bin: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub doc: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub example: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub lib: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub man: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub test: Option<String>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub other: Option<IndexMap<String, String>>,
}