-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathffi.rs
More file actions
207 lines (180 loc) · 5.98 KB
/
Copy pathffi.rs
File metadata and controls
207 lines (180 loc) · 5.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//! C ABI consumed by the C++ application.
//!
//! Schema handles and returned strings are Rust allocations. Callers must release
//! them with `cpp_rs_free_schema` and `cpp_rs_free_string`, respectively.
use crate::clang_parser::parse_header_with_clang;
use crate::schema::Schema;
use serde_json::Value;
use std::cell::RefCell;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
thread_local! {
static LAST_ERROR: RefCell<String> = const { RefCell::new(String::new()) };
}
fn set_last_error(message: impl Into<String>) {
LAST_ERROR.with(|slot| *slot.borrow_mut() = message.into());
}
fn into_c_string(value: String) -> *mut c_char {
match CString::new(value) {
Ok(owned) => owned.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
fn format_json(value: &Value, indent: bool) -> String {
if indent {
serde_json::to_string_pretty(value)
} else {
serde_json::to_string(value)
}
.unwrap_or_else(|_| "{}".to_string())
}
/// Runs one wire conversion, reporting failures through `cpp_rs_last_error`.
fn convert(
schema: *mut Schema,
type_name: *const c_char,
json_value: *const c_char,
indent: bool,
to_named: bool,
) -> *mut c_char {
if schema.is_null() || type_name.is_null() || json_value.is_null() {
set_last_error("Null argument passed to conversion");
return std::ptr::null_mut();
}
let schema_ref = unsafe { &*schema };
let type_name = unsafe { CStr::from_ptr(type_name) }.to_string_lossy();
let payload = match unsafe { CStr::from_ptr(json_value) }.to_str() {
Ok(text) => text,
Err(_) => {
set_last_error("JSON payload is not valid UTF-8");
return std::ptr::null_mut();
}
};
let value: Value = match serde_json::from_str(payload) {
Ok(value) => value,
Err(error) => {
set_last_error(format!("Invalid JSON payload: {}", error));
return std::ptr::null_mut();
}
};
let result = if to_named {
schema_ref.to_named(type_name.as_ref(), &value)
} else {
schema_ref.to_positional(type_name.as_ref(), &value)
};
match result {
Ok(converted) => into_c_string(format_json(&converted, indent)),
Err(error) => {
set_last_error(error);
std::ptr::null_mut()
}
}
}
#[no_mangle]
/// Creates an opaque schema handle. `path` must point to a valid NUL-terminated string.
pub extern "C" fn cpp_rs_init(path: *const c_char) -> *mut Schema {
let c_str = unsafe { CStr::from_ptr(path) };
let path = c_str.to_string_lossy().to_string();
match parse_header_with_clang(&path) {
Ok(schema) => Box::into_raw(Box::new(schema)),
Err(error) => {
set_last_error(error);
std::ptr::null_mut()
}
}
}
#[no_mangle]
/// Returns the last error recorded on this thread; the caller owns the string.
pub extern "C" fn cpp_rs_last_error() -> *mut c_char {
LAST_ERROR.with(|slot| into_c_string(slot.borrow().clone()))
}
#[no_mangle]
/// Returns newly allocated schema JSON owned by the caller.
pub extern "C" fn cpp_rs_schema_json(schema: *mut Schema) -> *mut c_char {
if schema.is_null() {
return into_c_string("{}".to_string());
}
let schema_ref = unsafe { &*schema };
into_c_string(schema_ref.get_schema_json())
}
#[no_mangle]
/// Turns a positional payload from the C++ reflection layer into named JSON.
pub extern "C" fn cpp_rs_serialize(
schema: *mut Schema,
type_name: *const c_char,
json_value: *const c_char,
indent: bool,
) -> *mut c_char {
convert(schema, type_name, json_value, indent, true)
}
#[no_mangle]
/// Turns named JSON back into the positional payload the C++ layer materializes.
pub extern "C" fn cpp_rs_deserialize(
schema: *mut Schema,
type_name: *const c_char,
json_value: *const c_char,
indent: bool,
) -> *mut c_char {
convert(schema, type_name, json_value, indent, false)
}
#[no_mangle]
/// Validates named JSON against the schema and returns its canonical form.
pub extern "C" fn cpp_rs_normalize(
schema: *mut Schema,
type_name: *const c_char,
json_value: *const c_char,
indent: bool,
) -> *mut c_char {
if schema.is_null() || type_name.is_null() || json_value.is_null() {
set_last_error("Null argument passed to normalization");
return std::ptr::null_mut();
}
let schema_ref = unsafe { &*schema };
let type_name = unsafe { CStr::from_ptr(type_name) }.to_string_lossy();
let payload = unsafe { CStr::from_ptr(json_value) }.to_string_lossy();
let value: Value = match serde_json::from_str(payload.as_ref()) {
Ok(value) => value,
Err(error) => {
set_last_error(format!("Invalid JSON payload: {}", error));
return std::ptr::null_mut();
}
};
match schema_ref.serialize_for_type(type_name.as_ref(), &value) {
Ok(converted) => into_c_string(format_json(&converted, indent)),
Err(error) => {
set_last_error(error);
std::ptr::null_mut()
}
}
}
#[no_mangle]
/// Releases a handle returned by `cpp_rs_init`; null is accepted.
pub extern "C" fn cpp_rs_free_schema(schema: *mut Schema) {
if !schema.is_null() {
unsafe {
drop(Box::from_raw(schema));
}
}
}
#[no_mangle]
/// Releases a string returned by this library; null is accepted.
pub extern "C" fn cpp_rs_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
let _ = CString::from_raw(ptr);
}
}
}
#[cfg(test)]
mod tests {
use super::format_json;
use serde_json::json;
#[test]
fn formats_json_as_compact_or_indented() {
let value = json!({"name": "TechCorp", "employees": []});
let compact = format_json(&value, false);
let indented = format_json(&value, true);
assert_eq!(compact, r#"{"employees":[],"name":"TechCorp"}"#);
assert!(indented.contains('\n'));
assert!(indented.contains(" \"employees\""));
}
}