-
Notifications
You must be signed in to change notification settings - Fork 286
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1efcd4e
commit 0d8969e
Showing
8 changed files
with
461 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
use std::{error, fmt, mem::MaybeUninit}; | ||
|
||
use crate::{ | ||
context::{internal::Env, Context}, | ||
handle::{internal::TransparentNoCopyWrapper, root::NapiRef, Handle}, | ||
macro_internal::NeonResultTag, | ||
object::Object, | ||
result::{JsResult, NeonResult, ResultExt}, | ||
sys::{self, raw}, | ||
types::{private, JsFunction, JsObject, Value}, | ||
}; | ||
|
||
use super::{JsBoolean, JsNumber, JsUndefined}; | ||
|
||
#[derive(Debug)] | ||
#[repr(transparent)] | ||
/// The type of JavaScript | ||
/// [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) | ||
/// objects. | ||
pub struct JsMap(raw::Local); | ||
|
||
impl JsMap { | ||
pub fn new<'cx, C>(cx: &mut C) -> NeonResult<Handle<'cx, Self>> | ||
where | ||
C: Context<'cx>, | ||
{ | ||
let map = cx | ||
.global::<JsFunction>("Map")? | ||
.construct_with(cx) | ||
.apply::<JsObject, _>(cx)?; | ||
|
||
Ok(map.downcast_or_throw(cx)?) | ||
} | ||
|
||
pub fn size<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<Handle<'a, JsNumber>> { | ||
Object::get(self, cx, "size") | ||
} | ||
|
||
// TODO: is the return type important here ? | ||
// I see three possibilities here: | ||
// 1. Stick to the JS and return the `undefined` (this is what we do now) | ||
// 2. Check we get an `undefined` and return `Ok(())` | ||
// 3. Just discard the return value and return `Ok(())` | ||
// Solutions 2 & 3 are more user-friendly, but make more assumptions (though it | ||
// should be fine given `Map` is not expected to be overridden ?). | ||
pub fn clear<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<Handle<'a, JsUndefined>> { | ||
Object::call_method_with(self, cx, "clear")?.apply(cx) | ||
} | ||
|
||
pub fn delete<'a, C: Context<'a>, K: Value>( | ||
&self, | ||
cx: &mut C, | ||
key: Handle<'a, K>, | ||
) -> NeonResult<bool> { | ||
Object::call_method_with(self, cx, "delete")? | ||
.arg(key) | ||
.apply::<JsBoolean, _>(cx) | ||
.map(|v| v.value(cx)) | ||
} | ||
|
||
pub fn entries<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<Handle<'a, JsObject>> { | ||
Object::call_method_with(self, cx, "entries")?.apply(cx) | ||
} | ||
|
||
pub fn for_each<'a, C: Context<'a>, F: Value>( | ||
&self, | ||
cx: &mut C, | ||
cb: Handle<'a, F>, | ||
) -> NeonResult<Handle<'a, JsUndefined>> { | ||
Object::call_method_with(self, cx, "forEach")? | ||
.arg(cb) | ||
.apply(cx) | ||
} | ||
|
||
pub fn get<'a, C: Context<'a>, K: Value, R: Value>( | ||
&self, | ||
cx: &mut C, | ||
key: Handle<'a, K>, | ||
) -> NeonResult<Handle<'a, R>> { | ||
Object::call_method_with(self, cx, "get")? | ||
.arg(key) | ||
.apply(cx) | ||
} | ||
|
||
pub fn has<'a, C: Context<'a>, K: Value>( | ||
&self, | ||
cx: &mut C, | ||
key: Handle<'a, K>, | ||
) -> NeonResult<bool> { | ||
Object::call_method_with(self, cx, "has")? | ||
.arg(key) | ||
.apply::<JsBoolean, _>(cx) | ||
.map(|v| v.value(cx)) | ||
} | ||
|
||
pub fn keys<'a, C: Context<'a>, R: Value>(&self, cx: &mut C) -> NeonResult<Handle<'a, R>> { | ||
Object::call_method_with(self, cx, "keys")?.apply(cx) | ||
} | ||
|
||
pub fn set<'a, C: Context<'a>, K: Value, V: Value>( | ||
&self, | ||
cx: &mut C, | ||
key: Handle<'a, K>, | ||
value: Handle<'a, V>, | ||
) -> NeonResult<Handle<'a, JsMap>> { | ||
Object::call_method_with(self, cx, "set")? | ||
.arg(key) | ||
.arg(value) | ||
.apply(cx) | ||
} | ||
|
||
pub fn values<'a, C: Context<'a>, R: Value>(&self, cx: &mut C) -> NeonResult<Handle<'a, R>> { | ||
Object::call_method_with(self, cx, "values")?.apply(cx) | ||
} | ||
|
||
pub fn group_by<'a, C: Context<'a>, A: Value, B: Value, R: Value>( | ||
cx: &mut C, | ||
elements: Handle<'a, A>, | ||
cb: Handle<'a, B>, | ||
) -> NeonResult<Handle<'a, R>> { | ||
// TODO: This is broken and leads to a `failed to downcast any to object` error | ||
// when trying to downcast `Map.groupBy` into a `JsFunction`... | ||
cx.global::<JsObject>("Map")? | ||
.call_method_with(cx, "groupBy")? | ||
.arg(elements) | ||
.arg(cb) | ||
.apply(cx) | ||
} | ||
|
||
// TODO: should we implementd those as well ? | ||
// Map[Symbol.species] | ||
// Map.prototype[Symbol.iterator]() | ||
} | ||
|
||
unsafe impl TransparentNoCopyWrapper for JsMap { | ||
type Inner = raw::Local; | ||
|
||
fn into_inner(self) -> Self::Inner { | ||
self.0 | ||
} | ||
} | ||
|
||
impl private::ValueInternal for JsMap { | ||
fn name() -> &'static str { | ||
"Map" | ||
} | ||
|
||
fn is_typeof<Other: Value>(env: Env, other: &Other) -> bool { | ||
unsafe { sys::tag::is_map(env.to_raw(), other.to_local()) } | ||
} | ||
|
||
fn to_local(&self) -> raw::Local { | ||
self.0 | ||
} | ||
|
||
unsafe fn from_local(_env: Env, h: raw::Local) -> Self { | ||
Self(h) | ||
} | ||
} | ||
|
||
impl Value for JsMap {} | ||
|
||
impl Object for JsMap {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
var addon = require(".."); | ||
var assert = require("chai").assert; | ||
|
||
describe("JsMap", function () { | ||
it("return a JsMap built in Rust", function () { | ||
assert.deepEqual(new Map(), addon.return_js_map()); | ||
}); | ||
|
||
it("return a JsMap with a number as keys and values", function () { | ||
assert.deepEqual( | ||
new Map([ | ||
[1, 1000], | ||
[-1, -1000], | ||
]), | ||
addon.return_js_map_with_number_as_keys_and_values() | ||
); | ||
}); | ||
|
||
it("return a JsMap with heterogeneous keys/values", function () { | ||
assert.deepEqual( | ||
new Map([ | ||
["a", 1], | ||
[26, "z"], | ||
]), | ||
addon.return_js_map_with_heterogeneous_keys_and_values() | ||
); | ||
}); | ||
|
||
it("can read from a JsMap", function () { | ||
const map = new Map([ | ||
[1, "a"], | ||
[2, "b"], | ||
]); | ||
assert.strictEqual(addon.read_js_map(map, 2), "b"); | ||
}); | ||
|
||
it("can get size from a JsMap", function () { | ||
const map = new Map([ | ||
[1, "a"], | ||
[2, "b"], | ||
]); | ||
assert.strictEqual(addon.get_js_map_size(map), 2); | ||
assert.strictEqual(addon.get_js_map_size(new Map()), 0); | ||
}); | ||
|
||
it("can modify a JsMap", function () { | ||
const map = new Map([[1, "a"]]); | ||
addon.modify_js_map(map, 2, "b"); | ||
assert.deepEqual( | ||
map, | ||
new Map([ | ||
[1, "a"], | ||
[2, "b"], | ||
]) | ||
); | ||
}); | ||
|
||
it("returns undefined when accessing outside JsMap bounds", function () { | ||
assert.strictEqual(addon.read_js_map(new Map(), "x"), undefined); | ||
}); | ||
|
||
it("can clear a JsMap", function () { | ||
const map = new Map([[1, "a"]]); | ||
addon.clear_js_map(map); | ||
assert.deepEqual(map, new Map()); | ||
}); | ||
|
||
it("can delete key from JsMap", function () { | ||
const map = new Map([ | ||
[1, "a"], | ||
["z", 26], | ||
]); | ||
|
||
assert.strictEqual(addon.delete_js_map(map, "unknown"), false); | ||
assert.deepEqual( | ||
map, | ||
new Map([ | ||
[1, "a"], | ||
["z", 26], | ||
]) | ||
); | ||
|
||
assert.strictEqual(addon.delete_js_map(map, 1), true); | ||
assert.deepEqual(map, new Map([["z", 26]])); | ||
|
||
assert.strictEqual(addon.delete_js_map(map, "z"), true); | ||
assert.deepEqual(map, new Map()); | ||
}); | ||
|
||
it("can use `has` on JsMap", function () { | ||
const map = new Map([ | ||
[1, "a"], | ||
["z", 26], | ||
]); | ||
|
||
assert.strictEqual(addon.has_js_map(map, 1), true); | ||
assert.strictEqual(addon.has_js_map(map, "z"), true); | ||
assert.strictEqual(addon.has_js_map(map, "unknown"), false); | ||
}); | ||
|
||
it("can use `forEach` on JsMap", function () { | ||
const map = new Map([ | ||
[1, "a"], | ||
["z", 26], | ||
]); | ||
const collected = []; | ||
|
||
assert.strictEqual( | ||
addon.for_each_js_map(map, (value, key, map) => { | ||
collected.push([key, value, map]); | ||
}), | ||
undefined | ||
); | ||
|
||
assert.deepEqual(collected, [ | ||
[1, "a", map], | ||
["z", 26, map], | ||
]); | ||
}); | ||
|
||
it("can use `groupBy` on JsMap", function () { | ||
const inventory = [ | ||
{ name: "asparagus", type: "vegetables", quantity: 9 }, | ||
{ name: "bananas", type: "fruit", quantity: 5 }, | ||
{ name: "goat", type: "meat", quantity: 23 }, | ||
{ name: "cherries", type: "fruit", quantity: 12 }, | ||
{ name: "fish", type: "meat", quantity: 22 }, | ||
]; | ||
|
||
const restock = { restock: true }; | ||
const sufficient = { restock: false }; | ||
const result = addon.group_by_js_map(inventory, ({ quantity }) => | ||
quantity < 6 ? restock : sufficient | ||
); | ||
assert.deepEqual( | ||
result, | ||
new Map([ | ||
[restock, [{ name: "bananas", type: "fruit", quantity: 5 }]], | ||
[ | ||
sufficient, | ||
[ | ||
{ name: "asparagus", type: "vegetables", quantity: 9 }, | ||
{ name: "goat", type: "meat", quantity: 23 }, | ||
{ name: "cherries", type: "fruit", quantity: 12 }, | ||
{ name: "fish", type: "meat", quantity: 22 }, | ||
], | ||
], | ||
]) | ||
); | ||
}); | ||
}); |
Oops, something went wrong.