1use std::fmt::Formatter;
8
9use pad::{Alignment, PadStr};
10use serde::Serialize;
11use serde_json::{Value, ser::PrettyFormatter};
12use thiserror::Error;
13
14use super::{ArgumentList, Message};
15use crate::sprintf::message::{
16 ArgumentReference, PaddingSpecifier, Part, Placeholder, TypeSpecifier,
17};
18
19macro_rules! format_placeholder {
20 ($value:expr, $type:literal, $placeholder:expr) => {
21 format_step_plus_sign!($value, $type, $placeholder, "",)
22 };
23 ($value:expr, $placeholder:expr) => {
24 format_placeholder!($value, "", $placeholder)
25 };
26}
27
28macro_rules! format_step_plus_sign {
29 ($value:expr, $type:literal, $placeholder:expr, $modifiers:expr, $($argk:ident = $argv:expr),* $(,)?) => {{
30 if $placeholder.plus_sign {
31 format_step_zero!(
32 $value,
33 $type,
34 $placeholder,
35 concat!($modifiers, "+"),
36 $($argk = $argv),*
37 )
38 } else {
39 format_step_zero!(
40 $value,
41 $type,
42 $placeholder,
43 $modifiers,
44 $($argk = $argv),*
45 )
46 }
47 }};
48}
49
50macro_rules! format_step_zero {
51 ($value:expr, $type:literal, $placeholder:expr, $modifiers:expr, $($argk:ident = $argv:expr),* $(,)?) => {{
52 if $placeholder.padding_specifier_is_zero() {
53 format_step_width!(
54 $value,
55 $type,
56 $placeholder,
57 concat!($modifiers, "0"),
58 $($argk = $argv),*
59 )
60 } else {
61 format_step_width!(
62 $value,
63 $type,
64 $placeholder,
65 $modifiers,
66 $($argk = $argv),*
67 )
68 }
69 }};
70}
71
72macro_rules! format_step_width {
73 ($value:expr, $type:literal, $placeholder:expr, $modifiers:expr, $($argk:ident = $argv:expr),* $(,)?) => {{
74 if let Some(width) = $placeholder.numeric_width() {
75 format_step_precision!(
76 $value,
77 $type,
78 $placeholder,
79 concat!($modifiers, "width$"),
80 width = width,
81 $($argk = $argv),*
82 )
83 } else {
84 format_step_precision!(
85 $value,
86 $type,
87 $placeholder,
88 $modifiers,
89 $($argk = $argv),*
90 )
91 }
92 }};
93}
94
95macro_rules! format_step_precision {
96 ($value:expr, $type:literal, $placeholder:expr, $modifiers:expr, $($argk:ident = $argv:expr),* $(,)?) => {{
97 if let Some(precision) = $placeholder.precision {
98 format_end!(
99 $value,
100 $type,
101 $placeholder,
102 concat!($modifiers, ".precision$"),
103 precision = precision,
104 $($argk = $argv),*
105 )
106 } else {
107 format_end!(
108 $value,
109 $type,
110 $placeholder,
111 $modifiers,
112 $($argk = $argv),*
113 )
114 }
115 }};
116}
117
118macro_rules! format_end {
119 ($value:expr, $type:literal, $placeholder:expr, $modifiers:expr, $($argk:ident = $argv:expr),* $(,)?) => {
120 format!(concat!("{value:", $modifiers, $type, "}"), value = $value, $($argk = $argv),*)
121 };
122}
123
124#[derive(Debug)]
125pub enum ValueType {
126 String,
127 Number,
128 Float,
129 Null,
130 Bool,
131 Array,
132 Object,
133}
134
135impl ValueType {
136 fn of_value(value: &Value) -> Self {
137 match value {
138 Value::String(_) => Self::String,
139 Value::Number(_) => Self::Number,
140 Value::Null => Self::Null,
141 Value::Bool(_) => Self::Bool,
142 Value::Array(_) => Self::Array,
143 Value::Object(_) => Self::Object,
144 }
145 }
146}
147
148#[derive(Debug, Error)]
149pub enum FormatError {
150 #[error("Can't format a {value_type:?} as a %{type_specifier}")]
151 InvalidTypeSpecifier {
152 type_specifier: TypeSpecifier,
153 value_type: ValueType,
154 },
155
156 #[error("Unsupported type specifier %{type_specifier}")]
157 UnsupportedTypeSpecifier { type_specifier: TypeSpecifier },
158
159 #[error("Unexpected number type")]
160 NumberIsNotANumber,
161
162 #[error("Unknown named argument {name}")]
163 UnknownNamedArgument { name: String },
164
165 #[error("Unknown indexed argument {index}")]
166 UnknownIndexedArgument { index: usize },
167
168 #[error("Not enough arguments")]
169 NotEnoughArguments,
170
171 #[error("Can't serialize value")]
172 Serialize(#[from] serde_json::Error),
173
174 #[error("Can't convert value to UTF-8")]
175 InvalidUtf8(#[from] std::string::FromUtf8Error),
176}
177
178fn find_value<'a>(
179 arguments: &'a ArgumentList,
180 requested_argument: Option<&ArgumentReference>,
181 current_index: usize,
182) -> Result<&'a Value, FormatError> {
183 match requested_argument {
184 Some(ArgumentReference::Named(name)) => arguments
185 .get_by_name(name)
186 .ok_or(FormatError::UnknownNamedArgument { name: name.clone() }),
187
188 Some(ArgumentReference::Indexed(index)) => arguments
189 .get_by_index(*index - 1)
190 .ok_or(FormatError::UnknownIndexedArgument { index: *index }),
191
192 None => arguments
193 .get_by_index(current_index)
194 .ok_or(FormatError::NotEnoughArguments),
195 }
196}
197
198fn to_precision(number: f64, mut placeholder: Placeholder) -> String {
200 let Some(precision) = placeholder.precision else {
202 return format_placeholder!(number, &placeholder);
203 };
204
205 if !number.is_normal() {
207 return format_placeholder!(number, &placeholder);
208 }
209
210 #[allow(clippy::cast_possible_truncation)]
214 let log10 = number.abs().log10().floor() as i64;
215 let precision_i64 = precision.try_into().unwrap_or(i64::MAX);
216 if log10 > 0 && log10 <= precision_i64 || number.abs() < 10.0 {
218 placeholder.precision = Some(precision - 1 - log10.try_into().unwrap_or(0usize));
220 format_placeholder!(number, &placeholder)
221 } else {
222 placeholder.precision = Some(precision - 1);
225 format_placeholder!(number, "e", &placeholder)
226 }
227}
228
229#[allow(clippy::too_many_lines, clippy::match_same_arms)]
230fn format_value(value: &Value, placeholder: &Placeholder) -> Result<String, FormatError> {
231 match (value, &placeholder.type_specifier) {
232 (Value::Number(number), ts @ TypeSpecifier::BinaryNumber) => {
233 if let Some(number) = number.as_u64() {
234 Ok(format_placeholder!(number, "b", placeholder))
235 } else if let Some(number) = number.as_i64() {
236 Ok(format_placeholder!(number, "b", placeholder))
237 } else {
238 Err(FormatError::InvalidTypeSpecifier {
239 type_specifier: *ts,
240 value_type: ValueType::Float,
241 })
242 }
243 }
244 (v, ts @ TypeSpecifier::BinaryNumber) => Err(FormatError::InvalidTypeSpecifier {
245 type_specifier: *ts,
246 value_type: ValueType::of_value(v),
247 }),
248
249 (Value::String(string), TypeSpecifier::CharacterAsciiValue) if string.len() == 1 => {
250 Ok(format_placeholder!(string, placeholder))
251 }
252 (Value::Number(n), TypeSpecifier::CharacterAsciiValue) => {
253 if let Some(character) = n
254 .as_u64()
255 .and_then(|n| u32::try_from(n).ok())
256 .and_then(|n| char::try_from(n).ok())
257 {
258 Ok(format_placeholder!(character, placeholder))
259 } else {
260 Err(FormatError::InvalidTypeSpecifier {
261 type_specifier: TypeSpecifier::CharacterAsciiValue,
262 value_type: ValueType::Number,
263 })
264 }
265 }
266 (v, ts @ TypeSpecifier::CharacterAsciiValue) => Err(FormatError::InvalidTypeSpecifier {
267 type_specifier: *ts,
268 value_type: ValueType::of_value(v),
269 }),
270
271 (
272 Value::Number(number),
273 ts @ (TypeSpecifier::DecimalNumber | TypeSpecifier::IntegerNumber),
274 ) => {
275 if let Some(number) = number.as_u64() {
276 Ok(format_placeholder!(number, placeholder))
277 } else if let Some(number) = number.as_i64() {
278 Ok(format_placeholder!(number, placeholder))
279 } else {
280 Err(FormatError::InvalidTypeSpecifier {
281 type_specifier: *ts,
282 value_type: ValueType::Float,
283 })
284 }
285 }
286 (v, ts @ (TypeSpecifier::DecimalNumber | TypeSpecifier::IntegerNumber)) => {
287 Err(FormatError::InvalidTypeSpecifier {
288 type_specifier: *ts,
289 value_type: ValueType::of_value(v),
290 })
291 }
292
293 (Value::Number(number), ts @ TypeSpecifier::UnsignedDecimalNumber) => {
294 if let Some(number) = number.as_u64() {
295 Ok(format_placeholder!(number, placeholder))
296 } else if let Some(number) = number.as_i64() {
297 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
299 let number = number as i32 as u32;
300 Ok(format_placeholder!(number, placeholder))
301 } else {
302 Err(FormatError::InvalidTypeSpecifier {
303 type_specifier: *ts,
304 value_type: ValueType::Float,
305 })
306 }
307 }
308 (v, ts @ TypeSpecifier::UnsignedDecimalNumber) => Err(FormatError::InvalidTypeSpecifier {
309 type_specifier: *ts,
310 value_type: ValueType::of_value(v),
311 }),
312
313 (Value::Number(number), TypeSpecifier::ScientificNotation) => {
314 if let Some(number) = number.as_u64() {
315 Ok(format_placeholder!(number, "e", placeholder))
316 } else if let Some(number) = number.as_i64() {
317 Ok(format_placeholder!(number, "e", placeholder))
318 } else if let Some(number) = number.as_f64() {
319 Ok(format_placeholder!(number, "e", placeholder))
320 } else {
321 Err(FormatError::NumberIsNotANumber)
323 }
324 }
325 (v, ts @ TypeSpecifier::ScientificNotation) => Err(FormatError::InvalidTypeSpecifier {
326 type_specifier: *ts,
327 value_type: ValueType::of_value(v),
328 }),
329
330 (Value::Number(number), TypeSpecifier::FloatingPointNumber) => {
331 if let Some(number) = number.as_u64() {
332 Ok(format_placeholder!(number, placeholder))
333 } else if let Some(number) = number.as_i64() {
334 Ok(format_placeholder!(number, placeholder))
335 } else if let Some(number) = number.as_f64() {
336 Ok(format_placeholder!(number, placeholder))
337 } else {
338 Err(FormatError::NumberIsNotANumber)
340 }
341 }
342 (v, ts @ TypeSpecifier::FloatingPointNumber) => Err(FormatError::InvalidTypeSpecifier {
343 type_specifier: *ts,
344 value_type: ValueType::of_value(v),
345 }),
346
347 (Value::Number(number), TypeSpecifier::FloatingPointNumberWithSignificantDigits) => {
348 if let Some(number) = number.as_f64() {
349 Ok(to_precision(number, placeholder.clone()))
350 } else {
351 Err(FormatError::NumberIsNotANumber)
353 }
354 }
355 (v, ts @ TypeSpecifier::FloatingPointNumberWithSignificantDigits) => {
356 Err(FormatError::InvalidTypeSpecifier {
357 type_specifier: *ts,
358 value_type: ValueType::of_value(v),
359 })
360 }
361
362 (Value::Number(number), ts @ TypeSpecifier::OctalNumber) => {
363 if let Some(number) = number.as_u64() {
364 Ok(format_placeholder!(number, "o", placeholder))
365 } else if let Some(number) = number.as_i64() {
366 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
368 let number = number as i32 as u32;
369 Ok(format_placeholder!(number, "o", placeholder))
370 } else {
371 Err(FormatError::InvalidTypeSpecifier {
372 type_specifier: *ts,
373 value_type: ValueType::Float,
374 })
375 }
376 }
377 (v, ts @ TypeSpecifier::OctalNumber) => Err(FormatError::InvalidTypeSpecifier {
378 type_specifier: *ts,
379 value_type: ValueType::of_value(v),
380 }),
381
382 (Value::String(string), TypeSpecifier::String) => {
383 Ok(format_placeholder!(string, placeholder))
384 }
385 (Value::Number(number), TypeSpecifier::String) => {
386 let string = format!("{number}");
387 Ok(format_placeholder!(string, placeholder))
388 }
389 (v, ts @ TypeSpecifier::String) => Err(FormatError::InvalidTypeSpecifier {
390 type_specifier: *ts,
391 value_type: ValueType::of_value(v),
392 }),
393
394 (Value::Bool(boolean), TypeSpecifier::TrueOrFalse) => {
395 Ok(format_placeholder!(boolean, placeholder))
396 }
397 (v, ts @ TypeSpecifier::TrueOrFalse) => Err(FormatError::InvalidTypeSpecifier {
398 type_specifier: *ts,
399 value_type: ValueType::of_value(v),
400 }),
401
402 (v, TypeSpecifier::TypeOfArgument) => match v {
403 Value::String(_) => Ok("string".to_owned()),
404 Value::Number(_) => Ok("number".to_owned()),
405 Value::Null => Ok("null".to_owned()),
406 Value::Bool(_) => Ok("boolean".to_owned()),
407 Value::Array(_) => Ok("array".to_owned()),
408 Value::Object(_) => Ok("object".to_owned()),
409 },
410
411 (_v, TypeSpecifier::PrimitiveValue) => Err(FormatError::UnsupportedTypeSpecifier {
413 type_specifier: placeholder.type_specifier,
414 }),
415
416 (Value::Number(n), ts @ TypeSpecifier::HexadecimalNumberLowercase) => {
417 if let Some(number) = n.as_u64() {
418 Ok(format_placeholder!(number, "x", placeholder))
419 } else if let Some(number) = n.as_i64() {
420 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
422 let number = number as i32 as u32;
423 Ok(format_placeholder!(number, "x", placeholder))
424 } else {
425 Err(FormatError::InvalidTypeSpecifier {
426 type_specifier: *ts,
427 value_type: ValueType::Float,
428 })
429 }
430 }
431 (v, ts @ TypeSpecifier::HexadecimalNumberLowercase) => {
432 Err(FormatError::InvalidTypeSpecifier {
433 type_specifier: *ts,
434 value_type: ValueType::of_value(v),
435 })
436 }
437
438 (Value::Number(n), ts @ TypeSpecifier::HexadecimalNumberUppercase) => {
439 if let Some(number) = n.as_u64() {
440 Ok(format_placeholder!(number, "X", placeholder))
441 } else if let Some(number) = n.as_i64() {
442 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
444 let number = number as i32 as u32;
445 Ok(format_placeholder!(number, "X", placeholder))
446 } else {
447 Err(FormatError::InvalidTypeSpecifier {
448 type_specifier: *ts,
449 value_type: ValueType::Float,
450 })
451 }
452 }
453 (v, ts @ TypeSpecifier::HexadecimalNumberUppercase) => {
454 Err(FormatError::InvalidTypeSpecifier {
455 type_specifier: *ts,
456 value_type: ValueType::of_value(v),
457 })
458 }
459
460 (value, TypeSpecifier::Json) => {
461 let mut json = Vec::new();
462 if let Some(width) = placeholder.width {
463 let indent = b" ".repeat(width);
464 let mut serializer = serde_json::Serializer::with_formatter(
465 &mut json,
466 PrettyFormatter::with_indent(indent.as_slice()),
467 );
468 value.serialize(&mut serializer)?;
469 } else {
470 let mut serializer = serde_json::Serializer::new(&mut json);
471 value.serialize(&mut serializer)?;
472 }
473 let json = String::from_utf8(json)?;
474 Ok(format_placeholder!(json, placeholder))
475 }
476 }
477}
478
479pub enum FormattedMessagePart<'a> {
480 Text(&'a str),
482 Placeholder(String),
484}
485
486impl FormattedMessagePart<'_> {
487 fn len(&self) -> usize {
488 match self {
489 FormattedMessagePart::Text(text) => text.len(),
490 FormattedMessagePart::Placeholder(placeholder) => placeholder.len(),
491 }
492 }
493}
494
495impl std::fmt::Display for FormattedMessagePart<'_> {
496 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
497 match self {
498 FormattedMessagePart::Text(text) => write!(f, "{text}"),
499 FormattedMessagePart::Placeholder(placeholder) => write!(f, "{placeholder}"),
500 }
501 }
502}
503
504pub struct FormattedMessage<'a> {
505 parts: Vec<FormattedMessagePart<'a>>,
506 total_len: usize,
507}
508
509impl FormattedMessage<'_> {
510 #[must_use]
512 pub fn len(&self) -> usize {
513 self.total_len
514 }
515
516 #[must_use]
518 pub fn is_empty(&self) -> bool {
519 self.total_len == 0
520 }
521
522 #[must_use]
524 pub fn parts(&self) -> &[FormattedMessagePart<'_>] {
525 &self.parts
526 }
527}
528
529impl std::fmt::Display for FormattedMessage<'_> {
530 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
531 for part in &self.parts {
532 write!(f, "{part}")?;
533 }
534 Ok(())
535 }
536}
537
538impl Message {
539 pub fn format(&self, arguments: &ArgumentList) -> Result<String, FormatError> {
546 self.format_(arguments).map(|fm| fm.to_string())
547 }
548
549 #[doc(hidden)]
550 pub fn format_(&self, arguments: &ArgumentList) -> Result<FormattedMessage<'_>, FormatError> {
551 let mut parts = Vec::with_capacity(self.parts().len());
552
553 let mut current_placeholder = 0usize;
556 let mut total_len = 0usize;
558 for part in self.parts() {
559 let formatted = match part {
560 Part::Percent => FormattedMessagePart::Text("%"),
561 Part::Text(text) => FormattedMessagePart::Text(text),
562 Part::Placeholder(placeholder) => {
563 let value = find_value(
564 arguments,
565 placeholder.requested_argument.as_ref(),
566 current_placeholder,
567 )?;
568
569 let formatted = format_value(value, placeholder)?;
570
571 let formatted = if let Some(width) = placeholder.width {
573 let spacer = placeholder
574 .padding_specifier
575 .map_or(' ', PaddingSpecifier::char);
576
577 let alignment = if placeholder.left_align {
578 Alignment::Left
579 } else {
580 Alignment::Right
581 };
582
583 formatted.pad(width, spacer, alignment, false)
584 } else {
585 formatted
586 };
587
588 current_placeholder += 1;
589 FormattedMessagePart::Placeholder(formatted)
590 }
591 };
592 total_len += formatted.len();
593 parts.push(formatted);
594 }
595
596 Ok(FormattedMessage { parts, total_len })
597 }
598}