diff --git a/crates/nu-plugin/src/plugin/interface/stream/tests.rs b/crates/nu-plugin/src/plugin/interface/stream/tests.rs index ecdb561b9a..2992ee2889 100644 --- a/crates/nu-plugin/src/plugin/interface/stream/tests.rs +++ b/crates/nu-plugin/src/plugin/interface/stream/tests.rs @@ -103,7 +103,8 @@ fn list_reader_recv_wrong_type() -> Result<(), ShellError> { #[test] fn reader_recv_raw_messages() -> Result<(), ShellError> { let (tx, rx) = mpsc::channel(); - let mut reader = StreamReader::new(0, rx, TestSink::default()); + let mut reader = + StreamReader::, ShellError>, _>::new(0, rx, TestSink::default()); tx.send(Ok(Some(StreamData::Raw(Ok(vec![10, 20]))))) .unwrap(); @@ -458,7 +459,7 @@ fn stream_manager_write_scenario() -> Result<(), ShellError> { let expected_values = vec![b"hello".to_vec(), b"world".to_vec(), b"test".to_vec()]; for value in &expected_values { - writable.write(Ok(value.clone()))?; + writable.write(Ok::<_, ShellError>(value.clone()))?; } // Now try signalling ack diff --git a/crates/nu-plugin/src/plugin/interface/tests.rs b/crates/nu-plugin/src/plugin/interface/tests.rs index d92a152e03..a96be38f5a 100644 --- a/crates/nu-plugin/src/plugin/interface/tests.rs +++ b/crates/nu-plugin/src/plugin/interface/tests.rs @@ -184,8 +184,14 @@ fn read_pipeline_data_external_stream() -> Result<(), ShellError> { test.add(StreamMessage::Data(14, Value::test_int(1).into())); for _ in 0..iterations { - test.add(StreamMessage::Data(12, Ok(out_pattern.clone()).into())); - test.add(StreamMessage::Data(13, Ok(err_pattern.clone()).into())); + test.add(StreamMessage::Data( + 12, + StreamData::Raw(Ok(out_pattern.clone())), + )); + test.add(StreamMessage::Data( + 13, + StreamData::Raw(Ok(err_pattern.clone())), + )); } test.add(StreamMessage::End(12)); test.add(StreamMessage::End(13)); diff --git a/crates/nu-plugin/src/protocol/mod.rs b/crates/nu-plugin/src/protocol/mod.rs index 5f2a9b8510..432961c29d 100644 --- a/crates/nu-plugin/src/protocol/mod.rs +++ b/crates/nu-plugin/src/protocol/mod.rs @@ -236,7 +236,7 @@ impl From for PluginInput { #[derive(Serialize, Deserialize, Debug, Clone)] pub enum StreamData { List(Value), - Raw(Result, ShellError>), + Raw(Result, LabeledError>), } impl From for StreamData { @@ -245,9 +245,15 @@ impl From for StreamData { } } +impl From, LabeledError>> for StreamData { + fn from(value: Result, LabeledError>) -> Self { + StreamData::Raw(value) + } +} + impl From, ShellError>> for StreamData { fn from(value: Result, ShellError>) -> Self { - StreamData::Raw(value) + value.map_err(LabeledError::from).into() } } @@ -264,10 +270,10 @@ impl TryFrom for Value { } } -impl TryFrom for Result, ShellError> { +impl TryFrom for Result, LabeledError> { type Error = ShellError; - fn try_from(data: StreamData) -> Result, ShellError>, ShellError> { + fn try_from(data: StreamData) -> Result, LabeledError>, ShellError> { match data { StreamData::Raw(value) => Ok(value), StreamData::List(_) => Err(ShellError::PluginFailedToDecode { @@ -277,6 +283,14 @@ impl TryFrom for Result, ShellError> { } } +impl TryFrom for Result, ShellError> { + type Error = ShellError; + + fn try_from(value: StreamData) -> Result, ShellError>, ShellError> { + Result::, LabeledError>::try_from(value).map(|res| res.map_err(ShellError::from)) + } +} + /// A stream control or data message. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum StreamMessage { diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index f7e9e16ca8..1d8a2ef872 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -3,13 +3,14 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::{ - ast::Operator, engine::StateWorkingSet, format_error, ParseError, Span, Spanned, Value, + ast::Operator, engine::StateWorkingSet, format_error, LabeledError, ParseError, Span, Spanned, + Value, }; /// The fundamental error type for the evaluation engine. These cases represent different kinds of errors /// the evaluator might face, along with helpful spans to label. An error renderer will take this error value /// and pass it into an error viewer to display to the user. -#[derive(Debug, Clone, Error, Diagnostic, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Error, Diagnostic, PartialEq)] pub enum ShellError { /// An operator received two arguments of incompatible types. /// @@ -1407,6 +1408,75 @@ impl From for ShellError { } } +/// `ShellError` always serializes as [`LabeledError`]. +impl Serialize for ShellError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + LabeledError::from_diagnostic(self).serialize(serializer) + } +} + +/// `ShellError` always deserializes as if it were [`LabeledError`], resulting in a +/// [`ShellError::LabeledError`] variant. +impl<'de> Deserialize<'de> for ShellError { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + LabeledError::deserialize(deserializer).map(ShellError::from) + } +} + pub fn into_code(err: &ShellError) -> Option { err.code().map(|code| code.to_string()) } + +#[test] +fn shell_error_serialize_roundtrip() { + // Ensure that we can serialize and deserialize `ShellError`, and check that it basically would + // look the same + let original_error = ShellError::CantConvert { + span: Span::new(100, 200), + to_type: "Foo".into(), + from_type: "Bar".into(), + help: Some("this is a test".into()), + }; + println!("orig_error = {:#?}", original_error); + + let serialized = + serde_json::to_string_pretty(&original_error).expect("serde_json::to_string_pretty failed"); + println!("serialized = {}", serialized); + + let deserialized: ShellError = + serde_json::from_str(&serialized).expect("serde_json::from_str failed"); + println!("deserialized = {:#?}", deserialized); + + // We don't expect the deserialized error to be the same as the original error, but its miette + // properties should be comparable + assert_eq!(original_error.to_string(), deserialized.to_string()); + + assert_eq!( + original_error.code().map(|c| c.to_string()), + deserialized.code().map(|c| c.to_string()) + ); + + let orig_labels = original_error + .labels() + .into_iter() + .flatten() + .collect::>(); + let deser_labels = deserialized + .labels() + .into_iter() + .flatten() + .collect::>(); + + assert_eq!(orig_labels, deser_labels); + + assert_eq!( + original_error.help().map(|c| c.to_string()), + deserialized.help().map(|c| c.to_string()) + ); +}