pyo3/conversions/std/
cstring.rs

1#[cfg(feature = "experimental-inspect")]
2use crate::inspect::TypeHint;
3#[cfg(feature = "experimental-inspect")]
4use crate::type_object::PyTypeInfo;
5use crate::types::PyString;
6use crate::{Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, Python};
7use std::borrow::Cow;
8use std::ffi::{CStr, CString};
9use std::str::Utf8Error;
10#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
11use {
12    crate::{exceptions::PyValueError, ffi},
13    std::slice,
14};
15
16impl<'py> IntoPyObject<'py> for &CStr {
17    type Target = PyString;
18    type Output = Bound<'py, Self::Target>;
19    type Error = Utf8Error;
20
21    #[cfg(feature = "experimental-inspect")]
22    const OUTPUT_TYPE: TypeHint = <&str>::OUTPUT_TYPE;
23
24    #[inline]
25    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
26        self.to_str()?.into_pyobject(py).map_err(|err| match err {})
27    }
28}
29
30impl<'py> IntoPyObject<'py> for CString {
31    type Target = PyString;
32    type Output = Bound<'py, Self::Target>;
33    type Error = Utf8Error;
34
35    #[cfg(feature = "experimental-inspect")]
36    const OUTPUT_TYPE: TypeHint = <&CStr>::OUTPUT_TYPE;
37
38    #[inline]
39    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
40        (&*self).into_pyobject(py)
41    }
42}
43
44impl<'py> IntoPyObject<'py> for &CString {
45    type Target = PyString;
46    type Output = Bound<'py, Self::Target>;
47    type Error = Utf8Error;
48
49    #[cfg(feature = "experimental-inspect")]
50    const OUTPUT_TYPE: TypeHint = <&CStr>::OUTPUT_TYPE;
51
52    #[inline]
53    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
54        (&**self).into_pyobject(py)
55    }
56}
57
58impl<'py> IntoPyObject<'py> for Cow<'_, CStr> {
59    type Target = PyString;
60    type Output = Bound<'py, Self::Target>;
61    type Error = Utf8Error;
62
63    #[cfg(feature = "experimental-inspect")]
64    const OUTPUT_TYPE: TypeHint = <&CStr>::OUTPUT_TYPE;
65
66    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
67        (*self).into_pyobject(py)
68    }
69}
70
71impl<'py> IntoPyObject<'py> for &Cow<'_, CStr> {
72    type Target = PyString;
73    type Output = Bound<'py, Self::Target>;
74    type Error = Utf8Error;
75
76    #[cfg(feature = "experimental-inspect")]
77    const OUTPUT_TYPE: TypeHint = <&CStr>::OUTPUT_TYPE;
78
79    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
80        (&**self).into_pyobject(py)
81    }
82}
83
84#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
85impl<'a> FromPyObject<'a, '_> for &'a CStr {
86    type Error = PyErr;
87
88    #[cfg(feature = "experimental-inspect")]
89    const INPUT_TYPE: TypeHint = PyString::TYPE_HINT;
90
91    fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
92        let obj = obj.cast::<PyString>()?;
93        let mut size = 0;
94        // SAFETY: obj is a PyString so we can safely call PyUnicode_AsUTF8AndSize
95        let ptr = unsafe { ffi::PyUnicode_AsUTF8AndSize(obj.as_ptr(), &mut size) };
96
97        if ptr.is_null() {
98            return Err(PyErr::fetch(obj.py()));
99        }
100
101        // SAFETY: PyUnicode_AsUTF8AndSize always returns a NUL-terminated string but size does not
102        // include the NUL terminator. So we add 1 to the size to include it.
103        let slice = unsafe { slice::from_raw_parts(ptr.cast(), size as usize + 1) };
104
105        CStr::from_bytes_with_nul(slice).map_err(|err| PyValueError::new_err(err.to_string()))
106    }
107}
108
109impl<'a> FromPyObject<'a, '_> for Cow<'a, CStr> {
110    type Error = PyErr;
111
112    #[cfg(feature = "experimental-inspect")]
113    const INPUT_TYPE: TypeHint = PyString::TYPE_HINT;
114
115    fn extract(obj: Borrowed<'a, '_, PyAny>) -> Result<Self, Self::Error> {
116        #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
117        {
118            Ok(Cow::Borrowed(obj.extract::<&CStr>()?))
119        }
120
121        #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
122        {
123            Ok(Cow::Owned(obj.extract::<CString>()?))
124        }
125    }
126}
127impl FromPyObject<'_, '_> for CString {
128    type Error = PyErr;
129
130    #[cfg(feature = "experimental-inspect")]
131    const INPUT_TYPE: TypeHint = PyString::TYPE_HINT;
132
133    fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<Self, Self::Error> {
134        #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
135        {
136            Ok(obj.extract::<&CStr>()?.to_owned())
137        }
138
139        #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
140        {
141            CString::new(&*obj.cast::<PyString>()?.to_cow()?).map_err(Into::into)
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::types::string::PyStringMethods;
150    use crate::types::PyAnyMethods;
151    use crate::Python;
152
153    #[test]
154    fn test_into_pyobject() {
155        Python::attach(|py| {
156            let s = "Hello, Python!";
157            let cstr = CString::new(s).unwrap();
158
159            let py_string = cstr.as_c_str().into_pyobject(py).unwrap();
160            assert_eq!(py_string.to_cow().unwrap(), s);
161
162            let py_string = cstr.into_pyobject(py).unwrap();
163            assert_eq!(py_string.to_cow().unwrap(), s);
164        })
165    }
166
167    #[test]
168    fn test_extract_with_nul_error() {
169        Python::attach(|py| {
170            let s = "Hello\0Python";
171            let py_string = s.into_pyobject(py).unwrap();
172
173            #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
174            {
175                let err = py_string.extract::<&CStr>();
176                assert!(err.is_err());
177            }
178
179            let err = py_string.extract::<CString>();
180            assert!(err.is_err());
181        })
182    }
183
184    #[test]
185    fn test_extract_cstr_and_cstring() {
186        Python::attach(|py| {
187            let s = "Hello, world!";
188            let cstr = CString::new(s).unwrap();
189            let py_string = cstr.as_c_str().into_pyobject(py).unwrap();
190
191            #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
192            {
193                let extracted_cstr: &CStr = py_string.extract().unwrap();
194                assert_eq!(extracted_cstr.to_str().unwrap(), s);
195            }
196
197            let extracted_cstring: CString = py_string.extract().unwrap();
198            assert_eq!(extracted_cstring.to_str().unwrap(), s);
199        })
200    }
201
202    #[test]
203    fn test_cow_roundtrip() {
204        Python::attach(|py| {
205            let s = "Hello, world!";
206            let cstr = CString::new(s).unwrap();
207            let cow: Cow<'_, CStr> = Cow::Borrowed(cstr.as_c_str());
208
209            let py_string = cow.into_pyobject(py).unwrap();
210            assert_eq!(py_string.to_cow().unwrap(), s);
211
212            let roundtripped: Cow<'_, CStr> = py_string.extract().unwrap();
213            assert_eq!(roundtripped.as_ref(), cstr.as_c_str());
214        })
215    }
216}