scale_value/string_impls/
to_string.rs

1// Copyright (C) 2022-2023 Parity Technologies (UK) Ltd. (admin@parity.io)
2// This file is a part of the scale-value crate.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//         http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::string_helpers;
17use crate::prelude::*;
18use crate::value_type::{BitSequence, Composite, Primitive, Value, ValueDef, Variant};
19use core::fmt::{Display, Write};
20
21/// A struct which will try to format a [`Value`] and write the output to a writer.
22/// This can be configured with custom parsers to extend how to write out the value.
23pub struct ToWriterBuilder<T, W> {
24    style: FormatStyle,
25    custom_formatters: Vec<CustomFormatter<T, W>>,
26    indent_by: String,
27    leading_indent: String,
28    print_context: Option<ContextPrinter<T, W>>,
29}
30
31type CustomFormatter<T, W> = Box<dyn Fn(&Value<T>, &mut W) -> Option<core::fmt::Result> + 'static>;
32type ContextPrinter<T, W> = Box<dyn Fn(&T, &mut W) -> core::fmt::Result + 'static>;
33
34impl<T, W: core::fmt::Write> ToWriterBuilder<T, W> {
35    pub(crate) fn new() -> Self {
36        ToWriterBuilder {
37            style: FormatStyle::Normal,
38            custom_formatters: Vec::new(),
39            indent_by: "  ".to_owned(),
40            leading_indent: String::new(),
41            print_context: None,
42        }
43    }
44
45    /// Write the [`crate::Value`] to a compact string, omitting spaces.
46    ///
47    /// # Example
48    ///
49    /// ```rust
50    /// use scale_value::{value,Value};
51    /// use scale_value::stringify::to_writer_custom;
52    ///
53    /// let val = value!({
54    ///     foo: (1,2,3,4,5),
55    ///     bar: true
56    /// });
57    ///
58    /// let mut s = String::new();
59    ///
60    /// to_writer_custom()
61    ///     .compact()
62    ///     .write(&val, &mut s)
63    ///     .unwrap();
64    ///
65    /// assert_eq!(s, r#"{foo:(1,2,3,4,5),bar:true}"#);
66    /// ```
67    pub fn compact(mut self) -> Self {
68        self.style = FormatStyle::Compact;
69        self
70    }
71
72    /// Write the [`crate::Value`] to a pretty spaced/indented string.
73    ///
74    /// # Example
75    ///
76    /// ```rust
77    /// use scale_value::{value, Value};
78    /// use scale_value::stringify::to_writer_custom;
79    ///
80    /// let val = value!({
81    ///     foo: (1,2,3,4,5),
82    ///     bar: true
83    /// });
84    ///
85    /// let mut s = String::new();
86    ///
87    /// to_writer_custom()
88    ///     .pretty()
89    ///     .write(&val, &mut s)
90    ///     .unwrap();
91    ///
92    /// assert_eq!(s, r#"{
93    ///   foo: (
94    ///     1,
95    ///     2,
96    ///     3,
97    ///     4,
98    ///     5
99    ///   ),
100    ///   bar: true
101    /// }"#);
102    /// ```
103    pub fn pretty(mut self) -> Self {
104        self.style = FormatStyle::Pretty(0);
105        self
106    }
107
108    /// Write the [`crate::Value`] to a pretty spaced string, indenting
109    /// each new line by applying [`Self::indent_by()`] the number of
110    /// times given here. Defaults to nothing.
111    ///
112    /// # Example
113    ///
114    /// ```rust
115    /// use scale_value::{value, Value};
116    /// use scale_value::stringify::to_writer_custom;
117    ///
118    /// let val = value!({
119    ///     foo: (1,2,3,4,5),
120    ///     bar: true
121    /// });
122    ///
123    /// let mut s = String::new();
124    ///
125    /// to_writer_custom()
126    ///     .pretty()
127    ///     .leading_indent("****")
128    ///     .write(&val, &mut s)
129    ///     .unwrap();
130    ///
131    /// assert_eq!(s, r#"{
132    /// ****  foo: (
133    /// ****    1,
134    /// ****    2,
135    /// ****    3,
136    /// ****    4,
137    /// ****    5
138    /// ****  ),
139    /// ****  bar: true
140    /// ****}"#);
141    /// ```
142    pub fn leading_indent(mut self, s: impl Into<String>) -> Self {
143        self.leading_indent = s.into();
144        self
145    }
146
147    /// When using [`Self::pretty()`], this defines the characters used
148    /// to add each step of indentation to newlines. Defaults to
149    /// two spaces.
150    ///
151    /// # Example
152    ///
153    /// ```rust
154    /// use scale_value::{value, Value};
155    /// use scale_value::stringify::to_writer_custom;
156    ///
157    /// let val = value!({
158    ///     foo: (1,2,3,4,5),
159    ///     bar: true
160    /// });
161    ///
162    /// let mut s = String::new();
163    ///
164    /// to_writer_custom()
165    ///     .pretty()
166    ///     .indent_by("**")
167    ///     .write(&val, &mut s)
168    ///     .unwrap();
169    ///
170    /// assert_eq!(s, r#"{
171    /// **foo: (
172    /// ****1,
173    /// ****2,
174    /// ****3,
175    /// ****4,
176    /// ****5
177    /// **),
178    /// **bar: true
179    /// }"#);
180    /// ```
181    pub fn indent_by(mut self, s: impl Into<String>) -> Self {
182        self.indent_by = s.into();
183        self
184    }
185
186    /// Write the context contained in each value out, too. This is prepended to
187    /// each value within angle brackets (`<` and `>`).
188    ///
189    /// # Warning
190    ///
191    /// When this is used, the resulting outpuot cannot then be parsed back into [`Value`]
192    /// (in part since the user can output arbitrary content for the context). Nevertheless,
193    /// writing the context can be useful for debugging errors and providing more verbose output.
194    ///
195    /// # Example
196    ///
197    /// ```rust
198    /// use scale_value::{value, Value, ValueDef, Primitive};
199    /// use scale_value::stringify::to_writer_custom;
200    /// use std::fmt::Write;
201    ///
202    /// let val = Value {
203    ///     value: ValueDef::Primitive(Primitive::Bool(true)),
204    ///     context: "hi"
205    /// };
206    ///
207    /// let mut s = String::new();
208    ///
209    /// to_writer_custom()
210    ///     .format_context(|ctx, w: &mut &mut String| write!(w, "context: {ctx}"))
211    ///     .write(&val, &mut s)
212    ///     .unwrap();
213    ///
214    /// assert_eq!(s, r#"<context: hi> true"#);
215    /// ```
216    pub fn format_context<F: Fn(&T, &mut W) -> core::fmt::Result + 'static>(
217        mut self,
218        f: F,
219    ) -> Self {
220        self.print_context = Some(Box::new(f));
221        self
222    }
223
224    /// Add a custom formatter (for example, [`crate::stringify::custom_formatters::format_hex`]).
225    /// Custom formatters have the opportunity to read the value at each stage, and:
226    ///
227    /// - Should output `None` if they do not wish to override the standard formatting (in this case,
228    ///   they should also avoid writing anything to the provided writer).
229    /// - Should output `Some(core::fmt::Result)` if they decide to override the default formatting at
230    ///   this point.
231    ///
232    /// Custom formatters are tried in the order that they are added here, and when one decides
233    /// to write output (signalled by returning `Some(..)`), no others will be tried. Thus, the order
234    /// in which they are added is important.
235    pub fn add_custom_formatter<F: Fn(&Value<T>, &mut W) -> Option<core::fmt::Result> + 'static>(
236        mut self,
237        f: F,
238    ) -> Self {
239        self.custom_formatters.push(Box::new(f));
240        self
241    }
242
243    /// Write some value to the provided writer, using the currently configured options.
244    pub fn write(&self, value: &Value<T>, writer: W) -> core::fmt::Result {
245        let mut formatter = self.as_formatter(writer);
246        fmt_value(value, &mut formatter)
247    }
248
249    fn as_formatter(&self, writer: W) -> Formatter<'_, T, W> {
250        Formatter {
251            writer,
252            style: self.style,
253            custom_formatters: &self.custom_formatters,
254            indent_by: &self.indent_by,
255            leading_indent: &self.leading_indent,
256            print_context: &self.print_context,
257        }
258    }
259}
260
261struct Formatter<'a, T, W> {
262    writer: W,
263    style: FormatStyle,
264    custom_formatters: &'a [CustomFormatter<T, W>],
265    indent_by: &'a str,
266    leading_indent: &'a str,
267    print_context: &'a Option<ContextPrinter<T, W>>,
268}
269
270impl<'a, T, W: core::fmt::Write> Formatter<'a, T, W> {
271    fn indent_step(&mut self) {
272        self.style = match &self.style {
273            FormatStyle::Compact => FormatStyle::Compact,
274            FormatStyle::Normal => FormatStyle::Normal,
275            FormatStyle::Pretty(n) => FormatStyle::Pretty(n + 1),
276        };
277    }
278    fn unindent_step(&mut self) {
279        self.style = match &self.style {
280            FormatStyle::Compact => FormatStyle::Compact,
281            FormatStyle::Normal => FormatStyle::Normal,
282            FormatStyle::Pretty(n) => FormatStyle::Pretty(n.saturating_sub(1)),
283        };
284    }
285    fn space(&mut self) -> core::fmt::Result {
286        match self.style {
287            FormatStyle::Compact => Ok(()),
288            FormatStyle::Normal | FormatStyle::Pretty(_) => self.writer.write_char(' '),
289        }
290    }
291    fn newline(&mut self) -> core::fmt::Result {
292        match self.style {
293            FormatStyle::Compact | FormatStyle::Normal => Ok(()),
294            FormatStyle::Pretty(n) => {
295                write_newline(&mut self.writer, self.leading_indent, self.indent_by, n)
296            }
297        }
298    }
299    fn item_separator(&mut self) -> core::fmt::Result {
300        match self.style {
301            FormatStyle::Compact => Ok(()),
302            FormatStyle::Normal => self.writer.write_char(' '),
303            FormatStyle::Pretty(n) => {
304                write_newline(&mut self.writer, self.leading_indent, self.indent_by, n)
305            }
306        }
307    }
308    fn should_print_context(&self) -> bool {
309        self.print_context.is_some()
310    }
311    fn print_context(&mut self, ctx: &T) -> core::fmt::Result {
312        if let Some(f) = &self.print_context {
313            f(ctx, &mut self.writer)
314        } else {
315            Ok(())
316        }
317    }
318    fn print_custom_format(&mut self, value: &Value<T>) -> Option<core::fmt::Result> {
319        for formatter in self.custom_formatters {
320            // Try each formatter until one "accepts" the value, and then return the result from that.
321            if let Some(res) = formatter(value, &mut self.writer) {
322                return Some(res);
323            }
324        }
325        None
326    }
327}
328
329impl<'a, T, W: core::fmt::Write> core::fmt::Write for Formatter<'a, T, W> {
330    fn write_str(&mut self, s: &str) -> core::fmt::Result {
331        // For now, we don't apply any formatting to this, and expect
332        // things to manually call `newline` etc to have formatting.
333        self.writer.write_str(s)
334    }
335}
336
337fn write_newline(
338    writer: &mut impl core::fmt::Write,
339    leading_indent: &str,
340    indent_str: &str,
341    indent: usize,
342) -> core::fmt::Result {
343    writer.write_char('\n')?;
344    writer.write_str(leading_indent)?;
345    for _ in 0..indent {
346        writer.write_str(indent_str)?;
347    }
348    Ok(())
349}
350
351/// this defines whether the above [`ToWriterBuilder`] will write output in a
352/// compact style (no spaces), normal (some spaces) or indented (ie spaced; newlines
353/// between items)
354#[derive(Clone, Copy)]
355enum FormatStyle {
356    /// Pretty (indented/spaced formatting), where amount of current indent is tracked.
357    Pretty(usize),
358    /// Normal (single line but with spacing).
359    Normal,
360    /// Compact (no spaces anywhere).
361    Compact,
362}
363
364// Make a default formatter to use in the Display impls.
365fn default_builder<T, W: core::fmt::Write>(alternate: bool) -> ToWriterBuilder<T, W> {
366    let mut builder = ToWriterBuilder::new();
367    if alternate {
368        builder = builder.pretty();
369    }
370    builder
371}
372
373impl<T> Display for Value<T> {
374    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375        self.value.fmt(f)
376    }
377}
378
379impl<T> Display for ValueDef<T> {
380    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
381        let builder = default_builder(f.alternate());
382        fmt_valuedef(self, &mut builder.as_formatter(f))
383    }
384}
385
386impl<T> Display for Composite<T> {
387    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
388        let builder = default_builder(f.alternate());
389        fmt_composite(self, &mut builder.as_formatter(f))
390    }
391}
392
393impl<T> Display for Variant<T> {
394    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
395        let builder = default_builder(f.alternate());
396        fmt_variant(self, &mut builder.as_formatter(f))
397    }
398}
399
400impl Display for Primitive {
401    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
402        let builder = default_builder::<(), _>(f.alternate());
403        fmt_primitive(self, &mut builder.as_formatter(f))
404    }
405}
406
407fn fmt_value<T, W: core::fmt::Write>(v: &Value<T>, f: &mut Formatter<T, W>) -> core::fmt::Result {
408    if f.should_print_context() {
409        f.write_char('<')?;
410        f.print_context(&v.context)?;
411        f.write_str("> ")?;
412    }
413
414    // Print custom output if there is some, else fall back to the normal logic.
415    f.print_custom_format(v).unwrap_or_else(|| fmt_valuedef(&v.value, f))
416}
417
418fn fmt_valuedef<T, W: core::fmt::Write>(
419    v: &ValueDef<T>,
420    f: &mut Formatter<T, W>,
421) -> core::fmt::Result {
422    match v {
423        ValueDef::Composite(c) => fmt_composite(c, f),
424        ValueDef::Variant(v) => fmt_variant(v, f),
425        ValueDef::BitSequence(b) => fmt_bitsequence(b, f),
426        ValueDef::Primitive(p) => fmt_primitive(p, f),
427    }
428}
429
430fn fmt_variant<T, W: core::fmt::Write>(
431    v: &Variant<T>,
432    f: &mut Formatter<T, W>,
433) -> core::fmt::Result {
434    if is_ident(&v.name) {
435        f.write_str(&v.name)?;
436    } else {
437        // If the variant name isn't a valid ident, we parse it into
438        // a special "v" prefixed string to allow arbitrary content while
439        // keeping it easy to parse variant names with minimal lookahead.
440        // Most use cases should never see or care about this.
441        f.write_char('v')?;
442        fmt_string(&v.name, f)?;
443    }
444    f.space()?;
445    fmt_composite(&v.values, f)
446}
447
448fn fmt_composite<T, W: core::fmt::Write>(
449    v: &Composite<T>,
450    f: &mut Formatter<T, W>,
451) -> core::fmt::Result {
452    match v {
453        Composite::Named(vals) => {
454            if vals.is_empty() {
455                f.write_str("{}")?;
456            } else {
457                f.write_str("{")?;
458                f.indent_step();
459                f.item_separator()?;
460                for (idx, (name, val)) in vals.iter().enumerate() {
461                    if idx != 0 {
462                        f.write_str(",")?;
463                        f.item_separator()?;
464                    }
465                    if is_ident(name) {
466                        f.write_str(name)?;
467                    } else {
468                        fmt_string(name, f)?;
469                    }
470                    f.write_char(':')?;
471                    f.space()?;
472                    fmt_value(val, f)?;
473                }
474                f.unindent_step();
475                f.item_separator()?;
476                f.write_str("}")?;
477            }
478        }
479        Composite::Unnamed(vals) => {
480            if vals.is_empty() {
481                f.write_str("()")?;
482            } else {
483                f.write_char('(')?;
484                f.indent_step();
485                f.newline()?;
486                for (idx, val) in vals.iter().enumerate() {
487                    if idx != 0 {
488                        f.write_str(",")?;
489                        f.item_separator()?;
490                    }
491                    fmt_value(val, f)?;
492                }
493                f.unindent_step();
494                f.newline()?;
495                f.write_char(')')?;
496            }
497        }
498    }
499    Ok(())
500}
501
502fn fmt_primitive<T, W: core::fmt::Write>(
503    p: &Primitive,
504    f: &mut Formatter<T, W>,
505) -> core::fmt::Result {
506    match p {
507        Primitive::Bool(true) => f.write_str("true"),
508        Primitive::Bool(false) => f.write_str("false"),
509        Primitive::Char(c) => fmt_char(*c, f),
510        Primitive::I128(n) => write!(f, "{n}"),
511        Primitive::U128(n) => write!(f, "{n}"),
512        Primitive::String(s) => fmt_string(s, f),
513        // We don't currently have a sane way to parse into these or
514        // format out of them:
515        Primitive::U256(_) | Primitive::I256(_) => Err(core::fmt::Error),
516    }
517}
518
519fn fmt_string<T, W: core::fmt::Write>(s: &str, f: &mut Formatter<T, W>) -> core::fmt::Result {
520    f.write_char('"')?;
521    for char in s.chars() {
522        match string_helpers::to_escape_code(char) {
523            Some(escaped) => {
524                f.write_char('\\')?;
525                f.write_char(escaped)?
526            }
527            None => f.write_char(char)?,
528        }
529    }
530    f.write_char('"')
531}
532
533fn fmt_char<T, W: core::fmt::Write>(c: char, f: &mut Formatter<T, W>) -> core::fmt::Result {
534    f.write_char('\'')?;
535    match string_helpers::to_escape_code(c) {
536        Some(escaped) => {
537            f.write_char('\\')?;
538            f.write_char(escaped)?
539        }
540        None => f.write_char(c)?,
541    }
542    f.write_char('\'')
543}
544
545fn fmt_bitsequence<T, W: core::fmt::Write>(
546    b: &BitSequence,
547    f: &mut Formatter<T, W>,
548) -> core::fmt::Result {
549    f.write_char('<')?;
550    for bit in b.iter() {
551        match bit {
552            true => f.write_char('1')?,
553            false => f.write_char('0')?,
554        }
555    }
556    f.write_char('>')
557}
558
559/// Is the string provided a valid ident (as per from_string::parse_ident).
560fn is_ident(s: &str) -> bool {
561    let mut chars = s.chars();
562
563    // First char must be a letter (false if no chars)
564    let Some(fst) = chars.next() else { return false };
565    if !fst.is_alphabetic() {
566        return false;
567    }
568
569    // Other chars must be letter, number or underscore
570    for c in chars {
571        if !c.is_alphanumeric() && c != '_' {
572            return false;
573        }
574    }
575    true
576}
577
578#[cfg(test)]
579mod test {
580    use crate::value;
581
582    use super::*;
583
584    #[test]
585    fn outputs_expected_string() {
586        let expected = [
587            (Value::bool(true), "true"),
588            (Value::bool(false), "false"),
589            (Value::char('a'), "'a'"),
590            (Value::u128(123), "123"),
591            (value!((true, "hi")), r#"(true, "hi")"#),
592            (
593                Value::named_composite([
594                    ("hi there", Value::bool(true)),
595                    ("other", Value::string("hi")),
596                ]),
597                r#"{ "hi there": true, other: "hi" }"#,
598            ),
599            (value!(Foo { ns: (1u8, 2u8, 3u8), other: 'a' }), "Foo { ns: (1, 2, 3), other: 'a' }"),
600        ];
601
602        for (value, expected_str) in expected {
603            assert_eq!(&value.to_string(), expected_str);
604        }
605    }
606
607    #[test]
608    fn expanded_output_works() {
609        let v = value!({
610            hello: true,
611            empty: (),
612            sequence: (1,2,3),
613            variant: MyVariant (1,2,3),
614            inner: {
615                foo: "hello"
616            }
617        });
618
619        assert_eq!(
620            format!("{v:#}"),
621            "{
622  hello: true,
623  empty: (),
624  sequence: (
625    1,
626    2,
627    3
628  ),
629  variant: MyVariant (
630    1,
631    2,
632    3
633  ),
634  inner: {
635    foo: \"hello\"
636  }
637}"
638        );
639    }
640
641    #[test]
642    fn compact_output_works() {
643        let v = value!({
644            hello: true,
645            empty: (),
646            sequence: (1u8,2u8,3u8),
647            variant: MyVariant (1u8,2u8,3u8),
648            inner: {
649                foo: "hello"
650            }
651        });
652
653        let mut s = String::new();
654        ToWriterBuilder::new().compact().write(&v, &mut s).unwrap();
655
656        assert_eq!(
657            s,
658            "{hello:true,empty:(),sequence:(1,2,3),variant:MyVariant(1,2,3),inner:{foo:\"hello\"}}"
659        );
660    }
661
662    #[test]
663    fn pretty_variant_ident_used_when_possible() {
664        let expected = [
665            ("simpleIdent", true),
666            ("S", true),
667            ("S123", true),
668            ("S123_", true),
669            ("", false),
670            ("complex ident", false),
671            ("0Something", false),
672            ("_Something", false),
673        ];
674
675        for (ident, should_be_simple) in expected {
676            let v = Value::variant(ident, Composite::Named(vec![]));
677            let s = v.to_string();
678            assert_eq!(
679                !s.trim().starts_with('v'),
680                should_be_simple,
681                "{s} should be simple: {should_be_simple}"
682            );
683        }
684    }
685
686    // These tests stringify and then parse from string, so need "from-string" feature.
687    #[cfg(feature = "from-string")]
688    mod from_to {
689        use super::*;
690
691        fn assert_from_to<T: core::fmt::Debug + PartialEq>(val: Value<T>) {
692            let s = val.to_string();
693            match crate::stringify::from_str(&s) {
694                (Err(e), _) => {
695                    panic!("'{s}' cannot be parsed back into the value {val:?}: {e}");
696                }
697                (Ok(new_val), rest) => {
698                    assert_eq!(
699                        val.remove_context(),
700                        new_val,
701                        "value should be the same after parsing to/from a string"
702                    );
703                    assert_eq!(
704                        rest.len(),
705                        0,
706                        "there should be no unparsed string but got '{rest}'"
707                    );
708                }
709            }
710        }
711
712        #[test]
713        fn primitives() {
714            assert_from_to(Value::bool(true));
715            assert_from_to(Value::bool(false));
716
717            assert_from_to(Value::char('\n'));
718            assert_from_to(Value::char('😀'));
719            assert_from_to(Value::char('a'));
720            assert_from_to(Value::char('\0'));
721            assert_from_to(Value::char('\t'));
722
723            assert_from_to(Value::i128(-123_456));
724            assert_from_to(Value::u128(0));
725            assert_from_to(Value::u128(123456));
726
727            assert_from_to(Value::string("hello \"you\",\n\n\t How are you??"));
728            assert_from_to(Value::string(""));
729        }
730
731        #[test]
732        fn composites() {
733            assert_from_to(Value::named_composite([
734                ("foo", Value::u128(12345)),
735                ("bar", Value::bool(true)),
736                ("a \"weird\" name", Value::string("Woop!")),
737            ]));
738            assert_from_to(Value::unnamed_composite([
739                Value::u128(12345),
740                Value::bool(true),
741                Value::string("Woop!"),
742            ]));
743        }
744
745        #[test]
746        fn variants() {
747            assert_from_to(Value::named_variant(
748                "A weird variant name",
749                [
750                    ("foo", Value::u128(12345)),
751                    ("bar", Value::bool(true)),
752                    ("a \"weird\" name", Value::string("Woop!")),
753                ],
754            ));
755            assert_from_to(value!(MyVariant(12345u32, true, "Woop!")));
756        }
757
758        #[test]
759        fn bit_sequences() {
760            use scale_bits::bits;
761            assert_from_to(Value::bit_sequence(bits![0, 1, 1, 0, 1, 1, 0]));
762            assert_from_to(Value::bit_sequence(bits![]));
763        }
764    }
765}