Mountain/ApplicationState/DTO/
TerminalStateDTO.rs

1//! # TerminalStateDTO
2//!
3//! # RESPONSIBILITY
4//! - Data transfer object for integrated terminal state
5//! - Serializable format for gRPC/IPC transmission
6//! - Used by Mountain to track terminal lifecycle and configuration
7//! - Contains runtime handles for PTY I/O
8//!
9//! # FIELDS
10//! - Identifier: Unique terminal identifier
11//! - Name: Terminal display name
12//! - OSProcessIdentifier: OS process ID
13//! - ShellPath: Shell executable path
14//! - ShellArguments: Shell launch arguments
15//! - CurrentWorkingDirectory: Working directory path
16//! - EnvironmentVariables: Environment variable map
17//! - IsPTY: PTY mode flag
18//! - PTYInputTransmitter: PTY input channel sender
19//! - ReaderTaskHandle: Output reader task handle
20//! - ProcessWaitHandle: Process wait task handle
21use std::{collections::HashMap, path::PathBuf, sync::Arc};
22
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25use tokio::{
26	sync::{Mutex as TokioMutex, mpsc as TokioMPSC},
27	task::JoinHandle,
28};
29
30/// Maximum terminal name length
31const MAX_TERMINAL_NAME_LENGTH:usize = 128;
32
33/// Maximum shell path length
34const MAX_SHELL_PATH_LENGTH:usize = 1024;
35
36/// Maximum number of shell arguments
37const MAX_SHELL_ARGUMENTS:usize = 100;
38
39/// Maximum argument string length
40const MAX_ARGUMENT_LENGTH:usize = 4096;
41
42/// Maximum number of environment variables
43const MAX_ENV_VARS:usize = 1000;
44
45/// Holds the complete state and runtime resources for a single pseudo-terminal
46/// (PTY) instance. This includes configuration, process identifiers, and
47/// handles for I/O tasks.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct TerminalStateDTO {
50	// --- Identifiers ---
51	/// Unique terminal identifier
52	pub Identifier:u64,
53
54	/// Terminal display name
55	#[serde(skip_serializing_if = "String::is_empty")]
56	pub Name:String,
57
58	/// OS process identifier (if running)
59	pub OSProcessIdentifier:Option<u32>,
60
61	// --- Configuration ---
62	/// Shell executable path
63	#[serde(skip_serializing_if = "String::is_empty")]
64	pub ShellPath:String,
65
66	/// Shell launch arguments
67	#[serde(skip_serializing_if = "Vec::is_empty")]
68	pub ShellArguments:Vec<String>,
69
70	/// Current working directory
71	pub CurrentWorkingDirectory:Option<PathBuf>,
72
73	/// Environment variables map
74	#[serde(skip_serializing_if = "Option::is_none")]
75	pub EnvironmentVariables:Option<HashMap<String, Option<String>>>,
76
77	/// Whether this is a PTY terminal
78	pub IsPTY:bool,
79
80	// --- Runtime Handles ---
81	/// Channel for sending input to PTY
82	#[serde(skip)]
83	pub PTYInputTransmitter:Option<TokioMPSC::Sender<String>>,
84
85	/// Handle for output reader task
86	#[serde(skip)]
87	pub ReaderTaskHandle:Option<Arc<TokioMutex<Option<JoinHandle<()>>>>>,
88
89	/// Handle for process wait task
90	#[serde(skip)]
91	pub ProcessWaitHandle:Option<Arc<TokioMutex<Option<JoinHandle<()>>>>>,
92}
93
94impl TerminalStateDTO {
95	/// Creates a new `TerminalStateDTO` by parsing terminal options from a
96	/// `serde_json::Value` with validation.
97	///
98	/// # Arguments
99	/// * `Identifier` - Unique terminal identifier
100	/// * `Name` - Terminal display name
101	/// * `OptionsValue` - Terminal options JSON
102	/// * `DefaultShellPath` - Default shell if not specified
103	///
104	/// # Returns
105	/// Result containing the DTO or validation error
106	pub fn Create(Identifier:u64, Name:String, OptionsValue:&Value, DefaultShellPath:String) -> Result<Self, String> {
107		// Validate name length
108		if Name.len() > MAX_TERMINAL_NAME_LENGTH {
109			return Err(format!(
110				"Terminal name exceeds maximum length of {} bytes",
111				MAX_TERMINAL_NAME_LENGTH
112			));
113		}
114
115		let ShellPath = OptionsValue
116			.get("shellPath")
117			.and_then(Value::as_str)
118			.unwrap_or(&DefaultShellPath)
119			.to_string();
120
121		// Validate shell path length
122		if ShellPath.len() > MAX_SHELL_PATH_LENGTH {
123			return Err(format!("Shell path exceeds maximum length of {} bytes", MAX_SHELL_PATH_LENGTH));
124		}
125
126		let ShellArguments = match OptionsValue.get("shellArgs") {
127			Some(Value::Array(Array)) => {
128				let Args:Vec<String> = Array.iter().filter_map(Value::as_str).map(String::from).collect();
129
130				// Validate argument count
131				if Args.len() > MAX_SHELL_ARGUMENTS {
132					return Err(format!("Shell arguments exceed maximum count of {}", MAX_SHELL_ARGUMENTS));
133				}
134
135				// Validate individual argument lengths
136				for Arg in &Args {
137					if Arg.len() > MAX_ARGUMENT_LENGTH {
138						return Err(format!(
139							"Shell argument exceeds maximum length of {} bytes",
140							MAX_ARGUMENT_LENGTH
141						));
142					}
143				}
144
145				Args
146			},
147
148			_ => Vec::new(),
149		};
150
151		let CWD = OptionsValue.get("cwd").and_then(Value::as_str).map(PathBuf::from);
152
153		// A more complete implementation would parse the `env` object.
154		let EnvVars = None;
155
156		Ok(Self {
157			Identifier,
158			Name,
159			ShellPath,
160			ShellArguments,
161			CurrentWorkingDirectory:CWD,
162			EnvironmentVariables:EnvVars,
163			OSProcessIdentifier:None,
164			IsPTY:true,
165			PTYInputTransmitter:None,
166			ReaderTaskHandle:None,
167			ProcessWaitHandle:None,
168		})
169	}
170
171	/// Checks if the terminal process is currently running.
172	pub fn IsRunning(&self) -> bool { self.OSProcessIdentifier.is_some() }
173
174	/// Checks if the terminal has an active PTY input channel.
175	pub fn HasInputChannel(&self) -> bool { self.PTYInputTransmitter.is_some() }
176
177	/// Returns the working directory as a string, or default if not set.
178	pub fn GetWorkingDirectory(&self) -> String {
179		self.CurrentWorkingDirectory
180			.as_ref()
181			.and_then(|Path| Path.to_str())
182			.unwrap_or("")
183			.to_string()
184	}
185
186	/// Clears the runtime handles (useful when terminating terminal).
187	pub fn ClearHandles(&mut self) {
188		self.PTYInputTransmitter = None;
189		self.ReaderTaskHandle = None;
190		self.ProcessWaitHandle = None;
191	}
192}