1use std::{borrow::Cow, fmt::Write, ptr};23use crate::{bail, Result, ResultExt, 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 .with_description(|| format!("elem <{i}> manifestification"))?;240 }241 cur_padding.truncate(old_len);242243 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {244 buf.push_str(options.newline);245 buf.push_str(cur_padding);246 }247 } else if mtype == JsonFormatting::Std {248 buf.push_str(options.newline);249 buf.push_str(options.newline);250 buf.push_str(cur_padding);251 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {252 buf.push(' ');253 }254 buf.push(']');255 }256 Val::Obj(obj) => {257 obj.run_assertions()?;258 buf.push('{');259 let fields = obj.fields(260 #[cfg(feature = "exp-preserve-order")]261 options.preserve_order,262 );263 if !fields.is_empty() {264 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {265 buf.push_str(options.newline);266 }267268 let old_len = cur_padding.len();269 cur_padding.push_str(&options.padding);270 for (i, field) in fields.into_iter().enumerate() {271 if i != 0 {272 buf.push(',');273 if mtype == JsonFormatting::ToString {274 buf.push(' ');275 } else if mtype != JsonFormatting::Minify {276 buf.push_str(options.newline);277 }278 }279 buf.push_str(cur_padding);280 escape_string_json_buf(&field, buf);281 buf.push_str(options.key_val_sep);282 State::push_description(283 || format!("field <{}> manifestification", field.clone()),284 || {285 let value = obj.get(field.clone())?.unwrap();286 manifest_json_ex_buf(&value, buf, cur_padding, options)?;287 Ok(())288 },289 )?;290 }291 cur_padding.truncate(old_len);292293 if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {294 buf.push_str(options.newline);295 buf.push_str(cur_padding);296 }297 } else if mtype == JsonFormatting::Std {298 buf.push_str(options.newline);299 buf.push_str(options.newline);300 buf.push_str(cur_padding);301 } else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {302 buf.push(' ');303 }304 buf.push('}');305 }306 Val::Func(_) => bail!("tried to manifest function"),307 };308 Ok(())309}310311impl ManifestFormat for JsonFormat<'_> {312 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {313 manifest_json_ex_buf(&val, buf, &mut String::new(), self)314 }315}316317pub struct ToStringFormat;318impl ManifestFormat for ToStringFormat {319 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {320 JsonFormat::std_to_string().manifest_buf(val, out)321 }322 fn file_trailing_newline(&self) -> bool {323 false324 }325}326pub struct StringFormat;327impl ManifestFormat for StringFormat {328 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {329 let Val::Str(s) = val else {330 bail!(331 "output should be string for string manifest format, got {}",332 val.value_type()333 )334 };335 write!(out, "{s}").unwrap();336 Ok(())337 }338 fn file_trailing_newline(&self) -> bool {339 false340 }341}342343pub struct YamlStreamFormat<I>(pub I);344impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {345 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {346 let Val::Arr(arr) = val else {347 bail!(348 "output should be array for yaml stream format, got {}",349 val.value_type()350 )351 };352 if !arr.is_empty() {353 for v in arr.iter() {354 let v = v?;355 out.push_str("---\n");356 self.0.manifest_buf(v, out)?;357 out.push('\n');358 }359 out.push_str("...");360 }361 Ok(())362 }363}364365pub fn escape_string_json(s: &str) -> String {366 let mut buf = String::new();367 escape_string_json_buf(s, &mut buf);368 buf369}370371372373const BB: u8 = b'b'; 374const TT: u8 = b't'; 375const NN: u8 = b'n'; 376const FF: u8 = b'f'; 377const RR: u8 = b'r'; 378const QU: u8 = b'"'; 379const BS: u8 = b'\\'; 380const UU: u8 = b'u'; 381const __: u8 = 0;382383384385static ESCAPE: [u8; 256] = [386 387 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 388 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 389 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 390 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 391 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 392 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 393 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 394 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 395 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 396 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 397 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 398 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 399 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 400 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 401 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 402 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 403];404405pub fn escape_string_json_buf(value: &str, buf: &mut String) {406 407 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };408 let bytes = value.as_bytes();409410 411 buf.reserve(value.len() + 2);412413 buf.push(b'"');414415 let mut start = 0;416417 for (i, &byte) in bytes.iter().enumerate() {418 let escape = ESCAPE[byte as usize];419 if escape == __ {420 continue;421 }422423 if start < i {424 buf.extend_from_slice(&bytes[start..i]);425 }426 start = i + 1;427428 match escape {429 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {430 buf.extend_from_slice(&[b'\\', escape]);431 }432 self::UU => {433 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";434 let bytes = &[435 b'\\',436 b'u',437 b'0',438 b'0',439 HEX_DIGITS[(byte >> 4) as usize],440 HEX_DIGITS[(byte & 0xF) as usize],441 ];442 buf.extend_from_slice(bytes);443 }444 _ => unreachable!(),445 }446 }447448 if start == bytes.len() {449 buf.push(b'"');450 return;451 }452453 buf.extend_from_slice(&bytes[start..]);454 buf.push(b'"');455}