rquickjs_core/value/
string.rs

1use crate::{qjs, Ctx, Error, Result, StdString, Value};
2use core::{ffi::c_char, mem, ptr::NonNull, slice, str};
3
4/// Rust representation of a JavaScript string.
5#[derive(Debug, Clone, PartialEq, Hash)]
6#[repr(transparent)]
7pub struct String<'js>(pub(crate) Value<'js>);
8
9impl<'js> String<'js> {
10    /// Convert the JavaScript string to a Rust string.
11    pub fn to_string(&self) -> Result<StdString> {
12        let mut len = mem::MaybeUninit::uninit();
13        let ptr = unsafe {
14            qjs::JS_ToCStringLen(self.0.ctx.as_ptr(), len.as_mut_ptr(), self.0.as_js_value())
15        };
16        if ptr.is_null() {
17            // Might not ever happen but I am not 100% sure
18            // so just incase check it.
19            return Err(Error::Unknown);
20        }
21        let len = unsafe { len.assume_init() };
22        let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr as _, len as _) };
23        let result = str::from_utf8(bytes).map(|s| s.into());
24        unsafe { qjs::JS_FreeCString(self.0.ctx.as_ptr(), ptr) };
25        Ok(result?)
26    }
27
28    /// Convert the Javascript string to a Javascript C string.
29    pub fn to_cstring(self) -> Result<CString<'js>> {
30        CString::from_string(self)
31    }
32
33    /// Create a new JavaScript string from an Rust string.
34    pub fn from_str(ctx: Ctx<'js>, s: &str) -> Result<Self> {
35        let len = s.len();
36        let ptr = s.as_ptr();
37        Ok(unsafe {
38            let js_val = qjs::JS_NewStringLen(ctx.as_ptr(), ptr as _, len as _);
39            let js_val = ctx.handle_exception(js_val)?;
40            String::from_js_value(ctx, js_val)
41        })
42    }
43}
44
45/// Rust representation of a JavaScript C string.
46#[derive(Debug)]
47pub struct CString<'js> {
48    ptr: NonNull<c_char>,
49    len: usize,
50    ctx: Ctx<'js>,
51}
52
53impl<'js> CString<'js> {
54    /// Create a new JavaScript C string from a JavaScript string.
55    pub fn from_string(string: String<'js>) -> Result<Self> {
56        let mut len = mem::MaybeUninit::uninit();
57        // SAFETY: The pointer points to a JSString content which is ref counted
58        let ptr = unsafe {
59            qjs::JS_ToCStringLen(string.0.ctx.as_ptr(), len.as_mut_ptr(), string.as_raw())
60        };
61        if ptr.is_null() {
62            // Might not ever happen but I am not 100% sure
63            // so just incase check it.
64            return Err(Error::Unknown);
65        }
66        let len = unsafe { len.assume_init() };
67        Ok(Self {
68            ptr: unsafe { NonNull::new_unchecked(ptr as *mut _) },
69            len,
70            ctx: string.0.ctx.clone(),
71        })
72    }
73
74    /// Converts a `CString` to a raw pointer.
75    pub fn as_ptr(&self) -> *const c_char {
76        self.ptr.as_ptr() as *const _
77    }
78
79    /// Returns the length of this `CString`, in bytes (not chars or graphemes).
80    pub fn len(&self) -> usize {
81        self.len
82    }
83
84    /// Returns `true` if this `CString` has a length of zero, and `false` otherwise.
85    pub fn is_empty(&self) -> bool {
86        self.len == 0
87    }
88
89    /// Extracts a string slice containing the entire `CString`.
90    pub fn as_str(&self) -> &str {
91        // SAFETY: The pointer points to a JSString content which is ref counted
92        let bytes = unsafe { slice::from_raw_parts(self.ptr.as_ptr() as *const u8, self.len) };
93        // SAFETY: The bytes are garanteed to be valid utf8 by QuickJS
94        unsafe { str::from_utf8_unchecked(bytes) }
95    }
96}
97
98impl<'js> Drop for CString<'js> {
99    fn drop(&mut self) {
100        unsafe { qjs::JS_FreeCString(self.ctx.as_ptr(), self.ptr.as_ptr()) };
101    }
102}
103impl<'js> AsRef<str> for CString<'js> {
104    fn as_ref(&self) -> &str {
105        self.as_str()
106    }
107}
108#[cfg(feature = "std")]
109impl<'js> AsRef<std::path::Path> for CString<'js> {
110    fn as_ref(&self) -> &std::path::Path {
111        std::path::Path::new(self.as_str())
112    }
113}
114impl<'js> core::ops::Deref for CString<'js> {
115    type Target = str;
116
117    fn deref(&self) -> &Self::Target {
118        self.as_str()
119    }
120}
121#[cfg(feature = "std")]
122impl<'js> AsRef<std::ffi::OsStr> for CString<'js> {
123    fn as_ref(&self) -> &std::ffi::OsStr {
124        self.as_str().as_ref()
125    }
126}
127impl<'js> AsRef<[u8]> for CString<'js> {
128    fn as_ref(&self) -> &[u8] {
129        self.as_str().as_ref()
130    }
131}
132impl<'js> PartialEq for CString<'js> {
133    fn eq(&self, other: &Self) -> bool {
134        PartialEq::eq(self.as_str(), other.as_str())
135    }
136}
137impl<'js> Eq for CString<'js> {}
138impl<'js> core::hash::Hash for CString<'js> {
139    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
140        core::hash::Hash::hash(self.as_str(), state)
141    }
142}
143
144#[cfg(test)]
145mod test {
146    use crate::{prelude::*, *};
147    #[test]
148    fn from_javascript() {
149        test_with(|ctx| {
150            let s: String = ctx.eval(" 'foo bar baz' ").unwrap();
151            assert_eq!(s.to_string().unwrap(), "foo bar baz");
152        });
153    }
154
155    #[test]
156    fn to_javascript() {
157        test_with(|ctx| {
158            let string = String::from_str(ctx.clone(), "foo").unwrap();
159            let func: Function = ctx.eval("x =>  x + 'bar'").unwrap();
160            let text: StdString = (string,).apply(&func).unwrap();
161            assert_eq!(text, "foobar".to_string());
162        });
163    }
164
165    #[test]
166    fn from_javascript_c() {
167        test_with(|ctx| {
168            let s: CString = ctx.eval(" 'foo bar baz' ").unwrap();
169            assert_eq!(s.as_str(), "foo bar baz");
170        });
171    }
172
173    #[test]
174    fn to_javascript_c() {
175        test_with(|ctx| {
176            let string = String::from_str(ctx.clone(), "foo")
177                .unwrap()
178                .to_cstring()
179                .unwrap();
180            let func: Function = ctx.eval("x =>  x + 'bar'").unwrap();
181            let text: StdString = (string,).apply(&func).unwrap();
182            assert_eq!(text, "foobar".to_string());
183        });
184    }
185}