Repository: parcel-bundler/parcel
File: crates/node-bindings/src/file_system/file_system_napi.rs (Rust)

Code before:
use std::path::Path;

use napi::JsObject;
use parcel::file_system::FileSystem;

use crate::helpers::js_callable::JsCallable;

// TODO error handling

pub struct FileSystemNapi {
  read_file_fn: JsCallable,
  is_file_fn: JsCallable,
  is_dir_fn: JsCallable,
}

impl FileSystemNapi {
  pub fn new(js_file_system: &JsObject) -> napi::Result<Self> {
    Ok(Self {
      read_file_fn: JsCallable::new_from_object_prop("readFileSync", &js_file_system)?,
      is_file_fn: JsCallable::new_from_object_prop("isFile", &js_file_system)?,
      is_dir_fn: JsCallable::new_from_object_prop("isDir", &js_file_system)?,
    })
  }
}

impl FileSystem for FileSystemNapi {
  fn read_to_string(&self, path: &Path) -> std::io::Result<String> {
    self
      .read_file_fn
      .call_with_return((path.to_path_buf(), "utf8"))
      .map_err(|e| std::io::Error::other(e))
  }

  fn is_file(&self, path: &Path) -> bool {
    self
      .is_file_fn
      .call_with_return(path.to_path_buf())
      .expect("TODO handle error case")
  }

  fn is_dir(&self, path: &Path) -> bool {
    self
      .is_dir_fn
      .call_with_return(path.to_path_buf())
      .expect("TODO handle error case")
  }
}


Review comment (suggestion):
The idea was to use:
```
/// ## Safety
/// This function will do unsafe casting of a raw env pointer in order to access it
/// from the thread-safe function block. This is safe because even though the NAPI API
/// does not expose env there, that block runs on the main JS thread.
fn create_async_js_thread_safe_method<
  Params: Send + Serialize + 'static,
  Response: Send + DeserializeOwned + 'static + FromNapiValue,
>(
  env: &Env,
  js_object: &JsObject,
  method_name: &str,
) -> Result<impl (Fn(Params) -> Response) + Send, napi::Error> {
  let jsfn: JsFunction = js_object.get_property(env.create_string(method_name)?)?;

  let threadsafe_function = env.create_threadsafe_function(
    &jsfn,
    0,
    |ctx: napi::threadsafe_function::ThreadSafeCallContext<Params>| {
      Ok(vec![ctx.env.to_js_value(&ctx.value)?])
    },
  )?;

  let result = move |params| {
    let (tx, rx) = std::sync::mpsc::sync_channel(1);
    threadsafe_function.call_with_return_value(
      Ok(params),
      ThreadsafeFunctionCallMode::Blocking,
      move |result: CustomUnknownWeAreToSmart| {
        let env = result.env;
        // This is safe because this block runs on the main-thread.
        let env = unsafe { napi::Env::from_raw(env) };
        let promise = Promise::<Result>::from_js_unknown(result.value);
        let _ = env.spawn_future(async move {
            let result = result.await?;
            let _ = tx.send(result);
            Ok(())
        })?;
        Ok(())
      },
    );
    rx.recv().unwrap()
  };

  Ok(result)
}
```

+  the hack to get env passed in
```
struct CustomUnknownWeAreTooSmart {
   env: napi_env,
   value: JsUnknown
}

impl FromNapiValue for CustomUnknownWeAreTooSmart {
  unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
    Ok(Self {
       env: env,
       value: JsUnknown::from_napi_value(env, napi_val),
    })
  }
}
```

Code after:
use std::path::Path;

use napi::JsObject;
use parcel::file_system::FileSystem;

use crate::helpers::js_callable::JsCallable;

// TODO error handling

pub struct FileSystemNapi {
  read_file_fn: JsCallable,
  is_file_fn: JsCallable,
  is_dir_fn: JsCallable,
}

impl FileSystemNapi {
  pub fn new(js_file_system: &JsObject) -> napi::Result<Self> {
    Ok(Self {
      read_file_fn: JsCallable::new_from_object_prop("readFileSync", &js_file_system)?,
      is_file_fn: JsCallable::new_from_object_prop("isFile", &js_file_system)?,
      is_dir_fn: JsCallable::new_from_object_prop("isDir", &js_file_system)?,
    })
  }
}

impl FileSystem for FileSystemNapi {
  fn read_to_string(&self, path: &Path) -> std::io::Result<String> {
    self
      .read_file_fn
      .call_with_return_serde((path.to_path_buf(), "utf8"))
      .map_err(|e| std::io::Error::other(e))
  }

  fn is_file(&self, path: &Path) -> bool {
    self
      .is_file_fn
      .call_with_return_serde(path.to_path_buf())
      .expect("TODO handle error case")
  }

  fn is_dir(&self, path: &Path) -> bool {
    self
      .is_dir_fn
      .call_with_return_serde(path.to_path_buf())
      .expect("TODO handle error case")
  }
}