haematite-cli 0.21.8

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
//! Caller-visible conflicts at three captured versions, without mutation admission.

use std::num::NonZeroUsize;

use haematite::db::{ObservedChange, ReadOnlyDatabase};
use serde::Serialize;

use super::super::{Captured, Catalogue, Failure, ForkProvenance};
use super::{choice, compare, values};
use crate::serve::protocol::ReviewPage;

#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum State {
    SourceOnly,
    SameOnBoth,
    Conflict,
}

#[derive(Serialize)]
pub(in crate::serve) struct Line {
    #[serde(skip_serializing_if = "Option::is_none")]
    choice_token: Option<String>,
    collection: String,
    document_id: Option<String>,
    what: String,
    ancestor: String,
    target: String,
    source: String,
    state: State,
}

pub(in crate::serve) struct Page {
    pub(in crate::serve) selection: Captured,
    pub(in crate::serve) from: ForkProvenance,
    pub(in crate::serve) parent: Captured,
    pub(in crate::serve) lines: Vec<Line>,
    pub(in crate::serve) next_after: Option<String>,
}

pub(in crate::serve) fn page(
    reader: &ReadOnlyDatabase,
    catalogue: &Catalogue,
    request: &ReviewPage,
    limit: NonZeroUsize,
) -> Result<Page, Failure> {
    let compared = compare::page(reader, catalogue, request, limit)?;
    let pins = choice::Pins::new(&compared.origin, &compared.parent, &compared.selection)?;
    let lines = compared
        .source
        .iter()
        .map(|change| line(reader, &compared, &pins, change))
        .filter_map(Result::transpose)
        .collect::<Result<Vec<_>, _>>()?;
    Ok(Page {
        selection: compared.selection,
        from: compared.from,
        parent: compared.parent,
        lines,
        next_after: compared.next_after,
    })
}

fn line(
    reader: &ReadOnlyDatabase,
    compared: &compare::Page,
    pins: &choice::Pins,
    change: &ObservedChange,
) -> Result<Option<Line>, Failure> {
    let (ancestor, source) = compare::sides(change);
    if ancestor == source {
        return Ok(None);
    }
    let Some(identity) = values::identity(change.key())? else {
        return Ok(None);
    };
    let target = compare::target_value(change, &compared.target)?;
    let state = if target == ancestor {
        State::SourceOnly
    } else if target == source {
        State::SameOnBoth
    } else {
        State::Conflict
    };
    let (collection, document_id, what) = identity.description();
    Ok(Some(Line {
        choice_token: if choice::embedded_field(change.key(), ancestor, target, source)?.is_some() {
            None
        } else {
            Some(pins.token(change.key(), ancestor, target, source)?)
        },
        collection,
        document_id,
        what,
        ancestor: identity.value(reader, &compared.origin, ancestor)?,
        target: identity.value(reader, &compared.parent, target)?,
        source: identity.value(reader, &compared.selection, source)?,
        state,
    }))
}