Skip to main content

libobs_wrapper\utils/
obs_string.rs

1//! String handling utilities for OBS API integration
2//!
3//! This module provides safe string handling between Rust and the OBS C API.
4//! The core type `ObsString` wraps C-compatible strings in a memory-safe way,
5//! ensuring proper lifetime management and UTF-8 validation.
6
7use std::ffi::{CStr, CString};
8use std::fmt;
9use std::os::raw::c_char;
10
11use crate::unsafe_send::Sendable;
12
13fn cstring_without_nul(mut bytes: Vec<u8>) -> CString {
14    bytes.retain(|byte| *byte != 0);
15    // SAFETY: all interior NUL bytes were removed immediately above.
16    unsafe { CString::from_vec_unchecked(bytes) }
17}
18
19/// String wrapper for OBS function calls.
20///
21/// `ObsString` provides safe interaction with OBS C API functions that require
22/// C-style strings. It wraps `CString` internally with convenient helper functions
23/// for converting between Rust strings and C-compatible strings.
24///
25/// # Safety
26///
27/// - Any NUL byte in input strings is stripped during conversion to prevent panicking
28/// - Memory is properly managed to prevent use-after-free and memory leaks
29/// - Automatically handles conversion between Rust's UTF-8 strings and C's NUL-terminated strings
30///
31/// # Examples
32///
33/// ```
34/// use libobs_wrapper::utils::ObsString;
35///
36/// // Create an ObsString from a Rust string
37/// let obs_string = ObsString::new("Hello, OBS!");
38///
39/// assert_eq!(obs_string.as_c_str().to_bytes(), b"Hello, OBS!");
40/// ```
41#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
42pub struct ObsString {
43    /// The underlying C string representation
44    c_string: CString,
45}
46
47impl ObsString {
48    /// Creates a new `ObsString` from a string slice.
49    ///
50    /// Any NUL bytes in the input are automatically stripped to prevent
51    /// panicking when converting to a C string.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// use libobs_wrapper::utils::ObsString;
57    ///
58    /// let obs_string = ObsString::new("source_name");
59    /// ```
60    pub fn new<S: AsRef<str>>(s: S) -> Self {
61        Self {
62            c_string: cstring_without_nul(s.as_ref().as_bytes().to_vec()),
63        }
64    }
65
66    /// Borrows this value as a standard C string.
67    pub fn as_c_str(&self) -> &CStr {
68        self.c_string.as_c_str()
69    }
70
71    pub(crate) fn as_ptr(&self) -> Sendable<*const c_char> {
72        Sendable(self.c_string.as_ptr())
73    }
74}
75impl fmt::Display for ObsString {
76    /// Converts the `ObsString` back to a Rust `String` for display.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use libobs_wrapper::utils::ObsString;
82    ///
83    /// let obs_string = ObsString::new("Hello");
84    /// assert_eq!(format!("{}", obs_string), "Hello");
85    /// ```
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "{}", self.c_string.to_string_lossy())
88    }
89}
90
91impl From<&str> for ObsString {
92    /// Creates an `ObsString` from a string slice.
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use libobs_wrapper::utils::ObsString;
98    ///
99    /// let obs_string: ObsString = "Hello".into();
100    /// ```
101    fn from(value: &str) -> Self {
102        Self {
103            c_string: cstring_without_nul(value.as_bytes().to_vec()),
104        }
105    }
106}
107
108impl From<Vec<u8>> for ObsString {
109    /// Creates an `ObsString` from a vector of bytes.
110    ///
111    /// Any NUL bytes in the input are automatically filtered out.
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use libobs_wrapper::utils::ObsString;
117    ///
118    /// let bytes = b"Hello".to_vec();
119    /// let obs_string: ObsString = bytes.into();
120    /// ```
121    fn from(value: Vec<u8>) -> Self {
122        Self {
123            c_string: cstring_without_nul(value),
124        }
125    }
126}
127
128impl From<String> for ObsString {
129    /// Creates an `ObsString` from a `String`.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use libobs_wrapper::utils::ObsString;
135    ///
136    /// let s = String::from("Hello");
137    /// let obs_string: ObsString = s.into();
138    /// ```
139    fn from(value: String) -> Self {
140        Self {
141            c_string: cstring_without_nul(value.as_bytes().to_vec()),
142        }
143    }
144}
145
146impl PartialEq<&str> for ObsString {
147    /// Compares an `ObsString` with a string slice for equality.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use libobs_wrapper::utils::ObsString;
153    ///
154    /// let obs_string = ObsString::new("Hello");
155    /// assert_eq!(obs_string, "Hello");
156    /// ```
157    fn eq(&self, other: &&str) -> bool {
158        self.c_string.to_str() == Ok(*other)
159    }
160}