Skip to main content

buntrail_protocol/
terminal.rs

1//! Explicit terminal boundary values; output is raw bytes, never a screen snapshot.
2use crate::services::invalid;
3use base64::{Engine as _, engine::general_purpose::STANDARD as B64};
4use buntrail_core::contracts as c;
5use serde::{Deserialize, Serialize};
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum EndIntent {
9    ExplicitClose,
10    FinalWorkspaceClose,
11    RetentionExpiry,
12}
13impl EndIntent {
14    pub fn core(&self) -> c::TerminalEndIntent {
15        match self {
16            Self::ExplicitClose => c::TerminalEndIntent::ExplicitClose,
17            Self::FinalWorkspaceClose => c::TerminalEndIntent::FinalWorkspaceClose,
18            Self::RetentionExpiry => c::TerminalEndIntent::RetentionExpiry,
19        }
20    }
21    pub fn from_core(v: c::TerminalEndIntent) -> Self {
22        match v {
23            c::TerminalEndIntent::ExplicitClose => Self::ExplicitClose,
24            c::TerminalEndIntent::FinalWorkspaceClose => Self::FinalWorkspaceClose,
25            c::TerminalEndIntent::RetentionExpiry => Self::RetentionExpiry,
26        }
27    }
28}
29#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
31pub enum Event {
32    Output {
33        sequence: u64,
34        bytes_base64: String,
35    },
36    Gap {
37        first: u64,
38        last: u64,
39    },
40    ProcessExit {
41        #[serde(deserialize_with = "crate::types::required_option")]
42        code: Option<i32>,
43    },
44    Ended {
45        intent: EndIntent,
46    },
47    Disconnected {
48        expires_at_unix_seconds: u64,
49    },
50    Reattached,
51    Expired,
52    RuntimeLost,
53}
54impl Event {
55    pub fn from_core(e: c::TerminalEvent) -> Self {
56        match e {
57            c::TerminalEvent::Output { sequence, bytes } => Self::Output {
58                sequence,
59                bytes_base64: B64.encode(bytes),
60            },
61            c::TerminalEvent::Gap { first, last } => Self::Gap { first, last },
62            c::TerminalEvent::ProcessExit { code } => Self::ProcessExit { code },
63            c::TerminalEvent::Ended { intent } => Self::Ended {
64                intent: EndIntent::from_core(intent),
65            },
66            c::TerminalEvent::Disconnected {
67                expires_at_unix_seconds,
68            } => Self::Disconnected {
69                expires_at_unix_seconds,
70            },
71            c::TerminalEvent::Reattached => Self::Reattached,
72            c::TerminalEvent::Expired => Self::Expired,
73            c::TerminalEvent::RuntimeLost => Self::RuntimeLost,
74        }
75    }
76    pub fn core(self) -> Result<c::TerminalEvent, c::CoreError> {
77        Ok(match self {
78            Self::Output {
79                sequence,
80                bytes_base64,
81            } => c::TerminalEvent::Output {
82                sequence,
83                bytes: B64.decode(bytes_base64).map_err(|_| invalid())?,
84            },
85            Self::Gap { first, last } => c::TerminalEvent::Gap { first, last },
86            Self::ProcessExit { code } => c::TerminalEvent::ProcessExit { code },
87            Self::Ended { intent } => c::TerminalEvent::Ended {
88                intent: intent.core(),
89            },
90            Self::Disconnected {
91                expires_at_unix_seconds,
92            } => c::TerminalEvent::Disconnected {
93                expires_at_unix_seconds,
94            },
95            Self::Reattached => c::TerminalEvent::Reattached,
96            Self::Expired => c::TerminalEvent::Expired,
97            Self::RuntimeLost => c::TerminalEvent::RuntimeLost,
98        })
99    }
100}
101pub fn size(columns: u16, rows: u16) -> Result<c::TerminalSize, c::CoreError> {
102    Ok(c::TerminalSize {
103        columns: columns.try_into().map_err(|_| invalid())?,
104        rows: rows.try_into().map_err(|_| invalid())?,
105    })
106}