use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SkillMessageType {
Execute = 1,
Cancel = 2,
StdinData = 3,
Resize = 4,
Signal = 5,
StartSession = 6,
Shutdown = 7,
Ack = 128,
StdoutChunk = 129,
StderrChunk = 130,
Progress = 131,
Completed = 132,
Error = 133,
SessionStarted = 134,
}
impl SkillMessageType {
pub fn from_u32(v: u32) -> Option<Self> {
match v {
1 => Some(Self::Execute),
2 => Some(Self::Cancel),
3 => Some(Self::StdinData),
4 => Some(Self::Resize),
5 => Some(Self::Signal),
6 => Some(Self::StartSession),
7 => Some(Self::Shutdown),
128 => Some(Self::Ack),
129 => Some(Self::StdoutChunk),
130 => Some(Self::StderrChunk),
131 => Some(Self::Progress),
132 => Some(Self::Completed),
133 => Some(Self::Error),
134 => Some(Self::SessionStarted),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct SkillMessage {
pub msg_type: SkillMessageType,
pub payload: Vec<u8>,
}
impl SkillMessage {
pub fn new(msg_type: SkillMessageType, payload: Vec<u8>) -> Self {
Self { msg_type, payload }
}
pub fn deserialize_payload<T: for<'de> Deserialize<'de>>(&self) -> Option<T> {
serde_json::from_slice(&self.payload).ok()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ExecutePayload {
pub execution_id: String,
pub command_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub args: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub working_directory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<HashMap<String, String>>,
#[serde(default)]
pub timeout_ms: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct StartSessionPayload {
pub session_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub shell: Option<String>,
#[serde(default = "default_rows")]
pub rows: i32,
#[serde(default = "default_cols")]
pub cols: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub working_directory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<HashMap<String, String>>,
}
fn default_rows() -> i32 {
24
}
fn default_cols() -> i32 {
80
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct StdinDataPayload {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(with = "base64_bytes")]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ResizePayload {
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
pub rows: i32,
pub cols: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SignalPayload {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub signal_number: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AckPayload {
pub id: String,
pub accepted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct DataChunkPayload {
pub id: String,
pub seq: u64,
#[serde(with = "base64_bytes")]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ProgressPayload {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub percent: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stage: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct CompletedPayload {
pub id: String,
pub exit_code: i32,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub finished_at_unix: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ErrorPayload {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub code: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SessionStartedPayload {
pub id: String,
pub status: String,
}
mod base64_bytes {
use base64::prelude::*;
use serde::{self, Deserialize, Deserializer, Serializer};
pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&BASE64_STANDARD.encode(bytes))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
BASE64_STANDARD.decode(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
#[path = "message_tests.rs"]
mod tests;