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