Skip to main content

speechmarkdown_rust/
ffi.rs

1use std::cell::RefCell;
2use std::ffi::{CStr, CString};
3use std::os::raw::c_char;
4
5use crate::formatters::base::Platform;
6use crate::parser::SpeechMarkdownParser;
7
8thread_local! {
9    static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
10}
11
12fn set_last_error(msg: &str) {
13    LAST_ERROR.with(|e| {
14        *e.borrow_mut() = Some(
15            CString::new(msg)
16                .unwrap_or_else(|_| CString::new("error message contained null byte").unwrap()),
17        );
18    });
19}
20
21fn parse_platform(platform: *const c_char) -> Option<Platform> {
22    if platform.is_null() {
23        set_last_error("platform argument is null");
24        return None;
25    }
26    let platform_str = unsafe { CStr::from_ptr(platform) }.to_str().unwrap_or("");
27    match Platform::from_platform_str(platform_str) {
28        Some(p) => Some(p),
29        None => {
30            set_last_error(&format!("unsupported platform: '{}'", platform_str));
31            None
32        }
33    }
34}
35
36fn to_c_string(s: String) -> *mut c_char {
37    match CString::new(s) {
38        Ok(cs) => cs.into_raw(),
39        Err(_) => {
40            set_last_error("output contained null byte");
41            std::ptr::null_mut()
42        }
43    }
44}
45
46/// # Safety
47/// `input` and `platform` must be valid pointers to null-terminated C strings.
48#[no_mangle]
49pub unsafe extern "C" fn speechmarkdown_to_ssml(
50    input: *const c_char,
51    platform: *const c_char,
52) -> *mut c_char {
53    if input.is_null() {
54        set_last_error("input argument is null");
55        return std::ptr::null_mut();
56    }
57
58    let input_str = match CStr::from_ptr(input).to_str() {
59        Ok(s) => s,
60        Err(_) => {
61            set_last_error("input is not valid UTF-8");
62            return std::ptr::null_mut();
63        }
64    };
65
66    let platform_val = match parse_platform(platform) {
67        Some(p) => p,
68        None => return std::ptr::null_mut(),
69    };
70
71    match SpeechMarkdownParser::to_ssml(input_str, platform_val) {
72        Ok(ssml) => to_c_string(ssml),
73        Err(e) => {
74            set_last_error(&e.to_string());
75            std::ptr::null_mut()
76        }
77    }
78}
79
80/// # Safety
81/// `input` must be a valid pointer to a null-terminated C string.
82#[no_mangle]
83pub unsafe extern "C" fn speechmarkdown_to_text(input: *const c_char) -> *mut c_char {
84    if input.is_null() {
85        set_last_error("input argument is null");
86        return std::ptr::null_mut();
87    }
88
89    let input_str = match CStr::from_ptr(input).to_str() {
90        Ok(s) => s,
91        Err(_) => {
92            set_last_error("input is not valid UTF-8");
93            return std::ptr::null_mut();
94        }
95    };
96
97    match SpeechMarkdownParser::to_text(input_str) {
98        Ok(text) => to_c_string(text),
99        Err(e) => {
100            set_last_error(&e.to_string());
101            std::ptr::null_mut()
102        }
103    }
104}
105
106/// # Safety
107/// `s` must be a valid pointer to a `CString` previously returned by this library, or null.
108#[no_mangle]
109pub unsafe extern "C" fn speechmarkdown_free(s: *mut c_char) {
110    if !s.is_null() {
111        drop(CString::from_raw(s));
112    }
113}
114
115#[no_mangle]
116pub extern "C" fn speechmarkdown_get_error() -> *mut c_char {
117    LAST_ERROR.with(|e| match e.borrow().as_ref() {
118        Some(cs) => {
119            let copy = cs.clone();
120            copy.into_raw()
121        }
122        None => std::ptr::null_mut(),
123    })
124}
125
126/// # Safety
127/// `input` must be a valid pointer to a null-terminated C string.
128#[no_mangle]
129pub unsafe extern "C" fn speechmarkdown_parse(input: *const c_char) -> *mut c_char {
130    if input.is_null() {
131        set_last_error("input argument is null");
132        return std::ptr::null_mut();
133    }
134
135    let input_str = match CStr::from_ptr(input).to_str() {
136        Ok(s) => s,
137        Err(_) => {
138            set_last_error("input is not valid UTF-8");
139            return std::ptr::null_mut();
140        }
141    };
142
143    match SpeechMarkdownParser::parse(input_str) {
144        Ok(ast) => match serde_json::to_string(&ast) {
145            Ok(json) => to_c_string(json),
146            Err(e) => {
147                set_last_error(&format!("JSON serialization error: {}", e));
148                std::ptr::null_mut()
149            }
150        },
151        Err(e) => {
152            set_last_error(&e.to_string());
153            std::ptr::null_mut()
154        }
155    }
156}
157
158/// # Safety
159/// `input` must be a valid pointer to a null-terminated C string.
160#[no_mangle]
161pub unsafe extern "C" fn speechmarkdown_is_speech_markdown(input: *const c_char) -> bool {
162    if input.is_null() {
163        return false;
164    }
165
166    let input_str = match CStr::from_ptr(input).to_str() {
167        Ok(s) => s,
168        Err(_) => return false,
169    };
170
171    SpeechMarkdownParser::is_speech_markdown(input_str)
172}
173
174/// # Safety
175/// `input` must be a valid pointer to a null-terminated C string.
176#[no_mangle]
177pub unsafe extern "C" fn speechmarkdown_validate(input: *const c_char) -> bool {
178    if input.is_null() {
179        set_last_error("input argument is null");
180        return false;
181    }
182
183    let input_str = match CStr::from_ptr(input).to_str() {
184        Ok(s) => s,
185        Err(_) => {
186            set_last_error("input is not valid UTF-8");
187            return false;
188        }
189    };
190
191    match SpeechMarkdownParser::validate(input_str) {
192        Ok(()) => true,
193        Err(e) => {
194            set_last_error(&e.to_string());
195            false
196        }
197    }
198}
199
200/// # Safety
201/// `input` must be a valid pointer to a null-terminated C string.
202#[no_mangle]
203pub unsafe extern "C" fn speechmarkdown_to_smd(input: *const c_char) -> *mut c_char {
204    if input.is_null() {
205        set_last_error("input argument is null");
206        return std::ptr::null_mut();
207    }
208
209    let input_str = match CStr::from_ptr(input).to_str() {
210        Ok(s) => s,
211        Err(_) => {
212            set_last_error("input is not valid UTF-8");
213            return std::ptr::null_mut();
214        }
215    };
216
217    match SpeechMarkdownParser::to_smd(input_str) {
218        Ok(smd) => to_c_string(smd),
219        Err(e) => {
220            set_last_error(&e.to_string());
221            std::ptr::null_mut()
222        }
223    }
224}
225
226/// # Safety
227/// `platform` must be a valid pointer to a null-terminated C string.
228#[no_mangle]
229pub unsafe extern "C" fn speechmarkdown_supported_ssml(platform: *const c_char) -> *mut c_char {
230    if platform.is_null() {
231        set_last_error("platform argument is null");
232        return std::ptr::null_mut();
233    }
234
235    let platform_str = match CStr::from_ptr(platform).to_str() {
236        Ok(s) => s,
237        Err(_) => {
238            set_last_error("platform is not valid UTF-8");
239            return std::ptr::null_mut();
240        }
241    };
242
243    let platform_val = match Platform::from_platform_str(platform_str) {
244        Some(p) => p,
245        None => {
246            set_last_error(&format!("unsupported platform: '{}'", platform_str));
247            return std::ptr::null_mut();
248        }
249    };
250
251    let caps = SpeechMarkdownParser::supported_ssml(platform_val);
252    match serde_json::to_string(&caps) {
253        Ok(json) => to_c_string(json),
254        Err(e) => {
255            set_last_error(&format!("JSON serialization error: {}", e));
256            std::ptr::null_mut()
257        }
258    }
259}