docker_wrapper/command/
inspect.rs

1//! Docker inspect command implementation.
2//!
3//! This module provides the `docker inspect` command for getting detailed information
4//! about Docker objects (containers, images, volumes, networks, etc.).
5
6use super::{CommandExecutor, CommandOutput, DockerCommand};
7use crate::error::Result;
8use async_trait::async_trait;
9use serde_json::Value;
10
11/// Docker inspect command builder
12///
13/// # Example
14///
15/// ```no_run
16/// use docker_wrapper::InspectCommand;
17///
18/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
19/// // Inspect a container
20/// let info = InspectCommand::new("my-container")
21///     .run()
22///     .await?;
23///
24/// // Parse as JSON
25/// let json = info.json()?;
26/// println!("Container state: {}", json[0]["State"]["Status"]);
27/// # Ok(())
28/// # }
29/// ```
30#[derive(Debug, Clone)]
31pub struct InspectCommand {
32    /// Objects to inspect (container/image/volume/network IDs or names)
33    objects: Vec<String>,
34    /// Output format
35    format: Option<String>,
36    /// Return size information
37    size: bool,
38    /// Type of object to inspect
39    object_type: Option<String>,
40    /// Command executor
41    pub executor: CommandExecutor,
42}
43
44impl InspectCommand {
45    /// Create a new inspect command for a single object
46    ///
47    /// # Example
48    ///
49    /// ```
50    /// use docker_wrapper::InspectCommand;
51    ///
52    /// let cmd = InspectCommand::new("my-container");
53    /// ```
54    #[must_use]
55    pub fn new(object: impl Into<String>) -> Self {
56        Self {
57            objects: vec![object.into()],
58            format: None,
59            size: false,
60            object_type: None,
61            executor: CommandExecutor::new(),
62        }
63    }
64
65    /// Create a new inspect command for multiple objects
66    ///
67    /// # Example
68    ///
69    /// ```
70    /// use docker_wrapper::InspectCommand;
71    ///
72    /// let cmd = InspectCommand::new_multiple(vec!["container1", "container2"]);
73    /// ```
74    #[must_use]
75    pub fn new_multiple(objects: Vec<impl Into<String>>) -> Self {
76        Self {
77            objects: objects.into_iter().map(Into::into).collect(),
78            format: None,
79            size: false,
80            object_type: None,
81            executor: CommandExecutor::new(),
82        }
83    }
84
85    /// Add another object to inspect
86    #[must_use]
87    pub fn object(mut self, object: impl Into<String>) -> Self {
88        self.objects.push(object.into());
89        self
90    }
91
92    /// Set custom format string (Go template)
93    ///
94    /// # Example
95    ///
96    /// ```
97    /// use docker_wrapper::InspectCommand;
98    ///
99    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
100    /// let cmd = InspectCommand::new("my-container")
101    ///     .format("{{.State.Status}}");
102    /// # Ok(())
103    /// # }
104    /// ```
105    #[must_use]
106    pub fn format(mut self, format: impl Into<String>) -> Self {
107        self.format = Some(format.into());
108        self
109    }
110
111    /// Display total file sizes
112    #[must_use]
113    pub fn size(mut self) -> Self {
114        self.size = true;
115        self
116    }
117
118    /// Specify the type of object to inspect
119    ///
120    /// Valid types: container, image, volume, network, plugin, node, service, etc.
121    #[must_use]
122    pub fn object_type(mut self, typ: impl Into<String>) -> Self {
123        self.object_type = Some(typ.into());
124        self
125    }
126
127    /// Execute the inspect command
128    ///
129    /// # Errors
130    /// Returns an error if:
131    /// - The Docker daemon is not running
132    /// - The specified object doesn't exist
133    /// - The object type is invalid
134    pub async fn run(&self) -> Result<InspectOutput> {
135        let output = self.execute().await?;
136        Ok(InspectOutput { output })
137    }
138
139    /// Gets the command executor
140    #[must_use]
141    pub fn get_executor(&self) -> &CommandExecutor {
142        &self.executor
143    }
144
145    /// Gets the command executor mutably
146    pub fn get_executor_mut(&mut self) -> &mut CommandExecutor {
147        &mut self.executor
148    }
149
150    /// Builds the command arguments for Docker inspect
151    #[must_use]
152    pub fn build_command_args(&self) -> Vec<String> {
153        let mut args = vec!["inspect".to_string()];
154
155        if let Some(ref format) = self.format {
156            args.push("--format".to_string());
157            args.push(format.clone());
158        }
159
160        if self.size {
161            args.push("--size".to_string());
162        }
163
164        if let Some(ref typ) = self.object_type {
165            args.push("--type".to_string());
166            args.push(typ.clone());
167        }
168
169        // Add object names/IDs
170        args.extend(self.objects.clone());
171
172        // Add any additional raw arguments
173        args.extend(self.executor.raw_args.clone());
174
175        args
176    }
177}
178
179#[async_trait]
180impl DockerCommand for InspectCommand {
181    type Output = CommandOutput;
182
183    fn get_executor(&self) -> &CommandExecutor {
184        &self.executor
185    }
186
187    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
188        &mut self.executor
189    }
190
191    fn build_command_args(&self) -> Vec<String> {
192        self.build_command_args()
193    }
194
195    async fn execute(&self) -> Result<Self::Output> {
196        let args = self.build_command_args();
197        self.execute_command(args).await
198    }
199}
200
201/// Result from the inspect command
202#[derive(Debug, Clone)]
203pub struct InspectOutput {
204    /// Raw command output
205    pub output: CommandOutput,
206}
207
208impl InspectOutput {
209    /// Parse the output as JSON
210    ///
211    /// # Errors
212    /// Returns an error if the output is not valid JSON
213    ///
214    /// # Example
215    ///
216    /// ```no_run
217    /// # use docker_wrapper::InspectCommand;
218    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
219    /// let info = InspectCommand::new("my-container").run().await?;
220    /// let json = info.json()?;
221    /// println!("Container ID: {}", json[0]["Id"]);
222    /// # Ok(())
223    /// # }
224    /// ```
225    pub fn json(&self) -> Result<Value> {
226        serde_json::from_str(&self.output.stdout)
227            .map_err(|e| crate::error::Error::parse_error(format!("Failed to parse JSON: {e}")))
228    }
229
230    /// Get raw stdout
231    #[must_use]
232    pub fn stdout(&self) -> &str {
233        &self.output.stdout
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_inspect_single_object() {
243        let cmd = InspectCommand::new("test-container");
244        let args = cmd.build_command_args();
245        assert_eq!(args, vec!["inspect", "test-container"]);
246    }
247
248    #[test]
249    fn test_inspect_multiple_objects() {
250        let cmd = InspectCommand::new_multiple(vec!["container1", "image1", "volume1"]);
251        let args = cmd.build_command_args();
252        assert_eq!(args, vec!["inspect", "container1", "image1", "volume1"]);
253    }
254
255    #[test]
256    fn test_inspect_with_format() {
257        let cmd = InspectCommand::new("test-container").format("{{.State.Status}}");
258        let args = cmd.build_command_args();
259        assert_eq!(
260            args,
261            vec!["inspect", "--format", "{{.State.Status}}", "test-container"]
262        );
263    }
264
265    #[test]
266    fn test_inspect_with_size() {
267        let cmd = InspectCommand::new("test-image").size();
268        let args = cmd.build_command_args();
269        assert_eq!(args, vec!["inspect", "--size", "test-image"]);
270    }
271
272    #[test]
273    fn test_inspect_with_type() {
274        let cmd = InspectCommand::new("my-network").object_type("network");
275        let args = cmd.build_command_args();
276        assert_eq!(args, vec!["inspect", "--type", "network", "my-network"]);
277    }
278
279    #[test]
280    fn test_inspect_all_options() {
281        let cmd = InspectCommand::new("test-container")
282            .format("{{json .}}")
283            .size()
284            .object_type("container");
285        let args = cmd.build_command_args();
286        assert_eq!(
287            args,
288            vec![
289                "inspect",
290                "--format",
291                "{{json .}}",
292                "--size",
293                "--type",
294                "container",
295                "test-container"
296            ]
297        );
298    }
299}