| 1 | use std::borrow::Cow; |
| 2 | use std::fmt::{Display, Formatter, Result, Write}; |
| 3 | |
| 4 | use toml_datetime::Datetime; |
| 5 | |
| 6 | use crate::inline_table::DEFAULT_INLINE_KEY_DECOR; |
| 7 | use crate::key::Key; |
| 8 | use crate::repr::{Formatted, Repr, ValueRepr}; |
| 9 | use crate::table::{ |
| 10 | DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_ROOT_DECOR, DEFAULT_TABLE_DECOR, |
| 11 | }; |
| 12 | use crate::value::{ |
| 13 | DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR, |
| 14 | }; |
| 15 | use crate::DocumentMut; |
| 16 | use crate::{Array, InlineTable, Item, Table, Value}; |
| 17 | |
| 18 | pub(crate) fn encode_key(this: &Key, buf: &mut dyn Write, input: Option<&str>) -> Result { |
| 19 | if let Some(input: &str) = input { |
| 20 | let repr: Cow<'_, Repr> = thisOption> |
| 21 | .as_repr() |
| 22 | .map(Cow::Borrowed) |
| 23 | .unwrap_or_else(|| Cow::Owned(this.default_repr())); |
| 24 | repr.encode(buf, input)?; |
| 25 | } else { |
| 26 | let repr: Cow<'_, str> = this.display_repr(); |
| 27 | write!(buf, " {repr}" )?; |
| 28 | }; |
| 29 | |
| 30 | Ok(()) |
| 31 | } |
| 32 | |
| 33 | fn encode_key_path( |
| 34 | this: &[Key], |
| 35 | buf: &mut dyn Write, |
| 36 | input: Option<&str>, |
| 37 | default_decor: (&str, &str), |
| 38 | ) -> Result { |
| 39 | let leaf_decor = this.last().expect("always at least one key" ).leaf_decor(); |
| 40 | for (i, key) in this.iter().enumerate() { |
| 41 | let dotted_decor = key.dotted_decor(); |
| 42 | |
| 43 | let first = i == 0; |
| 44 | let last = i + 1 == this.len(); |
| 45 | |
| 46 | if first { |
| 47 | leaf_decor.prefix_encode(buf, input, default_decor.0)?; |
| 48 | } else { |
| 49 | write!(buf, "." )?; |
| 50 | dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?; |
| 51 | } |
| 52 | |
| 53 | encode_key(key, buf, input)?; |
| 54 | |
| 55 | if last { |
| 56 | leaf_decor.suffix_encode(buf, input, default_decor.1)?; |
| 57 | } else { |
| 58 | dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?; |
| 59 | } |
| 60 | } |
| 61 | Ok(()) |
| 62 | } |
| 63 | |
| 64 | pub(crate) fn encode_key_path_ref( |
| 65 | this: &[&Key], |
| 66 | buf: &mut dyn Write, |
| 67 | input: Option<&str>, |
| 68 | default_decor: (&str, &str), |
| 69 | ) -> Result { |
| 70 | let leaf_decor = this.last().expect("always at least one key" ).leaf_decor(); |
| 71 | for (i, key) in this.iter().enumerate() { |
| 72 | let dotted_decor = key.dotted_decor(); |
| 73 | |
| 74 | let first = i == 0; |
| 75 | let last = i + 1 == this.len(); |
| 76 | |
| 77 | if first { |
| 78 | leaf_decor.prefix_encode(buf, input, default_decor.0)?; |
| 79 | } else { |
| 80 | write!(buf, "." )?; |
| 81 | dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?; |
| 82 | } |
| 83 | |
| 84 | encode_key(key, buf, input)?; |
| 85 | |
| 86 | if last { |
| 87 | leaf_decor.suffix_encode(buf, input, default_decor.1)?; |
| 88 | } else { |
| 89 | dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?; |
| 90 | } |
| 91 | } |
| 92 | Ok(()) |
| 93 | } |
| 94 | |
| 95 | pub(crate) fn encode_formatted<T: ValueRepr>( |
| 96 | this: &Formatted<T>, |
| 97 | buf: &mut dyn Write, |
| 98 | input: Option<&str>, |
| 99 | default_decor: (&str, &str), |
| 100 | ) -> Result { |
| 101 | let decor: &Decor = this.decor(); |
| 102 | decor.prefix_encode(buf, input, default_decor.0)?; |
| 103 | |
| 104 | if let Some(input: &str) = input { |
| 105 | let repr: Cow<'_, Repr> = thisOption> |
| 106 | .as_repr() |
| 107 | .map(Cow::Borrowed) |
| 108 | .unwrap_or_else(|| Cow::Owned(this.default_repr())); |
| 109 | repr.encode(buf, input)?; |
| 110 | } else { |
| 111 | let repr: Cow<'_, str> = this.display_repr(); |
| 112 | write!(buf, " {repr}" )?; |
| 113 | }; |
| 114 | |
| 115 | decor.suffix_encode(buf, input, default_decor.1)?; |
| 116 | Ok(()) |
| 117 | } |
| 118 | |
| 119 | pub(crate) fn encode_array( |
| 120 | this: &Array, |
| 121 | buf: &mut dyn Write, |
| 122 | input: Option<&str>, |
| 123 | default_decor: (&str, &str), |
| 124 | ) -> Result { |
| 125 | let decor = this.decor(); |
| 126 | decor.prefix_encode(buf, input, default_decor.0)?; |
| 127 | write!(buf, "[" )?; |
| 128 | |
| 129 | for (i, elem) in this.iter().enumerate() { |
| 130 | let inner_decor; |
| 131 | if i == 0 { |
| 132 | inner_decor = DEFAULT_LEADING_VALUE_DECOR; |
| 133 | } else { |
| 134 | inner_decor = DEFAULT_VALUE_DECOR; |
| 135 | write!(buf, "," )?; |
| 136 | } |
| 137 | encode_value(elem, buf, input, inner_decor)?; |
| 138 | } |
| 139 | if this.trailing_comma() && !this.is_empty() { |
| 140 | write!(buf, "," )?; |
| 141 | } |
| 142 | |
| 143 | this.trailing().encode_with_default(buf, input, "" )?; |
| 144 | write!(buf, "]" )?; |
| 145 | decor.suffix_encode(buf, input, default_decor.1)?; |
| 146 | |
| 147 | Ok(()) |
| 148 | } |
| 149 | |
| 150 | pub(crate) fn encode_table( |
| 151 | this: &InlineTable, |
| 152 | buf: &mut dyn Write, |
| 153 | input: Option<&str>, |
| 154 | default_decor: (&str, &str), |
| 155 | ) -> Result { |
| 156 | let decor = this.decor(); |
| 157 | decor.prefix_encode(buf, input, default_decor.0)?; |
| 158 | write!(buf, " {{" )?; |
| 159 | this.preamble().encode_with_default(buf, input, "" )?; |
| 160 | |
| 161 | let children = this.get_values(); |
| 162 | let len = children.len(); |
| 163 | for (i, (key_path, value)) in children.into_iter().enumerate() { |
| 164 | if i != 0 { |
| 165 | write!(buf, "," )?; |
| 166 | } |
| 167 | let inner_decor = if i == len - 1 { |
| 168 | DEFAULT_TRAILING_VALUE_DECOR |
| 169 | } else { |
| 170 | DEFAULT_VALUE_DECOR |
| 171 | }; |
| 172 | encode_key_path_ref(&key_path, buf, input, DEFAULT_INLINE_KEY_DECOR)?; |
| 173 | write!(buf, "=" )?; |
| 174 | encode_value(value, buf, input, inner_decor)?; |
| 175 | } |
| 176 | |
| 177 | write!(buf, " }}" )?; |
| 178 | decor.suffix_encode(buf, input, default_decor.1)?; |
| 179 | |
| 180 | Ok(()) |
| 181 | } |
| 182 | |
| 183 | pub(crate) fn encode_value( |
| 184 | this: &Value, |
| 185 | buf: &mut dyn Write, |
| 186 | input: Option<&str>, |
| 187 | default_decor: (&str, &str), |
| 188 | ) -> Result { |
| 189 | match this { |
| 190 | Value::String(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
| 191 | Value::Integer(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
| 192 | Value::Float(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
| 193 | Value::Boolean(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
| 194 | Value::Datetime(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
| 195 | Value::Array(array: &Array) => encode_array(this:array, buf, input, default_decor), |
| 196 | Value::InlineTable(table: &InlineTable) => encode_table(this:table, buf, input, default_decor), |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | impl Display for DocumentMut { |
| 201 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { |
| 202 | let decor = self.decor(); |
| 203 | decor.prefix_encode(f, None, DEFAULT_ROOT_DECOR.0)?; |
| 204 | |
| 205 | let mut path = Vec::new(); |
| 206 | let mut last_position = 0; |
| 207 | let mut tables = Vec::new(); |
| 208 | visit_nested_tables(self.as_table(), &mut path, false, &mut |t, p, is_array| { |
| 209 | if let Some(pos) = t.position() { |
| 210 | last_position = pos; |
| 211 | } |
| 212 | tables.push((last_position, t, p.clone(), is_array)); |
| 213 | Ok(()) |
| 214 | }) |
| 215 | .unwrap(); |
| 216 | |
| 217 | tables.sort_by_key(|&(id, _, _, _)| id); |
| 218 | let mut first_table = true; |
| 219 | for (_, table, path, is_array) in tables { |
| 220 | visit_table(f, None, table, &path, is_array, &mut first_table)?; |
| 221 | } |
| 222 | decor.suffix_encode(f, None, DEFAULT_ROOT_DECOR.1)?; |
| 223 | self.trailing().encode_with_default(f, None, "" ) |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | fn visit_nested_tables<'t, F>( |
| 228 | table: &'t Table, |
| 229 | path: &mut Vec<Key>, |
| 230 | is_array_of_tables: bool, |
| 231 | callback: &mut F, |
| 232 | ) -> Result |
| 233 | where |
| 234 | F: FnMut(&'t Table, &Vec<Key>, bool) -> Result, |
| 235 | { |
| 236 | if !table.is_dotted() { |
| 237 | callback(table, path, is_array_of_tables)?; |
| 238 | } |
| 239 | |
| 240 | for (key, value) in table.items.iter() { |
| 241 | match value { |
| 242 | Item::Table(ref t) => { |
| 243 | let key = key.clone(); |
| 244 | path.push(key); |
| 245 | visit_nested_tables(t, path, false, callback)?; |
| 246 | path.pop(); |
| 247 | } |
| 248 | Item::ArrayOfTables(ref a) => { |
| 249 | for t in a.iter() { |
| 250 | let key = key.clone(); |
| 251 | path.push(key); |
| 252 | visit_nested_tables(t, path, true, callback)?; |
| 253 | path.pop(); |
| 254 | } |
| 255 | } |
| 256 | _ => {} |
| 257 | } |
| 258 | } |
| 259 | Ok(()) |
| 260 | } |
| 261 | |
| 262 | fn visit_table( |
| 263 | buf: &mut dyn Write, |
| 264 | input: Option<&str>, |
| 265 | table: &Table, |
| 266 | path: &[Key], |
| 267 | is_array_of_tables: bool, |
| 268 | first_table: &mut bool, |
| 269 | ) -> Result { |
| 270 | let children = table.get_values(); |
| 271 | // We are intentionally hiding implicit tables without any tables nested under them (ie |
| 272 | // `table.is_empty()` which is in contrast to `table.get_values().is_empty()`). We are |
| 273 | // trusting the user that an empty implicit table is not semantically meaningful |
| 274 | // |
| 275 | // This allows a user to delete all tables under this implicit table and the implicit table |
| 276 | // will disappear. |
| 277 | // |
| 278 | // However, this means that users need to take care in deciding what tables get marked as |
| 279 | // implicit. |
| 280 | let is_visible_std_table = !(table.implicit && children.is_empty()); |
| 281 | |
| 282 | if path.is_empty() { |
| 283 | // don't print header for the root node |
| 284 | if !children.is_empty() { |
| 285 | *first_table = false; |
| 286 | } |
| 287 | } else if is_array_of_tables { |
| 288 | let default_decor = if *first_table { |
| 289 | *first_table = false; |
| 290 | ("" , DEFAULT_TABLE_DECOR.1) |
| 291 | } else { |
| 292 | DEFAULT_TABLE_DECOR |
| 293 | }; |
| 294 | table.decor.prefix_encode(buf, input, default_decor.0)?; |
| 295 | write!(buf, "[[" )?; |
| 296 | encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR)?; |
| 297 | write!(buf, "]]" )?; |
| 298 | table.decor.suffix_encode(buf, input, default_decor.1)?; |
| 299 | writeln!(buf)?; |
| 300 | } else if is_visible_std_table { |
| 301 | let default_decor = if *first_table { |
| 302 | *first_table = false; |
| 303 | ("" , DEFAULT_TABLE_DECOR.1) |
| 304 | } else { |
| 305 | DEFAULT_TABLE_DECOR |
| 306 | }; |
| 307 | table.decor.prefix_encode(buf, input, default_decor.0)?; |
| 308 | write!(buf, "[" )?; |
| 309 | encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR)?; |
| 310 | write!(buf, "]" )?; |
| 311 | table.decor.suffix_encode(buf, input, default_decor.1)?; |
| 312 | writeln!(buf)?; |
| 313 | } |
| 314 | // print table body |
| 315 | for (key_path, value) in children { |
| 316 | encode_key_path_ref(&key_path, buf, input, DEFAULT_KEY_DECOR)?; |
| 317 | write!(buf, "=" )?; |
| 318 | encode_value(value, buf, input, DEFAULT_VALUE_DECOR)?; |
| 319 | writeln!(buf)?; |
| 320 | } |
| 321 | Ok(()) |
| 322 | } |
| 323 | |
| 324 | impl ValueRepr for String { |
| 325 | fn to_repr(&self) -> Repr { |
| 326 | to_string_repr(self, style:None, literal:None) |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | pub(crate) fn to_string_repr( |
| 331 | value: &str, |
| 332 | style: Option<StringStyle>, |
| 333 | literal: Option<bool>, |
| 334 | ) -> Repr { |
| 335 | let (style, literal) = infer_style(value, style, literal); |
| 336 | |
| 337 | let mut output = String::with_capacity(value.len() * 2); |
| 338 | if literal { |
| 339 | output.push_str(style.literal_start()); |
| 340 | output.push_str(value); |
| 341 | output.push_str(style.literal_end()); |
| 342 | } else { |
| 343 | output.push_str(style.standard_start()); |
| 344 | for ch in value.chars() { |
| 345 | match ch { |
| 346 | ' \u{8}' => output.push_str(" \\b" ), |
| 347 | ' \u{9}' => output.push_str(" \\t" ), |
| 348 | ' \u{a}' => match style { |
| 349 | StringStyle::NewlineTriple => output.push(' \n' ), |
| 350 | StringStyle::OnelineSingle => output.push_str(" \\n" ), |
| 351 | StringStyle::OnelineTriple => unreachable!(), |
| 352 | }, |
| 353 | ' \u{c}' => output.push_str(" \\f" ), |
| 354 | ' \u{d}' => output.push_str(" \\r" ), |
| 355 | ' \u{22}' => output.push_str(" \\\"" ), |
| 356 | ' \u{5c}' => output.push_str(" \\\\" ), |
| 357 | c if c <= ' \u{1f}' || c == ' \u{7f}' => { |
| 358 | write!(output, " \\u {:04X}" , ch as u32).unwrap(); |
| 359 | } |
| 360 | ch => output.push(ch), |
| 361 | } |
| 362 | } |
| 363 | output.push_str(style.standard_end()); |
| 364 | } |
| 365 | |
| 366 | Repr::new_unchecked(output) |
| 367 | } |
| 368 | |
| 369 | #[derive (Copy, Clone, Debug, PartialEq, Eq)] |
| 370 | pub(crate) enum StringStyle { |
| 371 | NewlineTriple, |
| 372 | OnelineTriple, |
| 373 | OnelineSingle, |
| 374 | } |
| 375 | |
| 376 | impl StringStyle { |
| 377 | fn literal_start(self) -> &'static str { |
| 378 | match self { |
| 379 | Self::NewlineTriple => "''' \n" , |
| 380 | Self::OnelineTriple => "'''" , |
| 381 | Self::OnelineSingle => "'" , |
| 382 | } |
| 383 | } |
| 384 | fn literal_end(self) -> &'static str { |
| 385 | match self { |
| 386 | Self::NewlineTriple => "'''" , |
| 387 | Self::OnelineTriple => "'''" , |
| 388 | Self::OnelineSingle => "'" , |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | fn standard_start(self) -> &'static str { |
| 393 | match self { |
| 394 | Self::NewlineTriple => " \"\"\"\n" , |
| 395 | // note: OnelineTriple can happen if do_pretty wants to do |
| 396 | // '''it's one line''' |
| 397 | // but literal == false |
| 398 | Self::OnelineTriple | Self::OnelineSingle => " \"" , |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | fn standard_end(self) -> &'static str { |
| 403 | match self { |
| 404 | Self::NewlineTriple => " \"\"\"" , |
| 405 | // note: OnelineTriple can happen if do_pretty wants to do |
| 406 | // '''it's one line''' |
| 407 | // but literal == false |
| 408 | Self::OnelineTriple | Self::OnelineSingle => " \"" , |
| 409 | } |
| 410 | } |
| 411 | } |
| 412 | |
| 413 | fn infer_style( |
| 414 | value: &str, |
| 415 | style: Option<StringStyle>, |
| 416 | literal: Option<bool>, |
| 417 | ) -> (StringStyle, bool) { |
| 418 | match (style, literal) { |
| 419 | (Some(style: StringStyle), Some(literal: bool)) => (style, literal), |
| 420 | (None, Some(literal: bool)) => (infer_all_style(value).0, literal), |
| 421 | (Some(style: StringStyle), None) => { |
| 422 | let literal: bool = infer_literal(value); |
| 423 | (style, literal) |
| 424 | } |
| 425 | (None, None) => infer_all_style(value), |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | fn infer_literal(value: &str) -> bool { |
| 430 | #[cfg (feature = "parse" )] |
| 431 | { |
| 432 | use winnow::stream::ContainsToken as _; |
| 433 | (value.contains('"' ) | value.contains(' \\' )) |
| 434 | && valueChars<'_> |
| 435 | .chars() |
| 436 | .all(|c: char| crate::parser::strings::LITERAL_CHAR.contains_token(c)) |
| 437 | } |
| 438 | #[cfg (not(feature = "parse" ))] |
| 439 | { |
| 440 | false |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | fn infer_all_style(value: &str) -> (StringStyle, bool) { |
| 445 | // We need to determine: |
| 446 | // - if we are a "multi-line" pretty (if there are \n) |
| 447 | // - if ['''] appears if multi or ['] if single |
| 448 | // - if there are any invalid control characters |
| 449 | // |
| 450 | // Doing it any other way would require multiple passes |
| 451 | // to determine if a pretty string works or not. |
| 452 | let mut ty = StringStyle::OnelineSingle; |
| 453 | // found consecutive single quotes |
| 454 | let mut max_found_singles = 0; |
| 455 | let mut found_singles = 0; |
| 456 | let mut prefer_literal = false; |
| 457 | let mut can_be_pretty = true; |
| 458 | |
| 459 | for ch in value.chars() { |
| 460 | if can_be_pretty { |
| 461 | if ch == ' \'' { |
| 462 | found_singles += 1; |
| 463 | if found_singles >= 3 { |
| 464 | can_be_pretty = false; |
| 465 | } |
| 466 | } else { |
| 467 | if found_singles > max_found_singles { |
| 468 | max_found_singles = found_singles; |
| 469 | } |
| 470 | found_singles = 0; |
| 471 | } |
| 472 | match ch { |
| 473 | ' \t' => {} |
| 474 | '"' => { |
| 475 | prefer_literal = true; |
| 476 | } |
| 477 | ' \\' => { |
| 478 | prefer_literal = true; |
| 479 | } |
| 480 | ' \n' => ty = StringStyle::NewlineTriple, |
| 481 | // Escape codes are needed if any ascii control |
| 482 | // characters are present, including \b \f \r. |
| 483 | c if c <= ' \u{1f}' || c == ' \u{7f}' => can_be_pretty = false, |
| 484 | _ => {} |
| 485 | } |
| 486 | } else { |
| 487 | // the string cannot be represented as pretty, |
| 488 | // still check if it should be multiline |
| 489 | if ch == ' \n' { |
| 490 | ty = StringStyle::NewlineTriple; |
| 491 | } |
| 492 | } |
| 493 | } |
| 494 | if found_singles > 0 && value.ends_with(' \'' ) { |
| 495 | // We cannot escape the ending quote so we must use """ |
| 496 | can_be_pretty = false; |
| 497 | } |
| 498 | if !prefer_literal { |
| 499 | can_be_pretty = false; |
| 500 | } |
| 501 | if !can_be_pretty { |
| 502 | debug_assert!(ty != StringStyle::OnelineTriple); |
| 503 | return (ty, false); |
| 504 | } |
| 505 | if found_singles > max_found_singles { |
| 506 | max_found_singles = found_singles; |
| 507 | } |
| 508 | debug_assert!(max_found_singles < 3); |
| 509 | if ty == StringStyle::OnelineSingle && max_found_singles >= 1 { |
| 510 | // no newlines, but must use ''' because it has ' in it |
| 511 | ty = StringStyle::OnelineTriple; |
| 512 | } |
| 513 | (ty, true) |
| 514 | } |
| 515 | |
| 516 | impl ValueRepr for i64 { |
| 517 | fn to_repr(&self) -> Repr { |
| 518 | Repr::new_unchecked(self.to_string()) |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | impl ValueRepr for f64 { |
| 523 | fn to_repr(&self) -> Repr { |
| 524 | to_f64_repr(*self) |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | fn to_f64_repr(f: f64) -> Repr { |
| 529 | let repr: String = match (f.is_sign_negative(), f.is_nan(), f == 0.0) { |
| 530 | (true, true, _) => "-nan" .to_owned(), |
| 531 | (false, true, _) => "nan" .to_owned(), |
| 532 | (true, false, true) => "-0.0" .to_owned(), |
| 533 | (false, false, true) => "0.0" .to_owned(), |
| 534 | (_, false, false) => { |
| 535 | if f % 1.0 == 0.0 { |
| 536 | format!(" {f}.0" ) |
| 537 | } else { |
| 538 | format!(" {f}" ) |
| 539 | } |
| 540 | } |
| 541 | }; |
| 542 | Repr::new_unchecked(raw:repr) |
| 543 | } |
| 544 | |
| 545 | impl ValueRepr for bool { |
| 546 | fn to_repr(&self) -> Repr { |
| 547 | Repr::new_unchecked(self.to_string()) |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | impl ValueRepr for Datetime { |
| 552 | fn to_repr(&self) -> Repr { |
| 553 | Repr::new_unchecked(self.to_string()) |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | #[cfg (test)] |
| 558 | mod test { |
| 559 | use super::*; |
| 560 | use proptest::prelude::*; |
| 561 | |
| 562 | proptest! { |
| 563 | #[test] |
| 564 | #[cfg(feature = "parse" )] |
| 565 | fn parseable_string(string in " \\PC*" ) { |
| 566 | let string = Value::from(string); |
| 567 | let encoded = string.to_string(); |
| 568 | let _: Value = encoded.parse().unwrap_or_else(|err| { |
| 569 | panic!("error: {err} |
| 570 | |
| 571 | string: |
| 572 | ``` |
| 573 | {string} |
| 574 | ``` |
| 575 | " ) |
| 576 | }); |
| 577 | } |
| 578 | } |
| 579 | |
| 580 | proptest! { |
| 581 | #[test] |
| 582 | #[cfg(feature = "parse" )] |
| 583 | fn parseable_key(string in " \\PC*" ) { |
| 584 | let string = Key::new(string); |
| 585 | let encoded = string.to_string(); |
| 586 | let _: Key = encoded.parse().unwrap_or_else(|err| { |
| 587 | panic!("error: {err} |
| 588 | |
| 589 | string: |
| 590 | ``` |
| 591 | {string} |
| 592 | ``` |
| 593 | " ) |
| 594 | }); |
| 595 | } |
| 596 | } |
| 597 | } |
| 598 | |