nushell/crates/nu_plugin_from_bson/src/from_bson.rs
Jonathan Turner d8594a62c2
Add wasm support (#2199)
* Working towards a PoC for wasm

* Move bson and sqlite to plugins

* proof of concept now working

* tests are green

* Add CI test for --no-default-features

* Fix some tests

* Fix clippy and windows build

* More fixes

* Fix the windows build

* Fix the windows test
2020-07-18 13:59:23 +12:00

201 lines
7 KiB
Rust

use bigdecimal::BigDecimal;
use bson::{decode_document, spec::BinarySubtype, Bson};
use nu_errors::{ExpectedRange, ShellError};
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::{SpannedItem, Tag};
use std::str::FromStr;
#[derive(Default)]
pub struct FromBSON {
pub state: Vec<u8>,
pub name_tag: Tag,
}
impl FromBSON {
pub fn new() -> FromBSON {
FromBSON {
state: vec![],
name_tag: Tag::unknown(),
}
}
}
fn bson_array(input: &[Bson], tag: Tag) -> Result<Vec<Value>, ShellError> {
let mut out = vec![];
for value in input {
out.push(convert_bson_value_to_nu_value(value, &tag)?);
}
Ok(out)
}
fn convert_bson_value_to_nu_value(v: &Bson, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into();
let span = tag.span;
Ok(match v {
Bson::FloatingPoint(n) => UntaggedValue::Primitive(Primitive::from(*n)).into_value(&tag),
Bson::String(s) => {
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
}
Bson::Array(a) => UntaggedValue::Table(bson_array(a, tag.clone())?).into_value(&tag),
Bson::Document(doc) => {
let mut collected = TaggedDictBuilder::new(tag.clone());
for (k, v) in doc.iter() {
collected.insert_value(k.clone(), convert_bson_value_to_nu_value(v, &tag)?);
}
collected.into_value()
}
Bson::Boolean(b) => UntaggedValue::Primitive(Primitive::Boolean(*b)).into_value(&tag),
Bson::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
Bson::RegExp(r, opts) => {
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_value(
"$regex".to_string(),
UntaggedValue::Primitive(Primitive::String(String::from(r))).into_value(&tag),
);
collected.insert_value(
"$options".to_string(),
UntaggedValue::Primitive(Primitive::String(String::from(opts))).into_value(&tag),
);
collected.into_value()
}
Bson::I32(n) => UntaggedValue::int(*n).into_value(&tag),
Bson::I64(n) => UntaggedValue::int(*n).into_value(&tag),
Bson::Decimal128(n) => {
// TODO: this really isn't great, and we should update this to do a higher
// fidelity translation
let decimal = BigDecimal::from_str(&format!("{}", n)).map_err(|_| {
ShellError::range_error(
ExpectedRange::BigDecimal,
&n.spanned(span),
"converting BSON Decimal128 to BigDecimal".to_owned(),
)
})?;
UntaggedValue::Primitive(Primitive::Decimal(decimal)).into_value(&tag)
}
Bson::JavaScriptCode(js) => {
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_value(
"$javascript".to_string(),
UntaggedValue::Primitive(Primitive::String(String::from(js))).into_value(&tag),
);
collected.into_value()
}
Bson::JavaScriptCodeWithScope(js, doc) => {
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_value(
"$javascript".to_string(),
UntaggedValue::Primitive(Primitive::String(String::from(js))).into_value(&tag),
);
collected.insert_value(
"$scope".to_string(),
convert_bson_value_to_nu_value(&Bson::Document(doc.to_owned()), tag)?,
);
collected.into_value()
}
Bson::TimeStamp(ts) => {
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_value(
"$timestamp".to_string(),
UntaggedValue::int(*ts).into_value(&tag),
);
collected.into_value()
}
Bson::Binary(bst, bytes) => {
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_value(
"$binary_subtype".to_string(),
match bst {
BinarySubtype::UserDefined(u) => UntaggedValue::int(*u),
_ => {
UntaggedValue::Primitive(Primitive::String(binary_subtype_to_string(*bst)))
}
}
.into_value(&tag),
);
collected.insert_value(
"$binary".to_string(),
UntaggedValue::Primitive(Primitive::Binary(bytes.to_owned())).into_value(&tag),
);
collected.into_value()
}
Bson::ObjectId(obj_id) => {
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_value(
"$object_id".to_string(),
UntaggedValue::Primitive(Primitive::String(obj_id.to_hex())).into_value(&tag),
);
collected.into_value()
}
Bson::UtcDatetime(dt) => UntaggedValue::Primitive(Primitive::Date(*dt)).into_value(&tag),
Bson::Symbol(s) => {
let mut collected = TaggedDictBuilder::new(tag.clone());
collected.insert_value(
"$symbol".to_string(),
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag),
);
collected.into_value()
}
})
}
fn binary_subtype_to_string(bst: BinarySubtype) -> String {
match bst {
BinarySubtype::Generic => "generic",
BinarySubtype::Function => "function",
BinarySubtype::BinaryOld => "binary_old",
BinarySubtype::UuidOld => "uuid_old",
BinarySubtype::Uuid => "uuid",
BinarySubtype::Md5 => "md5",
_ => unreachable!(),
}
.to_string()
}
#[derive(Debug)]
struct BytesReader {
pos: usize,
inner: Vec<u8>,
}
impl BytesReader {
fn new(bytes: Vec<u8>) -> BytesReader {
BytesReader {
pos: 0,
inner: bytes,
}
}
}
impl std::io::Read for BytesReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let src: &mut &[u8] = &mut self.inner[self.pos..].as_ref();
let diff = src.read(buf)?;
self.pos += diff;
Ok(diff)
}
}
pub fn from_bson_bytes_to_value(bytes: Vec<u8>, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let mut docs = Vec::new();
let mut b_reader = BytesReader::new(bytes);
while let Ok(v) = decode_document(&mut b_reader) {
docs.push(Bson::Document(v));
}
convert_bson_value_to_nu_value(&Bson::Array(docs), tag)
}
pub fn from_bson(bytes: Vec<u8>, name_tag: Tag) -> Result<Vec<ReturnValue>, ShellError> {
match from_bson_bytes_to_value(bytes, name_tag.clone()) {
Ok(x) => Ok(vec![ReturnSuccess::value(x)]),
Err(_) => Err(ShellError::labeled_error(
"Could not parse as BSON",
"input cannot be parsed as BSON",
name_tag,
)),
}
}