This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

mlang / native / mlang_http / src / lib.rs
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}