1use 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#[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 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 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#[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}