difftreelog
feat add description stacktrace frames for all formats
in: master
5 files changed
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth1use 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 /// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13 /// the meaning).14 ///15 /// Default implementation returns `true`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 // Applied in manifestification50 Manifest,51 /// Used for std.manifestJson52 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest53 Std,54 /// No line breaks, used in `obj+''`55 ToString,56 /// Minified json57 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 // Minifying format74 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 // Same format as std.toString88 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 // Same format as CLI manifestification120 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 // Same format as CLI manifestification143 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 // Stdlib format always inserts newline at the end354 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}396397// Json string encoding was borrowed from https://github.com/serde-rs/json398399const BB: u8 = b'b'; // \x08400const TT: u8 = b't'; // \x09401const NN: u8 = b'n'; // \x0A402const FF: u8 = b'f'; // \x0C403const RR: u8 = b'r'; // \x0D404const QU: u8 = b'"'; // \x22405const BS: u8 = b'\\'; // \x5C406const UU: u8 = b'u'; // \x00...\x1F except the ones above407const __: u8 = 0;408409// Lookup table of escape sequences. A value of b'x' at index i means that byte410// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.411static ESCAPE: [u8; 256] = [412 // 1 2 3 4 5 6 7 8 9 A B C D E F413 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0414 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1415 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2416 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3417 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4418 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5419 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6420 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7421 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8422 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9423 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A424 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B425 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C426 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D427 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E428 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F429];430431pub fn escape_string_json_buf(value: &str, buf: &mut String) {432 // Safety: we only write correct utf-8 in this function433 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };434 let bytes = value.as_bytes();435436 // Perfect for ascii strings, removes any reallocations437 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}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 /// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13 /// the meaning).14 ///15 /// Default implementation returns `true`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 // Applied in manifestification50 Manifest,51 /// Used for std.manifestJson52 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest53 Std,54 /// No line breaks, used in `obj+''`55 ToString,56 /// Minified json57 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 // Minifying format74 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 // Same format as std.toString88 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 // Same format as CLI manifestification120 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 // Same format as CLI manifestification143 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 use JsonFormatting::*;187188 let mtype = options.mtype;189 match val {190 Val::Bool(v) => {191 if *v {192 buf.push_str("true");193 } else {194 buf.push_str("false");195 }196 }197 Val::Null => buf.push_str("null"),198 Val::Str(s) => {199 let flat = s.clone().into_flat();200 if let Some(truncate) = options.debug_truncate_strings {201 if flat.len() > truncate {202 let (start, end) = flat.split_at(truncate / 2);203 let (_, end) = end.split_at(end.len() - truncate / 2);204 escape_string_json_buf(&format!("{start}..{end}"), buf);205 } else {206 escape_string_json_buf(&flat, buf);207 }208 } else {209 escape_string_json_buf(&flat, buf);210 }211 }212 Val::Num(n) => write!(buf, "{n}").unwrap(),213 #[cfg(feature = "exp-bigint")]214 Val::BigInt(n) => {215 if options.preserve_bigints {216 write!(buf, "{n}").unwrap();217 } else {218 write!(buf, "{:?}", n.to_string()).unwrap();219 }220 }221 Val::Arr(items) => {222 buf.push('[');223224 let old_len = cur_padding.len();225 cur_padding.push_str(&options.padding);226227 let mut had_items = false;228 for (i, item) in items.iter().enumerate() {229 had_items = true;230 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;231232 if i != 0 {233 buf.push(',');234 }235 match mtype {236 Manifest | Std => {237 buf.push_str(options.newline);238 buf.push_str(cur_padding);239 }240 ToString => buf.push(' '),241 Minify => {}242 };243244 buf.push_str(cur_padding);245 State::push_description(246 || format!("elem <{i}> manifestification"),247 || manifest_json_ex_buf(&item, buf, cur_padding, options),248 )?;249 }250251 cur_padding.truncate(old_len);252253 match mtype {254 Manifest | ToString if !had_items => {255 // Empty array as "[ ]"256 buf.push(' ');257 }258 Manifest => {259 buf.push_str(options.newline);260 buf.push_str(cur_padding);261 }262 Std => {263 if !had_items {264 // Stdlib formats empty array as "[\n\n]"265 buf.push_str(options.newline);266 }267 buf.push_str(options.newline);268 buf.push_str(cur_padding);269 }270 Minify | ToString => {}271 }272273 buf.push(']');274 }275 Val::Obj(obj) => {276 obj.run_assertions()?;277 buf.push('{');278279 let old_len = cur_padding.len();280 cur_padding.push_str(&options.padding);281282 let mut had_fields = false;283 for (i, (key, value)) in obj284 .iter(285 #[cfg(feature = "exp-preserve-order")]286 options.preserve_order,287 )288 .enumerate()289 {290 had_fields = true;291 let value = value.with_description(|| format!("field <{key}> evaluation"))?;292293 if i != 0 {294 buf.push(',');295 }296 match mtype {297 Manifest | Std => {298 buf.push_str(options.newline);299 buf.push_str(cur_padding);300 }301 ToString => buf.push(' '),302 Minify => {}303 }304305 escape_string_json_buf(&key, buf);306 buf.push_str(options.key_val_sep);307 State::push_description(308 || format!("field <{key}> manifestification"),309 || manifest_json_ex_buf(&value, buf, cur_padding, options),310 )?;311 }312313 cur_padding.truncate(old_len);314315 match mtype {316 Manifest | ToString if !had_fields => {317 // Empty object as "{ }"318 buf.push(' ');319 }320 Manifest => {321 buf.push_str(options.newline);322 buf.push_str(cur_padding);323 }324 Std => {325 if !had_fields {326 // Stdlib formats empty object as "{\n\n}"327 buf.push_str(options.newline);328 }329 buf.push_str(options.newline);330 buf.push_str(cur_padding);331 }332 Minify | ToString => {}333 }334335 buf.push('}');336 }337 Val::Func(_) => bail!("tried to manifest function"),338 };339 Ok(())340}341342impl ManifestFormat for JsonFormat<'_> {343 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {344 manifest_json_ex_buf(&val, buf, &mut String::new(), self)345 }346}347348pub struct ToStringFormat;349impl ManifestFormat for ToStringFormat {350 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {351 JsonFormat::std_to_string().manifest_buf(val, out)352 }353 fn file_trailing_newline(&self) -> bool {354 false355 }356}357pub struct StringFormat;358impl ManifestFormat for StringFormat {359 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {360 let Val::Str(s) = val else {361 bail!(362 "output should be string for string manifest format, got {}",363 val.value_type()364 )365 };366 write!(out, "{s}").unwrap();367 Ok(())368 }369 fn file_trailing_newline(&self) -> bool {370 false371 }372}373374pub struct YamlStreamFormat<I> {375 inner: I,376 c_document_end: bool,377 end_newline: bool,378}379impl<I> YamlStreamFormat<I> {380 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {381 Self {382 inner,383 c_document_end,384 // Stdlib format always inserts useless newline at the end385 end_newline: true,386 }387 }388 pub fn cli(inner: I) -> Self {389 Self {390 inner,391 c_document_end: true,392 end_newline: false,393 }394 }395}396impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {397 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {398 let Val::Arr(arr) = val else {399 bail!(400 "output should be array for yaml stream format, got {}",401 val.value_type()402 )403 };404 if !arr.is_empty() {405 for (i, v) in arr.iter().enumerate() {406 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;407 out.push_str("---\n");408 State::push_description(409 || format!("elem <{i}> manifestification"),410 || self.inner.manifest_buf(v, out),411 )?;412 out.push('\n');413 }414 }415 if self.c_document_end {416 out.push_str("...");417 }418 if self.end_newline {419 out.push('\n');420 }421 Ok(())422 }423}424425pub fn escape_string_json(s: &str) -> String {426 let mut buf = String::new();427 escape_string_json_buf(s, &mut buf);428 buf429}430431// Json string encoding was borrowed from https://github.com/serde-rs/json432433const BB: u8 = b'b'; // \x08434const TT: u8 = b't'; // \x09435const NN: u8 = b'n'; // \x0A436const FF: u8 = b'f'; // \x0C437const RR: u8 = b'r'; // \x0D438const QU: u8 = b'"'; // \x22439const BS: u8 = b'\\'; // \x5C440const UU: u8 = b'u'; // \x00...\x1F except the ones above441const __: u8 = 0;442443// Lookup table of escape sequences. A value of b'x' at index i means that byte444// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.445static ESCAPE: [u8; 256] = [446 // 1 2 3 4 5 6 7 8 9 A B C D E F447 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0448 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1449 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2450 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3451 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4452 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5453 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6454 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7455 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8456 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9457 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A458 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B459 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C460 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D461 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E462 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F463];464465pub fn escape_string_json_buf(value: &str, buf: &mut String) {466 // Safety: we only write correct utf-8 in this function467 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };468 let bytes = value.as_bytes();469470 // Perfect for ascii strings, removes any reallocations471 buf.reserve(value.len() + 2);472473 buf.push(b'"');474475 let mut start = 0;476477 for (i, &byte) in bytes.iter().enumerate() {478 let escape = ESCAPE[byte as usize];479 if escape == __ {480 continue;481 }482483 if start < i {484 buf.extend_from_slice(&bytes[start..i]);485 }486 start = i + 1;487488 match escape {489 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {490 buf.extend_from_slice(&[b'\\', escape]);491 }492 self::UU => {493 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";494 let bytes = &[495 b'\\',496 b'u',497 b'0',498 b'0',499 HEX_DIGITS[(byte >> 4) as usize],500 HEX_DIGITS[(byte & 0xF) as usize],501 ];502 buf.extend_from_slice(bytes);503 }504 _ => unreachable!(),505 }506 }507508 if start == bytes.len() {509 buf.push(b'"');510 return;511 }512513 buf.extend_from_slice(&bytes[start..]);514 buf.push(b'"');515}crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -11,7 +11,7 @@
function::{native::NativeDesc, FuncDesc, FuncVal},
typed::CheckType,
val::{IndexableVal, StrValue, ThunkMapper},
- ObjValue, ObjValueBuilder, Result, Thunk, Val,
+ ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,
};
#[derive(Trace)]
@@ -359,7 +359,12 @@
unreachable!("typecheck should fail")
};
a.iter()
- .map(|r| r.and_then(T::from_untyped))
+ .enumerate()
+ .map(|(i, r)| {
+ r.and_then(|t| {
+ T::from_untyped(t).with_description(|| format!("parsing elem <{i}>"))
+ })
+ })
.collect::<Result<Self>>()
}
}
crates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/toml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/toml.rs
@@ -4,7 +4,7 @@
bail,
manifest::{escape_string_json_buf, ManifestFormat},
val::ArrValue,
- IStr, ObjValue, Result, Val,
+ IStr, ObjValue, Result, ResultExt, Val, State,
};
pub struct TomlFormat<'s> {
@@ -106,16 +106,15 @@
#[cfg(feature = "exp-bigint")]
Val::BigInt(n) => write!(buf, "{n}").unwrap(),
Val::Arr(a) => {
- if a.is_empty() {
- buf.push_str("[]");
- return Ok(());
- }
+ buf.push('[');
+
+ let mut had_items = false;
for (i, e) in a.iter().enumerate() {
- let e = e?;
+ had_items = true;
+ let e = e.with_description(|| format!("elem <{i}> evaluation"))?;
+
if i != 0 {
buf.push(',');
- } else {
- buf.push('[');
}
if inline {
buf.push(' ');
@@ -124,9 +123,15 @@
buf.push_str(cur_padding);
buf.push_str(&options.padding);
}
- manifest_value(&e, true, buf, "", options)?;
+
+ State::push_description(
+ || format!("elem <{i}> manifestification"),
+ || manifest_value(&e, true, buf, "", options),
+ )?;
}
- if inline {
+
+ if !had_items {
+ } else if inline {
buf.push(' ');
} else {
buf.push('\n');
@@ -135,10 +140,10 @@
buf.push(']');
}
Val::Obj(o) => {
- if o.is_empty() {
- buf.push_str("{}");
- }
- buf.push_str("{ ");
+ o.run_assertions()?;
+ buf.push('{');
+
+ let mut had_fields = false;
for (i, (k, v)) in o
.iter(
#[cfg(feature = "exp-preserve-order")]
@@ -146,15 +151,27 @@
)
.enumerate()
{
- let v = v?;
+ had_fields = true;
+ let v = v.with_description(|| format!("field <{k}> evaluation"))?;
+
if i != 0 {
- buf.push_str(", ");
+ buf.push(',');
}
+ buf.push(' ');
+
escape_key_toml_buf(&k, buf);
buf.push_str(" = ");
- manifest_value(&v, true, buf, "", options)?;
+ State::push_description(
+ || format!("field <{k}> manifestification"),
+ || manifest_value(&v, true, buf, "", options),
+ )?;
}
- buf.push_str(" }");
+
+ if had_fields {
+ buf.push(' ');
+ }
+
+ buf.push('}');
}
Val::Null => {
bail!("tried to manifest null")
crates/jrsonnet-stdlib/src/manifest/xml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/xml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs
@@ -1,9 +1,9 @@
use jrsonnet_evaluator::{
bail,
manifest::{ManifestFormat, ToStringFormat},
- typed::{ComplexValType, Either4, Typed, ValType},
+ typed::{ComplexValType, Either2, Either4, Typed, ValType},
val::{ArrValue, IndexableVal},
- Either, ObjValue, Result, ResultExt, Val,
+ Either, ObjValue, Result, ResultExt, Val, State,
};
pub struct XmlJsonmlFormat {
@@ -38,20 +38,22 @@
}
fn from_untyped(untyped: Val) -> Result<Self> {
- let Val::Arr(arr) = untyped else {
- if let Val::Str(s) = untyped {
- return Ok(Self::String(s.to_string()));
- };
- bail!("expected JSONML value (an array or string)");
+ let val = <Either![ArrValue, String]>::from_untyped(untyped)
+ .with_description(|| format!("parsing JSONML value (an array or string)"))?;
+ let arr = match val {
+ Either2::A(a) => a,
+ Either2::B(s) => return Ok(Self::String(s)),
};
if arr.len() < 1 {
- bail!("JSONML value should have tag");
+ bail!("JSONML value should have tag (array length should be >=1)");
};
let tag = String::from_untyped(
arr.get(0)
.with_description(|| "getting JSONML tag")?
.expect("length checked"),
- )?;
+ )
+ .with_description(|| format!("parsing JSONML tag"))?;
+
let (has_attrs, attrs) = if arr.len() >= 2 {
let maybe_attrs = arr
.get(1)
@@ -68,11 +70,16 @@
Ok(Self::Tag {
tag,
attrs,
- children: Typed::from_untyped(Val::Arr(arr.slice(
- Some(if has_attrs { 2 } else { 1 }),
- None,
- None,
- )))?,
+ children: State::push_description(
+ || format!("parsing children"),
+ || {
+ Typed::from_untyped(Val::Arr(arr.slice(
+ Some(if has_attrs { 2 } else { 1 }),
+ None,
+ None,
+ )))
+ },
+ )?,
})
}
}
crates/jrsonnet-stdlib/src/manifest/yaml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/yaml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/yaml.rs
@@ -3,7 +3,7 @@
use jrsonnet_evaluator::{
bail,
manifest::{escape_string_json_buf, ManifestFormat},
- Result, Val,
+ Result, ResultExt, State, Val,
};
pub struct YamlFormat<'s> {
@@ -152,80 +152,87 @@
#[cfg(feature = "exp-bigint")]
Val::BigInt(n) => write!(buf, "{}", *n).unwrap(),
Val::Arr(a) => {
- if a.is_empty() {
- buf.push_str("[]");
- } else {
- for (i, item) in a.iter().enumerate() {
- if i != 0 {
+ let mut had_items = false;
+ for (i, item) in a.iter().enumerate() {
+ had_items = true;
+ let item = item.with_description(|| format!("elem <{i}> evaluation"))?;
+ if i != 0 {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ }
+ buf.push('-');
+ match &item {
+ Val::Arr(a) if !a.is_empty() => {
buf.push('\n');
buf.push_str(cur_padding);
- }
- let item = item?;
- buf.push('-');
- match &item {
- Val::Arr(a) if !a.is_empty() => {
- buf.push('\n');
- buf.push_str(cur_padding);
- buf.push_str(&options.padding);
- }
- _ => buf.push(' '),
- }
- let extra_padding = match &item {
- Val::Arr(a) => !a.is_empty(),
- Val::Obj(o) => !o.is_empty(),
- _ => false,
- };
- let prev_len = cur_padding.len();
- if extra_padding {
- cur_padding.push_str(&options.padding);
+ buf.push_str(&options.padding);
}
- manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;
- cur_padding.truncate(prev_len);
+ _ => buf.push(' '),
}
+ let extra_padding = match &item {
+ Val::Arr(a) => !a.is_empty(),
+ Val::Obj(o) => !o.is_empty(),
+ _ => false,
+ };
+ let prev_len = cur_padding.len();
+ if extra_padding {
+ cur_padding.push_str(&options.padding);
+ }
+ State::push_description(
+ || format!("elem <{i}> manifestification"),
+ || manifest_yaml_ex_buf(&item, buf, cur_padding, options),
+ )?;
+ cur_padding.truncate(prev_len);
}
+ if !had_items {
+ buf.push_str("[]");
+ }
}
Val::Obj(o) => {
- if o.is_empty() {
- buf.push_str("{}");
- } else {
- for (i, key) in o
- .fields(
- #[cfg(feature = "exp-preserve-order")]
- options.preserve_order,
- )
- .iter()
- .enumerate()
- {
- if i != 0 {
+ let mut had_fields = false;
+ for (i, (key, value)) in o
+ .iter(
+ #[cfg(feature = "exp-preserve-order")]
+ options.preserve_order,
+ )
+ .enumerate()
+ {
+ had_fields = true;
+ let value = value.with_description(|| format!("field <{key}> evaluation"))?;
+ if i != 0 {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ }
+ if !options.quote_keys && !yaml_needs_quotes(&key) {
+ buf.push_str(&key);
+ } else {
+ escape_string_json_buf(&key, buf);
+ }
+ buf.push(':');
+ let prev_len = cur_padding.len();
+ match &value {
+ Val::Arr(a) if !a.is_empty() => {
buf.push('\n');
buf.push_str(cur_padding);
+ buf.push_str(&options.arr_element_padding);
+ cur_padding.push_str(&options.arr_element_padding);
}
- if !options.quote_keys && !yaml_needs_quotes(key) {
- buf.push_str(key);
- } else {
- escape_string_json_buf(key, buf);
+ Val::Obj(o) if !o.is_empty() => {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ buf.push_str(&options.padding);
+ cur_padding.push_str(&options.padding);
}
- buf.push(':');
- let prev_len = cur_padding.len();
- let item = o.get(key.clone())?.expect("field exists");
- match &item {
- Val::Arr(a) if !a.is_empty() => {
- buf.push('\n');
- buf.push_str(cur_padding);
- buf.push_str(&options.arr_element_padding);
- cur_padding.push_str(&options.arr_element_padding);
- }
- Val::Obj(o) if !o.is_empty() => {
- buf.push('\n');
- buf.push_str(cur_padding);
- buf.push_str(&options.padding);
- cur_padding.push_str(&options.padding);
- }
- _ => buf.push(' '),
- }
- manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;
- cur_padding.truncate(prev_len);
+ _ => buf.push(' '),
}
+ State::push_description(
+ || format!("field <{key}> manifestification"),
+ || manifest_yaml_ex_buf(&value, buf, cur_padding, options),
+ )?;
+ cur_padding.truncate(prev_len);
+ }
+ if !had_fields {
+ buf.push_str("{}");
}
}
Val::Func(_) => bail!("tried to manifest function"),