Repository: ron-rs/ron
Pull request #407 (2022-09-11 08:21:26)

Implemented ron::value::RawValue
Adds the `ron::value::RawValue` type, inspired by [`serde-rs`](https://docs.rs/serde_json/latest/serde_json/value/struct.RawValue.html), which holds a valid RON fragment string and can be (de)serialised inside any container.

* [x] I've included my change in `CHANGELOG.md`


codecov-commenter: # [Codecov](https://codecov.io/gh/ron-rs/ron/pull/407?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#407](https://codecov.io/gh/ron-rs/ron/pull/407?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (8f77817) into [master](https://codecov.io/gh/ron-rs/ron/commit/a63b3c8864bd76d10d40394a0c4ff78ea23c6f8c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (a63b3c8) will **increase** coverage by `0.31%`.
> The diff coverage is `59.50%`.

```diff
@@            Coverage Diff             @@
##           master     #407      +/-   ##
==========================================
+ Coverage   78.74%   79.05%   +0.31%     
==========================================
  Files          51       54       +3     
  Lines        6737     7000     +263     
==========================================
+ Hits         5305     5534     +229     
- Misses       1432     1466      +34     
```


| [Impacted Files](https://codecov.io/gh/ron-rs/ron/pull/407?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [src/error.rs](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2Vycm9yLnJz) | `36.01% <0.00%> (-0.29%)` | :arrow_down: |
| [src/value/mod.rs](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3ZhbHVlL21vZC5ycw==) | `67.47% <ø> (ø)` | |
| [src/ser/raw.rs](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3Nlci9yYXcucnM=) | `5.21% <5.21%> (ø)` | |
| [src/value/raw.rs](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3ZhbHVlL3Jhdy5ycw==) | `69.49% <69.49%> (ø)` | |
| [src/de/mod.rs](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2RlL21vZC5ycw==) | `76.02% <100.00%> (+2.10%)` | :arrow_up: |
| [src/parse.rs](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3BhcnNlLnJz) | `79.53% <100.00%> (ø)` | |
| [src/ser/mod.rs](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL3Nlci9tb2QucnM=) | `74.63% <100.00%> (-3.64%)` | :arrow_down: |
| [tests/407\_raw\_value.rs](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-dGVzdHMvNDA3X3Jhd192YWx1ZS5ycw==) | `100.00% <100.00%> (ø)` | |
| ... and [3 more](https://codecov.io/gh/ron-rs/ron/pull/407/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

MomoLangenstein: ?r @torkleyy I stumbled across this API some time ago and thought it would be quite useful to also have it inside `ron`. While I added some convenience functions to `RawValue` for the common cases, I decided against implementing `Serializer` and `Deserializer` directly as those would only duplicate functionality which is already available by just using the existing `Deserializer` with the `&str` one can get from `RawValue`.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea725a61..f4144aa3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Fix issues [#277](https://github.com/ron-rs/ron/issues/277) and [#405](https://github.com/ron-rs/ron/issues/405) with `Value::Map` `IntoIter` and extraneous item check for `Value::Seq` ([#406](https://github.com/ron-rs/ron/pull/406))
 - Fix issue [#401](https://github.com/ron-rs/ron/issues/401) with correct raw struct name identifier parsing ([#402](https://github.com/ron-rs/ron/pull/402))
+- Add `ron::value::RawValue` helper type which can (de)serialize any valid RON ([#407](https://github.com/ron-rs/ron/pull/407))
 
 ## [0.8.0] - 2022-08-17
 
diff --git a/src/de/mod.rs b/src/de/mod.rs
index 9c5b6f99..3329facb 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -447,6 +447,21 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
     where
         V: Visitor<'de>,
     {
+        if name == crate::value::raw::RAW_VALUE_TOKEN {
+            let bytes_before = self.bytes.bytes();
+            self.bytes.skip_ws()?;
+            let _ignored = self.deserialize_ignored_any(serde::de::IgnoredAny)?;
+            self.bytes.skip_ws()?;
+            let bytes_after = self.bytes.bytes();
+
+            let ron_bytes = &bytes_before[..bytes_before.len() - bytes_after.len()];
+            let ron_str = str::from_utf8(ron_bytes).map_err(Error::from)?;
+
+            return visitor
+                .visit_borrowed_str::<Error>(ron_str)
+                .map_err(|_| Error::ExpectedRawValue);
+        }
+
         if self.bytes.exts.contains(Extensions::UNWRAP_NEWTYPES) || self.newtype_variant {
             self.newtype_variant = false;
 
diff --git a/src/error.rs b/src/error.rs
index 6c815345..07d2d379 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -90,6 +90,7 @@ pub enum Error {
     },
     InvalidIdentifier(String),
     SuggestRawIdentifier(String),
+    ExpectedRawValue,
 }
 
 impl fmt::Display for SpannedError {
@@ -248,6 +249,7 @@ impl fmt::Display for Error {
                 "Found invalid std identifier `{}`, try the raw identifier `r#{}` instead",
                 identifier, identifier
             ),
+            Error::ExpectedRawValue => f.write_str("Expected a `ron::value::RawValue`"),
         }
     }
 }
diff --git a/src/parse.rs b/src/parse.rs
index 6c91d40d..264d0685 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -355,7 +355,7 @@ impl<'a> Bytes<'a> {
         }
     }
 
-    pub fn bytes(&self) -> &[u8] {
+    pub fn bytes(&self) -> &'a [u8] {
         self.bytes
     }
 
diff --git a/src/ser/mod.rs b/src/ser/mod.rs
index 109a5255..8a25cfe9 100644
--- a/src/ser/mod.rs
+++ b/src/ser/mod.rs
@@ -9,6 +9,7 @@ use crate::{
     parse::{is_ident_first_char, is_ident_other_char, is_ident_raw_char, LargeSInt, LargeUInt},
 };
 
+mod raw;
 #[cfg(test)]
 mod tests;
 mod value;
@@ -533,6 +534,10 @@ impl<'a, W: io::Write> ser::Serializer for &'a mut Serializer<W> {
     where
         T: ?Sized + Serialize,
     {
+        if name == crate::value::raw::RAW_VALUE_TOKEN {
+            return value.serialize(raw::RawValueSerializer::new(self));
+        }
+
         if self.extensions().contains(Extensions::UNWRAP_NEWTYPES) || self.newtype_variant {
             self.newtype_variant = false;
 
diff --git a/src/ser/raw.rs b/src/ser/raw.rs
new file mode 100644
index 00000000..f9186798
--- /dev/null
+++ b/src/ser/raw.rs
@@ -0,0 +1,172 @@
+use std::io;
+
+use serde::{ser, Serialize};
+
+use super::{Error, Result, Serializer};
+
+pub struct RawValueSerializer<'a, W: io::Write> {
+    ser: &'a mut Serializer<W>,
+}
+
+impl<'a, W: io::Write> RawValueSerializer<'a, W> {
+    pub fn new(ser: &'a mut Serializer<W>) -> Self {
+        Self { ser }
+    }
+}
+
+impl<'a, W: io::Write> ser::Serializer for RawValueSerializer<'a, W> {
+    type Error = Error;
+    type Ok = ();
+    type SerializeMap = ser::Impossible<(), Error>;
+    type SerializeSeq = ser::Impossible<(), Error>;
+    type SerializeStruct = ser::Impossible<(), Error>;
+    type SerializeStructVariant = ser::Impossible<(), Error>;
+    type SerializeTuple = ser::Impossible<(), Error>;
+    type SerializeTupleStruct = ser::Impossible<(), Error>;
+    type SerializeTupleVariant = ser::Impossible<(), Error>;
+
+    fn serialize_bool(self, _: bool) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_i8(self, _: i8) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_i16(self, _: i16) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_i32(self, _: i32) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_i64(self, _: i64) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    #[cfg(feature = "integer128")]
+    fn serialize_i128(self, _: i128) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_u8(self, _: u8) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_u16(self, _: u16) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_u32(self, _: u32) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_u64(self, _: u64) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    #[cfg(feature = "integer128")]
+    fn serialize_u128(self, _: u128) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_f32(self, _: f32) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_f64(self, _: f64) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_char(self, _: char) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_str(self, ron: &str) -> Result<()> {
+        self.ser.output.write_all(ron.as_bytes())?;
+        Ok(())
+    }
+
+    fn serialize_bytes(self, _: &[u8]) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_none(self) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_some<T: ?Sized + Serialize>(self, _: &T) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_unit(self) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_unit_struct(self, _: &'static str) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_unit_variant(self, _: &'static str, _: u32, _: &'static str) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_newtype_struct<T: ?Sized + Serialize>(self, _: &'static str, _: &T) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_newtype_variant<T: ?Sized + Serialize>(
+        self,
+        _: &'static str,
+        _: u32,
+        _: &'static str,
+        _: &T,
+    ) -> Result<()> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_tuple_struct(
+        self,
+        _: &'static str,
+        _: usize,
+    ) -> Result<Self::SerializeTupleStruct> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_tuple_variant(
+        self,
+        _: &'static str,
+        _: u32,
+        _: &'static str,
+        _: usize,
+    ) -> Result<Self::SerializeTupleVariant> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_map(self, _: Option<usize>) -> Result<Self::SerializeMap> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_struct(self, _: &'static str, _: usize) -> Result<Self::SerializeStruct> {
+        Err(Error::ExpectedRawValue)
+    }
+
+    fn serialize_struct_variant(
+        self,
+        _: &'static str,
+        _: u32,
+        _: &'static str,
+        _: usize,
+    ) -> Result<Self::SerializeStructVariant> {
+        Err(Error::ExpectedRawValue)
+    }
+}
diff --git a/src/value.rs b/src/value/mod.rs
similarity index 99%
rename from src/value.rs
rename to src/value/mod.rs
index 1f462869..5914295b 100644
--- a/src/value.rs
+++ b/src/value/mod.rs
@@ -14,6 +14,10 @@ use serde::{
 
 use crate::{de::Error, error::Result};
 
+pub(crate) mod raw;
+
+pub use raw::RawValue;
+
 /// A `Value` to `Value` map.
 ///
 /// This structure either uses a [BTreeMap](std::collections::BTreeMap) or the
diff --git a/src/value/raw.rs b/src/value/raw.rs
new file mode 100644
index 00000000..77c2cf64
--- /dev/null
+++ b/src/value/raw.rs
@@ -0,0 +1,189 @@
+// Inspired by David Tolnay's serde-rs
+// https://github.com/serde-rs/json/blob/master/src/raw.rs
+// Licensed under either of Apache License, Version 2.0 or MIT license at your option.
+
+use std::fmt;
+
+use serde::{de, ser, Deserialize, Serialize};
+
+use crate::{
+    error::{Error, SpannedResult},
+    options::Options,
+};
+
+pub(crate) const RAW_VALUE_TOKEN: &str = "$ron::private::RawValue";
+
+#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[repr(transparent)]
+pub struct RawValue {
+    ron: str,
+}
+
+impl RawValue {
+    fn from_borrowed_str(ron: &str) -> &Self {
+        // Safety: RawValue is a transparent newtype around str
+        unsafe { std::mem::transmute::<&str, &RawValue>(ron) }
+    }
+
+    fn from_boxed_str(ron: Box<str>) -> Box<Self> {
+        // Safety: RawValue is a transparent newtype around str
+        unsafe { std::mem::transmute::<Box<str>, Box<RawValue>>(ron) }
+    }
+
+    fn into_boxed_str(raw_value: Box<Self>) -> Box<str> {
+        // Safety: RawValue is a transparent newtype around str
+        unsafe { std::mem::transmute::<Box<RawValue>, Box<str>>(raw_value) }
+    }
+}
+
+impl Clone for Box<RawValue> {
+    fn clone(&self) -> Self {
+        (**self).to_owned()
+    }
+}
+
+impl ToOwned for RawValue {
+    type Owned = Box<RawValue>;
+
+    fn to_owned(&self) -> Self::Owned {
+        RawValue::from_boxed_str(self.ron.to_owned().into_boxed_str())
+    }
+}
+
+impl fmt::Debug for RawValue {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.debug_tuple("RawValue")
+            .field(&format_args!("{}", &self.ron))
+            .finish()
+    }
+}
+
+impl fmt::Display for RawValue {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(&self.ron)
+    }
+}
+
+impl RawValue {
+    /// Get the inner raw RON string, which is guaranteed to contain valid RON.
+    pub fn get_ron(&self) -> &str {
+        &self.ron
+    }
+
+    /// Helper function to validate a RON string and turn it into a `RawValue`.
+    pub fn from_ron(ron: &str) -> SpannedResult<&Self> {
+        Options::default()
+            .from_str::<&Self>(ron)
+            .map(|_| Self::from_borrowed_str(ron))
+    }
+
+    /// Helper function to validate a RON string and turn it into a `RawValue`.
+    pub fn from_boxed_ron(ron: Box<str>) -> SpannedResult<Box<Self>> {
+        match Options::default().from_str::<&Self>(&*ron) {
+            Ok(_) => Ok(Self::from_boxed_str(ron)),
+            Err(err) => Err(err),
+        }
+    }
+
+    /// Helper function to deserialize the inner RON string into `T`.
+    pub fn into_rust<'de, T: Deserialize<'de>>(&'de self) -> SpannedResult<T> {
+        Options::default().from_str(&self.ron)
+    }
+
+    /// Helper function to serialize `value` into a RON string.
+    pub fn from_rust<T: Serialize>(value: &T) -> Result<Box<Self>, Error> {
+        let ron = Options::default().to_string(value)?;
+
+        Ok(RawValue::from_boxed_str(ron.into_boxed_str()))
+    }
+}
+
+impl From<Box<RawValue>> for Box<str> {
+    fn from(raw_value: Box<RawValue>) -> Self {
+        RawValue::into_boxed_str(raw_value)
+    }
+}
+
+impl Serialize for RawValue {
+    fn serialize<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        serializer.serialize_newtype_struct(RAW_VALUE_TOKEN, &self.ron)
+    }
+}
+
+impl<'de: 'a, 'a> Deserialize<'de> for &'a RawValue {
+    fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        struct ReferenceVisitor;
+
+        impl<'de> de::Visitor<'de> for ReferenceVisitor {
+            type Value = &'de RawValue;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                // This error message only shows up with foreign Deserializers
+                write!(formatter, "any valid borrowed RON-value-string")
+            }
+
+            fn visit_borrowed_str<E: de::Error>(self, ron: &'de str) -> Result<Self::Value, E> {
+                match Options::default().from_str::<serde::de::IgnoredAny>(ron) {
+                    Ok(_) => Ok(RawValue::from_borrowed_str(ron)),
+                    Err(err) => Err(de::Error::custom(format!(
+                        "invalid RON value at {}: {}",
+                        err.position, err.code
+                    ))),
+                }
+            }
+
+            fn visit_newtype_struct<D: de::Deserializer<'de>>(
+                self,
+                deserializer: D,
+            ) -> Result<Self::Value, D::Error> {
+                deserializer.deserialize_str(self)
+            }
+        }
+
+        deserializer.deserialize_newtype_struct(RAW_VALUE_TOKEN, ReferenceVisitor)
+    }
+}
+
+impl<'de> Deserialize<'de> for Box<RawValue> {
+    fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        struct BoxedVisitor;
+
+        impl<'de> de::Visitor<'de> for BoxedVisitor {
+            type Value = Box<RawValue>;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                // This error message only shows up with foreign Deserializers
+                write!(formatter, "any valid RON-value-string")
+            }
+
+            fn visit_str<E: de::Error>(self, ron: &str) -> Result<Self::Value, E> {
+                match Options::default().from_str::<serde::de::IgnoredAny>(ron) {
+                    Ok(_) => Ok(RawValue::from_boxed_str(ron.to_owned().into_boxed_str())),
+                    Err(err) => Err(de::Error::custom(format!(
+                        "invalid RON value at {}: {}",
+                        err.position, err.code
+                    ))),
+                }
+            }
+
+            fn visit_string<E: de::Error>(self, ron: String) -> Result<Self::Value, E> {
+                match Options::default().from_str::<serde::de::IgnoredAny>(&ron) {
+                    Ok(_) => Ok(RawValue::from_boxed_str(ron.into_boxed_str())),
+                    Err(err) => Err(de::Error::custom(format!(
+                        "invalid RON value at {}: {}",
+                        err.position, err.code
+                    ))),
+                }
+            }
+
+            fn visit_newtype_struct<D: de::Deserializer<'de>>(
+                self,
+                deserializer: D,
+            ) -> Result<Self::Value, D::Error> {
+                deserializer.deserialize_string(self)
+            }
+        }
+
+        deserializer.deserialize_newtype_struct(RAW_VALUE_TOKEN, BoxedVisitor)
+    }
+}


Test patch:
diff --git a/tests/407_raw_value.rs b/tests/407_raw_value.rs
new file mode 100644
index 00000000..c80eb201
--- /dev/null
+++ b/tests/407_raw_value.rs
@@ -0,0 +1,220 @@
+use ron::{
+    de::from_bytes,
+    error::{Error, Position, SpannedError},
+    from_str, to_string,
+    value::RawValue,
+};
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
+struct WithRawValue {
+    a: bool,
+    b: Box<RawValue>,
+}
+
+#[test]
+fn test_raw_value_simple() {
+    let raw: &RawValue = from_str("true").unwrap();
+    assert_eq!(raw.get_ron(), "true");
+    let ser = to_string(raw).unwrap();
+    assert_eq!(ser, "true");
+}
+
+#[test]
+fn test_raw_value_inner() {
+    let raw: WithRawValue = from_str("(a: false, b: [1, /* lol */ 2, 3])").unwrap();
+    assert_eq!(raw.b.get_ron(), "[1, /* lol */ 2, 3]");
+    let ser = to_string(&raw).unwrap();
+    assert_eq!(ser, "(a:false,b:[1, /* lol */ 2, 3])");
+}
+
+#[test]
+fn test_raw_value_comment() {
+    let raw: WithRawValue = from_str("(a: false, b: /* nope */ 4)").unwrap();
+    assert_eq!(raw.b.get_ron(), "4");
+
+    let raw: WithRawValue = from_str("(a: false, b: 4 /* yes */)").unwrap();
+    assert_eq!(raw.b.get_ron(), "4 /* yes */");
+
+    let raw: WithRawValue = from_str("(a: false, b: (/* this */ 4 /* too */))").unwrap();
+    assert_eq!(raw.b.get_ron(), "(/* this */ 4 /* too */)");
+}
+
+#[test]
+fn test_raw_value_invalid() {
+    let err = from_str::<&RawValue>("4.d").unwrap_err();
+    assert_eq!(
+        err,
+        SpannedError {
+            code: Error::TrailingCharacters,
+            position: Position { line: 1, col: 3 }
+        }
+    );
+
+    let err = from_bytes::<&RawValue>(b"\0").unwrap_err();
+    assert_eq!(
+        err,
+        SpannedError {
+            code: Error::UnexpectedByte('\0'),
+            position: Position { line: 1, col: 1 }
+        }
+    )
+}
+
+#[test]
+fn test_raw_value_from_ron() {
+    let raw = RawValue::from_ron("/* hi */ (None, 4.2) /* bye */").unwrap();
+    assert_eq!(raw.get_ron(), "/* hi */ (None, 4.2) /* bye */");
+
+    let err = RawValue::from_ron("4.d").unwrap_err();
+    assert_eq!(
+        err,
+        SpannedError {
+            code: Error::TrailingCharacters,
+            position: Position { line: 1, col: 3 }
+        }
+    );
+
+    let raw =
+        RawValue::from_boxed_ron(String::from("/* hi */ (None, 4.2) /* bye */").into_boxed_str())
+            .unwrap();
+    assert_eq!(raw.get_ron(), "/* hi */ (None, 4.2) /* bye */");
+
+    let err = RawValue::from_boxed_ron(String::from("(").into_boxed_str()).unwrap_err();
+    assert_eq!(
+        err,
+        SpannedError {
+            code: Error::Eof,
+            position: Position { line: 1, col: 2 },
+        }
+    );
+}
+
+#[test]
+fn test_raw_value_into_rust() {
+    let raw = RawValue::from_ron("/* hi */ (a: false, b: None) /* bye */").unwrap();
+
+    let with: WithRawValue = raw.into_rust().unwrap();
+    assert_eq!(
+        with,
+        WithRawValue {
+            a: false,
+            b: from_str("None").unwrap(),
+        }
+    );
+
+    let err = raw.into_rust::<i32>().unwrap_err();
+    assert_eq!(
+        err,
+        SpannedError {
+            code: Error::ExpectedInteger,
+            position: Position { line: 1, col: 10 },
+        }
+    );
+}
+
+#[test]
+fn test_raw_value_from_rust() {
+    let raw = RawValue::from_rust(&42).unwrap();
+    assert_eq!(raw.get_ron(), "42");
+
+    let raw = RawValue::from_rust(&WithRawValue {
+        a: true,
+        b: from_str("4.2").unwrap(),
+    })
+    .unwrap();
+    assert_eq!(raw.get_ron(), "(a:true,b:4.2)");
+}
+
+#[test]
+fn test_raw_value_serde_json() {
+    let raw = RawValue::from_ron("/* hi */ (None, 4.2) /* bye */").unwrap();
+
+    let ser = serde_json::to_string(&WithRawValue {
+        a: true,
+        b: raw.to_owned(),
+    })
+    .unwrap();
+    assert_eq!(ser, "{\"a\":true,\"b\":\"/* hi */ (None, 4.2) /* bye */\"}");
+
+    let with: WithRawValue = serde_json::from_str(&ser).unwrap();
+    assert_eq!(raw, &*with.b);
+
+    let err =
+        serde_json::from_str::<WithRawValue>("{\"a\":true,\"b\":\"/* hi */ (a:) /* bye */\"}")
+            .unwrap_err();
+    assert_eq!(
+        err.to_string(),
+        "invalid RON value at 1:13: Unexpected byte ')' at line 1 column 39"
+    );
+
+    let err = serde_json::from_str::<WithRawValue>("{\"a\":true,\"b\":42}").unwrap_err();
+    assert_eq!(
+        err.to_string(),
+        "invalid type: integer `42`, expected any valid RON-value-string at line 1 column 16"
+    );
+
+    let err = serde_json::from_str::<&RawValue>("\"/* hi */ (a:) /* bye */\"").unwrap_err();
+    assert_eq!(
+        err.to_string(),
+        "invalid RON value at 1:13: Unexpected byte ')' at line 1 column 25"
+    );
+
+    let err = serde_json::from_str::<&RawValue>("42").unwrap_err();
+    assert_eq!(
+        err.to_string(),
+        "invalid type: integer `42`, expected any valid borrowed RON-value-string at line 1 column 2"
+    );
+}
+
+#[test]
+fn test_raw_value_clone_into() {
+    let raw = RawValue::from_boxed_ron(String::from("(None, 4.2)").into_boxed_str()).unwrap();
+    let raw2 = raw.clone();
+    assert_eq!(raw, raw2);
+
+    let boxed_str: Box<str> = raw2.into();
+    assert_eq!(&*boxed_str, "(None, 4.2)");
+}
+
+#[test]
+fn test_raw_value_debug_display() {
+    let raw = RawValue::from_ron("/* hi */ (None, 4.2) /* bye */").unwrap();
+
+    assert_eq!(format!("{}", raw), "/* hi */ (None, 4.2) /* bye */");
+    assert_eq!(
+        format!("{:#?}", raw),
+        "\
+RawValue(
+    /* hi */ (None, 4.2) /* bye */,
+)\
+    "
+    );
+}
+
+#[test]
+fn test_boxed_raw_value_deserialise_from_string() {
+    let string = serde::de::value::StringDeserializer::<Error>::new(String::from("4.2"));
+
+    let err = <&RawValue>::deserialize(string.clone()).unwrap_err();
+    assert_eq!(
+        err,
+        Error::InvalidValueForType {
+            expected: String::from("any valid borrowed RON-value-string"),
+            found: String::from("the string \"4.2\""),
+        }
+    );
+
+    let boxed_raw = Box::<RawValue>::deserialize(string).unwrap();
+    assert_eq!(boxed_raw.get_ron(), "4.2");
+
+    let string = serde::de::value::StringDeserializer::<Error>::new(String::from("["));
+
+    let err = Box::<RawValue>::deserialize(string).unwrap_err();
+    assert_eq!(
+        err,
+        Error::Message(String::from(
+            "invalid RON value at 1:2: Unexpected end of RON"
+        ))
+    );
+}