1use std::io::{self, Read, Write};
17
18use serde::de::Error as _;
19use serde::{Deserialize, Deserializer, Serialize, Serializer};
20
21use crate::segments::Segment;
22
23pub const REQUEST_SCHEMA: &str = "text-mirror/adapter-request@1";
25
26pub const RESPONSE_SCHEMA: &str = "text-mirror/adapter-response@1";
28
29pub const MAX_REQUEST_BYTES: u32 = 1024 * 1024;
33
34macro_rules! literal_schema {
35 ($name:ident, $value:expr) => {
36 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
39 pub struct $name;
40
41 impl Serialize for $name {
42 fn serialize<S: Serializer>(
43 &self,
44 serializer: S,
45 ) -> std::result::Result<S::Ok, S::Error> {
46 serializer.serialize_str($value)
47 }
48 }
49
50 impl<'de> Deserialize<'de> for $name {
51 fn deserialize<D: Deserializer<'de>>(
52 deserializer: D,
53 ) -> std::result::Result<Self, D::Error> {
54 let value = String::deserialize(deserializer)?;
55 if value == $value {
56 Ok($name)
57 } else {
58 Err(D::Error::custom(format!(
59 "unrecognized schema {value:?}, expected {:?}",
60 $value
61 )))
62 }
63 }
64 }
65 };
66}
67
68literal_schema!(RequestSchema, REQUEST_SCHEMA);
69literal_schema!(ResponseSchema, RESPONSE_SCHEMA);
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73#[serde(deny_unknown_fields)]
74pub struct Request {
75 pub schema: RequestSchema,
77 pub adapter: String,
79 pub payload: serde_json::Value,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
88#[serde(deny_unknown_fields)]
89pub struct Response {
90 pub schema: ResponseSchema,
92 #[serde(skip_serializing_if = "Option::is_none", default)]
94 pub ok: Option<serde_json::Value>,
95 #[serde(skip_serializing_if = "Option::is_none", default)]
97 pub error: Option<WireError>,
98}
99
100impl Response {
101 pub fn ok(value: serde_json::Value) -> Response {
103 Response {
104 schema: ResponseSchema,
105 ok: Some(value),
106 error: None,
107 }
108 }
109
110 pub fn err(code: &str, message: String) -> Response {
112 Response {
113 schema: ResponseSchema,
114 ok: None,
115 error: Some(WireError {
116 code: code.to_string(),
117 message,
118 }),
119 }
120 }
121
122 pub fn validate(&self) -> std::result::Result<(), String> {
124 match (&self.ok, &self.error) {
125 (Some(_), None) | (None, Some(_)) => Ok(()),
126 (Some(_), Some(_)) => Err("response carries both ok and error".to_string()),
127 (None, None) => Err("response carries neither ok nor error".to_string()),
128 }
129 }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(deny_unknown_fields)]
135pub struct WireError {
136 pub code: String,
138 pub message: String,
140}
141
142#[derive(Debug)]
144pub enum FrameRead {
145 Complete(Vec<u8>),
147 Empty,
149 Truncated,
151 Oversized {
154 declared: u64,
156 },
157}
158
159pub fn read_frame(reader: &mut impl Read, max_bytes: u32) -> io::Result<FrameRead> {
162 let mut header = [0u8; 4];
163 match read_exact_or_eof(reader, &mut header)? {
164 Fill::Empty => return Ok(FrameRead::Empty),
165 Fill::Partial => return Ok(FrameRead::Truncated),
166 Fill::Full => {}
167 }
168 let declared = u32::from_be_bytes(header);
169 if declared > max_bytes {
170 return Ok(FrameRead::Oversized {
171 declared: u64::from(declared),
172 });
173 }
174 let mut payload = vec![0u8; declared as usize];
175 match read_exact_or_eof(reader, &mut payload)? {
176 Fill::Full => Ok(FrameRead::Complete(payload)),
177 Fill::Empty | Fill::Partial => Ok(FrameRead::Truncated),
178 }
179}
180
181enum Fill {
182 Full,
183 Partial,
184 Empty,
185}
186
187fn read_exact_or_eof(reader: &mut impl Read, buffer: &mut [u8]) -> io::Result<Fill> {
188 let mut filled = 0;
189 while filled < buffer.len() {
190 match reader.read(&mut buffer[filled..]) {
191 Ok(0) => {
192 return Ok(if filled == 0 {
193 Fill::Empty
194 } else {
195 Fill::Partial
196 });
197 }
198 Ok(n) => filled += n,
199 Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
200 Err(e) => return Err(e),
201 }
202 }
203 Ok(Fill::Full)
204}
205
206pub fn write_frame<T: Serialize>(writer: &mut impl Write, value: &T) -> io::Result<()> {
208 let payload = serde_json::to_vec(value).map_err(io::Error::other)?;
209 let length = u32::try_from(payload.len())
210 .map_err(|_| io::Error::other("frame payload exceeds the u32 length prefix"))?;
211 writer.write_all(&length.to_be_bytes())?;
212 writer.write_all(&payload)?;
213 writer.flush()
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn frames_roundtrip() {
222 let mut buffer = Vec::new();
223 let request = Request {
224 schema: RequestSchema,
225 adapter: "pdf".to_string(),
226 payload: serde_json::json!({"input": "input.pdf"}),
227 };
228 write_frame(&mut buffer, &request).unwrap();
229 let mut cursor = std::io::Cursor::new(buffer);
230 let FrameRead::Complete(payload) = read_frame(&mut cursor, MAX_REQUEST_BYTES).unwrap()
231 else {
232 panic!("expected a complete frame");
233 };
234 let back: Request = serde_json::from_slice(&payload).unwrap();
235 assert_eq!(back.adapter, "pdf");
236 }
237
238 #[test]
239 fn oversized_declarations_are_refused_before_allocation() {
240 let mut bytes = u32::MAX.to_be_bytes().to_vec();
241 bytes.extend_from_slice(b"junk");
242 let mut cursor = std::io::Cursor::new(bytes);
243 let FrameRead::Oversized { declared } = read_frame(&mut cursor, 1024).unwrap() else {
244 panic!("expected an oversized refusal");
245 };
246 assert_eq!(declared, u64::from(u32::MAX));
247 }
248
249 #[test]
250 fn truncation_is_detected_in_header_and_payload() {
251 let mut cursor = std::io::Cursor::new(vec![0u8, 0, 0]);
252 assert!(matches!(
253 read_frame(&mut cursor, 1024).unwrap(),
254 FrameRead::Truncated
255 ));
256
257 let mut bytes = 10u32.to_be_bytes().to_vec();
258 bytes.extend_from_slice(b"half");
259 let mut cursor = std::io::Cursor::new(bytes);
260 assert!(matches!(
261 read_frame(&mut cursor, 1024).unwrap(),
262 FrameRead::Truncated
263 ));
264
265 let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
266 assert!(matches!(
267 read_frame(&mut cursor, 1024).unwrap(),
268 FrameRead::Empty
269 ));
270 }
271
272 #[test]
273 fn responses_require_exactly_one_body() {
274 assert!(Response::ok(serde_json::json!({})).validate().is_ok());
275 assert!(Response::err("io", "detail".to_string()).validate().is_ok());
276 let both = Response {
277 schema: ResponseSchema,
278 ok: Some(serde_json::json!({})),
279 error: Some(WireError {
280 code: "io".to_string(),
281 message: "detail".to_string(),
282 }),
283 };
284 assert!(both.validate().is_err());
285 let neither = Response {
286 schema: ResponseSchema,
287 ok: None,
288 error: None,
289 };
290 assert!(neither.validate().is_err());
291 }
292
293 #[test]
294 fn wrong_schema_values_are_rejected() {
295 let json = serde_json::to_string(&Response::ok(serde_json::json!({}))).unwrap();
296 let bumped = json.replace("adapter-response@1", "adapter-response@2");
297 assert!(serde_json::from_str::<Response>(&bumped).is_err());
298 }
299}
300
301pub mod bodies {
304 use super::*;
305
306 #[derive(Debug, Clone, Serialize, Deserialize)]
308 #[serde(deny_unknown_fields)]
309 pub struct PdfRequest {
310 pub input: String,
312 }
313
314 #[derive(Debug, Clone, Serialize, Deserialize)]
316 #[serde(deny_unknown_fields)]
317 pub struct PdfOk {
318 pub text: String,
320 pub warnings: Vec<String>,
322 pub segments: Vec<Segment>,
324 }
325
326 #[derive(Debug, Clone, Serialize, Deserialize)]
334 #[serde(deny_unknown_fields)]
335 pub struct RecordsRequest {
336 pub input: String,
338 pub format: String,
340 pub max_records: u64,
343 pub max_tables: u64,
345 pub max_output_bytes: u64,
347 }
348
349 #[derive(Debug, Clone, Serialize, Deserialize)]
351 #[serde(deny_unknown_fields)]
352 pub struct RecordsOk {
353 pub text: String,
355 pub warnings: Vec<String>,
357 pub segments: Vec<Segment>,
360 }
361
362 #[derive(Debug, Clone, Serialize, Deserialize)]
364 #[serde(rename_all = "snake_case", deny_unknown_fields)]
365 pub enum OcrInput {
366 Image,
368 PageRender {
370 page: u32,
372 dpi: u32,
374 },
375 }
376
377 #[derive(Debug, Clone, Serialize, Deserialize)]
379 #[serde(deny_unknown_fields)]
380 pub struct OcrRequest {
381 pub input: String,
383 pub kind: OcrInput,
385 }
386
387 #[derive(Debug, Clone, Serialize, Deserialize)]
389 #[serde(deny_unknown_fields)]
390 pub struct OcrSpan {
391 pub text: String,
393 pub confidence: f64,
395 }
396
397 #[derive(Debug, Clone, Serialize, Deserialize)]
399 #[serde(deny_unknown_fields)]
400 pub struct OcrOk {
401 pub spans: Vec<OcrSpan>,
403 }
404
405 #[derive(Debug, Clone, Serialize, Deserialize)]
407 #[serde(deny_unknown_fields)]
408 pub struct AsrRequest {
409 pub input: String,
411 #[serde(skip_serializing_if = "Option::is_none", default)]
413 pub language: Option<String>,
414 }
415
416 #[derive(Debug, Clone, Serialize, Deserialize)]
418 #[serde(deny_unknown_fields)]
419 pub struct AsrSegment {
420 pub start_seconds: f64,
422 pub end_seconds: f64,
424 pub speaker: u32,
426 pub text: String,
428 }
429
430 #[derive(Debug, Clone, Serialize, Deserialize)]
432 #[serde(deny_unknown_fields)]
433 pub struct AsrOk {
434 pub segments: Vec<AsrSegment>,
436 #[serde(skip_serializing_if = "Option::is_none", default)]
438 pub language: Option<String>,
439 #[serde(skip_serializing_if = "Option::is_none", default)]
441 pub duration_seconds: Option<f64>,
442 }
443
444 #[derive(Debug, Clone, Serialize, Deserialize)]
446 #[serde(deny_unknown_fields)]
447 pub struct VideoRequest {
448 pub input: String,
450 pub sample_interval_seconds: f64,
452 pub max_frames: u32,
454 }
455
456 #[derive(Debug, Clone, Serialize, Deserialize)]
458 #[serde(deny_unknown_fields)]
459 pub struct ScreenState {
460 pub first_seen_seconds: f64,
462 pub text: String,
464 }
465
466 #[derive(Debug, Clone, Serialize, Deserialize)]
468 #[serde(deny_unknown_fields)]
469 pub struct VideoOk {
470 pub states: Vec<ScreenState>,
473 #[serde(skip_serializing_if = "Option::is_none", default)]
475 pub duration_seconds: Option<f64>,
476 }
477
478 #[derive(Debug, Clone, Serialize, Deserialize)]
480 #[serde(deny_unknown_fields)]
481 pub struct ProbeReport {
482 pub attempts: Vec<ProbeAttempt>,
484 }
485
486 #[derive(Debug, Clone, Serialize, Deserialize)]
488 #[serde(deny_unknown_fields)]
489 pub struct ProbeAttempt {
490 pub name: String,
492 pub outcome: String,
496 pub detail: String,
498 }
499
500 #[derive(Debug, Clone, Serialize, Deserialize)]
507 #[serde(deny_unknown_fields)]
508 pub struct ProbeNetRequest {
509 pub unix_target: String,
513 pub tcp4_target: String,
516 pub tcp6_target: String,
519 }
520}