apple_clis/xcrun/simctl/list/
output.rs1use crate::prelude::*;
2use crate::shared::identifiers::IPadVariant;
3use crate::shared::identifiers::{DeviceName, IPhoneVariant, RuntimeIdentifier};
4
5#[derive(Debug, Serialize)]
6#[non_exhaustive]
7#[must_use = include_doc!(must_use_cmd_output)]
8pub enum ListOutput {
9 SuccessJson(ListJson),
11
12 #[doc = include_doc!(cmd_success)]
13 SuccessUnImplemented { stdout: String },
14
15 #[doc = include_doc!(cmd_error)]
16 ErrorUnImplemented { stderr: String },
17}
18
19impl ListOutput {
20 pub fn success(&self) -> Result<&ListJson> {
23 match self {
24 ListOutput::SuccessJson(output) => Ok(output),
25 _ => Err(Error::output_errored(self)),
26 }
27 }
28
29 pub fn unwrap_success(&self) -> &ListJson {
30 match self {
31 ListOutput::SuccessJson(output) => output,
32 _ => {
33 error!(?self, "Tried to unwrap a non-successful ListOutput");
34 panic!("Tried to unwrap a non-successful ListOutput")
35 }
36 }
37 }
38
39 pub fn get_success(&self) -> Option<&ListJson> {
40 match self {
41 ListOutput::SuccessJson(output) => Some(output),
42 _ => None,
43 }
44 }
45
46 #[cfg(feature = "cli")]
49 pub fn get_success_reported(&self) -> std::result::Result<&ListJson, color_eyre::Report> {
50 match self {
51 Self::SuccessJson(output) => Ok(output),
52 Self::ErrorUnImplemented { stderr } => Err(eyre!(
53 "xcrun simctl list output didn't exist successfully: {:?}",
54 stderr
55 )),
56 Self::SuccessUnImplemented { stdout } => Err(eyre!(
57 "xcrun simctl list output didn't produce valid output: {:?}",
58 stdout
59 )),
60 }
61 }
62}
63
64impl CommandNomParsable for ListOutput {
65 fn success_unimplemented(stdout: String) -> Self {
66 Self::SuccessUnImplemented { stdout }
67 }
68
69 fn error_unimplemented(stderr: String) -> Self {
70 Self::ErrorUnImplemented { stderr }
71 }
72
73 fn success_from_str(input: &str) -> Self {
74 match serde_json::from_str(input) {
75 Ok(output) => Self::SuccessJson(output),
76 Err(e) => {
77 error!(?e, "Failed to parse JSON");
78 Self::success_unimplemented(input.to_owned())
79 }
80 }
81 }
82}
83
84impl PublicCommandOutput for ListOutput {
85 type PrimarySuccess = ListJson;
86
87 fn success(&self) -> Result<&Self::PrimarySuccess> {
88 match self {
89 ListOutput::SuccessJson(output) => Ok(output),
90 _ => Err(Error::output_errored(self)),
91 }
92 }
93}
94
95impl ListJson {
96 pub fn devices(&self) -> impl Iterator<Item = &ListDevice> + '_ {
98 self.devices.values().flatten()
99 }
100}
101
102#[derive(Deserialize, Debug, Serialize)]
103pub struct ListJson {
104 devices: HashMap<RuntimeIdentifier, Vec<ListDevice>>,
105}
106
107#[extension_traits::extension(pub trait ListDevicesExt)]
110impl<'src, T> T
111where
112 T: Iterator<Item = &'src ListDevice>,
113{
114 fn names(self) -> impl Iterator<Item = &'src DeviceName> {
116 self.map(|device| &device.name)
117 }
118
119 fn a_device(mut self) -> Option<&'src ListDevice> {
120 self.next()
121 }
122}
123
124#[extension_traits::extension(pub trait ListDeviceNamesExt)]
125impl<'src, T> T
126where
127 T: Iterator<Item = &'src DeviceName>,
128{
129 fn an_ipad(self) -> Option<&'src IPadVariant> {
132 self.ipads().max()
133 }
134
135 fn an_iphone(self) -> Option<&'src IPhoneVariant> {
138 self.iphones().max()
139 }
140
141 fn iphones(self) -> impl Iterator<Item = &'src IPhoneVariant> {
142 self.filter_map(|names| match names {
143 DeviceName::IPhone(ref variant) => Some(variant),
144 _ => None,
145 })
146 }
147
148 fn ipads(self) -> impl Iterator<Item = &'src IPadVariant> {
149 self.filter_map(|names| match names {
150 DeviceName::IPad(ref variant) => Some(variant),
151 _ => None,
152 })
153 }
154}
155
156#[derive(Deserialize, Serialize, Debug, Clone)]
157#[serde(rename_all(deserialize = "camelCase"))]
158pub struct ListDevice {
159 pub availability_error: Option<String>,
160 pub data_path: Utf8PathBuf,
161 pub log_path: Utf8PathBuf,
162 pub udid: String,
163 pub is_available: bool,
164 pub device_type_identifier: String,
165 pub state: State,
166
167 pub name: DeviceName,
168}
169
170#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
171pub enum State {
172 Shutdown,
173 Booted,
174}
175
176impl State {
177 pub fn ready(&self) -> bool {
178 matches!(self, State::Booted)
179 }
180}
181
182impl ListDevice {
183 pub fn ready(&self) -> bool {
184 self.state.ready() && self.is_available
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use tracing::debug;
191
192 use super::*;
193
194 #[test]
195 fn test_simctl_list() {
196 let example = include_str!(concat!(
197 env!("CARGO_MANIFEST_DIR"),
198 "/tests/simctl-list-full.json"
199 ));
200 let output = serde_json::from_str::<ListJson>(example);
201 match output {
202 Ok(output) => {
203 debug!("Output: {:?}", output);
204 }
205 Err(e) => {
206 panic!("Error parsing: {:?}", e)
207 }
208 }
209 }
210}