extern crate alloc;
use alloc::string::ToString;
use crate::document::constructor::DocumentConstructor;
use crate::path::PathSegment;
use crate::value::ObjectKey;
use super::{IntoEure, WriteError};
pub struct RecordWriter<'a> {
constructor: &'a mut DocumentConstructor,
ext_mode: bool,
}
impl<'a> RecordWriter<'a> {
pub(crate) fn new(constructor: &'a mut DocumentConstructor) -> Self {
Self {
constructor,
ext_mode: false,
}
}
pub fn new_with_ext_mode(constructor: &'a mut DocumentConstructor, ext_mode: bool) -> Self {
Self {
constructor,
ext_mode,
}
}
pub fn field<T: IntoEure>(&mut self, name: &str, value: T) -> Result<(), T::Error> {
if self.ext_mode {
return self.constructor.set_extension(name, value);
}
let scope = self.constructor.begin_scope();
self.constructor
.navigate(PathSegment::Value(ObjectKey::String(name.to_string())))
.map_err(WriteError::from)?;
T::write(value, self.constructor)?;
self.constructor
.end_scope(scope)
.map_err(WriteError::from)?;
Ok(())
}
pub fn field_via<M, T>(&mut self, name: &str, value: T) -> Result<(), M::Error>
where
M: IntoEure<T>,
{
if self.ext_mode {
let ident: crate::identifier::Identifier = name
.parse()
.map_err(|_| WriteError::InvalidIdentifier(name.into()))?;
let scope = self.constructor.begin_scope();
self.constructor
.navigate(PathSegment::Extension(ident))
.map_err(WriteError::from)?;
M::write(value, self.constructor)?;
self.constructor
.end_scope(scope)
.map_err(WriteError::from)?;
return Ok(());
}
let scope = self.constructor.begin_scope();
self.constructor
.navigate(PathSegment::Value(ObjectKey::String(name.to_string())))
.map_err(WriteError::from)?;
M::write(value, self.constructor)?;
self.constructor
.end_scope(scope)
.map_err(WriteError::from)?;
Ok(())
}
pub fn field_optional<T: IntoEure>(
&mut self,
name: &str,
value: Option<T>,
) -> Result<(), T::Error> {
if let Some(v) = value {
self.field(name, v)?;
}
Ok(())
}
pub fn field_with<F, T>(&mut self, name: &str, f: F) -> Result<T, WriteError>
where
F: FnOnce(&mut DocumentConstructor) -> Result<T, WriteError>,
{
let scope = self.constructor.begin_scope();
self.constructor
.navigate(PathSegment::Value(ObjectKey::String(name.to_string())))
.map_err(WriteError::from)?;
let result = f(self.constructor)?;
self.constructor
.end_scope(scope)
.map_err(WriteError::from)?;
Ok(result)
}
pub fn field_with_optional<T, F, R>(
&mut self,
name: &str,
value: Option<T>,
f: F,
) -> Result<Option<R>, WriteError>
where
F: FnOnce(&mut DocumentConstructor, T) -> Result<R, WriteError>,
{
if let Some(v) = value {
let result = self.field_with(name, |c| f(c, v))?;
Ok(Some(result))
} else {
Ok(None)
}
}
pub fn flatten<M, T>(&mut self, value: T) -> Result<(), M::Error>
where
M: IntoEure<T>,
{
M::write_flatten(value, self)
}
pub fn flatten_ext<M, T>(&mut self, value: T) -> Result<(), M::Error>
where
M: IntoEure<T>,
{
let mut ext_rec = RecordWriter::new_with_ext_mode(self.constructor, true);
M::write_flatten(value, &mut ext_rec)
}
pub fn constructor(&mut self) -> &mut DocumentConstructor {
self.constructor
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::string::{String, ToString};
use super::*;
use crate::document::node::NodeValue;
use crate::text::Text;
use crate::value::PrimitiveValue;
#[test]
fn test_field() {
let mut c = DocumentConstructor::new();
c.record(|rec| {
rec.field("name", "Alice")?;
Ok::<(), WriteError>(())
})
.unwrap();
let doc = c.finish();
let map = doc.root().as_map().unwrap();
let name_id = map.get(&ObjectKey::String("name".to_string())).unwrap();
let node = doc.node(*name_id);
assert_eq!(
node.content,
NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
);
}
#[test]
fn test_field_optional_some() {
let mut c = DocumentConstructor::new();
c.record(|rec| {
rec.field_optional("age", Some(30i32))?;
Ok::<(), WriteError>(())
})
.unwrap();
let doc = c.finish();
let map = doc.root().as_map().unwrap();
assert!(map.get(&ObjectKey::String("age".to_string())).is_some());
}
#[test]
fn test_field_optional_none() {
let mut c = DocumentConstructor::new();
c.record(|rec| {
rec.field_optional::<i32>("age", None)?;
Ok::<(), WriteError>(())
})
.unwrap();
let doc = c.finish();
let map = doc.root().as_map().unwrap();
assert!(map.get(&ObjectKey::String("age".to_string())).is_none());
}
#[test]
fn test_field_with() {
let mut c = DocumentConstructor::new();
c.record(|rec| {
rec.field_with("nested", |c| {
c.record(|rec| {
rec.field("inner", "value")?;
Ok::<(), WriteError>(())
})
})?;
Ok::<(), WriteError>(())
})
.unwrap();
let doc = c.finish();
let map = doc.root().as_map().unwrap();
let nested_id = map.get(&ObjectKey::String("nested".to_string())).unwrap();
let nested = doc.node(*nested_id).as_map().unwrap();
assert!(
nested
.get(&ObjectKey::String("inner".to_string()))
.is_some()
);
}
#[test]
fn test_multiple_fields() {
let mut c = DocumentConstructor::new();
c.record(|rec| {
rec.field("name", "Bob")?;
rec.field("age", 25i32)?;
rec.field("active", true)?;
Ok::<(), WriteError>(())
})
.unwrap();
let doc = c.finish();
let map = doc.root().as_map().unwrap();
assert_eq!(map.len(), 3);
}
struct TestAddress {
city: String,
country: String,
}
impl IntoEure for TestAddress {
type Error = WriteError;
fn write(value: TestAddress, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
c.record(|rec| {
rec.field("city", value.city)?;
rec.field("country", value.country)?;
Ok::<(), WriteError>(())
})
}
fn write_flatten(
value: TestAddress,
rec: &mut super::RecordWriter<'_>,
) -> Result<(), Self::Error> {
rec.field("city", value.city)?;
rec.field("country", value.country)?;
Ok(())
}
}
#[test]
fn test_flatten() {
let mut c = DocumentConstructor::new();
c.record(|rec| {
rec.field("name", "Alice")?;
rec.flatten::<TestAddress, _>(TestAddress {
city: "Tokyo".to_string(),
country: "Japan".to_string(),
})?;
Ok::<(), WriteError>(())
})
.unwrap();
let doc = c.finish();
let map = doc.root().as_map().unwrap();
assert_eq!(map.len(), 3);
assert!(map.get(&ObjectKey::String("name".to_string())).is_some());
assert!(map.get(&ObjectKey::String("city".to_string())).is_some());
assert!(map.get(&ObjectKey::String("country".to_string())).is_some());
}
struct TestMeta {
version: i32,
deprecated: bool,
}
impl IntoEure for TestMeta {
type Error = WriteError;
fn write(value: TestMeta, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
c.record(|rec| {
rec.field("version", value.version)?;
rec.field("deprecated", value.deprecated)?;
Ok::<(), WriteError>(())
})
}
fn write_flatten(
value: TestMeta,
rec: &mut super::RecordWriter<'_>,
) -> Result<(), Self::Error> {
rec.field("version", value.version)?;
rec.field("deprecated", value.deprecated)?;
Ok(())
}
}
#[test]
fn test_flatten_ext() {
use crate::identifier::Identifier;
let mut c = DocumentConstructor::new();
c.record(|rec| {
rec.field("name", "test")?;
rec.flatten_ext::<TestMeta, _>(TestMeta {
version: 2,
deprecated: true,
})?;
Ok::<(), WriteError>(())
})
.unwrap();
let doc = c.finish();
let root = doc.root();
let map = root.as_map().unwrap();
assert_eq!(map.len(), 1);
assert!(map.get(&ObjectKey::String("name".to_string())).is_some());
assert!(
root.extensions
.contains_key(&"version".parse::<Identifier>().unwrap())
);
assert!(
root.extensions
.contains_key(&"deprecated".parse::<Identifier>().unwrap())
);
}
}