Current section

Files

Jump to
rbt_weave crates weave-content src html jsonld.rs
Raw

crates/weave-content/src/html/jsonld.rs

//! JSON-LD structured data and `OpenGraph` description builders.
use crate::output::{CaseOutput, NodeOutput};
use crate::parser::SourceEntry;
use super::countries::{country_name, format_jurisdiction};
use super::escape::{format_enum, truncate};
pub(crate) fn build_case_og_description(case: &CaseOutput) -> String {
if let Some(tagline) = &case.tagline {
return truncate(tagline, 200);
}
if !case.summary.is_empty() {
return truncate(&case.summary, 200);
}
let people_count = case.nodes.iter().filter(|n| n.label == "person").count();
let org_count = case
.nodes
.iter()
.filter(|n| n.label == "organization")
.count();
truncate(
&format!(
"{} people, {} organizations, {} connections",
people_count,
org_count,
case.relationships.len()
),
200,
)
}
pub(crate) fn build_person_og_description(node: &NodeOutput) -> String {
let mut parts = Vec::new();
if let Some(q) = &node.qualifier {
parts.push(q.clone());
}
if !node.role.is_empty() {
let roles: Vec<_> = node.role.iter().map(|r| format_enum(r)).collect();
parts.push(roles.join(", "));
}
if !node.nationality.is_empty() {
let names: Vec<String> = node.nationality.iter().map(|n| country_name(n)).collect();
parts.push(names.join(", "));
}
if parts.is_empty() {
return truncate(&node.name, 200);
}
truncate(&format!("{} — {}", node.name, parts.join(" · ")), 200)
}
pub(crate) fn build_org_og_description(node: &NodeOutput) -> String {
let mut parts = Vec::new();
if let Some(q) = &node.qualifier {
parts.push(q.clone());
}
if let Some(ot) = &node.org_type {
parts.push(format_enum(ot));
}
if let Some(j) = &node.jurisdiction {
parts.push(format_jurisdiction(j));
}
if parts.is_empty() {
return truncate(&node.name, 200);
}
truncate(&format!("{} — {}", node.name, parts.join(" · ")), 200)
}
pub(crate) fn render_case_json_ld(html: &mut String, case: &CaseOutput) {
let mut ld = serde_json::json!({
"@context": "https://schema.org",
"@type": "Article",
"headline": truncate(&case.title, 120),
"description": truncate(&case.summary, 200),
"url": format!("/{}", case.slug.as_deref().unwrap_or(&case.case_id)),
});
if !case.sources.is_empty() {
let urls: Vec<&str> = case
.sources
.iter()
.map(|s| match s {
SourceEntry::Url(u) => u.as_str(),
SourceEntry::Structured { url, .. } => url.as_str(),
})
.collect();
ld["citation"] = serde_json::json!(urls);
}
html.push_str(&format!(
" <script type=\"application/ld+json\">{}</script>\n",
serde_json::to_string(&ld).unwrap_or_default()
));
}
pub(crate) fn render_person_json_ld(html: &mut String, node: &NodeOutput) {
let mut ld = serde_json::json!({
"@context": "https://schema.org",
"@type": "Person",
"name": &node.name,
"url": format!("/{}", node.slug.as_deref().unwrap_or(&node.id)),
});
if !node.nationality.is_empty() {
let names: Vec<String> = node.nationality.iter().map(|n| country_name(n)).collect();
if names.len() == 1 {
ld["nationality"] = serde_json::json!(names[0]);
} else {
ld["nationality"] = serde_json::json!(names);
}
}
if let Some(desc) = &node.description {
ld["description"] = serde_json::json!(truncate(desc, 200));
}
if let Some(thumb) = &node.thumbnail {
ld["image"] = serde_json::json!(thumb);
}
html.push_str(&format!(
" <script type=\"application/ld+json\">{}</script>\n",
serde_json::to_string(&ld).unwrap_or_default()
));
}
pub(crate) fn render_org_json_ld(html: &mut String, node: &NodeOutput) {
let mut ld = serde_json::json!({
"@context": "https://schema.org",
"@type": "Organization",
"name": &node.name,
"url": format!("/{}", node.slug.as_deref().unwrap_or(&node.id)),
});
if let Some(desc) = &node.description {
ld["description"] = serde_json::json!(truncate(desc, 200));
}
if let Some(thumb) = &node.thumbnail {
ld["logo"] = serde_json::json!(thumb);
}
html.push_str(&format!(
" <script type=\"application/ld+json\">{}</script>\n",
serde_json::to_string(&ld).unwrap_or_default()
));
}