1use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
52use links_notation::{LiNo, parse_lino_to_links};
53use std::collections::{HashMap, HashSet};
54use std::fmt;
55
56pub mod debug;
57#[cfg(feature = "serde_json")]
58pub mod json;
59pub mod readable;
60
61pub use readable::{BASE64_MARKER, DEFAULT_INDENT, ESCAPED_MARKER, OBJECT_MARKER};
62
63mod type_ids {
65 pub const NULL: &str = "null";
66 pub const BOOL: &str = "bool";
67 pub const INT: &str = "int";
68 pub const FLOAT: &str = "float";
69 pub const STR: &str = "str";
70 pub const ARRAY: &str = "array";
71 pub const OBJECT: &str = "object";
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum CodecError {
77 ParseError(String),
79 DecodeError(String),
81 UnknownType(String),
83}
84
85impl fmt::Display for CodecError {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 match self {
88 CodecError::ParseError(msg) => write!(f, "Parse error: {}", msg),
89 CodecError::DecodeError(msg) => write!(f, "Decode error: {}", msg),
90 CodecError::UnknownType(t) => write!(f, "Unknown type marker: {}", t),
91 }
92 }
93}
94
95impl std::error::Error for CodecError {}
96
97#[derive(Debug, Clone)]
102pub enum LinoValue {
103 Null,
105 Bool(bool),
107 Int(i64),
109 Float(f64),
111 #[cfg(feature = "serde_json")]
113 JsonNumber(serde_json::Number),
114 String(String),
116 Array(Vec<LinoValue>),
118 Object(Vec<(String, LinoValue)>),
120}
121
122impl PartialEq for LinoValue {
123 fn eq(&self, other: &Self) -> bool {
124 match (self, other) {
125 (LinoValue::Null, LinoValue::Null) => true,
126 (LinoValue::Bool(a), LinoValue::Bool(b)) => a == b,
127 (LinoValue::Int(a), LinoValue::Int(b)) => a == b,
128 (LinoValue::Float(a), LinoValue::Float(b)) => {
129 if a.is_nan() && b.is_nan() {
131 true
132 } else {
133 a == b
134 }
135 }
136 #[cfg(feature = "serde_json")]
137 (LinoValue::JsonNumber(a), LinoValue::JsonNumber(b)) => a == b,
138 (LinoValue::String(a), LinoValue::String(b)) => a == b,
139 (LinoValue::Array(a), LinoValue::Array(b)) => a == b,
140 (LinoValue::Object(a), LinoValue::Object(b)) => {
141 if a.len() != b.len() {
143 return false;
144 }
145 let a_map: HashMap<&str, &LinoValue> =
147 a.iter().map(|(k, v)| (k.as_str(), v)).collect();
148 let b_map: HashMap<&str, &LinoValue> =
149 b.iter().map(|(k, v)| (k.as_str(), v)).collect();
150 a_map == b_map
151 }
152 _ => false,
153 }
154 }
155}
156
157impl LinoValue {
158 pub fn object<I, K, V>(iter: I) -> Self
160 where
161 I: IntoIterator<Item = (K, V)>,
162 K: Into<String>,
163 V: Into<LinoValue>,
164 {
165 LinoValue::Object(
166 iter.into_iter()
167 .map(|(k, v)| (k.into(), v.into()))
168 .collect(),
169 )
170 }
171
172 pub fn array<I, V>(iter: I) -> Self
174 where
175 I: IntoIterator<Item = V>,
176 V: Into<LinoValue>,
177 {
178 LinoValue::Array(iter.into_iter().map(|v| v.into()).collect())
179 }
180
181 pub fn is_null(&self) -> bool {
183 matches!(self, LinoValue::Null)
184 }
185
186 pub fn as_bool(&self) -> Option<bool> {
188 match self {
189 LinoValue::Bool(b) => Some(*b),
190 _ => None,
191 }
192 }
193
194 pub fn as_int(&self) -> Option<i64> {
196 match self {
197 LinoValue::Int(i) => Some(*i),
198 _ => None,
199 }
200 }
201
202 pub fn as_float(&self) -> Option<f64> {
204 match self {
205 LinoValue::Float(f) => Some(*f),
206 LinoValue::Int(i) => Some(*i as f64),
207 _ => None,
208 }
209 }
210
211 pub fn as_str(&self) -> Option<&str> {
213 match self {
214 LinoValue::String(s) => Some(s),
215 _ => None,
216 }
217 }
218
219 pub fn as_array(&self) -> Option<&Vec<LinoValue>> {
221 match self {
222 LinoValue::Array(a) => Some(a),
223 _ => None,
224 }
225 }
226
227 pub fn as_object(&self) -> Option<&Vec<(String, LinoValue)>> {
229 match self {
230 LinoValue::Object(o) => Some(o),
231 _ => None,
232 }
233 }
234
235 pub fn get(&self, key: &str) -> Option<&LinoValue> {
237 match self {
238 LinoValue::Object(o) => o.iter().find(|(k, _)| k == key).map(|(_, v)| v),
239 _ => None,
240 }
241 }
242
243 pub fn get_index(&self, index: usize) -> Option<&LinoValue> {
245 match self {
246 LinoValue::Array(a) => a.get(index),
247 _ => None,
248 }
249 }
250}
251
252impl From<()> for LinoValue {
254 fn from(_: ()) -> Self {
255 LinoValue::Null
256 }
257}
258
259impl From<bool> for LinoValue {
260 fn from(b: bool) -> Self {
261 LinoValue::Bool(b)
262 }
263}
264
265impl From<i32> for LinoValue {
266 fn from(i: i32) -> Self {
267 LinoValue::Int(i as i64)
268 }
269}
270
271impl From<i64> for LinoValue {
272 fn from(i: i64) -> Self {
273 LinoValue::Int(i)
274 }
275}
276
277impl From<f64> for LinoValue {
278 fn from(f: f64) -> Self {
279 LinoValue::Float(f)
280 }
281}
282
283impl From<&str> for LinoValue {
284 fn from(s: &str) -> Self {
285 LinoValue::String(s.to_string())
286 }
287}
288
289impl From<String> for LinoValue {
290 fn from(s: String) -> Self {
291 LinoValue::String(s)
292 }
293}
294
295impl<T: Into<LinoValue>> From<Vec<T>> for LinoValue {
296 fn from(v: Vec<T>) -> Self {
297 LinoValue::Array(v.into_iter().map(|x| x.into()).collect())
298 }
299}
300
301impl<T: Into<LinoValue>> From<Option<T>> for LinoValue {
302 fn from(opt: Option<T>) -> Self {
303 match opt {
304 Some(v) => v.into(),
305 None => LinoValue::Null,
306 }
307 }
308}
309
310pub struct ObjectCodec {
316 encode_counter: usize,
318 encode_memo: HashMap<String, String>,
320 needs_id: HashSet<String>,
322 all_definitions: Vec<(String, LiNo<String>)>,
324 decode_memo: HashMap<String, LinoValue>,
326 all_links: Vec<LiNo<String>>,
328}
329
330impl Default for ObjectCodec {
331 fn default() -> Self {
332 Self::new()
333 }
334}
335
336impl ObjectCodec {
337 pub fn new() -> Self {
339 ObjectCodec {
340 encode_counter: 0,
341 encode_memo: HashMap::new(),
342 needs_id: HashSet::new(),
343 all_definitions: Vec::new(),
344 decode_memo: HashMap::new(),
345 all_links: Vec::new(),
346 }
347 }
348
349 fn reset_encode_state(&mut self) {
351 self.encode_counter = 0;
352 self.encode_memo.clear();
353 self.needs_id.clear();
354 self.all_definitions.clear();
355 }
356
357 fn reset_decode_state(&mut self) {
359 self.decode_memo.clear();
360 self.all_links.clear();
361 }
362
363 fn make_link(&self, parts: &[&str]) -> LiNo<String> {
365 let values: Vec<LiNo<String>> = parts.iter().map(|p| LiNo::Ref(p.to_string())).collect();
366 LiNo::Link { id: None, values }
367 }
368
369 fn make_ref(&self, id: &str) -> LiNo<String> {
371 LiNo::Ref(id.to_string())
372 }
373
374 fn object_key(&self, value: &LinoValue) -> String {
376 format!("{:p}", value)
378 }
379
380 fn find_objects_needing_ids(&mut self, value: &LinoValue, seen: &mut HashMap<String, bool>) {
382 match value {
384 LinoValue::Array(arr) => {
385 let key = self.object_key(value);
386
387 if seen.contains_key(&key) {
388 self.needs_id.insert(key);
390 return;
391 }
392
393 seen.insert(key, true);
394
395 for item in arr {
396 self.find_objects_needing_ids(item, seen);
397 }
398 }
399 LinoValue::Object(obj) => {
400 let key = self.object_key(value);
401
402 if seen.contains_key(&key) {
403 self.needs_id.insert(key);
405 return;
406 }
407
408 seen.insert(key, true);
409
410 for (_, v) in obj {
411 self.find_objects_needing_ids(v, seen);
412 }
413 }
414 _ => {}
415 }
416 }
417
418 pub fn encode(&mut self, value: &LinoValue) -> String {
432 readable::encode(value, DEFAULT_INDENT)
433 }
434
435 pub fn encode_with_indent(&mut self, value: &LinoValue, indent: &str) -> String {
442 readable::encode(value, indent)
443 }
444
445 pub fn encode_line(&mut self, value: &LinoValue) -> String {
460 readable::encode_line(value)
461 }
462
463 pub fn encode_compact(&mut self, value: &LinoValue) -> String {
477 self.reset_encode_state();
478
479 let mut seen = HashMap::new();
481 self.find_objects_needing_ids(value, &mut seen);
482
483 let mut visited = HashSet::new();
485 let main_link = self.encode_value(value, &mut visited, 0);
486
487 if !self.all_definitions.is_empty() {
489 let mut all_links = vec![main_link];
490
491 for (ref_id, link) in &self.all_definitions {
493 let main_id = match &all_links[0] {
494 LiNo::Link { id: Some(id), .. } => Some(id.clone()),
495 _ => None,
496 };
497 if main_id.as_ref() != Some(ref_id) {
498 all_links.push(link.clone());
499 }
500 }
501
502 all_links
504 .iter()
505 .map(Self::format_link)
506 .collect::<Vec<_>>()
507 .join("\n")
508 } else {
509 Self::format_link(&main_link)
510 }
511 }
512
513 pub fn encode_obfuscated(&mut self, value: &LinoValue) -> String {
518 self.encode_compact(value)
519 }
520
521 fn format_link(link: &LiNo<String>) -> String {
523 match link {
524 LiNo::Ref(s) => s.clone(),
525 LiNo::Link { id, values } => {
526 let inner: Vec<String> = values.iter().map(Self::format_link).collect();
527
528 if let Some(link_id) = id {
529 if inner.is_empty() {
530 format!("({}:)", link_id)
531 } else {
532 format!("({}: {})", link_id, inner.join(" "))
533 }
534 } else if inner.is_empty() {
535 "()".to_string()
536 } else {
537 format!("({})", inner.join(" "))
538 }
539 }
540 }
541 }
542
543 fn encode_value(
545 &mut self,
546 value: &LinoValue,
547 visited: &mut HashSet<String>,
548 depth: usize,
549 ) -> LiNo<String> {
550 match value {
551 LinoValue::Null => self.make_link(&[type_ids::NULL]),
552
553 LinoValue::Bool(b) => {
554 if *b {
555 self.make_link(&[type_ids::BOOL, "true"])
556 } else {
557 self.make_link(&[type_ids::BOOL, "false"])
558 }
559 }
560
561 LinoValue::Int(i) => self.make_link(&[type_ids::INT, &i.to_string()]),
562
563 LinoValue::Float(f) => {
564 if f.is_nan() {
565 self.make_link(&[type_ids::FLOAT, "NaN"])
566 } else if f.is_infinite() {
567 if f.is_sign_positive() {
568 self.make_link(&[type_ids::FLOAT, "Infinity"])
569 } else {
570 self.make_link(&[type_ids::FLOAT, "-Infinity"])
571 }
572 } else {
573 self.make_link(&[type_ids::FLOAT, &f.to_string()])
574 }
575 }
576
577 #[cfg(feature = "serde_json")]
578 LinoValue::JsonNumber(n) => self.make_link(&["json_number", &n.to_string()]),
579
580 LinoValue::String(s) => {
581 let b64_encoded = BASE64.encode(s.as_bytes());
582 self.make_link(&[type_ids::STR, &b64_encoded])
583 }
584
585 LinoValue::Array(arr) => {
586 let obj_key = self.object_key(value);
587
588 if let Some(ref_id) = self.encode_memo.get(&obj_key).cloned() {
590 return self.make_ref(&ref_id);
591 }
592
593 let needs_id = self.needs_id.contains(&obj_key);
595
596 if needs_id {
597 if visited.contains(&obj_key) {
599 if let Some(ref_id) = self.encode_memo.get(&obj_key) {
601 return self.make_ref(ref_id);
602 }
603 }
604
605 let ref_id = format!("obj_{}", self.encode_counter);
607 self.encode_counter += 1;
608 self.encode_memo.insert(obj_key.clone(), ref_id.clone());
609 visited.insert(obj_key.clone());
610
611 let mut parts: Vec<LiNo<String>> = vec![LiNo::Ref(type_ids::ARRAY.to_string())];
613 for item in arr {
614 let item_link = self.encode_value(item, visited, depth + 1);
615 parts.push(item_link);
616 }
617
618 let definition = LiNo::Link {
619 id: Some(ref_id.clone()),
620 values: parts,
621 };
622
623 if depth > 0 {
625 self.all_definitions.push((ref_id.clone(), definition));
626 return self.make_ref(&ref_id);
627 }
628
629 definition
630 } else {
631 let mut parts: Vec<LiNo<String>> = vec![LiNo::Ref(type_ids::ARRAY.to_string())];
633 for item in arr {
634 let item_link = self.encode_value(item, visited, depth + 1);
635 parts.push(item_link);
636 }
637 LiNo::Link {
638 id: None,
639 values: parts,
640 }
641 }
642 }
643
644 LinoValue::Object(obj) => {
645 let obj_key = self.object_key(value);
646
647 if let Some(ref_id) = self.encode_memo.get(&obj_key).cloned() {
649 return self.make_ref(&ref_id);
650 }
651
652 let needs_id = self.needs_id.contains(&obj_key);
654
655 if needs_id {
656 if visited.contains(&obj_key) {
658 if let Some(ref_id) = self.encode_memo.get(&obj_key) {
660 return self.make_ref(ref_id);
661 }
662 }
663
664 let ref_id = format!("obj_{}", self.encode_counter);
666 self.encode_counter += 1;
667 self.encode_memo.insert(obj_key.clone(), ref_id.clone());
668 visited.insert(obj_key.clone());
669
670 let mut parts: Vec<LiNo<String>> =
672 vec![LiNo::Ref(type_ids::OBJECT.to_string())];
673 for (k, v) in obj {
674 let key_link =
675 self.encode_value(&LinoValue::String(k.clone()), visited, depth + 1);
676 let value_link = self.encode_value(v, visited, depth + 1);
677 let pair = LiNo::Link {
678 id: None,
679 values: vec![key_link, value_link],
680 };
681 parts.push(pair);
682 }
683
684 let definition = LiNo::Link {
685 id: Some(ref_id.clone()),
686 values: parts,
687 };
688
689 if depth > 0 {
691 self.all_definitions.push((ref_id.clone(), definition));
692 return self.make_ref(&ref_id);
693 }
694
695 definition
696 } else {
697 let mut parts: Vec<LiNo<String>> =
699 vec![LiNo::Ref(type_ids::OBJECT.to_string())];
700 for (k, v) in obj {
701 let key_link =
702 self.encode_value(&LinoValue::String(k.clone()), visited, depth + 1);
703 let value_link = self.encode_value(v, visited, depth + 1);
704 let pair = LiNo::Link {
705 id: None,
706 values: vec![key_link, value_link],
707 };
708 parts.push(pair);
709 }
710 LiNo::Link {
711 id: None,
712 values: parts,
713 }
714 }
715 }
716 }
717 }
718
719 pub fn decode(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
732 if notation.trim().is_empty() {
733 return Ok(LinoValue::Null);
734 }
735
736 if is_compact_notation(notation) {
737 crate::debug::trace("codec.decode", || "compact notation detected".to_string());
738 return self.decode_compact(notation);
739 }
740
741 crate::debug::trace("codec.decode", || "readable notation detected".to_string());
742 readable::decode(notation)
743 }
744
745 pub fn decode_line(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
759 readable::decode_line(notation)
760 }
761
762 pub fn decode_compact(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
772 self.reset_decode_state();
773
774 let links = parse_lino_to_links(notation)
775 .map_err(|e| CodecError::ParseError(format!("{:?}", e)))?;
776
777 if links.is_empty() {
778 return Ok(LinoValue::Null);
779 }
780
781 if links.len() > 1 {
783 self.all_links = links.clone();
784 }
785
786 self.decode_link(&links[0])
788 }
789
790 fn decode_link(&mut self, link: &LiNo<String>) -> Result<LinoValue, CodecError> {
792 match link {
793 LiNo::Ref(id) => {
794 if let Some(value) = self.decode_memo.get(id) {
796 return Ok(value.clone());
797 }
798
799 if id.starts_with("obj_") && !self.all_links.is_empty() {
801 for other_link in self.all_links.clone() {
803 if let LiNo::Link {
804 id: Some(link_id), ..
805 } = &other_link
806 {
807 if link_id == id {
808 return self.decode_link(&other_link);
809 }
810 }
811 }
812 let result = LinoValue::Array(vec![]);
814 self.decode_memo.insert(id.clone(), result.clone());
815 return Ok(result);
816 }
817
818 match id.as_str() {
820 type_ids::NULL => return Ok(LinoValue::Null),
821 type_ids::ARRAY => return Ok(LinoValue::Array(vec![])),
822 type_ids::OBJECT => return Ok(LinoValue::Object(vec![])),
823 type_ids::STR => return Ok(LinoValue::String(String::new())),
824 _ => {}
825 }
826
827 Ok(LinoValue::String(id.clone()))
829 }
830
831 LiNo::Link { id, values } => {
832 let self_ref_id = id.as_ref().filter(|i| i.starts_with("obj_"));
834 if let Some(ref_id) = self_ref_id {
835 if let Some(value) = self.decode_memo.get(ref_id) {
836 return Ok(value.clone());
837 }
838 }
839
840 if values.is_empty() {
841 return Ok(LinoValue::Null);
842 }
843
844 let type_marker = match &values[0] {
846 LiNo::Ref(t) => t.as_str(),
847 LiNo::Link { .. } => return Ok(LinoValue::Null),
848 };
849
850 match type_marker {
851 #[cfg(feature = "serde_json")]
852 "json_number" => crate::json::decode_compact_number(values),
853 type_ids::NULL => Ok(LinoValue::Null),
854
855 type_ids::BOOL => {
856 if values.len() > 1 {
857 if let LiNo::Ref(val) = &values[1] {
858 return Ok(LinoValue::Bool(val.eq_ignore_ascii_case("true")));
859 }
860 }
861 Ok(LinoValue::Bool(false))
862 }
863
864 type_ids::INT => {
865 if values.len() > 1 {
866 if let LiNo::Ref(val) = &values[1] {
867 if let Ok(i) = val.parse::<i64>() {
868 return Ok(LinoValue::Int(i));
869 }
870 }
871 }
872 Ok(LinoValue::Int(0))
873 }
874
875 type_ids::FLOAT => {
876 if values.len() > 1 {
877 if let LiNo::Ref(val) = &values[1] {
878 return match val.as_str() {
879 "NaN" => Ok(LinoValue::Float(f64::NAN)),
880 "Infinity" => Ok(LinoValue::Float(f64::INFINITY)),
881 "-Infinity" => Ok(LinoValue::Float(f64::NEG_INFINITY)),
882 s => {
883 if let Ok(f) = s.parse::<f64>() {
884 Ok(LinoValue::Float(f))
885 } else {
886 Ok(LinoValue::Float(0.0))
887 }
888 }
889 };
890 }
891 }
892 Ok(LinoValue::Float(0.0))
893 }
894
895 type_ids::STR => {
896 if values.len() > 1 {
897 if let LiNo::Ref(b64_str) = &values[1] {
898 if let Ok(bytes) = BASE64.decode(b64_str) {
899 if let Ok(s) = String::from_utf8(bytes) {
900 return Ok(LinoValue::String(s));
901 }
902 }
903 return Ok(LinoValue::String(b64_str.clone()));
905 }
906 }
907 Ok(LinoValue::String(String::new()))
908 }
909
910 type_ids::ARRAY => {
911 let result_array = LinoValue::Array(vec![]);
913 if let Some(ref_id) = self_ref_id {
914 self.decode_memo.insert(ref_id.clone(), result_array);
915 }
916
917 let mut items = Vec::new();
919 for item_link in values.iter().skip(1) {
920 let decoded = self.decode_link(item_link)?;
921 items.push(decoded);
922 }
923
924 let result = LinoValue::Array(items);
925
926 if let Some(ref_id) = self_ref_id {
928 self.decode_memo.insert(ref_id.clone(), result.clone());
929 }
930
931 Ok(result)
932 }
933
934 type_ids::OBJECT => {
935 let result_object = LinoValue::Object(vec![]);
937 if let Some(ref_id) = self_ref_id {
938 self.decode_memo.insert(ref_id.clone(), result_object);
939 }
940
941 let mut obj = Vec::new();
943 for pair_link in values.iter().skip(1) {
944 if let LiNo::Link { values: pair, .. } = pair_link {
945 if pair.len() >= 2 {
946 let key = self.decode_link(&pair[0])?;
947 let value = self.decode_link(&pair[1])?;
948
949 if let LinoValue::String(k) = key {
951 obj.push((k, value));
952 }
953 }
954 }
955 }
956
957 let result = LinoValue::Object(obj);
958
959 if let Some(ref_id) = self_ref_id {
961 self.decode_memo.insert(ref_id.clone(), result.clone());
962 }
963
964 Ok(result)
965 }
966
967 unknown => Err(CodecError::UnknownType(unknown.to_string())),
968 }
969 }
970 }
971 }
972}
973
974const COMPACT_TYPE_MARKERS: &[&str] = &[
986 type_ids::NULL,
987 "None",
988 type_ids::BOOL,
989 type_ids::INT,
990 type_ids::FLOAT,
991 type_ids::STR,
992 type_ids::ARRAY,
993 "list",
994 type_ids::OBJECT,
995 "dict",
996 #[cfg(feature = "serde_json")]
997 "json_number",
998];
999
1000const EMPTY_BODY_MARKERS: [&str; 2] = [type_ids::NULL, "None"];
1003
1004fn is_compact_notation(notation: &str) -> bool {
1005 let Some(first_line) = notation.lines().map(str::trim).find(|l| !l.is_empty()) else {
1006 return false;
1007 };
1008
1009 let Some(rest) = first_line.strip_prefix('(') else {
1010 return false;
1011 };
1012
1013 let (mut marker, mut rest) = split_token(rest.trim_start());
1016
1017 if let Some(id) = marker.strip_suffix(':') {
1019 if !id.starts_with("obj_") {
1020 return false;
1021 }
1022 (marker, rest) = split_token(rest.trim_start());
1023 }
1024
1025 if !COMPACT_TYPE_MARKERS.contains(&marker) {
1026 return false;
1027 }
1028
1029 if EMPTY_BODY_MARKERS.contains(&marker) {
1032 return rest.trim_start().starts_with(')');
1033 }
1034
1035 true
1036}
1037
1038fn split_token(input: &str) -> (&str, &str) {
1041 let end = input
1042 .find(|c: char| c.is_whitespace() || c == '(' || c == ')')
1043 .unwrap_or(input.len());
1044 input.split_at(end)
1045}
1046
1047thread_local! {
1049 static DEFAULT_CODEC: std::cell::RefCell<ObjectCodec> = std::cell::RefCell::new(ObjectCodec::new());
1050}
1051
1052pub fn encode(value: &LinoValue) -> String {
1078 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode(value))
1079}
1080
1081pub fn encode_with_indent(value: &LinoValue, indent: &str) -> String {
1097 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_with_indent(value, indent))
1098}
1099
1100pub fn encode_line(value: &LinoValue) -> String {
1131 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_line(value))
1132}
1133
1134pub fn decode_line(notation: &str) -> Result<LinoValue, CodecError> {
1169 DEFAULT_CODEC.with(|codec| codec.borrow_mut().decode_line(notation))
1170}
1171
1172pub fn encode_compact(value: &LinoValue) -> String {
1198 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_compact(value))
1199}
1200
1201pub fn encode_obfuscated(value: &LinoValue) -> String {
1205 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_obfuscated(value))
1206}
1207
1208pub fn decode(notation: &str) -> Result<LinoValue, CodecError> {
1231 DEFAULT_CODEC.with(|codec| codec.borrow_mut().decode(notation))
1232}
1233
1234pub mod format;
1236
1237#[cfg(test)]
1238mod tests {
1239 use super::*;
1240
1241 #[test]
1242 fn test_roundtrip_null() {
1243 let original = LinoValue::Null;
1244 let encoded = encode(&original);
1245 let decoded = decode(&encoded).unwrap();
1246 assert_eq!(decoded, original);
1247 }
1248
1249 #[test]
1250 fn test_roundtrip_bool() {
1251 for value in [true, false] {
1252 let original = LinoValue::Bool(value);
1253 let encoded = encode(&original);
1254 let decoded = decode(&encoded).unwrap();
1255 assert_eq!(decoded, original);
1256 }
1257 }
1258
1259 #[test]
1260 fn test_roundtrip_int() {
1261 let test_values: Vec<i64> = vec![0, 1, -1, 42, -42, 123456789, -123456789];
1262 for value in test_values {
1263 let original = LinoValue::Int(value);
1264 let encoded = encode(&original);
1265 let decoded = decode(&encoded).unwrap();
1266 assert_eq!(decoded.as_int(), Some(value));
1267 }
1268 }
1269
1270 #[test]
1271 fn test_roundtrip_float() {
1272 let test_values: Vec<f64> = vec![0.0, 1.0, -1.0, 3.14, -3.14, 0.123456789, -999.999];
1273 for value in test_values {
1274 let original = LinoValue::Float(value);
1275 let encoded = encode(&original);
1276 let decoded = decode(&encoded).unwrap();
1277 let decoded_f = decoded.as_float().unwrap();
1278 assert!((decoded_f - value).abs() < 0.0001);
1279 }
1280 }
1281
1282 #[test]
1283 fn test_float_special_values() {
1284 let inf = LinoValue::Float(f64::INFINITY);
1286 let encoded = encode(&inf);
1287 let decoded = decode(&encoded).unwrap();
1288 let decoded_f = decoded.as_float().unwrap();
1289 assert!(decoded_f.is_infinite());
1290 assert!(decoded_f.is_sign_positive());
1291
1292 let neg_inf = LinoValue::Float(f64::NEG_INFINITY);
1294 let encoded = encode(&neg_inf);
1295 let decoded = decode(&encoded).unwrap();
1296 let decoded_f = decoded.as_float().unwrap();
1297 assert!(decoded_f.is_infinite());
1298 assert!(decoded_f.is_sign_negative());
1299
1300 let nan = LinoValue::Float(f64::NAN);
1302 let encoded = encode(&nan);
1303 let decoded = decode(&encoded).unwrap();
1304 let decoded_f = decoded.as_float().unwrap();
1305 assert!(decoded_f.is_nan());
1306 }
1307
1308 #[test]
1309 fn test_roundtrip_string() {
1310 let test_values = [
1311 "",
1312 "hello",
1313 "hello world",
1314 "Hello, World!",
1315 "multi\nline\nstring",
1316 "tab\tseparated",
1317 "special chars: @#$%^&*()",
1318 ];
1319 for value in test_values {
1320 let original = LinoValue::String(value.to_string());
1321 let encoded = encode(&original);
1322 let decoded = decode(&encoded).unwrap();
1323 assert_eq!(decoded.as_str(), Some(value));
1324 }
1325 }
1326
1327 #[test]
1328 fn test_string_with_quotes() {
1329 let test_values = [
1330 "string with 'single quotes'",
1331 "string with \"double quotes\"",
1332 "string with \"both\" 'quotes'",
1333 ];
1334 for value in test_values {
1335 let original = LinoValue::String(value.to_string());
1336 let encoded = encode(&original);
1337 let decoded = decode(&encoded).unwrap();
1338 assert_eq!(decoded.as_str(), Some(value));
1339 }
1340 }
1341
1342 #[test]
1343 fn test_unicode_string() {
1344 let value = "unicode: 你好世界 🌍";
1345 let original = LinoValue::String(value.to_string());
1346 let encoded = encode(&original);
1347 let decoded = decode(&encoded).unwrap();
1348 assert_eq!(decoded.as_str(), Some(value));
1349 }
1350
1351 #[test]
1352 fn test_roundtrip_empty_array() {
1353 let original = LinoValue::Array(vec![]);
1354 let encoded = encode(&original);
1355 let decoded = decode(&encoded).unwrap();
1356 assert_eq!(decoded, original);
1357 }
1358
1359 #[test]
1360 fn test_roundtrip_simple_array() {
1361 let original = LinoValue::Array(vec![
1362 LinoValue::Int(1),
1363 LinoValue::Int(2),
1364 LinoValue::Int(3),
1365 ]);
1366 let encoded = encode(&original);
1367 let decoded = decode(&encoded).unwrap();
1368 assert_eq!(decoded, original);
1369 }
1370
1371 #[test]
1372 fn test_roundtrip_mixed_array() {
1373 let original = LinoValue::Array(vec![
1374 LinoValue::Int(1),
1375 LinoValue::String("hello".to_string()),
1376 LinoValue::Bool(true),
1377 LinoValue::Null,
1378 ]);
1379 let encoded = encode(&original);
1380 let decoded = decode(&encoded).unwrap();
1381 assert_eq!(decoded, original);
1382 }
1383
1384 #[test]
1385 fn test_nested_arrays() {
1386 let original = LinoValue::Array(vec![LinoValue::Array(vec![])]);
1387 let encoded = encode(&original);
1388 let decoded = decode(&encoded).unwrap();
1389 assert_eq!(decoded, original);
1390
1391 let original2 = LinoValue::Array(vec![
1392 LinoValue::Array(vec![LinoValue::Int(1), LinoValue::Int(2)]),
1393 LinoValue::Array(vec![LinoValue::Int(3), LinoValue::Int(4)]),
1394 ]);
1395 let encoded2 = encode(&original2);
1396 let decoded2 = decode(&encoded2).unwrap();
1397 assert_eq!(decoded2, original2);
1398 }
1399
1400 #[test]
1401 fn test_roundtrip_empty_object() {
1402 let original = LinoValue::Object(vec![]);
1403 let encoded = encode(&original);
1404 let decoded = decode(&encoded).unwrap();
1405 assert_eq!(decoded, original);
1406 }
1407
1408 #[test]
1409 fn test_roundtrip_simple_object() {
1410 let original = LinoValue::object([("a", LinoValue::Int(1)), ("b", LinoValue::Int(2))]);
1411 let encoded = encode(&original);
1412 let decoded = decode(&encoded).unwrap();
1413 assert_eq!(decoded, original);
1414 }
1415
1416 #[test]
1417 fn test_nested_objects() {
1418 let original = LinoValue::object([(
1419 "user",
1420 LinoValue::object([
1421 ("name", LinoValue::String("Alice".to_string())),
1422 (
1423 "address",
1424 LinoValue::object([
1425 ("city", LinoValue::String("NYC".to_string())),
1426 ("zip", LinoValue::String("10001".to_string())),
1427 ]),
1428 ),
1429 ]),
1430 )]);
1431 let encoded = encode(&original);
1432 let decoded = decode(&encoded).unwrap();
1433 assert_eq!(decoded, original);
1434 }
1435
1436 #[test]
1437 fn test_complex_structure() {
1438 let original = LinoValue::object([
1439 ("id", LinoValue::Int(123)),
1440 ("name", LinoValue::String("Test Object".to_string())),
1441 ("active", LinoValue::Bool(true)),
1442 (
1443 "tags",
1444 LinoValue::array([
1445 LinoValue::String("tag1".to_string()),
1446 LinoValue::String("tag2".to_string()),
1447 LinoValue::String("tag3".to_string()),
1448 ]),
1449 ),
1450 (
1451 "metadata",
1452 LinoValue::object([
1453 ("created", LinoValue::String("2025-01-01".to_string())),
1454 ("modified", LinoValue::Null),
1455 ("count", LinoValue::Int(42)),
1456 ]),
1457 ),
1458 (
1459 "items",
1460 LinoValue::array([
1461 LinoValue::object([
1462 ("id", LinoValue::Int(1)),
1463 ("value", LinoValue::String("first".to_string())),
1464 ]),
1465 LinoValue::object([
1466 ("id", LinoValue::Int(2)),
1467 ("value", LinoValue::String("second".to_string())),
1468 ]),
1469 ]),
1470 ),
1471 ]);
1472 let encoded = encode(&original);
1473 let decoded = decode(&encoded).unwrap();
1474 assert_eq!(decoded, original);
1475 }
1476
1477 #[test]
1478 fn test_list_of_dicts() {
1479 let original = LinoValue::array([
1480 LinoValue::object([
1481 ("name", LinoValue::String("Alice".to_string())),
1482 ("age", LinoValue::Int(30)),
1483 ]),
1484 LinoValue::object([
1485 ("name", LinoValue::String("Bob".to_string())),
1486 ("age", LinoValue::Int(25)),
1487 ]),
1488 ]);
1489 let encoded = encode(&original);
1490 let decoded = decode(&encoded).unwrap();
1491 assert_eq!(decoded, original);
1492 }
1493
1494 #[test]
1495 fn test_dict_of_lists() {
1496 let original = LinoValue::object([
1497 (
1498 "numbers",
1499 LinoValue::array([LinoValue::Int(1), LinoValue::Int(2), LinoValue::Int(3)]),
1500 ),
1501 (
1502 "strings",
1503 LinoValue::array([
1504 LinoValue::String("a".to_string()),
1505 LinoValue::String("b".to_string()),
1506 LinoValue::String("c".to_string()),
1507 ]),
1508 ),
1509 ]);
1510 let encoded = encode(&original);
1511 let decoded = decode(&encoded).unwrap();
1512 assert_eq!(decoded, original);
1513 }
1514}
1515
1516#[cfg(test)]
1517mod format_tests {
1518 use super::format::*;
1519 use std::collections::HashMap;
1520
1521 #[test]
1522 fn test_escape_reference_simple_string() {
1523 assert_eq!(escape_reference("hello"), "hello");
1524 assert_eq!(escape_reference("world"), "world");
1525 }
1526
1527 #[test]
1528 fn test_escape_reference_string_with_spaces() {
1529 let result = escape_reference("hello world");
1530 assert!(result.starts_with('\'') || result.starts_with('"'));
1531 assert!(result.contains("hello world"));
1532 }
1533
1534 #[test]
1535 fn test_escape_reference_string_with_single_quotes() {
1536 let result = escape_reference("it's");
1537 assert_eq!(result, "\"it's\"");
1538 }
1539
1540 #[test]
1541 fn test_escape_reference_string_with_double_quotes() {
1542 let result = escape_reference("he said \"hello\"");
1543 assert_eq!(result, "'he said \"hello\"'");
1544 }
1545
1546 #[test]
1547 fn test_unescape_reference_doubled_quotes() {
1548 assert_eq!(
1549 unescape_reference("he said \"\"hello\"\""),
1550 "he said \"hello\""
1551 );
1552 assert_eq!(unescape_reference("it''s"), "it's");
1553 }
1554
1555 #[test]
1556 fn test_format_indented_ordered_basic() {
1557 let pairs = [
1558 ("uuid", "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019"),
1559 ("status", "executed"),
1560 ("command", "echo test"),
1561 ("exitCode", "0"),
1562 ];
1563 let result =
1564 format_indented_ordered("6dcf4c1b-ff3f-482c-95ab-711ea7d1b019", &pairs, " ").unwrap();
1565 let lines: Vec<&str> = result.lines().collect();
1566 assert_eq!(lines[0], "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1567 assert_eq!(lines[1], " uuid \"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\"");
1568 assert_eq!(lines[2], " status \"executed\"");
1569 assert_eq!(lines[3], " command \"echo test\"");
1570 assert_eq!(lines[4], " exitCode \"0\"");
1571 }
1572
1573 #[test]
1574 fn test_format_indented_value_with_quotes() {
1575 let pairs = [("message", "He said \"hello\"")];
1577 let result = format_indented_ordered("test-id", &pairs, " ").unwrap();
1578 let lines: Vec<&str> = result.lines().collect();
1579 assert_eq!(lines[1], " message 'He said \"hello\"'");
1580 }
1581
1582 #[test]
1583 fn test_format_indented_requires_id() {
1584 let mut obj = HashMap::new();
1585 obj.insert("key".to_string(), "value".to_string());
1586 let result = format_indented("", &obj, " ");
1587 assert!(result.is_err());
1588 }
1589
1590 #[test]
1591 fn test_parse_indented_basic() {
1592 let text = "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\n uuid \"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\"\n status \"executed\"\n exitCode \"0\"";
1593 let (id, obj) = parse_indented(text).unwrap();
1594 assert_eq!(id, "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1595 assert_eq!(
1596 obj.get("uuid"),
1597 Some(&"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019".to_string())
1598 );
1599 assert_eq!(obj.get("status"), Some(&"executed".to_string()));
1600 assert_eq!(obj.get("exitCode"), Some(&"0".to_string()));
1601 }
1602
1603 #[test]
1604 fn test_parse_indented_with_quotes() {
1605 let text = "test-id\n message 'He said \"hello\"'";
1607 let (id, obj) = parse_indented(text).unwrap();
1608 assert_eq!(id, "test-id");
1609 assert_eq!(obj.get("message"), Some(&"He said \"hello\"".to_string()));
1610 }
1611
1612 #[test]
1613 fn test_parse_indented_empty_lines_skipped() {
1614 let text = "test-id\n\n key \"value\"\n\n another \"value2\"";
1615 let (id, obj) = parse_indented(text).unwrap();
1616 assert_eq!(id, "test-id");
1617 assert_eq!(obj.get("key"), Some(&"value".to_string()));
1618 assert_eq!(obj.get("another"), Some(&"value2".to_string()));
1619 }
1620
1621 #[test]
1622 fn test_parse_indented_requires_text() {
1623 let result = parse_indented("");
1624 assert!(result.is_err());
1625 }
1626
1627 #[test]
1628 fn test_roundtrip_format_indented() {
1629 let pairs = [
1630 ("uuid", "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019"),
1631 ("status", "executed"),
1632 ("command", "echo test"),
1633 ("exitCode", "0"),
1634 ];
1635 let formatted =
1636 format_indented_ordered("6dcf4c1b-ff3f-482c-95ab-711ea7d1b019", &pairs, " ").unwrap();
1637 let (parsed_id, parsed_obj) = parse_indented(&formatted).unwrap();
1638
1639 assert_eq!(parsed_id, "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1640 for (key, value) in pairs {
1641 assert_eq!(parsed_obj.get(key), Some(&value.to_string()));
1642 }
1643 }
1644
1645 #[test]
1646 fn test_roundtrip_with_quotes() {
1647 let pairs = [("message", "He said \"hello\"")];
1648 let formatted = format_indented_ordered("test-id", &pairs, " ").unwrap();
1649 let (parsed_id, parsed_obj) = parse_indented(&formatted).unwrap();
1650
1651 assert_eq!(parsed_id, "test-id");
1652 assert_eq!(
1653 parsed_obj.get("message"),
1654 Some(&"He said \"hello\"".to_string())
1655 );
1656 }
1657}