soroban_sdk/
string.rs

1use core::{cmp::Ordering, convert::Infallible, fmt::Debug};
2
3use super::{
4    env::internal::{Env as _, EnvBase as _, StringObject},
5    Bytes, ConversionError, Env, IntoVal, 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})")?;
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 TryFromVal<Env, &String> for Val {
115    type Error = ConversionError;
116
117    fn try_from_val(_env: &Env, v: &&String) -> Result<Self, Self::Error> {
118        Ok(v.to_val())
119    }
120}
121
122impl From<String> for Val {
123    #[inline(always)]
124    fn from(v: String) -> Self {
125        v.obj.into()
126    }
127}
128
129impl From<String> for StringObject {
130    #[inline(always)]
131    fn from(v: String) -> Self {
132        v.obj
133    }
134}
135
136impl From<&String> for StringObject {
137    #[inline(always)]
138    fn from(v: &String) -> Self {
139        v.obj
140    }
141}
142
143impl From<&String> for String {
144    #[inline(always)]
145    fn from(v: &String) -> Self {
146        v.clone()
147    }
148}
149
150impl From<&String> for Bytes {
151    fn from(v: &String) -> Self {
152        Env::string_to_bytes(&v.env, v.obj.clone())
153            .unwrap_infallible()
154            .into_val(&v.env)
155    }
156}
157
158impl From<String> for Bytes {
159    fn from(v: String) -> Self {
160        (&v).into()
161    }
162}
163
164#[cfg(not(target_family = "wasm"))]
165impl From<&String> for ScVal {
166    fn from(v: &String) -> Self {
167        // This conversion occurs only in test utilities, and theoretically all
168        // values should convert to an ScVal because the Env won't let the host
169        // type to exist otherwise, unwrapping. Even if there are edge cases
170        // that don't, this is a trade off for a better test developer
171        // experience.
172        ScVal::try_from_val(&v.env, &v.obj.to_val()).unwrap()
173    }
174}
175
176#[cfg(not(target_family = "wasm"))]
177impl From<String> for ScVal {
178    fn from(v: String) -> Self {
179        (&v).into()
180    }
181}
182
183#[cfg(not(target_family = "wasm"))]
184impl TryFromVal<Env, ScVal> for String {
185    type Error = ConversionError;
186    fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
187        Ok(
188            StringObject::try_from_val(env, &Val::try_from_val(env, val)?)?
189                .try_into_val(env)
190                .unwrap_infallible(),
191        )
192    }
193}
194
195impl TryFromVal<Env, &str> for String {
196    type Error = ConversionError;
197
198    fn try_from_val(env: &Env, v: &&str) -> Result<Self, Self::Error> {
199        Ok(String::from_str(env, v))
200    }
201}
202
203#[cfg(not(target_family = "wasm"))]
204impl core::fmt::Display for String {
205    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
206        let sc_val: ScVal = self.try_into().unwrap();
207        if let ScVal::String(ScString(s)) = sc_val {
208            let utf8_s = s.to_utf8_string().unwrap();
209            write!(f, "{utf8_s}")?;
210        } else {
211            panic!("value is not a string");
212        }
213        Ok(())
214    }
215}
216
217impl String {
218    #[inline(always)]
219    pub(crate) unsafe fn unchecked_new(env: Env, obj: StringObject) -> Self {
220        Self { env, obj }
221    }
222
223    #[inline(always)]
224    pub fn env(&self) -> &Env {
225        &self.env
226    }
227
228    pub fn as_val(&self) -> &Val {
229        self.obj.as_val()
230    }
231
232    pub fn to_val(&self) -> Val {
233        self.obj.to_val()
234    }
235
236    pub fn as_object(&self) -> &StringObject {
237        &self.obj
238    }
239
240    pub fn to_object(&self) -> StringObject {
241        self.obj
242    }
243
244    #[inline(always)]
245    #[doc(hidden)]
246    #[deprecated(note = "use from_str")]
247    pub fn from_slice(env: &Env, slice: &str) -> String {
248        Self::from_str(env, slice)
249    }
250
251    #[inline(always)]
252    pub fn from_bytes(env: &Env, b: &[u8]) -> String {
253        String {
254            env: env.clone(),
255            obj: env.string_new_from_slice(b).unwrap_optimized(),
256        }
257    }
258
259    #[inline(always)]
260    pub fn from_str(env: &Env, s: &str) -> String {
261        String {
262            env: env.clone(),
263            obj: env.string_new_from_slice(s.as_bytes()).unwrap_optimized(),
264        }
265    }
266
267    #[inline(always)]
268    pub fn len(&self) -> u32 {
269        self.env().string_len(self.obj).unwrap_infallible().into()
270    }
271
272    #[inline(always)]
273    pub fn is_empty(&self) -> bool {
274        self.len() == 0
275    }
276
277    /// Copy the bytes in [String] into the given slice.
278    ///
279    /// ### Panics
280    ///
281    /// If the output slice and string are of different lengths.
282    #[inline(always)]
283    pub fn copy_into_slice(&self, slice: &mut [u8]) {
284        let env = self.env();
285        if self.len() as usize != slice.len() {
286            sdk_panic!("String::copy_into_slice with mismatched slice length")
287        }
288        env.string_copy_to_slice(self.to_object(), Val::U32_ZERO, slice)
289            .unwrap_optimized();
290    }
291
292    /// Converts the contents of the String into a respective Bytes object.
293    pub fn to_bytes(&self) -> Bytes {
294        self.into()
295    }
296}
297
298#[cfg(test)]
299mod test {
300    use super::*;
301    use crate::IntoVal;
302
303    #[test]
304    fn string_from_and_to_slices() {
305        let env = Env::default();
306
307        let msg = "a message";
308        let s = String::from_str(&env, msg);
309        let mut out = [0u8; 9];
310        s.copy_into_slice(&mut out);
311        assert_eq!(msg.as_bytes(), out)
312    }
313
314    #[test]
315    fn string_from_and_to_bytes() {
316        let env = Env::default();
317
318        let msg = b"a message";
319        let s = String::from_bytes(&env, msg);
320        let mut out = [0u8; 9];
321        s.copy_into_slice(&mut out);
322        assert_eq!(msg, &out)
323    }
324
325    #[test]
326    #[should_panic]
327    fn string_to_short_slice() {
328        let env = Env::default();
329        let msg = "a message";
330        let s = String::from_str(&env, msg);
331        let mut out = [0u8; 8];
332        s.copy_into_slice(&mut out);
333    }
334
335    #[test]
336    #[should_panic]
337    fn string_to_long_slice() {
338        let env = Env::default();
339        let msg = "a message";
340        let s = String::from_str(&env, msg);
341        let mut out = [0u8; 10];
342        s.copy_into_slice(&mut out);
343    }
344
345    #[test]
346    fn string_to_val() {
347        let env = Env::default();
348
349        let s = String::from_str(&env, "abcdef");
350        let val: Val = s.clone().into_val(&env);
351        let rt: String = val.into_val(&env);
352
353        assert_eq!(s, rt);
354    }
355
356    #[test]
357    fn ref_string_to_val() {
358        let env = Env::default();
359
360        let s = String::from_str(&env, "abcdef");
361        let val: Val = (&s).into_val(&env);
362        let rt: String = val.into_val(&env);
363
364        assert_eq!(s, rt);
365    }
366
367    #[test]
368    fn double_ref_string_to_val() {
369        let env = Env::default();
370
371        let s = String::from_str(&env, "abcdef");
372        let val: Val = (&&s).into_val(&env);
373        let rt: String = val.into_val(&env);
374
375        assert_eq!(s, rt);
376    }
377
378    #[test]
379    fn test_string_to_bytes() {
380        let env = Env::default();
381        let s = String::from_str(&env, "abcdef");
382        let b: Bytes = s.clone().into();
383        assert_eq!(b.len(), 6);
384        let mut slice = [0u8; 6];
385        b.copy_into_slice(&mut slice);
386        assert_eq!(&slice, b"abcdef");
387        let b2 = s.to_bytes();
388        assert_eq!(b, b2);
389    }
390
391    #[test]
392    fn test_string_accepts_any_bytes_even_invalid_utf8() {
393        let env = Env::default();
394        let input = b"a\xc3\x28d"; // \xc3 is invalid utf8
395        let s = String::from_bytes(&env, &input[..]);
396        let b = s.to_bytes().to_buffer::<4>();
397        assert_eq!(b.as_slice(), input);
398    }
399
400    #[test]
401    fn test_string_display_to_string() {
402        let env = Env::default();
403        let input = "abcdef";
404        let s = String::from_str(&env, input);
405        let rt = s.to_string();
406        assert_eq!(input, &rt);
407    }
408
409    #[test]
410    #[should_panic = "Utf8Error"]
411    fn test_string_display_to_string_invalid_utf8() {
412        let env = Env::default();
413        let input = b"a\xc3\x28d"; // \xc3 is invalid utf8
414        let s = String::from_bytes(&env, &input[..]);
415        let _ = s.to_string();
416    }
417}