difftreelog
fix(nixlike) string roundtrips
in: trunk
6 files changed
crates/nixlike/fuzz/.gitignorediffbeforeafterboth--- /dev/null
+++ b/crates/nixlike/fuzz/.gitignore
@@ -0,0 +1,4 @@
+
+target
+corpus
+artifacts
crates/nixlike/fuzz/Cargo.lockdiffbeforeafterboth--- /dev/null
+++ b/crates/nixlike/fuzz/Cargo.lock
@@ -0,0 +1,182 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "arbitrary"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "577b08a4acd7b99869f863c50011b01eb73424ccc798ecd996f2e24817adfca7"
+
+[[package]]
+name = "bumpalo"
+version = "3.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
+
+[[package]]
+name = "cc"
+version = "1.0.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
+
+[[package]]
+name = "dprint-core"
+version = "0.46.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df98b7d98583d9d57311b9df81f571f35a6ca3e8675d0128333b2e923dc85bcd"
+dependencies = [
+ "bumpalo",
+ "rustc-hash",
+ "serde",
+]
+
+[[package]]
+name = "libfuzzer-sys"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3"
+dependencies = [
+ "arbitrary",
+ "cc",
+ "once_cell",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "nixlike"
+version = "0.1.0"
+dependencies = [
+ "dprint-core",
+ "linked-hash-map",
+ "peg",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "nixlike-fuzz"
+version = "0.0.0"
+dependencies = [
+ "libfuzzer-sys",
+ "nixlike",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+
+[[package]]
+name = "peg"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a"
+dependencies = [
+ "peg-macros",
+ "peg-runtime",
+]
+
+[[package]]
+name = "peg-macros"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c"
+dependencies = [
+ "peg-runtime",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "peg-runtime"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "serde"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
crates/nixlike/fuzz/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/crates/nixlike/fuzz/Cargo.toml
@@ -0,0 +1,26 @@
+
+[package]
+name = "nixlike-fuzz"
+version = "0.0.0"
+authors = ["Automatically generated"]
+publish = false
+edition = "2018"
+
+[package.metadata]
+cargo-fuzz = true
+
+[dependencies]
+libfuzzer-sys = "0.4"
+
+[dependencies.nixlike]
+path = ".."
+
+# Prevent this from interfering with workspaces
+[workspace]
+members = ["."]
+
+[[bin]]
+name = "fuzz_target_1"
+path = "fuzz_targets/fuzz_target_1.rs"
+test = false
+doc = false
crates/nixlike/fuzz/fuzz_targets/fuzz_target_1.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/nixlike/fuzz/fuzz_targets/fuzz_target_1.rs
@@ -0,0 +1,9 @@
+#![no_main]
+use libfuzzer_sys::fuzz_target;
+
+fuzz_target!(|data: String| {
+ let serialized = nixlike::serialize(data.clone()).unwrap();
+ let deserialized: String = nixlike::parse_str(&serialized).unwrap();
+
+ assert_eq!(data, deserialized);
+});
crates/nixlike/src/lib.rsdiffbeforeafterboth1use linked_hash_map::LinkedHashMap;2use peg::str::LineCol;3use se_impl::MySerialize;4use serde::{Deserialize, Serialize};56mod de_impl;7mod se_impl;8mod to_string;910#[derive(thiserror::Error, Debug)]11pub enum Error {12 #[error("bad number")]13 BadNumber,14 #[error("expected {0}")]15 Expected(&'static str),16 #[error("parse error")]17 ParseError(#[from] peg::error::ParseError<LineCol>),18 #[error("{0}")]19 Custom(String),20 #[error("io: {0}")]21 Io(#[from] std::io::Error),22 #[error("fmt: {0}")]23 Fmt(#[from] std::fmt::Error),24}2526#[derive(Debug)]27pub enum Value {28 Number(i64),29 String(String),30 Boolean(bool),31 Object(LinkedHashMap<String, Value>),32 Array(Vec<Value>),33 Null,34}3536peg::parser! {37pub grammar nixlike() for str {38 rule number() -> i6439 = quiet! { v:$(['0'..='9' | '+' | '-']+) {? v.parse().map_err(|_| "<number>")} } / expected!("<number>")40 rule string_char() -> &'input str41 = "\\\"" { "\"" }42 / "\\\\" { "\\" }43 / c:$([_]) { c }44 rule string() -> String45 = quiet! { "\"" v:(!"\"" c:string_char() {c})* "\"" { v.into_iter().collect() } } / expected!("<string>")46 rule boolean() -> bool47 = quiet! { "true" {true}48 / "false" {false} } / expected!("<boolean>")49 rule indent() -> String50 = quiet! { s:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-']+) { s.to_owned() } } / expected!("<identifier>")51 rule object() -> LinkedHashMap<String, Value>52 = "{" _53 e:(k:indent()++(_ "." _) _ "=" _ v:value() _ ";" _ {(k, v)})*54 "}" {?55 let mut out = LinkedHashMap::new();56 for (k, v) in e {57 let mut map = &mut out;58 for v in k.iter().take(k.len() - 1) {59 map = match map.entry(v.clone()).or_insert_with(|| Value::Object(Default::default())) {60 Value::Object(v) => v,61 _ => return Err("expected object"),62 }63 }6465 let key = k.into_iter().last().unwrap();66 if map.contains_key(&key) {67 return Err("can't override object");68 }69 map.insert(key, v);70 }71 Ok(out)72 }7374 rule array() -> Vec<Value>75 = "[" _ v:value()**_ _ "]" {v}7677 rule value() -> Value78 = o:object() { Value::Object(o) }79 / a:array() { Value::Array(a) }80 / s:string() { Value::String(s) }81 / "null" { Value::Null }82 / b:boolean() { Value::Boolean(b) }83 / n:number() { Value::Number(n) }8485 pub rule root() -> Value86 = _ v:value() _ { v }8788 rule _()89 = ( quiet!{ [' ' | '\t' | '\n']+ }90 / "#" (!['\n'] [_])* "\n" )*91}92}9394pub fn parse_str<'de, D: Deserialize<'de>>(s: &str) -> Result<D, Error> {95 let value = nixlike::root(s)?;96 D::deserialize(value)97}9899pub fn parse_value<'de, D: Deserialize<'de>>(value: Value) -> Result<D, Error> {100 D::deserialize(value)101}102103pub fn serialize_value_pretty(value: Value) -> String {104 to_string::write_nix(&value)105}106107pub fn serialize<S: Serialize>(value: S) -> Result<String, Error> {108 let value: Value = value.serialize(MySerialize)?;109 Ok(serialize_value_pretty(value))110}111112#[test]113fn test() {114 let v: serde_json::Value = parse_str(115 r#"116 {117 b.c = 2;118 b.d = "hello";119 c = {120 k = 123;121 p = 231;122 ll = [1 2 3 [] [[4 5 6]] ];123 };124 }125 "#,126 )127 .unwrap();128 let s: String = serialize(v).unwrap();129 println!("{}", s);130}1use linked_hash_map::LinkedHashMap;2use peg::str::LineCol;3use se_impl::MySerialize;4use serde::{Deserialize, Serialize};56mod de_impl;7mod se_impl;8mod to_string;910#[derive(thiserror::Error, Debug)]11pub enum Error {12 #[error("bad number")]13 BadNumber,14 #[error("expected {0}")]15 Expected(&'static str),16 #[error("parse error")]17 ParseError(#[from] peg::error::ParseError<LineCol>),18 #[error("{0}")]19 Custom(String),20 #[error("io: {0}")]21 Io(#[from] std::io::Error),22 #[error("fmt: {0}")]23 Fmt(#[from] std::fmt::Error),24}2526#[derive(Debug)]27pub enum Value {28 Number(i64),29 String(String),30 Boolean(bool),31 Object(LinkedHashMap<String, Value>),32 Array(Vec<Value>),33 Null,34}3536peg::parser! {37pub grammar nixlike() for str {38 rule number() -> i6439 = quiet! { v:$(['0'..='9' | '+' | '-']+) {? v.parse().map_err(|_| "<number>")} } / expected!("<number>")40 rule string_char() -> &'input str41 = "\\\"" { "\"" }42 / "\\\\" { "\\" }43 / "\\n" { "\n" }44 / "\\t" { "\t" }45 / "\\r" { "\r" }46 / "''$" { "$" }47 / c:$([_]) { c }48 rule string() -> String49 = quiet! { "\"" v:(!"\"" c:string_char() {c})* "\"" { v.into_iter().collect() } } / expected!("<string>")50 rule boolean() -> bool51 = quiet! { "true" {true}52 / "false" {false} } / expected!("<boolean>")53 rule indent() -> String54 = quiet! {55 s:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-']+) { s.to_owned() }56 / "\"" s:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '.']+) "\"" { s.to_owned() }57 } / expected!("<identifier>")58 rule object() -> LinkedHashMap<String, Value>59 = "{" _60 e:(k:indent()++(_ "." _) _ "=" _ v:value() _ ";" _ {(k, v)})*61 "}" {?62 let mut out = LinkedHashMap::new();63 for (k, v) in e {64 let mut map = &mut out;65 for v in k.iter().take(k.len() - 1) {66 map = match map.entry(v.clone()).or_insert_with(|| Value::Object(Default::default())) {67 Value::Object(v) => v,68 _ => return Err("expected object"),69 }70 }7172 let key = k.into_iter().last().unwrap();73 if map.contains_key(&key) {74 return Err("can't override object");75 }76 map.insert(key, v);77 }78 Ok(out)79 }8081 rule array() -> Vec<Value>82 = "[" _ v:value()**_ _ "]" {v}8384 rule value() -> Value85 = o:object() { Value::Object(o) }86 / a:array() { Value::Array(a) }87 / s:string() { Value::String(s) }88 / "null" { Value::Null }89 / b:boolean() { Value::Boolean(b) }90 / n:number() { Value::Number(n) }9192 pub rule root() -> Value93 = _ v:value() _ { v }9495 rule _()96 = ( quiet!{ [' ' | '\t' | '\n']+ }97 / "#" (!['\n'] [_])* "\n" )*98}99}100101pub fn parse_str<'de, D: Deserialize<'de>>(s: &str) -> Result<D, Error> {102 let value = nixlike::root(s)?;103 D::deserialize(value)104}105106pub fn parse_value<'de, D: Deserialize<'de>>(value: Value) -> Result<D, Error> {107 D::deserialize(value)108}109110pub fn serialize_value_pretty(value: Value) -> String {111 to_string::write_nix(&value)112}113114pub fn serialize<S: Serialize>(value: S) -> Result<String, Error> {115 let value: Value = value.serialize(MySerialize)?;116 Ok(serialize_value_pretty(value))117}118119#[test]120fn test() {121 assert_eq!(serialize("Hello\nworld").unwrap(), "\"Hello\\nworld\"");122}crates/nixlike/src/to_string.rsdiffbeforeafterboth--- a/crates/nixlike/src/to_string.rs
+++ b/crates/nixlike/src/to_string.rs
@@ -5,7 +5,13 @@
};
fn write_nix_obj_key_buf(k: &str, v: &Value, out: &mut PrintItems) {
- out.push_str(k);
+ if k.contains(".") {
+ out.push_str("\"");
+ out.push_str(k);
+ out.push_str("\"");
+ } else {
+ out.push_str(k);
+ }
match v {
Value::Object(o) if o.len() == 1 => {
let (k, v) = o.iter().next().unwrap();
@@ -26,7 +32,15 @@
Value::Null => out.push_str("null"),
Value::Boolean(v) => out.push_str(if *v { "true" } else { "false" }),
Value::Number(n) => out.push_str(&format!("{}", n)),
- Value::String(s) => out.push_str(&format!("{:?}", s)),
+ Value::String(s) => out.push_str(&format!(
+ "\"{}\"",
+ s.replace('\\', "\\\\")
+ .replace('"', "\\\"")
+ .replace('\n', "\\n")
+ .replace('\t', "\\t")
+ .replace('\r', "\\r")
+ .replace("$", "''$")
+ )),
Value::Array(a) => {
if a.is_empty() {
out.push_str("[ ]");