Trait-validering for samlingsnoder (oppgave 13.1)
Maskinrommet validerer nå metadata.traits ved create_node og update_node for collection-noder. Ukjente trait-navn avvises med 400 Bad Request. Lukket katalog med 48 gyldige traits fra docs/primitiver/traits.md. Konfigurasjon per trait er fri JSONB — kun nøkkelnavnene valideres. Noder som ikke er collections valideres ikke (traits ignoreres). Inkluderer 6 unit-tester for valideringsfunksjonen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a21da84122
commit
37cd133e1d
2 changed files with 150 additions and 2 deletions
|
|
@ -28,6 +28,71 @@ const MAX_UPLOAD_SIZE: usize = 100 * 1024 * 1024;
|
|||
/// Gyldige visibility-verdier (speiler PG enum).
|
||||
const VALID_VISIBILITIES: &[&str] = &["hidden", "discoverable", "readable", "open"];
|
||||
|
||||
/// Gyldige trait-navn for samlingsnoder.
|
||||
/// Lukket katalog — ref: docs/primitiver/traits.md § "Trait-katalog"
|
||||
const VALID_TRAITS: &[&str] = &[
|
||||
// Innhold & redigering
|
||||
"editor", "versioning", "collaboration", "translation", "templates",
|
||||
// Publisering & distribusjon
|
||||
"publishing", "rss", "newsletter", "custom_domain", "analytics", "embed", "api",
|
||||
// Lyd & video
|
||||
"podcast", "recording", "transcription", "tts", "clips", "playlist",
|
||||
// Kommunikasjon
|
||||
"chat", "forum", "comments", "guest_input", "announcements", "polls", "qa",
|
||||
// Organisering
|
||||
"kanban", "calendar", "timeline", "table", "gallery", "bookmarks", "tags",
|
||||
// Kunnskap
|
||||
"knowledge_graph", "wiki", "glossary", "faq", "bibliography",
|
||||
// Automatisering & AI
|
||||
"auto_tag", "auto_summarize", "digest", "bridge", "moderation",
|
||||
// Tilgang & fellesskap
|
||||
"membership", "roles", "invites", "paywall", "directory",
|
||||
// Ekstern integrasjon
|
||||
"webhook", "import", "export", "ical_sync",
|
||||
];
|
||||
|
||||
/// Validerer `metadata.traits`-objektet for samlingsnoder.
|
||||
///
|
||||
/// Regler:
|
||||
/// - Kun samlingsnoder (`node_kind == "collection"`) valideres.
|
||||
/// - `traits` må være et objekt (ikke array, string, etc.).
|
||||
/// - Hvert nøkkelnavn må finnes i VALID_TRAITS.
|
||||
/// - Verdien per trait er fri JSONB (åpen konfigurasjon).
|
||||
///
|
||||
/// Ref: docs/primitiver/traits.md § "Lukket katalog, åpen konfigurasjon"
|
||||
fn validate_collection_traits(
|
||||
node_kind: &str,
|
||||
metadata: &serde_json::Value,
|
||||
) -> Result<(), String> {
|
||||
if node_kind != "collection" {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let traits = match metadata.get("traits") {
|
||||
None => return Ok(()), // Ingen traits er OK — samling uten funksjonalitet
|
||||
Some(t) => t,
|
||||
};
|
||||
|
||||
let traits_obj = traits.as_object().ok_or(
|
||||
"metadata.traits må være et objekt".to_string(),
|
||||
)?;
|
||||
|
||||
let unknown: Vec<&String> = traits_obj
|
||||
.keys()
|
||||
.filter(|k| !VALID_TRAITS.contains(&k.as_str()))
|
||||
.collect();
|
||||
|
||||
if !unknown.is_empty() {
|
||||
let unknown_str: Vec<&str> = unknown.iter().map(|s| s.as_str()).collect();
|
||||
return Err(format!(
|
||||
"Ukjente traits: {:?}. Gyldige traits: se docs/primitiver/traits.md",
|
||||
unknown_str,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub error: String,
|
||||
|
|
@ -284,6 +349,10 @@ pub async fn create_node(
|
|||
let metadata = req
|
||||
.metadata
|
||||
.unwrap_or_else(|| serde_json::json!({}));
|
||||
|
||||
// -- Valider traits for samlingsnoder (oppgave 13.1) --
|
||||
validate_collection_traits(&node_kind, &metadata).map_err(|e| bad_request(&e))?;
|
||||
|
||||
let metadata_str = metadata.to_string();
|
||||
|
||||
// -- Kontekstbasert identitet (oppgave 8.2) --
|
||||
|
|
@ -653,6 +722,10 @@ pub async fn update_node(
|
|||
let title = req.title.unwrap_or(existing.title.unwrap_or_default());
|
||||
let content = req.content.unwrap_or(existing.content.unwrap_or_default());
|
||||
let metadata = req.metadata.unwrap_or(existing.metadata);
|
||||
|
||||
// -- Valider traits for samlingsnoder (oppgave 13.1) --
|
||||
validate_collection_traits(&node_kind, &metadata).map_err(|e| bad_request(&e))?;
|
||||
|
||||
let metadata_str = metadata.to_string();
|
||||
|
||||
let node_id_str = req.node_id.to_string();
|
||||
|
|
@ -2709,3 +2782,79 @@ pub async fn close_communication(
|
|||
status: "closed".to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tester
|
||||
// =============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_validate_traits_ok_empty() {
|
||||
let meta = json!({});
|
||||
assert!(validate_collection_traits("collection", &meta).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_traits_ok_known() {
|
||||
let meta = json!({
|
||||
"traits": {
|
||||
"publishing": { "slug": "test" },
|
||||
"rss": { "format": "atom" },
|
||||
"editor": { "preset": "longform" }
|
||||
}
|
||||
});
|
||||
assert!(validate_collection_traits("collection", &meta).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_traits_rejects_unknown() {
|
||||
let meta = json!({
|
||||
"traits": {
|
||||
"publishing": {},
|
||||
"banana": {}
|
||||
}
|
||||
});
|
||||
let err = validate_collection_traits("collection", &meta).unwrap_err();
|
||||
assert!(err.contains("banana"), "Feilmelding skal nevne ukjent trait: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_traits_rejects_non_object() {
|
||||
let meta = json!({ "traits": ["publishing"] });
|
||||
let err = validate_collection_traits("collection", &meta).unwrap_err();
|
||||
assert!(err.contains("objekt"), "Feilmelding: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_traits_skips_non_collection() {
|
||||
let meta = json!({ "traits": { "totally_invalid": {} } });
|
||||
assert!(validate_collection_traits("content", &meta).is_ok());
|
||||
assert!(validate_collection_traits("person", &meta).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_traits_all_known() {
|
||||
// Verifiser at alle traits fra katalogen er gyldige
|
||||
let all_traits = vec![
|
||||
"editor", "versioning", "collaboration", "translation", "templates",
|
||||
"publishing", "rss", "newsletter", "custom_domain", "analytics", "embed", "api",
|
||||
"podcast", "recording", "transcription", "tts", "clips", "playlist",
|
||||
"chat", "forum", "comments", "guest_input", "announcements", "polls", "qa",
|
||||
"kanban", "calendar", "timeline", "table", "gallery", "bookmarks", "tags",
|
||||
"knowledge_graph", "wiki", "glossary", "faq", "bibliography",
|
||||
"auto_tag", "auto_summarize", "digest", "bridge", "moderation",
|
||||
"membership", "roles", "invites", "paywall", "directory",
|
||||
"webhook", "import", "export", "ical_sync",
|
||||
];
|
||||
let mut traits_obj = serde_json::Map::new();
|
||||
for t in &all_traits {
|
||||
traits_obj.insert(t.to_string(), json!({}));
|
||||
}
|
||||
let meta = json!({ "traits": traits_obj });
|
||||
assert!(validate_collection_traits("collection", &meta).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
tasks.md
3
tasks.md
|
|
@ -130,8 +130,7 @@ Uavhengige faser kan fortsatt plukkes.
|
|||
|
||||
## Fase 13: Trait-system
|
||||
|
||||
- [~] 13.1 Trait-metadata på samlingsnoder: maskinrommet validerer `metadata.traits`-objektet ved `create_node` og `update_node` for samlingsnoder. Avvis ukjente trait-navn. Ref: `docs/primitiver/traits.md`.
|
||||
> Påbegynt: 2026-03-18T00:10
|
||||
- [x] 13.1 Trait-metadata på samlingsnoder: maskinrommet validerer `metadata.traits`-objektet ved `create_node` og `update_node` for samlingsnoder. Avvis ukjente trait-navn. Ref: `docs/primitiver/traits.md`.
|
||||
- [ ] 13.2 Trait-aware frontend: samlingssider leser `traits` fra metadata og rendrer kun aktive komponenter. Dynamisk komponent-lasting basert på trait-liste.
|
||||
- [ ] 13.3 Pakkevelger: UI for å opprette ny samling med forhåndsdefinert pakke (nettmagasin, podcaststudio, redaksjon osv.) eller manuelt valg av traits.
|
||||
- [ ] 13.4 Trait-administrasjon: admin-UI for å legge til/fjerne traits på eksisterende samlinger med konfigurasjon per trait.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue