mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 10:06:17 +08:00
feat(y-octo): import y-octo monorepo (#11750)
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
use napi::{bindgen_prelude::Array as JsArray, Env, JsUnknown, ValueType};
|
||||
use y_octo::{Any, Array, Value};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[napi]
|
||||
pub struct YArray {
|
||||
pub(crate) array: Array,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl YArray {
|
||||
#[allow(clippy::new_without_default)]
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub(crate) fn inner_new(array: Array) -> Self {
|
||||
Self { array }
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn length(&self) -> i64 {
|
||||
self.array.len() as i64
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.array.is_empty()
|
||||
}
|
||||
|
||||
#[napi(ts_generic_types = "T = unknown", ts_return_type = "T")]
|
||||
pub fn get(&self, env: Env, index: i64) -> Result<MixedYType> {
|
||||
if let Some(value) = self.array.get(index as u64) {
|
||||
match value {
|
||||
Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D),
|
||||
Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))),
|
||||
Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))),
|
||||
Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))),
|
||||
_ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D),
|
||||
}
|
||||
.map_err(anyhow::Error::from)
|
||||
} else {
|
||||
Ok(MixedYType::D(env.get_null()?.into_unknown()))
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(
|
||||
ts_args_type = "index: number, value: YArray | YMap | YText | boolean | number | string | \
|
||||
Record<string, any> | null | undefined"
|
||||
)]
|
||||
pub fn insert(&mut self, index: i64, value: MixedRefYType) -> Result<()> {
|
||||
match value {
|
||||
MixedRefYType::A(array) => self
|
||||
.array
|
||||
.insert(index as u64, array.array.clone())
|
||||
.map_err(anyhow::Error::from),
|
||||
MixedRefYType::B(map) => self
|
||||
.array
|
||||
.insert(index as u64, map.map.clone())
|
||||
.map_err(anyhow::Error::from),
|
||||
MixedRefYType::C(text) => self
|
||||
.array
|
||||
.insert(index as u64, text.text.clone())
|
||||
.map_err(anyhow::Error::from),
|
||||
MixedRefYType::D(unknown) => match unknown.get_type() {
|
||||
Ok(value_type) => match value_type {
|
||||
ValueType::Undefined | ValueType::Null => self
|
||||
.array
|
||||
.insert(index as u64, Any::Null)
|
||||
.map_err(anyhow::Error::from),
|
||||
ValueType::Boolean => match unknown.coerce_to_bool().and_then(|v| v.get_value()) {
|
||||
Ok(boolean) => self
|
||||
.array
|
||||
.insert(index as u64, boolean)
|
||||
.map_err(anyhow::Error::from),
|
||||
Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to boolean")),
|
||||
},
|
||||
ValueType::Number => match unknown.coerce_to_number().and_then(|v| v.get_double()) {
|
||||
Ok(number) => self
|
||||
.array
|
||||
.insert(index as u64, number)
|
||||
.map_err(anyhow::Error::from),
|
||||
Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to number")),
|
||||
},
|
||||
ValueType::String => {
|
||||
match unknown
|
||||
.coerce_to_string()
|
||||
.and_then(|v| v.into_utf8())
|
||||
.and_then(|s| s.as_str().map(|s| s.to_string()))
|
||||
{
|
||||
Ok(string) => self
|
||||
.array
|
||||
.insert(index as u64, string)
|
||||
.map_err(anyhow::Error::from),
|
||||
Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to string")),
|
||||
}
|
||||
}
|
||||
ValueType::Object => match unknown
|
||||
.coerce_to_object()
|
||||
.and_then(|o| o.get_array_length().map(|l| (o, l)))
|
||||
{
|
||||
Ok((object, length)) => {
|
||||
for i in 0..length {
|
||||
if let Ok(any) = object
|
||||
.get_element::<JsUnknown>(i)
|
||||
.and_then(get_any_from_js_unknown)
|
||||
{
|
||||
self
|
||||
.array
|
||||
.insert(index as u64 + i as u64, Value::Any(any))
|
||||
.map_err(anyhow::Error::from)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to object")),
|
||||
},
|
||||
ValueType::BigInt => Err(anyhow::Error::msg("BigInt values are not supported")),
|
||||
ValueType::Symbol => Err(anyhow::Error::msg("Symbol values are not supported")),
|
||||
ValueType::Function => Err(anyhow::Error::msg("Function values are not supported")),
|
||||
ValueType::External => Err(anyhow::Error::msg("External values are not supported")),
|
||||
ValueType::Unknown => Err(anyhow::Error::msg("Unknown values are not supported")),
|
||||
},
|
||||
Err(e) => Err(anyhow::Error::from(e)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn remove(&mut self, index: i64, len: i64) -> Result<()> {
|
||||
self
|
||||
.array
|
||||
.remove(index as u64, len as u64)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn to_json(&self, env: Env) -> Result<JsArray> {
|
||||
let mut js_array = env.create_array(0)?;
|
||||
for value in self.array.iter() {
|
||||
js_array.insert(get_js_unknown_from_value(env, value)?)?;
|
||||
}
|
||||
Ok(js_array)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_array_init() {
|
||||
let doc = Doc::new(None);
|
||||
let array = doc.get_or_create_array("array".into()).unwrap();
|
||||
assert_eq!(array.length(), 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
use napi::{
|
||||
bindgen_prelude::{Buffer, Uint8Array},
|
||||
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
||||
};
|
||||
use y_octo::{CrdtRead, Doc as YDoc, History, RawDecoder, StateVector};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[napi]
|
||||
pub struct Doc {
|
||||
doc: YDoc,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl Doc {
|
||||
#[napi(constructor)]
|
||||
pub fn new(client_id: Option<i64>) -> Self {
|
||||
Self {
|
||||
doc: if let Some(client_id) = client_id {
|
||||
YDoc::with_client(client_id as u64)
|
||||
} else {
|
||||
YDoc::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn client_id(&self) -> i64 {
|
||||
self.doc.client() as i64
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn guid(&self) -> &str {
|
||||
self.doc.guid()
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn keys(&self) -> Vec<String> {
|
||||
self.doc.keys()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_or_create_array(&self, key: String) -> Result<YArray> {
|
||||
self
|
||||
.doc
|
||||
.get_or_create_array(key)
|
||||
.map(YArray::inner_new)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_or_create_text(&self, key: String) -> Result<YText> {
|
||||
self
|
||||
.doc
|
||||
.get_or_create_text(key)
|
||||
.map(YText::inner_new)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_or_create_map(&self, key: String) -> Result<YMap> {
|
||||
self
|
||||
.doc
|
||||
.get_or_create_map(key)
|
||||
.map(YMap::inner_new)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn create_array(&self) -> Result<YArray> {
|
||||
self
|
||||
.doc
|
||||
.create_array()
|
||||
.map(YArray::inner_new)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn create_text(&self) -> Result<YText> {
|
||||
self
|
||||
.doc
|
||||
.create_text()
|
||||
.map(YText::inner_new)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn create_map(&self) -> Result<YMap> {
|
||||
self
|
||||
.doc
|
||||
.create_map()
|
||||
.map(YMap::inner_new)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn apply_update(&mut self, update: &[u8]) -> Result<()> {
|
||||
self.doc.apply_update_from_binary_v1(update)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn encode_state_as_update_v1(&self, state: Option<&[u8]>) -> Result<Uint8Array> {
|
||||
let result = match state {
|
||||
Some(state) => {
|
||||
let mut decoder = RawDecoder::new(state);
|
||||
let state = StateVector::read(&mut decoder)?;
|
||||
self.doc.encode_state_as_update_v1(&state)
|
||||
}
|
||||
None => self.doc.encode_update_v1(),
|
||||
};
|
||||
|
||||
result.map(|v| v.into()).map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn gc(&self) -> Result<()> {
|
||||
self.doc.gc().map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi(ts_args_type = "callback: (result: Uint8Array) => void")]
|
||||
pub fn on_update(&mut self, callback: ThreadsafeFunction<Buffer>) -> Result<()> {
|
||||
let callback = move |update: &[u8], _h: &[History]| {
|
||||
callback.call(
|
||||
Ok(update.to_vec().into()),
|
||||
ThreadsafeFunctionCallMode::Blocking,
|
||||
);
|
||||
};
|
||||
self.doc.subscribe(Box::new(callback));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_doc_client() {
|
||||
let client_id = 1;
|
||||
let doc = Doc::new(Some(client_id));
|
||||
assert_eq!(doc.client_id(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_doc_guid() {
|
||||
let doc = Doc::new(None);
|
||||
assert_eq!(doc.guid().len(), 21);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_array() {
|
||||
let doc = Doc::new(None);
|
||||
let array = doc.get_or_create_array("array".into()).unwrap();
|
||||
assert_eq!(array.length(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_text() {
|
||||
let doc = Doc::new(None);
|
||||
let text = doc.get_or_create_text("text".into()).unwrap();
|
||||
assert_eq!(text.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keys() {
|
||||
let doc = Doc::new(None);
|
||||
doc.get_or_create_array("array".into()).unwrap();
|
||||
doc.get_or_create_text("text".into()).unwrap();
|
||||
doc.get_or_create_map("map".into()).unwrap();
|
||||
let mut keys = doc.keys();
|
||||
keys.sort();
|
||||
assert_eq!(keys, vec!["array", "map", "text"]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use anyhow::Result;
|
||||
use napi_derive::napi;
|
||||
|
||||
mod array;
|
||||
mod doc;
|
||||
mod map;
|
||||
mod text;
|
||||
mod utils;
|
||||
|
||||
pub use array::YArray;
|
||||
pub use doc::Doc;
|
||||
pub use map::YMap;
|
||||
pub use text::YText;
|
||||
use utils::{
|
||||
get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any,
|
||||
get_js_unknown_from_value, MixedRefYType, MixedYType,
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
use napi::{Env, JsObject, ValueType};
|
||||
use y_octo::{Any, Map, Value};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[napi]
|
||||
pub struct YMap {
|
||||
pub(crate) map: Map,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl YMap {
|
||||
#[allow(clippy::new_without_default)]
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub(crate) fn inner_new(map: Map) -> Self {
|
||||
Self { map }
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn length(&self) -> i64 {
|
||||
self.map.len() as i64
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.map.is_empty()
|
||||
}
|
||||
|
||||
#[napi(ts_generic_types = "T = unknown", ts_return_type = "T")]
|
||||
pub fn get(&self, env: Env, key: String) -> Result<MixedYType> {
|
||||
if let Some(value) = self.map.get(&key) {
|
||||
match value {
|
||||
Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D),
|
||||
Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))),
|
||||
Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))),
|
||||
Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))),
|
||||
_ => env.get_null().map(|v| v.into_unknown()).map(MixedYType::D),
|
||||
}
|
||||
.map_err(anyhow::Error::from)
|
||||
} else {
|
||||
Ok(MixedYType::D(env.get_null()?.into_unknown()))
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(
|
||||
ts_args_type = "key: string, value: YArray | YMap | YText | boolean | number | string | \
|
||||
Record<string, any> | null | undefined"
|
||||
)]
|
||||
pub fn set(&mut self, key: String, value: MixedRefYType) -> Result<()> {
|
||||
match value {
|
||||
MixedRefYType::A(array) => self
|
||||
.map
|
||||
.insert(key, array.array.clone())
|
||||
.map_err(anyhow::Error::from),
|
||||
MixedRefYType::B(map) => self
|
||||
.map
|
||||
.insert(key, map.map.clone())
|
||||
.map_err(anyhow::Error::from),
|
||||
MixedRefYType::C(text) => self
|
||||
.map
|
||||
.insert(key, text.text.clone())
|
||||
.map_err(anyhow::Error::from),
|
||||
MixedRefYType::D(unknown) => match unknown.get_type() {
|
||||
Ok(value_type) => match value_type {
|
||||
ValueType::Undefined | ValueType::Null => {
|
||||
self.map.insert(key, Any::Null).map_err(anyhow::Error::from)
|
||||
}
|
||||
ValueType::Boolean => match unknown.coerce_to_bool().and_then(|v| v.get_value()) {
|
||||
Ok(boolean) => self.map.insert(key, boolean).map_err(anyhow::Error::from),
|
||||
Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to boolean")),
|
||||
},
|
||||
ValueType::Number => match unknown.coerce_to_number().and_then(|v| v.get_double()) {
|
||||
Ok(number) => self.map.insert(key, number).map_err(anyhow::Error::from),
|
||||
Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to number")),
|
||||
},
|
||||
ValueType::String => {
|
||||
match unknown
|
||||
.coerce_to_string()
|
||||
.and_then(|v| v.into_utf8())
|
||||
.and_then(|s| s.as_str().map(|s| s.to_string()))
|
||||
{
|
||||
Ok(string) => self.map.insert(key, string).map_err(anyhow::Error::from),
|
||||
Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to string")),
|
||||
}
|
||||
}
|
||||
ValueType::Object => match unknown.coerce_to_object().and_then(get_any_from_js_object) {
|
||||
Ok(any) => self
|
||||
.map
|
||||
.insert(key, Value::Any(any))
|
||||
.map_err(anyhow::Error::from),
|
||||
Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to object")),
|
||||
},
|
||||
ValueType::BigInt => Err(anyhow::Error::msg("BigInt values are not supported")),
|
||||
ValueType::Symbol => Err(anyhow::Error::msg("Symbol values are not supported")),
|
||||
ValueType::Function => Err(anyhow::Error::msg("Function values are not supported")),
|
||||
ValueType::External => Err(anyhow::Error::msg("External values are not supported")),
|
||||
ValueType::Unknown => Err(anyhow::Error::msg("Unknown values are not supported")),
|
||||
},
|
||||
Err(e) => Err(anyhow::Error::from(e)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn remove(&mut self, key: String) {
|
||||
self.map.remove(&key);
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn to_json(&self, env: Env) -> Result<JsObject> {
|
||||
let mut js_object = env.create_object()?;
|
||||
for (key, value) in self.map.iter() {
|
||||
js_object.set(key, get_js_unknown_from_value(env, value))?;
|
||||
}
|
||||
Ok(js_object)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_map_init() {
|
||||
let doc = Doc::new(None);
|
||||
let text = doc.get_or_create_map("map".into()).unwrap();
|
||||
assert_eq!(text.length(), 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use y_octo::Text;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[napi]
|
||||
pub struct YText {
|
||||
pub(crate) text: Text,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl YText {
|
||||
#[allow(clippy::new_without_default)]
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub(crate) fn inner_new(text: Text) -> Self {
|
||||
Self { text }
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn len(&self) -> i64 {
|
||||
self.text.len() as i64
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.text.is_empty()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn insert(&mut self, index: i64, str: String) -> Result<()> {
|
||||
self
|
||||
.text
|
||||
.insert(index as u64, str)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn remove(&mut self, index: i64, len: i64) -> Result<()> {
|
||||
self
|
||||
.text
|
||||
.remove(index as u64, len as u64)
|
||||
.map_err(anyhow::Error::from)
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn length(&self) -> i64 {
|
||||
self.text.len() as i64
|
||||
}
|
||||
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
#[napi]
|
||||
pub fn to_string(&self) -> String {
|
||||
self.text.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_text_init() {
|
||||
let doc = Doc::new(None);
|
||||
let text = doc.get_or_create_text("text".into()).unwrap();
|
||||
assert_eq!(text.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_edit() {
|
||||
let doc = Doc::new(None);
|
||||
let mut text = doc.get_or_create_text("text".into()).unwrap();
|
||||
text.insert(0, "hello".into()).unwrap();
|
||||
assert_eq!(text.to_string(), "hello");
|
||||
text.insert(5, " world".into()).unwrap();
|
||||
assert_eq!(text.to_string(), "hello world");
|
||||
text.remove(5, 6).unwrap();
|
||||
assert_eq!(text.to_string(), "hello");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
use napi::{bindgen_prelude::Either4, Env, Error, JsObject, JsUnknown, Result, Status, ValueType};
|
||||
use y_octo::{AHashMap, Any, HashMapExt, Value};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub type MixedYType = Either4<YArray, YMap, YText, JsUnknown>;
|
||||
pub type MixedRefYType<'a> = Either4<&'a YArray, &'a YMap, &'a YText, JsUnknown>;
|
||||
|
||||
pub fn get_js_unknown_from_any(env: Env, any: Any) -> Result<JsUnknown> {
|
||||
match any {
|
||||
Any::Null | Any::Undefined => env.get_null().map(|v| v.into_unknown()),
|
||||
Any::True => env.get_boolean(true).map(|v| v.into_unknown()),
|
||||
Any::False => env.get_boolean(false).map(|v| v.into_unknown()),
|
||||
Any::Integer(number) => env.create_int32(number).map(|v| v.into_unknown()),
|
||||
Any::BigInt64(number) => env.create_int64(number).map(|v| v.into_unknown()),
|
||||
Any::Float32(number) => env.create_double(number.0 as f64).map(|v| v.into_unknown()),
|
||||
Any::Float64(number) => env.create_double(number.0).map(|v| v.into_unknown()),
|
||||
Any::String(string) => env.create_string(string.as_str()).map(|v| v.into_unknown()),
|
||||
Any::Array(array) => {
|
||||
let mut js_array = env.create_array_with_length(array.len())?;
|
||||
for (i, value) in array.into_iter().enumerate() {
|
||||
js_array.set_element(i as u32, get_js_unknown_from_any(env, value)?)?;
|
||||
}
|
||||
Ok(js_array.into_unknown())
|
||||
}
|
||||
_ => env.get_null().map(|v| v.into_unknown()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
// Wait for NAPI-RS External::into_unknown to be stabilized
|
||||
pub fn get_js_unknown_from_value(env: Env, value: Value) -> Result<JsUnknown> {
|
||||
match value {
|
||||
Value::Any(any) => get_js_unknown_from_any(env, any),
|
||||
Value::Array(array) => env
|
||||
.create_external(YArray::inner_new(array), None)
|
||||
.map(|o| o.into_unknown()),
|
||||
Value::Map(map) => env
|
||||
.create_external(YMap::inner_new(map), None)
|
||||
.map(|o| o.into_unknown()),
|
||||
Value::Text(text) => env
|
||||
.create_external(YText::inner_new(text), None)
|
||||
.map(|o| o.into_unknown()),
|
||||
_ => env.get_null().map(|v| v.into_unknown()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_any_from_js_object(object: JsObject) -> Result<Any> {
|
||||
if let Ok(length) = object.get_array_length() {
|
||||
let mut array = Vec::with_capacity(length as usize);
|
||||
for i in 0..length {
|
||||
if let Ok(value) = object.get_element::<JsUnknown>(i) {
|
||||
array.push(get_any_from_js_unknown(value)?);
|
||||
}
|
||||
}
|
||||
Ok(Any::Array(array))
|
||||
} else {
|
||||
let mut map = AHashMap::new();
|
||||
let keys = object.get_property_names()?;
|
||||
if let Ok(length) = keys.get_array_length() {
|
||||
for i in 0..length {
|
||||
if let Ok((obj, key)) = keys.get_element::<JsUnknown>(i).and_then(|o| {
|
||||
o.coerce_to_string().and_then(|obj| {
|
||||
obj
|
||||
.into_utf8()
|
||||
.and_then(|s| s.as_str().map(|s| (obj, s.to_string())))
|
||||
})
|
||||
}) {
|
||||
if let Ok(value) = object.get_property::<_, JsUnknown>(obj) {
|
||||
println!("key: {}", key);
|
||||
map.insert(key, get_any_from_js_unknown(value)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Any::Object(map))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_any_from_js_unknown(js_unknown: JsUnknown) -> Result<Any> {
|
||||
match js_unknown.get_type()? {
|
||||
ValueType::Undefined | ValueType::Null => Ok(Any::Null),
|
||||
ValueType::Boolean => Ok(
|
||||
js_unknown
|
||||
.coerce_to_bool()
|
||||
.and_then(|v| v.get_value())?
|
||||
.into(),
|
||||
),
|
||||
ValueType::Number => Ok(
|
||||
js_unknown
|
||||
.coerce_to_number()
|
||||
.and_then(|v| v.get_double())
|
||||
.map(|v| v.into())?,
|
||||
),
|
||||
ValueType::String => Ok(
|
||||
js_unknown
|
||||
.coerce_to_string()
|
||||
.and_then(|v| v.into_utf8())
|
||||
.and_then(|s| s.as_str().map(|s| s.to_string()))?
|
||||
.into(),
|
||||
),
|
||||
ValueType::Object => {
|
||||
if let Ok(object) = js_unknown.coerce_to_object() {
|
||||
get_any_from_js_object(object)
|
||||
} else {
|
||||
Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
"Failed to coerce value to object",
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
"Failed to coerce value to any",
|
||||
)),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user