1use std::collections::BTreeMap;
17use std::fmt;
18use std::io::{self, Read, Write};
19use std::time::Duration;
20
21use serde::{Deserialize, Serialize};
22use serde_json::Value;
23use serde_json::value::RawValue;
24
25use crate::types::{
26 LeanWorkerCapabilityMetadata, LeanWorkerDeclarationFilter, LeanWorkerDeclarationInspectionRequest,
27 LeanWorkerDeclarationInspectionResult, LeanWorkerDeclarationRow, LeanWorkerDeclarationSearch,
28 LeanWorkerDeclarationSearchResult, LeanWorkerDeclarationType, LeanWorkerDeclarationVerificationRequest,
29 LeanWorkerDeclarationVerificationResult, LeanWorkerDoctorReport, LeanWorkerElabOptions, LeanWorkerElabResult,
30 LeanWorkerKernelResult, LeanWorkerMetaResult, LeanWorkerMetaTransparency, LeanWorkerModuleQuery,
31 LeanWorkerModuleQueryBatchOutcome, LeanWorkerModuleQueryOutcome, LeanWorkerModuleQuerySelector,
32 LeanWorkerOutputBudgets, LeanWorkerProofAttemptRequest, LeanWorkerProofAttemptResult, LeanWorkerRendered,
33};
34
35pub const PROTOCOL_VERSION: u16 = 8;
38
39pub const MAX_FRAME_BYTES: u32 = 1024 * 1024;
49
50pub const MIN_FRAME_BYTES: u32 = 64 * 1024;
53
54pub const MAX_FRAME_BYTES_HARD_CAP: u32 = 256 * 1024 * 1024;
57
58#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
60#[non_exhaustive]
61pub struct Frame {
62 pub version: u16,
64 pub message: Message,
66}
67
68impl Frame {
69 #[must_use]
71 pub fn new(message: Message) -> Self {
72 Self {
73 version: PROTOCOL_VERSION,
74 message,
75 }
76 }
77}
78
79#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
81#[serde(tag = "type", content = "body", rename_all = "snake_case")]
82#[non_exhaustive]
83pub enum Message {
84 Handshake {
87 worker_version: String,
89 protocol_version: u16,
91 },
92 ConfigureFrameLimit {
99 max_frame_bytes: u32,
101 },
102 Request(Request),
104 Response(Response),
106 Diagnostic(Diagnostic),
108 ProgressTick(ProgressTick),
110 DataRow(DataRow),
112 FatalExit(FatalExit),
115}
116
117#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
119#[serde(tag = "op", rename_all = "snake_case")]
120#[non_exhaustive]
121pub enum Request {
122 Health,
123 LoadFixtureCapability {
124 manifest_path: String,
125 },
126 CallFixtureMul {
127 manifest_path: String,
128 lhs: u64,
129 rhs: u64,
130 },
131 TriggerLeanPanic {
132 manifest_path: String,
133 },
134 OpenHostSession {
135 project_root: String,
136 mode: HostSessionMode,
137 imports: Vec<String>,
138 },
139 Elaborate {
140 source: String,
141 options: LeanWorkerElabOptions,
142 },
143 KernelCheck {
144 source: String,
145 options: LeanWorkerElabOptions,
146 progress: bool,
147 },
148 DeclarationKinds {
149 names: Vec<String>,
150 progress: bool,
151 },
152 DeclarationNames {
153 names: Vec<String>,
154 progress: bool,
155 },
156 RunDataStream {
157 export: String,
158 request_json: String,
159 progress: bool,
160 },
161 CapabilityMetadata {
162 export: String,
163 request_json: String,
164 },
165 CapabilityDoctor {
166 export: String,
167 request_json: String,
168 },
169 JsonCommand {
170 export: String,
171 request_json: String,
172 },
173 InferType {
174 source: String,
175 options: LeanWorkerElabOptions,
176 },
177 Whnf {
178 source: String,
179 options: LeanWorkerElabOptions,
180 },
181 IsDefEq {
182 lhs: String,
183 rhs: String,
184 transparency: LeanWorkerMetaTransparency,
185 options: LeanWorkerElabOptions,
186 },
187 Describe {
188 name: String,
189 },
190 SearchDeclarations {
191 search: LeanWorkerDeclarationSearch,
192 },
193 DeclarationType {
194 name: String,
195 max_bytes: usize,
196 },
197 InspectDeclaration {
198 request: LeanWorkerDeclarationInspectionRequest,
199 },
200 AttemptProof {
201 request: LeanWorkerProofAttemptRequest,
202 options: LeanWorkerElabOptions,
203 progress: bool,
204 },
205 VerifyDeclaration {
206 request: LeanWorkerDeclarationVerificationRequest,
207 options: LeanWorkerElabOptions,
208 progress: bool,
209 },
210 ListDeclarationsStrings {
211 filter: LeanWorkerDeclarationFilter,
212 progress: bool,
213 },
214 DescribeBulk {
215 names: Vec<String>,
216 progress: bool,
217 },
218 ProcessModuleQuery {
219 source: String,
220 query: LeanWorkerModuleQuery,
221 options: LeanWorkerElabOptions,
222 },
223 ProcessModuleQueryBatch {
224 source: String,
225 selectors: Vec<LeanWorkerModuleQuerySelector>,
226 budgets: LeanWorkerOutputBudgets,
227 options: LeanWorkerElabOptions,
228 },
229 ClearModuleSnapshotCache,
230 EmitTestRows {
233 streams: Vec<String>,
234 },
235 EmitTestRowsThenExit,
236 EmitTestRowsThenPanic,
237 Terminate,
238}
239
240#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
242#[serde(tag = "kind", rename_all = "snake_case")]
243#[non_exhaustive]
244pub enum HostSessionMode {
245 Capability {
247 package: String,
248 lib_name: String,
249 manifest_path: Option<String>,
250 },
251 ShimsOnly,
253}
254
255#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
257#[serde(tag = "status", rename_all = "snake_case")]
258#[non_exhaustive]
259pub enum Response {
260 HealthOk,
261 CapabilityLoaded,
262 U64 {
263 value: u64,
264 },
265 HostSessionOpened,
266 Elaboration {
267 outcome: LeanWorkerElabResult,
268 },
269 KernelCheck {
270 outcome: LeanWorkerKernelResult,
271 },
272 Strings {
273 values: Vec<String>,
274 },
275 StreamComplete {
276 summary: StreamSummary,
277 },
278 StreamExportFailed {
279 status_byte: u8,
280 },
281 StreamCallbackFailed {
282 status_byte: u8,
283 description: String,
284 },
285 StreamRowMalformed {
286 message: String,
287 },
288 CapabilityMetadata {
289 metadata: LeanWorkerCapabilityMetadata,
290 },
291 CapabilityDoctor {
292 report: LeanWorkerDoctorReport,
293 },
294 CapabilityMetadataMalformed {
295 message: String,
296 },
297 CapabilityDoctorMalformed {
298 message: String,
299 },
300 JsonCommand {
301 response_json: String,
302 },
303 MetaExpr {
304 result: LeanWorkerMetaResult<LeanWorkerRendered>,
305 },
306 MetaBool {
307 result: LeanWorkerMetaResult<bool>,
308 },
309 Declaration {
310 row: Option<LeanWorkerDeclarationRow>,
311 },
312 DeclarationSearch {
313 result: LeanWorkerDeclarationSearchResult,
314 },
315 DeclarationType {
316 row: Option<LeanWorkerDeclarationType>,
317 },
318 DeclarationInspection {
319 result: LeanWorkerDeclarationInspectionResult,
320 },
321 ProofAttempt {
322 result: LeanWorkerProofAttemptResult,
323 },
324 DeclarationVerification {
325 result: LeanWorkerDeclarationVerificationResult,
326 },
327 DeclarationBulk {
328 rows: Vec<LeanWorkerDeclarationRow>,
329 },
330 ProcessModuleQuery {
331 outcome: LeanWorkerModuleQueryOutcome,
332 },
333 ProcessModuleQueryBatch {
334 outcome: LeanWorkerModuleQueryBatchOutcome,
335 },
336 ModuleSnapshotCacheCleared {
337 result: crate::types::LeanWorkerModuleSnapshotCacheClearResult,
338 },
339 RowsComplete {
340 count: u64,
341 },
342 Terminating,
343 Error {
344 code: String,
345 message: String,
346 },
347}
348
349#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
351#[non_exhaustive]
352pub struct Diagnostic {
353 pub code: String,
355 pub message: String,
357}
358
359impl Diagnostic {
360 #[must_use]
362 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
363 Self {
364 code: code.into(),
365 message: message.into(),
366 }
367 }
368}
369
370#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
373#[non_exhaustive]
374pub struct ProgressTick {
375 pub phase: String,
377 pub current: u64,
379 pub total: Option<u64>,
381}
382
383impl ProgressTick {
384 #[must_use]
386 pub fn new(phase: impl Into<String>, current: u64, total: Option<u64>) -> Self {
387 Self {
388 phase: phase.into(),
389 current,
390 total,
391 }
392 }
393}
394
395#[derive(Clone, Debug, Deserialize, Serialize)]
402pub struct DataRow {
403 pub stream: String,
405 pub sequence: u64,
407 pub payload: Box<RawValue>,
409}
410
411impl PartialEq for DataRow {
412 fn eq(&self, other: &Self) -> bool {
413 self.stream == other.stream && self.sequence == other.sequence && self.payload.get() == other.payload.get()
414 }
415}
416
417impl Eq for DataRow {}
418
419#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
421#[non_exhaustive]
422pub struct StreamSummary {
423 pub total_rows: u64,
425 pub per_stream_counts: BTreeMap<String, u64>,
427 pub elapsed_micros: u64,
429 pub metadata: Option<Value>,
431}
432
433impl StreamSummary {
434 #[must_use]
437 pub fn new(
438 total_rows: u64,
439 per_stream_counts: BTreeMap<String, u64>,
440 elapsed: Duration,
441 metadata: Option<Value>,
442 ) -> Self {
443 Self {
444 total_rows,
445 per_stream_counts,
446 elapsed_micros: elapsed.as_micros().try_into().unwrap_or(u64::MAX),
447 metadata,
448 }
449 }
450}
451
452#[derive(Debug, Default)]
455#[non_exhaustive]
456pub struct DataRowEmitter {
457 sequences: BTreeMap<String, u64>,
458 count: u64,
459}
460
461impl DataRowEmitter {
462 pub fn next(&mut self, stream: impl Into<String>, payload: Box<RawValue>) -> DataRow {
465 let stream = stream.into();
466 let sequence = self.sequences.entry(stream.clone()).or_insert(0);
467 let row = DataRow {
468 stream,
469 sequence: *sequence,
470 payload,
471 };
472 *sequence = sequence.saturating_add(1);
473 self.count = self.count.saturating_add(1);
474 row
475 }
476
477 #[cfg(test)]
478 fn emit(
479 &mut self,
480 writer: &mut impl Write,
481 stream: impl Into<String>,
482 payload: &Value,
483 ) -> Result<(), ProtocolError> {
484 let row = self.next(stream, serde_json::value::to_raw_value(payload)?);
485 write_frame(writer, Message::DataRow(row), MAX_FRAME_BYTES)
486 }
487
488 #[must_use]
490 pub fn count(&self) -> u64 {
491 self.count
492 }
493
494 #[must_use]
496 pub fn per_stream_counts(&self) -> BTreeMap<String, u64> {
497 self.sequences.clone()
498 }
499}
500
501#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
504#[non_exhaustive]
505pub struct FatalExit {
506 pub status: String,
508 pub stderr: String,
510}
511
512impl FatalExit {
513 #[must_use]
515 pub fn new(status: impl Into<String>, stderr: impl Into<String>) -> Self {
516 Self {
517 status: status.into(),
518 stderr: stderr.into(),
519 }
520 }
521}
522
523#[derive(Debug)]
525#[non_exhaustive]
526pub enum ProtocolError {
527 Io(io::Error),
529 Json(serde_json::Error),
531 FrameTooLarge {
533 len: u32,
535 max: u32,
537 },
538 VersionMismatch {
540 expected: u16,
542 actual: u16,
544 },
545}
546
547impl ProtocolError {
548 #[must_use]
552 pub fn is_eof(&self) -> bool {
553 matches!(self, Self::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof)
554 }
555}
556
557impl fmt::Display for ProtocolError {
558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559 match self {
560 Self::Io(err) => write!(f, "worker protocol I/O failed: {err}"),
561 Self::Json(err) => write!(f, "worker protocol JSON decode failed: {err}"),
562 Self::FrameTooLarge { len, max } => {
563 write!(f, "worker protocol frame too large: {len} bytes exceeds {max}")
564 }
565 Self::VersionMismatch { expected, actual } => {
566 write!(
567 f,
568 "worker protocol version mismatch: expected {expected}, received {actual}"
569 )
570 }
571 }
572 }
573}
574
575impl std::error::Error for ProtocolError {}
576
577impl From<io::Error> for ProtocolError {
578 fn from(value: io::Error) -> Self {
579 Self::Io(value)
580 }
581}
582
583impl From<serde_json::Error> for ProtocolError {
584 fn from(value: serde_json::Error) -> Self {
585 Self::Json(value)
586 }
587}
588
589pub fn write_frame(writer: &mut impl Write, message: Message, max_frame_bytes: u32) -> Result<(), ProtocolError> {
602 let bytes = serde_json::to_vec(&Frame::new(message))?;
603 let len = u32::try_from(bytes.len()).map_err(|_| ProtocolError::FrameTooLarge {
604 len: u32::MAX,
605 max: max_frame_bytes,
606 })?;
607 if len > max_frame_bytes {
608 return Err(ProtocolError::FrameTooLarge {
609 len,
610 max: max_frame_bytes,
611 });
612 }
613 writer.write_all(&len.to_be_bytes())?;
614 writer.write_all(&bytes)?;
615 writer.flush()?;
616 Ok(())
617}
618
619pub fn read_frame(reader: &mut impl Read, max_frame_bytes: u32) -> Result<Frame, ProtocolError> {
632 let mut len_bytes = [0_u8; 4];
633 reader.read_exact(&mut len_bytes)?;
634 let len = u32::from_be_bytes(len_bytes);
635 if len > max_frame_bytes {
636 return Err(ProtocolError::FrameTooLarge {
637 len,
638 max: max_frame_bytes,
639 });
640 }
641 let mut bytes = vec![0_u8; len as usize];
642 reader.read_exact(&mut bytes)?;
643 let frame: Frame = serde_json::from_slice(&bytes)?;
644 if frame.version != PROTOCOL_VERSION {
645 return Err(ProtocolError::VersionMismatch {
646 expected: PROTOCOL_VERSION,
647 actual: frame.version,
648 });
649 }
650 Ok(frame)
651}
652
653#[cfg(test)]
654mod tests {
655 #![allow(clippy::expect_used, clippy::panic)]
656
657 use std::io::Cursor;
658
659 use serde_json::json;
660 use serde_json::value::RawValue;
661
662 use super::{
663 DataRow, DataRowEmitter, MAX_FRAME_BYTES, MAX_FRAME_BYTES_HARD_CAP, MIN_FRAME_BYTES, Message, ProtocolError,
664 Request, Response, read_frame, write_frame,
665 };
666 use crate::types::{
667 LeanWorkerDeclarationFilter, LeanWorkerDeclarationFlags, LeanWorkerDeclarationInspection,
668 LeanWorkerDeclarationInspectionFields, LeanWorkerDeclarationInspectionRequest,
669 LeanWorkerDeclarationInspectionResult, LeanWorkerDeclarationNameMatch, LeanWorkerDeclarationProofSearchFacts,
670 LeanWorkerDeclarationSearch, LeanWorkerDeclarationSearchBias, LeanWorkerDeclarationSearchFacts,
671 LeanWorkerDeclarationSearchPruning, LeanWorkerDeclarationSearchResult, LeanWorkerDeclarationSearchRow,
672 LeanWorkerDeclarationSearchScope, LeanWorkerDeclarationSearchTimings, LeanWorkerDeclarationTargetInfo,
673 LeanWorkerDeclarationVerificationFacts, LeanWorkerDeclarationVerificationRequest,
674 LeanWorkerDeclarationVerificationResult, LeanWorkerDeclarationVerificationStatus,
675 LeanWorkerDeclarationVerificationTarget, LeanWorkerElabFailure, LeanWorkerElabOptions,
676 LeanWorkerModuleCacheStatus, LeanWorkerModuleQuery, LeanWorkerModuleQueryBatchEnvelope,
677 LeanWorkerModuleQueryBatchItem, LeanWorkerModuleQueryBatchOutcome, LeanWorkerModuleQueryBatchResult,
678 LeanWorkerModuleQueryCacheFacts, LeanWorkerModuleQueryOutcome, LeanWorkerModuleQueryResult,
679 LeanWorkerModuleQuerySelector, LeanWorkerModuleQueryTimings, LeanWorkerModuleSourceSpan,
680 LeanWorkerOutputBudgets, LeanWorkerProofAttemptEnvelope, LeanWorkerProofAttemptRequest,
681 LeanWorkerProofAttemptResult, LeanWorkerProofAttemptRow, LeanWorkerProofAttemptStatus,
682 LeanWorkerProofCandidate, LeanWorkerProofEditTarget, LeanWorkerProofPositionSelector,
683 LeanWorkerProofPositionSummary, LeanWorkerProofStateResult, LeanWorkerRenderedInfo, LeanWorkerRendering,
684 LeanWorkerSorryPolicy, LeanWorkerSourceRange, LeanWorkerTypeAtResult,
685 };
686
687 fn raw_json(value: &serde_json::Value) -> Box<RawValue> {
688 serde_json::value::to_raw_value(value).expect("test JSON converts to raw value")
689 }
690
691 fn assert_frame_round_trips(message: &Message) {
692 let mut bytes = Vec::new();
693 write_frame(&mut bytes, message.clone(), MAX_FRAME_BYTES).expect("frame writes");
694 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("frame reads");
695 assert_eq!(&frame.message, message);
696 }
697
698 fn declaration_target_info_fixture(declaration_name: &str) -> LeanWorkerDeclarationTargetInfo {
699 let span = LeanWorkerModuleSourceSpan {
700 start_line: 1,
701 start_column: 1,
702 end_line: 1,
703 end_column: 10,
704 };
705 let short_name = declaration_name.rsplit('.').next().unwrap_or(declaration_name);
706 LeanWorkerDeclarationTargetInfo {
707 short_name: short_name.to_owned(),
708 declaration_name: declaration_name.to_owned(),
709 namespace_name: declaration_name
710 .strip_suffix(&format!(".{short_name}"))
711 .unwrap_or("")
712 .to_owned(),
713 declaration_kind: "theorem".to_owned(),
714 declaration_span: span.clone(),
715 name_span: span.clone(),
716 body_span: span,
717 }
718 }
719
720 fn verification_facts_fixture(
721 candidates: Vec<LeanWorkerDeclarationTargetInfo>,
722 axioms_available: bool,
723 ) -> LeanWorkerDeclarationVerificationFacts {
724 LeanWorkerDeclarationVerificationFacts {
725 target: None,
726 diagnostics: LeanWorkerElabFailure {
727 diagnostics: Vec::new(),
728 truncated: false,
729 },
730 unresolved_goals: Vec::new(),
731 contains_sorry: false,
732 contains_admit: false,
733 contains_sorry_ax: false,
734 axioms: Vec::new(),
735 axioms_truncated: false,
736 output_truncated: false,
737 candidates,
738 axioms_available,
739 }
740 }
741
742 #[test]
743 fn data_row_round_trips_through_length_delimited_frame() {
744 let row = DataRow {
745 stream: "rows".to_owned(),
746 sequence: 7,
747 payload: raw_json(&json!({ "name": "Nat.add", "score": 3 })),
748 };
749 let mut bytes = Vec::new();
750 write_frame(&mut bytes, Message::DataRow(row.clone()), MAX_FRAME_BYTES).expect("data row writes");
751 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("data row reads");
752 assert_eq!(frame.message, Message::DataRow(row));
753 }
754
755 #[test]
756 fn data_row_emitter_assigns_per_stream_sequences() {
757 let mut emitter = DataRowEmitter::default();
758 let mut bytes = Vec::new();
759 emitter
760 .emit(&mut bytes, "rows", &json!({ "i": 0 }))
761 .expect("first row writes");
762 emitter
763 .emit(&mut bytes, "warnings", &json!({ "i": 1 }))
764 .expect("second row writes");
765 emitter
766 .emit(&mut bytes, "rows", &json!({ "i": 2 }))
767 .expect("third row writes");
768 assert_eq!(emitter.count(), 3);
769
770 let mut cursor = Cursor::new(bytes);
771 let rows = [
772 read_frame(&mut cursor, MAX_FRAME_BYTES).expect("first row reads"),
773 read_frame(&mut cursor, MAX_FRAME_BYTES).expect("second row reads"),
774 read_frame(&mut cursor, MAX_FRAME_BYTES).expect("third row reads"),
775 ];
776 assert_eq!(
777 rows.map(|frame| frame.message),
778 [
779 Message::DataRow(DataRow {
780 stream: "rows".to_owned(),
781 sequence: 0,
782 payload: raw_json(&json!({ "i": 0 })),
783 }),
784 Message::DataRow(DataRow {
785 stream: "warnings".to_owned(),
786 sequence: 0,
787 payload: raw_json(&json!({ "i": 1 })),
788 }),
789 Message::DataRow(DataRow {
790 stream: "rows".to_owned(),
791 sequence: 1,
792 payload: raw_json(&json!({ "i": 2 })),
793 }),
794 ],
795 );
796 }
797
798 #[test]
799 fn oversized_data_row_is_rejected_before_write() {
800 let row = DataRow {
801 stream: "rows".to_owned(),
802 sequence: 0,
803 payload: raw_json(&json!({ "blob": "x".repeat(MAX_FRAME_BYTES as usize) })),
804 };
805 let mut bytes = Vec::new();
806 let err =
807 write_frame(&mut bytes, Message::DataRow(row), MAX_FRAME_BYTES).expect_err("oversized frame is rejected");
808 match err {
809 ProtocolError::FrameTooLarge { len, max } => {
810 assert!(len > max);
811 assert_eq!(max, MAX_FRAME_BYTES);
812 }
813 other @ (ProtocolError::Io(_) | ProtocolError::Json(_) | ProtocolError::VersionMismatch { .. }) => {
814 panic!("expected FrameTooLarge, got {other:?}");
815 }
816 }
817 }
818
819 #[test]
820 fn oversized_data_row_is_rejected_before_read_allocation() {
821 let mut bytes = Vec::new();
822 bytes.extend_from_slice(&(MAX_FRAME_BYTES.saturating_add(1)).to_be_bytes());
823 let err = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect_err("oversized frame is rejected");
824 match err {
825 ProtocolError::FrameTooLarge { len, max } => {
826 assert_eq!(len, MAX_FRAME_BYTES + 1);
827 assert_eq!(max, MAX_FRAME_BYTES);
828 }
829 other @ (ProtocolError::Io(_) | ProtocolError::Json(_) | ProtocolError::VersionMismatch { .. }) => {
830 panic!("expected FrameTooLarge, got {other:?}");
831 }
832 }
833 }
834
835 #[test]
836 fn larger_cap_accepts_frame_rejected_under_default() {
837 let raised = MAX_FRAME_BYTES.saturating_mul(8);
841 let row = DataRow {
842 stream: "rows".to_owned(),
843 sequence: 0,
844 payload: raw_json(&json!({ "blob": "x".repeat(2 * MAX_FRAME_BYTES as usize) })),
845 };
846 let mut buf = Vec::new();
847 write_frame(&mut buf, Message::DataRow(row.clone()), raised).expect("oversize-under-default frame writes");
848 let frame = read_frame(&mut Cursor::new(buf), raised).expect("oversize-under-default frame reads");
849 assert_eq!(frame.message, Message::DataRow(row));
850 }
851
852 #[test]
853 fn frame_cap_bounds_constants_are_consistent() {
854 const { assert!(MIN_FRAME_BYTES <= MAX_FRAME_BYTES) };
855 const { assert!(MAX_FRAME_BYTES <= MAX_FRAME_BYTES_HARD_CAP) };
856 }
857
858 #[test]
859 fn malformed_frame_payload_is_protocol_error() {
860 let mut bytes = Vec::new();
861 bytes.extend_from_slice(&1_u32.to_be_bytes());
862 bytes.push(b'{');
863 let err = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect_err("malformed JSON is rejected");
864 match err {
865 ProtocolError::Json(_) => {}
866 other @ (ProtocolError::Io(_)
867 | ProtocolError::FrameTooLarge { .. }
868 | ProtocolError::VersionMismatch { .. }) => {
869 panic!("expected Json error, got {other:?}");
870 }
871 }
872 }
873
874 #[test]
875 fn rows_complete_response_round_trips() {
876 let mut bytes = Vec::new();
877 write_frame(
878 &mut bytes,
879 Message::Response(Response::RowsComplete { count: 2 }),
880 MAX_FRAME_BYTES,
881 )
882 .expect("rows complete writes");
883 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("rows complete reads");
884 assert_eq!(frame.message, Message::Response(Response::RowsComplete { count: 2 }));
885 }
886
887 #[test]
888 fn declaration_search_request_and_response_round_trip() {
889 let request = Message::Request(Request::SearchDeclarations {
890 search: LeanWorkerDeclarationSearch {
891 name_fragment: Some("map".to_owned()),
892 name_match: LeanWorkerDeclarationNameMatch::Suffix,
893 kind: Some("theorem".to_owned()),
894 required_constants: vec!["List.map".to_owned()],
895 conclusion_head: Some("Eq".to_owned()),
896 scope_biases: vec![LeanWorkerDeclarationSearchBias {
897 scope: LeanWorkerDeclarationSearchScope::Namespace,
898 prefix: "List".to_owned(),
899 strict: false,
900 weight: 7,
901 }],
902 limit: 3,
903 filter: LeanWorkerDeclarationFilter {
904 include_private: false,
905 include_generated: false,
906 include_internal: false,
907 },
908 include_source: false,
909 },
910 });
911 let mut bytes = Vec::new();
912 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("declaration search request writes");
913 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("declaration search request reads");
914 assert_eq!(frame.message, request);
915
916 let response = Message::Response(Response::DeclarationSearch {
917 result: LeanWorkerDeclarationSearchResult {
918 declarations: vec![LeanWorkerDeclarationSearchRow {
919 name: "List.map_map".to_owned(),
920 kind: "theorem".to_owned(),
921 module: Some("Init.Data.List.Lemmas".to_owned()),
922 source: None,
923 match_reason: "name,kind,required_constants,conclusion_head".to_owned(),
924 score: 127,
925 rank: 1,
926 flags: LeanWorkerDeclarationFlags::default(),
927 }],
928 truncated: true,
929 facts: LeanWorkerDeclarationSearchFacts {
930 declarations_scanned: 100,
931 after_name_filter: 10,
932 after_kind_filter: 8,
933 after_required_constants_filter: 4,
934 after_conclusion_filter: 2,
935 after_scope_filter: 2,
936 source_lookups: 0,
937 broad_pruning: vec![LeanWorkerDeclarationSearchPruning {
938 stage: "limit".to_owned(),
939 reason: "broad_search_limit".to_owned(),
940 count: 1,
941 }],
942 truncated: true,
943 timings: LeanWorkerDeclarationSearchTimings {
944 scan_micros: 1000,
945 rank_micros: 50,
946 source_micros: 0,
947 },
948 },
949 },
950 });
951 let mut bytes = Vec::new();
952 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("declaration search response writes");
953 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("declaration search response reads");
954 assert_eq!(frame.message, response);
955 }
956
957 #[test]
958 fn declaration_inspection_request_and_response_round_trip() {
959 let request = Message::Request(Request::InspectDeclaration {
960 request: LeanWorkerDeclarationInspectionRequest {
961 name: "List.map_map".to_owned(),
962 fields: LeanWorkerDeclarationInspectionFields {
963 source: true,
964 statement: true,
965 docstring: true,
966 attributes: true,
967 flags: true,
968 rendering: LeanWorkerRendering::Pretty,
969 },
970 budgets: LeanWorkerOutputBudgets {
971 per_field_bytes: 128,
972 total_bytes: 512,
973 },
974 },
975 });
976 let mut bytes = Vec::new();
977 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("declaration inspection request writes");
978 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("declaration inspection request reads");
979 assert_eq!(frame.message, request);
980
981 let response = Message::Response(Response::DeclarationInspection {
982 result: LeanWorkerDeclarationInspectionResult::Found {
983 declaration: Box::new(LeanWorkerDeclarationInspection {
984 name: "List.map_map".to_owned(),
985 kind: "theorem".to_owned(),
986 module: Some("Init.Data.List.Lemmas".to_owned()),
987 source: Some(LeanWorkerSourceRange {
988 file: "Init/Data/List/Lemmas.lean".to_owned(),
989 start_line: 1,
990 start_column: 1,
991 end_line: 1,
992 end_column: 10,
993 }),
994 statement: Some(LeanWorkerRenderedInfo {
995 value: "forall ...".to_owned(),
996 truncated: true,
997 }),
998 docstring: Some(LeanWorkerRenderedInfo {
999 value: "doc".to_owned(),
1000 truncated: false,
1001 }),
1002 attributes: vec!["simp".to_owned(), "rw".to_owned()],
1003 proof_search: LeanWorkerDeclarationProofSearchFacts {
1004 is_simp: true,
1005 is_rw_candidate: true,
1006 is_instance: false,
1007 is_class: false,
1008 class_name: None,
1009 },
1010 flags: LeanWorkerDeclarationFlags::default(),
1011 statement_rendering: Some(LeanWorkerRendering::Pretty),
1012 }),
1013 },
1014 });
1015 let mut bytes = Vec::new();
1016 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("declaration inspection response writes");
1017 let frame =
1018 read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("declaration inspection response reads");
1019 assert_eq!(frame.message, response);
1020
1021 let not_found = Message::Response(Response::DeclarationInspection {
1022 result: LeanWorkerDeclarationInspectionResult::NotFound {
1023 name: "Missing.name".to_owned(),
1024 },
1025 });
1026 let mut bytes = Vec::new();
1027 write_frame(&mut bytes, not_found.clone(), MAX_FRAME_BYTES)
1028 .expect("declaration inspection not-found response writes");
1029 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES)
1030 .expect("declaration inspection not-found response reads");
1031 assert_eq!(frame.message, not_found);
1032
1033 let unsupported = Message::Response(Response::DeclarationInspection {
1034 result: LeanWorkerDeclarationInspectionResult::Unsupported,
1035 });
1036 let mut bytes = Vec::new();
1037 write_frame(&mut bytes, unsupported.clone(), MAX_FRAME_BYTES)
1038 .expect("declaration inspection unsupported response writes");
1039 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES)
1040 .expect("declaration inspection unsupported response reads");
1041 assert_eq!(frame.message, unsupported);
1042 }
1043
1044 #[test]
1045 fn module_query_request_and_response_round_trip() {
1046 let request = Message::Request(Request::ProcessModuleQuery {
1047 source: "def x := 1\n#check x\n".to_owned(),
1048 query: LeanWorkerModuleQuery::TypeAt { line: 2, column: 8 },
1049 options: LeanWorkerElabOptions::default(),
1050 });
1051 let mut bytes = Vec::new();
1052 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("module query request writes");
1053 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("module query request reads");
1054 assert_eq!(frame.message, request);
1055
1056 let response = Message::Response(Response::ProcessModuleQuery {
1057 outcome: LeanWorkerModuleQueryOutcome::Ok {
1058 imports: Vec::new(),
1059 result: LeanWorkerModuleQueryResult::TypeAt(LeanWorkerTypeAtResult::Term {
1060 span: LeanWorkerModuleSourceSpan {
1061 start_line: 2,
1062 start_column: 8,
1063 end_line: 2,
1064 end_column: 9,
1065 },
1066 expr: LeanWorkerRenderedInfo {
1067 value: "x".to_owned(),
1068 truncated: false,
1069 },
1070 type_str: LeanWorkerRenderedInfo {
1071 value: "Nat".to_owned(),
1072 truncated: false,
1073 },
1074 expected_type: None,
1075 }),
1076 },
1077 });
1078 let mut bytes = Vec::new();
1079 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("module query response writes");
1080 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("module query response reads");
1081 assert_eq!(frame.message, response);
1082
1083 let unsupported = LeanWorkerModuleQueryOutcome::Unsupported;
1084 let json = serde_json::to_value(&unsupported).expect("unsupported serializes");
1085 assert_eq!(json, json!({ "status": "unsupported" }));
1086
1087 let diagnostics = LeanWorkerModuleQueryResult::Diagnostics(LeanWorkerElabFailure {
1088 diagnostics: Vec::new(),
1089 truncated: false,
1090 });
1091 let json = serde_json::to_value(&diagnostics).expect("diagnostics serializes");
1092 assert_eq!(
1093 json,
1094 json!({
1095 "result": "diagnostics",
1096 "body": {
1097 "diagnostics": [],
1098 "truncated": false
1099 }
1100 })
1101 );
1102 }
1103
1104 #[test]
1105 fn module_query_batch_request_and_response_round_trip() {
1106 let request = Message::Request(Request::ProcessModuleQueryBatch {
1107 source: "theorem t : True := by\n trivial\n".to_owned(),
1108 selectors: vec![
1109 LeanWorkerModuleQuerySelector::Diagnostics {
1110 id: "diagnostics".to_owned(),
1111 },
1112 LeanWorkerModuleQuerySelector::ProofState {
1113 id: "state".to_owned(),
1114 line: 2,
1115 column: 4,
1116 },
1117 ],
1118 budgets: LeanWorkerOutputBudgets::default(),
1119 options: LeanWorkerElabOptions::default(),
1120 });
1121 let mut bytes = Vec::new();
1122 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("module query batch request writes");
1123 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("module query batch request reads");
1124 assert_eq!(frame.message, request);
1125
1126 let response = Message::Response(Response::ProcessModuleQueryBatch {
1127 outcome: LeanWorkerModuleQueryBatchOutcome::Ok {
1128 imports: Vec::new(),
1129 result: LeanWorkerModuleQueryBatchEnvelope {
1130 items: vec![LeanWorkerModuleQueryBatchItem::Ok {
1131 id: "diagnostics".to_owned(),
1132 result: Box::new(LeanWorkerModuleQueryBatchResult::Diagnostics(LeanWorkerElabFailure {
1133 diagnostics: Vec::new(),
1134 truncated: false,
1135 })),
1136 }],
1137 total_truncated: false,
1138 },
1139 facts: LeanWorkerModuleQueryCacheFacts {
1140 cache_status: LeanWorkerModuleCacheStatus::Miss,
1141 timings: LeanWorkerModuleQueryTimings::zero(),
1142 output_bytes: 0,
1143 cache_entry_count: Some(1),
1144 cache_approx_bytes: Some(1024),
1145 },
1146 },
1147 });
1148 let mut bytes = Vec::new();
1149 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("module query batch response writes");
1150 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("module query batch response reads");
1151 assert_eq!(frame.message, response);
1152 }
1153
1154 #[test]
1155 fn proof_attempt_request_and_response_round_trip() {
1156 let span = LeanWorkerModuleSourceSpan {
1157 start_line: 1,
1158 start_column: 22,
1159 end_line: 2,
1160 end_column: 7,
1161 };
1162 let request = Message::Request(Request::AttemptProof {
1163 request: LeanWorkerProofAttemptRequest {
1164 source: "theorem t : True := by\n trivial\n".to_owned(),
1165 edit: LeanWorkerProofEditTarget::Declaration {
1166 name: "t".to_owned(),
1167 position: LeanWorkerProofPositionSelector::Default,
1168 },
1169 candidates: vec![LeanWorkerProofCandidate {
1170 id: "rfl".to_owned(),
1171 text: "by trivial".to_owned(),
1172 }],
1173 budgets: LeanWorkerOutputBudgets::default(),
1174 },
1175 options: LeanWorkerElabOptions::default(),
1176 progress: true,
1177 });
1178 let mut bytes = Vec::new();
1179 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("proof attempt request writes");
1180 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("proof attempt request reads");
1181 assert_eq!(frame.message, request);
1182
1183 let response = Message::Response(Response::ProofAttempt {
1184 result: LeanWorkerProofAttemptResult::Ok {
1185 imports: Vec::new(),
1186 result: LeanWorkerProofAttemptEnvelope {
1187 candidates: vec![LeanWorkerProofAttemptRow {
1188 id: "rfl".to_owned(),
1189 status: LeanWorkerProofAttemptStatus::Closed,
1190 candidate_text: LeanWorkerRenderedInfo {
1191 value: "rfl".to_owned(),
1192 truncated: false,
1193 },
1194 diagnostics: LeanWorkerElabFailure {
1195 diagnostics: Vec::new(),
1196 truncated: false,
1197 },
1198 downstream_diagnostics: LeanWorkerElabFailure {
1199 diagnostics: Vec::new(),
1200 truncated: false,
1201 },
1202 goals: Vec::new(),
1203 declaration: Some(LeanWorkerDeclarationTargetInfo {
1204 short_name: "t".to_owned(),
1205 declaration_name: "t".to_owned(),
1206 namespace_name: String::new(),
1207 declaration_kind: "theorem".to_owned(),
1208 declaration_span: span.clone(),
1209 name_span: span.clone(),
1210 body_span: span,
1211 }),
1212 proof_position: Some(LeanWorkerProofPositionSummary {
1213 index: 0,
1214 tactic: LeanWorkerRenderedInfo {
1215 value: "trivial".to_owned(),
1216 truncated: false,
1217 },
1218 }),
1219 output_truncated: false,
1220 }],
1221 candidate_limit: 8,
1222 candidates_truncated: false,
1223 },
1224 },
1225 });
1226 let mut bytes = Vec::new();
1227 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("proof attempt response writes");
1228 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("proof attempt response reads");
1229 assert_eq!(frame.message, response);
1230 }
1231
1232 #[test]
1233 fn declaration_verification_request_and_response_round_trip() {
1234 let request = Message::Request(Request::VerifyDeclaration {
1235 request: LeanWorkerDeclarationVerificationRequest {
1236 source: "theorem t : True := by\n trivial\n".to_owned(),
1237 target: LeanWorkerDeclarationVerificationTarget::Name { name: "t".to_owned() },
1238 sorry_policy: LeanWorkerSorryPolicy::Deny,
1239 report_axioms: true,
1240 budgets: LeanWorkerOutputBudgets::default(),
1241 },
1242 options: LeanWorkerElabOptions::default(),
1243 progress: false,
1244 });
1245 let mut bytes = Vec::new();
1246 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("verification request writes");
1247 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("verification request reads");
1248 assert_eq!(frame.message, request);
1249
1250 let response = Message::Response(Response::DeclarationVerification {
1251 result: LeanWorkerDeclarationVerificationResult::Ok {
1252 verification_status: LeanWorkerDeclarationVerificationStatus::Accepted,
1253 facts: Box::new(LeanWorkerDeclarationVerificationFacts {
1254 target: None,
1255 diagnostics: LeanWorkerElabFailure {
1256 diagnostics: Vec::new(),
1257 truncated: false,
1258 },
1259 unresolved_goals: Vec::new(),
1260 contains_sorry: false,
1261 contains_admit: false,
1262 contains_sorry_ax: false,
1263 axioms: vec!["propext".to_owned(), "Classical.choice".to_owned()],
1264 axioms_truncated: false,
1265 output_truncated: false,
1266 candidates: Vec::new(),
1267 axioms_available: true,
1268 }),
1269 imports: Vec::new(),
1270 },
1271 });
1272 let mut bytes = Vec::new();
1273 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("verification response writes");
1274 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("verification response reads");
1275 assert_eq!(frame.message, response);
1276 }
1277
1278 #[test]
1279 fn verification_needs_build_and_ambiguous_round_trip() {
1280 let needs_build = Message::Response(Response::DeclarationVerification {
1283 result: LeanWorkerDeclarationVerificationResult::MissingImports {
1284 verification_status: LeanWorkerDeclarationVerificationStatus::NeedsBuild,
1285 facts: Box::new(verification_facts_fixture(Vec::new(), false)),
1286 imports: vec!["Mathlib.Tactic".to_owned()],
1287 missing: vec!["Mathlib.Unbuilt.Dep".to_owned()],
1288 },
1289 });
1290 assert_frame_round_trips(&needs_build);
1291
1292 let ambiguous = Message::Response(Response::DeclarationVerification {
1294 result: LeanWorkerDeclarationVerificationResult::Ok {
1295 verification_status: LeanWorkerDeclarationVerificationStatus::Ambiguous,
1296 facts: Box::new(verification_facts_fixture(
1297 vec![
1298 declaration_target_info_fixture("A.foo"),
1299 declaration_target_info_fixture("B.foo"),
1300 ],
1301 false,
1302 )),
1303 imports: Vec::new(),
1304 },
1305 });
1306 assert_frame_round_trips(&ambiguous);
1307 }
1308
1309 #[test]
1310 fn proof_state_ambiguous_and_needs_build_round_trip() {
1311 let ambiguous = Message::Response(Response::ProcessModuleQueryBatch {
1312 outcome: LeanWorkerModuleQueryBatchOutcome::Ok {
1313 result: LeanWorkerModuleQueryBatchEnvelope {
1314 items: vec![LeanWorkerModuleQueryBatchItem::Ok {
1315 id: "state".to_owned(),
1316 result: Box::new(LeanWorkerModuleQueryBatchResult::ProofState(
1317 LeanWorkerProofStateResult::Ambiguous {
1318 candidates: vec![
1319 declaration_target_info_fixture("A.foo"),
1320 declaration_target_info_fixture("B.foo"),
1321 ],
1322 },
1323 )),
1324 }],
1325 total_truncated: false,
1326 },
1327 imports: Vec::new(),
1328 facts: LeanWorkerModuleQueryCacheFacts::uncached(0),
1329 },
1330 });
1331 assert_frame_round_trips(&ambiguous);
1332
1333 let needs_build = Message::Response(Response::ProcessModuleQueryBatch {
1334 outcome: LeanWorkerModuleQueryBatchOutcome::Ok {
1335 result: LeanWorkerModuleQueryBatchEnvelope {
1336 items: vec![LeanWorkerModuleQueryBatchItem::Ok {
1337 id: "state".to_owned(),
1338 result: Box::new(LeanWorkerModuleQueryBatchResult::ProofState(
1339 LeanWorkerProofStateResult::NeedsBuild {
1340 missing: vec!["Mathlib.Unbuilt.Dep".to_owned()],
1341 },
1342 )),
1343 }],
1344 total_truncated: false,
1345 },
1346 imports: Vec::new(),
1347 facts: LeanWorkerModuleQueryCacheFacts::uncached(0),
1348 },
1349 });
1350 assert_frame_round_trips(&needs_build);
1351 }
1352
1353 #[test]
1354 fn inspection_fields_default_rendering_is_pretty() {
1355 let json = serde_json::json!({
1359 "source": true,
1360 "statement": true,
1361 "docstring": false,
1362 "attributes": false,
1363 "flags": false,
1364 });
1365 let fields: LeanWorkerDeclarationInspectionFields =
1366 serde_json::from_value(json).expect("fields without rendering deserialize");
1367 assert_eq!(fields.rendering, LeanWorkerRendering::Pretty);
1368 }
1369}