feat(server): lib target + pure org-hook helpers (classify_path, extract_schema_version) + unit tests

This commit is contained in:
adlee-was-taken
2026-06-19 20:43:25 -04:00
parent 743a46f3d5
commit 675b7836e1
3 changed files with 130 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
//! Library surface for relicario-server, exposing pure helpers used by the
//! pre-receive hooks so they can be unit-tested.
/// Classification of a single changed path inside an org repo.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathClass {
/// `members.json`, `collections.json`, `org.json` — only Owner/Admin may write.
Protected,
/// `items/<slug>/<id>.enc` — writer must hold a grant for `<slug>`.
Item { collection: String },
/// `keys/<id>.enc`, `manifest.enc`, `.gitignore`, etc. — gated only by the
/// per-commit signature check (signer must be a current member).
Unrestricted,
/// Structurally invalid path; commit must be rejected.
Rejected(String),
}
/// Classify a repo-relative path. Pure; no I/O.
pub fn classify_path(path: &str) -> PathClass {
match path {
"members.json" | "collections.json" | "org.json" => return PathClass::Protected,
_ => {}
}
if let Some(rest) = path.strip_prefix("items/") {
// Expect exactly: <slug>/<id>.enc → two segments after the prefix.
let segments: Vec<&str> = rest.split('/').collect();
if segments.len() != 2 {
return PathClass::Rejected("items path must be items/<slug>/<id>.enc".to_string());
}
let slug = segments[0];
if slug.is_empty() {
return PathClass::Rejected("empty collection slug in items path".to_string());
}
return PathClass::Item { collection: slug.to_string() };
}
PathClass::Unrestricted
}
/// Extract the `schema_version` field from any org JSON document.
/// Returns an error if the field is absent or not a u32.
pub fn extract_schema_version(json: &str) -> Result<u32, String> {
let value: serde_json::Value =
serde_json::from_str(json).map_err(|e| format!("parse json: {e}"))?;
value
.get("schema_version")
.and_then(|v| v.as_u64())
.map(|n| n as u32)
.ok_or_else(|| "missing or non-integer schema_version".to_string())
}