1use std::alloc::{alloc, dealloc, Layout};
21
22use packdiff_dto::review::{Comment, ReviewDocument};
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]
132pub extern "C" fn pd_merge(doc_ptr: *const u8, doc_len: u32, incoming_ptr: *const u8, incoming_len: u32) -> u64 {
133 let mut doc = match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
134 Ok(d) => d,
135 Err(e) => return err(e),
136 };
137 let incoming = match ReviewDocument::parse(&read_arg(incoming_ptr, incoming_len)) {
138 Ok(d) => d,
139 Err(e) => return err(format!("import rejected: {e}")),
140 };
141 doc.merge(&incoming);
142 ok(doc_value(&doc))
143}
144
145#[no_mangle]
146pub extern "C" fn pd_export_json(doc_ptr: *const u8, doc_len: u32) -> u64 {
147 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
148 Ok(doc) => ok(serde_json::Value::String(export::to_json(&doc))),
149 Err(e) => err(e),
150 }
151}
152
153#[no_mangle]
154pub extern "C" fn pd_export_markdown(doc_ptr: *const u8, doc_len: u32) -> u64 {
155 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
156 Ok(doc) => ok(serde_json::Value::String(export::to_markdown(&doc))),
157 Err(e) => err(e),
158 }
159}
160
161#[no_mangle]
162pub extern "C" fn pd_export_csv(doc_ptr: *const u8, doc_len: u32) -> u64 {
163 match ReviewDocument::parse(&read_arg(doc_ptr, doc_len)) {
164 Ok(doc) => ok(serde_json::Value::String(export::to_csv(&doc))),
165 Err(e) => err(e),
166 }
167}
168
169#[no_mangle]
175pub extern "C" fn pd_range_diff(snap_ptr: *const u8, snap_len: u32, params_ptr: *const u8, params_len: u32) -> u64 {
176 #[derive(serde::Deserialize)]
177 #[serde(deny_unknown_fields)]
178 struct Params {
179 from: usize,
180 to: usize,
181 context: usize,
182 }
183 let snap: packdiff_dto::snapshot::RangeSnapshots = match serde_json::from_str(&read_arg(snap_ptr, snap_len)) {
184 Ok(s) => s,
185 Err(e) => return err(format!("invalid snapshots: {e}")),
186 };
187 let params: Params = match serde_json::from_str(&read_arg(params_ptr, params_len)) {
188 Ok(p) => p,
189 Err(e) => return err(format!("invalid params: {e}")),
190 };
191 match packdiff_dto::snapshot::range_diff(&snap, params.from, params.to, params.context) {
192 Ok(files) => ok(serde_json::to_value(files).expect("FileDiff serializes: no non-string keys")),
193 Err(e) => err(e),
194 }
195}
196
197#[no_mangle]
201pub extern "C" fn pd_markdown_html(text_ptr: *const u8, text_len: u32) -> u64 {
202 ok(serde_json::Value::String(packdiff_dto::markdown::to_html(&read_arg(text_ptr, text_len))))
203}
204
205#[no_mangle]
207pub extern "C" fn pd_storage_key(meta_ptr: *const u8, meta_len: u32) -> u64 {
208 #[derive(serde::Deserialize)]
209 struct Meta {
210 repo: String,
211 base: RefInfo,
212 head: RefInfo,
213 }
214 match serde_json::from_str::<Meta>(&read_arg(meta_ptr, meta_len)) {
215 Ok(m) => ok(serde_json::Value::String(storage_key(&m.repo, &m.base.sha, &m.head.sha))),
216 Err(e) => err(format!("invalid meta: {e}")),
217 }
218}