Skip to main content

oxihuman_export/
jsonld_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! JSON-LD (Linked Data) document export.
6
7/// A JSON-LD context entry.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct LdContextEntry {
11    pub prefix: String,
12    pub iri: String,
13}
14
15/// A JSON-LD node.
16#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct LdNode {
19    pub id: Option<String>,
20    pub ld_type: Option<String>,
21    pub properties: Vec<(String, String)>,
22}
23
24impl LdNode {
25    #[allow(dead_code)]
26    pub fn new() -> Self {
27        LdNode {
28            id: None,
29            ld_type: None,
30            properties: Vec::new(),
31        }
32    }
33
34    #[allow(dead_code)]
35    pub fn with_id(mut self, id: &str) -> Self {
36        self.id = Some(id.to_string());
37        self
38    }
39
40    #[allow(dead_code)]
41    pub fn with_type(mut self, t: &str) -> Self {
42        self.ld_type = Some(t.to_string());
43        self
44    }
45
46    #[allow(dead_code)]
47    pub fn add_property(&mut self, key: &str, val: &str) {
48        self.properties.push((key.to_string(), val.to_string()));
49    }
50}
51
52impl Default for LdNode {
53    fn default() -> Self {
54        LdNode::new()
55    }
56}
57
58/// A JSON-LD document.
59#[allow(dead_code)]
60#[derive(Debug, Clone)]
61pub struct LdDocument {
62    pub context: Vec<LdContextEntry>,
63    pub nodes: Vec<LdNode>,
64}
65
66impl LdDocument {
67    #[allow(dead_code)]
68    pub fn new() -> Self {
69        LdDocument {
70            context: Vec::new(),
71            nodes: Vec::new(),
72        }
73    }
74
75    #[allow(dead_code)]
76    pub fn add_context(&mut self, prefix: &str, iri: &str) {
77        self.context.push(LdContextEntry {
78            prefix: prefix.to_string(),
79            iri: iri.to_string(),
80        });
81    }
82
83    #[allow(dead_code)]
84    pub fn add_node(&mut self, node: LdNode) {
85        self.nodes.push(node);
86    }
87}
88
89impl Default for LdDocument {
90    fn default() -> Self {
91        LdDocument::new()
92    }
93}
94
95/// Serialize a JSON-LD node to JSON.
96#[allow(dead_code)]
97pub fn serialize_node(node: &LdNode) -> String {
98    let mut parts = Vec::new();
99    if let Some(ref id) = node.id {
100        parts.push(format!(r#""@id":"{}""#, id));
101    }
102    if let Some(ref t) = node.ld_type {
103        parts.push(format!(r#""@type":"{}""#, t));
104    }
105    for (k, v) in &node.properties {
106        parts.push(format!(r#""{}":"{}""#, k, v));
107    }
108    format!("{{{}}}", parts.join(","))
109}
110
111/// Export a JSON-LD document to JSON text.
112#[allow(dead_code)]
113pub fn export_jsonld(doc: &LdDocument) -> String {
114    let ctx_parts: Vec<String> = doc
115        .context
116        .iter()
117        .map(|e| format!(r#""{}":{{"@id":"{}"}}"#, e.prefix, e.iri))
118        .collect();
119    let ctx = format!("{{{}}}", ctx_parts.join(","));
120
121    let nodes: Vec<String> = doc.nodes.iter().map(serialize_node).collect();
122    let graph = format!("[{}]", nodes.join(","));
123
124    format!(r#"{{"@context":{},"@graph":{}}}"#, ctx, graph)
125}
126
127/// Node count.
128#[allow(dead_code)]
129pub fn node_count(doc: &LdDocument) -> usize {
130    doc.nodes.len()
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn new_doc_empty() {
139        let doc = LdDocument::new();
140        assert_eq!(node_count(&doc), 0);
141    }
142
143    #[test]
144    fn add_node_count() {
145        let mut doc = LdDocument::new();
146        doc.add_node(LdNode::new().with_id("http://example.com/1"));
147        assert_eq!(node_count(&doc), 1);
148    }
149
150    #[test]
151    fn serialize_node_with_id() {
152        let node = LdNode::new().with_id("http://example.com/1");
153        let s = serialize_node(&node);
154        assert!(s.contains("@id"));
155    }
156
157    #[test]
158    fn serialize_node_with_type() {
159        let node = LdNode::new().with_type("Person");
160        let s = serialize_node(&node);
161        assert!(s.contains("Person"));
162    }
163
164    #[test]
165    fn serialize_node_with_property() {
166        let mut node = LdNode::new();
167        node.add_property("schema:name", "Alice");
168        let s = serialize_node(&node);
169        assert!(s.contains("Alice"));
170    }
171
172    #[test]
173    fn export_contains_context() {
174        let mut doc = LdDocument::new();
175        doc.add_context("schema", "http://schema.org/");
176        let s = export_jsonld(&doc);
177        assert!(s.contains("@context"));
178    }
179
180    #[test]
181    fn export_contains_graph() {
182        let doc = LdDocument::new();
183        let s = export_jsonld(&doc);
184        assert!(s.contains("@graph"));
185    }
186
187    #[test]
188    fn context_prefix_in_export() {
189        let mut doc = LdDocument::new();
190        doc.add_context("schema", "http://schema.org/");
191        let s = export_jsonld(&doc);
192        assert!(s.contains("schema"));
193    }
194
195    #[test]
196    fn node_id_in_export() {
197        let mut doc = LdDocument::new();
198        doc.add_node(LdNode::new().with_id("http://example.com/1"));
199        let s = export_jsonld(&doc);
200        assert!(s.contains("http://example.com/1"));
201    }
202
203    #[test]
204    fn empty_node_braces() {
205        let node = LdNode::new();
206        assert_eq!(serialize_node(&node), "{}");
207    }
208}