use core::{cmp::Ordering, convert::Infallible, fmt::Debug};
use super::{
env::internal::{Env as _, EnvBase as _, StringObject},
Bytes, ConversionError, Env, IntoVal, TryFromVal, TryIntoVal, Val,
};
use crate::unwrap::{UnwrapInfallible, UnwrapOptimized};
#[cfg(doc)]
use crate::{storage::Storage, Map, Vec};
#[cfg(not(target_family = "wasm"))]
use super::xdr::{ScString, ScVal};
#[derive(Clone)]
pub struct String {
env: Env,
obj: StringObject,
}
impl Debug for String {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
#[cfg(target_family = "wasm")]
write!(f, "String(..)")?;
#[cfg(not(target_family = "wasm"))]
write!(f, "String({self})")?;
Ok(())
}
}
impl Eq for String {}
impl PartialEq for String {
fn eq(&self, other: &Self) -> bool {
self.partial_cmp(other) == Some(Ordering::Equal)
}
}
impl PartialOrd for String {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(Ord::cmp(self, other))
}
}
impl Ord for String {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
#[cfg(not(target_family = "wasm"))]
if !self.env.is_same_env(&other.env) {
return ScVal::from(self).cmp(&ScVal::from(other));
}
let v = self
.env
.obj_cmp(self.obj.to_val(), other.obj.to_val())
.unwrap_infallible();
v.cmp(&0)
}
}
impl TryFromVal<Env, String> for String {
type Error = ConversionError;
fn try_from_val(_env: &Env, v: &String) -> Result<Self, Self::Error> {
Ok(v.clone())
}
}
impl TryFromVal<Env, StringObject> for String {
type Error = Infallible;
fn try_from_val(env: &Env, val: &StringObject) -> Result<Self, Self::Error> {
Ok(unsafe { String::unchecked_new(env.clone(), *val) })
}
}
impl TryFromVal<Env, Val> for String {
type Error = ConversionError;
fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
Ok(StringObject::try_from_val(env, val)?
.try_into_val(env)
.unwrap_infallible())
}
}
impl TryFromVal<Env, String> for Val {
type Error = ConversionError;
fn try_from_val(_env: &Env, v: &String) -> Result<Self, Self::Error> {
Ok(v.to_val())
}
}
impl TryFromVal<Env, &String> for Val {
type Error = ConversionError;
fn try_from_val(_env: &Env, v: &&String) -> Result<Self, Self::Error> {
Ok(v.to_val())
}
}
impl From<String> for Val {
#[inline(always)]
fn from(v: String) -> Self {
v.obj.into()
}
}
impl From<String> for StringObject {
#[inline(always)]
fn from(v: String) -> Self {
v.obj
}
}
impl From<&String> for StringObject {
#[inline(always)]
fn from(v: &String) -> Self {
v.obj
}
}
impl From<&String> for String {
#[inline(always)]
fn from(v: &String) -> Self {
v.clone()
}
}
impl From<&String> for Bytes {
fn from(v: &String) -> Self {
Env::string_to_bytes(&v.env, v.obj.clone())
.unwrap_infallible()
.into_val(&v.env)
}
}
impl From<String> for Bytes {
fn from(v: String) -> Self {
(&v).into()
}
}
#[cfg(not(target_family = "wasm"))]
impl From<&String> for ScVal {
fn from(v: &String) -> Self {
ScVal::try_from_val(&v.env, &v.obj.to_val()).unwrap()
}
}
#[cfg(not(target_family = "wasm"))]
impl From<String> for ScVal {
fn from(v: String) -> Self {
(&v).into()
}
}
#[cfg(not(target_family = "wasm"))]
impl TryFromVal<Env, ScVal> for String {
type Error = ConversionError;
fn try_from_val(env: &Env, val: &ScVal) -> Result<Self, Self::Error> {
Ok(
StringObject::try_from_val(env, &Val::try_from_val(env, val)?)?
.try_into_val(env)
.unwrap_infallible(),
)
}
}
impl TryFromVal<Env, &str> for String {
type Error = ConversionError;
fn try_from_val(env: &Env, v: &&str) -> Result<Self, Self::Error> {
Ok(String::from_str(env, v))
}
}
#[cfg(not(target_family = "wasm"))]
impl core::fmt::Display for String {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
let sc_val: ScVal = self.try_into().unwrap();
if let ScVal::String(ScString(s)) = sc_val {
let utf8_s = s.to_utf8_string().unwrap();
write!(f, "{utf8_s}")?;
} else {
panic!("value is not a string");
}
Ok(())
}
}
impl String {
#[inline(always)]
pub(crate) unsafe fn unchecked_new(env: Env, obj: StringObject) -> Self {
Self { env, obj }
}
#[inline(always)]
pub fn env(&self) -> &Env {
&self.env
}
pub fn as_val(&self) -> &Val {
self.obj.as_val()
}
pub fn to_val(&self) -> Val {
self.obj.to_val()
}
pub fn as_object(&self) -> &StringObject {
&self.obj
}
pub fn to_object(&self) -> StringObject {
self.obj
}
#[inline(always)]
#[doc(hidden)]
#[deprecated(note = "use from_str")]
pub fn from_slice(env: &Env, slice: &str) -> String {
Self::from_str(env, slice)
}
#[inline(always)]
pub fn from_bytes(env: &Env, b: &[u8]) -> String {
String {
env: env.clone(),
obj: env.string_new_from_slice(b).unwrap_optimized(),
}
}
#[inline(always)]
pub fn from_str(env: &Env, s: &str) -> String {
String {
env: env.clone(),
obj: env.string_new_from_slice(s.as_bytes()).unwrap_optimized(),
}
}
#[inline(always)]
pub fn len(&self) -> u32 {
self.env().string_len(self.obj).unwrap_infallible().into()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline(always)]
pub fn copy_into_slice(&self, slice: &mut [u8]) {
let env = self.env();
if self.len() as usize != slice.len() {
sdk_panic!("String::copy_into_slice with mismatched slice length")
}
env.string_copy_to_slice(self.to_object(), Val::U32_ZERO, slice)
.unwrap_optimized();
}
pub fn to_bytes(&self) -> Bytes {
self.into()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::IntoVal;
#[test]
fn string_from_and_to_slices() {
let env = Env::default();
let msg = "a message";
let s = String::from_str(&env, msg);
let mut out = [0u8; 9];
s.copy_into_slice(&mut out);
assert_eq!(msg.as_bytes(), out)
}
#[test]
fn string_from_and_to_bytes() {
let env = Env::default();
let msg = b"a message";
let s = String::from_bytes(&env, msg);
let mut out = [0u8; 9];
s.copy_into_slice(&mut out);
assert_eq!(msg, &out)
}
#[test]
#[should_panic]
fn string_to_short_slice() {
let env = Env::default();
let msg = "a message";
let s = String::from_str(&env, msg);
let mut out = [0u8; 8];
s.copy_into_slice(&mut out);
}
#[test]
#[should_panic]
fn string_to_long_slice() {
let env = Env::default();
let msg = "a message";
let s = String::from_str(&env, msg);
let mut out = [0u8; 10];
s.copy_into_slice(&mut out);
}
#[test]
fn string_to_val() {
let env = Env::default();
let s = String::from_str(&env, "abcdef");
let val: Val = s.clone().into_val(&env);
let rt: String = val.into_val(&env);
assert_eq!(s, rt);
}
#[test]
fn ref_string_to_val() {
let env = Env::default();
let s = String::from_str(&env, "abcdef");
let val: Val = (&s).into_val(&env);
let rt: String = val.into_val(&env);
assert_eq!(s, rt);
}
#[test]
fn double_ref_string_to_val() {
let env = Env::default();
let s = String::from_str(&env, "abcdef");
let val: Val = (&&s).into_val(&env);
let rt: String = val.into_val(&env);
assert_eq!(s, rt);
}
#[test]
fn test_string_to_bytes() {
let env = Env::default();
let s = String::from_str(&env, "abcdef");
let b: Bytes = s.clone().into();
assert_eq!(b.len(), 6);
let mut slice = [0u8; 6];
b.copy_into_slice(&mut slice);
assert_eq!(&slice, b"abcdef");
let b2 = s.to_bytes();
assert_eq!(b, b2);
}
#[test]
fn test_string_accepts_any_bytes_even_invalid_utf8() {
let env = Env::default();
let input = b"a\xc3\x28d"; let s = String::from_bytes(&env, &input[..]);
let b = s.to_bytes().to_buffer::<4>();
assert_eq!(b.as_slice(), input);
}
#[test]
fn test_string_display_to_string() {
let env = Env::default();
let input = "abcdef";
let s = String::from_str(&env, input);
let rt = s.to_string();
assert_eq!(input, &rt);
}
#[test]
#[should_panic = "Utf8Error"]
fn test_string_display_to_string_invalid_utf8() {
let env = Env::default();
let input = b"a\xc3\x28d"; let s = String::from_bytes(&env, &input[..]);
let _ = s.to_string();
}
}