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}