1use alloc::{
30 borrow::ToOwned,
31 collections::{BTreeMap, BTreeSet},
32 format,
33 string::{String, ToString},
34 vec::Vec,
35};
36use serde_json::{Map, Value};
37use sha2::{Digest, Sha256};
38
39use crate::{
40 Error, Limits, Registry,
41 context::{ActiveContext, Session, is_absolute_iri},
42 expand,
43};
44
45const RDF_TYPE: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
46const RDF_FIRST: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#first";
47const RDF_REST: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest";
48const RDF_NIL: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil";
49const RDF_LANG_STRING: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#langString";
50const XSD_STRING: &str = "http://www.w3.org/2001/XMLSchema#string";
51const XSD_BOOLEAN: &str = "http://www.w3.org/2001/XMLSchema#boolean";
52const XSD_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#integer";
53const XSD_DOUBLE: &str = "http://www.w3.org/2001/XMLSchema#double";
54
55const CANONICALIZATION_BUDGET: usize = 4_096;
61
62#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
64pub enum Term {
65 Iri(String),
67 Blank(String),
69 Literal {
72 value: String,
73 datatype: String,
74 language: Option<String>,
75 },
76}
77
78#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
80pub struct Quad {
81 pub subject: Term,
82 pub predicate: String,
83 pub object: Term,
84 pub graph: Option<Term>,
86}
87
88pub fn expand_for_rdf(
97 registry: &Registry,
98 document: &Value,
99 limits: Limits,
100) -> Result<Value, Error> {
101 let mut session = Session::new(registry, limits);
102 session.rdf = true;
103 let expanded = expand::expand_document(&ActiveContext::default(), document, &mut session)?;
104 if let Some(unresolved) = session.unresolved.into_iter().next() {
105 return Err(Error::UnresolvedContext(unresolved));
106 }
107 Ok(expanded)
108}
109
110pub fn unresolved_contexts(
126 registry: &Registry,
127 document: &Value,
128 limits: Limits,
129) -> Result<Vec<String>, Error> {
130 let mut session = Session::new(registry, limits);
131 session.rdf = true;
132 let expanded = expand::expand_document(&ActiveContext::default(), document, &mut session);
133 if !session.unresolved.is_empty() {
134 return Ok(session.unresolved);
135 }
136 expanded.map(|_| Vec::new())
137}
138
139pub fn canonize(registry: &Registry, document: &Value) -> Result<String, Error> {
141 canonize_with(registry, document, Limits::default())
142}
143
144pub fn canonize_with(
146 registry: &Registry,
147 document: &Value,
148 limits: Limits,
149) -> Result<String, Error> {
150 let expanded = expand_for_rdf(registry, document, limits)?;
151 let quads = to_rdf(&expanded)?;
152 canonicalize(&quads)
153}
154
155pub fn to_rdf(expanded: &Value) -> Result<Vec<Quad>, Error> {
162 let mut writer = Writer::default();
163 match expanded {
164 Value::Array(items) => {
165 for item in items {
166 writer.node(item, None)?;
167 }
168 }
169 item => {
170 writer.node(item, None)?;
171 }
172 }
173 let mut quads = writer.quads;
174 quads.sort();
175 quads.dedup();
176 Ok(quads)
177}
178
179#[derive(Default)]
180struct Writer {
181 quads: Vec<Quad>,
182 labels: BTreeMap<String, String>,
184 next: usize,
185}
186
187impl Writer {
188 fn fresh(&mut self) -> String {
189 let label = format!("b{}", self.next);
190 self.next += 1;
191 label
192 }
193
194 fn id_term(&mut self, id: &str) -> Option<Term> {
197 if let Some(label) = id.strip_prefix("_:") {
198 if let Some(mapped) = self.labels.get(label) {
199 return Some(Term::Blank(mapped.clone()));
200 }
201 let mapped = self.fresh();
202 self.labels.insert(label.to_owned(), mapped.clone());
203 return Some(Term::Blank(mapped));
204 }
205 well_formed_iri(id).then(|| Term::Iri(id.to_owned()))
206 }
207
208 fn emit(&mut self, subject: &Term, predicate: &str, object: Term, graph: &Option<Term>) {
209 self.quads.push(Quad {
210 subject: subject.clone(),
211 predicate: predicate.to_owned(),
212 object,
213 graph: graph.clone(),
214 });
215 }
216
217 fn node(&mut self, node: &Value, graph: Option<Term>) -> Result<Option<Term>, Error> {
220 let Value::Object(map) = node else {
221 return Ok(None);
222 };
223 if map.contains_key("@value") || map.contains_key("@list") {
224 return Ok(None);
226 }
227 if let Some(contents) = map.get("@graph") {
228 return self.graph_object(map, contents);
229 }
230 let subject = match map.get("@id").and_then(Value::as_str) {
231 Some(id) => self.id_term(id),
232 None => Some(Term::Blank(self.fresh())),
233 };
234
235 let mut keys: Vec<&String> = map.keys().collect();
236 keys.sort();
237 for key in keys {
238 let values = match &map[key] {
239 Value::Array(items) => items.as_slice(),
240 single => core::slice::from_ref(single),
241 };
242 match key.as_str() {
243 "@id" => {}
244 "@type" => {
245 for kind in values {
246 let Some(kind) = kind.as_str() else { continue };
247 let object = self.id_term(kind);
248 if let (Some(subject), Some(object)) = (&subject, object) {
249 self.emit(subject, RDF_TYPE, object, &graph);
250 }
251 }
252 }
253 key if key.starts_with('@') => {}
254 property => {
255 let usable = well_formed_iri(property);
256 for value in values {
257 let object = self.object(value, &graph)?;
258 if let (true, Some(subject), Some(object)) = (usable, &subject, object) {
259 self.emit(subject, property, object, &graph);
260 }
261 }
262 }
263 }
264 }
265 Ok(subject)
266 }
267
268 fn graph_object(
271 &mut self,
272 map: &Map<String, Value>,
273 contents: &Value,
274 ) -> Result<Option<Term>, Error> {
275 let name = match map.get("@id").and_then(Value::as_str) {
276 Some(id) => self.id_term(id),
277 None => Some(Term::Blank(self.fresh())),
278 };
279 let items = match contents {
280 Value::Array(items) => items.as_slice(),
281 single => core::slice::from_ref(single),
282 };
283 for item in items {
284 if name.is_some() {
286 self.node(item, name.clone())?;
287 }
288 }
289 Ok(name)
290 }
291
292 fn object(&mut self, value: &Value, graph: &Option<Term>) -> Result<Option<Term>, Error> {
295 let Value::Object(map) = value else {
296 return Ok(None);
297 };
298 if map.contains_key("@value") {
299 return literal(map);
300 }
301 if let Some(items) = map.get("@list") {
302 let items = match items {
303 Value::Array(items) => items.as_slice(),
304 single => core::slice::from_ref(single),
305 };
306 return self.list(items, graph);
307 }
308 self.node(value, graph.clone())
309 }
310
311 fn list(&mut self, items: &[Value], graph: &Option<Term>) -> Result<Option<Term>, Error> {
313 if items.is_empty() {
314 return Ok(Some(Term::Iri(RDF_NIL.to_owned())));
315 }
316 let cells: Vec<Term> = items.iter().map(|_| Term::Blank(self.fresh())).collect();
317 for (index, item) in items.iter().enumerate() {
318 let cell = &cells[index];
319 if let Some(object) = self.object(item, graph)? {
320 self.emit(cell, RDF_FIRST, object, graph);
321 }
322 let rest = cells
323 .get(index + 1)
324 .cloned()
325 .unwrap_or_else(|| Term::Iri(RDF_NIL.to_owned()));
326 self.emit(cell, RDF_REST, rest, graph);
327 }
328 Ok(cells.into_iter().next())
329 }
330}
331
332fn literal(map: &Map<String, Value>) -> Result<Option<Term>, Error> {
334 let value = &map["@value"];
335 let datatype = map.get("@type").and_then(Value::as_str);
336 if datatype == Some("@json") {
337 return Err(Error::Unsupported("@json literal".to_owned()));
338 }
339 if let Some(datatype) = datatype
340 && !well_formed_iri(datatype)
341 {
342 return Ok(None);
343 }
344 let language = map.get("@language").and_then(Value::as_str);
345 let (lexical, datatype) = match value {
346 Value::Bool(flag) => (
347 if *flag { "true" } else { "false" }.to_owned(),
348 datatype.unwrap_or(XSD_BOOLEAN).to_owned(),
349 ),
350 Value::Number(number) => {
351 let double = number.as_f64().unwrap_or(0.0);
352 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
355 let integral = number.is_i64()
356 || number.is_u64()
357 || double.abs() >= 9_007_199_254_740_992.0
358 || (double as i64) as f64 == double;
359 if !integral || double.abs() >= 1e21 || datatype == Some(XSD_DOUBLE) {
360 (
361 canonical_double(double),
362 datatype.unwrap_or(XSD_DOUBLE).to_owned(),
363 )
364 } else {
365 let lexical = if let Some(integer) = number.as_i64() {
366 integer.to_string()
367 } else if let Some(integer) = number.as_u64() {
368 integer.to_string()
369 } else {
370 format!("{double:.0}")
373 };
374 (lexical, datatype.unwrap_or(XSD_INTEGER).to_owned())
375 }
376 }
377 Value::String(text) => match (language, datatype) {
378 (Some(language), _) => {
379 if !well_formed_language(language) {
380 return Ok(None);
381 }
382 return Ok(Some(Term::Literal {
383 value: text.clone(),
384 datatype: RDF_LANG_STRING.to_owned(),
385 language: Some(language.to_owned()),
386 }));
387 }
388 (None, Some(datatype)) => (text.clone(), datatype.to_owned()),
389 (None, None) => (text.clone(), XSD_STRING.to_owned()),
390 },
391 _ => {
392 return Err(Error::Unsupported(
393 "a value object whose @value is not a scalar".to_owned(),
394 ));
395 }
396 };
397 Ok(Some(Term::Literal {
398 value: lexical,
399 datatype,
400 language: None,
401 }))
402}
403
404fn canonical_double(value: f64) -> String {
407 if value == 0.0 {
408 return "0.0E0".to_owned();
410 }
411 let formatted = format!("{value:.15E}");
412 let (mantissa, exponent) = formatted.split_once('E').unwrap_or((&formatted, "0"));
413 let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
414 let fraction = fraction.trim_end_matches('0');
415 let fraction = if fraction.is_empty() { "0" } else { fraction };
416 format!("{whole}.{fraction}E{exponent}")
417}
418
419fn well_formed_iri(value: &str) -> bool {
422 is_absolute_iri(value) && iri_string::types::IriStr::new(value).is_ok()
423}
424
425fn well_formed_language(tag: &str) -> bool {
427 let mut parts = tag.split('-');
428 let first = parts.next().unwrap_or("");
429 !first.is_empty()
430 && first.chars().all(|c| c.is_ascii_alphabetic())
431 && parts.all(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_alphanumeric()))
432}
433
434pub fn canonicalize(quads: &[Quad]) -> Result<String, Error> {
436 let mut state = Canonicalizer::new(quads);
437 state.label()?;
438 let mut lines: Vec<String> = quads
439 .iter()
440 .map(|quad| state.relabelled(quad))
441 .map(|quad| nquad(&quad))
442 .collect();
443 lines.sort();
444 lines.dedup();
445 Ok(lines.concat())
446}
447
448#[derive(Clone, Debug)]
451struct Issuer {
452 prefix: &'static str,
453 issued: BTreeMap<String, String>,
454 order: Vec<String>,
455}
456
457impl Issuer {
458 fn new(prefix: &'static str) -> Self {
459 Self {
460 prefix,
461 issued: BTreeMap::new(),
462 order: Vec::new(),
463 }
464 }
465
466 fn issue(&mut self, existing: &str) -> String {
467 if let Some(issued) = self.issued.get(existing) {
468 return issued.clone();
469 }
470 let issued = format!("{}{}", self.prefix, self.order.len());
471 self.issued.insert(existing.to_owned(), issued.clone());
472 self.order.push(existing.to_owned());
473 issued
474 }
475
476 fn get(&self, existing: &str) -> Option<&String> {
477 self.issued.get(existing)
478 }
479}
480
481struct Canonicalizer<'a> {
482 quads: &'a [Quad],
483 mentions: BTreeMap<String, Vec<usize>>,
485 canonical: Issuer,
486 first_degree: BTreeMap<String, String>,
487 budget: usize,
488}
489
490fn blank(term: &Term) -> Option<&str> {
491 match term {
492 Term::Blank(label) => Some(label),
493 _ => None,
494 }
495}
496
497impl<'a> Canonicalizer<'a> {
498 fn new(quads: &'a [Quad]) -> Self {
499 let mut mentions: BTreeMap<String, Vec<usize>> = BTreeMap::new();
500 for (index, quad) in quads.iter().enumerate() {
501 let mut seen = BTreeSet::new();
502 for term in [Some(&quad.subject), Some(&quad.object), quad.graph.as_ref()]
503 .into_iter()
504 .flatten()
505 {
506 if let Some(label) = blank(term)
507 && seen.insert(label)
508 {
509 mentions.entry(label.to_owned()).or_default().push(index);
510 }
511 }
512 }
513 Self {
514 quads,
515 mentions,
516 canonical: Issuer::new("c14n"),
517 first_degree: BTreeMap::new(),
518 budget: CANONICALIZATION_BUDGET,
519 }
520 }
521
522 fn spend(&mut self) -> Result<(), Error> {
523 self.budget = self
524 .budget
525 .checked_sub(1)
526 .ok_or(Error::CanonicalizationBudgetExceeded)?;
527 Ok(())
528 }
529
530 fn label(&mut self) -> Result<(), Error> {
533 let nodes: Vec<String> = self.mentions.keys().cloned().collect();
534 let mut by_hash: BTreeMap<String, Vec<String>> = BTreeMap::new();
535 for node in &nodes {
536 let hash = self.hash_first_degree(node);
537 by_hash.entry(hash).or_default().push(node.clone());
538 }
539 let mut shared = Vec::new();
541 for (_, group) in by_hash {
542 if group.len() == 1 {
543 self.canonical.issue(&group[0]);
544 } else {
545 shared.push(group);
546 }
547 }
548 for group in shared {
549 let mut results = Vec::new();
550 for node in group {
551 if self.canonical.get(&node).is_some() {
552 continue;
553 }
554 let mut issuer = Issuer::new("b");
555 issuer.issue(&node);
556 results.push(self.hash_n_degree(&node, issuer)?);
557 }
558 results.sort_by(|a, b| a.0.cmp(&b.0));
559 for (_, issuer) in results {
560 for existing in &issuer.order {
561 self.canonical.issue(existing);
562 }
563 }
564 }
565 Ok(())
566 }
567
568 fn hash_first_degree(&mut self, node: &str) -> String {
570 if let Some(hash) = self.first_degree.get(node) {
571 return hash.clone();
572 }
573 let mut lines: Vec<String> = self.mentions[node]
574 .iter()
575 .map(|&index| {
576 let quad = &self.quads[index];
577 let mark = |term: &Term| match term {
578 Term::Blank(label) if label == node => Term::Blank("a".to_owned()),
579 Term::Blank(_) => Term::Blank("z".to_owned()),
580 other => other.clone(),
581 };
582 nquad(&Quad {
583 subject: mark(&quad.subject),
584 predicate: quad.predicate.clone(),
585 object: mark(&quad.object),
586 graph: quad.graph.as_ref().map(mark),
587 })
588 })
589 .collect();
590 lines.sort();
591 let hash = sha256_hex(lines.concat().as_bytes());
592 self.first_degree.insert(node.to_owned(), hash.clone());
593 hash
594 }
595
596 fn hash_related(
598 &mut self,
599 related: &str,
600 quad: &Quad,
601 issuer: &Issuer,
602 position: &str,
603 ) -> String {
604 let identifier = match (self.canonical.get(related), issuer.get(related)) {
605 (Some(id), _) | (None, Some(id)) => format!("_:{id}"),
606 (None, None) => self.hash_first_degree(related),
607 };
608 let mut input = String::from(position);
609 if position != "g" {
610 input.push('<');
611 input.push_str(&quad.predicate);
612 input.push('>');
613 }
614 input.push_str(&identifier);
615 sha256_hex(input.as_bytes())
616 }
617
618 fn hash_n_degree(&mut self, node: &str, mut issuer: Issuer) -> Result<(String, Issuer), Error> {
620 self.spend()?;
621 let mut related_by_hash: BTreeMap<String, Vec<String>> = BTreeMap::new();
622 let mentions = self.mentions[node].clone();
623 for index in mentions {
624 let quad = &self.quads[index];
625 for (term, position) in [
626 (Some(&quad.subject), "s"),
627 (Some(&quad.object), "o"),
628 (quad.graph.as_ref(), "g"),
629 ] {
630 let Some(related) = term.and_then(blank) else {
631 continue;
632 };
633 if related == node {
634 continue;
635 }
636 let hash = self.hash_related(related, quad, &issuer, position);
637 related_by_hash
638 .entry(hash)
639 .or_default()
640 .push(related.to_owned());
641 }
642 }
643
644 let mut data = String::new();
645 for (hash, related) in related_by_hash {
646 data.push_str(&hash);
647 let mut chosen_path = String::new();
648 let mut chosen_issuer: Option<Issuer> = None;
649 for permutation in permutations(&related) {
650 self.spend()?;
651 let mut issuer_copy = issuer.clone();
652 let mut path = String::new();
653 let mut recursion = Vec::new();
654 let mut skip = false;
655 for node in &permutation {
656 if let Some(id) = self.canonical.get(node) {
657 path.push_str("_:");
658 path.push_str(id);
659 } else {
660 if issuer_copy.get(node).is_none() {
661 recursion.push(node.clone());
662 }
663 path.push_str("_:");
664 path.push_str(&issuer_copy.issue(node));
665 }
666 if !chosen_path.is_empty()
667 && path.len() >= chosen_path.len()
668 && path > chosen_path
669 {
670 skip = true;
671 break;
672 }
673 }
674 if skip {
675 continue;
676 }
677 for node in recursion {
678 let (result_hash, result_issuer) =
679 self.hash_n_degree(&node, issuer_copy.clone())?;
680 path.push_str("_:");
681 path.push_str(&issuer_copy.issue(&node));
682 path.push('<');
683 path.push_str(&result_hash);
684 path.push('>');
685 issuer_copy = result_issuer;
686 if !chosen_path.is_empty()
687 && path.len() >= chosen_path.len()
688 && path > chosen_path
689 {
690 skip = true;
691 break;
692 }
693 }
694 if skip {
695 continue;
696 }
697 if chosen_path.is_empty() || path < chosen_path {
698 chosen_path = path;
699 chosen_issuer = Some(issuer_copy);
700 }
701 }
702 data.push_str(&chosen_path);
703 if let Some(chosen) = chosen_issuer {
704 issuer = chosen;
705 }
706 }
707 Ok((sha256_hex(data.as_bytes()), issuer))
708 }
709
710 fn relabelled(&self, quad: &Quad) -> Quad {
711 let relabel = |term: &Term| match term {
712 Term::Blank(label) => Term::Blank(
713 self.canonical
714 .get(label)
715 .cloned()
716 .unwrap_or_else(|| label.clone()),
717 ),
718 other => other.clone(),
719 };
720 Quad {
721 subject: relabel(&quad.subject),
722 predicate: quad.predicate.clone(),
723 object: relabel(&quad.object),
724 graph: quad.graph.as_ref().map(relabel),
725 }
726 }
727}
728
729struct Permutations<'a> {
732 items: &'a [String],
733 order: Option<Vec<usize>>,
734}
735
736fn permutations(items: &[String]) -> Permutations<'_> {
737 Permutations {
738 items,
739 order: Some((0..items.len()).collect()),
740 }
741}
742
743impl Iterator for Permutations<'_> {
744 type Item = Vec<String>;
745
746 fn next(&mut self) -> Option<Vec<String>> {
747 let order = self.order.as_mut()?;
748 let current = order.iter().map(|&i| self.items[i].clone()).collect();
749 match (1..order.len()).rev().find(|&i| order[i - 1] < order[i]) {
751 Some(pivot) => {
752 let swap = (pivot..order.len())
753 .rev()
754 .find(|&j| order[j] > order[pivot - 1])
755 .unwrap_or(pivot);
756 order.swap(pivot - 1, swap);
757 order[pivot..].reverse();
758 }
759 None => self.order = None,
760 }
761 Some(current)
762 }
763}
764
765fn sha256_hex(bytes: &[u8]) -> String {
766 let digest = Sha256::digest(bytes);
767 let mut hex = String::with_capacity(64);
768 for byte in digest {
769 hex.push_str(&format!("{byte:02x}"));
770 }
771 hex
772}
773
774fn nquad(quad: &Quad) -> String {
776 let mut line = String::new();
777 write_term(&mut line, &quad.subject);
778 line.push_str(" <");
779 line.push_str(&quad.predicate);
780 line.push_str("> ");
781 write_term(&mut line, &quad.object);
782 if let Some(graph) = &quad.graph {
783 line.push(' ');
784 write_term(&mut line, graph);
785 }
786 line.push_str(" .\n");
787 line
788}
789
790fn write_term(out: &mut String, term: &Term) {
791 match term {
792 Term::Iri(iri) => {
793 out.push('<');
794 out.push_str(iri);
795 out.push('>');
796 }
797 Term::Blank(label) => {
798 out.push_str("_:");
799 out.push_str(label);
800 }
801 Term::Literal {
802 value,
803 datatype,
804 language,
805 } => {
806 out.push('"');
807 for c in value.chars() {
808 match c {
809 '\\' => out.push_str("\\\\"),
810 '"' => out.push_str("\\\""),
811 '\n' => out.push_str("\\n"),
812 '\r' => out.push_str("\\r"),
813 '\t' => out.push_str("\\t"),
814 '\u{8}' => out.push_str("\\b"),
815 '\u{c}' => out.push_str("\\f"),
816 c if (c as u32) < 0x20 || c == '\u{7f}' => {
817 out.push_str(&format!("\\u{:04X}", c as u32));
818 }
819 c => out.push(c),
820 }
821 }
822 out.push('"');
823 if let Some(language) = language {
824 out.push('@');
825 out.push_str(language);
826 } else if datatype != XSD_STRING {
827 out.push_str("^^<");
828 out.push_str(datatype);
829 out.push('>');
830 }
831 }
832 }
833}
834
835#[cfg(test)]
836mod tests {
837 use super::*;
838
839 #[test]
840 fn doubles_are_written_as_xsd_writes_them() {
841 assert_eq!(canonical_double(0.5), "5.0E-1");
842 assert_eq!(canonical_double(-0.5), "-5.0E-1");
843 assert_eq!(canonical_double(0.0), "0.0E0");
844 assert_eq!(canonical_double(-0.0), "0.0E0");
845 assert_eq!(canonical_double(1e22), "1.0E22");
846 assert_eq!(canonical_double(0.300_000_000_000_000_04), "3.0E-1");
847 assert_eq!(canonical_double(1.25), "1.25E0");
848 }
849
850 #[test]
851 fn every_ordering_is_tried_once() {
852 let items: Vec<String> = ["a", "b", "c"].iter().map(|s| (*s).to_owned()).collect();
853 let all: Vec<String> = permutations(&items).map(|p| p.concat()).collect();
854 assert_eq!(all, ["abc", "acb", "bac", "bca", "cab", "cba"]);
855 assert_eq!(permutations(&[]).count(), 1);
856 }
857
858 #[test]
859 fn language_tags_are_checked() {
860 assert!(well_formed_language("en"));
861 assert!(well_formed_language("zh-hant-tw"));
862 assert!(!well_formed_language("en_US"));
863 assert!(!well_formed_language(""));
864 assert!(!well_formed_language("en-"));
865 }
866}