mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 08:23:24 +01:00
Remove serde derive for ShellError
, replace via LabeledError
(#12319)
# Description This changes the interface for plugins to always represent errors as `LabeledError`s. This is good for altlang plugins, as it would suck for them to have to implement and track `ShellError`. We save a lot of generated code from the `ShellError` serde impl too, so `nu` and plugins get to have a smaller binary size. Reduces the release binary size by 1.2 MiB on my build configuration. # User-Facing Changes - Changes plugin protocol. `ShellError` no longer serialized. - `ShellError` serialize output is different - `ShellError` no longer deserializes to exactly the same value as serialized # Tests + Formatting - 🟢 `toolkit fmt` - 🟢 `toolkit clippy` - 🟢 `toolkit test` - 🟢 `toolkit test stdlib` # After Submitting - [ ] Document in plugin protocol reference
This commit is contained in:
parent
cc39069e13
commit
714a0ccd24
@ -103,7 +103,8 @@ fn list_reader_recv_wrong_type() -> Result<(), ShellError> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn reader_recv_raw_messages() -> Result<(), ShellError> {
|
fn reader_recv_raw_messages() -> Result<(), ShellError> {
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let mut reader = StreamReader::new(0, rx, TestSink::default());
|
let mut reader =
|
||||||
|
StreamReader::<Result<Vec<u8>, ShellError>, _>::new(0, rx, TestSink::default());
|
||||||
|
|
||||||
tx.send(Ok(Some(StreamData::Raw(Ok(vec![10, 20])))))
|
tx.send(Ok(Some(StreamData::Raw(Ok(vec![10, 20])))))
|
||||||
.unwrap();
|
.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()];
|
let expected_values = vec![b"hello".to_vec(), b"world".to_vec(), b"test".to_vec()];
|
||||||
|
|
||||||
for value in &expected_values {
|
for value in &expected_values {
|
||||||
writable.write(Ok(value.clone()))?;
|
writable.write(Ok::<_, ShellError>(value.clone()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now try signalling ack
|
// Now try signalling ack
|
||||||
|
@ -184,8 +184,14 @@ fn read_pipeline_data_external_stream() -> Result<(), ShellError> {
|
|||||||
|
|
||||||
test.add(StreamMessage::Data(14, Value::test_int(1).into()));
|
test.add(StreamMessage::Data(14, Value::test_int(1).into()));
|
||||||
for _ in 0..iterations {
|
for _ in 0..iterations {
|
||||||
test.add(StreamMessage::Data(12, Ok(out_pattern.clone()).into()));
|
test.add(StreamMessage::Data(
|
||||||
test.add(StreamMessage::Data(13, Ok(err_pattern.clone()).into()));
|
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(12));
|
||||||
test.add(StreamMessage::End(13));
|
test.add(StreamMessage::End(13));
|
||||||
|
@ -236,7 +236,7 @@ impl From<StreamMessage> for PluginInput {
|
|||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum StreamData {
|
pub enum StreamData {
|
||||||
List(Value),
|
List(Value),
|
||||||
Raw(Result<Vec<u8>, ShellError>),
|
Raw(Result<Vec<u8>, LabeledError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Value> for StreamData {
|
impl From<Value> for StreamData {
|
||||||
@ -245,9 +245,15 @@ impl From<Value> for StreamData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Result<Vec<u8>, LabeledError>> for StreamData {
|
||||||
|
fn from(value: Result<Vec<u8>, LabeledError>) -> Self {
|
||||||
|
StreamData::Raw(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Result<Vec<u8>, ShellError>> for StreamData {
|
impl From<Result<Vec<u8>, ShellError>> for StreamData {
|
||||||
fn from(value: Result<Vec<u8>, ShellError>) -> Self {
|
fn from(value: Result<Vec<u8>, ShellError>) -> Self {
|
||||||
StreamData::Raw(value)
|
value.map_err(LabeledError::from).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,10 +270,10 @@ impl TryFrom<StreamData> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<StreamData> for Result<Vec<u8>, ShellError> {
|
impl TryFrom<StreamData> for Result<Vec<u8>, LabeledError> {
|
||||||
type Error = ShellError;
|
type Error = ShellError;
|
||||||
|
|
||||||
fn try_from(data: StreamData) -> Result<Result<Vec<u8>, ShellError>, ShellError> {
|
fn try_from(data: StreamData) -> Result<Result<Vec<u8>, LabeledError>, ShellError> {
|
||||||
match data {
|
match data {
|
||||||
StreamData::Raw(value) => Ok(value),
|
StreamData::Raw(value) => Ok(value),
|
||||||
StreamData::List(_) => Err(ShellError::PluginFailedToDecode {
|
StreamData::List(_) => Err(ShellError::PluginFailedToDecode {
|
||||||
@ -277,6 +283,14 @@ impl TryFrom<StreamData> for Result<Vec<u8>, ShellError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<StreamData> for Result<Vec<u8>, ShellError> {
|
||||||
|
type Error = ShellError;
|
||||||
|
|
||||||
|
fn try_from(value: StreamData) -> Result<Result<Vec<u8>, ShellError>, ShellError> {
|
||||||
|
Result::<Vec<u8>, LabeledError>::try_from(value).map(|res| res.map_err(ShellError::from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A stream control or data message.
|
/// A stream control or data message.
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum StreamMessage {
|
pub enum StreamMessage {
|
||||||
|
@ -3,13 +3,14 @@ use serde::{Deserialize, Serialize};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
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 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
|
/// 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.
|
/// 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 {
|
pub enum ShellError {
|
||||||
/// An operator received two arguments of incompatible types.
|
/// An operator received two arguments of incompatible types.
|
||||||
///
|
///
|
||||||
@ -1407,6 +1408,75 @@ impl From<super::LabeledError> for ShellError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `ShellError` always serializes as [`LabeledError`].
|
||||||
|
impl Serialize for ShellError {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
LabeledError::deserialize(deserializer).map(ShellError::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_code(err: &ShellError) -> Option<String> {
|
pub fn into_code(err: &ShellError) -> Option<String> {
|
||||||
err.code().map(|code| code.to_string())
|
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::<Vec<_>>();
|
||||||
|
let deser_labels = deserialized
|
||||||
|
.labels()
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(orig_labels, deser_labels);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
original_error.help().map(|c| c.to_string()),
|
||||||
|
deserialized.help().map(|c| c.to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user