1use std::alloc::{alloc, dealloc, Layout};
21
22use packdiff_dto::review::{Comment, ReviewDocument, Verdict};
23use packdiff_dto::{export, storage_key, RefInfo};
24use serde_json::json;
25
26#[no_mangle]
30pub extern "C" fn pd_alloc(len: u32) -> *mut u8 {
31 if len == 0 {
32 return core::ptr::null_mut();
33 }
34 unsafe { alloc(Layout::from_size_align_unchecked(len as usize, 1)) }
35}
36
37#[no_mangle]
41#[allow(clippy::not_unsafe_ptr_arg_deref)]
42pub extern "C" fn pd_free(ptr: *mut u8, len: u32) {
43 if ptr.is_null() || len == 0 {
44 return;
45 }
46 unsafe { dealloc(ptr, Layout::from_size_align_unchecked(len as usize, 1)) }
47}
48
49fn read_arg(ptr: *const u8, len: u32) -> String {
50 if ptr.is_null() || len == 0 {
51 return String::new();
52 }
53 let bytes = unsafe { core::slice::from_raw_parts(ptr, len as usize) };
54 String::from_utf8_lossy(bytes).into_owned()
55}
56
57fn pack(s: String) -> u64 {
58 let boxed = s.into_bytes().into_boxed_slice();
59 let len = boxed.len() as u64;
60 let ptr = Box::leak(boxed).as_mut_ptr() as u32 as u64;
61 (ptr << 32) | len
62}
63
64fn ok(value: serde_json::Value) -> u64 {
65 pack(json!({ "Ok": value }).to_string())
66}
67
68fn err(message: impl std::fmt::Display) -> u64 {
69 pack(json!({ "Error": { "message": message.to_string() } }).to_string())
70}
71
72fn doc_value(doc: &ReviewDocument) -> serde_json::Value {
73 serde_json::to_value(doc).expect("document serializes")
74}
75
76#[no_mangle]
81pub extern "C" fn pd_new_document(meta_ptr: *const u8, meta_len: u32) -> u64 {
82 #[derive(serde::Deserialize)]
83 struct Meta {
84 repo: String,
85 base: RefInfo,
86 head: RefInfo,
87 }
88 match serde_json::from_str::<Meta>(&read_arg(meta_ptr, meta_len)) {
89 Ok(m) => ok(doc_value(&ReviewDocument::new(m.repo, m.base, m.head))),
90 Err(e) => err(format!("invalid meta: {e}")),
91 }
92}
93
94#[no_mangle]
96pub extern "C" fn pd_parse_document(doc_ptr: *const u8, doc_len: u32) -> u64 {
97 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
98 Ok(doc) => ok(doc_value(&doc)),
99 Err(e) => err(e),
100 }
101}
102
103#[no_mangle]
105pub extern "C" fn pd_upsert_comment(doc_ptr: *const u8, doc_len: u32, comment_ptr: *const u8, comment_len: u32) -> u64 {
106 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
107 Ok(d) => d,
108 Err(e) => return err(e),
109 };
110 let comment: Comment = match serde_json::from_str(&read_arg(comment_ptr, comment_len)) {
111 Ok(c) => c,
112 Err(e) => return err(format!("invalid comment: {e}")),
113 };
114 match doc.upsert(comment) {
115 Ok(()) => ok(doc_value(&doc)),
116 Err(e) => err(e),
117 }
118}
119
120#[no_mangle]
123pub extern "C" fn pd_delete_comment(doc_ptr: *const u8, doc_len: u32, id_ptr: *const u8, id_len: u32) -> u64 {
124 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
125 Ok(d) => d,
126 Err(e) => return err(e),
127 };
128 doc.delete(&read_arg(id_ptr, id_len));
129 ok(doc_value(&doc))
130}
131
132#[no_mangle]
137pub extern "C" fn pd_set_verdict(doc_ptr: *const u8, doc_len: u32, verdict_ptr: *const u8, verdict_len: u32) -> u64 {
138 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
139 Ok(d) => d,
140 Err(e) => return err(e),
141 };
142 let verdict: Option<Verdict> = match serde_json::from_str(&read_arg(verdict_ptr, verdict_len)) {
143 Ok(v) => v,
144 Err(e) => return err(format!("invalid verdict: {e}")),
145 };
146 match doc.set_verdict(verdict) {
147 Ok(()) => ok(doc_value(&doc)),
148 Err(e) => err(e),
149 }
150}
151
152#[no_mangle]
155pub extern "C" fn pd_merge(doc_ptr: *const u8, doc_len: u32, incoming_ptr: *const u8, incoming_len: u32) -> u64 {
156 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
157 Ok(d) => d,
158 Err(e) => return err(e),
159 };
160 let incoming = match ReviewDocument::parse(&read_arg(incoming_ptr, incoming_len)) {
161 Ok(d) => d,
162 Err(e) => return err(format!("import rejected: {e}")),
163 };
164 doc.merge(&incoming);
165 ok(doc_value(&doc))
166}
167
168#[no_mangle]
169pub extern "C" fn pd_export_json(doc_ptr: *const u8, doc_len: u32) -> u64 {
170 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
171 Ok(doc) => ok(serde_json::Value::String(export::to_json(&doc))),
172 Err(e) => err(e),
173 }
174}
175
176#[no_mangle]
177pub extern "C" fn pd_export_markdown(doc_ptr: *const u8, doc_len: u32) -> u64 {
178 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
179 Ok(doc) => ok(serde_json::Value::String(export::to_markdown(&doc))),
180 Err(e) => err(e),
181 }
182}
183
184#[no_mangle]
185pub extern "C" fn pd_export_csv(doc_ptr: *const u8, doc_len: u32) -> u64 {
186 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
187 Ok(doc) => ok(serde_json::Value::String(export::to_csv(&doc))),
188 Err(e) => err(e),
189 }
190}
191
192#[no_mangle]
198pub extern "C" fn pd_range_diff(snap_ptr: *const u8, snap_len: u32, params_ptr: *const u8, params_len: u32) -> u64 {
199 #[derive(serde::Deserialize)]
200 #[serde(deny_unknown_fields)]
201 struct Params {
202 from: usize,
203 to: usize,
204 context: usize,
205 }
206 let snap: packdiff_dto::snapshot::RangeSnapshots = match serde_json::from_str(&read_arg(snap_ptr, snap_len)) {
207 Ok(s) => s,
208 Err(e) => return err(format!("invalid snapshots: {e}")),
209 };
210 let params: Params = match serde_json::from_str(&read_arg(params_ptr, params_len)) {
211 Ok(p) => p,
212 Err(e) => return err(format!("invalid params: {e}")),
213 };
214 match packdiff_dto::snapshot::range_diff(&snap, params.from, params.to, params.context) {
215 Ok(files) => ok(serde_json::to_value(files).expect("FileDiff serializes: no non-string keys")),
216 Err(e) => err(e),
217 }
218}
219
220#[no_mangle]
227pub extern "C" fn pd_context_slice(snap_ptr: *const u8, snap_len: u32, params_ptr: *const u8, params_len: u32) -> u64 {
228 #[derive(serde::Deserialize)]
229 #[serde(deny_unknown_fields)]
230 struct Params {
231 old_path: String,
232 new_path: String,
233 old_start: u32,
234 new_start: u32,
235 count: u32,
236 }
237 let snap: packdiff_dto::snapshot::RangeSnapshots = match serde_json::from_str(&read_arg(snap_ptr, snap_len)) {
238 Ok(s) => s,
239 Err(e) => return err(format!("invalid snapshots: {e}")),
240 };
241 let params: Params = match serde_json::from_str(&read_arg(params_ptr, params_len)) {
242 Ok(p) => p,
243 Err(e) => return err(format!("invalid params: {e}")),
244 };
245 match packdiff_dto::snapshot::context_slice(
246 &snap,
247 ¶ms.old_path,
248 ¶ms.new_path,
249 params.old_start,
250 params.new_start,
251 params.count,
252 ) {
253 Ok(lines) => ok(serde_json::to_value(lines).expect("Line serializes: no non-string keys")),
254 Err(e) => err(e),
255 }
256}
257
258#[no_mangle]
262pub extern "C" fn pd_markdown_html(text_ptr: *const u8, text_len: u32) -> u64 {
263 ok(serde_json::Value::String(packdiff_dto::markdown::to_html(&read_arg(text_ptr, text_len))))
264}
265
266#[no_mangle]
269pub extern "C" fn pd_highlight_lines(path_ptr: *const u8, path_len: u32, lines_ptr: *const u8, lines_len: u32) -> u64 {
270 let path = read_arg(path_ptr, path_len);
271 let lines: Vec<String> = match serde_json::from_str(&read_arg(lines_ptr, lines_len)) {
272 Ok(lines) => lines,
273 Err(e) => return err(format!("invalid lines: {e}")),
274 };
275 let refs: Vec<&str> = lines.iter().map(String::as_str).collect();
276 ok(serde_json::to_value(packdiff_dto::highlight::highlight_lines(&path, &refs)).expect("highlighted lines serialize"))
277}
278
279#[no_mangle]
283pub extern "C" fn pd_highlight_hunk(path_ptr: *const u8, path_len: u32, lines_ptr: *const u8, lines_len: u32) -> u64 {
284 let path = read_arg(path_ptr, path_len);
285 let lines: Vec<packdiff_dto::diff::Line> = match serde_json::from_str(&read_arg(lines_ptr, lines_len)) {
286 Ok(lines) => lines,
287 Err(e) => return err(format!("invalid hunk lines: {e}")),
288 };
289 ok(serde_json::to_value(packdiff_dto::highlight::highlight_hunk(&path, &lines)).expect("highlighted hunk serializes"))
290}
291
292#[no_mangle]
296pub extern "C" fn pd_storage_key(meta_ptr: *const u8, meta_len: u32) -> u64 {
297 #[derive(serde::Deserialize)]
298 struct Meta {
299 repo: String,
300 base: RefInfo,
301 head: RefInfo,
302 }
303 match serde_json::from_str::<Meta>(&read_arg(meta_ptr, meta_len)) {
304 Ok(m) => ok(serde_json::Value::String(storage_key(&m.repo, &m.base.sha, &m.head.sha))),
305 Err(e) => err(format!("invalid meta: {e}")),
306 }
307}