frame_cli/error.rs
1//! Typed failures for every `frame` verb — each one loud, each one carrying
2//! its fix.
3
4use std::io;
5use std::net::SocketAddr;
6use std::path::PathBuf;
7use std::time::Duration;
8
9use thiserror::Error;
10
11use crate::doctor::MissingTools;
12use crate::scaffold::NewError;
13
14/// Typed refusal or failure from any `frame` verb.
15#[derive(Debug, Error)]
16pub enum CliError {
17 /// Scaffold generation refused or failed.
18 #[error(transparent)]
19 New(#[from] NewError),
20
21 /// Initialising the new application's git repository failed (a git command
22 /// that ran refused — never a missing-git or already-in-a-repo condition,
23 /// which `frame new` reports and continues past).
24 #[error(transparent)]
25 Git(#[from] crate::scaffold::GitError),
26
27 /// A verb's prerequisite tools are missing (with install links).
28 #[error(transparent)]
29 MissingTools(#[from] MissingTools),
30
31 /// The real-browser proof has no browser to drive.
32 #[error("{reason}")]
33 MissingChrome {
34 /// The refusal, naming the probed path and the install link.
35 reason: String,
36 },
37
38 /// The verb was run outside a Frame application.
39 #[error(
40 "not inside a Frame application: no frame.toml in `{start}` or any parent directory; \
41 run this inside an application created by `frame new` (or create one: `frame new my_app`)"
42 )]
43 NotAnApplication {
44 /// Directory the search started from.
45 start: PathBuf,
46 },
47
48 /// The application's `frame.toml` failed to load or validate.
49 #[error("`{path}` is not a valid frame.toml")]
50 Config {
51 /// The config file that failed.
52 path: PathBuf,
53 /// The host's own typed load failure (boxed: the host error is
54 /// large, and `Result` should stay lean on the Ok path).
55 #[source]
56 source: Box<frame_host::HostError>,
57 },
58
59 /// `frame host` failed to boot or serve.
60 #[error("frame host failed")]
61 Host {
62 /// The host's own typed failure (boxed as above).
63 #[source]
64 source: Box<frame_host::HostError>,
65 },
66
67 /// A build or verification command exited unsuccessfully.
68 #[error("`{command}` failed with {status}; its output above names the failure")]
69 CommandFailed {
70 /// The command line that failed.
71 command: String,
72 /// Its exit status.
73 status: String,
74 },
75
76 /// A command could not be spawned at all (present prerequisites were
77 /// already checked, so this is an unexpected process failure, not a
78 /// missing tool).
79 #[error("`{command}` could not be started: {source}")]
80 CommandSpawn {
81 /// The command line that failed to start.
82 command: String,
83 /// The underlying process error.
84 #[source]
85 source: io::Error,
86 },
87
88 /// Page sources changed but the toolchain that recompiles them is not
89 /// installed, and this verb never installs anything.
90 #[error(
91 "{changed} page source file(s) changed since the page was last compiled, and the page \
92 toolchain is not installed; run `npm --prefix page install` once (network), or run \
93 `frame check` or `frame test`, which install it for you"
94 )]
95 PageToolchainMissing {
96 /// How many sources are newer than their compiled modules.
97 changed: usize,
98 },
99
100 /// A named filesystem operation failed.
101 #[error("{operation} `{path}` failed: {source}")]
102 Io {
103 /// Operation that failed.
104 operation: &'static str,
105 /// File or directory involved.
106 path: PathBuf,
107 /// Underlying filesystem error.
108 #[source]
109 source: io::Error,
110 },
111
112 /// The current directory could not be read.
113 #[error("reading the current directory failed: {source}")]
114 CurrentDir {
115 /// Underlying filesystem error.
116 #[source]
117 source: io::Error,
118 },
119
120 /// The async runtime backing `frame run` could not be built.
121 #[error("starting frame's process supervisor failed: {source}")]
122 Runtime {
123 /// Underlying runtime construction error.
124 #[source]
125 source: io::Error,
126 },
127
128 /// An EXPLICITLY-stated frame.toml socket is already taken — named before
129 /// the host even tries to bind it, with the frame.toml key that holds it
130 /// and the coherence rule for changing it. Only stated addresses are
131 /// probed; a portless config (no `[frame].bind`, no `[bus]`) has nothing to
132 /// probe because the host prefers-and-walks / OS-assigns those ports.
133 #[error(
134 "cannot start the {role}: the port named by frame.toml `{key}` ({addr}) is unavailable \
135 ({source}). {coherence}"
136 )]
137 PortUnavailable {
138 /// Which socket role is blocked (page server / bus / bus health /
139 /// websocket).
140 role: &'static str,
141 /// The frame.toml key that holds this socket's address.
142 key: &'static str,
143 /// The address the host would have bound.
144 addr: SocketAddr,
145 /// The coherence rule to honour when moving this port.
146 coherence: &'static str,
147 /// The underlying bind failure (address-in-use, permission, …).
148 #[source]
149 source: io::Error,
150 },
151
152 /// The booted application never started accepting connections.
153 #[error(
154 "the application booted but `{bind}` never accepted a connection within {deadline:?}; \
155 its output above names what went wrong"
156 )]
157 NeverReady {
158 /// The configured page-server address.
159 bind: String,
160 /// How long frame waited.
161 deadline: Duration,
162 },
163
164 /// The application ignored a stop request past the teardown deadline.
165 #[error(
166 "the application did not exit within {deadline:?} of SIGTERM and was killed; \
167 this is a bug in the application's shutdown path"
168 )]
169 TeardownTimeout {
170 /// How long frame waited before killing it.
171 deadline: Duration,
172 },
173
174 /// One or more scopes of a multi-part verdict failed.
175 #[error("{verb}: FAIL ({})", failed.join(", "))]
176 VerdictFailed {
177 /// The verb whose verdict failed.
178 verb: &'static str,
179 /// The scopes that failed.
180 failed: Vec<String>,
181 },
182
183 /// The doctor found problems.
184 #[error(
185 "frame doctor found {problems} problem(s); each line above carries its fix and install link"
186 )]
187 DoctorProblems {
188 /// How many problems the walkthrough found.
189 problems: usize,
190 },
191}