1extern crate alloc;
4
5use alloc::string::ToString;
6
7use crate::document::constructor::DocumentConstructor;
8use crate::path::PathSegment;
9use crate::value::ObjectKey;
10
11use super::{IntoEure, WriteError};
12
13pub struct RecordWriter<'a> {
30 constructor: &'a mut DocumentConstructor,
31 ext_mode: bool,
32}
33
34impl<'a> RecordWriter<'a> {
35 pub(crate) fn new(constructor: &'a mut DocumentConstructor) -> Self {
37 Self {
38 constructor,
39 ext_mode: false,
40 }
41 }
42
43 pub fn new_with_ext_mode(constructor: &'a mut DocumentConstructor, ext_mode: bool) -> Self {
48 Self {
49 constructor,
50 ext_mode,
51 }
52 }
53
54 pub fn field<T: IntoEure>(&mut self, name: &str, value: T) -> Result<(), T::Error> {
64 if self.ext_mode {
65 return self.constructor.set_extension(name, value);
66 }
67 let scope = self.constructor.begin_scope();
68 self.constructor
69 .navigate(PathSegment::Value(ObjectKey::String(name.to_string())))
70 .map_err(WriteError::from)?;
71 T::write(value, self.constructor)?;
72 self.constructor
73 .end_scope(scope)
74 .map_err(WriteError::from)?;
75 Ok(())
76 }
77
78 pub fn field_via<M, T>(&mut self, name: &str, value: T) -> Result<(), M::Error>
92 where
93 M: IntoEure<T>,
94 {
95 if self.ext_mode {
96 let ident: crate::identifier::Identifier = name
97 .parse()
98 .map_err(|_| WriteError::InvalidIdentifier(name.into()))?;
99 let scope = self.constructor.begin_scope();
100 self.constructor
101 .navigate(PathSegment::Extension(ident))
102 .map_err(WriteError::from)?;
103 M::write(value, self.constructor)?;
104 self.constructor
105 .end_scope(scope)
106 .map_err(WriteError::from)?;
107 return Ok(());
108 }
109 let scope = self.constructor.begin_scope();
110 self.constructor
111 .navigate(PathSegment::Value(ObjectKey::String(name.to_string())))
112 .map_err(WriteError::from)?;
113 M::write(value, self.constructor)?;
114 self.constructor
115 .end_scope(scope)
116 .map_err(WriteError::from)?;
117 Ok(())
118 }
119
120 pub fn field_optional<T: IntoEure>(
129 &mut self,
130 name: &str,
131 value: Option<T>,
132 ) -> Result<(), T::Error> {
133 if let Some(v) = value {
134 self.field(name, v)?;
135 }
136 Ok(())
137 }
138
139 pub fn field_with<F, T>(&mut self, name: &str, f: F) -> Result<T, WriteError>
154 where
155 F: FnOnce(&mut DocumentConstructor) -> Result<T, WriteError>,
156 {
157 let scope = self.constructor.begin_scope();
158 self.constructor
159 .navigate(PathSegment::Value(ObjectKey::String(name.to_string())))
160 .map_err(WriteError::from)?;
161 let result = f(self.constructor)?;
162 self.constructor
163 .end_scope(scope)
164 .map_err(WriteError::from)?;
165 Ok(result)
166 }
167
168 pub fn field_with_optional<T, F, R>(
179 &mut self,
180 name: &str,
181 value: Option<T>,
182 f: F,
183 ) -> Result<Option<R>, WriteError>
184 where
185 F: FnOnce(&mut DocumentConstructor, T) -> Result<R, WriteError>,
186 {
187 if let Some(v) = value {
188 let result = self.field_with(name, |c| f(c, v))?;
189 Ok(Some(result))
190 } else {
191 Ok(None)
192 }
193 }
194
195 pub fn flatten<M, T>(&mut self, value: T) -> Result<(), M::Error>
206 where
207 M: IntoEure<T>,
208 {
209 M::write_flatten(value, self)
210 }
211
212 pub fn flatten_ext<M, T>(&mut self, value: T) -> Result<(), M::Error>
223 where
224 M: IntoEure<T>,
225 {
226 let mut ext_rec = RecordWriter::new_with_ext_mode(self.constructor, true);
227 M::write_flatten(value, &mut ext_rec)
228 }
229
230 pub fn constructor(&mut self) -> &mut DocumentConstructor {
234 self.constructor
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 extern crate alloc;
241 use alloc::string::{String, ToString};
242
243 use super::*;
244 use crate::document::node::NodeValue;
245 use crate::text::Text;
246 use crate::value::PrimitiveValue;
247
248 #[test]
249 fn test_field() {
250 let mut c = DocumentConstructor::new();
251 c.record(|rec| {
252 rec.field("name", "Alice")?;
253 Ok::<(), WriteError>(())
254 })
255 .unwrap();
256 let doc = c.finish();
257 let map = doc.root().as_map().unwrap();
258 let name_id = map.get(&ObjectKey::String("name".to_string())).unwrap();
259 let node = doc.node(*name_id);
260 assert_eq!(
261 node.content,
262 NodeValue::Primitive(PrimitiveValue::Text(Text::plaintext("Alice")))
263 );
264 }
265
266 #[test]
267 fn test_field_optional_some() {
268 let mut c = DocumentConstructor::new();
269 c.record(|rec| {
270 rec.field_optional("age", Some(30i32))?;
271 Ok::<(), WriteError>(())
272 })
273 .unwrap();
274 let doc = c.finish();
275 let map = doc.root().as_map().unwrap();
276 assert!(map.get(&ObjectKey::String("age".to_string())).is_some());
277 }
278
279 #[test]
280 fn test_field_optional_none() {
281 let mut c = DocumentConstructor::new();
282 c.record(|rec| {
283 rec.field_optional::<i32>("age", None)?;
284 Ok::<(), WriteError>(())
285 })
286 .unwrap();
287 let doc = c.finish();
288 let map = doc.root().as_map().unwrap();
289 assert!(map.get(&ObjectKey::String("age".to_string())).is_none());
290 }
291
292 #[test]
293 fn test_field_with() {
294 let mut c = DocumentConstructor::new();
295 c.record(|rec| {
296 rec.field_with("nested", |c| {
297 c.record(|rec| {
298 rec.field("inner", "value")?;
299 Ok::<(), WriteError>(())
300 })
301 })?;
302 Ok::<(), WriteError>(())
303 })
304 .unwrap();
305 let doc = c.finish();
306 let map = doc.root().as_map().unwrap();
307 let nested_id = map.get(&ObjectKey::String("nested".to_string())).unwrap();
308 let nested = doc.node(*nested_id).as_map().unwrap();
309 assert!(
310 nested
311 .get(&ObjectKey::String("inner".to_string()))
312 .is_some()
313 );
314 }
315
316 #[test]
317 fn test_multiple_fields() {
318 let mut c = DocumentConstructor::new();
319 c.record(|rec| {
320 rec.field("name", "Bob")?;
321 rec.field("age", 25i32)?;
322 rec.field("active", true)?;
323 Ok::<(), WriteError>(())
324 })
325 .unwrap();
326 let doc = c.finish();
327 let map = doc.root().as_map().unwrap();
328 assert_eq!(map.len(), 3);
329 }
330
331 struct TestAddress {
333 city: String,
334 country: String,
335 }
336
337 impl IntoEure for TestAddress {
338 type Error = WriteError;
339
340 fn write(value: TestAddress, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
341 c.record(|rec| {
342 rec.field("city", value.city)?;
343 rec.field("country", value.country)?;
344 Ok::<(), WriteError>(())
345 })
346 }
347
348 fn write_flatten(
349 value: TestAddress,
350 rec: &mut super::RecordWriter<'_>,
351 ) -> Result<(), Self::Error> {
352 rec.field("city", value.city)?;
353 rec.field("country", value.country)?;
354 Ok(())
355 }
356 }
357
358 #[test]
359 fn test_flatten() {
360 let mut c = DocumentConstructor::new();
361 c.record(|rec| {
362 rec.field("name", "Alice")?;
363 rec.flatten::<TestAddress, _>(TestAddress {
364 city: "Tokyo".to_string(),
365 country: "Japan".to_string(),
366 })?;
367 Ok::<(), WriteError>(())
368 })
369 .unwrap();
370 let doc = c.finish();
371 let map = doc.root().as_map().unwrap();
372 assert_eq!(map.len(), 3);
373 assert!(map.get(&ObjectKey::String("name".to_string())).is_some());
374 assert!(map.get(&ObjectKey::String("city".to_string())).is_some());
375 assert!(map.get(&ObjectKey::String("country".to_string())).is_some());
376 }
377
378 struct TestMeta {
379 version: i32,
380 deprecated: bool,
381 }
382
383 impl IntoEure for TestMeta {
384 type Error = WriteError;
385
386 fn write(value: TestMeta, c: &mut DocumentConstructor) -> Result<(), Self::Error> {
387 c.record(|rec| {
388 rec.field("version", value.version)?;
389 rec.field("deprecated", value.deprecated)?;
390 Ok::<(), WriteError>(())
391 })
392 }
393
394 fn write_flatten(
395 value: TestMeta,
396 rec: &mut super::RecordWriter<'_>,
397 ) -> Result<(), Self::Error> {
398 rec.field("version", value.version)?;
399 rec.field("deprecated", value.deprecated)?;
400 Ok(())
401 }
402 }
403
404 #[test]
405 fn test_flatten_ext() {
406 use crate::identifier::Identifier;
407
408 let mut c = DocumentConstructor::new();
409 c.record(|rec| {
410 rec.field("name", "test")?;
411 rec.flatten_ext::<TestMeta, _>(TestMeta {
412 version: 2,
413 deprecated: true,
414 })?;
415 Ok::<(), WriteError>(())
416 })
417 .unwrap();
418 let doc = c.finish();
419 let root = doc.root();
420 let map = root.as_map().unwrap();
422 assert_eq!(map.len(), 1);
423 assert!(map.get(&ObjectKey::String("name".to_string())).is_some());
424 assert!(
426 root.extensions
427 .contains_key(&"version".parse::<Identifier>().unwrap())
428 );
429 assert!(
430 root.extensions
431 .contains_key(&"deprecated".parse::<Identifier>().unwrap())
432 );
433 }
434}