Skip to main content

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}