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