use std::{
fmt,
path::{Path, PathBuf},
};
use serde_json::Value;
use crate::{FileSystem, JSONError, ResolveError, path::PathUtil, replace_bom_with_whitespace};
use super::{ImportsExportsKind, PackageType, SideEffects};
pub struct PackageJson {
pub path: PathBuf,
pub realpath: PathBuf,
value: Value,
}
impl fmt::Debug for PackageJson {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PackageJson")
.field("path", &self.path)
.field("realpath", &self.realpath)
.field("name", &self.name())
.field("type", &self.r#type())
.finish_non_exhaustive()
}
}
impl PackageJson {
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub fn realpath(&self) -> &Path {
&self.realpath
}
#[must_use]
pub fn directory(&self) -> &Path {
debug_assert!(self.realpath.file_name().is_some_and(|x| x == "package.json"));
self.realpath.parent().unwrap()
}
#[must_use]
pub fn name(&self) -> Option<&str> {
self.value.as_object().and_then(|obj| obj.get("name")).and_then(|v| v.as_str())
}
#[must_use]
pub fn version(&self) -> Option<&str> {
self.value.as_object().and_then(|obj| obj.get("version")).and_then(|v| v.as_str())
}
#[must_use]
pub fn r#type(&self) -> Option<PackageType> {
self.value
.as_object()
.and_then(|obj| obj.get("type"))
.and_then(|v| v.as_str())
.and_then(PackageType::from_str)
}
#[must_use]
pub fn side_effects(&self) -> Option<SideEffects<'_>> {
self.value.as_object().and_then(|obj| obj.get("sideEffects")).and_then(
|value| match value {
Value::Bool(b) => Some(SideEffects::Bool(*b)),
Value::String(s) => Some(SideEffects::String(s.as_str())),
Value::Array(arr) => {
let strings: Vec<&str> = arr.iter().filter_map(|v| v.as_str()).collect();
Some(SideEffects::Array(strings))
}
_ => None,
},
)
}
#[must_use]
pub fn exports(&self) -> Option<ImportsExportsEntry<'_>> {
self.value.as_object().and_then(|obj| obj.get("exports")).map(ImportsExportsEntry)
}
#[must_use]
pub fn types(&self) -> Option<&str> {
self.value.as_object().and_then(|obj| obj.get("types")).and_then(|v| v.as_str())
}
#[must_use]
pub fn typings(&self) -> Option<&str> {
self.value.as_object().and_then(|obj| obj.get("typings")).and_then(|v| v.as_str())
}
pub(crate) fn types_versions(&self) -> Option<ImportsExportsMap<'_>> {
self.value
.as_object()
.and_then(|obj| obj.get("typesVersions"))
.and_then(|v| v.as_object())
.map(ImportsExportsMap)
}
pub(crate) fn main_fields<'a>(
&'a self,
main_fields: &'a [String],
) -> impl Iterator<Item = &'a str> + 'a {
let json_object = self.value.as_object();
main_fields
.iter()
.filter_map(move |main_field| json_object.and_then(|obj| obj.get(main_field.as_str())))
.filter_map(|v| v.as_str())
}
pub(crate) fn exports_fields<'a>(
&'a self,
exports_fields: &'a [Vec<String>],
) -> impl Iterator<Item = ImportsExportsEntry<'a>> + 'a {
exports_fields
.iter()
.filter_map(move |object_path| {
self.value
.as_object()
.and_then(|json_object| Self::get_value_by_path(json_object, object_path))
})
.map(ImportsExportsEntry)
}
pub(crate) fn imports_fields<'a>(
&'a self,
imports_fields: &'a [Vec<String>],
) -> impl Iterator<Item = ImportsExportsMap<'a>> + 'a {
imports_fields
.iter()
.filter_map(move |object_path| {
self.value
.as_object()
.and_then(|json_object| Self::get_value_by_path(json_object, object_path))
.and_then(|v| v.as_object())
})
.map(ImportsExportsMap)
}
pub(crate) fn resolve_browser_field<'a>(
&'a self,
path: &Path,
request: Option<&str>,
alias_fields: &'a [Vec<String>],
) -> Result<Option<&'a str>, ResolveError> {
for object in self.browser_fields(alias_fields) {
if let Some(request) = request {
if let Some(value) = object.get(request) {
return Self::alias_value(path, value);
}
} else {
let dir = self.path.parent().unwrap();
for (key, value) in object {
let joined = dir.normalize_with(key.as_str());
if joined == path {
return Self::alias_value(path, value);
}
}
}
}
Ok(None)
}
pub fn parse<Fs: FileSystem>(
_fs: &Fs,
path: PathBuf,
realpath: PathBuf,
json: Vec<u8>,
) -> Result<Self, JSONError> {
let mut json = json;
replace_bom_with_whitespace(&mut json);
super::check_if_empty(&json, &path)?;
let value = serde_json::from_slice::<Value>(&json).map_err(|error| JSONError {
path: path.clone(),
message: error.to_string(),
line: error.line(),
column: error.column(),
})?;
Ok(Self { path, realpath, value })
}
fn get_value_by_path<'a>(
fields: &'a serde_json::Map<String, Value>,
path: &[String],
) -> Option<&'a Value> {
if path.is_empty() {
return None;
}
let mut value = fields.get(path[0].as_str())?;
for key in path.iter().skip(1) {
if let Some(obj) = value.as_object() {
value = obj.get(key.as_str())?;
} else {
return None;
}
}
Some(value)
}
pub(crate) fn browser_fields<'a>(
&'a self,
alias_fields: &'a [Vec<String>],
) -> impl Iterator<Item = &'a serde_json::Map<String, Value>> + 'a {
alias_fields.iter().filter_map(move |object_path| {
self.value
.as_object()
.and_then(|json_object| Self::get_value_by_path(json_object, object_path))
.and_then(|value| value.as_object())
})
}
pub(crate) fn alias_value<'a>(
key: &Path,
value: &'a Value,
) -> Result<Option<&'a str>, ResolveError> {
match value {
Value::String(s) => Ok(Some(s.as_str())),
Value::Bool(false) => Err(ResolveError::Ignored(key.to_path_buf())),
_ => Ok(None),
}
}
}
#[derive(Clone)]
pub struct ImportsExportsEntry<'a>(pub(crate) &'a Value);
impl<'a> ImportsExportsEntry<'a> {
#[must_use]
pub fn kind(&self) -> ImportsExportsKind {
match self.0 {
Value::String(_) => ImportsExportsKind::String,
Value::Array(_) => ImportsExportsKind::Array,
Value::Object(_) => ImportsExportsKind::Map,
_ => ImportsExportsKind::Invalid,
}
}
#[must_use]
pub fn as_string(&self) -> Option<&'a str> {
match self.0 {
Value::String(s) => Some(s.as_str()),
_ => None,
}
}
#[must_use]
pub fn as_array(&self) -> Option<ImportsExportsArray<'a>> {
match self.0 {
Value::Array(arr) => Some(ImportsExportsArray(arr)),
_ => None,
}
}
#[must_use]
pub fn as_map(&self) -> Option<ImportsExportsMap<'a>> {
match self.0 {
Value::Object(obj) => Some(ImportsExportsMap(obj)),
_ => None,
}
}
}
#[derive(Clone)]
pub struct ImportsExportsArray<'a>(&'a [Value]);
impl<'a> ImportsExportsArray<'a> {
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item = ImportsExportsEntry<'a>> {
ImportsExportsArrayIter { slice: self.0, index: 0 }
}
}
struct ImportsExportsArrayIter<'a> {
slice: &'a [Value],
index: usize,
}
impl<'a> Iterator for ImportsExportsArrayIter<'a> {
type Item = ImportsExportsEntry<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.slice.get(self.index).map(|value| {
self.index += 1;
ImportsExportsEntry(value)
})
}
}
#[derive(Clone)]
pub struct ImportsExportsMap<'a>(pub(crate) &'a serde_json::Map<String, Value>);
impl<'a> ImportsExportsMap<'a> {
pub fn get(&self, key: &str) -> Option<ImportsExportsEntry<'a>> {
self.0.get(key).map(ImportsExportsEntry)
}
pub fn keys(&self) -> impl Iterator<Item = &'a str> {
self.0.keys().map(String::as_str)
}
pub fn iter(&self) -> impl Iterator<Item = (&'a str, ImportsExportsEntry<'a>)> {
self.0.iter().map(|(k, v)| (k.as_str(), ImportsExportsEntry(v)))
}
}