From af02c8f6ea367decb7edb97a8d4ffd35cde3c139 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 26 Oct 2021 20:50:39 +0100 Subject: [PATCH] call info encoder --- crates/nu-plugin/schema/value.capnp | 48 ++- crates/nu-plugin/src/lib.rs | 205 +------------ crates/nu-plugin/src/plugin.rs | 4 + crates/nu-plugin/src/serializers/call.rs | 296 +++++++++++++++++++ crates/nu-plugin/src/serializers/callinfo.rs | 83 ++++++ crates/nu-plugin/src/serializers/mod.rs | 3 + crates/nu-plugin/src/serializers/value.rs | 260 ++++++++++++++++ crates/nu-protocol/src/shell_error.rs | 8 + 8 files changed, 700 insertions(+), 207 deletions(-) create mode 100644 crates/nu-plugin/src/plugin.rs create mode 100644 crates/nu-plugin/src/serializers/call.rs create mode 100644 crates/nu-plugin/src/serializers/callinfo.rs create mode 100644 crates/nu-plugin/src/serializers/mod.rs create mode 100644 crates/nu-plugin/src/serializers/value.rs diff --git a/crates/nu-plugin/schema/value.capnp b/crates/nu-plugin/schema/value.capnp index 0a3cefb77..36077f25a 100644 --- a/crates/nu-plugin/schema/value.capnp +++ b/crates/nu-plugin/schema/value.capnp @@ -1,13 +1,29 @@ @0xb299d30dc02d72bc; +# Generic structs used as helpers for the encoding +struct Option(Value) { + union { + none @0 :Void; + some @1 :Value; + } +} + +struct Map(Key, Value) { + struct Entry { + key @0 :Key; + value @1 :Value; + } + entries @0 :List(Entry); +} + +struct Span { + start @0 :UInt64; + end @1 :UInt64; +} + struct Value { span @0: Span; - struct Span { - start @0 :UInt64; - end @1 :UInt64; - } - union { void @1 :Void; bool @2 :Bool; @@ -17,3 +33,25 @@ struct Value { list @6 :List(Value); } } + +struct Expression { + union { + garbage @0 :Void; + bool @1 :Bool; + int @2 :Int64; + float @3 :Float64; + string @4 :Text; + list @5 :List(Expression); + } +} + +struct Call { + head @0: Span; + positional @1 :List(Expression); + named @2 :Map(Text, Option(Expression)); +} + +struct CallInfo { + call @0: Call; + input @1: Value; +} diff --git a/crates/nu-plugin/src/lib.rs b/crates/nu-plugin/src/lib.rs index 2a296e6ad..0588db667 100644 --- a/crates/nu-plugin/src/lib.rs +++ b/crates/nu-plugin/src/lib.rs @@ -1,205 +1,6 @@ +pub mod plugin; +pub mod serializers; + pub mod value_capnp { include!(concat!(env!("OUT_DIR"), "/value_capnp.rs")); } - -pub mod plugin_value { - - use crate::value_capnp::value; - use capnp::serialize_packed; - use nu_protocol::{Span, Value}; - - pub fn serialize_message(value: &Value, writer: &mut impl std::io::Write) -> capnp::Result<()> { - let mut message = ::capnp::message::Builder::new_default(); - - let mut builder = message.init_root::(); - - let value_span = serialize_value(value, builder.reborrow()); - let mut span = builder.reborrow().init_span(); - span.set_start(value_span.start as u64); - span.set_end(value_span.end as u64); - - serialize_packed::write_message(writer, &message) - } - - fn serialize_value(value: &Value, mut builder: value::Builder) -> Span { - match value { - Value::Nothing { span } => { - builder.set_void(()); - *span - } - Value::Bool { val, span } => { - builder.set_bool(*val); - *span - } - Value::Int { val, span } => { - builder.set_int(*val); - *span - } - Value::Float { val, span } => { - builder.set_float(*val); - *span - } - Value::String { val, span } => { - builder.set_string(&val); - *span - } - Value::List { vals, span } => { - let mut list_builder = builder.reborrow().init_list(vals.len() as u32); - for (index, value) in vals.iter().enumerate() { - let inner_builder = list_builder.reborrow().get(index as u32); - serialize_value(value, inner_builder); - } - - *span - } - _ => Span::unknown(), - } - } - - pub fn deserialize_message(reader: &mut impl std::io::BufRead) -> Value { - let message_reader = - serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); - - let plugin_value = message_reader.get_root::().unwrap(); - - let span_reader = plugin_value.get_span().unwrap(); - let span = Span { - start: span_reader.get_start() as usize, - end: span_reader.get_end() as usize, - }; - - deserialize_value(span, plugin_value.reborrow()) - } - - fn deserialize_value(span: Span, reader: value::Reader) -> Value { - match reader.which() { - Ok(value::Void(())) => Value::Nothing { span }, - Ok(value::Bool(val)) => Value::Bool { val, span }, - Ok(value::Int(val)) => Value::Int { val, span }, - Ok(value::Float(val)) => Value::Float { val, span }, - Ok(value::String(val)) => Value::String { - val: val.unwrap().to_string(), - span, - }, - Ok(value::List(vals)) => { - let values = vals.expect("something"); - - let values_list = values - .iter() - .map(|value| match value.which() { - Ok(value::Void(())) => Value::Nothing { span }, - Ok(value::Bool(val)) => Value::Bool { val, span }, - Ok(value::Int(val)) => Value::Int { val, span }, - Ok(value::Float(val)) => Value::Float { val, span }, - Ok(value::String(val)) => Value::String { - val: val.unwrap().to_string(), - span, - }, - Ok(value::List(_)) => Value::Nothing { span }, - Err(capnp::NotInSchema(_)) => Value::Nothing { - span: Span::unknown(), - }, - }) - .collect::>(); - - Value::List { - vals: values_list, - span, - } - } - Err(capnp::NotInSchema(_)) => Value::Nothing { - span: Span::unknown(), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use nu_protocol::{Span, Value}; - - #[test] - fn value_round_trip() { - let values = [ - Value::Bool { - val: false, - span: Span { start: 1, end: 20 }, - }, - Value::Int { - val: 10, - span: Span { start: 2, end: 30 }, - }, - Value::Float { - val: 10.0, - span: Span { start: 3, end: 40 }, - }, - Value::String { - val: "a string".into(), - span: Span { start: 4, end: 50 }, - }, - ]; - - for value in values { - let mut buffer: Vec = Vec::new(); - plugin_value::serialize_message(&value, &mut buffer).expect("unable to write message"); - let returned_value = plugin_value::deserialize_message(&mut buffer.as_slice()); - - assert_eq!(value, returned_value) - } - } - - #[test] - fn value_nothing_round_trip() { - // Since nothing doesn't implement PartialOrd, we only compare that the - // encoded and decoded spans are correct - let value = Value::Nothing { - span: Span { start: 0, end: 10 }, - }; - - let mut buffer: Vec = Vec::new(); - plugin_value::serialize_message(&value, &mut buffer).expect("unable to write message"); - let returned_value = plugin_value::deserialize_message(&mut buffer.as_slice()); - - assert_eq!( - value.span().expect("span"), - returned_value.span().expect("span") - ) - } - - #[test] - fn list_round_trip() { - let values = vec![ - Value::Bool { - val: false, - span: Span { start: 1, end: 20 }, - }, - Value::Int { - val: 10, - span: Span { start: 2, end: 30 }, - }, - Value::Float { - val: 10.0, - span: Span { start: 3, end: 40 }, - }, - Value::String { - val: "a string".into(), - span: Span { start: 4, end: 50 }, - }, - ]; - - let value = Value::List { - vals: values, - span: Span { start: 1, end: 10 }, - }; - - let mut buffer: Vec = Vec::new(); - plugin_value::serialize_message(&value, &mut buffer).expect("unable to write message"); - let returned_value = plugin_value::deserialize_message(&mut buffer.as_slice()); - - assert_eq!( - value.span().expect("span"), - returned_value.span().expect("span") - ) - } -} diff --git a/crates/nu-plugin/src/plugin.rs b/crates/nu-plugin/src/plugin.rs new file mode 100644 index 000000000..2026ed938 --- /dev/null +++ b/crates/nu-plugin/src/plugin.rs @@ -0,0 +1,4 @@ +//use nu_protocol::{ShellError, Value}; + +/// The `Plugin` trait defines the API which plugins may use to "hook" into nushell. +pub trait Plugin {} diff --git a/crates/nu-plugin/src/serializers/call.rs b/crates/nu-plugin/src/serializers/call.rs new file mode 100644 index 000000000..7c9e4c903 --- /dev/null +++ b/crates/nu-plugin/src/serializers/call.rs @@ -0,0 +1,296 @@ +use crate::value_capnp::{call, expression, option}; +use capnp::serialize_packed; +use nu_protocol::{ + ast::{Call, Expr, Expression}, + ShellError, Span, Spanned, Type, +}; + +pub fn write_buffer(call: &Call, writer: &mut impl std::io::Write) -> Result<(), ShellError> { + let mut message = ::capnp::message::Builder::new_default(); + + let builder = message.init_root::(); + serialize_call(call, builder)?; + + serialize_packed::write_message(writer, &message) + .map_err(|e| ShellError::EncodingError(e.to_string())) +} + +pub(crate) fn serialize_call(call: &Call, mut builder: call::Builder) -> Result<(), ShellError> { + let mut head = builder.reborrow().init_head(); + head.set_start(call.head.start as u64); + head.set_end(call.head.end as u64); + + serialize_positional(&call.positional, builder.reborrow()); + serialize_named(&call.named, builder)?; + + Ok(()) +} + +fn serialize_positional(positional: &[Expression], mut builder: call::Builder) { + let mut positional_builder = builder.reborrow().init_positional(positional.len() as u32); + + for (index, expression) in positional.iter().enumerate() { + serialize_expression(expression, positional_builder.reborrow().get(index as u32)) + } +} + +fn serialize_named( + named: &[(Spanned, Option)], + mut builder: call::Builder, +) -> Result<(), ShellError> { + let mut named_builder = builder + .reborrow() + .init_named() + .init_entries(named.len() as u32); + + for (index, (key, expression)) in named.iter().enumerate() { + let mut entry_builder = named_builder.reborrow().get(index as u32); + entry_builder + .reborrow() + .set_key(key.item.as_str()) + .map_err(|e| ShellError::EncodingError(e.to_string()))?; + + let mut value_builder = entry_builder.init_value(); + match expression { + None => value_builder.set_none(()), + Some(expr) => { + let expression_builder = value_builder.init_some(); + serialize_expression(expr, expression_builder); + } + } + } + + Ok(()) +} + +fn serialize_expression(expression: &Expression, mut builder: expression::Builder) { + match &expression.expr { + Expr::Garbage => builder.set_garbage(()), + Expr::Bool(val) => builder.set_bool(*val), + Expr::Int(val) => builder.set_int(*val), + Expr::Float(val) => builder.set_float(*val), + Expr::String(val) => builder.set_string(&val), + Expr::List(values) => { + let mut list_builder = builder.reborrow().init_list(values.len() as u32); + for (index, expression) in values.iter().enumerate() { + let inner_builder = list_builder.reborrow().get(index as u32); + serialize_expression(expression, inner_builder) + } + } + _ => { + // If there is the need to pass other type of argument to the plugin + // we have to define the encoding for that parameter in this match + } + } +} + +pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + deserialize_call(reader) +} + +pub(crate) fn deserialize_call(reader: call::Reader) -> Result { + let head_reader = reader + .get_head() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + let head = Span { + start: head_reader.get_start() as usize, + end: head_reader.get_end() as usize, + }; + + let positional = deserialize_positionals(head, reader)?; + let named = deserialize_named(head, reader)?; + + Ok(Call { + decl_id: 0, + head, + positional, + named, + }) +} + +fn deserialize_positionals( + span: Span, + reader: call::Reader, +) -> Result, ShellError> { + let positional_reader = reader + .get_positional() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + positional_reader + .iter() + .map(|expression_reader| deserialize_expression(span, expression_reader)) + .collect() +} + +fn deserialize_named( + span: Span, + reader: call::Reader, +) -> Result, Option)>, ShellError> { + let named_reader = reader + .get_named() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + let entries_list = named_reader + .get_entries() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + let mut entries: Vec<(Spanned, Option)> = + Vec::with_capacity(entries_list.len() as usize); + + for entry_reader in entries_list { + let item = entry_reader + .get_key() + .map_err(|e| ShellError::DecodingError(e.to_string()))? + .to_string(); + + let value_reader = entry_reader + .get_value() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + let value = match value_reader.which() { + Ok(option::None(())) => None, + Ok(option::Some(expression_reader)) => { + let expression_reader = + expression_reader.map_err(|e| ShellError::DecodingError(e.to_string()))?; + + let expression = deserialize_expression(span, expression_reader) + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + Some(expression) + } + Err(capnp::NotInSchema(_)) => None, + }; + + let key = Spanned { item, span }; + + entries.push((key, value)) + } + + Ok(entries) +} + +fn deserialize_expression( + span: Span, + reader: expression::Reader, +) -> Result { + let expr = match reader.which() { + Ok(expression::Garbage(())) => Expr::Garbage, + Ok(expression::Bool(val)) => Expr::Bool(val), + Ok(expression::Int(val)) => Expr::Int(val), + Ok(expression::Float(val)) => Expr::Float(val), + Ok(expression::String(val)) => { + let string = val + .map_err(|e| ShellError::DecodingError(e.to_string()))? + .to_string(); + + Expr::String(string) + } + Ok(expression::List(values)) => { + let values = values.map_err(|e| ShellError::DecodingError(e.to_string()))?; + + let values_list = values + .iter() + .map(|inner_reader| deserialize_expression(span, inner_reader)) + .collect::, ShellError>>()?; + + Expr::List(values_list) + } + Err(capnp::NotInSchema(_)) => Expr::Garbage, + }; + + Ok(Expression { + expr, + span, + ty: Type::Unknown, + custom_completion: None, + }) +} + +#[cfg(test)] +mod tests { + use core::panic; + + use super::*; + use nu_protocol::{ + ast::{Call, Expr, Expression}, + Span, Spanned, + }; + + fn compare_expressions(lhs: &Expression, rhs: &Expression) { + match (&lhs.expr, &rhs.expr) { + (Expr::Bool(a), Expr::Bool(b)) => assert_eq!(a, b), + (Expr::Int(a), Expr::Int(b)) => assert_eq!(a, b), + (Expr::Float(a), Expr::Float(b)) => assert_eq!(a, b), + (Expr::String(a), Expr::String(b)) => assert_eq!(a, b), + _ => panic!("not matching values"), + } + } + + #[test] + fn call_round_trip() { + let call = Call { + decl_id: 1, + head: Span { start: 0, end: 10 }, + positional: vec![ + Expression { + expr: Expr::Float(1.0), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }, + Expression { + expr: Expr::String("something".into()), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }, + ], + named: vec![( + Spanned { + item: "name".to_string(), + span: Span { start: 0, end: 10 }, + }, + Some(Expression { + expr: Expr::Float(1.0), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }), + )], + }; + + let mut buffer: Vec = Vec::new(); + write_buffer(&call, &mut buffer).expect("unable to serialize message"); + let returned_call = read_buffer(&mut buffer.as_slice()).expect("unable to read buffer"); + + assert_eq!(call.head, returned_call.head); + assert_eq!(call.positional.len(), returned_call.positional.len()); + + call.positional + .iter() + .zip(returned_call.positional.iter()) + .for_each(|(lhs, rhs)| compare_expressions(lhs, rhs)); + + call.named + .iter() + .zip(returned_call.named.iter()) + .for_each(|(lhs, rhs)| { + // Comparing the keys + assert_eq!(lhs.0.item, rhs.0.item); + + match (&lhs.1, &rhs.1) { + (None, None) => {} + (Some(a), Some(b)) => compare_expressions(a, b), + _ => panic!("not matching values"), + } + }); + } +} diff --git a/crates/nu-plugin/src/serializers/callinfo.rs b/crates/nu-plugin/src/serializers/callinfo.rs new file mode 100644 index 000000000..97bc1ae76 --- /dev/null +++ b/crates/nu-plugin/src/serializers/callinfo.rs @@ -0,0 +1,83 @@ +use super::{call, value}; +use crate::value_capnp::call_info; +use capnp::serialize_packed; +use nu_protocol::{ast::Call, ShellError, Value}; + +pub fn write_buffer( + call: &Call, + input: &Value, + writer: &mut impl std::io::Write, +) -> Result<(), ShellError> { + let mut message = ::capnp::message::Builder::new_default(); + + let mut builder = message.init_root::(); + let value_builder = builder + .reborrow() + .get_input() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + value::serialize_value(input, value_builder); + + let call_builder = builder + .reborrow() + .get_call() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + call::serialize_call(call, call_builder) + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + serialize_packed::write_message(writer, &message) + .map_err(|e| ShellError::EncodingError(e.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + use nu_protocol::{ + ast::{Call, Expr, Expression}, + Span, Spanned, Value, + }; + + #[test] + fn callinfo_round_trip() { + let value = Value::Bool { + val: false, + span: Span { start: 1, end: 20 }, + }; + + let call = Call { + decl_id: 1, + head: Span { start: 0, end: 10 }, + positional: vec![ + Expression { + expr: Expr::Float(1.0), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }, + Expression { + expr: Expr::String("something".into()), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }, + ], + named: vec![( + Spanned { + item: "name".to_string(), + span: Span { start: 0, end: 10 }, + }, + Some(Expression { + expr: Expr::Float(1.0), + span: Span { start: 0, end: 10 }, + ty: nu_protocol::Type::Float, + custom_completion: None, + }), + )], + }; + + let mut buffer: Vec = Vec::new(); + write_buffer(&call, &value, &mut buffer).expect("unable to serialize message"); + println!("{:?}", buffer); + } +} diff --git a/crates/nu-plugin/src/serializers/mod.rs b/crates/nu-plugin/src/serializers/mod.rs new file mode 100644 index 000000000..3c4ebb40b --- /dev/null +++ b/crates/nu-plugin/src/serializers/mod.rs @@ -0,0 +1,3 @@ +pub mod call; +pub mod callinfo; +pub mod value; diff --git a/crates/nu-plugin/src/serializers/value.rs b/crates/nu-plugin/src/serializers/value.rs new file mode 100644 index 000000000..c66fe3919 --- /dev/null +++ b/crates/nu-plugin/src/serializers/value.rs @@ -0,0 +1,260 @@ +use crate::value_capnp::value; +use capnp::serialize_packed; +use nu_protocol::{ShellError, Span, Value}; + +pub fn write_buffer(value: &Value, writer: &mut impl std::io::Write) -> Result<(), ShellError> { + let mut message = ::capnp::message::Builder::new_default(); + + let mut builder = message.init_root::(); + + let value_span = serialize_value(value, builder.reborrow()); + let mut span = builder.reborrow().init_span(); + span.set_start(value_span.start as u64); + span.set_end(value_span.end as u64); + + serialize_packed::write_message(writer, &message) + .map_err(|e| ShellError::EncodingError(e.to_string())) +} + +pub(crate) fn serialize_value(value: &Value, mut builder: value::Builder) -> Span { + match value { + Value::Nothing { span } => { + builder.set_void(()); + *span + } + Value::Bool { val, span } => { + builder.set_bool(*val); + *span + } + Value::Int { val, span } => { + builder.set_int(*val); + *span + } + Value::Float { val, span } => { + builder.set_float(*val); + *span + } + Value::String { val, span } => { + builder.set_string(&val); + *span + } + Value::List { vals, span } => { + let mut list_builder = builder.reborrow().init_list(vals.len() as u32); + for (index, value) in vals.iter().enumerate() { + let inner_builder = list_builder.reborrow().get(index as u32); + serialize_value(value, inner_builder); + } + + *span + } + _ => { + // If there is the need to pass other type of value to the plugin + // we have to define the encoding for that object in this match + Span::unknown() + } + } +} + +pub fn read_buffer(reader: &mut impl std::io::BufRead) -> Result { + let message_reader = + serialize_packed::read_message(reader, ::capnp::message::ReaderOptions::new()).unwrap(); + + let reader = message_reader + .get_root::() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + deserialize_value(reader.reborrow()) +} + +pub(crate) fn deserialize_value(reader: value::Reader) -> Result { + let span_reader = reader + .get_span() + .map_err(|e| ShellError::DecodingError(e.to_string()))?; + + let span = Span { + start: span_reader.get_start() as usize, + end: span_reader.get_end() as usize, + }; + + match reader.which() { + Ok(value::Void(())) => Ok(Value::Nothing { span }), + Ok(value::Bool(val)) => Ok(Value::Bool { val, span }), + Ok(value::Int(val)) => Ok(Value::Int { val, span }), + Ok(value::Float(val)) => Ok(Value::Float { val, span }), + Ok(value::String(val)) => { + let string = val + .map_err(|e| ShellError::DecodingError(e.to_string()))? + .to_string(); + Ok(Value::String { val: string, span }) + } + Ok(value::List(vals)) => { + let values = vals.map_err(|e| ShellError::DecodingError(e.to_string()))?; + + let values_list = values + .iter() + .map(|inner_reader| deserialize_value(inner_reader)) + .collect::, ShellError>>()?; + + Ok(Value::List { + vals: values_list, + span, + }) + } + Err(capnp::NotInSchema(_)) => Ok(Value::Nothing { + span: Span::unknown(), + }), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nu_protocol::{Span, Value}; + + #[test] + fn value_round_trip() { + let values = [ + Value::Bool { + val: false, + span: Span { start: 1, end: 20 }, + }, + Value::Int { + val: 10, + span: Span { start: 2, end: 30 }, + }, + Value::Float { + val: 10.0, + span: Span { start: 3, end: 40 }, + }, + Value::String { + val: "a string".into(), + span: Span { start: 4, end: 50 }, + }, + ]; + + for value in values { + let mut buffer: Vec = Vec::new(); + write_buffer(&value, &mut buffer).expect("unable to serialize message"); + let returned_value = + read_buffer(&mut buffer.as_slice()).expect("unable to deserialize message"); + + assert_eq!(value, returned_value) + } + } + + #[test] + fn value_nothing_round_trip() { + // Since nothing doesn't implement PartialOrd, we only compare that the + // encoded and decoded spans are correct + let value = Value::Nothing { + span: Span { start: 0, end: 10 }, + }; + + let mut buffer: Vec = Vec::new(); + write_buffer(&value, &mut buffer).expect("unable to serialize message"); + let returned_value = + read_buffer(&mut buffer.as_slice()).expect("unable to deserialize message"); + + assert_eq!( + value.span().expect("span"), + returned_value.span().expect("span") + ) + } + + #[test] + fn list_round_trip() { + let values = vec![ + Value::Bool { + val: false, + span: Span { start: 1, end: 20 }, + }, + Value::Int { + val: 10, + span: Span { start: 2, end: 30 }, + }, + Value::Float { + val: 10.0, + span: Span { start: 3, end: 40 }, + }, + Value::String { + val: "a string".into(), + span: Span { start: 4, end: 50 }, + }, + ]; + + let value = Value::List { + vals: values, + span: Span { start: 1, end: 10 }, + }; + + let mut buffer: Vec = Vec::new(); + write_buffer(&value, &mut buffer).expect("unable to serialize message"); + let returned_value = + read_buffer(&mut buffer.as_slice()).expect("unable to deserialize message"); + + assert_eq!( + value.span().expect("span"), + returned_value.span().expect("span") + ) + } + + #[test] + fn nested_list_round_trip() { + let inner_values = vec![ + Value::Bool { + val: false, + span: Span { start: 1, end: 20 }, + }, + Value::Int { + val: 10, + span: Span { start: 2, end: 30 }, + }, + Value::Float { + val: 10.0, + span: Span { start: 3, end: 40 }, + }, + Value::String { + val: "inner string".into(), + span: Span { start: 4, end: 50 }, + }, + ]; + + let values = vec![ + Value::Bool { + val: true, + span: Span { start: 1, end: 20 }, + }, + Value::Int { + val: 66, + span: Span { start: 2, end: 30 }, + }, + Value::Float { + val: 66.6, + span: Span { start: 3, end: 40 }, + }, + Value::String { + val: "a string".into(), + span: Span { start: 4, end: 50 }, + }, + Value::List { + vals: inner_values, + span: Span { start: 5, end: 60 }, + }, + ]; + + let value = Value::List { + vals: values, + span: Span { start: 1, end: 10 }, + }; + + let mut buffer: Vec = Vec::new(); + write_buffer(&value, &mut buffer).expect("unable to serialize message"); + let returned_value = + read_buffer(&mut buffer.as_slice()).expect("unable to deserialize message"); + + assert_eq!( + value.span().expect("span"), + returned_value.span().expect("span") + ) + } +} diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index befcd029f..a98350106 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -169,6 +169,14 @@ pub enum ShellError { NoFileToBeMoved(), #[error("No file to be copied")] NoFileToBeCopied(), + + #[error("Unable to serialize message")] + #[diagnostic(code(nu::shell::EncodingError), url(docsrs))] + EncodingError(String), + + #[error("Unable to read message")] + #[diagnostic(code(nu::shell::DecodingError), url(docsrs))] + DecodingError(String), } impl From for ShellError {