1use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
34use links_notation::{parse_lino_to_links, LiNo};
35use std::collections::{HashMap, HashSet};
36use std::fmt;
37
38mod type_ids {
40 pub const NULL: &str = "null";
41 pub const BOOL: &str = "bool";
42 pub const INT: &str = "int";
43 pub const FLOAT: &str = "float";
44 pub const STR: &str = "str";
45 pub const ARRAY: &str = "array";
46 pub const OBJECT: &str = "object";
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum CodecError {
52 ParseError(String),
54 DecodeError(String),
56 UnknownType(String),
58}
59
60impl fmt::Display for CodecError {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match self {
63 CodecError::ParseError(msg) => write!(f, "Parse error: {}", msg),
64 CodecError::DecodeError(msg) => write!(f, "Decode error: {}", msg),
65 CodecError::UnknownType(t) => write!(f, "Unknown type marker: {}", t),
66 }
67 }
68}
69
70impl std::error::Error for CodecError {}
71
72#[derive(Debug, Clone)]
77pub enum LinoValue {
78 Null,
80 Bool(bool),
82 Int(i64),
84 Float(f64),
86 String(String),
88 Array(Vec<LinoValue>),
90 Object(Vec<(String, LinoValue)>),
92}
93
94impl PartialEq for LinoValue {
95 fn eq(&self, other: &Self) -> bool {
96 match (self, other) {
97 (LinoValue::Null, LinoValue::Null) => true,
98 (LinoValue::Bool(a), LinoValue::Bool(b)) => a == b,
99 (LinoValue::Int(a), LinoValue::Int(b)) => a == b,
100 (LinoValue::Float(a), LinoValue::Float(b)) => {
101 if a.is_nan() && b.is_nan() {
103 true
104 } else {
105 a == b
106 }
107 }
108 (LinoValue::String(a), LinoValue::String(b)) => a == b,
109 (LinoValue::Array(a), LinoValue::Array(b)) => a == b,
110 (LinoValue::Object(a), LinoValue::Object(b)) => {
111 if a.len() != b.len() {
113 return false;
114 }
115 let a_map: HashMap<&str, &LinoValue> =
117 a.iter().map(|(k, v)| (k.as_str(), v)).collect();
118 let b_map: HashMap<&str, &LinoValue> =
119 b.iter().map(|(k, v)| (k.as_str(), v)).collect();
120 a_map == b_map
121 }
122 _ => false,
123 }
124 }
125}
126
127impl LinoValue {
128 pub fn object<I, K, V>(iter: I) -> Self
130 where
131 I: IntoIterator<Item = (K, V)>,
132 K: Into<String>,
133 V: Into<LinoValue>,
134 {
135 LinoValue::Object(
136 iter.into_iter()
137 .map(|(k, v)| (k.into(), v.into()))
138 .collect(),
139 )
140 }
141
142 pub fn array<I, V>(iter: I) -> Self
144 where
145 I: IntoIterator<Item = V>,
146 V: Into<LinoValue>,
147 {
148 LinoValue::Array(iter.into_iter().map(|v| v.into()).collect())
149 }
150
151 pub fn is_null(&self) -> bool {
153 matches!(self, LinoValue::Null)
154 }
155
156 pub fn as_bool(&self) -> Option<bool> {
158 match self {
159 LinoValue::Bool(b) => Some(*b),
160 _ => None,
161 }
162 }
163
164 pub fn as_int(&self) -> Option<i64> {
166 match self {
167 LinoValue::Int(i) => Some(*i),
168 _ => None,
169 }
170 }
171
172 pub fn as_float(&self) -> Option<f64> {
174 match self {
175 LinoValue::Float(f) => Some(*f),
176 LinoValue::Int(i) => Some(*i as f64),
177 _ => None,
178 }
179 }
180
181 pub fn as_str(&self) -> Option<&str> {
183 match self {
184 LinoValue::String(s) => Some(s),
185 _ => None,
186 }
187 }
188
189 pub fn as_array(&self) -> Option<&Vec<LinoValue>> {
191 match self {
192 LinoValue::Array(a) => Some(a),
193 _ => None,
194 }
195 }
196
197 pub fn as_object(&self) -> Option<&Vec<(String, LinoValue)>> {
199 match self {
200 LinoValue::Object(o) => Some(o),
201 _ => None,
202 }
203 }
204
205 pub fn get(&self, key: &str) -> Option<&LinoValue> {
207 match self {
208 LinoValue::Object(o) => o.iter().find(|(k, _)| k == key).map(|(_, v)| v),
209 _ => None,
210 }
211 }
212
213 pub fn get_index(&self, index: usize) -> Option<&LinoValue> {
215 match self {
216 LinoValue::Array(a) => a.get(index),
217 _ => None,
218 }
219 }
220}
221
222impl From<()> for LinoValue {
224 fn from(_: ()) -> Self {
225 LinoValue::Null
226 }
227}
228
229impl From<bool> for LinoValue {
230 fn from(b: bool) -> Self {
231 LinoValue::Bool(b)
232 }
233}
234
235impl From<i32> for LinoValue {
236 fn from(i: i32) -> Self {
237 LinoValue::Int(i as i64)
238 }
239}
240
241impl From<i64> for LinoValue {
242 fn from(i: i64) -> Self {
243 LinoValue::Int(i)
244 }
245}
246
247impl From<f64> for LinoValue {
248 fn from(f: f64) -> Self {
249 LinoValue::Float(f)
250 }
251}
252
253impl From<&str> for LinoValue {
254 fn from(s: &str) -> Self {
255 LinoValue::String(s.to_string())
256 }
257}
258
259impl From<String> for LinoValue {
260 fn from(s: String) -> Self {
261 LinoValue::String(s)
262 }
263}
264
265impl<T: Into<LinoValue>> From<Vec<T>> for LinoValue {
266 fn from(v: Vec<T>) -> Self {
267 LinoValue::Array(v.into_iter().map(|x| x.into()).collect())
268 }
269}
270
271impl<T: Into<LinoValue>> From<Option<T>> for LinoValue {
272 fn from(opt: Option<T>) -> Self {
273 match opt {
274 Some(v) => v.into(),
275 None => LinoValue::Null,
276 }
277 }
278}
279
280pub struct ObjectCodec {
286 encode_counter: usize,
288 encode_memo: HashMap<String, String>,
290 needs_id: HashSet<String>,
292 all_definitions: Vec<(String, LiNo<String>)>,
294 decode_memo: HashMap<String, LinoValue>,
296 all_links: Vec<LiNo<String>>,
298}
299
300impl Default for ObjectCodec {
301 fn default() -> Self {
302 Self::new()
303 }
304}
305
306impl ObjectCodec {
307 pub fn new() -> Self {
309 ObjectCodec {
310 encode_counter: 0,
311 encode_memo: HashMap::new(),
312 needs_id: HashSet::new(),
313 all_definitions: Vec::new(),
314 decode_memo: HashMap::new(),
315 all_links: Vec::new(),
316 }
317 }
318
319 fn reset_encode_state(&mut self) {
321 self.encode_counter = 0;
322 self.encode_memo.clear();
323 self.needs_id.clear();
324 self.all_definitions.clear();
325 }
326
327 fn reset_decode_state(&mut self) {
329 self.decode_memo.clear();
330 self.all_links.clear();
331 }
332
333 fn make_link(&self, parts: &[&str]) -> LiNo<String> {
335 let values: Vec<LiNo<String>> = parts.iter().map(|p| LiNo::Ref(p.to_string())).collect();
336 LiNo::Link { id: None, values }
337 }
338
339 fn make_ref(&self, id: &str) -> LiNo<String> {
341 LiNo::Ref(id.to_string())
342 }
343
344 fn object_key(&self, value: &LinoValue) -> String {
346 format!("{:p}", value)
348 }
349
350 fn find_objects_needing_ids(&mut self, value: &LinoValue, seen: &mut HashMap<String, bool>) {
352 match value {
354 LinoValue::Array(arr) => {
355 let key = self.object_key(value);
356
357 if seen.contains_key(&key) {
358 self.needs_id.insert(key);
360 return;
361 }
362
363 seen.insert(key, true);
364
365 for item in arr {
366 self.find_objects_needing_ids(item, seen);
367 }
368 }
369 LinoValue::Object(obj) => {
370 let key = self.object_key(value);
371
372 if seen.contains_key(&key) {
373 self.needs_id.insert(key);
375 return;
376 }
377
378 seen.insert(key, true);
379
380 for (_, v) in obj {
381 self.find_objects_needing_ids(v, seen);
382 }
383 }
384 _ => {}
385 }
386 }
387
388 pub fn encode(&mut self, value: &LinoValue) -> String {
398 self.reset_encode_state();
399
400 let mut seen = HashMap::new();
402 self.find_objects_needing_ids(value, &mut seen);
403
404 let mut visited = HashSet::new();
406 let main_link = self.encode_value(value, &mut visited, 0);
407
408 if !self.all_definitions.is_empty() {
410 let mut all_links = vec![main_link];
411
412 for (ref_id, link) in &self.all_definitions {
414 let main_id = match &all_links[0] {
415 LiNo::Link { id: Some(id), .. } => Some(id.clone()),
416 _ => None,
417 };
418 if main_id.as_ref() != Some(ref_id) {
419 all_links.push(link.clone());
420 }
421 }
422
423 all_links
425 .iter()
426 .map(Self::format_link)
427 .collect::<Vec<_>>()
428 .join("\n")
429 } else {
430 Self::format_link(&main_link)
431 }
432 }
433
434 fn format_link(link: &LiNo<String>) -> String {
436 match link {
437 LiNo::Ref(s) => s.clone(),
438 LiNo::Link { id, values } => {
439 let inner: Vec<String> = values.iter().map(Self::format_link).collect();
440
441 if let Some(link_id) = id {
442 if inner.is_empty() {
443 format!("({}:)", link_id)
444 } else {
445 format!("({}: {})", link_id, inner.join(" "))
446 }
447 } else if inner.is_empty() {
448 "()".to_string()
449 } else {
450 format!("({})", inner.join(" "))
451 }
452 }
453 }
454 }
455
456 fn encode_value(
458 &mut self,
459 value: &LinoValue,
460 visited: &mut HashSet<String>,
461 depth: usize,
462 ) -> LiNo<String> {
463 match value {
464 LinoValue::Null => self.make_link(&[type_ids::NULL]),
465
466 LinoValue::Bool(b) => {
467 if *b {
468 self.make_link(&[type_ids::BOOL, "true"])
469 } else {
470 self.make_link(&[type_ids::BOOL, "false"])
471 }
472 }
473
474 LinoValue::Int(i) => self.make_link(&[type_ids::INT, &i.to_string()]),
475
476 LinoValue::Float(f) => {
477 if f.is_nan() {
478 self.make_link(&[type_ids::FLOAT, "NaN"])
479 } else if f.is_infinite() {
480 if f.is_sign_positive() {
481 self.make_link(&[type_ids::FLOAT, "Infinity"])
482 } else {
483 self.make_link(&[type_ids::FLOAT, "-Infinity"])
484 }
485 } else {
486 self.make_link(&[type_ids::FLOAT, &f.to_string()])
487 }
488 }
489
490 LinoValue::String(s) => {
491 let b64_encoded = BASE64.encode(s.as_bytes());
492 self.make_link(&[type_ids::STR, &b64_encoded])
493 }
494
495 LinoValue::Array(arr) => {
496 let obj_key = self.object_key(value);
497
498 if let Some(ref_id) = self.encode_memo.get(&obj_key).cloned() {
500 return self.make_ref(&ref_id);
501 }
502
503 let needs_id = self.needs_id.contains(&obj_key);
505
506 if needs_id {
507 if visited.contains(&obj_key) {
509 if let Some(ref_id) = self.encode_memo.get(&obj_key) {
511 return self.make_ref(ref_id);
512 }
513 }
514
515 let ref_id = format!("obj_{}", self.encode_counter);
517 self.encode_counter += 1;
518 self.encode_memo.insert(obj_key.clone(), ref_id.clone());
519 visited.insert(obj_key.clone());
520
521 let mut parts: Vec<LiNo<String>> = vec![LiNo::Ref(type_ids::ARRAY.to_string())];
523 for item in arr {
524 let item_link = self.encode_value(item, visited, depth + 1);
525 parts.push(item_link);
526 }
527
528 let definition = LiNo::Link {
529 id: Some(ref_id.clone()),
530 values: parts,
531 };
532
533 if depth > 0 {
535 self.all_definitions.push((ref_id.clone(), definition));
536 return self.make_ref(&ref_id);
537 }
538
539 definition
540 } else {
541 let mut parts: Vec<LiNo<String>> = vec![LiNo::Ref(type_ids::ARRAY.to_string())];
543 for item in arr {
544 let item_link = self.encode_value(item, visited, depth + 1);
545 parts.push(item_link);
546 }
547 LiNo::Link {
548 id: None,
549 values: parts,
550 }
551 }
552 }
553
554 LinoValue::Object(obj) => {
555 let obj_key = self.object_key(value);
556
557 if let Some(ref_id) = self.encode_memo.get(&obj_key).cloned() {
559 return self.make_ref(&ref_id);
560 }
561
562 let needs_id = self.needs_id.contains(&obj_key);
564
565 if needs_id {
566 if visited.contains(&obj_key) {
568 if let Some(ref_id) = self.encode_memo.get(&obj_key) {
570 return self.make_ref(ref_id);
571 }
572 }
573
574 let ref_id = format!("obj_{}", self.encode_counter);
576 self.encode_counter += 1;
577 self.encode_memo.insert(obj_key.clone(), ref_id.clone());
578 visited.insert(obj_key.clone());
579
580 let mut parts: Vec<LiNo<String>> =
582 vec![LiNo::Ref(type_ids::OBJECT.to_string())];
583 for (k, v) in obj {
584 let key_link =
585 self.encode_value(&LinoValue::String(k.clone()), visited, depth + 1);
586 let value_link = self.encode_value(v, visited, depth + 1);
587 let pair = LiNo::Link {
588 id: None,
589 values: vec![key_link, value_link],
590 };
591 parts.push(pair);
592 }
593
594 let definition = LiNo::Link {
595 id: Some(ref_id.clone()),
596 values: parts,
597 };
598
599 if depth > 0 {
601 self.all_definitions.push((ref_id.clone(), definition));
602 return self.make_ref(&ref_id);
603 }
604
605 definition
606 } else {
607 let mut parts: Vec<LiNo<String>> =
609 vec![LiNo::Ref(type_ids::OBJECT.to_string())];
610 for (k, v) in obj {
611 let key_link =
612 self.encode_value(&LinoValue::String(k.clone()), visited, depth + 1);
613 let value_link = self.encode_value(v, visited, depth + 1);
614 let pair = LiNo::Link {
615 id: None,
616 values: vec![key_link, value_link],
617 };
618 parts.push(pair);
619 }
620 LiNo::Link {
621 id: None,
622 values: parts,
623 }
624 }
625 }
626 }
627 }
628
629 pub fn decode(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
639 self.reset_decode_state();
640
641 let links = parse_lino_to_links(notation)
642 .map_err(|e| CodecError::ParseError(format!("{:?}", e)))?;
643
644 if links.is_empty() {
645 return Ok(LinoValue::Null);
646 }
647
648 if links.len() > 1 {
650 self.all_links = links.clone();
651 }
652
653 self.decode_link(&links[0])
655 }
656
657 fn decode_link(&mut self, link: &LiNo<String>) -> Result<LinoValue, CodecError> {
659 match link {
660 LiNo::Ref(id) => {
661 if let Some(value) = self.decode_memo.get(id) {
663 return Ok(value.clone());
664 }
665
666 if id.starts_with("obj_") && !self.all_links.is_empty() {
668 for other_link in self.all_links.clone() {
670 if let LiNo::Link {
671 id: Some(link_id), ..
672 } = &other_link
673 {
674 if link_id == id {
675 return self.decode_link(&other_link);
676 }
677 }
678 }
679 let result = LinoValue::Array(vec![]);
681 self.decode_memo.insert(id.clone(), result.clone());
682 return Ok(result);
683 }
684
685 match id.as_str() {
687 type_ids::NULL => return Ok(LinoValue::Null),
688 type_ids::ARRAY => return Ok(LinoValue::Array(vec![])),
689 type_ids::OBJECT => return Ok(LinoValue::Object(vec![])),
690 type_ids::STR => return Ok(LinoValue::String(String::new())),
691 _ => {}
692 }
693
694 Ok(LinoValue::String(id.clone()))
696 }
697
698 LiNo::Link { id, values } => {
699 let self_ref_id = id.as_ref().filter(|i| i.starts_with("obj_"));
701 if let Some(ref_id) = self_ref_id {
702 if let Some(value) = self.decode_memo.get(ref_id) {
703 return Ok(value.clone());
704 }
705 }
706
707 if values.is_empty() {
708 return Ok(LinoValue::Null);
709 }
710
711 let type_marker = match &values[0] {
713 LiNo::Ref(t) => t.as_str(),
714 LiNo::Link { .. } => return Ok(LinoValue::Null),
715 };
716
717 match type_marker {
718 type_ids::NULL => Ok(LinoValue::Null),
719
720 type_ids::BOOL => {
721 if values.len() > 1 {
722 if let LiNo::Ref(val) = &values[1] {
723 return Ok(LinoValue::Bool(val == "true"));
724 }
725 }
726 Ok(LinoValue::Bool(false))
727 }
728
729 type_ids::INT => {
730 if values.len() > 1 {
731 if let LiNo::Ref(val) = &values[1] {
732 if let Ok(i) = val.parse::<i64>() {
733 return Ok(LinoValue::Int(i));
734 }
735 }
736 }
737 Ok(LinoValue::Int(0))
738 }
739
740 type_ids::FLOAT => {
741 if values.len() > 1 {
742 if let LiNo::Ref(val) = &values[1] {
743 return match val.as_str() {
744 "NaN" => Ok(LinoValue::Float(f64::NAN)),
745 "Infinity" => Ok(LinoValue::Float(f64::INFINITY)),
746 "-Infinity" => Ok(LinoValue::Float(f64::NEG_INFINITY)),
747 s => {
748 if let Ok(f) = s.parse::<f64>() {
749 Ok(LinoValue::Float(f))
750 } else {
751 Ok(LinoValue::Float(0.0))
752 }
753 }
754 };
755 }
756 }
757 Ok(LinoValue::Float(0.0))
758 }
759
760 type_ids::STR => {
761 if values.len() > 1 {
762 if let LiNo::Ref(b64_str) = &values[1] {
763 if let Ok(bytes) = BASE64.decode(b64_str) {
764 if let Ok(s) = String::from_utf8(bytes) {
765 return Ok(LinoValue::String(s));
766 }
767 }
768 return Ok(LinoValue::String(b64_str.clone()));
770 }
771 }
772 Ok(LinoValue::String(String::new()))
773 }
774
775 type_ids::ARRAY => {
776 let result_array = LinoValue::Array(vec![]);
778 if let Some(ref_id) = self_ref_id {
779 self.decode_memo.insert(ref_id.clone(), result_array);
780 }
781
782 let mut items = Vec::new();
784 for item_link in values.iter().skip(1) {
785 let decoded = self.decode_link(item_link)?;
786 items.push(decoded);
787 }
788
789 let result = LinoValue::Array(items);
790
791 if let Some(ref_id) = self_ref_id {
793 self.decode_memo.insert(ref_id.clone(), result.clone());
794 }
795
796 Ok(result)
797 }
798
799 type_ids::OBJECT => {
800 let result_object = LinoValue::Object(vec![]);
802 if let Some(ref_id) = self_ref_id {
803 self.decode_memo.insert(ref_id.clone(), result_object);
804 }
805
806 let mut obj = Vec::new();
808 for pair_link in values.iter().skip(1) {
809 if let LiNo::Link { values: pair, .. } = pair_link {
810 if pair.len() >= 2 {
811 let key = self.decode_link(&pair[0])?;
812 let value = self.decode_link(&pair[1])?;
813
814 if let LinoValue::String(k) = key {
816 obj.push((k, value));
817 }
818 }
819 }
820 }
821
822 let result = LinoValue::Object(obj);
823
824 if let Some(ref_id) = self_ref_id {
826 self.decode_memo.insert(ref_id.clone(), result.clone());
827 }
828
829 Ok(result)
830 }
831
832 unknown => Err(CodecError::UnknownType(unknown.to_string())),
833 }
834 }
835 }
836 }
837}
838
839thread_local! {
841 static DEFAULT_CODEC: std::cell::RefCell<ObjectCodec> = std::cell::RefCell::new(ObjectCodec::new());
842}
843
844pub fn encode(value: &LinoValue) -> String {
870 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode(value))
871}
872
873pub fn decode(notation: &str) -> Result<LinoValue, CodecError> {
896 DEFAULT_CODEC.with(|codec| codec.borrow_mut().decode(notation))
897}
898
899pub mod format {
901 use super::{parse_lino_to_links, LiNo};
902 use std::collections::HashMap;
903
904 #[derive(Debug, Clone, PartialEq, Eq)]
906 pub enum FormatError {
907 MissingField(String),
909 InvalidInput(String),
911 }
912
913 impl std::fmt::Display for FormatError {
914 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
915 match self {
916 FormatError::MissingField(field) => write!(f, "Missing required field: {}", field),
917 FormatError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
918 }
919 }
920 }
921
922 impl std::error::Error for FormatError {}
923
924 pub fn escape_reference(value: &str) -> String {
936 let needs_escaping = value.chars().any(|c| {
938 c.is_whitespace() || c == '(' || c == ')' || c == '\'' || c == '"' || c == ':'
939 }) || value.contains('\n');
940
941 if !needs_escaping {
942 return value.to_string();
943 }
944
945 let has_single = value.contains('\'');
946 let has_double = value.contains('"');
947
948 if has_single && !has_double {
950 return format!("\"{}\"", value);
951 }
952
953 if has_double && !has_single {
955 return format!("'{}'", value);
956 }
957
958 if has_single && has_double {
960 let single_count = value.chars().filter(|&c| c == '\'').count();
961 let double_count = value.chars().filter(|&c| c == '"').count();
962
963 if double_count < single_count {
964 let escaped = value.replace('"', "\"\"");
966 return format!("\"{}\"", escaped);
967 }
968 let escaped = value.replace('\'', "''");
970 return format!("'{}'", escaped);
971 }
972
973 format!("'{}'", value)
975 }
976
977 pub fn unescape_reference(s: &str) -> String {
989 s.replace("\"\"", "\"").replace("''", "'")
990 }
991
992 fn format_indented_value(value: &str) -> String {
997 let has_single = value.contains('\'');
998 let has_double = value.contains('"');
999
1000 if has_double && !has_single {
1002 return format!("'{}'", value);
1003 }
1004
1005 if has_single && !has_double {
1007 return format!("\"{}\"", value);
1008 }
1009
1010 if has_single && has_double {
1012 let escaped = value.replace('\'', "''");
1013 return format!("'{}'", escaped);
1014 }
1015
1016 format!("\"{}\"", value)
1018 }
1019
1020 pub fn format_indented<S: ::std::hash::BuildHasher>(
1055 id: &str,
1056 obj: &HashMap<String, String, S>,
1057 indent: &str,
1058 ) -> Result<String, FormatError> {
1059 if id.is_empty() {
1060 return Err(FormatError::MissingField("id".to_string()));
1061 }
1062
1063 let mut lines = vec![id.to_string()];
1064
1065 for (key, value) in obj {
1066 let escaped_key = escape_reference(key);
1067 let formatted_value = format_indented_value(value);
1068 lines.push(format!("{}{} {}", indent, escaped_key, formatted_value));
1069 }
1070
1071 Ok(lines.join("\n"))
1072 }
1073
1074 pub fn format_indented_ordered(
1089 id: &str,
1090 pairs: &[(&str, &str)],
1091 indent: &str,
1092 ) -> Result<String, FormatError> {
1093 if id.is_empty() {
1094 return Err(FormatError::MissingField("id".to_string()));
1095 }
1096
1097 let mut lines = vec![id.to_string()];
1098
1099 for (key, value) in pairs {
1100 let escaped_key = escape_reference(key);
1101 let formatted_value = format_indented_value(value);
1102 lines.push(format!("{}{} {}", indent, escaped_key, formatted_value));
1103 }
1104
1105 Ok(lines.join("\n"))
1106 }
1107
1108 pub fn parse_indented(text: &str) -> Result<(String, HashMap<String, String>), FormatError> {
1148 if text.is_empty() {
1149 return Err(FormatError::InvalidInput(
1150 "text is required for parse_indented".to_string(),
1151 ));
1152 }
1153
1154 let lines: Vec<&str> = text.lines().collect();
1155 if lines.is_empty() {
1156 return Err(FormatError::InvalidInput(
1157 "text must have at least one line (the identifier)".to_string(),
1158 ));
1159 }
1160
1161 let non_empty_lines: Vec<&str> = lines
1164 .iter()
1165 .filter(|l| !l.trim().is_empty())
1166 .copied()
1167 .collect();
1168
1169 if non_empty_lines.is_empty() {
1170 return Err(FormatError::InvalidInput(
1171 "text must have at least one non-empty line (the identifier)".to_string(),
1172 ));
1173 }
1174
1175 let first_line = non_empty_lines[0].trim();
1178 let lino_text = if first_line.ends_with(':') {
1179 non_empty_lines.join("\n")
1180 } else {
1181 format!("{}:\n{}", first_line, non_empty_lines[1..].join("\n"))
1182 };
1183
1184 let parsed = parse_lino_to_links(&lino_text)
1186 .map_err(|e| FormatError::InvalidInput(format!("Parse error: {:?}", e)))?;
1187
1188 if parsed.is_empty() {
1189 return Err(FormatError::InvalidInput(
1190 "Failed to parse indented Links Notation".to_string(),
1191 ));
1192 }
1193
1194 let main_link = &parsed[0];
1196 let (result_id, values) = match main_link {
1197 LiNo::Link { id, values } => (id.clone().unwrap_or_default(), values),
1198 LiNo::Ref(id) => (id.clone(), &vec![]),
1199 };
1200
1201 let mut obj = HashMap::new();
1202
1203 for child in values {
1205 if let LiNo::Link {
1206 values: child_values,
1207 ..
1208 } = child
1209 {
1210 if child_values.len() == 2 {
1211 let key_ref = &child_values[0];
1212 let value_ref = &child_values[1];
1213
1214 let key = match key_ref {
1216 LiNo::Ref(k) => k.clone(),
1217 LiNo::Link { id, .. } => id.clone().unwrap_or_default(),
1218 };
1219
1220 let value = match value_ref {
1222 LiNo::Ref(v) => v.clone(),
1223 LiNo::Link { id, .. } => id.clone().unwrap_or_default(),
1224 };
1225
1226 obj.insert(key, value);
1227 }
1228 }
1229 }
1230
1231 Ok((result_id, obj))
1232 }
1233}
1234
1235#[cfg(test)]
1236mod tests {
1237 use super::*;
1238
1239 #[test]
1240 fn test_roundtrip_null() {
1241 let original = LinoValue::Null;
1242 let encoded = encode(&original);
1243 let decoded = decode(&encoded).unwrap();
1244 assert_eq!(decoded, original);
1245 }
1246
1247 #[test]
1248 fn test_roundtrip_bool() {
1249 for value in [true, false] {
1250 let original = LinoValue::Bool(value);
1251 let encoded = encode(&original);
1252 let decoded = decode(&encoded).unwrap();
1253 assert_eq!(decoded, original);
1254 }
1255 }
1256
1257 #[test]
1258 fn test_roundtrip_int() {
1259 let test_values: Vec<i64> = vec![0, 1, -1, 42, -42, 123456789, -123456789];
1260 for value in test_values {
1261 let original = LinoValue::Int(value);
1262 let encoded = encode(&original);
1263 let decoded = decode(&encoded).unwrap();
1264 assert_eq!(decoded.as_int(), Some(value));
1265 }
1266 }
1267
1268 #[test]
1269 fn test_roundtrip_float() {
1270 let test_values: Vec<f64> = vec![0.0, 1.0, -1.0, 3.14, -3.14, 0.123456789, -999.999];
1271 for value in test_values {
1272 let original = LinoValue::Float(value);
1273 let encoded = encode(&original);
1274 let decoded = decode(&encoded).unwrap();
1275 let decoded_f = decoded.as_float().unwrap();
1276 assert!((decoded_f - value).abs() < 0.0001);
1277 }
1278 }
1279
1280 #[test]
1281 fn test_float_special_values() {
1282 let inf = LinoValue::Float(f64::INFINITY);
1284 let encoded = encode(&inf);
1285 let decoded = decode(&encoded).unwrap();
1286 let decoded_f = decoded.as_float().unwrap();
1287 assert!(decoded_f.is_infinite());
1288 assert!(decoded_f.is_sign_positive());
1289
1290 let neg_inf = LinoValue::Float(f64::NEG_INFINITY);
1292 let encoded = encode(&neg_inf);
1293 let decoded = decode(&encoded).unwrap();
1294 let decoded_f = decoded.as_float().unwrap();
1295 assert!(decoded_f.is_infinite());
1296 assert!(decoded_f.is_sign_negative());
1297
1298 let nan = LinoValue::Float(f64::NAN);
1300 let encoded = encode(&nan);
1301 let decoded = decode(&encoded).unwrap();
1302 let decoded_f = decoded.as_float().unwrap();
1303 assert!(decoded_f.is_nan());
1304 }
1305
1306 #[test]
1307 fn test_roundtrip_string() {
1308 let test_values = [
1309 "",
1310 "hello",
1311 "hello world",
1312 "Hello, World!",
1313 "multi\nline\nstring",
1314 "tab\tseparated",
1315 "special chars: @#$%^&*()",
1316 ];
1317 for value in test_values {
1318 let original = LinoValue::String(value.to_string());
1319 let encoded = encode(&original);
1320 let decoded = decode(&encoded).unwrap();
1321 assert_eq!(decoded.as_str(), Some(value));
1322 }
1323 }
1324
1325 #[test]
1326 fn test_string_with_quotes() {
1327 let test_values = [
1328 "string with 'single quotes'",
1329 "string with \"double quotes\"",
1330 "string with \"both\" 'quotes'",
1331 ];
1332 for value in test_values {
1333 let original = LinoValue::String(value.to_string());
1334 let encoded = encode(&original);
1335 let decoded = decode(&encoded).unwrap();
1336 assert_eq!(decoded.as_str(), Some(value));
1337 }
1338 }
1339
1340 #[test]
1341 fn test_unicode_string() {
1342 let value = "unicode: 你好世界 🌍";
1343 let original = LinoValue::String(value.to_string());
1344 let encoded = encode(&original);
1345 let decoded = decode(&encoded).unwrap();
1346 assert_eq!(decoded.as_str(), Some(value));
1347 }
1348
1349 #[test]
1350 fn test_roundtrip_empty_array() {
1351 let original = LinoValue::Array(vec![]);
1352 let encoded = encode(&original);
1353 let decoded = decode(&encoded).unwrap();
1354 assert_eq!(decoded, original);
1355 }
1356
1357 #[test]
1358 fn test_roundtrip_simple_array() {
1359 let original = LinoValue::Array(vec![
1360 LinoValue::Int(1),
1361 LinoValue::Int(2),
1362 LinoValue::Int(3),
1363 ]);
1364 let encoded = encode(&original);
1365 let decoded = decode(&encoded).unwrap();
1366 assert_eq!(decoded, original);
1367 }
1368
1369 #[test]
1370 fn test_roundtrip_mixed_array() {
1371 let original = LinoValue::Array(vec![
1372 LinoValue::Int(1),
1373 LinoValue::String("hello".to_string()),
1374 LinoValue::Bool(true),
1375 LinoValue::Null,
1376 ]);
1377 let encoded = encode(&original);
1378 let decoded = decode(&encoded).unwrap();
1379 assert_eq!(decoded, original);
1380 }
1381
1382 #[test]
1383 fn test_nested_arrays() {
1384 let original = LinoValue::Array(vec![LinoValue::Array(vec![])]);
1385 let encoded = encode(&original);
1386 let decoded = decode(&encoded).unwrap();
1387 assert_eq!(decoded, original);
1388
1389 let original2 = LinoValue::Array(vec![
1390 LinoValue::Array(vec![LinoValue::Int(1), LinoValue::Int(2)]),
1391 LinoValue::Array(vec![LinoValue::Int(3), LinoValue::Int(4)]),
1392 ]);
1393 let encoded2 = encode(&original2);
1394 let decoded2 = decode(&encoded2).unwrap();
1395 assert_eq!(decoded2, original2);
1396 }
1397
1398 #[test]
1399 fn test_roundtrip_empty_object() {
1400 let original = LinoValue::Object(vec![]);
1401 let encoded = encode(&original);
1402 let decoded = decode(&encoded).unwrap();
1403 assert_eq!(decoded, original);
1404 }
1405
1406 #[test]
1407 fn test_roundtrip_simple_object() {
1408 let original = LinoValue::object([("a", LinoValue::Int(1)), ("b", LinoValue::Int(2))]);
1409 let encoded = encode(&original);
1410 let decoded = decode(&encoded).unwrap();
1411 assert_eq!(decoded, original);
1412 }
1413
1414 #[test]
1415 fn test_nested_objects() {
1416 let original = LinoValue::object([(
1417 "user",
1418 LinoValue::object([
1419 ("name", LinoValue::String("Alice".to_string())),
1420 (
1421 "address",
1422 LinoValue::object([
1423 ("city", LinoValue::String("NYC".to_string())),
1424 ("zip", LinoValue::String("10001".to_string())),
1425 ]),
1426 ),
1427 ]),
1428 )]);
1429 let encoded = encode(&original);
1430 let decoded = decode(&encoded).unwrap();
1431 assert_eq!(decoded, original);
1432 }
1433
1434 #[test]
1435 fn test_complex_structure() {
1436 let original = LinoValue::object([
1437 ("id", LinoValue::Int(123)),
1438 ("name", LinoValue::String("Test Object".to_string())),
1439 ("active", LinoValue::Bool(true)),
1440 (
1441 "tags",
1442 LinoValue::array([
1443 LinoValue::String("tag1".to_string()),
1444 LinoValue::String("tag2".to_string()),
1445 LinoValue::String("tag3".to_string()),
1446 ]),
1447 ),
1448 (
1449 "metadata",
1450 LinoValue::object([
1451 ("created", LinoValue::String("2025-01-01".to_string())),
1452 ("modified", LinoValue::Null),
1453 ("count", LinoValue::Int(42)),
1454 ]),
1455 ),
1456 (
1457 "items",
1458 LinoValue::array([
1459 LinoValue::object([
1460 ("id", LinoValue::Int(1)),
1461 ("value", LinoValue::String("first".to_string())),
1462 ]),
1463 LinoValue::object([
1464 ("id", LinoValue::Int(2)),
1465 ("value", LinoValue::String("second".to_string())),
1466 ]),
1467 ]),
1468 ),
1469 ]);
1470 let encoded = encode(&original);
1471 let decoded = decode(&encoded).unwrap();
1472 assert_eq!(decoded, original);
1473 }
1474
1475 #[test]
1476 fn test_list_of_dicts() {
1477 let original = LinoValue::array([
1478 LinoValue::object([
1479 ("name", LinoValue::String("Alice".to_string())),
1480 ("age", LinoValue::Int(30)),
1481 ]),
1482 LinoValue::object([
1483 ("name", LinoValue::String("Bob".to_string())),
1484 ("age", LinoValue::Int(25)),
1485 ]),
1486 ]);
1487 let encoded = encode(&original);
1488 let decoded = decode(&encoded).unwrap();
1489 assert_eq!(decoded, original);
1490 }
1491
1492 #[test]
1493 fn test_dict_of_lists() {
1494 let original = LinoValue::object([
1495 (
1496 "numbers",
1497 LinoValue::array([LinoValue::Int(1), LinoValue::Int(2), LinoValue::Int(3)]),
1498 ),
1499 (
1500 "strings",
1501 LinoValue::array([
1502 LinoValue::String("a".to_string()),
1503 LinoValue::String("b".to_string()),
1504 LinoValue::String("c".to_string()),
1505 ]),
1506 ),
1507 ]);
1508 let encoded = encode(&original);
1509 let decoded = decode(&encoded).unwrap();
1510 assert_eq!(decoded, original);
1511 }
1512}
1513
1514#[cfg(test)]
1515mod format_tests {
1516 use super::format::*;
1517 use std::collections::HashMap;
1518
1519 #[test]
1520 fn test_escape_reference_simple_string() {
1521 assert_eq!(escape_reference("hello"), "hello");
1522 assert_eq!(escape_reference("world"), "world");
1523 }
1524
1525 #[test]
1526 fn test_escape_reference_string_with_spaces() {
1527 let result = escape_reference("hello world");
1528 assert!(result.starts_with('\'') || result.starts_with('"'));
1529 assert!(result.contains("hello world"));
1530 }
1531
1532 #[test]
1533 fn test_escape_reference_string_with_single_quotes() {
1534 let result = escape_reference("it's");
1535 assert_eq!(result, "\"it's\"");
1536 }
1537
1538 #[test]
1539 fn test_escape_reference_string_with_double_quotes() {
1540 let result = escape_reference("he said \"hello\"");
1541 assert_eq!(result, "'he said \"hello\"'");
1542 }
1543
1544 #[test]
1545 fn test_unescape_reference_doubled_quotes() {
1546 assert_eq!(
1547 unescape_reference("he said \"\"hello\"\""),
1548 "he said \"hello\""
1549 );
1550 assert_eq!(unescape_reference("it''s"), "it's");
1551 }
1552
1553 #[test]
1554 fn test_format_indented_ordered_basic() {
1555 let pairs = [
1556 ("uuid", "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019"),
1557 ("status", "executed"),
1558 ("command", "echo test"),
1559 ("exitCode", "0"),
1560 ];
1561 let result =
1562 format_indented_ordered("6dcf4c1b-ff3f-482c-95ab-711ea7d1b019", &pairs, " ").unwrap();
1563 let lines: Vec<&str> = result.lines().collect();
1564 assert_eq!(lines[0], "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1565 assert_eq!(lines[1], " uuid \"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\"");
1566 assert_eq!(lines[2], " status \"executed\"");
1567 assert_eq!(lines[3], " command \"echo test\"");
1568 assert_eq!(lines[4], " exitCode \"0\"");
1569 }
1570
1571 #[test]
1572 fn test_format_indented_value_with_quotes() {
1573 let pairs = [("message", "He said \"hello\"")];
1575 let result = format_indented_ordered("test-id", &pairs, " ").unwrap();
1576 let lines: Vec<&str> = result.lines().collect();
1577 assert_eq!(lines[1], " message 'He said \"hello\"'");
1578 }
1579
1580 #[test]
1581 fn test_format_indented_requires_id() {
1582 let mut obj = HashMap::new();
1583 obj.insert("key".to_string(), "value".to_string());
1584 let result = format_indented("", &obj, " ");
1585 assert!(result.is_err());
1586 }
1587
1588 #[test]
1589 fn test_parse_indented_basic() {
1590 let text = "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\n uuid \"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\"\n status \"executed\"\n exitCode \"0\"";
1591 let (id, obj) = parse_indented(text).unwrap();
1592 assert_eq!(id, "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1593 assert_eq!(
1594 obj.get("uuid"),
1595 Some(&"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019".to_string())
1596 );
1597 assert_eq!(obj.get("status"), Some(&"executed".to_string()));
1598 assert_eq!(obj.get("exitCode"), Some(&"0".to_string()));
1599 }
1600
1601 #[test]
1602 fn test_parse_indented_with_quotes() {
1603 let text = "test-id\n message 'He said \"hello\"'";
1605 let (id, obj) = parse_indented(text).unwrap();
1606 assert_eq!(id, "test-id");
1607 assert_eq!(obj.get("message"), Some(&"He said \"hello\"".to_string()));
1608 }
1609
1610 #[test]
1611 fn test_parse_indented_empty_lines_skipped() {
1612 let text = "test-id\n\n key \"value\"\n\n another \"value2\"";
1613 let (id, obj) = parse_indented(text).unwrap();
1614 assert_eq!(id, "test-id");
1615 assert_eq!(obj.get("key"), Some(&"value".to_string()));
1616 assert_eq!(obj.get("another"), Some(&"value2".to_string()));
1617 }
1618
1619 #[test]
1620 fn test_parse_indented_requires_text() {
1621 let result = parse_indented("");
1622 assert!(result.is_err());
1623 }
1624
1625 #[test]
1626 fn test_roundtrip_format_indented() {
1627 let pairs = [
1628 ("uuid", "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019"),
1629 ("status", "executed"),
1630 ("command", "echo test"),
1631 ("exitCode", "0"),
1632 ];
1633 let formatted =
1634 format_indented_ordered("6dcf4c1b-ff3f-482c-95ab-711ea7d1b019", &pairs, " ").unwrap();
1635 let (parsed_id, parsed_obj) = parse_indented(&formatted).unwrap();
1636
1637 assert_eq!(parsed_id, "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1638 for (key, value) in pairs {
1639 assert_eq!(parsed_obj.get(key), Some(&value.to_string()));
1640 }
1641 }
1642
1643 #[test]
1644 fn test_roundtrip_with_quotes() {
1645 let pairs = [("message", "He said \"hello\"")];
1646 let formatted = format_indented_ordered("test-id", &pairs, " ").unwrap();
1647 let (parsed_id, parsed_obj) = parse_indented(&formatted).unwrap();
1648
1649 assert_eq!(parsed_id, "test-id");
1650 assert_eq!(
1651 parsed_obj.get("message"),
1652 Some(&"He said \"hello\"".to_string())
1653 );
1654 }
1655}