nushell/src/commands/to_bson.rs

257 lines
9.5 KiB
Rust
Raw Normal View History

2019-08-26 16:16:34 +02:00
use crate::commands::WholeStreamCommand;
use crate::object::{Dictionary, Primitive, Value};
use crate::prelude::*;
use bson::{encode_document, oid::ObjectId, spec::BinarySubtype, Bson, Document};
use std::convert::TryInto;
pub struct ToBSON;
impl WholeStreamCommand for ToBSON {
fn name(&self) -> &str {
"to-bson"
}
fn signature(&self) -> Signature {
Signature::build("to-bson")
}
fn usage(&self) -> &str {
"Convert table into .bson text."
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
to_bson(args, registry)
}
2019-09-04 03:50:23 +02:00
fn is_binary(&self) -> bool {
true
}
2019-08-26 16:16:34 +02:00
}
pub fn value_to_bson_value(v: &Tagged<Value>) -> Result<Bson, ShellError> {
Ok(match &v.item {
2019-08-26 16:16:34 +02:00
Value::Primitive(Primitive::Boolean(b)) => Bson::Boolean(*b),
// FIXME: What about really big decimals?
Value::Primitive(Primitive::Bytes(decimal)) => Bson::FloatingPoint(
(decimal)
.to_f64()
.expect("Unimplemented BUG: What about big decimals?"),
),
2019-08-26 16:16:34 +02:00
Value::Primitive(Primitive::Date(d)) => Bson::UtcDatetime(*d),
Value::Primitive(Primitive::EndOfStream) => Bson::Null,
Value::Primitive(Primitive::BeginningOfStream) => Bson::Null,
Value::Primitive(Primitive::Decimal(d)) => Bson::FloatingPoint(d.to_f64().unwrap()),
Value::Primitive(Primitive::Int(i)) => {
Bson::I64(i.tagged(v.tag).coerce_into("converting to BSON")?)
}
2019-08-26 16:16:34 +02:00
Value::Primitive(Primitive::Nothing) => Bson::Null,
Value::Primitive(Primitive::String(s)) => Bson::String(s.clone()),
Value::Primitive(Primitive::Path(s)) => Bson::String(s.display().to_string()),
Value::List(l) => Bson::Array(
l.iter()
.map(|x| value_to_bson_value(x))
.collect::<Result<_, _>>()?,
),
2019-08-26 16:16:34 +02:00
Value::Block(_) => Bson::Null,
Value::Binary(b) => Bson::Binary(BinarySubtype::Generic, b.clone()),
Value::Object(o) => object_value_to_bson(o)?,
})
2019-08-26 16:16:34 +02:00
}
// object_value_to_bson handles all Objects, even those that correspond to special
// types (things like regex or javascript code).
fn object_value_to_bson(o: &Dictionary) -> Result<Bson, ShellError> {
2019-08-26 16:16:34 +02:00
let mut it = o.entries.iter();
if it.len() > 2 {
return generic_object_value_to_bson(o);
}
match it.next() {
Some((regex, tagged_regex_value)) if regex == "$regex" => match it.next() {
Some((options, tagged_opts_value)) if options == "$options" => {
let r: Result<String, _> = tagged_regex_value.try_into();
let opts: Result<String, _> = tagged_opts_value.try_into();
if r.is_err() || opts.is_err() {
generic_object_value_to_bson(o)
} else {
Ok(Bson::RegExp(r.unwrap(), opts.unwrap()))
2019-08-26 16:16:34 +02:00
}
}
_ => generic_object_value_to_bson(o),
},
Some((javascript, tagged_javascript_value)) if javascript == "$javascript" => {
match it.next() {
Some((scope, tagged_scope_value)) if scope == "$scope" => {
let js: Result<String, _> = tagged_javascript_value.try_into();
let s: Result<&Dictionary, _> = tagged_scope_value.try_into();
if js.is_err() || s.is_err() {
generic_object_value_to_bson(o)
} else {
if let Bson::Document(doc) = object_value_to_bson(s.unwrap())? {
Ok(Bson::JavaScriptCodeWithScope(js.unwrap(), doc))
2019-08-26 16:16:34 +02:00
} else {
generic_object_value_to_bson(o)
}
}
}
None => {
let js: Result<String, _> = tagged_javascript_value.try_into();
match js {
Err(_) => generic_object_value_to_bson(o),
Ok(v) => Ok(Bson::JavaScriptCode(v)),
2019-08-26 16:16:34 +02:00
}
}
_ => generic_object_value_to_bson(o),
}
}
Some((timestamp, tagged_timestamp_value)) if timestamp == "$timestamp" => {
let ts: Result<i64, _> = tagged_timestamp_value.try_into();
if ts.is_err() {
generic_object_value_to_bson(o)
} else {
Ok(Bson::TimeStamp(ts.unwrap()))
2019-08-26 16:16:34 +02:00
}
}
Some((binary_subtype, tagged_binary_subtype_value))
if binary_subtype == "$binary_subtype" =>
{
match it.next() {
Some((binary, tagged_bin_value)) if binary == "$binary" => {
let bst = get_binary_subtype(tagged_binary_subtype_value);
let bin: Result<Vec<u8>, _> = tagged_bin_value.try_into();
match bst {
Err(_) => generic_object_value_to_bson(o),
Ok(v) => Ok(Bson::Binary(v, bin.unwrap())),
2019-08-26 16:16:34 +02:00
}
}
_ => generic_object_value_to_bson(o),
}
}
Some((object_id, tagged_object_id_value)) if object_id == "$object_id" => {
let obj_id: Result<String, _> = tagged_object_id_value.try_into();
if obj_id.is_err() {
generic_object_value_to_bson(o)
} else {
let obj_id = ObjectId::with_string(&obj_id.unwrap());
if obj_id.is_err() {
generic_object_value_to_bson(o)
} else {
Ok(Bson::ObjectId(obj_id.unwrap()))
2019-08-26 16:16:34 +02:00
}
}
}
Some((symbol, tagged_symbol_value)) if symbol == "$symbol" => {
let sym: Result<String, _> = tagged_symbol_value.try_into();
if sym.is_err() {
generic_object_value_to_bson(o)
} else {
Ok(Bson::Symbol(sym.unwrap()))
2019-08-26 16:16:34 +02:00
}
}
_ => generic_object_value_to_bson(o),
}
}
fn get_binary_subtype<'a>(tagged_value: &'a Tagged<Value>) -> Result<BinarySubtype, ShellError> {
2019-08-26 16:16:34 +02:00
match tagged_value.item() {
Value::Primitive(Primitive::String(s)) => Ok(match s.as_ref() {
2019-08-26 16:16:34 +02:00
"generic" => BinarySubtype::Generic,
"function" => BinarySubtype::Function,
"binary_old" => BinarySubtype::BinaryOld,
"uuid_old" => BinarySubtype::UuidOld,
"uuid" => BinarySubtype::Uuid,
"md5" => BinarySubtype::Md5,
_ => unreachable!(),
}),
Value::Primitive(Primitive::Int(i)) => Ok(BinarySubtype::UserDefined(
i.tagged(tagged_value.tag)
.coerce_into("converting to BSON binary subtype")?,
)),
_ => Err(ShellError::type_error(
"bson binary",
tagged_value.tagged_type_name(),
)),
2019-08-26 16:16:34 +02:00
}
}
// generic_object_value_bson handles any Object that does not
// correspond to a special bson type (things like regex or javascript code).
fn generic_object_value_to_bson(o: &Dictionary) -> Result<Bson, ShellError> {
2019-08-26 16:16:34 +02:00
let mut doc = Document::new();
for (k, v) in o.entries.iter() {
doc.insert(k.clone(), value_to_bson_value(v)?);
2019-08-26 16:16:34 +02:00
}
Ok(Bson::Document(doc))
2019-08-26 16:16:34 +02:00
}
fn shell_encode_document(
writer: &mut Vec<u8>,
doc: Document,
span: Span,
) -> Result<(), ShellError> {
match encode_document(writer, &doc) {
Err(e) => Err(ShellError::labeled_error(
format!("Failed to encode document due to: {:?}", e),
"requires BSON-compatible document",
span,
)),
_ => Ok(()),
}
}
fn bson_value_to_bytes(bson: Bson, span: Span) -> Result<Vec<u8>, ShellError> {
let mut out = Vec::new();
match bson {
Bson::Array(a) => {
for v in a.into_iter() {
match v {
Bson::Document(d) => shell_encode_document(&mut out, d, span)?,
_ => {
return Err(ShellError::labeled_error(
format!("All top level values must be Documents, got {:?}", v),
"requires BSON-compatible document",
span,
))
}
}
}
}
Bson::Document(d) => shell_encode_document(&mut out, d, span)?,
_ => {
return Err(ShellError::labeled_error(
format!("All top level values must be Documents, got {:?}", bson),
"requires BSON-compatible document",
span,
))
}
}
Ok(out)
}
fn to_bson(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once(registry)?;
let name_span = args.name_span();
let out = args.input;
Ok(out
.values
.map(
move |a| match bson_value_to_bytes(value_to_bson_value(&a)?, name_span) {
2019-08-26 16:16:34 +02:00
Ok(x) => ReturnSuccess::value(Value::Binary(x).simple_spanned(name_span)),
_ => Err(ShellError::labeled_error_with_secondary(
"Expected an object with BSON-compatible structure from pipeline",
"requires BSON-compatible input: Must be Array or Object",
name_span,
format!("{} originates from here", a.item.type_name()),
a.span(),
)),
},
)
.to_output_stream())
}