use std::{
fmt::{Debug, Formatter},
marker::PhantomData,
path::{Path, PathBuf},
};
use simd_json::{
borrowed::Value, prelude::*, to_borrowed_value, BorrowedValue, Error as SimdParseError,
};
use crate::{path::PathUtil, ResolveError};
pub type JSONMap<'a> = simd_json::borrowed::Object<'a>;
#[cfg(feature = "package_json_raw_json_api")]
use simd_json::serde::from_refborrowed_value;
pub use simd_json::BorrowedValue as JSONValue;
use crate::package_json::{ModuleType, SideEffects};
pub struct JSONCell {
value: BorrowedValue<'static>,
buf: Vec<u8>,
_invariant: PhantomData<&'static str>,
}
impl JSONCell {
pub fn try_new(mut buf: Vec<u8>) -> Result<Self, SimdParseError> {
let value = to_borrowed_value(&mut buf)?;
#[allow(unused_mut)]
let mut value =
unsafe { std::mem::transmute::<BorrowedValue<'_>, BorrowedValue<'static>>(value) };
#[cfg(feature = "package_json_raw_json_api")]
if let Some(json_object) = value.as_object_mut() {
json_object.remove("description");
json_object.remove("keywords");
json_object.remove("scripts");
json_object.remove("dependencies");
json_object.remove("devDependencies");
json_object.remove("peerDependencies");
json_object.remove("optionalDependencies");
}
Ok(Self {
value,
buf,
_invariant: PhantomData,
})
}
pub fn borrow_dependent(&self) -> &JSONValue<'_> {
&self.value
}
}
impl Debug for JSONCell {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let str = String::from_utf8_lossy(&self.buf);
f.write_fmt(format_args!("JSONCell({str})"))
}
}
impl Default for JSONCell {
fn default() -> Self {
Self {
value: JSONValue::Static(StaticNode::Null),
buf: Vec::new(),
_invariant: PhantomData,
}
}
}
#[derive(Debug, Default)]
pub struct PackageJson {
pub path: PathBuf,
pub realpath: PathBuf,
pub name: Option<String>,
pub r#type: Option<ModuleType>,
pub side_effects: Option<SideEffects>,
raw_json: std::sync::Arc<JSONCell>,
#[cfg(feature = "package_json_raw_json_api")]
serde_json: std::sync::Arc<serde_json::Value>,
}
const BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];
#[derive(Debug, PartialEq, Eq)]
pub struct ParseError {
pub(crate) message: String,
pub(crate) index: usize,
}
impl ParseError {
pub fn index(&self) -> usize {
self.index
}
pub fn error(&self) -> &str {
&self.message
}
}
impl From<SimdParseError> for ParseError {
fn from(value: SimdParseError) -> Self {
Self {
index: value.index(),
message: format!("{:?}", value.error()).to_lowercase(),
}
}
}
impl PackageJson {
pub(crate) fn parse(path: PathBuf, realpath: PathBuf, json: Vec<u8>) -> Result<Self, ParseError> {
if json.starts_with(&BOM) {
return Err(ParseError {
message: "BOM character found".to_string(),
index: 0,
});
}
let json_cell = JSONCell::try_new(json).map_err(ParseError::from)?;
let mut package_json = Self::default();
if let Some(json_object) = json_cell.borrow_dependent().as_object() {
package_json.name = json_object
.get("name")
.and_then(|field| field.as_str())
.map(ToString::to_string);
package_json.r#type = json_object
.get("type")
.and_then(|str| str.as_str())
.and_then(|str| str.try_into().ok());
package_json.side_effects = json_object
.get("sideEffects")
.and_then(|value| SideEffects::try_from(value).ok());
#[cfg(feature = "package_json_raw_json_api")]
{
package_json.init_serde_json(json_object);
}
}
package_json.path = path;
package_json.realpath = realpath;
package_json.raw_json = std::sync::Arc::new(json_cell);
Ok(package_json)
}
#[cfg(feature = "package_json_raw_json_api")]
fn init_serde_json(&mut self, value: &JSONMap) {
let mut json_map = serde_json::value::Map::with_capacity(9);
for (key, value) in value {
if let Ok(v) = from_refborrowed_value(value) {
json_map.insert(key.to_string(), v);
}
}
self.serde_json = std::sync::Arc::new(serde_json::Value::Object(json_map));
}
fn get_value_by_paths<'a>(fields: &'a JSONMap, paths: &[String]) -> Option<&'a JSONValue<'a>> {
if paths.is_empty() {
return None;
}
let mut value = fields.get(paths[0].as_str())?;
for key in paths.iter().skip(1) {
if let Some(inner_value) = value.as_object().and_then(|o| o.get(key.as_str())) {
value = inner_value;
} else {
return None;
}
}
Some(value)
}
#[cfg(feature = "package_json_raw_json_api")]
pub fn raw_json(&self) -> &std::sync::Arc<serde_json::Value> {
&self.serde_json
}
pub fn directory(&self) -> &Path {
debug_assert!(self
.realpath
.file_name()
.is_some_and(|x| x == "package.json"));
self.realpath.parent().unwrap()
}
pub(crate) fn main_fields<'a>(
&'a self,
main_fields: &'a [String],
) -> impl Iterator<Item = &'a str> + 'a {
main_fields.iter().filter_map(|main_field| {
self
.raw_json
.borrow_dependent()
.get_str(main_field.as_str())
})
}
pub(crate) fn exports_fields<'a>(
&'a self,
exports_fields: &'a [Vec<String>],
) -> impl Iterator<Item = &'a JSONValue<'a>> + 'a {
exports_fields.iter().filter_map(|object_path| {
self
.raw_json
.borrow_dependent()
.as_object()
.and_then(|json_object| Self::get_value_by_paths(json_object, object_path))
})
}
pub(crate) fn imports_fields<'a>(
&'a self,
imports_fields: &'a [Vec<String>],
) -> impl Iterator<Item = &'a JSONMap<'a>> + 'a {
imports_fields.iter().filter_map(|object_path| {
self
.raw_json
.borrow_dependent()
.as_object()
.and_then(|json_object| Self::get_value_by_paths(json_object, object_path))
.and_then(|value| value.as_object())
})
}
fn browser_fields<'a>(
&'a self,
alias_fields: &'a [Vec<String>],
) -> impl Iterator<Item = &'a JSONMap<'a>> + 'a {
alias_fields.iter().filter_map(|object_path| {
self
.raw_json
.borrow_dependent()
.as_object()
.and_then(|json_object| Self::get_value_by_paths(json_object, object_path))
.and_then(|value| value.as_object())
})
}
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.to_string());
if joined == path {
return Self::alias_value(path, value);
}
}
}
}
Ok(None)
}
fn alias_value<'a>(key: &Path, value: &'a JSONValue) -> Result<Option<&'a str>, ResolveError> {
match value {
JSONValue::String(value) => Ok(Some(value)),
JSONValue::Static(sn) => {
if matches!(sn.as_bool(), Some(false)) {
Err(ResolveError::Ignored(key.to_path_buf()))
} else {
Ok(None)
}
}
_ => Ok(None),
}
}
}
impl<'a> TryFrom<&'a JSONValue<'a>> for SideEffects {
type Error = &'static str;
fn try_from(value: &'a JSONValue<'a>) -> Result<Self, Self::Error> {
match value {
Value::Static(StaticNode::Bool(b)) => Ok(Self::Bool(*b)),
Value::String(str) => Ok(Self::String(str.to_string())),
Value::Array(arr) => {
let mut vec = Vec::with_capacity(arr.len());
for item in arr.iter() {
if let Value::String(s) = item {
vec.push(s.to_string());
} else {
return Err("Invalid sideEffects array item, expected string");
}
}
Ok(Self::Array(vec))
}
_ => Err("Invalid sideEffects value, expected bool, string or array of strings"),
}
}
}