use crate::serve::protocol::{Change, Edge, Operation};
use crate::serve::route_matrix::{Route, route};
use crate::serve::storage::Failure;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subject {
pub route: &'static Route,
pub name: Option<String>,
}
pub fn subjects(operation: &Operation) -> Result<Vec<Subject>, Failure> {
let named = |wire: &str, name: &str| one(wire, Some(name.to_owned()));
let database = |wire: &str| one(wire, None);
match operation {
Operation::Hello(_)
| Operation::Present(_)
| Operation::QueryCheck(_)
| Operation::QueryWords(_) => Ok(Vec::new()),
Operation::Branches(_) => database("branches.list"),
Operation::Collections(_) => database("collections.list"),
Operation::Get(read) => named("documents.get", &read.collection),
Operation::List(page) => named("documents.list", &page.collection),
Operation::History(page) => named("history.list", &page.branch),
Operation::Follow(follow) => named("head.follow", &follow.branch),
Operation::Review(page) => named("branch.review", &page.branch),
Operation::ReviewCounts(page) => named("branch.review_counts", &page.branch),
Operation::MergePrepare(page) => named("branch.merge_prepare", &page.branch),
Operation::MergeDangling(page) => named("branch.merge_dangling", &page.branch),
Operation::Mutate(mutation) => {
let mut subjects = change_subjects(&mutation.change, &mutation.branch)?;
if !matches!(
mutation.change,
Change::Fork(_) | Change::Discard(_) | Change::Merge(_)
) {
subjects.extend(named("mutation.apply", &mutation.branch)?);
}
Ok(subjects)
}
Operation::Receipt(_) => database("mutation.receipt"),
Operation::DraftsRun(_) => database("drafts.run"),
Operation::CountsRebuild(rebuild) => named("counts.rebuild", &rebuild.branch),
Operation::CountsAudit(selection) => named("counts.audit", &selection.branch),
Operation::EdgesOut(page) => named("edge.out", &page.node.collection),
Operation::EdgesIn(page) => named("edge.in", &page.node.collection),
Operation::GraphExport(_) => database("graph.export"),
Operation::VectorGet(read) => named("vectors.get", &read.collection),
Operation::VectorList(page) => named("vectors.list", &page.collection),
Operation::VectorSearch(search) => named("vectors.search", &search.collection),
Operation::VectorSearchText(search) => named("vectors.search_text", &search.collection),
Operation::StoreStats(_) => database("store.stats"),
Operation::VectorFields(_) => database("vectors.fields"),
Operation::EmbedRewalk(rewalk) => named("embed.rewalk_longer", &rewalk.collection),
Operation::Relations(_) => database("relations.list"),
Operation::QueryRun(_) => database("query.run"),
Operation::ChangesList(_) => database("changes.list"),
}
}
fn change_subjects(change: &Change, branch: &str) -> Result<Vec<Subject>, Failure> {
let named = |wire: &str, name: &str| one(wire, Some(name.to_owned()));
match change {
Change::Collection(_) => one("collection.put", None),
Change::Put(write) => named("document.put", &write.collection),
Change::AppendPrune(write) => named("document.append_prune", &write.collection),
Change::Delete(delete) => named("document.delete", &delete.collection),
Change::DeleteMany(delete) => named("documents.delete_many", &delete.collection),
Change::Fork(_) => one("branch.fork", None),
Change::Discard(_) => one("branch.discard", None),
Change::Merge(_) => named("branch.merge", branch),
Change::EdgePut(edge) => ends("edge.put", [edge]),
Change::EdgeDelete(edge) => {
both_ends("edge.delete", &edge.from.collection, &edge.to.collection)
}
Change::IndexDeclare(declaration) => named("index.declare", &declaration.collection),
Change::VectorDeclare(declaration) => named("vector.declare", &declaration.collection),
Change::VectorPut(write) => named("vector.put", &write.collection),
Change::VectorPutMany(import) => {
let route = row("vectors.put_many")?;
let mut subjects = Vec::new();
let mut collections = std::collections::BTreeSet::new();
for vector in &import.vectors {
if collections.insert(&vector.collection) {
subjects.push(Subject {
route,
name: Some(vector.collection.clone()),
});
}
}
if subjects.is_empty() {
return Err(Failure::new(
"import_empty",
"a vector import names no collection",
));
}
Ok(subjects)
}
Change::VectorDelete(delete) => named("vector.delete", &delete.collection),
Change::PutMany(import) => named("documents.put_many", &import.collection),
Change::EdgePutMany(import) => ends("edges.put_many", &import.edges),
Change::RelationPut(_) => one("relation.put", None),
Change::RelationDelete(_) => one("relation.delete", None),
}
}
fn row(wire: &str) -> Result<&'static Route, Failure> {
route(wire).ok_or_else(|| {
Failure::new(
"route_unclassified",
"this route has no row in the access matrix and is refused",
)
})
}
fn one(wire: &str, name: Option<String>) -> Result<Vec<Subject>, Failure> {
Ok(vec![Subject {
route: row(wire)?,
name,
}])
}
fn both_ends(wire: &str, from: &str, to: &str) -> Result<Vec<Subject>, Failure> {
let route = row(wire)?;
let mut subjects = vec![Subject {
route,
name: Some(from.to_owned()),
}];
if to != from {
subjects.push(Subject {
route,
name: Some(to.to_owned()),
});
}
Ok(subjects)
}
fn ends<'a>(
wire: &str,
edges: impl IntoIterator<Item = &'a Edge>,
) -> Result<Vec<Subject>, Failure> {
let route = row(wire)?;
let mut subjects: Vec<Subject> = Vec::new();
for edge in edges {
for collection in [&edge.from.collection, &edge.to.collection] {
if !subjects
.iter()
.any(|subject| subject.name.as_deref() == Some(collection.as_str()))
{
subjects.push(Subject {
route,
name: Some(collection.clone()),
});
}
}
}
if subjects.is_empty() {
return Err(Failure::new(
"import_empty",
"an edge import names no edge, so it acts on no collection",
));
}
Ok(subjects)
}
#[cfg(test)]
#[path = "authorize_tests.rs"]
mod tests;