tc_value/
string.rs

1//! A TinyChain String
2
3use std::cmp::Ordering;
4use std::fmt;
5use std::mem::size_of;
6use std::sync::Arc;
7
8use async_trait::async_trait;
9use base64::engine::general_purpose::STANDARD_NO_PAD;
10use base64::Engine;
11use bytes::Bytes;
12use collate::{Collate, Collator};
13use destream::{de, en};
14use futures::TryFutureExt;
15use get_size::GetSize;
16use handlebars::Handlebars;
17use safecast::TryCastFrom;
18use serde::{Deserialize, Deserializer, Serialize, Serializer};
19use serde_json::json;
20
21use tc_error::*;
22use tcgeneric::{Id, Label};
23
24use super::{Link, Number};
25
26/// A TinyChain String
27#[derive(Clone, Eq, PartialEq, Hash)]
28pub struct TCString(Arc<str>);
29
30impl GetSize for TCString {
31    fn get_size(&self) -> usize {
32        size_of::<Arc<str>>() + self.0.as_bytes().len()
33    }
34}
35
36impl Default for TCString {
37    fn default() -> Self {
38        Self::from(String::default())
39    }
40}
41
42impl TCString {
43    /// Render this string as a [`Handlebars`] template with the given `data`.
44    ///
45    /// Example:
46    /// ```
47    /// # use std::collections::HashMap;
48    /// # use tc_value::TCString;
49    /// let data: HashMap<_, _> = std::iter::once(("name", "world")).collect();
50    /// assert_eq!(
51    ///     TCString::from("Hello, {{name}}!".to_string()).render(data).unwrap().as_str(),
52    ///     "Hello, world!");
53    /// ```
54    ///
55    /// See the [`handlebars`] documentation for a complete description of the formatting options.
56    pub fn render<T: Serialize>(&self, data: T) -> TCResult<TCString> {
57        Handlebars::new()
58            .render_template(self.0.as_ref(), &json!(data))
59            .map(Self::from)
60            .map_err(|cause| bad_request!("template render error").consume(cause))
61    }
62
63    /// Borrow this [`TCString`] as a `str`.
64    pub fn as_str(&self) -> &str {
65        self.0.as_ref()
66    }
67}
68
69impl PartialEq<Id> for TCString {
70    fn eq(&self, other: &Id) -> bool {
71        self.as_str() == other.as_str()
72    }
73}
74
75impl PartialEq<String> for TCString {
76    fn eq(&self, other: &String) -> bool {
77        self.as_str() == other.as_str()
78    }
79}
80
81impl PartialEq<str> for TCString {
82    fn eq(&self, other: &str) -> bool {
83        self.as_str() == other
84    }
85}
86
87impl From<String> for TCString {
88    fn from(s: String) -> Self {
89        Self(s.into())
90    }
91}
92
93impl From<Id> for TCString {
94    fn from(id: Id) -> Self {
95        Self(id.into_inner())
96    }
97}
98
99impl From<Label> for TCString {
100    fn from(label: Label) -> Self {
101        Self::from((*label).to_string())
102    }
103}
104
105impl From<Link> for TCString {
106    fn from(link: Link) -> Self {
107        Self::from(link.to_string())
108    }
109}
110
111impl From<Number> for TCString {
112    fn from(n: Number) -> Self {
113        Self::from(n.to_string())
114    }
115}
116
117impl TryCastFrom<TCString> for Bytes {
118    fn can_cast_from(value: &TCString) -> bool {
119        if value.as_str().ends_with('=') {
120            STANDARD_NO_PAD.decode(value.as_str()).is_ok()
121        } else {
122            hex::decode(value.as_str()).is_ok()
123        }
124    }
125
126    fn opt_cast_from(value: TCString) -> Option<Self> {
127        if value.as_str().ends_with('=') {
128            STANDARD_NO_PAD.decode(value.as_str()).ok().map(Self::from)
129        } else {
130            hex::decode(value.as_str()).ok().map(Self::from)
131        }
132    }
133}
134
135#[async_trait]
136impl de::FromStream for TCString {
137    type Context = ();
138
139    async fn from_stream<D: de::Decoder>(cxt: (), decoder: &mut D) -> Result<Self, D::Error> {
140        String::from_stream(cxt, decoder).map_ok(Self::from).await
141    }
142}
143
144impl<'en> en::IntoStream<'en> for TCString {
145    fn into_stream<E: en::Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
146        encoder.encode_str(self.as_str())
147    }
148}
149
150impl<'en> en::ToStream<'en> for TCString {
151    fn to_stream<E: en::Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
152        encoder.encode_str(self.as_str())
153    }
154}
155
156impl<'de> Deserialize<'de> for TCString {
157    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
158        String::deserialize(deserializer).map(Self::from)
159    }
160}
161
162impl Serialize for TCString {
163    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
164        self.0.serialize(serializer)
165    }
166}
167
168impl fmt::Debug for TCString {
169    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
170        self.0.fmt(f)
171    }
172}
173
174impl fmt::Display for TCString {
175    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176        self.0.fmt(f)
177    }
178}
179
180/// A [`Collator`] for [`TCString`] values.
181#[derive(Clone, Default, Eq, PartialEq)]
182pub struct StringCollator {
183    collator: Collator<Arc<str>>,
184}
185
186impl Collate for StringCollator {
187    type Value = TCString;
188
189    fn cmp(&self, left: &Self::Value, right: &Self::Value) -> Ordering {
190        self.collator.cmp(&left.0, &right.0)
191    }
192}