1use header::Header;
4use metadata::Metadata;
5use serde_derive::Deserialize;
6use serde_json::Value;
7use std::collections::HashMap;
8
9#[derive(Deserialize, Debug, PartialEq, Eq)]
11pub struct HelpLink {
12 pub text: String,
14 pub url: String,
16}
17
18#[derive(Debug)]
29pub enum Response {
30 Shell(ShellResponse),
32 IoPub(IoPubResponse),
34}
35
36#[derive(Debug)]
38pub enum ShellResponse {
39 KernelInfo {
41 header: Header,
43 parent_header: Header,
45 metadata: Metadata,
47 content: KernelInfoContent,
49 },
50 Execute {
52 header: Header,
54 parent_header: Header,
56 metadata: Metadata,
58 content: ExecuteReplyContent,
60 },
61 Inspect {
63 header: Header,
65 parent_header: Header,
67 metadata: Metadata,
69 content: InspectContent,
71 },
72 Complete {
74 header: Header,
76 parent_header: Header,
78 metadata: Metadata,
80 content: CompleteContent,
82 },
83 History {
85 header: Header,
87 parent_header: Header,
89 metadata: Metadata,
91 content: HistoryContent,
93 },
94 IsComplete {
96 header: Header,
98 parent_header: Header,
100 metadata: Metadata,
102 content: IsCompleteStatus,
104 },
105 Shutdown {
107 header: Header,
109 parent_header: Header,
111 metadata: Metadata,
113 content: ShutdownContent,
115 },
116 CommInfo {
118 header: Header,
120 parent_header: Header,
122 metadata: Metadata,
124 content: CommInfoContent,
126 },
127}
128
129#[derive(Debug)]
131pub enum IoPubResponse {
132 Status {
134 header: Header,
136 parent_header: Header,
138 metadata: Metadata,
140 content: StatusContent,
142 },
143 ExecuteInput {
145 header: Header,
147 parent_header: Header,
149 metadata: Metadata,
151 content: ExecuteInputContent,
153 },
154 Stream {
156 header: Header,
158 parent_header: Header,
160 metadata: Metadata,
162 content: StreamContent,
164 },
165 ExecuteResult {
167 header: Header,
169 parent_header: Header,
171 metadata: Metadata,
173 content: ExecuteResultContent,
175 },
176 Error {
178 header: Header,
180 parent_header: Header,
182 metadata: Metadata,
184 content: ErrorContent,
186 },
187 ClearOutput {
189 header: Header,
191 parent_header: Header,
193 metadata: Metadata,
195 content: ClearOutputContent,
197 },
198}
199
200#[derive(Deserialize, Debug)]
202pub struct KernelInfoContent {
203 pub status: Status,
205 pub protocol_version: String,
207 pub implementation: String,
209 pub implementation_version: String,
211 pub language_info: LanguageInfo,
213 pub banner: String,
215 pub help_links: Vec<HelpLink>,
217}
218
219#[derive(Deserialize, Debug)]
221pub struct LanguageInfo {
222 pub name: String,
224 pub version: String,
226 pub mimetype: String,
228 pub file_extension: String,
230 pub pygments_lexer: String,
232 pub codemirror_mode: Value,
234 pub nbconvert_exporter: String,
237}
238
239#[derive(Deserialize, Debug)]
241pub struct ExecuteReplyContent {
242 pub status: Status,
244 pub execution_count: i64,
246 pub payload: Option<Vec<HashMap<String, Value>>>,
249 pub user_expressions: Option<HashMap<String, Value>>,
251 pub ename: Option<String>,
254 pub evalue: Option<String>,
256 pub traceback: Option<Vec<String>>,
258}
259
260#[derive(Deserialize, Debug)]
262pub struct StatusContent {
263 pub execution_state: ExecutionState,
265}
266
267#[derive(Deserialize, Debug)]
269pub struct ExecuteInputContent {
270 pub code: String,
272 pub execution_count: i64,
274}
275
276#[derive(Deserialize, Debug)]
278pub struct InspectContent {
279 pub status: Status,
281 pub found: bool,
283 pub data: HashMap<String, Value>,
285 pub metadata: HashMap<String, Value>,
287}
288
289#[derive(Deserialize, Debug)]
291pub struct StreamContent {
292 pub name: StreamType,
294 pub text: String,
296}
297
298#[derive(Deserialize, Debug)]
300pub struct ErrorContent {
301 pub ename: String,
303 pub evalue: String,
305 pub traceback: Vec<String>,
307}
308
309#[derive(Deserialize, Debug)]
311pub struct CompleteContent {
312 pub status: Status,
314 pub matches: Vec<String>,
316 pub cursor_start: u64,
318 pub cursor_end: u64,
320 pub metadata: HashMap<String, Value>,
322}
323
324#[derive(Deserialize, Debug)]
326pub struct HistoryContent {
327 pub status: Status,
329 pub history: Vec<Value>,
331}
332
333#[derive(Deserialize, Debug)]
335pub struct ShutdownContent {
336 pub status: Status,
338 pub restart: bool,
340}
341
342#[derive(Deserialize, Debug)]
344pub struct CommInfoContent {
345 pub status: Status,
347 pub comms: HashMap<String, HashMap<String, String>>,
349}
350
351#[derive(Deserialize, Debug)]
353pub struct ExecuteResultContent {
354 pub execution_count: i64,
356 pub data: HashMap<String, String>,
358 pub metadata: Value,
360}
361
362#[derive(Deserialize, Debug)]
364pub struct ClearOutputContent {
365 pub wait: bool,
367}
368
369#[derive(Deserialize, Debug, PartialEq)]
371#[serde(rename_all = "lowercase")]
372pub enum ExecutionState {
373 Busy,
375 Idle,
377 Starting,
379}
380
381#[derive(Deserialize, Debug, PartialEq)]
383#[serde(rename_all = "lowercase")]
384pub enum IsCompleteStatus {
385 Complete,
387 Incomplete(String),
389 Invalid,
391 Unknown,
393}
394
395#[derive(Deserialize, Debug, PartialEq)]
397#[serde(rename_all = "lowercase")]
398#[allow(missing_docs)]
399pub enum StreamType {
400 Stdout,
401 Stderr,
402}
403
404#[derive(Deserialize, Debug, PartialEq)]
406#[serde(rename_all = "lowercase")]
407#[allow(missing_docs)]
408pub enum Status {
409 Ok,
410 Error,
411 Abort,
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::test_helpers::*;
418 use crate::wire::WireMessage;
419
420 #[test]
421 fn test_kernel_info_message_parsing() {
422 let auth = FakeAuth::create();
423 let raw_response = vec![
424 "<IDS|MSG>".to_string().into_bytes(),
425 expected_signature().into_bytes(),
426 r#"{
428 "date": "",
429 "msg_id": "",
430 "username": "",
431 "session": "",
432 "msg_type": "kernel_info_reply",
433 "version": ""
434 }"#.to_string()
435 .into_bytes(),
436 r#"{
438 "date": "",
439 "msg_id": "",
440 "username": "",
441 "session": "",
442 "msg_type": "kernel_info_request",
443 "version": ""
444 }"#.to_string()
445 .into_bytes(),
446 r#"{}"#.to_string().into_bytes(),
448 r#"{
450 "banner": "banner",
451 "implementation": "implementation",
452 "implementation_version": "implementation_version",
453 "protocol_version": "protocol_version",
454 "status": "ok",
455 "language_info": {
456 "name": "python",
457 "version": "3.7.0",
458 "mimetype": "text/x-python",
459 "file_extension": ".py",
460 "pygments_lexer": "ipython3",
461 "codemirror_mode": {
462 "name": "ipython",
463 "version": 3
464 },
465 "nbconvert_exporter": "python"
466 },
467 "help_links": [{"text": "text", "url": "url"}]
468 }"#.to_string()
469 .into_bytes(),
470 ];
471 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
472 let response = msg.into_response().unwrap();
473 match response {
474 Response::Shell(ShellResponse::KernelInfo {
475 header,
476 parent_header: _parent_header,
477 metadata: _metadata,
478 content,
479 }) => {
480 assert_eq!(header.msg_type, "kernel_info_reply");
482
483 assert_eq!(content.banner, "banner");
485 assert_eq!(content.implementation, "implementation");
486 assert_eq!(content.implementation_version, "implementation_version");
487 assert_eq!(content.protocol_version, "protocol_version");
488 assert_eq!(content.status, Status::Ok);
489 assert_eq!(
490 content.help_links,
491 vec![HelpLink {
492 text: "text".to_string(),
493 url: "url".to_string(),
494 }]
495 );
496 assert_eq!(content.language_info.name, "python");
497 }
498 _ => unreachable!("Incorrect response type, should be KernelInfo"),
499 }
500 }
501
502 #[test]
503 fn test_execute_request_message_parsing() {
504 let auth = FakeAuth::create();
505 let raw_response = vec![
506 "<IDS|MSG>".to_string().into_bytes(),
507 expected_signature().into_bytes(),
508 r#"{
510 "date": "",
511 "msg_id": "",
512 "username": "",
513 "session": "",
514 "msg_type": "execute_reply",
515 "version": ""
516 }"#.to_string()
517 .into_bytes(),
518 r#"{
520 "date": "",
521 "msg_id": "",
522 "username": "",
523 "session": "",
524 "msg_type": "execute_request",
525 "version": ""
526 }"#.to_string()
527 .into_bytes(),
528 r#"{}"#.to_string().into_bytes(),
530 r#"{
532 "status": "ok",
533 "execution_count": 4
534 }"#.to_string()
535 .into_bytes(),
536 ];
537 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
538 let response = msg.into_response().unwrap();
539 match response {
540 Response::Shell(ShellResponse::Execute {
541 header,
542 parent_header: _parent_header,
543 metadata: _metadata,
544 content,
545 }) => {
546 assert_eq!(header.msg_type, "execute_reply");
548
549 assert_eq!(content.status, Status::Ok);
551 assert_eq!(content.execution_count, 4);
552 }
553 _ => unreachable!("Incorrect response type, should be KernelInfo"),
554 }
555 }
556
557 #[test]
558 fn test_status_message_parsing() {
559 let auth = FakeAuth::create();
560 let raw_response = vec![
561 "<IDS|MSG>".to_string().into_bytes(),
562 expected_signature().into_bytes(),
563 r#"{
565 "date": "",
566 "msg_id": "",
567 "username": "",
568 "session": "",
569 "msg_type": "status",
570 "version": ""
571 }"#.to_string()
572 .into_bytes(),
573 r#"{
575 "date": "",
576 "msg_id": "",
577 "username": "",
578 "session": "",
579 "msg_type": "execute_request",
580 "version": ""
581 }"#.to_string()
582 .into_bytes(),
583 r#"{}"#.to_string().into_bytes(),
585 r#"{
587 "status": "ok",
588 "execution_state": "busy"
589 }"#.to_string()
590 .into_bytes(),
591 ];
592 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
593 let response = msg.into_response().unwrap();
594 match response {
595 Response::IoPub(IoPubResponse::Status {
596 header,
597 parent_header: _parent_header,
598 metadata: _metadata,
599 content,
600 }) => {
601 assert_eq!(header.msg_type, "status");
603
604 assert_eq!(content.execution_state, ExecutionState::Busy);
606 }
607 _ => unreachable!("Incorrect response type, should be Status"),
608 }
609 }
610
611 #[test]
612 fn test_execute_input_parsing() {
613 let auth = FakeAuth::create();
614 let raw_response = vec![
615 "<IDS|MSG>".to_string().into_bytes(),
616 expected_signature().into_bytes(),
617 r#"{
619 "date": "",
620 "msg_id": "",
621 "username": "",
622 "session": "",
623 "msg_type": "execute_input",
624 "version": ""
625 }"#.to_string()
626 .into_bytes(),
627 r#"{
629 "date": "",
630 "msg_id": "",
631 "username": "",
632 "session": "",
633 "msg_type": "",
634 "version": ""
635 }"#.to_string()
636 .into_bytes(),
637 r#"{}"#.to_string().into_bytes(),
639 r#"{
641 "status": "ok",
642 "code": "a = 10",
643 "execution_count": 4
644 }"#.to_string()
645 .into_bytes(),
646 ];
647 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
648 let response = msg.into_response().unwrap();
649 match response {
650 Response::IoPub(IoPubResponse::ExecuteInput {
651 header,
652 parent_header: _parent_header,
653 metadata: _metadata,
654 content,
655 }) => {
656 assert_eq!(header.msg_type, "execute_input");
658
659 assert_eq!(content.code, "a = 10");
661 assert_eq!(content.execution_count, 4);
662 }
663 _ => unreachable!("Incorrect response type, should be Status"),
664 }
665 }
666
667 #[test]
668 fn test_stream_parsing() {
669 let auth = FakeAuth::create();
670 let raw_response = vec![
671 "<IDS|MSG>".to_string().into_bytes(),
672 expected_signature().into_bytes(),
673 r#"{
675 "date": "",
676 "msg_id": "",
677 "username": "",
678 "session": "",
679 "msg_type": "stream",
680 "version": ""
681 }"#.to_string()
682 .into_bytes(),
683 r#"{
685 "date": "",
686 "msg_id": "",
687 "username": "",
688 "session": "",
689 "msg_type": "",
690 "version": ""
691 }"#.to_string()
692 .into_bytes(),
693 r#"{}"#.to_string().into_bytes(),
695 r#"{
697 "status": "ok",
698 "name": "stdout",
699 "text": "10"
700 }"#.to_string()
701 .into_bytes(),
702 ];
703 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
704 let response = msg.into_response().unwrap();
705 match response {
706 Response::IoPub(IoPubResponse::Stream {
707 header,
708 parent_header: _parent_header,
709 metadata: _metadata,
710 content,
711 }) => {
712 assert_eq!(header.msg_type, "stream");
714
715 assert_eq!(content.name, StreamType::Stdout);
717 assert_eq!(content.text, "10");
718 }
719 _ => unreachable!("Incorrect response type, should be Stream"),
720 }
721 }
722
723 #[test]
724 fn test_is_complete_message_parsing() {
725 let auth = FakeAuth::create();
726 let raw_response = vec![
727 "<IDS|MSG>".to_string().into_bytes(),
728 expected_signature().into_bytes(),
729 r#"{
731 "date": "",
732 "msg_id": "",
733 "username": "",
734 "session": "",
735 "msg_type": "is_complete_reply",
736 "version": ""
737 }"#.to_string()
738 .into_bytes(),
739 r#"{
741 "date": "",
742 "msg_id": "",
743 "username": "",
744 "session": "",
745 "msg_type": "is_complete_request",
746 "version": ""
747 }"#.to_string()
748 .into_bytes(),
749 r#"{}"#.to_string().into_bytes(),
751 r#"{
753 "status": "complete"
754 }"#.to_string()
755 .into_bytes(),
756 ];
757 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
758 let response = msg.into_response().unwrap();
759 match response {
760 Response::Shell(ShellResponse::IsComplete {
761 header,
762 parent_header: _parent_header,
763 metadata: _metadata,
764 content,
765 }) => {
766 assert_eq!(header.msg_type, "is_complete_reply");
768
769 assert_eq!(content, IsCompleteStatus::Complete);
771 }
772 _ => unreachable!("Incorrect response type, should be IsComplete"),
773 }
774 }
775
776 #[test]
777 fn test_is_complete_message_parsing_with_incomplete_reply() {
778 let auth = FakeAuth::create();
779 let raw_response = vec![
780 "<IDS|MSG>".to_string().into_bytes(),
781 expected_signature().into_bytes(),
782 r#"{
784 "date": "",
785 "msg_id": "",
786 "username": "",
787 "session": "",
788 "msg_type": "is_complete_reply",
789 "version": ""
790 }"#.to_string()
791 .into_bytes(),
792 r#"{
794 "date": "",
795 "msg_id": "",
796 "username": "",
797 "session": "",
798 "msg_type": "is_complete_request",
799 "version": ""
800 }"#.to_string()
801 .into_bytes(),
802 r#"{}"#.to_string().into_bytes(),
804 r#"{
806 "status": "incomplete",
807 "indent": " "
808 }"#.to_string()
809 .into_bytes(),
810 ];
811 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
812 let response = msg.into_response().unwrap();
813 match response {
814 Response::Shell(ShellResponse::IsComplete {
815 header,
816 parent_header: _parent_header,
817 metadata: _metadata,
818 content,
819 }) => {
820 assert_eq!(header.msg_type, "is_complete_reply");
822
823 assert_eq!(content, IsCompleteStatus::Incomplete(" ".to_string()));
825 }
826 _ => unreachable!("Incorrect response type, should be IsComplete"),
827 }
828 }
829
830 #[test]
831 fn test_shutdown_message_parsing() {
832 let auth = FakeAuth::create();
833 let raw_response = vec![
834 "<IDS|MSG>".to_string().into_bytes(),
835 expected_signature().into_bytes(),
836 r#"{
838 "date": "",
839 "msg_id": "",
840 "username": "",
841 "session": "",
842 "msg_type": "shutdown_reply",
843 "version": ""
844 }"#.to_string()
845 .into_bytes(),
846 r#"{
848 "date": "",
849 "msg_id": "",
850 "username": "",
851 "session": "",
852 "msg_type": "kernel_info_request",
853 "version": ""
854 }"#.to_string()
855 .into_bytes(),
856 r#"{}"#.to_string().into_bytes(),
858 r#"{
860 "status": "ok",
861 "restart": false
862 }"#.to_string()
863 .into_bytes(),
864 ];
865 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
866 let response = msg.into_response().unwrap();
867 match response {
868 Response::Shell(ShellResponse::Shutdown {
869 header,
870 parent_header: _parent_header,
871 metadata: _metadata,
872 content,
873 }) => {
874 assert_eq!(header.msg_type, "shutdown_reply");
876
877 assert_eq!(content.restart, false);
879 }
880 _ => unreachable!("Incorrect response type, should be KernelInfo"),
881 }
882 }
883
884 #[test]
885 fn test_comm_info_message_parsing() {
886 let auth = FakeAuth::create();
887 let raw_response = vec![
888 "<IDS|MSG>".to_string().into_bytes(),
889 expected_signature().into_bytes(),
890 r#"{
892 "date": "",
893 "msg_id": "",
894 "username": "",
895 "session": "",
896 "msg_type": "comm_info_reply",
897 "version": ""
898 }"#.to_string()
899 .into_bytes(),
900 r#"{
902 "date": "",
903 "msg_id": "",
904 "username": "",
905 "session": "",
906 "msg_type": "comm_info_request",
907 "version": ""
908 }"#.to_string()
909 .into_bytes(),
910 r#"{}"#.to_string().into_bytes(),
912 r#"{
914 "status": "ok",
915 "comms": {
916 "u-u-i-d": {
917 "target_name": "foobar"
918 }
919 }
920 }"#.to_string()
921 .into_bytes(),
922 ];
923 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
924 let response = msg.into_response().unwrap();
925 match response {
926 Response::Shell(ShellResponse::CommInfo {
927 header,
928 parent_header: _parent_header,
929 metadata: _metadata,
930 content,
931 }) => {
932 assert_eq!(header.msg_type, "comm_info_reply");
934
935 assert_eq!(content.comms["u-u-i-d"]["target_name"], "foobar");
937 }
938 _ => unreachable!("Incorrect response type, should be CommInfo"),
939 }
940 }
941
942 #[test]
943 fn test_execute_result_message_parsing() {
944 use serde_json::json;
945
946 let auth = FakeAuth::create();
947 let raw_response = vec![
948 "<IDS|MSG>".to_string().into_bytes(),
949 expected_signature().into_bytes(),
950 r#"{
952 "date": "",
953 "msg_id": "",
954 "username": "",
955 "session": "",
956 "msg_type": "execute_result",
957 "version": ""
958 }"#.to_string()
959 .into_bytes(),
960 r#"{
962 "date": "",
963 "msg_id": "",
964 "username": "",
965 "session": "",
966 "msg_type": "execute_request",
967 "version": ""
968 }"#.to_string()
969 .into_bytes(),
970 r#"{}"#.to_string().into_bytes(),
972 r#"{
974 "data": {
975 "text/plain": "10"
976 },
977 "metadata": {},
978 "execution_count": 46
979 }"#.to_string()
980 .into_bytes(),
981 ];
982 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
983 let response = msg.into_response().unwrap();
984 match response {
985 Response::IoPub(IoPubResponse::ExecuteResult {
986 header,
987 parent_header: _parent_header,
988 metadata: _metadata,
989 content,
990 }) => {
991 assert_eq!(header.msg_type, "execute_result");
993
994 assert_eq!(content.data["text/plain"], "10");
996 assert_eq!(content.metadata, json!({}));
997 assert_eq!(content.execution_count, 46);
998 }
999 _ => unreachable!("Incorrect response type, should be ExecuteResult"),
1000 }
1001 }
1002
1003 #[test]
1004 fn test_clear_output_message_parsing() {
1005 let auth = FakeAuth::create();
1006 let raw_response = vec![
1007 "<IDS|MSG>".to_string().into_bytes(),
1008 expected_signature().into_bytes(),
1009 r#"{
1011 "date": "",
1012 "msg_id": "",
1013 "username": "",
1014 "session": "",
1015 "msg_type": "clear_output",
1016 "version": ""
1017 }"#.to_string()
1018 .into_bytes(),
1019 r#"{
1021 "date": "",
1022 "msg_id": "",
1023 "username": "",
1024 "session": "",
1025 "msg_type": "execute_request",
1026 "version": ""
1027 }"#.to_string()
1028 .into_bytes(),
1029 r#"{}"#.to_string().into_bytes(),
1031 r#"{
1033 "wait": false
1034 }"#.to_string()
1035 .into_bytes(),
1036 ];
1037 let msg = WireMessage::from_raw_response(raw_response, auth.clone()).unwrap();
1038 let response = msg.into_response().unwrap();
1039 match response {
1040 Response::IoPub(IoPubResponse::ClearOutput {
1041 header,
1042 parent_header: _parent_header,
1043 metadata: _metadata,
1044 content,
1045 }) => {
1046 assert_eq!(header.msg_type, "clear_output");
1048
1049 assert_eq!(content.wait, false);
1051 }
1052 _ => unreachable!("Incorrect response type, should be ClearOutput"),
1053 }
1054 }
1055}