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]
39pub extern "C" fn pd_free(ptr: *mut u8, len: u32) {
40 if ptr.is_null() || len == 0 {
41 return;
42 }
43 unsafe { dealloc(ptr, Layout::from_size_align_unchecked(len as usize, 1)) }
44}
45
46fn read_arg(ptr: *const u8, len: u32) -> String {
47 if ptr.is_null() || len == 0 {
48 return String::new();
49 }
50 let bytes = unsafe { core::slice::from_raw_parts(ptr, len as usize) };
51 String::from_utf8_lossy(bytes).into_owned()
52}
53
54fn pack(s: String) -> u64 {
55 let boxed = s.into_bytes().into_boxed_slice();
56 let len = boxed.len() as u64;
57 let ptr = Box::leak(boxed).as_mut_ptr() as u32 as u64;
58 (ptr << 32) | len
59}
60
61fn ok(value: serde_json::Value) -> u64 {
62 pack(json!({ "Ok": value }).to_string())
63}
64
65fn err(message: impl std::fmt::Display) -> u64 {
66 pack(json!({ "Error": { "message": message.to_string() } }).to_string())
67}
68
69fn doc_value(doc: &ReviewDocument) -> serde_json::Value {
70 serde_json::to_value(doc).expect("document serializes")
71}
72
73#[no_mangle]
78pub extern "C" fn pd_new_document(meta_ptr: *const u8, meta_len: u32) -> u64 {
79 #[derive(serde::Deserialize)]
80 struct Meta {
81 repo: String,
82 base: RefInfo,
83 head: RefInfo,
84 }
85 match serde_json::from_str::<Meta>(&read_arg(meta_ptr, meta_len)) {
86 Ok(m) => ok(doc_value(&ReviewDocument::new(m.repo, m.base, m.head))),
87 Err(e) => err(format!("invalid meta: {e}")),
88 }
89}
90
91#[no_mangle]
93pub extern "C" fn pd_parse_document(doc_ptr: *const u8, doc_len: u32) -> u64 {
94 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
95 Ok(doc) => ok(doc_value(&doc)),
96 Err(e) => err(e),
97 }
98}
99
100#[no_mangle]
102pub extern "C" fn pd_upsert_comment(doc_ptr: *const u8, doc_len: u32, comment_ptr: *const u8, comment_len: u32) -> u64 {
103 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
104 Ok(d) => d,
105 Err(e) => return err(e),
106 };
107 let comment: Comment = match serde_json::from_str(&read_arg(comment_ptr, comment_len)) {
108 Ok(c) => c,
109 Err(e) => return err(format!("invalid comment: {e}")),
110 };
111 match doc.upsert(comment) {
112 Ok(()) => ok(doc_value(&doc)),
113 Err(e) => err(e),
114 }
115}
116
117#[no_mangle]
120pub extern "C" fn pd_delete_comment(doc_ptr: *const u8, doc_len: u32, id_ptr: *const u8, id_len: u32) -> u64 {
121 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
122 Ok(d) => d,
123 Err(e) => return err(e),
124 };
125 doc.delete(&read_arg(id_ptr, id_len));
126 ok(doc_value(&doc))
127}
128
129#[no_mangle]
134pub extern "C" fn pd_set_verdict(doc_ptr: *const u8, doc_len: u32, verdict_ptr: *const u8, verdict_len: u32) -> u64 {
135 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
136 Ok(d) => d,
137 Err(e) => return err(e),
138 };
139 let verdict: Option<Verdict> = match serde_json::from_str(&read_arg(verdict_ptr, verdict_len)) {
140 Ok(v) => v,
141 Err(e) => return err(format!("invalid verdict: {e}")),
142 };
143 match doc.set_verdict(verdict) {
144 Ok(()) => ok(doc_value(&doc)),
145 Err(e) => err(e),
146 }
147}
148
149#[no_mangle]
152pub extern "C" fn pd_merge(doc_ptr: *const u8, doc_len: u32, incoming_ptr: *const u8, incoming_len: u32) -> u64 {
153 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
154 Ok(d) => d,
155 Err(e) => return err(e),
156 };
157 let incoming = match ReviewDocument::parse(&read_arg(incoming_ptr, incoming_len)) {
158 Ok(d) => d,
159 Err(e) => return err(format!("import rejected: {e}")),
160 };
161 doc.merge(&incoming);
162 ok(doc_value(&doc))
163}
164
165#[no_mangle]
166pub extern "C" fn pd_export_json(doc_ptr: *const u8, doc_len: u32) -> u64 {
167 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
168 Ok(doc) => ok(serde_json::Value::String(export::to_json(&doc))),
169 Err(e) => err(e),
170 }
171}
172
173#[no_mangle]
174pub extern "C" fn pd_export_markdown(doc_ptr: *const u8, doc_len: u32) -> u64 {
175 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
176 Ok(doc) => ok(serde_json::Value::String(export::to_markdown(&doc))),
177 Err(e) => err(e),
178 }
179}
180
181#[no_mangle]
182pub extern "C" fn pd_export_csv(doc_ptr: *const u8, doc_len: u32) -> u64 {
183 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
184 Ok(doc) => ok(serde_json::Value::String(export::to_csv(&doc))),
185 Err(e) => err(e),
186 }
187}
188
189#[no_mangle]
195pub extern "C" fn pd_range_diff(snap_ptr: *const u8, snap_len: u32, params_ptr: *const u8, params_len: u32) -> u64 {
196 #[derive(serde::Deserialize)]
197 #[serde(deny_unknown_fields)]
198 struct Params {
199 from: usize,
200 to: usize,
201 context: usize,
202 }
203 let snap: packdiff_dto::snapshot::RangeSnapshots = match serde_json::from_str(&read_arg(snap_ptr, snap_len)) {
204 Ok(s) => s,
205 Err(e) => return err(format!("invalid snapshots: {e}")),
206 };
207 let params: Params = match serde_json::from_str(&read_arg(params_ptr, params_len)) {
208 Ok(p) => p,
209 Err(e) => return err(format!("invalid params: {e}")),
210 };
211 match packdiff_dto::snapshot::range_diff(&snap, params.from, params.to, params.context) {
212 Ok(files) => ok(serde_json::to_value(files).expect("FileDiff serializes: no non-string keys")),
213 Err(e) => err(e),
214 }
215}
216
217#[no_mangle]
224pub extern "C" fn pd_context_slice(snap_ptr: *const u8, snap_len: u32, params_ptr: *const u8, params_len: u32) -> u64 {
225 #[derive(serde::Deserialize)]
226 #[serde(deny_unknown_fields)]
227 struct Params {
228 old_path: String,
229 new_path: String,
230 old_start: u32,
231 new_start: u32,
232 count: u32,
233 }
234 let snap: packdiff_dto::snapshot::RangeSnapshots = match serde_json::from_str(&read_arg(snap_ptr, snap_len)) {
235 Ok(s) => s,
236 Err(e) => return err(format!("invalid snapshots: {e}")),
237 };
238 let params: Params = match serde_json::from_str(&read_arg(params_ptr, params_len)) {
239 Ok(p) => p,
240 Err(e) => return err(format!("invalid params: {e}")),
241 };
242 match packdiff_dto::snapshot::context_slice(
243 &snap,
244 ¶ms.old_path,
245 ¶ms.new_path,
246 params.old_start,
247 params.new_start,
248 params.count,
249 ) {
250 Ok(lines) => ok(serde_json::to_value(lines).expect("Line serializes: no non-string keys")),
251 Err(e) => err(e),
252 }
253}
254
255#[no_mangle]
259pub extern "C" fn pd_markdown_html(text_ptr: *const u8, text_len: u32) -> u64 {
260 ok(serde_json::Value::String(packdiff_dto::markdown::to_html(&read_arg(text_ptr, text_len))))
261}
262
263#[no_mangle]
267pub extern "C" fn pd_storage_key(meta_ptr: *const u8, meta_len: u32) -> u64 {
268 #[derive(serde::Deserialize)]
269 struct Meta {
270 repo: String,
271 base: RefInfo,
272 head: RefInfo,
273 }
274 match serde_json::from_str::<Meta>(&read_arg(meta_ptr, meta_len)) {
275 Ok(m) => ok(serde_json::Value::String(storage_key(&m.repo, &m.base.sha, &m.head.sha))),
276 Err(e) => err(format!("invalid meta: {e}")),
277 }
278}