1use std::{borrow::Cow, fmt::Write};23use crate::{bail, Result, State, Val};45pub trait ManifestFormat {6 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;7 fn manifest(&self, val: Val) -> Result<String> {8 let mut out = String::new();9 self.manifest_buf(val, &mut out)?;10 Ok(out)11 }12 13 14 15 16 fn file_trailing_newline(&self) -> bool {17 true18 }19}20impl<T> ManifestFormat for Box<T>21where22 T: ManifestFormat + ?Sized,23{24 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {25 let inner = &**self;26 inner.manifest_buf(val, buf)27 }28 fn file_trailing_newline(&self) -> bool {29 let inner = &**self;30 inner.file_trailing_newline()31 }32}33impl<T> ManifestFormat for &'_ T34where35 T: ManifestFormat + ?Sized,36{37 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38 let inner = &**self;39 inner.manifest_buf(val, buf)40 }41 fn file_trailing_newline(&self) -> bool {42 let inner = &**self;43 inner.file_trailing_newline()44 }45}4647#[derive(PartialEq, Eq, Clone, Copy)]48enum JsonFormatting {49 50 Manifest,51 52 53 Std,54 55 ToString,56 57 Minify,58}5960pub struct JsonFormat<'s> {61 padding: Cow<'s, str>,62 mtype: JsonFormatting,63 newline: &'s str,64 key_val_sep: &'s str,65 #[cfg(feature = "exp-preserve-order")]66 preserve_order: bool,67 #[cfg(feature = "exp-bigint")]68 preserve_bigints: bool,69 debug_truncate_strings: Option<usize>,70}7172impl<'s> JsonFormat<'s> {73 74 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {75 Self {76 padding: Cow::Borrowed(""),77 mtype: JsonFormatting::Minify,78 newline: "\n",79 key_val_sep: ":",80 #[cfg(feature = "exp-preserve-order")]81 preserve_order,82 #[cfg(feature = "exp-bigint")]83 preserve_bigints: false,84 debug_truncate_strings: None,85 }86 }87 88 pub fn std_to_string() -> Self {89 Self {90 padding: Cow::Borrowed(""),91 mtype: JsonFormatting::ToString,92 newline: "\n",93 key_val_sep: ": ",94 #[cfg(feature = "exp-preserve-order")]95 preserve_order: false,96 #[cfg(feature = "exp-bigint")]97 preserve_bigints: false,98 debug_truncate_strings: None,99 }100 }101 pub fn std_to_json(102 padding: String,103 newline: &'s str,104 key_val_sep: &'s str,105 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,106 ) -> Self {107 Self {108 padding: Cow::Owned(padding),109 mtype: JsonFormatting::Std,110 newline,111 key_val_sep,112 #[cfg(feature = "exp-preserve-order")]113 preserve_order,114 #[cfg(feature = "exp-bigint")]115 preserve_bigints: false,116 debug_truncate_strings: None,117 }118 }119 120 pub fn cli(121 padding: usize,122 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,123 ) -> Self {124 if padding == 0 {125 return Self::minify(126 #[cfg(feature = "exp-preserve-order")]127 preserve_order,128 );129 }130 Self {131 padding: Cow::Owned(" ".repeat(padding)),132 mtype: JsonFormatting::Manifest,133 newline: "\n",134 key_val_sep: ": ",135 #[cfg(feature = "exp-preserve-order")]136 preserve_order,137 #[cfg(feature = "exp-bigint")]138 preserve_bigints: false,139 debug_truncate_strings: None,140 }141 }142 143 pub fn debug() -> Self {144 Self {145 padding: Cow::Borrowed(" "),146 mtype: JsonFormatting::Manifest,147 newline: "\n",148 key_val_sep: ": ",149 #[cfg(feature = "exp-preserve-order")]150 preserve_order: true,151 #[cfg(feature = "exp-bigint")]152 preserve_bigints: true,153 debug_truncate_strings: Some(256),154 }155 }156}157impl Default for JsonFormat<'static> {158 fn default() -> Self {159 Self {160 padding: Cow::Borrowed(" "),161 mtype: JsonFormatting::Manifest,162 newline: "\n",163 key_val_sep: ": ",164 #[cfg(feature = "exp-preserve-order")]165 preserve_order: false,166 #[cfg(feature = "exp-bigint")]167 preserve_bigints: false,168 debug_truncate_strings: None,169 }170 }171}172173pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {174 let mut out = String::new();175 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;176 Ok(out)177}178179#[allow(clippy::too_many_lines)]180fn manifest_json_ex_buf(181 val: &Val,182 buf: &mut String,183 cur_padding: &mut String,184 options: &JsonFormat<'_>,185) -> Result<()> {186 let mtype = options.mtype;187 match val {188 Val::Bool(v) => {189 if *v {190 buf.push_str("true");191 } else {192 buf.push_str("false");193 }194 }195 Val::Null => buf.push_str("null"),196 Val::Str(s) => {197 let flat = s.clone().into_flat();198 if let Some(truncate) = options.debug_truncate_strings {199 if flat.len() > truncate {200 let (start, end) = flat.split_at(truncate / 2);201 let (_, end) = end.split_at(end.len() - truncate / 2);202 escape_string_json_buf(&format!("{start}..{end}"), buf);203 } else {204 escape_string_json_buf(&flat, buf);205 }206 } else {207 escape_string_json_buf(&flat, buf);208 }209 }210 Val::Num(n) => write!(buf, "{n}").unwrap(),211 #[cfg(feature = "exp-bigint")]212 Val::BigInt(n) => {213 if options.preserve_bigints {214 write!(buf, "{n}").unwrap();215 } else {216 write!(buf, "{:?}", n.to_string()).unwrap();217 }218 }219 Val::Arr(items) => {220 buf.push('[');221 if !items.is_empty() {222 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {223 buf.push_str(options.newline);224 }225226 let old_len = cur_padding.len();227 cur_padding.push_str(&options.padding);228 for (i, item) in items.iter().enumerate() {229 if i != 0 {230 buf.push(',');231 if mtype == JsonFormatting::ToString {232 buf.push(' ');233 } else if mtype != JsonFormatting::Minify {234 buf.push_str(options.newline);235 }236 }237 buf.push_str(cur_padding);238 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;239 }240 cur_padding.truncate(old_len);241242 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {243 buf.push_str(options.newline);244 buf.push_str(cur_padding);245 }246 } else if mtype == JsonFormatting::Std {247 buf.push_str(options.newline);248 buf.push_str(options.newline);249 buf.push_str(cur_padding);250 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {251 buf.push(' ');252 }253 buf.push(']');254 }255 Val::Obj(obj) => {256 obj.run_assertions()?;257 buf.push('{');258 let fields = obj.fields(259 #[cfg(feature = "exp-preserve-order")]260 options.preserve_order,261 );262 if !fields.is_empty() {263 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {264 buf.push_str(options.newline);265 }266267 let old_len = cur_padding.len();268 cur_padding.push_str(&options.padding);269 for (i, field) in fields.into_iter().enumerate() {270 if i != 0 {271 buf.push(',');272 if mtype == JsonFormatting::ToString {273 buf.push(' ');274 } else if mtype != JsonFormatting::Minify {275 buf.push_str(options.newline);276 }277 }278 buf.push_str(cur_padding);279 escape_string_json_buf(&field, buf);280 buf.push_str(options.key_val_sep);281 State::push_description(282 || format!("field <{}> manifestification", field.clone()),283 || {284 let value = obj.get(field.clone())?.unwrap();285 manifest_json_ex_buf(&value, buf, cur_padding, options)?;286 Ok(())287 },288 )?;289 }290 cur_padding.truncate(old_len);291292 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {293 buf.push_str(options.newline);294 buf.push_str(cur_padding);295 }296 } else if mtype == JsonFormatting::Std {297 buf.push_str(options.newline);298 buf.push_str(options.newline);299 buf.push_str(cur_padding);300 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {301 buf.push(' ');302 }303 buf.push('}');304 }305 Val::Func(_) => bail!("tried to manifest function"),306 };307 Ok(())308}309310impl ManifestFormat for JsonFormat<'_> {311 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {312 manifest_json_ex_buf(&val, buf, &mut String::new(), self)313 }314}315316pub struct ToStringFormat;317impl ManifestFormat for ToStringFormat {318 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {319 JsonFormat::std_to_string().manifest_buf(val, out)320 }321 fn file_trailing_newline(&self) -> bool {322 false323 }324}325pub struct StringFormat;326impl ManifestFormat for StringFormat {327 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {328 let Val::Str(s) = val else {329 bail!(330 "output should be string for string manifest format, got {}",331 val.value_type()332 )333 };334 write!(out, "{s}").unwrap();335 Ok(())336 }337 fn file_trailing_newline(&self) -> bool {338 false339 }340}341342pub struct YamlStreamFormat<I>(pub I);343impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {344 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {345 let Val::Arr(arr) = val else {346 bail!(347 "output should be array for yaml stream format, got {}",348 val.value_type()349 )350 };351 if !arr.is_empty() {352 for v in arr.iter() {353 let v = v?;354 out.push_str("---\n");355 self.0.manifest_buf(v, out)?;356 out.push('\n');357 }358 out.push_str("...");359 }360 Ok(())361 }362}363364pub fn escape_string_json(s: &str) -> String {365 let mut buf = String::new();366 escape_string_json_buf(s, &mut buf);367 buf368}369370371372const BB: u8 = b'b'; 373const TT: u8 = b't'; 374const NN: u8 = b'n'; 375const FF: u8 = b'f'; 376const RR: u8 = b'r'; 377const QU: u8 = b'"'; 378const BS: u8 = b'\\'; 379const UU: u8 = b'u'; 380const __: u8 = 0;381382383384static ESCAPE: [u8; 256] = [385 386 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 387 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 388 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 389 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 390 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 391 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 392 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 393 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 394 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 395 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 396 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 397 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 398 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 399 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 400 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 401 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 402];403404pub fn escape_string_json_buf(value: &str, buf: &mut String) {405 406 let buf: &mut Vec<u8> = unsafe { &mut *(buf as *mut String).cast::<Vec<u8>>() };407 let bytes = value.as_bytes();408409 410 buf.reserve(value.len() + 2);411412 buf.push(b'"');413414 let mut start = 0;415416 for (i, &byte) in bytes.iter().enumerate() {417 let escape = ESCAPE[byte as usize];418 if escape == __ {419 continue;420 }421422 if start < i {423 buf.extend_from_slice(&bytes[start..i]);424 }425 start = i + 1;426427 match escape {428 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {429 buf.extend_from_slice(&[b'\\', escape]);430 }431 self::UU => {432 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";433 let bytes = &[434 b'\\',435 b'u',436 b'0',437 b'0',438 HEX_DIGITS[(byte >> 4) as usize],439 HEX_DIGITS[(byte & 0xF) as usize],440 ];441 buf.extend_from_slice(bytes);442 }443 _ => unreachable!(),444 }445 }446447 if start == bytes.len() {448 buf.push(b'"');449 return;450 }451452 buf.extend_from_slice(&bytes[start..]);453 buf.push(b'"');454}