use std::time::Duration;
use serde_json::Value;
use crate::outbound::{PeerError, PeerTasks};
pub const REPLY_TIMEOUT: Duration = Duration::from_secs(20);
#[derive(Debug, Clone, PartialEq)]
pub enum BoxAnswer {
Success { payload: Value, document: Value },
Refused { code: String, message: String },
}
#[derive(Debug, Clone)]
pub enum LinkError {
Unavailable(String),
NoMatchingProtocol(String),
}
impl std::fmt::Display for LinkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unavailable(m) | Self::NoMatchingProtocol(m) => f.write_str(m),
}
}
}
#[async_trait::async_trait]
pub trait BallotBoxChannel: Send + Sync {
async fn exchange(
&self,
ballot_box: &str,
type_uri: &str,
payload: &Value,
) -> Result<BoxAnswer, LinkError>;
}
pub struct BallotBoxLink {
pub(crate) peers: PeerTasks,
}
#[async_trait::async_trait]
impl BallotBoxChannel for BallotBoxLink {
async fn exchange(
&self,
ballot_box: &str,
type_uri: &str,
payload: &Value,
) -> Result<BoxAnswer, LinkError> {
let reply = self
.peers
.exchange(ballot_box, type_uri, payload, REPLY_TIMEOUT)
.await
.map_err(|e| match e {
PeerError::Transient(m) => LinkError::Unavailable(m),
PeerError::NoMatchingProtocol(m) => LinkError::NoMatchingProtocol(m),
})?;
let document = serde_json::to_value(&reply).unwrap_or(Value::Null);
Ok(classify(
&reply.type_uri.to_string(),
reply.payload,
document,
))
}
}
#[must_use]
pub fn classify(type_uri: &str, payload: Value, document: Value) -> BoxAnswer {
if type_uri.ends_with("#response") {
return BoxAnswer::Success { payload, document };
}
BoxAnswer::Refused {
code: payload["code"].as_str().unwrap_or("unknown").to_string(),
message: payload["message"].as_str().unwrap_or_default().to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn a_response_is_success_and_anything_else_a_refusal() {
let ok = classify(
"https://trusttasks.org/spec/voting/election/register/0.1#response",
json!({ "state": "registration" }),
json!({ "signed": true }),
);
assert_eq!(
ok,
BoxAnswer::Success {
payload: json!({ "state": "registration" }),
document: json!({ "signed": true })
}
);
let refused = classify(
"https://trusttasks.org/spec/trust-task-error/0.5",
json!({ "code": "voting/election/register:alreadyRegistered", "message": "no" }),
Value::Null,
);
assert_eq!(
refused,
BoxAnswer::Refused {
code: "voting/election/register:alreadyRegistered".into(),
message: "no".into()
}
);
}
}