use serde_json::{Map, Value};
use turbomcp_core::meta;
pub fn of(component_meta: &Map<String, Value>) -> impl Iterator<Item = &str> {
component_meta
.get(meta::keys::TAGS)
.and_then(Value::as_array)
.map(Vec::as_slice)
.unwrap_or_default()
.iter()
.filter_map(Value::as_str)
}
pub fn has(component_meta: &Map<String, Value>, tag: &str) -> bool {
of(component_meta).any(|t| t == tag)
}
pub fn has_any(component_meta: &Map<String, Value>, tags: &[&str]) -> bool {
of(component_meta).any(|t| tags.contains(&t))
}
pub fn has_all(component_meta: &Map<String, Value>, tags: &[&str]) -> bool {
tags.iter().all(|want| has(component_meta, want))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn meta(value: Value) -> Map<String, Value> {
let mut m = Map::new();
m.insert(meta::keys::TAGS.to_string(), value);
m
}
#[test]
fn reads_the_tags_the_macro_writes() {
let m = meta(json!(["admin", "dangerous"]));
assert_eq!(of(&m).collect::<Vec<_>>(), ["admin", "dangerous"]);
assert!(has(&m, "admin"));
assert!(!has(&m, "readonly"));
assert!(has_any(&m, &["readonly", "dangerous"]));
assert!(has_all(&m, &["admin", "dangerous"]));
assert!(!has_all(&m, &["admin", "readonly"]));
}
#[test]
fn an_untagged_component_matches_nothing_and_everything() {
let m = Map::new();
assert_eq!(of(&m).count(), 0);
assert!(!has(&m, "admin"));
assert!(has_all(&m, &[]));
assert!(!has_any(&m, &[]));
}
#[test]
fn a_malformed_value_reads_as_untagged() {
for bad in [json!("admin"), json!(42), json!({"a": 1}), json!(null)] {
assert_eq!(of(&meta(bad)).count(), 0);
}
let m = meta(json!(["admin", 7, null, "safe"]));
assert_eq!(of(&m).collect::<Vec<_>>(), ["admin", "safe"]);
}
}