use serde::Deserialize;
use serde_json::value::RawValue;
use super::ServiceRefusal;
use super::read_support::failure;
use crate::mcp::types::{self, ReviewValue};
#[derive(Deserialize)]
pub(super) struct Difference {
collection: String,
document_id: Option<String>,
what: String,
base: String,
here: String,
}
#[derive(Deserialize)]
pub(super) struct Conflict {
collection: String,
document_id: Option<String>,
what: String,
ancestor: String,
target: String,
source: String,
state: types::ConflictState,
choice_token: Option<String>,
}
#[derive(Deserialize)]
struct Edge {
relation: String,
from: types::RecordRef,
to: types::RecordRef,
}
fn invalid(message: &str) -> ServiceRefusal {
failure("mcp_review_owner_invalid", message)
}
fn record(collection: String, id: Option<String>) -> Result<types::RecordRef, ServiceRefusal> {
Ok(types::RecordRef {
collection,
document_id: id
.ok_or_else(|| invalid("The served owner requires a document identifier"))?,
})
}
fn declaration(id: Option<&str>) -> Result<(), ServiceRefusal> {
if id.is_none() {
Ok(())
} else {
Err(invalid(
"The served declaration unexpectedly has a document identifier",
))
}
}
fn edge(
collection: &str,
id: Option<&str>,
relation: &str,
values: [&str; 3],
) -> Result<types::ReviewOwner, ServiceRefusal> {
let value = values
.into_iter()
.find(|value| !value.is_empty())
.ok_or_else(|| invalid("The served edge has no value"))?;
let edge: Edge = serde_json::from_str(value)
.map_err(|error| failure("mcp_service_answer_invalid", error.to_string()))?;
if edge.relation != relation
|| edge.from.collection != collection
|| Some(edge.from.document_id.as_str()) != id
{
return Err(invalid("The served edge value does not identify its owner"));
}
Ok(types::ReviewOwner::Edge {
relation: edge.relation,
from: edge.from,
to: edge.to,
})
}
fn owner(
collection: String,
id: Option<String>,
what: &str,
values: [&str; 3],
) -> Result<types::ReviewOwner, ServiceRefusal> {
if what == "collection" {
declaration(id.as_deref())?;
return Ok(types::ReviewOwner::Collection { collection });
}
if what == "document" {
return Ok(types::ReviewOwner::Document {
record: record(collection, id)?,
});
}
if what == "relation" {
declaration(id.as_deref())?;
return Ok(types::ReviewOwner::Relation {
relation: collection,
});
}
if let Some(relation) = what.strip_prefix("edge ") {
return edge(&collection, id.as_deref(), relation, values);
}
if let Some(field) = what.strip_prefix("vector field ") {
declaration(id.as_deref())?;
return Ok(types::ReviewOwner::VectorField {
collection,
field: field.to_owned(),
});
}
if let Some(field) = what.strip_prefix("vector ") {
return Ok(types::ReviewOwner::Vector {
record: record(collection, id)?,
field: field.to_owned(),
});
}
Err(invalid(
"The service returned an unrepresentable review owner",
))
}
fn value(value: String) -> Result<ReviewValue, ServiceRefusal> {
if value.is_empty() {
return Ok(ReviewValue::Absent);
}
RawValue::from_string(value)
.map(|value| ReviewValue::Value { value })
.map_err(|error| failure("mcp_service_answer_invalid", error.to_string()))
}
pub(super) fn difference(line: Difference) -> Result<types::ReviewLine, ServiceRefusal> {
let owner = owner(
line.collection,
line.document_id,
&line.what,
[&line.here, &line.base, ""],
)?;
let before = value(line.base)?;
let after = value(line.here)?;
let kind = match (&before, &after) {
(ReviewValue::Absent, ReviewValue::Value { .. }) => types::DifferenceKind::Added,
(ReviewValue::Value { .. }, ReviewValue::Absent) => types::DifferenceKind::Removed,
(ReviewValue::Value { .. }, ReviewValue::Value { .. }) => types::DifferenceKind::Changed,
(ReviewValue::Absent, ReviewValue::Absent) => {
return Err(invalid("The served difference has neither value"));
}
};
Ok(types::ReviewLine {
kind,
owner,
before,
after,
})
}
pub(super) fn conflict(line: Conflict) -> Result<types::ConflictLine, ServiceRefusal> {
let owner = owner(
line.collection,
line.document_id,
&line.what,
[&line.source, &line.target, &line.ancestor],
)?;
Ok(types::ConflictLine {
owner,
ancestor: value(line.ancestor)?,
target: value(line.target)?,
source: value(line.source)?,
state: line.state,
choice_token: line.choice_token,
})
}