pvoutput_client/client/
post.rs

1//! Write-only API endpoint methods.
2
3use std::collections::HashMap;
4
5use const_format::formatcp;
6use jiff::civil::{Date, Time};
7use serde::Serialize;
8
9use crate::{
10	API_BASE_URL, Client,
11	parser::{parse_add_output_error, parse_add_status_error, parse_datetime_error, parse_post_system_error},
12	types::{
13		error::{AddOutputError, AddStatusError, DateTimeError, PostSystemError},
14		params::{
15			AddBatchStatusParams, AddNetBatchStatusParams, AddOutputParams, AddStatusParams, DeleteStatusParams,
16			PostSystemParams,
17		},
18		response::{BatchStatusReport, Response},
19	},
20};
21
22impl Client {
23	/// Adds one or more outputs for the [`Client`]'s system.
24	///
25	/// If the user has [donation status](https://pvoutput.org/help/donations.html), a maximum of **one hundred** outputs
26	/// can be provided at once; otherwise, only **one** may be provided.
27	///
28	/// [PVOutput docs](https://pvoutput.org/help/api_specification.html#add-output-service)
29	///
30	/// # Errors
31	/// Returns [`crate::ClientError::InvalidData`] containing [`DataError::NoData`] if the `body` vector is empty, or
32	/// any other [`DataError`] variant if the provided parameters are invalid.
33	pub async fn add_outputs(&self, body: Vec<AddOutputParams>) -> crate::Result<Response<()>, AddOutputError> {
34		if body.is_empty() {
35			return Err(AddOutputError::NoData.into());
36		}
37		self.post_empty_csv(formatcp!("{API_BASE_URL}/addoutput.jsp"), body, parse_add_output_error).await
38	}
39
40	/// Adds a single status for the [`Client`]'s system.
41	///
42	/// If the user has [donation status](https://pvoutput.org/help/donations.html), statuses from up to **ninety days**
43	/// in the past may be added; otherwise, statuses can only be from the past **fourteen days**.
44	///
45	/// [PVOutput docs](https://pvoutput.org/help/api_specification.html#add-status-service)
46	pub async fn add_status(&self, body: AddStatusParams) -> crate::Result<Response<()>, AddStatusError> {
47		self.post_empty(formatcp!("{API_BASE_URL}/addstatus.jsp"), body, parse_add_status_error).await
48	}
49
50	/// Functionality for the [`add_batch_status`] and [`add_batch_status_net`] endpoints.
51	async fn add_batch_common(
52		&self,
53		regular_statuses: Option<Vec<AddBatchStatusParams>>,
54		net_statuses: Option<Vec<AddNetBatchStatusParams>>,
55		cumulative: bool,
56	) -> crate::Result<Response<Vec<BatchStatusReport>>, AddStatusError> {
57		#[derive(Serialize)]
58		struct RegularBatchForm {
59			data: String,
60			c1: u8,
61		}
62
63		#[derive(Serialize)]
64		struct NetBatchForm {
65			data: String,
66			n: u8,
67		}
68
69		let url = formatcp!("{API_BASE_URL}/addbatchstatus.jsp");
70
71		let mut writer = Self::writer();
72		let regular = regular_statuses.is_some();
73		if let Some(statuses) = regular_statuses {
74			// regular statuses with optional extended data
75			for status in statuses {
76				if let Some(extended_data) = status.extended_data {
77					writer.serialize((status.common, extended_data))?;
78				} else {
79					writer.serialize(status.common)?;
80				}
81			}
82		} else if let Some(statuses) = net_statuses {
83			// net statuses
84			for status in statuses {
85				writer.serialize(status)?;
86			}
87		} else {
88			panic!("At least one of regular_statuses or net_statuses must be provided!");
89		}
90
91		let data = String::from_utf8_lossy(&writer.into_inner()?).to_string().replace('\n', ";");
92
93		if regular {
94			self
95				.post_multiple(
96					url,
97					RegularBatchForm {
98						data,
99						c1: cumulative.into(),
100					},
101					parse_add_status_error,
102				)
103				.await
104		} else {
105			self.post_multiple(url, NetBatchForm { data, n: 1 }, parse_add_status_error).await
106		}
107	}
108
109	/// Adds multiple statuses for the [`Client`]'s system.
110	///
111	/// If the user has [donation status](https://pvoutput.org/help/donations.html), statuses may be from up to
112	/// **ninety days** in the past (up from **fourteen days**), and **one hundred** statuses may be uploaded at a time
113	/// (up from **thirty**).
114	///
115	/// This method *does not* set the `n` (net) flag.
116	/// To upload net statuses, use the [`Client::add_batch_status_net`] method.
117	///
118	/// [PVOutput docs](https://pvoutput.org/help/api_specification.html#add-batch-status-service)
119	pub async fn add_batch_status(
120		&self,
121		statuses: Vec<AddBatchStatusParams>,
122		cumulative: bool,
123	) -> crate::Result<Response<Vec<BatchStatusReport>>, AddStatusError> {
124		if statuses.is_empty() {
125			return Err(AddStatusError::NoData.into());
126		}
127		self.add_batch_common(Some(statuses), None, cumulative).await
128	}
129
130	/// Adds multiple statuses for the [`Client`]'s system.
131	///
132	/// If the user has [donation status](https://pvoutput.org/help/donations.html), statuses may be from up to
133	/// **ninety days** in the past (up from **fourteen days**), and **one hundred** statuses may be uploaded at a time
134	/// (up from **thirty**).
135	///
136	/// This method sets the `n` (net) flag.
137	/// To upload statuses without the net flag set, use the [`Client::add_batch_status`] method.
138	///
139	/// [PVOutput docs](https://pvoutput.org/help/api_specification.html#add-batch-status-service)
140	pub async fn add_batch_status_net(
141		&self,
142		statuses: Vec<AddNetBatchStatusParams>,
143	) -> crate::Result<Response<Vec<BatchStatusReport>>, AddStatusError> {
144		if statuses.is_empty() {
145			return Err(AddStatusError::NoData.into());
146		}
147		self.add_batch_common(None, Some(statuses), false).await
148	}
149
150	/// Updates system configuration.
151	///
152	/// [PVOutput docs](https://pvoutput.org/help/api_specification.html#post-system-service)
153	///
154	/// # Errors
155	/// In addition to the standard errors, this method can return a wide array of [`crate::ClientError::InvalidData`]
156	/// variants:
157	/// - [`DataError::SystemNameTooLong`]
158	/// - [`DataError::MissingExtendedDataLabel`]: An extended variable update was provided with no `label`.
159	///   The `u8` value carried by this variant refers to the variable that was missing its `label`.
160	/// - [`DataError::MissingExtendedDataUnit`]: The same as above, but for the `unit` field.
161	/// - [`DataError::ExtendedDataLabelTooLong`]
162	/// - [`DataError::ExtendedDataUnitTooLong`]
163	/// - [`DataError::InvalidExtendedDataField`]: The variable specified by this variant's first value had an invalid
164	///   field (specified by the variant's second value).
165	pub async fn post_system(&self, system: PostSystemParams) -> crate::Result<Response<()>, PostSystemError> {
166		let mut form = HashMap::new();
167		system.name.map(|name| form.insert(String::from("name"), name));
168
169		for (v, update) in system.extended_variables {
170			let v = v as u8;
171			update.label.map(|l| form.insert(format!("v{v}l"), l));
172			update.unit.map(|u| form.insert(format!("v{v}u"), u));
173			update.colour.map(|c| form.insert(format!("v{v}c"), c));
174			update.axis.map(|a| form.insert(format!("v{v}a"), a.to_string()));
175			update.graph.map(|g| form.insert(format!("v{v}g"), g.to_string()));
176		}
177
178		self.post_empty(formatcp!("{API_BASE_URL}/postsystem.jsp"), form, parse_post_system_error).await
179	}
180
181	/// Deletes a status, or all statuses for a day.
182	///
183	/// If `time` is set, the specific status with the provided `date` and `time` will be deleted.
184	/// Otherwise, all statuses for the given `date` will be deleted.
185	///
186	/// [PVOutput docs](https://pvoutput.org/help/api_specification.html#delete-status-service)
187	pub async fn delete_status(&self, date: Date, time: Option<Time>) -> crate::Result<Response<()>, DateTimeError> {
188		self
189			.post_empty(
190				formatcp!("{API_BASE_URL}/deletestatus.jsp"),
191				DeleteStatusParams { d: date, t: time },
192				parse_datetime_error,
193			)
194			.await
195	}
196}