radix_common/data/scrypto/
custom_formatting.rs

1use crate::internal_prelude::*;
2
3#[derive(Clone, Copy, Debug, Default)]
4pub struct ScryptoValueDisplayContext<'a> {
5    pub address_bech32_encoder: Option<&'a AddressBech32Encoder>,
6}
7
8impl<'a> ScryptoValueDisplayContext<'a> {
9    pub fn no_context() -> Self {
10        Self {
11            address_bech32_encoder: None,
12        }
13    }
14
15    pub fn with_optional_bech32(address_bech32_encoder: Option<&'a AddressBech32Encoder>) -> Self {
16        Self {
17            address_bech32_encoder,
18        }
19    }
20}
21
22impl<'a> From<AddressDisplayContext<'a>> for ScryptoValueDisplayContext<'a> {
23    fn from(val: AddressDisplayContext<'a>) -> Self {
24        ScryptoValueDisplayContext::with_optional_bech32(val.encoder)
25    }
26}
27
28impl<'a> From<&'a AddressBech32Encoder> for ScryptoValueDisplayContext<'a> {
29    fn from(val: &'a AddressBech32Encoder) -> Self {
30        ScryptoValueDisplayContext::with_optional_bech32(Some(val))
31    }
32}
33
34impl<'a> From<Option<&'a AddressBech32Encoder>> for ScryptoValueDisplayContext<'a> {
35    fn from(val: Option<&'a AddressBech32Encoder>) -> Self {
36        ScryptoValueDisplayContext::with_optional_bech32(val)
37    }
38}
39
40impl<'a> CustomDisplayContext<'a> for ScryptoValueDisplayContext<'a> {
41    type CustomExtension = ScryptoCustomExtension;
42}
43
44impl FormattableCustomExtension for ScryptoCustomExtension {
45    type CustomDisplayContext<'a> = ScryptoValueDisplayContext<'a>;
46
47    fn display_string_content<'s, 'de, 'a, 't, 's1, 's2, F: fmt::Write>(
48        f: &mut F,
49        context: &Self::CustomDisplayContext<'a>,
50        value: &<Self::CustomTraversal as CustomTraversal>::CustomTerminalValueRef<'de>,
51    ) -> Result<(), fmt::Error> {
52        match &value.0 {
53            ScryptoCustomValue::Reference(value) => {
54                write!(f, "\"{}\"", value.0.display(context.address_bech32_encoder))?;
55            }
56            ScryptoCustomValue::Own(value) => {
57                write!(f, "\"{}\"", value.0.display(context.address_bech32_encoder))?;
58            }
59            ScryptoCustomValue::Decimal(value) => {
60                write!(f, "\"{}\"", value)?;
61            }
62            ScryptoCustomValue::PreciseDecimal(value) => {
63                write!(f, "\"{}\"", value)?;
64            }
65            ScryptoCustomValue::NonFungibleLocalId(value) => {
66                write!(f, "\"{}\"", value)?;
67            }
68        }
69        Ok(())
70    }
71
72    fn debug_string_content<'s, 'de, 'a, 't, 's1, 's2, F: fmt::Write>(
73        f: &mut F,
74        context: &Self::CustomDisplayContext<'a>,
75        value: &<Self::CustomTraversal as CustomTraversal>::CustomTerminalValueRef<'de>,
76    ) -> Result<(), fmt::Error> {
77        match &value.0 {
78            ScryptoCustomValue::Reference(value) => {
79                write!(f, "\"{}\"", value.0.display(context.address_bech32_encoder))?;
80            }
81            ScryptoCustomValue::Own(value) => {
82                write!(f, "\"{}\"", value.0.display(context.address_bech32_encoder))?;
83            }
84            ScryptoCustomValue::Decimal(value) => {
85                write!(f, "{value:?}")?;
86            }
87            ScryptoCustomValue::PreciseDecimal(value) => {
88                write!(f, "{value:?}")?;
89            }
90            ScryptoCustomValue::NonFungibleLocalId(value) => {
91                write!(f, "{value:?}")?;
92            }
93        }
94        Ok(())
95    }
96
97    fn code_generation_string_content<'s, 'de, 'a, 't, 's1, 's2, F: fmt::Write>(
98        f: &mut F,
99        context: &Self::CustomDisplayContext<'a>,
100        value: &<Self::CustomTraversal as CustomTraversal>::CustomTerminalValueRef<'de>,
101    ) -> Result<(), fmt::Error> {
102        match &value.0 {
103            ScryptoCustomValue::Reference(value) => {
104                let address_type = match value
105                    .as_node_id()
106                    .entity_type()
107                    .unwrap_or(EntityType::GlobalGenericComponent)
108                {
109                    entity_type if entity_type.is_internal() => "InternalAddress",
110                    entity_type if entity_type.is_global_package() => "PackageAddress",
111                    entity_type if entity_type.is_global_resource_manager() => "ResourceAddress",
112                    _ => "ComponentAddress",
113                };
114                write!(
115                    f,
116                    "{}::from_str(\"{}\").unwrap()",
117                    address_type,
118                    value.0.display(context.address_bech32_encoder)
119                )?;
120            }
121            ScryptoCustomValue::Own(value) => {
122                write!(
123                    f,
124                    "Own::from_str(\"{}\").unwrap()",
125                    value.0.display(context.address_bech32_encoder)
126                )?;
127            }
128            ScryptoCustomValue::Decimal(value) => {
129                write!(f, "dec!(\"{}\")", value)?;
130            }
131            ScryptoCustomValue::PreciseDecimal(value) => {
132                write!(f, "pdec!(\"{}\")", value)?;
133            }
134            ScryptoCustomValue::NonFungibleLocalId(value) => {
135                write!(f, "NonFungibleLocalId::from_str(\"{}\").unwrap()", value)?;
136            }
137        }
138        Ok(())
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::address::test_addresses::*;
146    use crate::address::AddressBech32Encoder;
147    use crate::data::scrypto::model::*;
148
149    #[test]
150    fn test_rustlike_string_format_with_network() {
151        use crate::math::{Decimal, PreciseDecimal};
152        let encoder = AddressBech32Encoder::for_simulator();
153        let value = ScryptoValue::Tuple {
154            fields: vec![
155                Value::Custom {
156                    value: ScryptoCustomValue::Reference(Reference(FUNGIBLE_RESOURCE_NODE_ID)),
157                },
158                Value::Custom {
159                    value: ScryptoCustomValue::Own(Own(FUNGIBLE_RESOURCE_NODE_ID)),
160                },
161                Value::Custom {
162                    value: ScryptoCustomValue::Decimal(Decimal::ONE),
163                },
164                Value::Custom {
165                    value: ScryptoCustomValue::Decimal(Decimal::ONE.checked_div(100).unwrap()),
166                },
167                Value::Custom {
168                    value: ScryptoCustomValue::PreciseDecimal(PreciseDecimal::ZERO),
169                },
170                Value::Custom {
171                    value: ScryptoCustomValue::NonFungibleLocalId(
172                        NonFungibleLocalId::string("hello").unwrap(),
173                    ),
174                },
175                Value::Custom {
176                    value: ScryptoCustomValue::NonFungibleLocalId(NonFungibleLocalId::integer(123)),
177                },
178                Value::Custom {
179                    value: ScryptoCustomValue::NonFungibleLocalId(
180                        NonFungibleLocalId::bytes(vec![0x23, 0x45]).unwrap(),
181                    ),
182                },
183                Value::Custom {
184                    value: ScryptoCustomValue::NonFungibleLocalId(NonFungibleLocalId::ruid(
185                        [0x11; 32],
186                    )),
187                },
188            ],
189        };
190
191        let expected = format!("Tuple(Reference(\"{FUNGIBLE_RESOURCE_SIM_ADDRESS}\"), Own(\"{FUNGIBLE_RESOURCE_SIM_ADDRESS}\"), Decimal(\"1\"), Decimal(\"0.01\"), PreciseDecimal(\"0\"), NonFungibleLocalId(\"<hello>\"), NonFungibleLocalId(\"#123#\"), NonFungibleLocalId(\"[2345]\"), NonFungibleLocalId(\"{{1111111111111111-1111111111111111-1111111111111111-1111111111111111}}\"))");
192
193        let context = ScryptoValueDisplayContext::with_optional_bech32(Some(&encoder));
194
195        let payload = ScryptoRawPayload::new_from_valid_owned(scrypto_encode(&value).unwrap());
196
197        let actual_rustlike = payload.to_string(ValueDisplayParameters::Schemaless {
198            display_mode: DisplayMode::RustLike(RustLikeOptions::full()),
199            print_mode: PrintMode::SingleLine,
200            custom_context: context,
201            depth_limit: SCRYPTO_SBOR_V1_MAX_DEPTH,
202        });
203        let actual_nested = payload.to_string(ValueDisplayParameters::Schemaless {
204            display_mode: DisplayMode::RustLike(RustLikeOptions::full()),
205            print_mode: PrintMode::SingleLine,
206            custom_context: context,
207            depth_limit: SCRYPTO_SBOR_V1_MAX_DEPTH,
208        });
209
210        // They're both the same
211        assert_eq!(actual_rustlike, expected);
212        assert_eq!(actual_nested, expected);
213    }
214}