Skip to main content

rustferry_apple/
error.rs

1use std::io;
2
3use camino::Utf8PathBuf;
4use thiserror::Error;
5
6/// Failure while discovering, generating, building, or validating an Apple artifact.
7#[derive(Debug, Error)]
8pub enum AppleError {
9    /// Project icon or splash asset validation failed.
10    #[error(transparent)]
11    Assets(#[from] rustferry_core::AssetError),
12    /// Deterministic platform asset generation or cache validation failed.
13    #[error(transparent)]
14    AssetPipeline(#[from] rustferry_codegen::AssetPipelineError),
15    /// A request value cannot safely enter a generated project or process argument.
16    #[error("invalid iOS build request: {0}")]
17    InvalidRequest(String),
18    /// `ferry.toml` failed semantic validation.
19    #[error("invalid RustFerry configuration: {0}")]
20    InvalidConfig(String),
21    /// iOS builds were requested on a non-macOS host.
22    #[error(
23        "iOS builds require macOS; detected host `{host}`. Run this build on a Mac with full Xcode installed"
24    )]
25    UnsupportedHost {
26        /// Detected host operating system.
27        host: String,
28    },
29    /// A required Apple or Rust executable was not found.
30    #[error("Apple build tool `{tool}` was not found; searched: {searched:?}. {fix}")]
31    ToolMissing {
32        /// Human-readable executable name.
33        tool: String,
34        /// Candidate paths inspected.
35        searched: Vec<Utf8PathBuf>,
36        /// Concrete remediation.
37        fix: String,
38    },
39    /// The selected developer directory is not a full Xcode installation.
40    #[error("full Xcode was not found; developer directory: {developer_dir:?}. {fix}")]
41    XcodeMissing {
42        /// Selected developer directory, when one was found.
43        developer_dir: Option<Utf8PathBuf>,
44        /// Concrete remediation.
45        fix: String,
46    },
47    /// Xcode does not expose an iPhone Simulator SDK.
48    #[error("an iPhone Simulator SDK was not found through xcrun. {fix}")]
49    SimulatorSdkMissing {
50        /// Concrete remediation.
51        fix: String,
52    },
53    /// The required Rust target is absent.
54    #[error(
55        "Rust target `{target}` is not installed. Run `rustup target add {target}`, then `cargo ferry doctor`"
56    )]
57    RustTargetMissing {
58        /// Missing Rust target triple.
59        target: String,
60    },
61    /// An external command could not be started.
62    #[error("could not start `{program}` during {stage}: {source}")]
63    CommandSpawn {
64        /// Build or validation stage.
65        stage: String,
66        /// Executable path.
67        program: Utf8PathBuf,
68        /// Operating-system error.
69        #[source]
70        source: io::Error,
71    },
72    /// An external command exceeded its deadline.
73    #[error("`{program}` timed out during {stage}; log: {log:?}")]
74    CommandTimedOut {
75        /// Build or validation stage.
76        stage: String,
77        /// Executable path.
78        program: Utf8PathBuf,
79        /// Redacted diagnostic log, when requested.
80        log: Option<Utf8PathBuf>,
81    },
82    /// An external command exceeded the per-stream output memory limit.
83    #[error(
84        "`{program}` produced more than {limit_bytes} bytes on {stream} during {stage}; its process tree was stopped; log: {log:?}"
85    )]
86    ProcessOutputTooLarge {
87        /// Build or validation stage.
88        stage: String,
89        /// Executable path.
90        program: Utf8PathBuf,
91        /// Stream that crossed the limit.
92        stream: String,
93        /// Maximum retained bytes for each stream.
94        limit_bytes: usize,
95        /// Diagnostic log containing only the retained prefixes.
96        log: Option<Utf8PathBuf>,
97    },
98    /// Cancellation interrupted an external build tool.
99    #[error("`{program}` was interrupted during {stage}")]
100    CommandInterrupted {
101        /// Build or validation stage.
102        stage: String,
103        /// Executable path.
104        program: Utf8PathBuf,
105    },
106    /// An external command returned a non-zero status.
107    #[error("`{program}` failed during {stage} with status {status}; {summary}; log: {log:?}")]
108    CommandFailed {
109        /// Build or validation stage.
110        stage: String,
111        /// Executable path.
112        program: Utf8PathBuf,
113        /// Exit code or signal description.
114        status: String,
115        /// Short stderr/stdout excerpt.
116        summary: String,
117        /// Redacted diagnostic log, when requested.
118        log: Option<Box<Utf8PathBuf>>,
119    },
120    /// A filesystem operation failed.
121    #[error("could not {operation} `{path}`: {source}")]
122    Io {
123        /// Operation being attempted.
124        operation: &'static str,
125        /// Affected path.
126        path: Utf8PathBuf,
127        /// Operating-system error.
128        #[source]
129        source: io::Error,
130    },
131    /// A generated path escaped or traversed a protected boundary.
132    #[error("unsafe generated path `{path}`: {reason}")]
133    UnsafeGeneratedPath {
134        /// Rejected path.
135        path: Utf8PathBuf,
136        /// Exact invariant that failed.
137        reason: String,
138    },
139    /// Independent artifact validation rejected an app or extension bundle.
140    #[error("iOS artifact validation failed for `{path}`: {reason}")]
141    InvalidArtifact {
142        /// Rejected artifact path.
143        path: Utf8PathBuf,
144        /// Exact failed invariant.
145        reason: String,
146    },
147    /// A platform path cannot be represented by this crate's UTF-8 API.
148    #[error("path is not valid UTF-8: {0:?}")]
149    NonUtf8Path(std::path::PathBuf),
150}
151
152pub(crate) fn io_error(
153    operation: &'static str,
154    path: impl Into<Utf8PathBuf>,
155    source: io::Error,
156) -> AppleError {
157    AppleError::Io {
158        operation,
159        path: path.into(),
160        source,
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn command_failure_stays_compact_and_preserves_its_diagnostic() {
170        assert!(std::mem::size_of::<AppleError>() < 128);
171
172        let error = AppleError::CommandFailed {
173            stage: "archive application".to_owned(),
174            program: Utf8PathBuf::from("xcodebuild"),
175            status: "65".to_owned(),
176            summary: "archive failed".to_owned(),
177            log: Some(Box::new(Utf8PathBuf::from("archive.log"))),
178        };
179        assert_eq!(
180            error.to_string(),
181            "`xcodebuild` failed during archive application with status 65; archive failed; log: Some(\"archive.log\")"
182        );
183    }
184}