Skip to main content

cdx_core/extensions/semantic/
jsonld.rs

1//! JSON-LD metadata for semantic web integration.
2
3use serde::{Deserialize, Serialize};
4
5use super::BibliographyEntry;
6
7/// JSON-LD metadata for semantic web integration.
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct JsonLdMetadata {
11    /// JSON-LD context URIs.
12    #[serde(rename = "@context")]
13    pub context: Vec<String>,
14
15    /// JSON-LD graph containing structured data.
16    #[serde(rename = "@graph", default, skip_serializing_if = "Vec::is_empty")]
17    pub graph: Vec<serde_json::Value>,
18}
19
20impl JsonLdMetadata {
21    /// Create new JSON-LD metadata with schema.org context.
22    #[must_use]
23    pub fn new() -> Self {
24        Self {
25            context: vec!["https://schema.org".to_string()],
26            graph: Vec::new(),
27        }
28    }
29
30    /// Add a context URI.
31    #[must_use]
32    pub fn with_context(mut self, context: impl Into<String>) -> Self {
33        self.context.push(context.into());
34        self
35    }
36
37    /// Add a graph node.
38    pub fn add_node(&mut self, node: serde_json::Value) {
39        self.graph.push(node);
40    }
41
42    /// Create a JSON-LD representation of a creative work.
43    #[must_use]
44    pub fn creative_work(name: impl Into<String>, author: impl Into<String>) -> serde_json::Value {
45        serde_json::json!({
46            "@type": "CreativeWork",
47            "name": name.into(),
48            "author": {
49                "@type": "Person",
50                "name": author.into()
51            }
52        })
53    }
54
55    /// Create a JSON-LD representation of a scholarly article.
56    #[must_use]
57    pub fn scholarly_article(entry: &BibliographyEntry) -> serde_json::Value {
58        let mut article = serde_json::json!({
59            "@type": "ScholarlyArticle",
60            "name": entry.title
61        });
62
63        if !entry.authors.is_empty() {
64            let authors: Vec<_> = entry
65                .authors
66                .iter()
67                .map(|a| {
68                    serde_json::json!({
69                        "@type": "Person",
70                        "name": a.display_name()
71                    })
72                })
73                .collect();
74            article["author"] = serde_json::json!(authors);
75        }
76
77        if let Some(date) = &entry.issued {
78            article["datePublished"] = serde_json::json!(date.to_string());
79        }
80
81        if let Some(doi) = &entry.doi {
82            article["identifier"] = serde_json::json!({
83                "@type": "PropertyValue",
84                "propertyID": "DOI",
85                "value": doi
86            });
87        }
88
89        if let Some(journal) = &entry.container_title {
90            article["isPartOf"] = serde_json::json!({
91                "@type": "Periodical",
92                "name": journal
93            });
94        }
95
96        article
97    }
98}
99
100impl Default for JsonLdMetadata {
101    fn default() -> Self {
102        Self::new()
103    }
104}