Reimplement parsers with nu-serde (#3880)

The nu-serde crate allows us to become much more generic with respect to how we
convert output to `nu-protocol::Value`s. This allows us to remove a lot of the
special-case code that we wrote for deserializing JSON values.

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
Lily Mara 2021-08-06 09:46:19 -07:00 committed by GitHub
parent 63abe1cb3e
commit ba483155d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 109 deletions

16
Cargo.lock generated
View file

@ -2762,7 +2762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
dependencies = [
"serde 1.0.126",
"serde_test 1.0.126",
"serde_test 1.0.127",
]
[[package]]
@ -3279,6 +3279,7 @@ dependencies = [
"nu-plugin",
"nu-pretty-hex",
"nu-protocol",
"nu-serde",
"nu-source",
"nu-stream",
"nu-table",
@ -3316,6 +3317,7 @@ dependencies = [
"term 0.7.0",
"term_size",
"termcolor",
"thiserror",
"titlecase",
"toml",
"trash",
@ -5488,9 +5490,9 @@ dependencies = [
[[package]]
name = "serde_test"
version = "1.0.126"
version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd1055d1c20532080b9da5040ec8e27425f4d4573d8e29eb19ba4ff1e4b9da2d"
checksum = "de9e52f2f83e2608a121618b6d3885b514613aac702306232c4f035ff60fdb56"
dependencies = [
"serde 1.0.126",
]
@ -6105,18 +6107,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]]
name = "thiserror"
version = "1.0.25"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.25"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
dependencies = [
"proc-macro2",
"quote 1.0.9",

View file

@ -19,6 +19,7 @@ nu-path = { version = "0.35.0", path="../nu-path" }
nu-parser = { version = "0.35.0", path="../nu-parser" }
nu-plugin = { version = "0.35.0", path="../nu-plugin" }
nu-protocol = { version = "0.35.0", path="../nu-protocol" }
nu-serde = { version = "0.35.0", path="../nu-serde" }
nu-source = { version = "0.35.0", path="../nu-source" }
nu-stream = { version = "0.35.0", path="../nu-stream" }
nu-table = { version = "0.35.0", path="../nu-table" }
@ -84,6 +85,7 @@ sha2 = "0.9.3"
strip-ansi-escapes = "0.1.0"
sxd-document = "0.3.2"
sxd-xpath = "0.4.2"
thiserror = "1.0.26"
tempfile = "3.2.0"
term = { version="0.7.0", optional=true }
term_size = "0.3.2"

View file

@ -1,9 +1,18 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_protocol::{Signature, UntaggedValue, Value};
use std::collections::HashMap;
#[derive(Debug, thiserror::Error)]
pub enum DeserializationError {
#[error("Failed to parse input as INI")]
Ini(#[from] serde_ini::de::Error),
#[error("Failed to convert to a nushell value")]
Nu(#[from] nu_serde::Error),
}
pub struct FromIni;
impl WholeStreamCommand for FromIni {
@ -24,39 +33,13 @@ impl WholeStreamCommand for FromIni {
}
}
fn convert_ini_second_to_nu_value(v: &HashMap<String, String>, tag: impl Into<Tag>) -> Value {
let mut second = TaggedDictBuilder::new(tag);
for (key, value) in v.iter() {
second.insert_untagged(key.clone(), Primitive::String(value.clone()));
}
second.into_value()
}
fn convert_ini_top_to_nu_value(
v: &HashMap<String, HashMap<String, String>>,
tag: impl Into<Tag>,
) -> Value {
let tag = tag.into();
let mut top_level = TaggedDictBuilder::new(tag.clone());
for (key, value) in v.iter() {
top_level.insert_value(
key.clone(),
convert_ini_second_to_nu_value(value, tag.clone()),
);
}
top_level.into_value()
}
pub fn from_ini_string_to_value(
s: String,
tag: impl Into<Tag>,
) -> Result<Value, serde_ini::de::Error> {
) -> Result<Value, DeserializationError> {
let v: HashMap<String, HashMap<String, String>> = serde_ini::from_str(&s)?;
Ok(convert_ini_top_to_nu_value(&v, tag))
Ok(nu_serde::to_value(v, tag)?)
}
fn from_ini(args: CommandArgs) -> Result<OutputStream, ShellError> {
@ -72,13 +55,20 @@ fn from_ini(args: CommandArgs) -> Result<OutputStream, ShellError> {
} => Ok(list.into_iter().into_output_stream()),
x => Ok(OutputStream::one(x)),
},
Err(_) => Err(ShellError::labeled_error_with_secondary(
"Could not parse as INI",
Err(DeserializationError::Ini(e)) => Err(ShellError::labeled_error_with_secondary(
format!("Could not parse as INI: {}", e),
"input cannot be parsed as INI",
&tag,
"value originates from here",
concat_string.tag,
)),
Err(DeserializationError::Nu(e)) => Err(ShellError::labeled_error_with_secondary(
format!("Could not convert to nushell value: {}", e),
"input cannot be converted to nushell",
&tag,
"value originates from here",
concat_string.tag,
)),
}
}

View file

@ -1,7 +1,16 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_protocol::{Signature, UntaggedValue, Value};
#[derive(Debug, thiserror::Error)]
pub enum DeserializationError {
#[error("Failed to parse input as JSON")]
Json(#[from] nu_json::Error),
#[error("Failed to convert JSON to a nushell value")]
Nu(#[from] Box<nu_serde::Error>),
}
pub struct FromJson;
@ -27,39 +36,13 @@ impl WholeStreamCommand for FromJson {
}
}
fn convert_json_value_to_nu_value(v: &nu_json::Value, tag: impl Into<Tag>) -> Value {
let tag = tag.into();
let span = tag.span;
match v {
nu_json::Value::Null => UntaggedValue::Primitive(Primitive::Nothing).into_value(&tag),
nu_json::Value::Bool(b) => UntaggedValue::boolean(*b).into_value(&tag),
nu_json::Value::F64(n) => UntaggedValue::decimal_from_float(*n, span).into_value(&tag),
nu_json::Value::U64(n) => UntaggedValue::big_int(*n).into_value(&tag),
nu_json::Value::I64(n) => UntaggedValue::int(*n).into_value(&tag),
nu_json::Value::String(s) => {
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(&tag)
}
nu_json::Value::Array(a) => UntaggedValue::Table(
a.iter()
.map(|x| convert_json_value_to_nu_value(x, &tag))
.collect(),
)
.into_value(tag),
nu_json::Value::Object(o) => {
let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in o.iter() {
collected.insert_value(k.clone(), convert_json_value_to_nu_value(v, &tag));
}
collected.into_value()
}
}
}
pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> nu_json::Result<Value> {
pub fn from_json_string_to_value(
s: String,
tag: impl Into<Tag>,
) -> Result<Value, DeserializationError> {
let v: nu_json::Value = nu_json::from_str(&s)?;
Ok(convert_json_value_to_nu_value(&v, tag))
Ok(nu_serde::to_value(v, tag).map_err(Box::new)?)
}
fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
@ -81,7 +64,19 @@ fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
match from_json_string_to_value(json_str, &name_tag) {
Ok(x) => Some(x),
Err(e) => {
Err(DeserializationError::Nu(e)) => {
let mut message = "Could not convert JSON to nushell value (".to_string();
message.push_str(&e.to_string());
message.push(')');
Some(Value::error(ShellError::labeled_error_with_secondary(
message,
"input cannot be converted to nushell values",
name_tag.clone(),
"value originates from here",
concat_string.tag.clone(),
)))
}
Err(DeserializationError::Json(e)) => {
let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string());
message.push(')');
@ -107,7 +102,7 @@ fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
x => Ok(OutputStream::one(x)),
},
Err(e) => {
Err(DeserializationError::Json(e)) => {
let mut message = "Could not parse as JSON (".to_string();
message.push_str(&e.to_string());
message.push(')');
@ -122,6 +117,20 @@ fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
),
)))
}
Err(DeserializationError::Nu(e)) => {
let mut message = "Could not convert JSON to nushell value (".to_string();
message.push_str(&e.to_string());
message.push(')');
Ok(OutputStream::one(Value::error(
ShellError::labeled_error_with_secondary(
message,
"input cannot be converted to nushell values",
name_tag,
"value originates from here",
concat_string.tag,
),
)))
}
}
}
}

View file

@ -1,7 +1,16 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
use nu_protocol::{Signature, UntaggedValue, Value};
#[derive(Debug, thiserror::Error)]
pub enum DeserializationError {
#[error("Failed to parse input as TOML")]
Toml(#[from] toml::de::Error),
#[error("Failed to convert to a nushell value")]
Nu(#[from] Box<nu_serde::Error>),
}
pub struct FromToml;
@ -23,41 +32,13 @@ impl WholeStreamCommand for FromToml {
}
}
pub fn convert_toml_value_to_nu_value(v: &toml::Value, tag: impl Into<Tag>) -> Value {
let tag = tag.into();
let span = tag.span;
match v {
toml::Value::Boolean(b) => UntaggedValue::boolean(*b).into_value(tag),
toml::Value::Integer(n) => UntaggedValue::int(*n).into_value(tag),
toml::Value::Float(n) => UntaggedValue::decimal_from_float(*n, span).into_value(tag),
toml::Value::String(s) => {
UntaggedValue::Primitive(Primitive::String(String::from(s))).into_value(tag)
}
toml::Value::Array(a) => UntaggedValue::Table(
a.iter()
.map(|x| convert_toml_value_to_nu_value(x, &tag))
.collect(),
)
.into_value(tag),
toml::Value::Datetime(dt) => {
UntaggedValue::Primitive(Primitive::String(dt.to_string())).into_value(tag)
}
toml::Value::Table(t) => {
let mut collected = TaggedDictBuilder::new(&tag);
for (k, v) in t.iter() {
collected.insert_value(k.clone(), convert_toml_value_to_nu_value(v, &tag));
}
collected.into_value()
}
}
}
pub fn from_toml_string_to_value(s: String, tag: impl Into<Tag>) -> Result<Value, toml::de::Error> {
pub fn from_toml_string_to_value(
s: String,
tag: impl Into<Tag>,
) -> Result<Value, DeserializationError> {
let v: toml::Value = s.parse::<toml::Value>()?;
Ok(convert_toml_value_to_nu_value(&v, tag))
Ok(nu_serde::to_value(v, tag).map_err(Box::new)?)
}
pub fn from_toml(args: CommandArgs) -> Result<OutputStream, ShellError> {