rspack_binding_values 0.2.0

rspack binding values
use std::collections::HashMap;

use napi_derive::napi;
use rspack_core::{
  Alias, AliasMap, ByDependency, DependencyCategory, Resolve, ResolveOptionsWithDependencyType,
  TsconfigOptions, TsconfigReferences,
};
use rspack_error::error;

pub type AliasValue = serde_json::Value;

#[derive(Debug)]
#[napi(object)]
pub struct RawAliasOptionItem {
  pub path: String,
  #[napi(ts_type = "Array<string | false>")]
  pub redirect: Vec<AliasValue>,
}

#[derive(Debug)]
#[napi(object)]
pub struct RawResolveTsconfigOptions {
  pub config_file: String,
  #[napi(ts_type = r#""auto" | "manual" | "disabled""#)]
  pub references_type: String,
  pub references: Option<Vec<String>>,
}

#[derive(Debug)]
#[napi(object)]
pub struct RawResolveOptions {
  pub prefer_relative: Option<bool>,
  pub prefer_absolute: Option<bool>,
  pub extensions: Option<Vec<String>>,
  pub main_files: Option<Vec<String>>,
  pub main_fields: Option<Vec<String>>,
  pub condition_names: Option<Vec<String>>,
  pub alias: Option<Vec<RawAliasOptionItem>>,
  pub fallback: Option<Vec<RawAliasOptionItem>>,
  pub symlinks: Option<bool>,
  pub tsconfig: Option<RawResolveTsconfigOptions>,
  pub modules: Option<Vec<String>>,
  pub by_dependency: Option<HashMap<String, RawResolveOptions>>,
  pub fully_specified: Option<bool>,
  pub exports_fields: Option<Vec<String>>,
  pub description_files: Option<Vec<String>>,
  pub enforce_extension: Option<bool>,
  pub imports_fields: Option<Vec<String>>,
  #[napi(ts_type = "Record<string, Array<string>>")]
  pub extension_alias: Option<HashMap<String, Vec<String>>>,
  pub alias_fields: Option<Vec<String>>,
  pub restrictions: Option<Vec<String>>,
  pub roots: Option<Vec<String>>,
}

fn normalize_alias(alias: Option<Vec<RawAliasOptionItem>>) -> rspack_error::Result<Option<Alias>> {
  alias
    .map(|alias| {
      alias
        .into_iter()
        .map(|alias_item| {
          alias_item
            .redirect
            .into_iter()
            .map(|value| {
              if let Some(s) = value.as_str() {
                Ok(AliasMap::Path(s.to_string()))
              } else if let Some(b) = value.as_bool() {
                if b {
                  Err(error!("Alias should not be true in {}", alias_item.path))
                } else {
                  Ok(AliasMap::Ignore)
                }
              } else {
                Err(error!(
                  "Alias should be false or string in {}",
                  alias_item.path
                ))
              }
            })
            .collect::<rspack_error::Result<_>>()
            .map(|value| (alias_item.path, value))
        })
        .collect::<rspack_error::Result<_>>()
    })
    .map_or(Ok(None), |v| v.map(Some))
}

impl TryFrom<RawResolveOptions> for Resolve {
  type Error = rspack_error::Error;

  fn try_from(value: RawResolveOptions) -> Result<Self, Self::Error> {
    let prefer_relative = value.prefer_relative;
    let prefer_absolute = value.prefer_absolute;
    let extensions = value.extensions;
    let main_files = value.main_files;
    let main_fields = value.main_fields;
    let condition_names = value.condition_names;
    let symlinks = value.symlinks;
    let fully_specified = value.fully_specified;
    let alias = normalize_alias(value.alias)?;
    let fallback = normalize_alias(value.fallback)?;
    let modules = value.modules;
    let tsconfig = match value.tsconfig {
      Some(config) => Some(TsconfigOptions::try_from(config)?),
      None => None,
    };
    let by_dependency = value
      .by_dependency
      .map(|i| {
        i.into_iter()
          .map(|(k, v)| Ok((k.into(), v.try_into()?)))
          .collect::<Result<ByDependency, Self::Error>>()
      })
      .transpose()?;
    let exports_fields = value
      .exports_fields
      .map(|v| v.into_iter().map(|s| vec![s]).collect());
    let extension_alias = value.extension_alias.map(|v| v.into_iter().collect());
    let alias_fields = value
      .alias_fields
      .map(|v| v.into_iter().map(|s| vec![s]).collect());
    let restrictions = value.restrictions;
    let roots = value.roots;
    let enforce_extension = value.enforce_extension;
    let description_files = value.description_files;
    let imports_fields = value
      .imports_fields
      .map(|v| v.into_iter().map(|s| vec![s]).collect());

    Ok(Resolve {
      modules,
      prefer_relative,
      prefer_absolute,
      extensions,
      main_fields,
      main_files,
      condition_names,
      alias,
      symlinks,
      tsconfig,
      fallback,
      by_dependency,
      fully_specified,
      exports_fields,
      extension_alias,
      alias_fields,
      restrictions,
      roots,
      enforce_extension,
      description_files,
      imports_fields,
    })
  }
}

impl TryFrom<RawResolveTsconfigOptions> for TsconfigOptions {
  type Error = rspack_error::Error;
  fn try_from(value: RawResolveTsconfigOptions) -> Result<Self, Self::Error> {
    let references = match value.references_type.as_str() {
      "manual" => TsconfigReferences::Paths(value.references.unwrap_or_default().into_iter().map(Into::into).collect()),
      "auto" => TsconfigReferences::Auto,
      "disabled" => TsconfigReferences::Disabled,
      _ => panic!(
          "Failed to resolve the references type {}. Expected type is `auto`, `manual` or `disabled`.",
          value.references_type
      )
  };
    Ok(TsconfigOptions {
      config_file: value.config_file.into(),
      references,
    })
  }
}

#[derive(Debug)]
#[napi(object)]
pub struct RawResolveOptionsWithDependencyType {
  pub prefer_relative: Option<bool>,
  pub prefer_absolute: Option<bool>,
  pub extensions: Option<Vec<String>>,
  pub main_files: Option<Vec<String>>,
  pub main_fields: Option<Vec<String>>,
  pub condition_names: Option<Vec<String>>,
  pub alias: Option<Vec<RawAliasOptionItem>>,
  pub fallback: Option<Vec<RawAliasOptionItem>>,
  pub symlinks: Option<bool>,
  pub tsconfig: Option<RawResolveTsconfigOptions>,
  pub modules: Option<Vec<String>>,
  pub by_dependency: Option<HashMap<String, RawResolveOptions>>,
  pub fully_specified: Option<bool>,
  pub exports_fields: Option<Vec<String>>,
  pub description_files: Option<Vec<String>>,
  pub enforce_extension: Option<bool>,
  pub imports_fields: Option<Vec<String>>,
  #[napi(ts_type = "Record<string, Array<string>>")]
  pub extension_alias: Option<HashMap<String, Vec<String>>>,
  pub alias_fields: Option<Vec<String>>,
  pub restrictions: Option<Vec<String>>,
  pub roots: Option<Vec<String>>,

  pub dependency_category: Option<String>,
  pub resolve_to_context: Option<bool>,
}

pub fn normalize_raw_resolve_options_with_dependency_type(
  raw: Option<RawResolveOptionsWithDependencyType>,
  default_resolve_to_context: bool,
) -> rspack_error::Result<ResolveOptionsWithDependencyType> {
  match raw {
    Some(raw) => {
      let tsconfig = match raw.tsconfig {
        Some(config) => Some(TsconfigOptions::try_from(config)?),
        None => None,
      };

      let exports_fields = raw
        .exports_fields
        .map(|v| v.into_iter().map(|s| vec![s]).collect());

      let extension_alias = raw.extension_alias.map(|v| v.into_iter().collect());

      let alias_fields = raw
        .alias_fields
        .map(|v| v.into_iter().map(|s| vec![s]).collect());

      let imports_fields = raw
        .imports_fields
        .map(|v| v.into_iter().map(|s| vec![s]).collect());

      let by_dependency = raw
        .by_dependency
        .map(|i| {
          i.into_iter()
            .map(|(k, v)| Ok((k.into(), v.try_into()?)))
            .collect::<rspack_error::Result<ByDependency>>()
        })
        .transpose()?;

      let resolve_options = Resolve {
        extensions: raw.extensions,
        alias: normalize_alias(raw.alias)?,
        prefer_relative: raw.prefer_relative,
        prefer_absolute: raw.prefer_absolute,
        symlinks: raw.symlinks,
        main_files: raw.main_files,
        main_fields: raw.main_fields,
        condition_names: raw.condition_names,
        tsconfig,
        modules: raw.modules,
        fallback: normalize_alias(raw.fallback)?,
        fully_specified: raw.fully_specified,
        exports_fields,
        extension_alias,
        alias_fields,
        roots: raw.roots,
        restrictions: raw.restrictions,
        imports_fields,
        by_dependency,
        description_files: raw.description_files,
        enforce_extension: raw.enforce_extension,
      };
      Ok(ResolveOptionsWithDependencyType {
        resolve_options: Some(Box::new(resolve_options)),
        resolve_to_context: raw.resolve_to_context.unwrap_or(default_resolve_to_context),
        dependency_category: raw
          .dependency_category
          .map(|c| DependencyCategory::from(c.as_str()))
          .unwrap_or(DependencyCategory::Unknown),
      })
    }
    None => Ok(ResolveOptionsWithDependencyType {
      resolve_options: None,
      resolve_to_context: default_resolve_to_context,
      dependency_category: DependencyCategory::Unknown,
    }),
  }
}