pvoutput_client/types/
error.rs

1//! Error types and methods.
2
3use std::fmt::{Debug, Display};
4
5use displaydoc::Display;
6use jiff::civil::{Date, Time};
7
8pub type Result<T, Endpoint> = std::result::Result<T, ClientError<Endpoint>>;
9
10/// Errors that can occur when using the [`Client`](crate::Client).
11///
12/// ## Endpoint-Specific Errors
13/// A [`ClientError`] may contain an endpoint-specific error.
14///
15/// Most endpoints return a [`ClientError`] with [`NoEndpointSpecificError`], indicating that no endpoint-specific
16/// errors can result from using the endpoint.
17///
18/// Some endpoints have their own error types, such as [`AddOutputError`], which is returned by the
19/// [`crate::Client::add_outputs`] endpoint.
20#[derive(Debug, Display)]
21#[ignore_extra_doc_attributes]
22pub enum ClientError<Endpoint: Debug + Display> {
23	/// The provided system ID is invalid.
24	InvalidSystemId,
25
26	/// The provided API key is invalid.
27	InvalidApiKey,
28
29	/// The provided API key has not been enabled in the Settings.
30	DisabledApiKey,
31
32	/// The provided API key is a read-only key and cannot access the requested service which updates system data.
33	/// Use the standard key to update system data.
34	ReadOnlyKey,
35
36	/// The provided system ID and API key combination is invalid,
37	/// or the API key is missing, invalid, or inactive.
38	#[displaydoc("Bad API key.")]
39	BadApiKey,
40
41	/// The maximum number of requests per hour has been reached for the API key.
42	///
43	/// The rate limit will refresh at the time specified by the returned
44	/// [`RateLimit`](crate::types::response::RateLimit) struct.
45	RateLimitExceeded,
46
47	/// [Donation mode](https://pvoutput.org/help/donations.html) is required for this request.
48	DonationModeRequired,
49
50	/// Another system ID was requested by an account without
51	/// [donation mode](https://pvoutput.org/help/donations.html) enabled.
52	InaccessibleSystemId,
53
54	/// The requested data were not found.
55	NotFound,
56
57	/// An unrecognised unsuccessful [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) was
58	/// returned.
59	#[displaydoc("An unrecognised unsuccessful HTTP status code of {0} was returned with this message: ({1:?})")]
60	UnrecognisedStatusCode(reqwest::StatusCode, Option<String>),
61
62	/// An error occurred while making the request.
63	#[displaydoc("An error occurred while making the request: {0}")]
64	Reqwest(reqwest::Error),
65
66	/// Deserialisation error.
67	#[displaydoc("Deserialisation error: {0}")]
68	Parse(csv::Error),
69
70	/// Error writing CSV data to a buffer.
71	#[displaydoc("Error writing CSV data to a buffer: {0}")]
72	Write(Box<csv::IntoInnerError<csv::Writer<Vec<u8>>>>),
73
74	/// Failed to parse [rate limit](crate::types::response::RateLimit) information from the response's
75	/// [HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers).
76	#[displaydoc("Failed to parse rate limit information from the response's HTTP headers: {0}")]
77	RateLimitParse(RateLimitError),
78
79	/// Missing field in API response.
80	MissingField,
81
82	/// An endpoint-specific error occurred.
83	#[displaydoc("An endpoint-specific error occurred: {0}")]
84	EndpointSpecific(Endpoint),
85}
86
87impl<Endpoint> From<reqwest::Error> for ClientError<Endpoint>
88where
89	Endpoint: Debug + Display,
90{
91	fn from(error: reqwest::Error) -> Self { Self::Reqwest(error) }
92}
93
94impl<Endpoint> From<csv::Error> for ClientError<Endpoint>
95where
96	Endpoint: Debug + Display,
97{
98	fn from(error: csv::Error) -> Self { Self::Parse(error) }
99}
100
101impl<Endpoint> From<RateLimitError> for ClientError<Endpoint>
102where
103	Endpoint: Debug + Display,
104{
105	fn from(error: RateLimitError) -> Self { Self::RateLimitParse(error) }
106}
107
108impl<Endpoint> From<csv::IntoInnerError<csv::Writer<Vec<u8>>>> for ClientError<Endpoint>
109where
110	Endpoint: Debug + Display,
111{
112	fn from(error: csv::IntoInnerError<csv::Writer<Vec<u8>>>) -> Self { Self::Write(Box::new(error)) }
113}
114
115impl From<AddOutputError> for ClientError<AddOutputError> {
116	fn from(error: AddOutputError) -> Self { Self::EndpointSpecific(error) }
117}
118
119impl From<AddStatusError> for ClientError<AddStatusError> {
120	fn from(error: AddStatusError) -> Self { Self::EndpointSpecific(error) }
121}
122
123impl From<PostSystemError> for ClientError<PostSystemError> {
124	fn from(error: PostSystemError) -> Self { Self::EndpointSpecific(error) }
125}
126
127impl<Endpoint> std::error::Error for ClientError<Endpoint> where Endpoint: Debug + Display {}
128
129/// No endpoint-specific error can occur for this endpoint.
130#[derive(Debug, Display, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
131pub enum NoEndpointSpecificError {}
132
133/// The provided date or time was invalid.
134#[derive(Debug, Display, PartialEq, Eq, Clone)]
135pub enum DateTimeError {
136	/// An invalid date was provided.
137	#[displaydoc("An invalid date was provided: {0}")]
138	InvalidDate(String),
139
140	/// An invalid time was provided.
141	#[displaydoc("An invalid time was provided: {0}")]
142	InvalidTime(String),
143
144	/// The date is too old.
145	#[displaydoc("The date \"{0}\" is too old.")]
146	DateTooOld(Date),
147
148	/// The date is too new.
149	#[displaydoc("The date \"{0}\" is too new.")]
150	DateTooNew(Date),
151
152	/// No date or time was provided to an endpoint that requires a date or time.
153	MissingDateOrTime,
154}
155
156impl std::error::Error for DateTimeError {}
157
158/// Errors that can occur when adding output data to a system using [`crate::Client::add_outputs`].
159#[derive(Debug, Display, PartialEq, Clone)]
160pub enum AddOutputError {
161	/// No data were provided.
162	NoData,
163
164	/// The provided date or time was invalid.
165	InvalidDateTime(DateTimeError),
166
167	/// The generation value is too high for the system size.
168	#[displaydoc("The generation value {generation} is too high for the system size {system_size} on {date}.")]
169	GenerationTooHigh {
170		generation: f32,
171		system_size: u32,
172		date: Date,
173	},
174
175	/// The export value is too high for the system size.
176	#[displaydoc("The export value {export} is too high for the system size {system_size} on {date}.")]
177	ExportTooHigh { export: f32, system_size: u32, date: Date },
178
179	/// The export value exceeds the generation value by more than 15%.
180	#[displaydoc("The export value {export} exceeds the generation value {generation} by more than 15%.")]
181	ExportExceedsGeneration { export: f32, generation: f32 },
182
183	/// The consumption value is above 999,999,999 Wh for the provided date.
184	#[displaydoc("The consumption value {0} is above 999,999,999 Wh on {1}.")]
185	ConsumptionTooHigh(f32, Date),
186
187	/// The peak power on the provided date is ≥50% greater than the system size.
188	#[displaydoc("The peak power {peak_power} is ≥50% greater than the system size {system_size} on {date}.")]
189	PeakPowerTooHigh {
190		peak_power: f32,
191		system_size: u32,
192		date: Date,
193	},
194
195	/// A maximum temperature was specified for this date without a minimum temperature.
196	#[displaydoc("A maximum temperature was specified on {0} without a minimum temperature.")]
197	MaxTemperatureWithoutMin(Date),
198
199	/// A minimum temperature was specified for this date without a maximum temperature.
200	#[displaydoc("A minimum temperature was specified on {0} without a maximum temperature.")]
201	MinTemperatureWithoutMax(Date),
202}
203
204impl std::error::Error for AddOutputError {}
205
206/// Errors that can occur when adding statuses to a system using [`crate::Client::add_status`],
207/// [`crate::Client::add_batch_status`], or [`crate::Client::add_batch_status_net`].
208#[derive(Debug, Display, PartialEq, Eq, Clone)]
209pub enum AddStatusError {
210	/// No data were provided.
211	NoData,
212
213	/// The provided date or time was invalid.
214	InvalidDateTime(DateTimeError),
215
216	/// None of energy generation, power generation, energy consumption, or power consumption were provided.
217	NoEnergyData,
218
219	/// Systems cannot generate power during hours without sunlight!
220	MoonPowered,
221
222	/// Both the cumulative and net flags were set.
223	BothCumulativeAndNet,
224
225	/// The energy generation value is too high for the time period.
226	#[displaydoc("The energy generation value {0} is too high for the time period {1}.")]
227	EnergyTooHighForTime(u32, Time),
228
229	/// The power generation value is too high for this system.
230	#[displaydoc("The power generation value {power} is too high for this system with size {system_size}.")]
231	ExcessivePowerGeneration { power: u32, system_size: u32 },
232
233	/// The energy generation value is too high for this system.
234	#[displaydoc("The energy generation value {energy} is too high for this system with size {system_size}.")]
235	ExcessiveEnergyGeneration { energy: u32, system_size: u32 },
236
237	/// Required fields were missing.
238	MissingFields,
239
240	/// Invalid data format.
241	#[displaydoc("Invalid data format: {0}.")]
242	InvalidDataFormat(String),
243}
244
245impl std::error::Error for AddStatusError {}
246
247/// Errors that can occur when updating a system using [`crate::Client::post_system`].
248#[derive(Debug, Display, Copy, Clone, PartialEq, Eq)]
249#[ignore_extra_doc_attributes]
250pub enum PostSystemError {
251	/// System name is too long.
252	///
253	/// System names can be at most **thirty** characters long.
254	SystemNameTooLong,
255
256	/// Missing label for extended data.
257	#[displaydoc("Missing label for extended data variable v{0}.")]
258	MissingExtendedDataLabel(u8),
259
260	/// Missing unit for extended data.
261	#[displaydoc("Missing unit for extended data variable v{0}.")]
262	MissingExtendedDataUnit(u8),
263
264	/// Extended data label too long.
265	///
266	/// Extended data labels can be at most **twenty** characters long.
267	#[displaydoc("Label too long for extended data variable v{0}.")]
268	ExtendedDataLabelTooLong(u8),
269
270	/// Extended data unit too long.
271	///
272	/// Extended data units can be at most **ten** characters long.
273	#[displaydoc("Unit too long for extended data variable v{0}.")]
274	ExtendedDataUnitTooLong(u8),
275
276	/// Invalid extended data field.
277	#[displaydoc("Field {1} is invalid for extended data variable v{0}.")]
278	InvalidExtendedDataField(u8, ExtendedDataField),
279}
280
281impl std::error::Error for PostSystemError {}
282
283/// [Extended data](https://pvoutput.org/help/extended_data.html) fields.
284/// Used to represent invalid field values for [`PostSystemError`]s.
285#[derive(Debug, Display, PartialEq, Eq, Clone, Copy, Hash)]
286pub enum ExtendedDataField {
287	/// Colour.
288	Colour,
289
290	/// Axis.
291	Axis,
292
293	/// Graph.
294	Graph,
295}
296
297/// Errors that can occur when parsing [rate limit](crate::types::response::RateLimit) information from the response's
298/// [HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers).
299#[derive(Debug, Display)]
300pub enum RateLimitError {
301	/// A header was missing.
302	MissingValues,
303
304	/// A header contained non-ASCII characters.
305	NonAsciiHeader,
306
307	/// Failed to parse a header value as an integer.
308	#[displaydoc("Failed to parse integer header value: {0}")]
309	InvalidInteger(std::num::ParseIntError),
310
311	/// Failed to parse the `reset` header value.
312	#[displaydoc("Failed to parse the `reset` header value: {0}")]
313	InvalidTimestamp(jiff::Error),
314}
315
316impl std::error::Error for RateLimitError {}
317
318impl From<http::header::ToStrError> for RateLimitError {
319	fn from(_: http::header::ToStrError) -> Self { Self::NonAsciiHeader }
320}
321
322impl From<std::num::ParseIntError> for RateLimitError {
323	fn from(error: std::num::ParseIntError) -> Self { Self::InvalidInteger(error) }
324}
325
326impl From<jiff::Error> for RateLimitError {
327	fn from(error: jiff::Error) -> Self { Self::InvalidTimestamp(error) }
328}