use haematite::tree::Hash;
use super::super::{
Captured, Failure,
embedded::{self, Field},
identifier,
};
pub(in crate::serve::storage) struct Pins(Hash);
impl Pins {
pub(in crate::serve::storage) fn new(
origin: &Captured,
target: &Captured,
source: &Captured,
) -> Result<Self, Failure> {
let mut bytes = b"haem.merge-choice-pins.v1\0".to_vec();
for at in [origin, target, source] {
append_bytes(&mut bytes, at.branch.as_bytes())?;
bytes.extend_from_slice(&at.version.generation.to_le_bytes());
bytes.extend_from_slice(&at.version.sequence.to_le_bytes());
append_length(&mut bytes, at.shards.len())?;
for shard in &at.shards {
append_length(&mut bytes, shard.shard_id)?;
bytes.extend_from_slice(shard.head.as_bytes());
}
}
Ok(Self(Hash::of(&bytes)))
}
pub(in crate::serve::storage) fn token(
&self,
key: &[u8],
ancestor: Option<&[u8]>,
target: Option<&[u8]>,
source: Option<&[u8]>,
) -> Result<String, Failure> {
let mut bytes = b"haem.merge-choice-owner.v1\0".to_vec();
bytes.extend_from_slice(self.0.as_bytes());
append_bytes(&mut bytes, key)?;
for value in [ancestor, target, source] {
match value {
None => bytes.push(0),
Some(value) => {
bytes.push(1);
append_length(&mut bytes, value.len())?;
bytes.extend_from_slice(Hash::of(value).as_bytes());
}
}
}
Ok(format!("merge-choice-v1:{}", Hash::of(&bytes)))
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::serve::storage) enum EmbeddedSide {
Source,
Target,
}
pub(in crate::serve::storage) fn embedded_field(
key: &[u8],
ancestor: Option<&[u8]>,
target: Option<&[u8]>,
source: Option<&[u8]>,
) -> Result<Option<EmbeddedSide>, Failure> {
let Some(name) = key.strip_prefix(b"w\0") else {
return Ok(None);
};
let text = std::str::from_utf8(name).map_err(Failure::storage)?;
let (collection, field) = text
.split_once('\0')
.ok_or_else(|| Failure::new("corrupt_key", "vector field key has invalid components"))?;
identifier(collection)?;
identifier(field)?;
let read = |bytes: Option<&[u8]>| bytes.map(embedded::read_field).transpose();
let ancestor = read(ancestor)?;
let target = read(target)?;
let source = read(source)?;
let versions = [ancestor.as_ref(), target.as_ref(), source.as_ref()];
if !versions
.iter()
.any(|field| matches!(field, Some(Field::Embedded(_))))
{
return Ok(None);
}
if versions
.iter()
.any(|field| matches!(field, Some(Field::Supplied { .. })))
{
return Err(Failure::new(
"vector_field_supplied",
"an embedded declaration cannot replace a supplied field",
));
}
let mut declared = None;
for field in versions.into_iter().flatten() {
if let Field::Embedded(current) = field {
if declared.is_some_and(|previous| previous != current) {
return Err(Failure::new(
"embed_model_change_not_available",
"different embedded declarations cannot be merged",
));
}
declared = Some(current);
}
}
if target == source || ancestor == source {
Ok(Some(EmbeddedSide::Target))
} else if ancestor == target {
Ok(Some(EmbeddedSide::Source))
} else {
Err(Failure::new(
"embed_model_change_not_available",
"embedded declaration has conflicting field versions",
))
}
}
fn append_length(bytes: &mut Vec<u8>, length: usize) -> Result<(), Failure> {
let length = u64::try_from(length).map_err(|_| {
Failure::new(
"choice_identity_length",
"choice input exceeds its length field",
)
})?;
bytes.extend_from_slice(&length.to_le_bytes());
Ok(())
}
fn append_bytes(bytes: &mut Vec<u8>, value: &[u8]) -> Result<(), Failure> {
append_length(bytes, value.len())?;
bytes.extend_from_slice(value);
Ok(())
}
#[cfg(test)]
#[path = "review_choice_tests.rs"]
mod tests;