mod impls_collection;
mod impls_primitive;
mod impls_wrapper;
mod map_access;
pub mod number;
mod spanned;
use alloc::string::String;
use std::io::Read;
pub use map_access::AstMapAccess;
pub use number::{ParsedInt, parse_int_raw};
pub use spanned::Spanned;
use crate::{
Value,
ast::{Expr, expr_to_value, format_expr, parse_document, value_to_expr},
error::{Error, ErrorKind, Result},
fmt::FormatConfig,
};
pub trait ToRon {
fn to_ast(&self) -> Result<Expr<'static>>;
fn to_ron(&self) -> Result<String> {
Ok(format_expr(&self.to_ast()?, &FormatConfig::default()))
}
fn to_ron_with(&self, config: &FormatConfig) -> Result<String> {
Ok(format_expr(&self.to_ast()?, config))
}
fn to_ron_value(&self) -> Result<Value> {
expr_to_value(&self.to_ast()?)
}
fn to_typed_document(&self, config: &SerializeConfig) -> Result<Document<'static>>
where
Self: Sized,
{
let mut doc = Document {
source: Cow::Borrowed(""),
leading: Trivia::empty(),
attributes: Vec::new(),
pre_value: Trivia::empty(),
value: Some(self.to_ast()?),
trailing: Trivia::empty(),
};
if config.include_type_attribute {
let type_path = core::any::type_name::<Self>();
doc.attributes.push(Attribute::synthetic_type(type_path));
}
Ok(doc)
}
fn to_typed_ron(&self) -> Result<String>
where
Self: Sized,
{
let config = SerializeConfig::default();
let doc = self.to_typed_document(&config)?;
Ok(format_document(&doc, &config.format))
}
fn to_typed_ron_with(&self, config: &SerializeConfig) -> Result<String>
where
Self: Sized,
{
let doc = self.to_typed_document(config)?;
Ok(format_document(&doc, &config.format))
}
}
pub trait FromRon: Sized {
fn from_ast(expr: &Expr<'_>) -> Result<Self>;
fn from_ron_value(value: Value) -> Result<Self> {
let expr = value_to_expr(value);
Self::from_ast(&expr)
}
fn from_ron(s: &str) -> Result<Self> {
let doc = parse_document(s)?;
match doc.value {
Some(ref expr) => Self::from_ast(expr),
None => Err(Error::at_start(ErrorKind::Eof)),
}
}
fn from_ron_reader<R: Read>(mut reader: R) -> Result<Self> {
let mut buf = String::new();
reader.read_to_string(&mut buf).map_err(|e| {
Error::at_start(ErrorKind::Io {
message: e.to_string(),
source: None,
})
})?;
Self::from_ron(&buf)
}
}
pub trait FromRonFields: Sized {
fn from_fields(access: &mut AstMapAccess<'_>) -> Result<Self>;
}
fn invalid_value(msg: impl Into<String>) -> Error {
Error::new(crate::error::ErrorKind::Message(msg.into()))
}
pub(crate) fn extract_seq_elements<'a>(
expr: &'a Expr<'a>,
) -> Option<alloc::vec::Vec<&'a Expr<'a>>> {
match expr {
Expr::Seq(seq) => Some(seq.items.iter().map(|item| &item.expr).collect()),
Expr::Tuple(tuple) => Some(tuple.elements.iter().map(|elem| &elem.expr).collect()),
_ => None,
}
}
#[must_use]
pub fn expr_type_name(expr: &Expr<'_>) -> &'static str {
match expr {
Expr::Unit(_) => "unit",
Expr::Bool(_) => "bool",
Expr::Char(_) => "char",
Expr::Byte(_) => "byte",
Expr::Number(_) => "number",
Expr::String(_) => "string",
Expr::Bytes(_) => "bytes",
Expr::Option(_) => "option",
Expr::Seq(_) => "sequence",
Expr::Map(_) => "map",
Expr::Tuple(_) => "tuple",
Expr::AnonStruct(_) => "struct",
Expr::Struct(_) => "named",
Expr::Error(_) => "error",
}
}
pub(crate) fn spanned_type_mismatch(expected: &str, expr: &Expr<'_>) -> Error {
Error::with_span(
ErrorKind::TypeMismatch {
expected: expected.into(),
found: expr_type_name(expr).into(),
},
*expr.span(),
)
}
pub(crate) fn spanned_err(err: Error, expr: &Expr<'_>) -> Error {
if err.span().is_synthetic() {
Error::with_span(err.kind().clone(), *expr.span())
} else {
err
}
}
impl ToRon for Value {
fn to_ast(&self) -> Result<Expr<'static>> {
Ok(crate::ast::value_to_expr(self.clone()))
}
}
impl FromRon for Value {
fn from_ast(expr: &Expr<'_>) -> Result<Self> {
expr_to_value(expr)
}
}
use alloc::borrow::Cow;
use crate::{
ast::{Attribute, Document, Trivia},
fmt::format_document,
};
#[derive(Clone, Debug)]
pub struct SerializeConfig {
pub format: FormatConfig,
pub include_type_attribute: bool,
}
impl Default for SerializeConfig {
fn default() -> Self {
Self {
format: FormatConfig::default(),
include_type_attribute: true,
}
}
}
impl SerializeConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn without_type_attribute() -> Self {
Self {
include_type_attribute: false,
..Default::default()
}
}
#[must_use]
pub fn format(mut self, format: FormatConfig) -> Self {
self.format = format;
self
}
#[must_use]
pub fn type_attribute(mut self, include: bool) -> Self {
self.include_type_attribute = include;
self
}
}
#[cfg(test)]
mod tests {
use alloc::{collections::BTreeMap, vec, vec::Vec};
use super::*;
fn minimal(v: &impl ToRon) -> String {
v.to_ron_with(&FormatConfig::minimal()).unwrap()
}
#[test]
fn test_primitives_to_ron() {
assert_eq!(minimal(&true), "true");
assert_eq!(minimal(&false), "false");
assert_eq!(minimal(&42i32), "42");
assert_eq!(minimal(&(-10i8)), "-10");
assert_eq!(minimal(&'a'), "'a'");
assert_eq!(minimal(&"hello"), "\"hello\"");
assert_eq!(minimal(&()), "()");
}
#[test]
fn test_primitives_from_ron() {
assert!(bool::from_ron("true").unwrap());
assert!(!bool::from_ron("false").unwrap());
assert_eq!(i32::from_ron("42").unwrap(), 42);
assert_eq!(i32::from_ron("-10").unwrap(), -10);
assert_eq!(char::from_ron("'a'").unwrap(), 'a');
assert_eq!(String::from_ron("\"hello\"").unwrap(), "hello");
assert_eq!(<()>::from_ron("()").unwrap(), ());
}
#[test]
fn test_floats() {
assert!((f32::from_ron("1.5").unwrap() - 1.5).abs() < 0.001);
assert!((f64::from_ron("2.5").unwrap() - 2.5).abs() < 0.00001);
assert!((f64::from_ron("42").unwrap() - 42.0).abs() < 0.00001);
}
#[test]
fn test_collections() {
assert_eq!(minimal(&vec![1, 2, 3]), "[1,2,3]");
assert_eq!(minimal(&Vec::<i32>::new()), "[]");
assert_eq!(Vec::<i32>::from_ron("[1, 2, 3]").unwrap(), vec![1, 2, 3]);
assert_eq!(Vec::<i32>::from_ron("[]").unwrap(), Vec::<i32>::new());
}
#[test]
fn test_option() {
assert_eq!(minimal(&Some(42)), "Some(42)");
assert_eq!(minimal(&Option::<i32>::None), "None");
assert_eq!(Option::<i32>::from_ron("Some(42)").unwrap(), Some(42));
assert_eq!(Option::<i32>::from_ron("None").unwrap(), None);
assert_eq!(Option::<i32>::from_ron("42").unwrap(), Some(42));
}
#[test]
fn test_tuple() {
let tuple_ron = minimal(&(1, 2));
assert!(tuple_ron.contains('1') && tuple_ron.contains('2'));
assert_eq!(<(i32, i32)>::from_ron("(1, 2)").unwrap(), (1, 2));
assert_eq!(
<(i32, String, bool)>::from_ron("(1, \"hello\", true)").unwrap(),
(1, "hello".to_string(), true)
);
}
#[test]
fn test_map() {
let mut map = BTreeMap::new();
map.insert("a".to_string(), 1);
map.insert("b".to_string(), 2);
let ron = minimal(&map);
assert!(ron.contains("\"a\""));
assert!(ron.contains("\"b\""));
let ron = r#"{"a": 1, "b": 2}"#;
let parsed: BTreeMap<String, i32> = BTreeMap::from_ron(ron).unwrap();
assert_eq!(parsed.get("a"), Some(&1));
assert_eq!(parsed.get("b"), Some(&2));
}
#[test]
fn test_array() {
assert_eq!(<[i32; 3]>::from_ron("[1, 2, 3]").unwrap(), [1, 2, 3]);
}
#[test]
fn test_integer_range() {
assert!(i8::from_ron("127").is_ok());
assert!(i8::from_ron("128").is_err());
assert!(u8::from_ron("255").is_ok());
assert!(u8::from_ron("256").is_err());
assert!(u8::from_ron("-1").is_err());
}
#[test]
fn test_ast_map_access() {
use crate::ast::parse_document;
let ron = r#"(name: "test", value: 42)"#;
let doc = parse_document(ron).unwrap();
if let Some(Expr::AnonStruct(s)) = &doc.value {
let mut access = AstMapAccess::from_anon(s, Some("TestStruct")).unwrap();
assert_eq!(access.required::<String>("name").unwrap(), "test");
assert_eq!(access.required::<i32>("value").unwrap(), 42);
assert!(access.deny_unknown_fields(&["name", "value"]).is_ok());
} else {
panic!("Expected anonymous struct");
}
}
#[test]
fn test_spanned_error_line_numbers() {
let err = i32::from_ron(r#""not a number""#).unwrap_err();
assert_eq!(err.span().start.line, 1);
assert_eq!(err.span().start.col, 1);
assert_eq!(err.span().end.line, 1);
assert_eq!(err.span().end.col, 15);
let input = r#"[
1,
"wrong",
3
]"#;
let err = Vec::<i32>::from_ron(input).unwrap_err();
assert_eq!(err.span().start.line, 3);
assert_eq!(err.span().start.col, 5);
assert_eq!(err.span().end.line, 3);
assert_eq!(err.span().end.col, 12);
let input = r#"(
100,
"not an int"
)"#;
let err = <(i32, i32)>::from_ron(input).unwrap_err();
assert_eq!(err.span().start.line, 3);
let err = i8::from_ron("128").unwrap_err();
assert_eq!(err.span().start.line, 1);
assert_eq!(err.span().start.col, 1);
let err = bool::from_ron("42").unwrap_err();
assert_eq!(err.span().start.line, 1);
assert!(matches!(
err.kind(),
crate::error::ErrorKind::TypeMismatch { .. }
));
}
#[test]
fn test_spanned_error_in_map() {
let input = r#"{
"a": 1,
"b": "wrong"
}"#;
let err = BTreeMap::<String, i32>::from_ron(input).unwrap_err();
assert_eq!(err.span().start.line, 3);
assert_eq!(err.span().start.col, 10);
}
#[test]
fn test_from_ron_value_strips_span() {
use crate::Value;
let value = Value::String("not a number".to_string());
let err = i32::from_ron_value(value).unwrap_err();
assert!(matches!(
err.kind(),
crate::error::ErrorKind::TypeMismatch { .. }
));
}
#[test]
fn test_indexmap() {
use indexmap::IndexMap;
let mut map: IndexMap<String, i32> = IndexMap::default();
map.insert("first".to_string(), 1);
map.insert("second".to_string(), 2);
let ron = map.to_ron().unwrap();
assert!(ron.contains("\"first\""));
assert!(ron.contains("\"second\""));
let ron = r#"{"a": 1, "b": 2, "c": 3}"#;
let parsed: IndexMap<String, i32> = IndexMap::from_ron(ron).unwrap();
let keys: Vec<_> = parsed.keys().collect();
assert_eq!(keys, vec!["a", "b", "c"]);
assert_eq!(parsed.get("a"), Some(&1));
assert_eq!(parsed.get("b"), Some(&2));
assert_eq!(parsed.get("c"), Some(&3));
}
#[test]
fn test_indexset() {
use indexmap::IndexSet;
let mut set: IndexSet<i32> = IndexSet::default();
set.insert(3);
set.insert(1);
set.insert(2);
let ron = set.to_ron().unwrap();
assert!(ron.contains('3'));
assert!(ron.contains('1'));
assert!(ron.contains('2'));
let ron = "[3, 1, 2]";
let parsed: IndexSet<i32> = IndexSet::from_ron(ron).unwrap();
let values: Vec<_> = parsed.iter().copied().collect();
assert_eq!(values, vec![3, 1, 2]);
}
#[test]
fn test_value_from_ron() {
use crate::value::Number;
let value: Value = Value::from_ron("42").unwrap();
assert_eq!(value, Value::Number(Number::U8(42)));
let value: Value = Value::from_ron(r#""hello""#).unwrap();
assert_eq!(value, Value::String("hello".to_string()));
let value: Value = Value::from_ron("true").unwrap();
assert_eq!(value, Value::Bool(true));
let value: Value = Value::from_ron("[1, 2, 3]").unwrap();
assert_eq!(
value,
Value::Seq(vec![
Value::Number(Number::U8(1)),
Value::Number(Number::U8(2)),
Value::Number(Number::U8(3)),
])
);
let value: Value = Value::from_ron(r#"(name: "test", count: 5)"#).unwrap();
if let Value::Struct(fields) = value {
assert_eq!(fields.len(), 2);
assert_eq!(
fields[0],
("name".to_string(), Value::String("test".to_string()))
);
assert_eq!(
fields[1],
("count".to_string(), Value::Number(Number::U8(5)))
);
} else {
panic!("Expected Struct, got {value:?}");
}
let value: Value = Value::from_ron("Some(42)").unwrap();
assert_eq!(
value,
Value::Option(Some(Box::new(Value::Number(Number::U8(42)))))
);
let value: Value = Value::from_ron("None").unwrap();
assert_eq!(value, Value::Option(None));
}
#[test]
fn test_serialize_config_default() {
let config = super::SerializeConfig::default();
assert!(config.include_type_attribute);
}
#[test]
fn test_serialize_config_without_type_attribute() {
let config = super::SerializeConfig::without_type_attribute();
assert!(!config.include_type_attribute);
}
#[test]
fn test_serialize_config_builder() {
let config = super::SerializeConfig::new()
.type_attribute(false)
.format(FormatConfig::minimal());
assert!(!config.include_type_attribute);
}
#[test]
fn test_to_typed_ron_includes_type_attribute() {
use super::ToRon;
let result = 42i32.to_typed_ron().unwrap();
assert!(result.contains("#![type = \"i32\"]"));
assert!(result.contains("42"));
let hello = "hello".to_string();
let result = hello.to_typed_ron().unwrap();
assert!(result.contains("#![type"));
assert!(result.contains("String"));
assert!(result.contains("hello"));
let result = vec![1, 2, 3].to_typed_ron().unwrap();
assert!(result.contains("#![type"));
assert!(result.contains("Vec"));
}
#[test]
fn test_to_typed_document_structure() {
use super::ToRon;
let config = super::SerializeConfig::default();
let doc = 42i32.to_typed_document(&config).unwrap();
assert!(doc.value.is_some());
assert_eq!(doc.attributes.len(), 1);
}
#[test]
fn test_to_typed_ron_with_config() {
use super::ToRon;
let config =
super::SerializeConfig::without_type_attribute().format(FormatConfig::minimal());
let result = vec![1, 2, 3].to_typed_ron_with(&config).unwrap();
assert_eq!(result, "[1,2,3]");
}
}