use serde_json::{Value, json};
use trust_tasks_rs::validate::ValidatedPayload;
use crate::{HolderKey, VtcClient, VtcError};
pub use trust_tasks_rs::specs::vtc::elections::{
cancel::v0_1 as cancel, certify::v0_1 as certify, close::v0_1 as close, create::v0_1 as create,
list::v0_1 as list, show::v0_1 as show, tickets::issue::v0_1 as tickets_issue,
};
pub mod task {
use trust_tasks_rs::Payload;
pub const CREATE: &str = <super::create::Payload as Payload>::TYPE_URI;
pub const LIST: &str = <super::list::Payload as Payload>::TYPE_URI;
pub const SHOW: &str = <super::show::Payload as Payload>::TYPE_URI;
pub const CLOSE: &str = <super::close::Payload as Payload>::TYPE_URI;
pub const CANCEL: &str = <super::cancel::Payload as Payload>::TYPE_URI;
pub const CERTIFY: &str = <super::certify::Payload as Payload>::TYPE_URI;
pub const TICKETS_ISSUE: &str = <super::tickets_issue::Payload as Payload>::TYPE_URI;
}
impl VtcClient {
pub async fn elections_create(
&self,
manifest: Value,
key: &HolderKey,
) -> Result<create::Response, VtcError> {
let payload = checked::<create::Payload>(json!({ "manifest": manifest }))?;
self.acl_task(task::CREATE, payload, key, create::ERROR_CODES)
.await
}
pub async fn elections_list(
&self,
state: Option<&str>,
cursor: Option<&str>,
limit: Option<u32>,
key: &HolderKey,
) -> Result<list::Response, VtcError> {
let mut body = json!({});
if let Some(s) = state {
body["state"] = json!(s);
}
if let Some(c) = cursor {
body["cursor"] = json!(c);
}
if let Some(l) = limit {
body["limit"] = json!(l);
}
let payload = checked::<list::Payload>(body)?;
self.acl_task(task::LIST, payload, key, &[]).await
}
pub async fn elections_show(
&self,
election_id: &str,
key: &HolderKey,
) -> Result<show::Response, VtcError> {
let payload = checked::<show::Payload>(json!({ "electionId": election_id }))?;
self.acl_task(task::SHOW, payload, key, show::ERROR_CODES)
.await
}
pub async fn elections_close(
&self,
election_id: &str,
reason: Option<&str>,
key: &HolderKey,
) -> Result<close::Response, VtcError> {
let mut body = json!({ "electionId": election_id });
if let Some(r) = reason {
body["reason"] = json!(r);
}
let payload = checked::<close::Payload>(body)?;
self.acl_task(task::CLOSE, payload, key, close::ERROR_CODES)
.await
}
pub async fn elections_cancel(
&self,
election_id: &str,
reason: &str,
key: &HolderKey,
) -> Result<cancel::Response, VtcError> {
let payload =
checked::<cancel::Payload>(json!({ "electionId": election_id, "reason": reason }))?;
self.acl_task(task::CANCEL, payload, key, cancel::ERROR_CODES)
.await
}
pub async fn elections_certify(
&self,
election_id: &str,
attestations: Vec<Value>,
key: &HolderKey,
) -> Result<certify::Response, VtcError> {
let payload = checked::<certify::Payload>(
json!({ "electionId": election_id, "auditAttestations": attestations }),
)?;
self.acl_task(task::CERTIFY, payload, key, certify::ERROR_CODES)
.await
}
pub async fn elections_ticket(
&self,
election_id: &str,
manifest_digest: &str,
request: Value,
key: &HolderKey,
) -> Result<tickets_issue::Response, VtcError> {
let payload = checked::<tickets_issue::Payload>(json!({
"electionId": election_id,
"manifestDigest": manifest_digest,
"request": request,
}))?;
self.acl_task(
task::TICKETS_ISSUE,
payload,
key,
tickets_issue::ERROR_CODES,
)
.await
}
}
impl VtcClient {
pub async fn send_signed_document(&self, document: &Value) -> Result<Value, VtcError> {
let resp = self
.http
.post(format!("{}/trust-tasks", self.base_url))
.header("content-type", "application/json")
.body(serde_json::to_string(document).map_err(|e| VtcError::Url(e.to_string()))?)
.send()
.await?;
let status = resp.status();
let text = resp.text().await?;
if !status.is_success() {
return Err(VtcError::Http {
status: status.as_u16(),
body: text,
});
}
let doc: trust_tasks_rs::TrustTask<Value> =
serde_json::from_str(&text).map_err(|e| VtcError::Http {
status: status.as_u16(),
body: format!("not a Trust Task document: {e}: {text}"),
})?;
Ok(doc.payload)
}
}
fn checked<P: ValidatedPayload>(payload: Value) -> Result<Value, VtcError> {
P::validate_value(&payload).map_err(|e| VtcError::InvalidPayload(e.to_string()))?;
Ok(payload)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn requests_out_of_schema_are_refused_before_sending() {
assert!(checked::<list::Payload>(json!({ "state": "voting" })).is_ok());
assert!(checked::<list::Payload>(json!({ "state": "nonsense" })).is_err());
assert!(checked::<show::Payload>(json!({ "electionId": "agm-2027" })).is_ok());
assert!(checked::<show::Payload>(json!({ "electionId": "has space" })).is_err());
assert!(checked::<cancel::Payload>(json!({ "electionId": "agm" })).is_err());
assert!(checked::<create::Payload>(json!({ "manifest": {} })).is_err());
}
}