1use std::process::ExitCode;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, thiserror::Error)]
10#[non_exhaustive]
11pub enum Error {
12 #[error("missing FMP API key; set FMP_API_KEY or pass --api-key")]
14 MissingApiKey,
15
16 #[error("invalid FMP base URL: {0}")]
18 InvalidBaseUrl(String),
19
20 #[error("missing required CLI argument: {0}")]
22 MissingArgument(&'static str),
23
24 #[error("FMP API request failed with HTTP {status}: {message}")]
26 Api {
27 status: u16,
29 message: String,
31 },
32
33 #[error("FMP API request was rate limited with HTTP {status}: {message}")]
35 RateLimited {
36 status: u16,
38 message: String,
40 },
41
42 #[error(
44 "empty result for symbol {symbol} from {endpoint}; try `fmp-agent search {search_query}` to verify the symbol, or rerun without --strict-empty to keep the raw FMP response"
45 )]
46 EmptyResult {
47 symbol: String,
49 search_query: String,
51 endpoint: &'static str,
53 },
54
55 #[error("endpoint {endpoint} is unavailable: {message}")]
57 EndpointUnavailable {
58 endpoint: &'static str,
60 message: &'static str,
62 },
63
64 #[error("HTTP request failed: {0}")]
66 Http(reqwest::Error),
67
68 #[error("failed to render JSON output: {0}")]
70 Json(#[from] serde_json::Error),
71}
72
73impl From<reqwest::Error> for Error {
74 fn from(error: reqwest::Error) -> Self {
75 Self::Http(error.without_url())
76 }
77}
78
79impl Error {
80 #[must_use]
82 pub const fn kind(&self) -> &'static str {
83 match self {
84 Self::MissingApiKey => "missing_api_key",
85 Self::InvalidBaseUrl(_) => "invalid_base_url",
86 Self::MissingArgument(_) => "missing_argument",
87 Self::Api { .. } => "api_error",
88 Self::RateLimited { .. } => "rate_limited",
89 Self::EmptyResult { .. } => "empty_result",
90 Self::EndpointUnavailable { .. } => "endpoint_unavailable",
91 Self::Http(_) => "http_error",
92 Self::Json(_) => "json_error",
93 }
94 }
95
96 #[must_use]
106 pub fn exit_code(&self) -> ExitCode {
107 match self {
108 Self::MissingArgument(_) => ExitCode::from(2),
109 Self::MissingApiKey | Self::InvalidBaseUrl(_) => ExitCode::from(3),
110 Self::Http(_) => ExitCode::from(4),
111 Self::Api { .. } | Self::RateLimited { .. } | Self::EndpointUnavailable { .. } => {
112 ExitCode::from(5)
113 }
114 Self::Json(_) => ExitCode::from(6),
115 Self::EmptyResult { .. } => ExitCode::from(7),
116 }
117 }
118}