soroban_sdk/
string.rs

1use core::{cmp::Ordering, convert::Infallible, fmt::Debug};
2
3use super::{
4    env::internal::{Env as _, EnvBase as _, StringObject},
5    ConversionError, Env, TryFromVal, TryIntoVal, Val,
6};
7
8use crate::unwrap::{UnwrapInfallible, UnwrapOptimized};
9#[cfg(doc)]
10use crate::{storage::Storage, Map, Vec};
11
12#[cfg(not(target_family = "wasm"))]
13use super::xdr::{ScString, ScVal};
14
15/// String is a contiguous growable array type containing `u8`s.
16///
17/// The array is stored in the Host and available to the Guest through the
18/// functions defined on String.
19///
20/// String values can be stored as [Storage], or in other types like [Vec],
21/// [Map], etc.
22///
23/// ### Examples
24///
25/// String values can be created from slices:
26/// ```
27/// use soroban_sdk::{String, Env};
28///
29/// let env = Env::default();
30/// let msg = "a message";
31/// let s = String::from_str(&env, msg);
32/// let mut out = [0u8; 9];
33/// s.copy_into_slice(&mut out);
34/// assert_eq!(msg.as_bytes(), out)
35/// ```
36#[derive(Clone)]
37pub struct String {
38    env: Env,
39    obj: StringObject,
40}
41
42impl Debug for String {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        #[cfg(target_family = "wasm")]
45        write!(f, "String(..)")?;
46        #[cfg(not(target_family = "wasm"))]
47        write!(f, "String({})", self.to_string())?;
48        Ok(())
49    }
50}
51
52impl Eq for String {}
53
54impl PartialEq for String {
55    fn eq(&self, other: &Self) -> bool {
56        self.partial_cmp(other) == Some(Ordering::Equal)
57    }
58}
59
60impl PartialOrd for String {
61    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
62        Some(Ord::cmp(self, other))
63    }
64}
65
66impl Ord for String {
67    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
68        #[cfg(not(target_family = "wasm"))]
69        if !self.env.is_same_env(&other.env) {
70            return ScVal::from(self).cmp(&ScVal::from(other));
71        }
72        let v = self
73            .env
74            .obj_cmp(self.obj.to_val(), other.obj.to_val())
75            .unwrap_infallible();
76        v.cmp(&0)
77    }
78}
79
80impl TryFromVal<Env, String> for String {
81    type Error = ConversionError;
82
83    fn try_from_val(_env: &Env, v: &String) -> Result<Self, Self::Error> {
84        Ok(v.clone())
85    }
86}
87
88impl TryFromVal<Env, StringObject> for String {
89    type Error = Infallible;
90
91    fn try_from_val(env: &Env, val: &StringObject) -> Result<Self, Self::Error> {
92        Ok(unsafe { String::unchecked_new(env.clone(), *val) })
93    }
94}
95
96impl TryFromVal<Env, Val> for String {
97    type Error = ConversionError;
98
99    fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
100        Ok(StringObject::try_from_val(env, val)?
101            .try_into_val(env)
102            .unwrap_infallible())
103    }
104}
105
106impl TryFromVal<Env, String> for Val {
107    type Error = ConversionError;
108
109    fn try_from_val(_env: &Env, v: &String) -> Result<Self, Self::Error> {
110        Ok(v.to_val())
111    }
112}
113
114impl From<String> for Val {
115    #[inline(always)]
116    fn from(v: String) -> Self {
117        v.obj.into()
118    }
119}
120
121impl From<String> for StringObject {
122    #[inline(always)]
123    fn from(v: String) -> Self {
124        v.obj
125    }
126}
127
128impl From<&String> for StringObject {
129    #[inline(always)]
130    fn from(v: &String) -> Self {
131        v.obj
132    }
133}
134
135impl From<&String> for String {
136    #[inline(always)]
137    fn from(v: &String) -> Self {
138        v.clone()
139    }
140}
141
142#[cfg(not(target_family = "wasm"))]
143impl From<&String> for ScVal {
144    fn from(v: &String) -> Self {
145        // This conversion occurs only in test utilities, and theoretically all
146        // values should convert to an ScVal because the Env won't let the host
147        // type to exist otherwise, unwrapping. Even if there are edge cases
148        // that don't, this is a trade off for a better test developer
149        // experience.
150        ScVal::try_from_val(&v.env, &v.obj.to_val()).unwrap()
151    }
152}
153
154#[cfg(not(target_family = "wasm"))]
155impl From<String> for ScVal {
156    fn from(v: String) -> Self {
157        (&v).into()
158    }
159}
160
161#[cfg(not(target_family = "wasm"))]
162impl TryFromVal<Env, ScVal> for String {
163    type Error = ConversionError;
164    fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
165        Ok(
166            StringObject::try_from_val(env, &Val::try_from_val(env, val)?)?
167                .try_into_val(env)
168                .unwrap_infallible(),
169        )
170    }
171}
172
173impl TryFromVal<Env, &str> for String {
174    type Error = ConversionError;
175
176    fn try_from_val(env: &Env, v: &&str) -> Result<Self, Self::Error> {
177        Ok(String::from_str(env, v))
178    }
179}
180
181#[cfg(not(target_family = "wasm"))]
182impl ToString for String {
183    fn to_string(&self) -> std::string::String {
184        let sc_val: ScVal = self.try_into().unwrap();
185        if let ScVal::String(ScString(s)) = sc_val {
186            s.to_utf8_string().unwrap()
187        } else {
188            panic!("value is not a string");
189        }
190    }
191}
192
193impl String {
194    #[inline(always)]
195    pub(crate) unsafe fn unchecked_new(env: Env, obj: StringObject) -> Self {
196        Self { env, obj }
197    }
198
199    #[inline(always)]
200    pub fn env(&self) -> &Env {
201        &self.env
202    }
203
204    pub fn as_val(&self) -> &Val {
205        self.obj.as_val()
206    }
207
208    pub fn to_val(&self) -> Val {
209        self.obj.to_val()
210    }
211
212    pub fn as_object(&self) -> &StringObject {
213        &self.obj
214    }
215
216    pub fn to_object(&self) -> StringObject {
217        self.obj
218    }
219
220    #[inline(always)]
221    #[doc(hidden)]
222    #[deprecated(note = "use from_str")]
223    pub fn from_slice(env: &Env, slice: &str) -> String {
224        Self::from_str(env, slice)
225    }
226
227    #[inline(always)]
228    pub fn from_bytes(env: &Env, b: &[u8]) -> String {
229        String {
230            env: env.clone(),
231            obj: env.string_new_from_slice(b).unwrap_optimized(),
232        }
233    }
234
235    #[inline(always)]
236    pub fn from_str(env: &Env, s: &str) -> String {
237        String {
238            env: env.clone(),
239            obj: env.string_new_from_slice(s.as_bytes()).unwrap_optimized(),
240        }
241    }
242
243    #[inline(always)]
244    pub fn len(&self) -> u32 {
245        self.env().string_len(self.obj).unwrap_infallible().into()
246    }
247
248    #[inline(always)]
249    pub fn is_empty(&self) -> bool {
250        self.len() == 0
251    }
252
253    /// Copy the bytes in [String] into the given slice.
254    ///
255    /// ### Panics
256    ///
257    /// If the output slice and string are of different lengths.
258    #[inline(always)]
259    pub fn copy_into_slice(&self, slice: &mut [u8]) {
260        let env = self.env();
261        if self.len() as usize != slice.len() {
262            sdk_panic!("String::copy_into_slice with mismatched slice length")
263        }
264        env.string_copy_to_slice(self.to_object(), Val::U32_ZERO, slice)
265            .unwrap_optimized();
266    }
267}
268
269#[cfg(test)]
270mod test {
271    use super::*;
272
273    #[test]
274    fn string_from_and_to_slices() {
275        let env = Env::default();
276
277        let msg = "a message";
278        let s = String::from_str(&env, msg);
279        let mut out = [0u8; 9];
280        s.copy_into_slice(&mut out);
281        assert_eq!(msg.as_bytes(), out)
282    }
283
284    #[test]
285    fn string_from_and_to_bytes() {
286        let env = Env::default();
287
288        let msg = b"a message";
289        let s = String::from_bytes(&env, msg);
290        let mut out = [0u8; 9];
291        s.copy_into_slice(&mut out);
292        assert_eq!(msg, &out)
293    }
294
295    #[test]
296    #[should_panic]
297    fn string_to_short_slice() {
298        let env = Env::default();
299        let msg = "a message";
300        let s = String::from_str(&env, msg);
301        let mut out = [0u8; 8];
302        s.copy_into_slice(&mut out);
303    }
304
305    #[test]
306    #[should_panic]
307    fn string_to_long_slice() {
308        let env = Env::default();
309        let msg = "a message";
310        let s = String::from_str(&env, msg);
311        let mut out = [0u8; 10];
312        s.copy_into_slice(&mut out);
313    }
314}