Skip to content

Commit

Permalink
Merge pull request #1078 from neon-bindings/kv/either
Browse files Browse the repository at this point in the history
feat(neon): Implement TryIntoJs/TryFromJs for either::Either
  • Loading branch information
kjvalencik authored Oct 7, 2024
2 parents d79788a + d10fde3 commit 1efcd4e
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 2 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/neon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ easy-cast = "0.5.2" # used for a doc example
nodejs-sys = "0.15.0"

[dependencies]
either = "1.13.0"
getrandom = { version = "0.2.11", optional = true }
libloading = "0.8.1"
linkme = "0.3.25"
Expand Down
115 changes: 115 additions & 0 deletions crates/neon/src/types_impl/extract/either.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::{any, error, fmt};

use either::Either;

use crate::{
context::Cx,
handle::Handle,
object::Object,
result::{JsResult, NeonResult},
types::{
extract::{private, TryFromJs, TryIntoJs},
JsError, JsValue,
},
};

impl<'cx, L, R> TryFromJs<'cx> for Either<L, R>
where
L: TryFromJs<'cx>,
R: TryFromJs<'cx>,
{
type Error = Error<L::Error, R::Error>;

fn try_from_js(
cx: &mut Cx<'cx>,
v: Handle<'cx, JsValue>,
) -> NeonResult<Result<Self, Self::Error>> {
let left = match L::try_from_js(cx, v)? {
Ok(l) => return Ok(Ok(Either::Left(l))),
Err(l) => l,
};

let right = match R::try_from_js(cx, v)? {
Ok(r) => return Ok(Ok(Either::Right(r))),
Err(r) => r,
};

Ok(Err(Error::new::<L, R>(left, right)))
}
}

impl<'cx, L, R> TryIntoJs<'cx> for Either<L, R>
where
L: TryIntoJs<'cx>,
R: TryIntoJs<'cx>,
{
type Value = JsValue;

fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
match self {
Either::Left(v) => v.try_into_js(cx).map(|v| v.upcast()),
Either::Right(v) => v.try_into_js(cx).map(|v| v.upcast()),
}
}
}

impl<L, R> private::Sealed for Either<L, R> {}

#[derive(Debug)]
pub struct Error<L, R> {
left: (&'static str, L),
right: (&'static str, R),
}

impl<'cx, L, R> Error<L, R> {
fn new<LT, RT>(left: L, right: R) -> Self
where
LT: TryFromJs<'cx, Error = L>,
RT: TryFromJs<'cx, Error = R>,
{
Self {
left: (any::type_name::<LT>(), left),
right: (any::type_name::<RT>(), right),
}
}
}

impl<L, R> fmt::Display for Error<L, R>
where
L: fmt::Display,
R: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Either::Left: {}", self.left.1)?;
write!(f, "Either::Right: {}", self.right.1)
}
}

impl<L, R> error::Error for Error<L, R>
where
L: error::Error,
R: error::Error,
{
}

impl<'cx, L, R> TryIntoJs<'cx> for Error<L, R>
where
L: TryIntoJs<'cx>,
R: TryIntoJs<'cx>,
{
type Value = JsError;

fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
let err = JsError::type_error(
cx,
format!("expected either {} or {}", self.left.0, self.right.0,),
)?;

err.prop(cx, "left").set(self.left.1)?;
err.prop(cx, "right").set(self.right.1)?;

Ok(err)
}
}

impl<L, R> private::Sealed for Error<L, R> {}
1 change: 1 addition & 0 deletions crates/neon/src/types_impl/extract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub use self::json::Json;
pub mod json;

mod boxed;
mod either;
mod error;
mod private;
mod try_from_js;
Expand Down
1 change: 1 addition & 0 deletions test/napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ edition = "2021"
crate-type = ["cdylib"]

[dependencies]
either = "1.13.0"
num-bigint-dig = "0.8.4"
once_cell = "1.18.0"
tokio = { version = "1.34.0", features = ["rt-multi-thread"] }
Expand Down
16 changes: 16 additions & 0 deletions test/napi/lib/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,20 @@ describe("Extractors", () => {
assert.strictEqual(addon.extract_json_sum([1, 2, 3, 4]), 10);
assert.strictEqual(addon.extract_json_sum([8, 16, 18]), 42);
});

it("Either", () => {
assert.strictEqual(addon.extract_either("hello"), "String: hello");
assert.strictEqual(addon.extract_either(42), "Number: 42");

assert.throws(
() => addon.extract_either({}),
(err) => {
assert.match(err.message, /expected either.*String.*f64/);
assert.match(err.left.message, /expected string/);
assert.match(err.right.message, /expected number/);

return true;
}
);
});
});
9 changes: 9 additions & 0 deletions test/napi/src/js/extract.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use either::Either;
use neon::{prelude::*, types::extract::*};

pub fn extract_values(mut cx: FunctionContext) -> JsResult<JsArray> {
Expand Down Expand Up @@ -136,3 +137,11 @@ pub fn extract_single_add_one(mut cx: FunctionContext) -> JsResult<JsNumber> {

Ok(cx.number(n + 1.0))
}

#[neon::export]
pub fn extract_either(either: Either<String, f64>) -> String {
match either {
Either::Left(s) => format!("String: {s}"),
Either::Right(n) => format!("Number: {n}"),
}
}

0 comments on commit 1efcd4e

Please sign in to comment.