1use std::fmt;
9
10use lino_objects_codec::{
11 LinoValue, decode as decode_any, decode_line, encode as encode_readable, encode_compact,
12 encode_line,
13};
14use serde_json::{Map, Number, Value as JsonValue};
15
16use crate::media_type::{
17 JSON_CONTENT_TYPE, LINO_COMPACT_CONTENT_TYPE, LINO_CONTENT_TYPE, LINO_LINE_CONTENT_TYPE,
18 normalize_media_type,
19};
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum CodecError {
24 UnsupportedMediaType(String),
26 Malformed(String),
28}
29
30impl fmt::Display for CodecError {
31 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 CodecError::UnsupportedMediaType(media_type) => {
34 write!(formatter, "Unsupported media type: {media_type}")
35 }
36 CodecError::Malformed(detail) => write!(formatter, "{detail}"),
37 }
38 }
39}
40
41impl std::error::Error for CodecError {}
42
43pub fn encode(value: &LinoValue) -> String {
45 encode_readable(value)
46}
47
48pub fn decode(notation: &str) -> Result<LinoValue, CodecError> {
50 decode_any(notation).map_err(|error| CodecError::Malformed(error.to_string()))
51}
52
53pub fn encode_single_line(value: &LinoValue) -> String {
55 encode_line(value)
56}
57
58pub fn decode_single_line(notation: &str) -> Result<LinoValue, CodecError> {
60 decode_line(notation).map_err(|error| CodecError::Malformed(error.to_string()))
61}
62
63pub fn encode_compact_notation(value: &LinoValue) -> String {
68 encode_compact(value)
69}
70
71pub fn encode_for(value: &LinoValue, media_type: &str) -> Result<String, CodecError> {
73 match normalize_media_type(media_type) {
74 LINO_CONTENT_TYPE => Ok(encode(value)),
75 LINO_LINE_CONTENT_TYPE => Ok(encode_single_line(value)),
76 LINO_COMPACT_CONTENT_TYPE => Ok(encode_compact_notation(value)),
77 JSON_CONTENT_TYPE => Ok(to_json_string(value)),
78 _ => Err(CodecError::UnsupportedMediaType(media_type.to_string())),
79 }
80}
81
82pub fn decode_from(body: &str, media_type: &str) -> Result<LinoValue, CodecError> {
84 match normalize_media_type(media_type) {
85 LINO_CONTENT_TYPE | LINO_COMPACT_CONTENT_TYPE => decode(body),
86 LINO_LINE_CONTENT_TYPE => decode_single_line(body),
87 JSON_CONTENT_TYPE => {
88 let json: JsonValue = serde_json::from_str(body)
89 .map_err(|error| CodecError::Malformed(error.to_string()))?;
90 Ok(from_json(&json))
91 }
92 _ => Err(CodecError::UnsupportedMediaType(media_type.to_string())),
93 }
94}
95
96pub fn to_json_string(value: &LinoValue) -> String {
102 let mut out = String::new();
103 write_json(value, &mut out);
104 out
105}
106
107fn write_json(value: &LinoValue, out: &mut String) {
109 match value {
110 LinoValue::Null => out.push_str("null"),
111 LinoValue::Bool(flag) => out.push_str(if *flag { "true" } else { "false" }),
112 LinoValue::Int(number) => out.push_str(&number.to_string()),
113 LinoValue::Float(number) => {
115 if number.is_finite() {
116 out.push_str(&JsonValue::from(*number).to_string());
117 } else {
118 out.push_str("null");
119 }
120 }
121 LinoValue::String(text) => out.push_str(&JsonValue::String(text.clone()).to_string()),
122 LinoValue::Array(items) => {
123 out.push('[');
124 for (index, item) in items.iter().enumerate() {
125 if index > 0 {
126 out.push(',');
127 }
128 write_json(item, out);
129 }
130 out.push(']');
131 }
132 LinoValue::Object(entries) => {
135 out.push('{');
136 for (index, (key, item)) in entries.iter().enumerate() {
137 if index > 0 {
138 out.push(',');
139 }
140 out.push_str(&JsonValue::String(key.clone()).to_string());
141 out.push(':');
142 write_json(item, out);
143 }
144 out.push('}');
145 }
146 }
147}
148
149pub fn to_json_pretty(value: &LinoValue) -> String {
163 let mut out = String::new();
164 write_json_pretty(value, 0, &mut out);
165 out
166}
167
168fn write_json_pretty(value: &LinoValue, depth: usize, out: &mut String) {
170 let outer = " ".repeat(depth);
171 let inner = " ".repeat(depth + 1);
172 match value {
173 LinoValue::Array(items) if items.is_empty() => out.push_str("[]"),
175 LinoValue::Object(entries) if entries.is_empty() => out.push_str("{}"),
176 LinoValue::Array(items) => {
177 out.push_str("[\n");
178 for (index, item) in items.iter().enumerate() {
179 if index > 0 {
180 out.push_str(",\n");
181 }
182 out.push_str(&inner);
183 write_json_pretty(item, depth + 1, out);
184 }
185 out.push('\n');
186 out.push_str(&outer);
187 out.push(']');
188 }
189 LinoValue::Object(entries) => {
190 out.push_str("{\n");
191 for (index, (key, item)) in entries.iter().enumerate() {
192 if index > 0 {
193 out.push_str(",\n");
194 }
195 out.push_str(&inner);
196 out.push_str(&JsonValue::String(key.clone()).to_string());
197 out.push_str(": ");
198 write_json_pretty(item, depth + 1, out);
199 }
200 out.push('\n');
201 out.push_str(&outer);
202 out.push('}');
203 }
204 scalar => write_json(scalar, out),
205 }
206}
207
208pub fn from_json(value: &JsonValue) -> LinoValue {
210 match value {
211 JsonValue::Null => LinoValue::Null,
212 JsonValue::Bool(flag) => LinoValue::Bool(*flag),
213 JsonValue::Number(number) => match number.as_i64() {
214 Some(integer) => LinoValue::Int(integer),
215 None => LinoValue::Float(number.as_f64().unwrap_or(f64::NAN)),
216 },
217 JsonValue::String(text) => LinoValue::String(text.clone()),
218 JsonValue::Array(items) => LinoValue::Array(items.iter().map(from_json).collect()),
219 JsonValue::Object(entries) => LinoValue::Object(
220 entries
221 .iter()
222 .map(|(key, item)| (key.clone(), from_json(item)))
223 .collect(),
224 ),
225 }
226}
227
228pub fn to_json(value: &LinoValue) -> JsonValue {
230 match value {
231 LinoValue::Null => JsonValue::Null,
232 LinoValue::Bool(flag) => JsonValue::Bool(*flag),
233 LinoValue::Int(number) => JsonValue::Number(Number::from(*number)),
234 LinoValue::Float(number) => Number::from_f64(*number)
235 .map(JsonValue::Number)
236 .unwrap_or(JsonValue::Null),
237 LinoValue::String(text) => JsonValue::String(text.clone()),
238 LinoValue::Array(items) => JsonValue::Array(items.iter().map(to_json).collect()),
239 LinoValue::Object(entries) => {
240 let mut map = Map::new();
241 for (key, item) in entries {
242 map.insert(key.clone(), to_json(item));
243 }
244 JsonValue::Object(map)
245 }
246 }
247}