1pub mod ai_converter;
18pub mod akoma_ntoso;
19#[cfg(feature = "async")]
20pub mod async_converter;
21pub mod basel3;
22#[cfg(feature = "batch")]
23pub mod batch;
24pub mod blockchain_docs;
25pub mod bpmn;
26pub mod cache;
27pub mod cadence;
28pub mod catala;
29pub mod cicero;
30pub mod clauseio;
31pub mod cli;
32pub mod cmmn;
33pub mod commonform;
34pub mod compatibility;
35pub mod contractexpress;
36pub mod coverage;
37pub mod creative_commons;
38pub mod dmn;
39pub mod dms;
40pub mod docusign;
41#[cfg(test)]
42mod edge_cases_tests;
43pub mod enhanced;
44pub mod error_handling;
45pub mod errors;
46pub mod fidelity;
47pub mod finreg;
48pub mod format_detection;
49pub mod format_validation;
50pub mod formex;
51pub mod incremental;
52pub mod l4;
53pub mod legalcite;
54pub mod legaldocml;
55pub mod legalruleml;
56pub mod lkif;
57pub mod metalex;
58pub mod metrics;
59pub mod mifid2;
60pub mod move_lang;
61pub mod mpeg21_rel;
62pub mod msword_legal;
63pub mod niem;
64pub mod openlaw;
65pub mod optimizations;
66pub mod pdf_legal;
67pub mod performance;
68pub mod quality;
69pub mod regml;
70pub mod rest_api;
71pub mod ruleml;
72pub mod salesforce_contract;
73pub mod sap_legal;
74pub mod sbvr;
75pub mod schema;
76pub mod solidity;
77pub mod spdx;
78pub mod stipula;
79pub mod streaming;
80pub mod streaming_v2;
81pub mod transformation;
82pub mod universal_format;
83pub mod validation;
84pub mod vyper;
85pub mod webhooks;
86pub mod xbrl;
87
88use legalis_core::Statute;
89use serde::{Deserialize, Serialize};
90use thiserror::Error;
91
92#[derive(Debug, Error)]
94pub enum InteropError {
95 #[error("Parse error: {0}")]
96 ParseError(String),
97
98 #[error("Unsupported format: {0}")]
99 UnsupportedFormat(String),
100
101 #[error("Conversion error: {0}")]
102 ConversionError(String),
103
104 #[error("Feature not supported in target format: {0}")]
105 UnsupportedFeature(String),
106
107 #[error("IO error: {0}")]
108 IoError(#[from] std::io::Error),
109
110 #[error("Serialization error: {0}")]
111 SerializationError(String),
112
113 #[error("Validation error: {0}")]
114 ValidationError(String),
115}
116
117pub type InteropResult<T> = Result<T, InteropError>;
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
122pub enum LegalFormat {
123 Catala,
125 Stipula,
127 L4,
129 AkomaNtoso,
131 LegalRuleML,
133 LegalDocML,
135 LKIF,
137 LegalCite,
139 MetaLex,
141 Mpeg21Rel,
143 CreativeCommons,
145 Spdx,
147 Legalis,
149 Bpmn,
151 Dmn,
153 Cmmn,
155 RuleML,
157 Sbvr,
159 OpenLaw,
161 Cicero,
163 CommonForm,
165 ClauseIo,
167 ContractExpress,
169 Formex,
171 Niem,
173 FinReg,
175 Xbrl,
177 RegML,
179 MiFID2,
181 Basel3,
183 SapLegal,
185 SalesforceContract,
187 DocuSign,
189 MsWordLegal,
191 PdfLegal,
193 Solidity,
195 Vyper,
197 Cadence,
199 Move,
201}
202
203impl LegalFormat {
204 pub fn extension(&self) -> &'static str {
206 match self {
207 LegalFormat::Catala => "catala_en",
208 LegalFormat::Stipula => "stipula",
209 LegalFormat::L4 => "l4",
210 LegalFormat::AkomaNtoso => "xml",
211 LegalFormat::LegalRuleML => "xml",
212 LegalFormat::LegalDocML => "xml",
213 LegalFormat::LKIF => "xml",
214 LegalFormat::LegalCite => "xml",
215 LegalFormat::MetaLex => "xml",
216 LegalFormat::Mpeg21Rel => "xml",
217 LegalFormat::CreativeCommons => "rdf",
218 LegalFormat::Spdx => "spdx",
219 LegalFormat::Legalis => "legal",
220 LegalFormat::Bpmn => "bpmn",
221 LegalFormat::Dmn => "dmn",
222 LegalFormat::Cmmn => "cmmn",
223 LegalFormat::RuleML => "ruleml",
224 LegalFormat::Sbvr => "sbvr",
225 LegalFormat::OpenLaw => "openlaw",
226 LegalFormat::Cicero => "cicero",
227 LegalFormat::CommonForm => "json",
228 LegalFormat::ClauseIo => "json",
229 LegalFormat::ContractExpress => "docx",
230 LegalFormat::Formex => "xml",
231 LegalFormat::Niem => "xml",
232 LegalFormat::FinReg => "json",
233 LegalFormat::Xbrl => "xbrl",
234 LegalFormat::RegML => "xml",
235 LegalFormat::MiFID2 => "json",
236 LegalFormat::Basel3 => "json",
237 LegalFormat::SapLegal => "json",
238 LegalFormat::SalesforceContract => "json",
239 LegalFormat::DocuSign => "json",
240 LegalFormat::MsWordLegal => "json",
241 LegalFormat::PdfLegal => "json",
242 LegalFormat::Solidity => "sol",
243 LegalFormat::Vyper => "vy",
244 LegalFormat::Cadence => "cdc",
245 LegalFormat::Move => "move",
246 }
247 }
248
249 pub fn from_extension(ext: &str) -> Option<Self> {
251 match ext.to_lowercase().as_str() {
252 "catala_en" | "catala_fr" | "catala" => Some(LegalFormat::Catala),
253 "stipula" => Some(LegalFormat::Stipula),
254 "l4" => Some(LegalFormat::L4),
255 "lkif" => Some(LegalFormat::LKIF),
256 "rdf" => Some(LegalFormat::CreativeCommons),
257 "spdx" => Some(LegalFormat::Spdx),
258 "legal" => Some(LegalFormat::Legalis),
259 "bpmn" => Some(LegalFormat::Bpmn),
260 "dmn" => Some(LegalFormat::Dmn),
261 "cmmn" => Some(LegalFormat::Cmmn),
262 "ruleml" => Some(LegalFormat::RuleML),
263 "sbvr" => Some(LegalFormat::Sbvr),
264 "openlaw" => Some(LegalFormat::OpenLaw),
265 "cicero" => Some(LegalFormat::Cicero),
266 "commonform" | "commonform.json" => Some(LegalFormat::CommonForm),
267 "clauseio" | "clauseio.json" => Some(LegalFormat::ClauseIo),
268 "contractexpress" | "docx" => Some(LegalFormat::ContractExpress),
269 "formex" => Some(LegalFormat::Formex),
270 "niem" => Some(LegalFormat::Niem),
271 "finreg" | "finreg.json" => Some(LegalFormat::FinReg),
272 "xbrl" => Some(LegalFormat::Xbrl),
273 "regml" | "regml.xml" => Some(LegalFormat::RegML),
274 "mifid2" | "mifid2.json" => Some(LegalFormat::MiFID2),
275 "basel3" | "basel3.json" => Some(LegalFormat::Basel3),
276 "saplegal" | "sap.json" | "sap-legal.json" => Some(LegalFormat::SapLegal),
277 "salesforce" | "sfdc.json" | "salesforce-contract.json" => {
278 Some(LegalFormat::SalesforceContract)
279 }
280 "docusign" | "docusign.json" | "envelope.json" => Some(LegalFormat::DocuSign),
281 "msword" | "word-legal.json" | "msword-legal.json" => Some(LegalFormat::MsWordLegal),
282 "pdf-legal" | "pdf-annotations.json" | "pdf-legal.json" => Some(LegalFormat::PdfLegal),
283 "sol" | "solidity" => Some(LegalFormat::Solidity),
284 "vy" | "vyper" => Some(LegalFormat::Vyper),
285 "cdc" | "cadence" => Some(LegalFormat::Cadence),
286 "move" => Some(LegalFormat::Move),
287 _ => None,
288 }
289 }
290}
291
292#[derive(Debug, Clone, Default, Serialize, Deserialize)]
294pub struct ConversionReport {
295 pub source_format: Option<LegalFormat>,
297 pub target_format: Option<LegalFormat>,
299 pub unsupported_features: Vec<String>,
301 pub warnings: Vec<String>,
303 pub confidence: f64,
305 pub statutes_converted: usize,
307}
308
309impl ConversionReport {
310 pub fn new(source: LegalFormat, target: LegalFormat) -> Self {
312 Self {
313 source_format: Some(source),
314 target_format: Some(target),
315 confidence: 1.0,
316 ..Default::default()
317 }
318 }
319
320 pub fn add_unsupported(&mut self, feature: impl Into<String>) {
322 self.unsupported_features.push(feature.into());
323 self.confidence = (self.confidence - 0.1).max(0.0);
324 }
325
326 pub fn add_warning(&mut self, warning: impl Into<String>) {
328 self.warnings.push(warning.into());
329 self.confidence = (self.confidence - 0.05).max(0.0);
330 }
331
332 pub fn is_high_quality(&self) -> bool {
334 self.confidence >= 0.8
335 }
336
337 pub fn is_lossless(&self) -> bool {
339 self.confidence >= 1.0 && self.unsupported_features.is_empty() && self.warnings.is_empty()
340 }
341}
342
343pub trait FormatImporter: Send + Sync {
345 fn format(&self) -> LegalFormat;
347
348 fn import(&self, source: &str) -> InteropResult<(Vec<Statute>, ConversionReport)>;
350
351 fn validate(&self, source: &str) -> bool;
353}
354
355pub trait FormatExporter: Send + Sync {
357 fn format(&self) -> LegalFormat;
359
360 fn export(&self, statutes: &[Statute]) -> InteropResult<(String, ConversionReport)>;
362
363 fn can_represent(&self, statute: &Statute) -> Vec<String>;
365}
366
367pub struct LegalConverter {
369 importers: Vec<Box<dyn FormatImporter>>,
370 exporters: Vec<Box<dyn FormatExporter>>,
371 cache: Option<cache::ConversionCache>,
372}
373
374impl Default for LegalConverter {
375 fn default() -> Self {
376 Self::new()
377 }
378}
379
380impl LegalConverter {
381 pub fn new() -> Self {
383 Self {
384 importers: vec![
385 Box::new(catala::CatalaImporter::new()),
386 Box::new(stipula::StipulaImporter::new()),
387 Box::new(l4::L4Importer::new()),
388 Box::new(akoma_ntoso::AkomaNtosoImporter::new()),
389 Box::new(legalruleml::LegalRuleMLImporter::new()),
390 Box::new(legaldocml::LegalDocMLImporter::new()),
391 Box::new(lkif::LkifImporter::new()),
392 Box::new(legalcite::LegalCiteImporter::new()),
393 Box::new(metalex::MetaLexImporter::new()),
394 Box::new(mpeg21_rel::Mpeg21RelImporter::new()),
395 Box::new(creative_commons::CreativeCommonsImporter::new()),
396 Box::new(spdx::SpdxImporter::new()),
397 Box::new(bpmn::BpmnImporter::new()),
398 Box::new(dmn::DmnImporter::new()),
399 Box::new(cmmn::CmmnImporter::new()),
400 Box::new(ruleml::RuleMLImporter::new()),
401 Box::new(sbvr::SbvrImporter::new()),
402 Box::new(openlaw::OpenLawImporter::new()),
403 Box::new(cicero::CiceroImporter::new()),
404 Box::new(commonform::CommonFormImporter::new()),
405 Box::new(clauseio::ClauseIoImporter::new()),
406 Box::new(contractexpress::ContractExpressImporter::new()),
407 Box::new(formex::FormexImporter::new()),
408 Box::new(niem::NiemImporter::new()),
409 Box::new(finreg::FinRegImporter::new()),
410 Box::new(xbrl::XbrlImporter::new()),
411 Box::new(regml::RegMLImporter::new()),
412 Box::new(mifid2::MiFID2Importer::new()),
413 Box::new(basel3::Basel3Importer::new()),
414 Box::new(sap_legal::SapLegalImporter::new()),
415 Box::new(salesforce_contract::SalesforceContractImporter::new()),
416 Box::new(docusign::DocuSignImporter::new()),
417 Box::new(msword_legal::MsWordLegalImporter::new()),
418 Box::new(pdf_legal::PdfLegalImporter::new()),
419 Box::new(solidity::SolidityImporter::new()),
420 Box::new(vyper::VyperImporter::new()),
421 Box::new(cadence::CadenceImporter::new()),
422 Box::new(move_lang::MoveImporter::new()),
423 ],
424 exporters: vec![
425 Box::new(catala::CatalaExporter::new()),
426 Box::new(stipula::StipulaExporter::new()),
427 Box::new(l4::L4Exporter::new()),
428 Box::new(akoma_ntoso::AkomaNtosoExporter::new()),
429 Box::new(legalruleml::LegalRuleMLExporter::new()),
430 Box::new(legaldocml::LegalDocMLExporter::new()),
431 Box::new(lkif::LkifExporter::new()),
432 Box::new(legalcite::LegalCiteExporter::new()),
433 Box::new(metalex::MetaLexExporter::new()),
434 Box::new(mpeg21_rel::Mpeg21RelExporter::new()),
435 Box::new(creative_commons::CreativeCommonsExporter::new()),
436 Box::new(spdx::SpdxExporter::new()),
437 Box::new(bpmn::BpmnExporter::new()),
438 Box::new(dmn::DmnExporter::new()),
439 Box::new(cmmn::CmmnExporter::new()),
440 Box::new(ruleml::RuleMLExporter::new()),
441 Box::new(sbvr::SbvrExporter::new()),
442 Box::new(openlaw::OpenLawExporter::new()),
443 Box::new(cicero::CiceroExporter::new()),
444 Box::new(commonform::CommonFormExporter::new()),
445 Box::new(clauseio::ClauseIoExporter::new()),
446 Box::new(contractexpress::ContractExpressExporter::new()),
447 Box::new(formex::FormexExporter::new()),
448 Box::new(niem::NiemExporter::new()),
449 Box::new(finreg::FinRegExporter::new()),
450 Box::new(xbrl::XbrlExporter::new()),
451 Box::new(regml::RegMLExporter::new()),
452 Box::new(mifid2::MiFID2Exporter::new()),
453 Box::new(basel3::Basel3Exporter::new()),
454 Box::new(sap_legal::SapLegalExporter::new()),
455 Box::new(salesforce_contract::SalesforceContractExporter::new()),
456 Box::new(docusign::DocuSignExporter::new()),
457 Box::new(msword_legal::MsWordLegalExporter::new()),
458 Box::new(pdf_legal::PdfLegalExporter::new()),
459 Box::new(solidity::SolidityExporter::new()),
460 Box::new(vyper::VyperExporter::new()),
461 Box::new(cadence::CadenceExporter::new()),
462 Box::new(move_lang::MoveExporter::new()),
463 ],
464 cache: None,
465 }
466 }
467
468 pub fn with_cache(cache_size: usize) -> Self {
470 let mut converter = Self::new();
471 converter.cache = Some(cache::ConversionCache::with_capacity(cache_size));
472 converter
473 }
474
475 pub fn enable_cache(&mut self, cache_size: usize) {
477 self.cache = Some(cache::ConversionCache::with_capacity(cache_size));
478 }
479
480 pub fn disable_cache(&mut self) {
482 self.cache = None;
483 }
484
485 pub fn clear_cache(&mut self) {
487 if let Some(cache) = &mut self.cache {
488 cache.clear();
489 }
490 }
491
492 pub fn cache_stats(&self) -> Option<cache::CacheStats> {
494 self.cache.as_ref().map(|c| c.stats())
495 }
496
497 pub fn import(
499 &mut self,
500 source: &str,
501 format: LegalFormat,
502 ) -> InteropResult<(Vec<Statute>, ConversionReport)> {
503 if let Some(cache) = &mut self.cache
505 && let Some(cached) = cache.get_import(source, format)
506 {
507 return Ok(cached);
508 }
509
510 let importer = self
511 .importers
512 .iter()
513 .find(|i| i.format() == format)
514 .ok_or_else(|| InteropError::UnsupportedFormat(format!("{:?}", format)))?;
515
516 let result = importer.import(source)?;
517
518 if let Some(cache) = &mut self.cache {
520 cache.put_import(source, format, result.0.clone(), result.1.clone());
521 }
522
523 Ok(result)
524 }
525
526 pub fn export(
528 &mut self,
529 statutes: &[Statute],
530 format: LegalFormat,
531 ) -> InteropResult<(String, ConversionReport)> {
532 let exporter = self
533 .exporters
534 .iter()
535 .find(|e| e.format() == format)
536 .ok_or_else(|| InteropError::UnsupportedFormat(format!("{:?}", format)))?;
537
538 exporter.export(statutes)
539 }
540
541 pub fn convert(
543 &mut self,
544 source: &str,
545 from: LegalFormat,
546 to: LegalFormat,
547 ) -> InteropResult<(String, ConversionReport)> {
548 if let Some(cache) = &mut self.cache
550 && let Some(cached) = cache.get_export(source, from, to)
551 {
552 return Ok(cached);
553 }
554
555 let (statutes, mut import_report) = self.import(source, from)?;
556 let (output, export_report) = self.export(&statutes, to)?;
557
558 import_report.target_format = Some(to);
560 import_report
561 .unsupported_features
562 .extend(export_report.unsupported_features);
563 import_report.warnings.extend(export_report.warnings);
564 import_report.confidence = (import_report.confidence * export_report.confidence).max(0.0);
565
566 if let Some(cache) = &mut self.cache {
568 cache.put_export(source, from, to, output.clone(), import_report.clone());
569 }
570
571 Ok((output, import_report))
572 }
573
574 pub fn auto_import(&mut self, source: &str) -> InteropResult<(Vec<Statute>, ConversionReport)> {
576 for importer in &self.importers {
577 if importer.validate(source) {
578 let format = importer.format();
579 return self.import(source, format);
580 }
581 }
582 Err(InteropError::UnsupportedFormat(
583 "Could not auto-detect format".to_string(),
584 ))
585 }
586
587 pub fn supported_imports(&self) -> Vec<LegalFormat> {
589 self.importers.iter().map(|i| i.format()).collect()
590 }
591
592 pub fn supported_exports(&self) -> Vec<LegalFormat> {
594 self.exporters.iter().map(|e| e.format()).collect()
595 }
596
597 pub fn batch_convert(
606 &mut self,
607 sources: &[(String, LegalFormat)],
608 target_format: LegalFormat,
609 ) -> InteropResult<Vec<(String, ConversionReport)>> {
610 let mut results = Vec::with_capacity(sources.len());
611
612 for (source_text, source_format) in sources {
613 match self.convert(source_text, *source_format, target_format) {
614 Ok(result) => results.push(result),
615 Err(e) => {
616 let mut report = ConversionReport::new(*source_format, target_format);
618 report.add_warning(format!("Conversion failed: {}", e));
619 report.confidence = 0.0;
620 results.push((String::new(), report));
621 }
622 }
623 }
624
625 Ok(results)
626 }
627
628 pub fn batch_import(
636 &mut self,
637 sources: &[(String, LegalFormat)],
638 ) -> InteropResult<Vec<(Vec<Statute>, ConversionReport)>> {
639 let mut results = Vec::with_capacity(sources.len());
640
641 for (source_text, source_format) in sources {
642 match self.import(source_text, *source_format) {
643 Ok(result) => results.push(result),
644 Err(e) => {
645 let mut report = ConversionReport::new(*source_format, LegalFormat::Legalis);
647 report.add_warning(format!("Import failed: {}", e));
648 report.confidence = 0.0;
649 results.push((Vec::new(), report));
650 }
651 }
652 }
653
654 Ok(results)
655 }
656
657 pub fn batch_export(
666 &mut self,
667 statutes: &[Statute],
668 target_formats: &[LegalFormat],
669 ) -> InteropResult<Vec<(LegalFormat, String, ConversionReport)>> {
670 let mut results = Vec::with_capacity(target_formats.len());
671
672 for &format in target_formats {
673 match self.export(statutes, format) {
674 Ok((output, report)) => results.push((format, output, report)),
675 Err(e) => {
676 let mut report = ConversionReport::new(LegalFormat::Legalis, format);
678 report.add_warning(format!("Export failed: {}", e));
679 report.confidence = 0.0;
680 results.push((format, String::new(), report));
681 }
682 }
683 }
684
685 Ok(results)
686 }
687
688 #[cfg(feature = "parallel")]
700 pub fn batch_convert_parallel(
701 sources: &[(String, LegalFormat)],
702 target_format: LegalFormat,
703 ) -> InteropResult<Vec<(String, ConversionReport)>> {
704 use rayon::prelude::*;
705
706 let results: Vec<_> = sources
707 .par_iter()
708 .map(|(source_text, source_format)| {
709 let mut converter = Self::new();
710 match converter.convert(source_text, *source_format, target_format) {
711 Ok(result) => result,
712 Err(e) => {
713 let mut report = ConversionReport::new(*source_format, target_format);
714 report.add_warning(format!("Conversion failed: {}", e));
715 report.confidence = 0.0;
716 (String::new(), report)
717 }
718 }
719 })
720 .collect();
721
722 Ok(results)
723 }
724
725 #[cfg(feature = "parallel")]
735 pub fn batch_import_parallel(
736 sources: &[(String, LegalFormat)],
737 ) -> InteropResult<Vec<(Vec<Statute>, ConversionReport)>> {
738 use rayon::prelude::*;
739
740 let results: Vec<_> = sources
741 .par_iter()
742 .map(|(source_text, source_format)| {
743 let mut converter = Self::new();
744 match converter.import(source_text, *source_format) {
745 Ok(result) => result,
746 Err(e) => {
747 let mut report =
748 ConversionReport::new(*source_format, LegalFormat::Legalis);
749 report.add_warning(format!("Import failed: {}", e));
750 report.confidence = 0.0;
751 (Vec::new(), report)
752 }
753 }
754 })
755 .collect();
756
757 Ok(results)
758 }
759
760 #[cfg(feature = "parallel")]
771 pub fn batch_export_parallel(
772 statutes: &[Statute],
773 target_formats: &[LegalFormat],
774 ) -> InteropResult<Vec<(LegalFormat, String, ConversionReport)>> {
775 use rayon::prelude::*;
776
777 let results: Vec<_> = target_formats
778 .par_iter()
779 .map(|&format| {
780 let mut converter = Self::new();
781 match converter.export(statutes, format) {
782 Ok((output, report)) => (format, output, report),
783 Err(e) => {
784 let mut report = ConversionReport::new(LegalFormat::Legalis, format);
785 report.add_warning(format!("Export failed: {}", e));
786 report.confidence = 0.0;
787 (format, String::new(), report)
788 }
789 }
790 })
791 .collect();
792
793 Ok(results)
794 }
795
796 pub fn validate_roundtrip(
808 &mut self,
809 source: &str,
810 source_format: LegalFormat,
811 target_format: LegalFormat,
812 ) -> InteropResult<SemanticValidation> {
813 let (original_statutes, import_report) = self.import(source, source_format)?;
815
816 let (target_output, convert_report) = self.export(&original_statutes, target_format)?;
818
819 let (roundtrip_statutes, reimport_report) = self.import(&target_output, target_format)?;
821
822 let mut validation = SemanticValidation::new(source_format, target_format);
824
825 if original_statutes.len() != roundtrip_statutes.len() {
827 validation.add_issue(format!(
828 "Statute count changed: {} -> {}",
829 original_statutes.len(),
830 roundtrip_statutes.len()
831 ));
832 }
833
834 for (i, (original, roundtrip)) in original_statutes
836 .iter()
837 .zip(roundtrip_statutes.iter())
838 .enumerate()
839 {
840 if original.preconditions.len() != roundtrip.preconditions.len() {
842 validation.add_issue(format!(
843 "Statute {}: Precondition count changed: {} -> {}",
844 i,
845 original.preconditions.len(),
846 roundtrip.preconditions.len()
847 ));
848 }
849
850 if original.effect.effect_type != roundtrip.effect.effect_type {
852 validation.add_issue(format!(
853 "Statute {}: Effect type changed: {:?} -> {:?}",
854 i, original.effect.effect_type, roundtrip.effect.effect_type
855 ));
856 }
857 }
858
859 validation.confidence =
861 (import_report.confidence * convert_report.confidence * reimport_report.confidence)
862 .max(0.0);
863
864 validation.import_report = import_report;
865 validation.convert_report = convert_report;
866 validation.reimport_report = reimport_report;
867
868 Ok(validation)
869 }
870}
871
872#[derive(Debug, Clone)]
874pub struct SemanticValidation {
875 pub source_format: LegalFormat,
877 pub target_format: LegalFormat,
879 pub issues: Vec<String>,
881 pub confidence: f64,
883 pub import_report: ConversionReport,
885 pub convert_report: ConversionReport,
887 pub reimport_report: ConversionReport,
889}
890
891impl SemanticValidation {
892 pub fn new(source: LegalFormat, target: LegalFormat) -> Self {
894 Self {
895 source_format: source,
896 target_format: target,
897 issues: Vec::new(),
898 confidence: 1.0,
899 import_report: ConversionReport::new(source, LegalFormat::Legalis),
900 convert_report: ConversionReport::new(LegalFormat::Legalis, target),
901 reimport_report: ConversionReport::new(target, LegalFormat::Legalis),
902 }
903 }
904
905 pub fn add_issue(&mut self, issue: impl Into<String>) {
907 self.issues.push(issue.into());
908 self.confidence = (self.confidence - 0.1).max(0.0);
909 }
910
911 pub fn passed(&self) -> bool {
913 self.issues.is_empty() && self.confidence >= 0.8
914 }
915
916 pub fn is_perfect(&self) -> bool {
918 self.issues.is_empty() && self.confidence >= 1.0
919 }
920}
921
922#[cfg(test)]
923mod tests {
924 use super::*;
925 use legalis_core::{ComparisonOp, Condition, Effect, EffectType};
926
927 #[test]
928 fn test_format_extension() {
929 assert_eq!(LegalFormat::Catala.extension(), "catala_en");
930 assert_eq!(LegalFormat::Stipula.extension(), "stipula");
931 assert_eq!(LegalFormat::L4.extension(), "l4");
932 }
933
934 #[test]
935 fn test_format_from_extension() {
936 assert_eq!(
937 LegalFormat::from_extension("catala_en"),
938 Some(LegalFormat::Catala)
939 );
940 assert_eq!(
941 LegalFormat::from_extension("stipula"),
942 Some(LegalFormat::Stipula)
943 );
944 assert_eq!(LegalFormat::from_extension("l4"), Some(LegalFormat::L4));
945 assert_eq!(LegalFormat::from_extension("unknown"), None);
946 }
947
948 #[test]
949 fn test_conversion_report() {
950 let mut report = ConversionReport::new(LegalFormat::Catala, LegalFormat::Legalis);
951 assert_eq!(report.confidence, 1.0);
952
953 report.add_unsupported("scopes");
954 assert!(report.confidence < 1.0);
955
956 report.add_warning("Date format normalized");
957 assert!(report.unsupported_features.contains(&"scopes".to_string()));
958 }
959
960 #[test]
961 fn test_converter_supported_formats() {
962 let converter = LegalConverter::new();
963 let imports = converter.supported_imports();
964 let exports = converter.supported_exports();
965
966 assert!(imports.contains(&LegalFormat::Catala));
967 assert!(imports.contains(&LegalFormat::Stipula));
968 assert!(imports.contains(&LegalFormat::L4));
969 assert!(imports.contains(&LegalFormat::AkomaNtoso));
970
971 assert!(exports.contains(&LegalFormat::Catala));
972 assert!(exports.contains(&LegalFormat::Stipula));
973 assert!(exports.contains(&LegalFormat::L4));
974 assert!(exports.contains(&LegalFormat::AkomaNtoso));
975 }
976
977 #[test]
978 fn test_catala_export_import_roundtrip() {
979 let mut converter = LegalConverter::new();
980
981 let statute = Statute::new(
983 "voting-rights",
984 "Voting Rights",
985 Effect::new(EffectType::Grant, "vote"),
986 )
987 .with_precondition(Condition::Age {
988 operator: ComparisonOp::GreaterOrEqual,
989 value: 18,
990 });
991
992 let (catala_output, export_report) =
994 converter.export(&[statute], LegalFormat::Catala).unwrap();
995 assert_eq!(export_report.statutes_converted, 1);
996 assert!(catala_output.contains("declaration scope VotingRights"));
997 assert!(catala_output.contains("input.age >= 18"));
998
999 let (imported, import_report) = converter
1001 .import(&catala_output, LegalFormat::Catala)
1002 .unwrap();
1003 assert_eq!(import_report.statutes_converted, 1);
1004 assert_eq!(imported.len(), 1);
1005 assert_eq!(imported[0].id, "votingrights");
1006 }
1007
1008 #[test]
1009 fn test_stipula_export_import_roundtrip() {
1010 let mut converter = LegalConverter::new();
1011
1012 let statute = Statute::new(
1014 "simple-contract",
1015 "Simple Contract",
1016 Effect::new(EffectType::Grant, "execute"),
1017 )
1018 .with_precondition(Condition::Age {
1019 operator: ComparisonOp::GreaterOrEqual,
1020 value: 21,
1021 });
1022
1023 let (stipula_output, export_report) =
1025 converter.export(&[statute], LegalFormat::Stipula).unwrap();
1026 assert_eq!(export_report.statutes_converted, 1);
1027 assert!(stipula_output.contains("agreement SimpleContract"));
1028 assert!(stipula_output.contains("age >= 21"));
1029
1030 let (imported, import_report) = converter
1032 .import(&stipula_output, LegalFormat::Stipula)
1033 .unwrap();
1034 assert_eq!(import_report.statutes_converted, 1);
1035 assert_eq!(imported.len(), 1);
1036 assert_eq!(imported[0].id, "simplecontract");
1037 }
1038
1039 #[test]
1040 fn test_l4_export_import_roundtrip() {
1041 let mut converter = LegalConverter::new();
1042
1043 let statute = Statute::new(
1045 "adult-rights",
1046 "Adult Rights",
1047 Effect::new(EffectType::Grant, "full_capacity"),
1048 )
1049 .with_precondition(Condition::Age {
1050 operator: ComparisonOp::GreaterOrEqual,
1051 value: 18,
1052 });
1053
1054 let (l4_output, export_report) = converter.export(&[statute], LegalFormat::L4).unwrap();
1056 assert_eq!(export_report.statutes_converted, 1);
1057 assert!(l4_output.contains("RULE AdultRights"));
1058 assert!(l4_output.contains("age >= 18"));
1059 assert!(l4_output.contains("MAY"));
1060
1061 let (imported, import_report) = converter.import(&l4_output, LegalFormat::L4).unwrap();
1063 assert_eq!(import_report.statutes_converted, 1);
1064 assert_eq!(imported.len(), 1);
1065 }
1066
1067 #[test]
1068 fn test_catala_to_l4_conversion() {
1069 let mut converter = LegalConverter::new();
1070
1071 let catala_source = r#"
1072```catala
1073declaration scope TaxBenefit:
1074 context input content Input
1075 context output content Output
1076```
1077
1078```catala
1079scope TaxBenefit:
1080 definition output.eligible equals
1081 input.age >= 65
1082```
1083"#;
1084
1085 let (l4_output, report) = converter
1087 .convert(catala_source, LegalFormat::Catala, LegalFormat::L4)
1088 .unwrap();
1089
1090 assert!(report.statutes_converted >= 1);
1091 assert!(l4_output.contains("RULE"));
1092 }
1093
1094 #[test]
1095 fn test_auto_detect_catala() {
1096 let mut converter = LegalConverter::new();
1097
1098 let catala_source = r#"
1099declaration scope Test:
1100 context input content integer
1101"#;
1102
1103 let (statutes, report) = converter.auto_import(catala_source).unwrap();
1104 assert_eq!(report.source_format, Some(LegalFormat::Catala));
1105 assert!(!statutes.is_empty());
1106 }
1107
1108 #[test]
1109 fn test_auto_detect_stipula() {
1110 let mut converter = LegalConverter::new();
1111
1112 let stipula_source = "agreement TestContract(Alice, Bob) { }";
1113
1114 let (statutes, report) = converter.auto_import(stipula_source).unwrap();
1115 assert_eq!(report.source_format, Some(LegalFormat::Stipula));
1116 assert!(!statutes.is_empty());
1117 }
1118
1119 #[test]
1120 fn test_auto_detect_l4() {
1121 let mut converter = LegalConverter::new();
1122
1123 let l4_source = "RULE TestRule WHEN age >= 18 THEN Person MAY vote";
1124
1125 let (statutes, report) = converter.auto_import(l4_source).unwrap();
1126 assert_eq!(report.source_format, Some(LegalFormat::L4));
1127 assert!(!statutes.is_empty());
1128 }
1129
1130 #[test]
1131 fn test_akoma_ntoso_export_import_roundtrip() {
1132 let mut converter = LegalConverter::new();
1133
1134 let statute = Statute::new(
1136 "adult-capacity",
1137 "Adult Capacity Act",
1138 Effect::new(EffectType::Grant, "Full legal capacity"),
1139 )
1140 .with_precondition(Condition::Age {
1141 operator: ComparisonOp::GreaterOrEqual,
1142 value: 18,
1143 });
1144
1145 let (akn_output, export_report) = converter
1147 .export(&[statute], LegalFormat::AkomaNtoso)
1148 .unwrap();
1149 assert_eq!(export_report.statutes_converted, 1);
1150 assert!(akn_output.contains("<akomaNtoso"));
1151 assert!(akn_output.contains("Adult Capacity Act"));
1152
1153 let (imported, import_report) = converter
1155 .import(&akn_output, LegalFormat::AkomaNtoso)
1156 .unwrap();
1157 assert_eq!(import_report.statutes_converted, 1);
1158 assert_eq!(imported.len(), 1);
1159 assert_eq!(imported[0].title, "Adult Capacity Act");
1160 }
1161
1162 #[test]
1163 fn test_auto_detect_akoma_ntoso() {
1164 let mut converter = LegalConverter::new();
1165
1166 let akn_source = r#"
1167 <akomaNtoso>
1168 <act>
1169 <body>
1170 <article eId="art_1">
1171 <heading>Test Article</heading>
1172 </article>
1173 </body>
1174 </act>
1175 </akomaNtoso>
1176 "#;
1177
1178 let (statutes, report) = converter.auto_import(akn_source).unwrap();
1179 assert_eq!(report.source_format, Some(LegalFormat::AkomaNtoso));
1180 assert!(!statutes.is_empty());
1181 }
1182
1183 #[test]
1184 fn test_catala_to_akoma_ntoso_conversion() {
1185 let mut converter = LegalConverter::new();
1186
1187 let catala_source = r#"
1188declaration scope AdultRights:
1189 context input content integer
1190"#;
1191
1192 let (akn_output, report) = converter
1194 .convert(catala_source, LegalFormat::Catala, LegalFormat::AkomaNtoso)
1195 .unwrap();
1196
1197 assert!(report.statutes_converted >= 1);
1198 assert!(akn_output.contains("<akomaNtoso"));
1199 assert!(akn_output.contains("<article"));
1200 }
1201
1202 #[test]
1203 fn test_legalruleml_export_import_roundtrip() {
1204 let mut converter = LegalConverter::new();
1205
1206 let statute = Statute::new(
1208 "legal-rule",
1209 "Legal Rule Example",
1210 Effect::new(EffectType::Grant, "Legal capacity"),
1211 )
1212 .with_precondition(Condition::Age {
1213 operator: ComparisonOp::GreaterOrEqual,
1214 value: 18,
1215 });
1216
1217 let (lrml_output, export_report) = converter
1219 .export(&[statute], LegalFormat::LegalRuleML)
1220 .unwrap();
1221 assert_eq!(export_report.statutes_converted, 1);
1222 assert!(lrml_output.contains("<legalruleml"));
1223 assert!(lrml_output.contains("Legal Rule Example"));
1224
1225 let (imported, import_report) = converter
1227 .import(&lrml_output, LegalFormat::LegalRuleML)
1228 .unwrap();
1229 assert_eq!(import_report.statutes_converted, 1);
1230 assert_eq!(imported.len(), 1);
1231 assert_eq!(imported[0].title, "Legal Rule Example");
1232 }
1233
1234 #[test]
1235 fn test_auto_detect_legalruleml() {
1236 let mut converter = LegalConverter::new();
1237
1238 let lrml_source = r#"
1239 <legalruleml>
1240 <Statements>
1241 <LegalRule key="test">
1242 <Name>Test</Name>
1243 <if><Premise>age >= 18</Premise></if>
1244 <then><Conclusion>Grant</Conclusion></then>
1245 </LegalRule>
1246 </Statements>
1247 </legalruleml>
1248 "#;
1249
1250 let (statutes, report) = converter.auto_import(lrml_source).unwrap();
1251 assert_eq!(report.source_format, Some(LegalFormat::LegalRuleML));
1252 assert!(!statutes.is_empty());
1253 }
1254
1255 #[test]
1256 fn test_catala_to_legalruleml_conversion() {
1257 let mut converter = LegalConverter::new();
1258
1259 let catala_source = r#"
1260declaration scope TaxRule:
1261 context input content integer
1262"#;
1263
1264 let (lrml_output, report) = converter
1266 .convert(catala_source, LegalFormat::Catala, LegalFormat::LegalRuleML)
1267 .unwrap();
1268
1269 assert!(report.statutes_converted >= 1);
1270 assert!(lrml_output.contains("<legalruleml"));
1271 assert!(lrml_output.contains("<LegalRule"));
1272 }
1273
1274 #[test]
1275 fn test_batch_convert() {
1276 let mut converter = LegalConverter::new();
1277
1278 let sources = vec![
1279 (
1280 "declaration scope Test1:\n context input content integer".to_string(),
1281 LegalFormat::Catala,
1282 ),
1283 (
1284 "agreement Test2(A, B) { }".to_string(),
1285 LegalFormat::Stipula,
1286 ),
1287 ];
1288
1289 let results = converter.batch_convert(&sources, LegalFormat::L4).unwrap();
1290
1291 assert_eq!(results.len(), 2);
1292 assert!(results[0].0.contains("RULE"));
1293 assert!(results[1].0.contains("RULE"));
1294 }
1295
1296 #[test]
1297 fn test_batch_import() {
1298 let mut converter = LegalConverter::new();
1299
1300 let sources = vec![
1301 (
1302 "declaration scope Test1:\n context input content integer".to_string(),
1303 LegalFormat::Catala,
1304 ),
1305 (
1306 "agreement Test2(A, B) { }".to_string(),
1307 LegalFormat::Stipula,
1308 ),
1309 ];
1310
1311 let results = converter.batch_import(&sources).unwrap();
1312
1313 assert_eq!(results.len(), 2);
1314 assert!(!results[0].0.is_empty());
1315 assert!(!results[1].0.is_empty());
1316 }
1317
1318 #[test]
1319 fn test_batch_export() {
1320 let mut converter = LegalConverter::new();
1321
1322 let statute = Statute::new(
1323 "test",
1324 "Test Statute",
1325 Effect::new(EffectType::Grant, "Test"),
1326 );
1327
1328 let formats = vec![LegalFormat::Catala, LegalFormat::L4, LegalFormat::Stipula];
1329
1330 let results = converter.batch_export(&[statute], &formats).unwrap();
1331
1332 assert_eq!(results.len(), 3);
1333 assert_eq!(results[0].0, LegalFormat::Catala);
1334 assert_eq!(results[1].0, LegalFormat::L4);
1335 assert_eq!(results[2].0, LegalFormat::Stipula);
1336 }
1337
1338 #[test]
1339 fn test_conversion_caching() {
1340 let mut converter = LegalConverter::with_cache(10);
1341
1342 let catala_source = "declaration scope Test:\n context input content integer";
1343
1344 let (output1, report1) = converter
1346 .convert(catala_source, LegalFormat::Catala, LegalFormat::L4)
1347 .unwrap();
1348
1349 let (output2, report2) = converter
1351 .convert(catala_source, LegalFormat::Catala, LegalFormat::L4)
1352 .unwrap();
1353
1354 assert_eq!(output1, output2);
1356 assert_eq!(report1.statutes_converted, report2.statutes_converted);
1357
1358 let stats = converter.cache_stats().unwrap();
1362 assert_eq!(stats.entries, 2); assert!(stats.access_count >= 3); }
1365
1366 #[test]
1367 fn test_cache_enable_disable() {
1368 let mut converter = LegalConverter::new();
1369
1370 assert!(converter.cache_stats().is_none());
1372
1373 converter.enable_cache(5);
1375 assert!(converter.cache_stats().is_some());
1376
1377 converter.disable_cache();
1379 assert!(converter.cache_stats().is_none());
1380 }
1381
1382 #[test]
1383 fn test_semantic_validation_roundtrip() {
1384 let mut converter = LegalConverter::new();
1385
1386 let l4_source = "RULE VotingAge WHEN age >= 18 THEN Person MAY vote";
1387
1388 let validation = converter
1389 .validate_roundtrip(l4_source, LegalFormat::L4, LegalFormat::Catala)
1390 .unwrap();
1391
1392 assert!(validation.confidence > 0.0);
1394 assert!(!validation.issues.is_empty() || validation.passed());
1395 }
1396
1397 #[test]
1398 fn test_conversion_report_quality() {
1399 let mut report = ConversionReport::new(LegalFormat::Catala, LegalFormat::L4);
1400
1401 assert!(report.is_lossless());
1402 assert!(report.is_high_quality());
1403
1404 report.add_warning("Minor issue");
1405 assert!(!report.is_lossless());
1406 assert!(report.is_high_quality());
1407
1408 report.add_unsupported("Major feature");
1409 report.add_unsupported("Another feature");
1410 report.add_unsupported("Yet another");
1411 assert!(!report.is_high_quality());
1412 }
1413
1414 #[test]
1415 fn test_semantic_validation_structure() {
1416 let mut converter = LegalConverter::new();
1417
1418 let catala_source = r#"
1419declaration scope AdultRights:
1420 context input content integer
1421"#;
1422
1423 let validation = converter
1424 .validate_roundtrip(catala_source, LegalFormat::Catala, LegalFormat::L4)
1425 .unwrap();
1426
1427 assert_eq!(validation.source_format, LegalFormat::Catala);
1429 assert_eq!(validation.target_format, LegalFormat::L4);
1430 assert!(validation.confidence <= 1.0);
1431 }
1432
1433 #[test]
1436 fn test_legalcite_export_import_roundtrip() {
1437 let mut converter = LegalConverter::new();
1438
1439 let statute = Statute::new(
1440 "test-statute",
1441 "Test Statute",
1442 Effect::new(EffectType::Grant, "legal_reference"),
1443 )
1444 .with_jurisdiction("US");
1445
1446 let (legalcite_output, export_report) = converter
1447 .export(&[statute], LegalFormat::LegalCite)
1448 .unwrap();
1449 assert_eq!(export_report.statutes_converted, 1);
1450 assert!(legalcite_output.contains("legalCite"));
1451
1452 let (imported, import_report) = converter
1453 .import(&legalcite_output, LegalFormat::LegalCite)
1454 .unwrap();
1455 assert_eq!(import_report.statutes_converted, 1);
1456 assert_eq!(imported.len(), 1);
1457 }
1458
1459 #[test]
1460 fn test_metalex_export_import_roundtrip() {
1461 let mut converter = LegalConverter::new();
1462
1463 let statute = Statute::new(
1464 "article-1",
1465 "Article 1",
1466 Effect::new(EffectType::Grant, "provision"),
1467 );
1468
1469 let (metalex_output, export_report) =
1470 converter.export(&[statute], LegalFormat::MetaLex).unwrap();
1471 assert_eq!(export_report.statutes_converted, 1);
1472 assert!(metalex_output.contains("metalex"));
1473
1474 let (imported, import_report) = converter
1475 .import(&metalex_output, LegalFormat::MetaLex)
1476 .unwrap();
1477 assert_eq!(import_report.statutes_converted, 1);
1478 assert_eq!(imported.len(), 1);
1479 }
1480
1481 #[test]
1482 fn test_mpeg21_rel_export_import_roundtrip() {
1483 let mut converter = LegalConverter::new();
1484
1485 let statute = Statute::new(
1486 "play-right",
1487 "Play Right",
1488 Effect::new(EffectType::Grant, "play"),
1489 );
1490
1491 let (mpeg21_output, export_report) = converter
1492 .export(&[statute], LegalFormat::Mpeg21Rel)
1493 .unwrap();
1494 assert_eq!(export_report.statutes_converted, 1);
1495
1496 let (imported, import_report) = converter
1497 .import(&mpeg21_output, LegalFormat::Mpeg21Rel)
1498 .unwrap();
1499 assert_eq!(import_report.statutes_converted, 1);
1500 assert_eq!(imported.len(), 1);
1501 }
1502
1503 #[test]
1504 fn test_creative_commons_export_import_roundtrip() {
1505 let mut converter = LegalConverter::new();
1506
1507 let statute = Statute::new(
1508 "cc-permit",
1509 "Permit Reproduction",
1510 Effect::new(EffectType::Grant, "Reproduction"),
1511 );
1512
1513 let (cc_output, export_report) = converter
1514 .export(&[statute], LegalFormat::CreativeCommons)
1515 .unwrap();
1516 assert_eq!(export_report.statutes_converted, 1);
1517
1518 let (imported, import_report) = converter
1519 .import(&cc_output, LegalFormat::CreativeCommons)
1520 .unwrap();
1521 assert_eq!(import_report.statutes_converted, 1);
1522 assert!(!imported.is_empty());
1523 }
1524
1525 #[test]
1526 fn test_spdx_export_import_roundtrip() {
1527 let mut converter = LegalConverter::new();
1528
1529 let mut effect = Effect::new(EffectType::Grant, "use");
1530 effect
1531 .parameters
1532 .insert("spdx_id".to_string(), "MIT".to_string());
1533 let statute = Statute::new("mit_license", "License: MIT", effect);
1534
1535 let (spdx_output, export_report) = converter.export(&[statute], LegalFormat::Spdx).unwrap();
1536 assert_eq!(export_report.statutes_converted, 1);
1537 assert_eq!(spdx_output, "MIT");
1538
1539 let (imported, import_report) = converter.import(&spdx_output, LegalFormat::Spdx).unwrap();
1540 assert_eq!(import_report.statutes_converted, 1);
1541 assert_eq!(imported.len(), 1);
1542 }
1543
1544 #[test]
1545 fn test_auto_detect_legalcite() {
1546 let mut converter = LegalConverter::new();
1547
1548 let legalcite_source = r#"<LegalCiteDocument>
1549 <legalCite>
1550 <citations>
1551 <id>test-1</id>
1552 <title>Test Citation</title>
1553 <citation_type>statute</citation_type>
1554 </citations>
1555 </legalCite>
1556 </LegalCiteDocument>"#;
1557
1558 let (statutes, report) = converter.auto_import(legalcite_source).unwrap();
1559 assert_eq!(report.source_format, Some(LegalFormat::LegalCite));
1560 assert!(!statutes.is_empty());
1561 }
1562
1563 #[test]
1564 fn test_auto_detect_metalex() {
1565 let mut converter = LegalConverter::new();
1566
1567 let metalex_source = r#"<MetaLexDocument>
1568 <metalex>
1569 <Body>
1570 <Article id="art-1">
1571 <Title>Test Article</Title>
1572 </Article>
1573 </Body>
1574 </metalex>
1575 </MetaLexDocument>"#;
1576
1577 let (statutes, report) = converter.auto_import(metalex_source).unwrap();
1578 assert_eq!(report.source_format, Some(LegalFormat::MetaLex));
1579 assert!(!statutes.is_empty());
1580 }
1581
1582 #[test]
1583 fn test_auto_detect_mpeg21_rel() {
1584 let mut converter = LegalConverter::new();
1585
1586 let mpeg21_source = r#"<Mpeg21RelDocument>
1587 <license>
1588 <grant>
1589 <right>play</right>
1590 </grant>
1591 </license>
1592 </Mpeg21RelDocument>"#;
1593
1594 let (statutes, report) = converter.auto_import(mpeg21_source).unwrap();
1595 assert_eq!(report.source_format, Some(LegalFormat::Mpeg21Rel));
1596 assert!(!statutes.is_empty());
1597 }
1598
1599 #[test]
1600 fn test_auto_detect_creative_commons() {
1601 let mut converter = LegalConverter::new();
1602
1603 let cc_source = "https://creativecommons.org/licenses/by/4.0/";
1604
1605 let (statutes, report) = converter.auto_import(cc_source).unwrap();
1606 assert_eq!(report.source_format, Some(LegalFormat::CreativeCommons));
1607 assert!(!statutes.is_empty());
1608 }
1609
1610 #[test]
1611 fn test_auto_detect_spdx() {
1612 let mut converter = LegalConverter::new();
1613
1614 let spdx_source = "MIT AND Apache-2.0";
1615
1616 let (statutes, report) = converter.auto_import(spdx_source).unwrap();
1617 assert_eq!(report.source_format, Some(LegalFormat::Spdx));
1618 assert!(!statutes.is_empty());
1619 }
1620
1621 #[test]
1622 fn test_new_formats_in_converter() {
1623 let converter = LegalConverter::new();
1624 let imports = converter.supported_imports();
1625 let exports = converter.supported_exports();
1626
1627 assert!(imports.contains(&LegalFormat::LegalCite));
1629 assert!(imports.contains(&LegalFormat::MetaLex));
1630 assert!(imports.contains(&LegalFormat::Mpeg21Rel));
1631 assert!(imports.contains(&LegalFormat::CreativeCommons));
1632 assert!(imports.contains(&LegalFormat::Spdx));
1633
1634 assert!(exports.contains(&LegalFormat::LegalCite));
1635 assert!(exports.contains(&LegalFormat::MetaLex));
1636 assert!(exports.contains(&LegalFormat::Mpeg21Rel));
1637 assert!(exports.contains(&LegalFormat::CreativeCommons));
1638 assert!(exports.contains(&LegalFormat::Spdx));
1639 }
1640
1641 #[test]
1644 fn test_blockchain_formats_registered() {
1645 let converter = LegalConverter::new();
1646 let imports = converter.supported_imports();
1647 let exports = converter.supported_exports();
1648
1649 assert!(imports.contains(&LegalFormat::Solidity));
1651 assert!(imports.contains(&LegalFormat::Vyper));
1652 assert!(imports.contains(&LegalFormat::Cadence));
1653 assert!(imports.contains(&LegalFormat::Move));
1654
1655 assert!(exports.contains(&LegalFormat::Solidity));
1656 assert!(exports.contains(&LegalFormat::Vyper));
1657 assert!(exports.contains(&LegalFormat::Cadence));
1658 assert!(exports.contains(&LegalFormat::Move));
1659 }
1660
1661 #[test]
1662 fn test_solidity_import_export_roundtrip() {
1663 let mut converter = LegalConverter::new();
1664
1665 let mut statute = Statute::new(
1666 "token_transfer",
1667 "Token Transfer",
1668 Effect::new(EffectType::MonetaryTransfer, "Transfer tokens"),
1669 );
1670 statute
1671 .effect
1672 .parameters
1673 .insert("contract".to_string(), "ERC20".to_string());
1674 statute
1675 .effect
1676 .parameters
1677 .insert("function".to_string(), "transfer".to_string());
1678 statute
1679 .effect
1680 .parameters
1681 .insert("license".to_string(), "MIT".to_string());
1682
1683 let (solidity_output, export_report) =
1684 converter.export(&[statute], LegalFormat::Solidity).unwrap();
1685 assert_eq!(export_report.statutes_converted, 1);
1686 assert!(solidity_output.contains("contract ERC20"));
1687 assert!(solidity_output.contains("function transfer()"));
1688
1689 let (imported, import_report) = converter
1690 .import(&solidity_output, LegalFormat::Solidity)
1691 .unwrap();
1692 assert_eq!(import_report.statutes_converted, 1);
1693 assert!(!imported.is_empty());
1694 }
1695
1696 #[test]
1697 fn test_vyper_import_export_roundtrip() {
1698 let mut converter = LegalConverter::new();
1699
1700 let mut statute = Statute::new(
1701 "vote",
1702 "Vote Function",
1703 Effect::new(EffectType::Grant, "Cast a vote"),
1704 );
1705 statute
1706 .effect
1707 .parameters
1708 .insert("contract".to_string(), "Voting".to_string());
1709 statute
1710 .effect
1711 .parameters
1712 .insert("function".to_string(), "vote".to_string());
1713 statute
1714 .effect
1715 .parameters
1716 .insert("license".to_string(), "MIT".to_string());
1717
1718 let (vyper_output, export_report) =
1719 converter.export(&[statute], LegalFormat::Vyper).unwrap();
1720 assert_eq!(export_report.statutes_converted, 1);
1721 assert!(vyper_output.contains("# Voting"));
1722 assert!(vyper_output.contains("def vote()"));
1723
1724 let (imported, import_report) =
1725 converter.import(&vyper_output, LegalFormat::Vyper).unwrap();
1726 assert_eq!(import_report.statutes_converted, 1);
1727 assert!(!imported.is_empty());
1728 }
1729
1730 #[test]
1731 fn test_cadence_import_export_roundtrip() {
1732 let mut converter = LegalConverter::new();
1733
1734 let mut statute = Statute::new(
1735 "mint_nft",
1736 "Mint NFT",
1737 Effect::new(EffectType::Grant, "Mint new NFT"),
1738 );
1739 statute
1740 .effect
1741 .parameters
1742 .insert("contract".to_string(), "NFT".to_string());
1743 statute
1744 .effect
1745 .parameters
1746 .insert("function".to_string(), "mintNFT".to_string());
1747
1748 let (cadence_output, export_report) =
1749 converter.export(&[statute], LegalFormat::Cadence).unwrap();
1750 assert_eq!(export_report.statutes_converted, 1);
1751 assert!(cadence_output.contains("pub contract NFT"));
1752 assert!(cadence_output.contains("pub fun mintNFT()"));
1753
1754 let (imported, import_report) = converter
1755 .import(&cadence_output, LegalFormat::Cadence)
1756 .unwrap();
1757 assert_eq!(import_report.statutes_converted, 1);
1758 assert!(!imported.is_empty());
1759 }
1760
1761 #[test]
1762 fn test_move_import_export_roundtrip() {
1763 let mut converter = LegalConverter::new();
1764
1765 let mut statute = Statute::new(
1766 "transfer_coin",
1767 "Transfer Coin",
1768 Effect::new(EffectType::MonetaryTransfer, "Transfer coins"),
1769 );
1770 statute
1771 .effect
1772 .parameters
1773 .insert("module_address".to_string(), "0x1".to_string());
1774 statute
1775 .effect
1776 .parameters
1777 .insert("module_name".to_string(), "Coin".to_string());
1778 statute
1779 .effect
1780 .parameters
1781 .insert("function".to_string(), "transfer".to_string());
1782 statute
1783 .effect
1784 .parameters
1785 .insert("entry".to_string(), "true".to_string());
1786
1787 let (move_output, export_report) = converter.export(&[statute], LegalFormat::Move).unwrap();
1788 assert_eq!(export_report.statutes_converted, 1);
1789 assert!(move_output.contains("module 0x1::Coin"));
1790 assert!(move_output.contains("public entry fun transfer()"));
1791
1792 let (imported, import_report) = converter.import(&move_output, LegalFormat::Move).unwrap();
1793 assert_eq!(import_report.statutes_converted, 1);
1794 assert!(!imported.is_empty());
1795 }
1796
1797 #[test]
1798 fn test_blockchain_format_extensions() {
1799 assert_eq!(LegalFormat::Solidity.extension(), "sol");
1800 assert_eq!(LegalFormat::Vyper.extension(), "vy");
1801 assert_eq!(LegalFormat::Cadence.extension(), "cdc");
1802 assert_eq!(LegalFormat::Move.extension(), "move");
1803 }
1804
1805 #[test]
1806 fn test_blockchain_format_from_extension() {
1807 assert_eq!(
1808 LegalFormat::from_extension("sol"),
1809 Some(LegalFormat::Solidity)
1810 );
1811 assert_eq!(
1812 LegalFormat::from_extension("solidity"),
1813 Some(LegalFormat::Solidity)
1814 );
1815 assert_eq!(LegalFormat::from_extension("vy"), Some(LegalFormat::Vyper));
1816 assert_eq!(
1817 LegalFormat::from_extension("vyper"),
1818 Some(LegalFormat::Vyper)
1819 );
1820 assert_eq!(
1821 LegalFormat::from_extension("cdc"),
1822 Some(LegalFormat::Cadence)
1823 );
1824 assert_eq!(
1825 LegalFormat::from_extension("cadence"),
1826 Some(LegalFormat::Cadence)
1827 );
1828 assert_eq!(LegalFormat::from_extension("move"), Some(LegalFormat::Move));
1829 }
1830
1831 #[test]
1832 fn test_solidity_source_import() {
1833 let mut converter = LegalConverter::new();
1834
1835 let source = r#"
1836 // SPDX-License-Identifier: MIT
1837 pragma solidity ^0.8.0;
1838
1839 contract SimpleStorage {
1840 function store(uint256 value) public {
1841 }
1842 }
1843 "#;
1844
1845 let (statutes, report) = converter.import(source, LegalFormat::Solidity).unwrap();
1846 assert_eq!(report.statutes_converted, 1);
1847 assert!(!statutes.is_empty());
1848 assert_eq!(
1849 statutes[0].effect.parameters.get("contract"),
1850 Some(&"SimpleStorage".to_string())
1851 );
1852 }
1853
1854 #[test]
1855 fn test_vyper_source_import() {
1856 let mut converter = LegalConverter::new();
1857
1858 let source = r#"
1859 # @version 0.3.0
1860 # @license MIT
1861
1862 @external
1863 def transfer():
1864 pass
1865 "#;
1866
1867 let (statutes, report) = converter.import(source, LegalFormat::Vyper).unwrap();
1868 assert_eq!(report.statutes_converted, 1);
1869 assert!(!statutes.is_empty());
1870 }
1871
1872 #[test]
1873 fn test_cadence_source_import() {
1874 let mut converter = LegalConverter::new();
1875
1876 let source = r#"
1877 pub contract FlowToken {
1878 pub fun transfer() {
1879 }
1880 }
1881 "#;
1882
1883 let (statutes, report) = converter.import(source, LegalFormat::Cadence).unwrap();
1884 assert_eq!(report.statutes_converted, 1);
1885 assert!(!statutes.is_empty());
1886 assert_eq!(
1887 statutes[0].effect.parameters.get("blockchain"),
1888 Some(&"Flow".to_string())
1889 );
1890 }
1891
1892 #[test]
1893 fn test_move_source_import() {
1894 let mut converter = LegalConverter::new();
1895
1896 let source = r#"
1897 module 0x1::Coin {
1898 public entry fun mint() {
1899 }
1900 }
1901 "#;
1902
1903 let (statutes, report) = converter.import(source, LegalFormat::Move).unwrap();
1904 assert_eq!(report.statutes_converted, 1);
1905 assert!(!statutes.is_empty());
1906 assert_eq!(
1907 statutes[0].effect.parameters.get("blockchain"),
1908 Some(&"Move".to_string())
1909 );
1910 }
1911
1912 #[test]
1913 fn test_cross_blockchain_conversion() {
1914 let mut converter = LegalConverter::new();
1915
1916 let solidity_source = r#"
1918 pragma solidity ^0.8.0;
1919 contract Token {
1920 function transfer() public {
1921 }
1922 }
1923 "#;
1924
1925 let (statutes, _) = converter
1926 .import(solidity_source, LegalFormat::Solidity)
1927 .unwrap();
1928
1929 let (vyper_output, report) = converter.export(&statutes, LegalFormat::Vyper).unwrap();
1931 assert_eq!(report.statutes_converted, 1);
1932 assert!(vyper_output.contains("def transfer()"));
1933
1934 let (cadence_output, report) = converter.export(&statutes, LegalFormat::Cadence).unwrap();
1936 assert_eq!(report.statutes_converted, 1);
1937 assert!(cadence_output.contains("pub fun transfer()"));
1938
1939 let (move_output, report) = converter.export(&statutes, LegalFormat::Move).unwrap();
1941 assert_eq!(report.statutes_converted, 1);
1942 assert!(move_output.contains("fun transfer()"));
1943 }
1944}