1use lino_objects_codec::LinoValue;
8
9pub fn object<I, K>(members: I) -> LinoValue
20where
21 I: IntoIterator<Item = (K, LinoValue)>,
22 K: Into<String>,
23{
24 LinoValue::Object(
25 members
26 .into_iter()
27 .map(|(key, value)| (key.into(), value))
28 .collect(),
29 )
30}
31
32pub fn array<I: IntoIterator<Item = LinoValue>>(items: I) -> LinoValue {
34 LinoValue::Array(items.into_iter().collect())
35}
36
37pub fn string<S: Into<String>>(text: S) -> LinoValue {
39 LinoValue::String(text.into())
40}
41
42pub fn int(number: i64) -> LinoValue {
44 LinoValue::Int(number)
45}
46
47pub fn boolean(flag: bool) -> LinoValue {
49 LinoValue::Bool(flag)
50}
51
52pub fn as_object(value: &LinoValue) -> Option<&Vec<(String, LinoValue)>> {
54 match value {
55 LinoValue::Object(members) => Some(members),
56 _ => None,
57 }
58}
59
60pub fn as_array(value: &LinoValue) -> Option<&Vec<LinoValue>> {
62 match value {
63 LinoValue::Array(items) => Some(items),
64 _ => None,
65 }
66}
67
68pub fn as_str(value: &LinoValue) -> Option<&str> {
70 match value {
71 LinoValue::String(text) => Some(text),
72 _ => None,
73 }
74}
75
76pub fn get<'a>(value: &'a LinoValue, key: &str) -> Option<&'a LinoValue> {
79 as_object(value)?
80 .iter()
81 .find(|(name, _)| name == key)
82 .map(|(_, member)| member)
83}
84
85pub fn has(value: &LinoValue, key: &str) -> bool {
87 get(value, key).is_some()
88}
89
90pub fn set<S: Into<String>>(value: &mut LinoValue, key: S, member: LinoValue) {
93 let key = key.into();
94 if let LinoValue::Object(members) = value {
95 if let Some(existing) = members.iter_mut().find(|(name, _)| *name == key) {
96 existing.1 = member;
97 } else {
98 members.push((key, member));
99 }
100 }
101}
102
103pub fn with<S: Into<String>>(value: &LinoValue, key: S, member: LinoValue) -> LinoValue {
105 let mut copy = value.clone();
106 if !matches!(copy, LinoValue::Object(_)) {
107 copy = LinoValue::Object(Vec::new());
108 }
109 set(&mut copy, key, member);
110 copy
111}
112
113pub fn merge(base: &LinoValue, patch: &LinoValue) -> LinoValue {
115 let mut merged = base.clone();
116 if !matches!(merged, LinoValue::Object(_)) {
117 merged = LinoValue::Object(Vec::new());
118 }
119 if let Some(members) = as_object(patch) {
120 for (key, member) in members {
121 set(&mut merged, key.clone(), member.clone());
122 }
123 }
124 merged
125}
126
127pub fn to_query_text(value: &LinoValue) -> String {
132 match value {
133 LinoValue::Null => "null".to_string(),
134 LinoValue::Bool(flag) => flag.to_string(),
135 LinoValue::Int(number) => number.to_string(),
136 LinoValue::Float(number) => format_float(*number),
137 LinoValue::String(text) => text.clone(),
138 other => crate::codec::encode_single_line(other),
139 }
140}
141
142pub fn format_float(number: f64) -> String {
144 if number.is_finite() && number.fract() == 0.0 && number.abs() < 1e15 {
145 format!("{}", number as i64)
146 } else {
147 format!("{number}")
148 }
149}
150
151pub fn compare(left: &LinoValue, right: &LinoValue) -> std::cmp::Ordering {
157 use std::cmp::Ordering;
158 match (left, right) {
159 (LinoValue::Null, LinoValue::Null) => Ordering::Equal,
160 (LinoValue::Null, _) => Ordering::Less,
161 (_, LinoValue::Null) => Ordering::Greater,
162 (LinoValue::Int(a), LinoValue::Int(b)) => a.cmp(b),
163 (LinoValue::Int(a), LinoValue::Float(b)) => {
164 (*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal)
165 }
166 (LinoValue::Float(a), LinoValue::Int(b)) => {
167 a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal)
168 }
169 (LinoValue::Float(a), LinoValue::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
170 (a, b) => to_query_text(a).cmp(&to_query_text(b)),
171 }
172}