This repository has no description
9.5 kB
318 lines
1use atproto_client::{HttpRecordResolver, RecordResolver};
2use atproto_identity::resolve::{HickoryDnsResolver, InnerIdentityResolver, SharedIdentityResolver};
3use atproto_record::aturi::ATURI;
4use nickel_lang_core::deserialize;
5use std::ffi::{CStr, CString};
6use std::io::Read;
7use std::os::raw::c_char;
8use std::ptr;
9use std::str::FromStr;
10use std::sync::Arc;
11
12fn render_json_value(value: &serde_json::Value) -> Result<String, String> {
13 match value {
14 serde_json::Value::Null => Ok("null".to_owned()),
15 serde_json::Value::Bool(b) => Ok(if *b { "true" } else { "false" }.to_owned()),
16 serde_json::Value::Number(n) => {
17 if let Some(i) = n.as_i64() {
18 Ok(i.to_string())
19 } else if let Some(u) = n.as_u64() {
20 Ok(u.to_string())
21 } else {
22 Ok(n.to_string())
23 }
24 }
25 serde_json::Value::String(s) => serde_json::to_string(s)
26 .map_err(|err| format!("failed to render Nickel string: {err}")),
27 serde_json::Value::Array(items) => {
28 let rendered = items
29 .iter()
30 .map(render_json_value)
31 .collect::<Result<Vec<_>, _>>()?;
32 Ok(format!("[{}]", rendered.join(", ")))
33 }
34 serde_json::Value::Object(fields) => {
35 let rendered = fields
36 .iter()
37 .map(|(key, value)| {
38 let key = if key
39 .chars()
40 .next()
41 .map(|c| c.is_ascii_alphabetic() || c == '_')
42 .unwrap_or(false)
43 && key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
44 {
45 key.clone()
46 } else {
47 serde_json::to_string(key)
48 .map_err(|err| format!("failed to render Nickel key: {err}"))?
49 };
50 Ok(format!("{key} = {}", render_json_value(value)?))
51 })
52 .collect::<Result<Vec<_>, String>>()?;
53 Ok(format!("{{ {} }}", rendered.join(", ")))
54 }
55 }
56}
57
58fn nickel_to_data_string(source: &str) -> Result<String, String> {
59 let value: serde_json::Value =
60 deserialize::from_str(source).map_err(|err| format!("{err}"))?;
61 render_json_value(&value)
62}
63
64fn nickel_file_to_data_string(path: &str) -> Result<String, String> {
65 let value: serde_json::Value =
66 deserialize::from_path(path).map_err(|err| format!("{err}"))?;
67 render_json_value(&value)
68}
69
70fn json_to_data_string(source: &str) -> Result<String, String> {
71 let value: serde_json::Value =
72 serde_json::from_str(source).map_err(|err| format!("{err}"))?;
73 render_json_value(&value)
74}
75
76fn aturi_to_data_string(source: &str) -> Result<String, String> {
77 ATURI::from_str(source).map_err(|err| format!("{err}"))?;
78 let runtime = tokio::runtime::Runtime::new()
79 .map_err(|err| format!("failed to create Tokio runtime: {err}"))?;
80 let value: serde_json::Value = runtime
81 .block_on(async {
82 let http_client = reqwest::Client::new();
83 let dns_resolver = Arc::new(HickoryDnsResolver::create_resolver(&[]));
84 let identity_resolver = SharedIdentityResolver(Arc::new(InnerIdentityResolver {
85 dns_resolver,
86 http_client: http_client.clone(),
87 plc_hostname: "plc.directory".to_owned(),
88 }));
89 let resolver = HttpRecordResolver::new(http_client, Arc::new(identity_resolver));
90 resolver.resolve(source).await
91 })
92 .map_err(|err| format!("AT-URI lookup failed: {err}"))?;
93 let body =
94 serde_json::to_string(&value).map_err(|err| format!("failed to encode record: {err}"))?;
95 json_to_data_string(&body)
96}
97
98fn alloc_c_string(s: String) -> *mut c_char {
99 CString::new(s)
100 .unwrap_or_else(|_| CString::new("string contained interior NUL").unwrap())
101 .into_raw()
102}
103
104#[no_mangle]
105pub extern "C" fn mlang_http_get_body(
106 url: *const c_char,
107 out_body: *mut *mut c_char,
108 out_error: *mut *mut c_char,
109) -> i32 {
110 if url.is_null() || out_body.is_null() || out_error.is_null() {
111 return 0;
112 }
113
114 unsafe {
115 *out_body = ptr::null_mut();
116 *out_error = ptr::null_mut();
117 }
118
119 let url = unsafe { CStr::from_ptr(url) };
120 let url = match url.to_str() {
121 Ok(url) => url,
122 Err(err) => {
123 unsafe {
124 *out_error = alloc_c_string(format!("invalid URL UTF-8: {err}"));
125 }
126 return 0;
127 }
128 };
129
130 let response = match ureq::get(url).call() {
131 Ok(response) => response,
132 Err(err) => {
133 unsafe {
134 *out_error = alloc_c_string(format!("http request failed: {err}"));
135 }
136 return 0;
137 }
138 };
139
140 let mut reader = response.into_reader();
141 let mut body = String::new();
142 if let Err(err) = reader.read_to_string(&mut body) {
143 unsafe {
144 *out_error = alloc_c_string(format!("failed to read HTTP response body: {err}"));
145 }
146 return 0;
147 }
148 unsafe {
149 *out_body = alloc_c_string(body);
150 }
151 1
152}
153
154#[no_mangle]
155pub extern "C" fn mlang_http_string_free(ptr: *mut c_char) {
156 if ptr.is_null() {
157 return;
158 }
159 unsafe {
160 drop(CString::from_raw(ptr));
161 }
162}
163
164#[no_mangle]
165pub extern "C" fn mlang_nickel_eval(
166 source: *const c_char,
167 out_body: *mut *mut c_char,
168 out_error: *mut *mut c_char,
169) -> i32 {
170 if source.is_null() || out_body.is_null() || out_error.is_null() {
171 return 0;
172 }
173 unsafe {
174 *out_body = ptr::null_mut();
175 *out_error = ptr::null_mut();
176 }
177 let source = unsafe { CStr::from_ptr(source) };
178 let source = match source.to_str() {
179 Ok(source) => source,
180 Err(err) => {
181 unsafe {
182 *out_error = alloc_c_string(format!("invalid Nickel source UTF-8: {err}"));
183 }
184 return 0;
185 }
186 };
187 match nickel_to_data_string(source) {
188 Ok(rendered) => {
189 unsafe {
190 *out_body = alloc_c_string(rendered);
191 }
192 1
193 }
194 Err(err) => {
195 unsafe {
196 *out_error = alloc_c_string(format!("Nickel parse error: {err}"));
197 }
198 0
199 }
200 }
201}
202
203#[no_mangle]
204pub extern "C" fn mlang_nickel_eval_file(
205 path: *const c_char,
206 out_body: *mut *mut c_char,
207 out_error: *mut *mut c_char,
208) -> i32 {
209 if path.is_null() || out_body.is_null() || out_error.is_null() {
210 return 0;
211 }
212 unsafe {
213 *out_body = ptr::null_mut();
214 *out_error = ptr::null_mut();
215 }
216 let path = unsafe { CStr::from_ptr(path) };
217 let path = match path.to_str() {
218 Ok(path) => path,
219 Err(err) => {
220 unsafe {
221 *out_error = alloc_c_string(format!("invalid Nickel file path UTF-8: {err}"));
222 }
223 return 0;
224 }
225 };
226 match nickel_file_to_data_string(path) {
227 Ok(rendered) => {
228 unsafe {
229 *out_body = alloc_c_string(rendered);
230 }
231 1
232 }
233 Err(err) => {
234 unsafe {
235 *out_error = alloc_c_string(format!("Nickel parse error: {err}"));
236 }
237 0
238 }
239 }
240}
241
242#[no_mangle]
243pub extern "C" fn mlang_json_eval(
244 source: *const c_char,
245 out_body: *mut *mut c_char,
246 out_error: *mut *mut c_char,
247) -> i32 {
248 if source.is_null() || out_body.is_null() || out_error.is_null() {
249 return 0;
250 }
251 unsafe {
252 *out_body = ptr::null_mut();
253 *out_error = ptr::null_mut();
254 }
255 let source = unsafe { CStr::from_ptr(source) };
256 let source = match source.to_str() {
257 Ok(source) => source,
258 Err(err) => {
259 unsafe {
260 *out_error = alloc_c_string(format!("invalid JSON source UTF-8: {err}"));
261 }
262 return 0;
263 }
264 };
265 match json_to_data_string(source) {
266 Ok(rendered) => {
267 unsafe {
268 *out_body = alloc_c_string(rendered);
269 }
270 1
271 }
272 Err(err) => {
273 unsafe {
274 *out_error = alloc_c_string(format!("JSON parse error: {err}"));
275 }
276 0
277 }
278 }
279}
280
281#[no_mangle]
282pub extern "C" fn mlang_aturi_parse(
283 source: *const c_char,
284 out_body: *mut *mut c_char,
285 out_error: *mut *mut c_char,
286) -> i32 {
287 if source.is_null() || out_body.is_null() || out_error.is_null() {
288 return 0;
289 }
290 unsafe {
291 *out_body = ptr::null_mut();
292 *out_error = ptr::null_mut();
293 }
294 let source = unsafe { CStr::from_ptr(source) };
295 let source = match source.to_str() {
296 Ok(source) => source,
297 Err(err) => {
298 unsafe {
299 *out_error = alloc_c_string(format!("invalid AT-URI UTF-8: {err}"));
300 }
301 return 0;
302 }
303 };
304 match aturi_to_data_string(source) {
305 Ok(rendered) => {
306 unsafe {
307 *out_body = alloc_c_string(rendered);
308 }
309 1
310 }
311 Err(err) => {
312 unsafe {
313 *out_error = alloc_c_string(format!("AT-URI parse error: {err}"));
314 }
315 0
316 }
317 }
318}