surrealdb_core/fnc/script/
from.rs

1use super::classes;
2use crate::sql::array::Array;
3use crate::sql::datetime::Datetime;
4use crate::sql::object::Object;
5use crate::sql::value::Value;
6use crate::sql::Bytes;
7use crate::sql::Geometry;
8use crate::sql::Id;
9use crate::sql::Strand;
10use chrono::{TimeZone, Utc};
11use js::prelude::This;
12use js::Coerced;
13use js::Ctx;
14use js::Error;
15use js::Exception;
16use js::FromAtom;
17use js::FromJs;
18use rust_decimal::Decimal;
19
20fn check_nul(s: &str) -> Result<(), Error> {
21	if s.contains('\0') {
22		Err(Error::InvalidString(std::ffi::CString::new(s).unwrap_err()))
23	} else {
24		Ok(())
25	}
26}
27
28fn try_object_to_geom(object: &Object) -> Option<Geometry> {
29	if object.len() != 2 {
30		return None;
31	}
32
33	let Some(Value::Strand(Strand(key))) = object.get("type") else {
34		return None;
35	};
36
37	match key.as_str() {
38		"Point" => {
39			object.get("coordinates").and_then(Geometry::array_to_point).map(Geometry::Point)
40		}
41		"LineString" => {
42			object.get("coordinates").and_then(Geometry::array_to_line).map(Geometry::Line)
43		}
44		"Polygon" => {
45			object.get("coordinates").and_then(Geometry::array_to_polygon).map(Geometry::Polygon)
46		}
47		"MultiPoint" => object
48			.get("coordinates")
49			.and_then(Geometry::array_to_multipoint)
50			.map(Geometry::MultiPoint),
51		"MultiLineString" => object
52			.get("coordinates")
53			.and_then(Geometry::array_to_multiline)
54			.map(Geometry::MultiLine),
55		"MultiPolygon" => object
56			.get("coordinates")
57			.and_then(Geometry::array_to_multipolygon)
58			.map(Geometry::MultiPolygon),
59		"GeometryCollection" => {
60			let Some(Value::Array(x)) = object.get("geometries") else {
61				return None;
62			};
63
64			let mut res = Vec::with_capacity(x.len());
65
66			for x in x.iter() {
67				let Value::Geometry(x) = x else {
68					return None;
69				};
70				res.push(x.clone());
71			}
72
73			Some(Geometry::Collection(res))
74		}
75
76		_ => None,
77	}
78}
79
80impl<'js> FromJs<'js> for Value {
81	fn from_js(ctx: &Ctx<'js>, val: js::Value<'js>) -> Result<Self, Error> {
82		match val.type_of() {
83			js::Type::Undefined => Ok(Value::None),
84			js::Type::Null => Ok(Value::Null),
85			js::Type::Bool => Ok(Value::from(val.as_bool().unwrap())),
86			js::Type::Int => Ok(Value::from(val.as_int().unwrap() as f64)),
87			js::Type::Float => Ok(Value::from(val.as_float().unwrap())),
88			js::Type::String => Ok(Value::from(val.as_string().unwrap().to_string()?)),
89			js::Type::Array => {
90				let v = val.as_array().unwrap();
91				let mut x = Array::with_capacity(v.len());
92				for i in v.iter() {
93					let v = i?;
94					x.push(Value::from_js(ctx, v)?);
95				}
96				Ok(x.into())
97			}
98			js::Type::BigInt => {
99				// TODO: Optimize this if rquickjs ever supports better conversion methods.
100				let str = Coerced::<String>::from_js(ctx, val)?;
101				if let Ok(i) = str.parse::<i64>() {
102					return Ok(Value::from(i));
103				}
104				match str.parse::<Decimal>() {
105					Ok(x) => Ok(Value::from(x)),
106					Err(e) => Err(Exception::from_message(ctx.clone(), &e.to_string())?.throw()),
107				}
108			}
109			js::Type::Object | js::Type::Exception => {
110				// Extract the value as an object
111				let v = val.into_object().unwrap();
112				// Check to see if this object is an error
113				if v.is_error() {
114					let e: String = v.get(js::atom::PredefinedAtom::Message)?;
115					let (Ok(e) | Err(e)) =
116						Exception::from_message(ctx.clone(), &e).map(|x| x.throw());
117					return Err(e);
118				}
119				// Check to see if this object is a record
120				if let Some(v) = v.as_class::<classes::record::Record>() {
121					let borrow = v.borrow();
122					let v: &classes::record::Record = &borrow;
123					check_nul(&v.value.tb)?;
124					if let Id::String(s) = &v.value.id {
125						check_nul(s)?;
126					}
127					return Ok(v.value.clone().into());
128				}
129				// Check to see if this object is a duration
130				if let Some(v) = v.as_class::<classes::duration::Duration>() {
131					let borrow = v.borrow();
132					let v: &classes::duration::Duration = &borrow;
133					return match &v.value {
134						Some(v) => Ok((*v).into()),
135						None => Ok(Value::None),
136					};
137				}
138				// Check to see if this object is a uuid
139				if let Some(v) = v.as_class::<classes::uuid::Uuid>() {
140					let borrow = v.borrow();
141					let v: &classes::uuid::Uuid = &borrow;
142					return match &v.value {
143						Some(v) => Ok((*v).into()),
144						None => Ok(Value::None),
145					};
146				}
147
148				if let Some(v) = v.as_typed_array::<u8>() {
149					let Some(data) = v.as_bytes() else {
150						return Err(Error::new_from_js_message(
151							"Uint8Array",
152							"Bytes",
153							"Uint8Array data was consumed.",
154						));
155					};
156
157					return Ok(Value::Bytes(Bytes(data.to_vec())));
158				}
159
160				// Check to see if this object is a date
161				let date: js::Object = ctx.globals().get(js::atom::PredefinedAtom::Date)?;
162				if (v).is_instance_of(&date) {
163					let f: js::Function = v.get("getTime")?;
164					let m: i64 = f.call((This(v),))?;
165					let d = Utc.timestamp_millis_opt(m).unwrap();
166					return Ok(Datetime::from(d).into());
167				}
168
169				// Check to see if this object is an array
170				if let Some(v) = v.as_array() {
171					let mut x = Array::with_capacity(v.len());
172					for i in v.iter() {
173						let v = i?;
174						let v = Value::from_js(ctx, v)?;
175						x.push(v);
176					}
177					return Ok(x.into());
178				}
179
180				// Check to see if this object is a function
181				if v.as_function().is_some() {
182					return Ok(Value::None);
183				}
184				// This object is a normal object
185				let mut x = Object::default();
186				for i in v.props() {
187					let (k, v) = i?;
188					let k = String::from_atom(k)?;
189					check_nul(&k)?;
190					let v = Value::from_js(ctx, v)?;
191					x.insert(k, v);
192				}
193
194				if let Some(x) = try_object_to_geom(&x) {
195					return Ok(x.into());
196				}
197
198				Ok(x.into())
199			}
200			_ => Ok(Value::Null),
201		}
202	}
203}