use serde::{Deserialize, Serialize};
use super::{Caller, ResolvedMutation, identifier};
use crate::serve::protocol::Version;
use crate::serve::storage::Failure;
#[path = "attribution_key.rs"]
mod keys;
pub use keys::{decode_key, key, version_prefix};
pub const TAG: &[u8] = b"a\0";
pub const MAX_ROW_BYTES: usize = 64 * 1024;
const FORMAT: u32 = 2;
const FORMAT_WITH_CHANGE: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActorKind {
Person,
Agent,
}
#[derive(Debug, Serialize)]
pub struct Row {
format: u32,
version: Version,
request_id: String,
caller: Caller,
origin: Version,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Wire {
format: u32,
version: Version,
request_id: String,
caller: Caller,
origin: Version,
#[serde(default)]
change: Option<serde::de::IgnoredAny>,
}
#[derive(Deserialize)]
struct Header {
format: Option<u64>,
}
impl Row {
pub fn for_request(request: &ResolvedMutation, written_at: Version) -> Result<Self, Failure> {
Self::by(
request.request_id(),
request.caller(),
request.origin(),
written_at,
)
}
pub fn by(
request_id: &str,
caller: &Caller,
origin: Version,
written_at: Version,
) -> Result<Self, Failure> {
identifier(request_id)?;
Ok(Self {
format: FORMAT,
version: written_at,
request_id: request_id.to_owned(),
caller: caller.clone(),
origin,
})
}
#[must_use]
pub fn author(&self) -> serde_json::Value {
serde_json::json!({"request_id": self.request_id, "caller": self.caller})
}
pub fn key(&self) -> Result<Vec<u8>, Failure> {
key(self.version, &self.request_id)
}
pub fn encode(&self) -> Result<Vec<u8>, Failure> {
let bytes = serde_json::to_vec(self).map_err(Failure::storage)?;
row_bound(&bytes)?;
Ok(bytes)
}
pub fn decode(key: &[u8], value: &[u8]) -> Result<Self, Failure> {
row_bound(value)?;
let (version, request_id) = decode_key(key)?;
let header: Header = serde_json::from_slice(value).map_err(corrupt)?;
if header.format != Some(u64::from(FORMAT))
&& header.format != Some(u64::from(FORMAT_WITH_CHANGE))
{
return Err(Failure::new(
"change_record_format_unsupported",
"attribution row is not format 1 or 2",
));
}
let wire: Wire = serde_json::from_slice(value).map_err(corrupt)?;
if wire.change.is_some() != (wire.format == FORMAT_WITH_CHANGE) {
return Err(corrupt(
"a format 1 row names its change and a format 2 row does not",
));
}
if wire.version != version || wire.request_id != request_id {
return Err(Failure::new(
"change_record_key_mismatch",
"attribution version or request id differs from its key",
));
}
Ok(Self {
format: FORMAT,
version: wire.version,
request_id: wire.request_id,
caller: wire.caller,
origin: wire.origin,
})
}
#[must_use]
pub const fn version(&self) -> Version {
self.version
}
#[must_use]
pub fn request_id(&self) -> &str {
&self.request_id
}
#[must_use]
pub const fn caller(&self) -> &Caller {
&self.caller
}
#[must_use]
pub const fn origin(&self) -> Version {
self.origin
}
#[must_use]
pub const fn actor_kind(&self) -> ActorKind {
if self.caller.responsible.is_some() {
ActorKind::Agent
} else {
ActorKind::Person
}
}
}
fn corrupt(error: impl std::fmt::Display) -> Failure {
Failure::new("change_record_corrupt", error.to_string())
}
fn row_bound(bytes: &[u8]) -> Result<(), Failure> {
if bytes.len() > MAX_ROW_BYTES {
return Err(Failure::new(
"change_record_too_large",
"attribution row exceeds 65536 bytes",
));
}
Ok(())
}
#[cfg(test)]
#[path = "attribution_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "attribution_size_tests.rs"]
mod size_tests;