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> {344 inner: I,345 c_document_end: bool,346 end_newline: bool,347}348impl<I> YamlStreamFormat<I> {349 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {350 Self {351 inner,352 c_document_end,353 354 end_newline: true,355 }356 }357 pub fn cli(inner: I) -> Self {358 Self {359 inner,360 c_document_end: true,361 end_newline: false,362 }363 }364}365impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {366 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {367 let Val::Arr(arr) = val else {368 bail!(369 "output should be array for yaml stream format, got {}",370 val.value_type()371 )372 };373 if !arr.is_empty() {374 for v in arr.iter() {375 let v = v?;376 out.push_str("---\n");377 self.inner.manifest_buf(v, out)?;378 out.push('\n');379 }380 }381 if self.c_document_end {382 out.push_str("...");383 }384 if self.end_newline {385 out.push('\n');386 }387 Ok(())388 }389}390391pub fn escape_string_json(s: &str) -> String {392 let mut buf = String::new();393 escape_string_json_buf(s, &mut buf);394 buf395}396397398399const BB: u8 = b'b'; 400const TT: u8 = b't'; 401const NN: u8 = b'n'; 402const FF: u8 = b'f'; 403const RR: u8 = b'r'; 404const QU: u8 = b'"'; 405const BS: u8 = b'\\'; 406const UU: u8 = b'u'; 407const __: u8 = 0;408409410411static ESCAPE: [u8; 256] = [412 413 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, 414 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, 415 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, 416 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 417 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 418 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, 419 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 420 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 421 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 422 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 423 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 424 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 425 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 426 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 427 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 428 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, 429];430431pub fn escape_string_json_buf(value: &str, buf: &mut String) {432 433 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };434 let bytes = value.as_bytes();435436 437 buf.reserve(value.len() + 2);438439 buf.push(b'"');440441 let mut start = 0;442443 for (i, &byte) in bytes.iter().enumerate() {444 let escape = ESCAPE[byte as usize];445 if escape == __ {446 continue;447 }448449 if start < i {450 buf.extend_from_slice(&bytes[start..i]);451 }452 start = i + 1;453454 match escape {455 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {456 buf.extend_from_slice(&[b'\\', escape]);457 }458 self::UU => {459 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";460 let bytes = &[461 b'\\',462 b'u',463 b'0',464 b'0',465 HEX_DIGITS[(byte >> 4) as usize],466 HEX_DIGITS[(byte & 0xF) as usize],467 ];468 buf.extend_from_slice(bytes);469 }470 _ => unreachable!(),471 }472 }473474 if start == bytes.len() {475 buf.push(b'"');476 return;477 }478479 buf.extend_from_slice(&bytes[start..]);480 buf.push(b'"');481}