use std::fmt;
use rpki::x509::Time;
use crate::commons::eventsourcing::store::CommandKey;
use crate::commons::eventsourcing::{Event, Storable};
use crate::commons::{
actor::Actor,
api::{CommandHistoryRecord, CommandSummary, Handle, StoredEffect},
};
pub trait WithStorableDetails: Storable + Send + Sync {
fn summary(&self) -> CommandSummary;
}
pub trait Command: fmt::Display + Send + Sync {
type StorableDetails: WithStorableDetails;
fn handle(&self) -> &Handle;
fn version(&self) -> Option<u64>;
fn actor(&self) -> &str;
fn store(&self) -> Self::StorableDetails;
}
#[derive(Clone)]
pub struct SentCommand<C: CommandDetails> {
handle: Handle,
version: Option<u64>,
details: C,
actor: String,
}
impl<C: CommandDetails> Command for SentCommand<C> {
type StorableDetails = C::StorableDetails;
fn handle(&self) -> &Handle {
&self.handle
}
fn version(&self) -> Option<u64> {
self.version
}
fn store(&self) -> Self::StorableDetails {
self.details.store()
}
fn actor(&self) -> &str {
&self.actor
}
}
impl<C: CommandDetails> SentCommand<C> {
pub fn new(id: &Handle, version: Option<u64>, details: C, actor: &Actor) -> Self {
let actor_name = if actor.is_user() {
format!("user:{}", actor.name())
} else {
actor.name().to_string()
};
SentCommand {
handle: id.clone(),
version,
details,
actor: actor_name,
}
}
pub fn into_details(self) -> C {
self.details
}
}
impl<C: CommandDetails> fmt::Display for SentCommand<C> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let version_string = self
.version
.as_ref()
.map(u64::to_string)
.unwrap_or_else(|| "any".to_string());
write!(
f,
"id '{}' version '{}' details '{}'",
self.handle, version_string, self.details
)
}
}
pub trait CommandDetails: fmt::Display + Send + Sync + 'static {
type Event: Event;
type StorableDetails: WithStorableDetails;
fn store(&self) -> Self::StorableDetails;
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct StoredCommand<S: WithStorableDetails> {
actor: String,
time: Time,
handle: Handle,
version: u64, sequence: u64, #[serde(deserialize_with = "S::deserialize")]
details: S,
effect: StoredEffect,
}
impl<S: WithStorableDetails> StoredCommand<S> {
pub fn new(
actor: String,
time: Time,
handle: Handle,
version: u64,
sequence: u64,
details: S,
effect: StoredEffect,
) -> Self {
StoredCommand {
actor,
time,
handle,
version,
sequence,
details,
effect,
}
}
pub fn time(&self) -> Time {
self.time
}
pub fn handle(&self) -> &Handle {
&self.handle
}
pub fn sequence(&self) -> u64 {
self.sequence
}
pub fn details(&self) -> &S {
&self.details
}
pub fn effect(&self) -> &StoredEffect {
&self.effect
}
}
impl<S: WithStorableDetails> From<StoredCommand<S>> for CommandHistoryRecord {
fn from(command: StoredCommand<S>) -> Self {
let summary = command.details.summary();
let command_key = CommandKey::new(command.sequence, command.time, summary.label.clone());
CommandHistoryRecord {
key: command_key.to_string(),
actor: command.actor,
timestamp: command.time.timestamp_millis(),
handle: command.handle,
version: command.version,
sequence: command.sequence,
summary,
effect: command.effect,
}
}
}
pub struct StoredCommandBuilder<C: Command> {
actor: String,
time: Time,
handle: Handle,
version: u64,
sequence: u64,
details: C::StorableDetails,
}
impl<C: Command> StoredCommandBuilder<C> {
pub fn new(cmd: &C, version: u64, sequence: u64) -> StoredCommandBuilder<C> {
let actor = cmd.actor().to_string();
let time = Time::now();
let handle = cmd.handle().clone();
let details = cmd.store();
StoredCommandBuilder {
actor,
time,
handle,
version,
sequence,
details,
}
}
fn finish(self, effect: StoredEffect) -> StoredCommand<C::StorableDetails> {
StoredCommand {
actor: self.actor,
time: self.time,
handle: self.handle,
version: self.version,
sequence: self.sequence,
details: self.details,
effect,
}
}
pub fn finish_with_events<E: Event>(self, events: &[E]) -> StoredCommand<C::StorableDetails> {
let events = events.iter().map(|e| e.version()).collect();
let effect = StoredEffect::Success { events };
self.finish(effect)
}
pub fn finish_with_error<E: fmt::Display>(self, err: &E) -> StoredCommand<C::StorableDetails> {
let effect = StoredEffect::Error { msg: err.to_string() };
self.finish(effect)
}
}