1#![forbid(unsafe_code)]
124#![warn(missing_docs)]
125
126pub mod api_security;
127pub mod ast;
128pub mod builder;
129pub mod caching;
130pub mod canonical;
131pub mod determinism;
132pub mod diff;
133pub mod error;
134pub mod fidelity;
135pub mod generator;
136pub mod guarantees;
137pub mod id_generator;
138pub mod linker;
139pub mod memory_optimization;
140pub mod messages;
141pub mod namespace_minimizer;
142pub mod optimized_strings;
143pub mod parallel_processing;
144pub mod preflight;
145pub mod presets;
146pub mod round_trip;
147pub mod schema;
148pub mod security;
149pub mod streaming;
150pub mod verification;
151pub mod versions;
152
153pub use builder::{BuildOptions, BuildRequest, BuildResult, DDEXBuilder};
155pub use canonical::DB_C14N;
156pub use determinism::DeterminismConfig;
157pub use diff::formatter::DiffFormatter;
158pub use diff::types::{ChangeSet, ChangeType, DiffPath, ImpactLevel, SemanticChange};
159pub use diff::{DiffConfig, DiffEngine, VersionCompatibility};
160pub use error::{BuildError, BuildWarning};
161pub use guarantees::{DeterminismGuarantee, DeterminismGuaranteeValidator, GuaranteeReport};
162pub use id_generator::{HashAlgorithm, StableHashConfig, StableHashGenerator};
163pub use linker::{EntityType, LinkerConfig, LinkingError, ReferenceLinker};
164pub use messages::{
165 UpdateAction, UpdateConfig, UpdateGenerator, UpdateReleaseMessage, ValidationStatus,
166};
167pub use preflight::{PreflightLevel, PreflightValidator, ValidationConfig, ValidationResult};
168pub use presets::DdexVersion;
169pub use presets::PartnerPreset;
170pub use schema::{JsonSchema, SchemaCommand, SchemaConfig, SchemaDraft, SchemaGenerator};
171pub use versions::{
172 ConversionOptions, ConverterResult as ConversionResult, VersionConverter, VersionManager,
173};
174
175pub use api_security::{ApiSecurityConfig, ApiSecurityManager, BatchStats, FfiDataType};
177pub use security::{InputValidator, OutputSanitizer, RateLimiter, SecureTempFile, SecurityConfig};
178
179pub use fidelity::{FidelityConfig, FidelityStatistics, PreservationLevel};
181pub use round_trip::{FidelityAnalysis, RoundTripTester};
182pub use verification::{BuildVerifier, VerificationStatistics};
183
184use indexmap::IndexMap;
185use serde::{Deserialize, Serialize};
186use std::collections::HashMap;
187use std::time::Duration;
188
189pub const DB_C14N_VERSION: &str = "1.0";
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct FidelityOptions {
195 pub enable_perfect_fidelity: bool,
197 pub preserve_comments: bool,
199 pub preserve_processing_instructions: bool,
201 pub preserve_extensions: bool,
203 pub preserve_attribute_order: bool,
205 pub preserve_namespace_prefixes: bool,
207 pub canonicalization: CanonicalizationAlgorithm,
209 pub custom_canonicalization_rules: Option<CustomCanonicalizationRules>,
211 pub enable_deterministic_ordering: bool,
213 pub collect_statistics: bool,
215 pub enable_verification: bool,
217 pub verification_config: VerificationConfig,
219}
220
221impl Default for FidelityOptions {
222 fn default() -> Self {
223 Self {
224 enable_perfect_fidelity: false,
225 preserve_comments: false,
226 preserve_processing_instructions: false,
227 preserve_extensions: true,
228 preserve_attribute_order: false,
229 preserve_namespace_prefixes: false,
230 canonicalization: CanonicalizationAlgorithm::DbC14N,
231 custom_canonicalization_rules: None,
232 enable_deterministic_ordering: true,
233 collect_statistics: false,
234 enable_verification: false,
235 verification_config: VerificationConfig::default(),
236 }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242pub enum CanonicalizationAlgorithm {
243 None,
245 C14N,
247 C14N11,
249 DbC14N,
251 Custom(CustomCanonicalizationRules),
253}
254
255#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
257pub struct CustomCanonicalizationRules {
258 pub preserve_whitespace: bool,
260 pub sort_attributes: bool,
262 pub normalize_line_endings: bool,
264 pub minimize_namespaces: bool,
266 pub attribute_ordering: Vec<String>,
268 pub element_ordering: HashMap<String, Vec<String>>,
270}
271
272impl Default for CustomCanonicalizationRules {
273 fn default() -> Self {
274 Self {
275 preserve_whitespace: false,
276 sort_attributes: true,
277 normalize_line_endings: true,
278 minimize_namespaces: true,
279 attribute_ordering: Vec::new(),
280 element_ordering: HashMap::new(),
281 }
282 }
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct VerificationConfig {
288 pub enable_round_trip_verification: bool,
290 pub enable_canonicalization_verification: bool,
292 pub enable_schema_validation: bool,
294 pub enable_determinism_verification: bool,
296 pub determinism_test_iterations: usize,
298 pub verification_timeout: Duration,
300}
301
302impl Default for VerificationConfig {
303 fn default() -> Self {
304 Self {
305 enable_round_trip_verification: true,
306 enable_canonicalization_verification: true,
307 enable_schema_validation: false,
308 enable_determinism_verification: true,
309 determinism_test_iterations: 3,
310 verification_timeout: Duration::from_secs(30),
311 }
312 }
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct VerificationResult {
318 pub success: bool,
320 pub round_trip_success: bool,
322 pub canonicalization_success: bool,
324 pub schema_validation_success: bool,
326 pub determinism_success: bool,
328 pub issues: Vec<VerificationIssue>,
330 pub verification_time: Duration,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct VerificationIssue {
337 pub severity: VerificationSeverity,
339 pub category: String,
341 pub message: String,
343 pub path: Option<String>,
345 pub suggestion: Option<String>,
347}
348
349#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
351pub enum VerificationSeverity {
352 Error,
354 Warning,
356 Info,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct BuildStatistics {
363 pub build_time: Duration,
365 pub canonicalization_time: Duration,
367 pub validation_time: Duration,
369 pub verification_time: Duration,
371 pub peak_memory_bytes: usize,
373 pub element_count: usize,
375 pub attribute_count: usize,
377 pub input_size_bytes: usize,
379 pub output_size_bytes: usize,
381 pub namespace_count: usize,
383 pub comment_count: usize,
385 pub processing_instruction_count: usize,
387}
388
389impl Default for BuildStatistics {
390 fn default() -> Self {
391 Self {
392 build_time: Duration::ZERO,
393 canonicalization_time: Duration::ZERO,
394 validation_time: Duration::ZERO,
395 verification_time: Duration::ZERO,
396 peak_memory_bytes: 0,
397 element_count: 0,
398 attribute_count: 0,
399 input_size_bytes: 0,
400 output_size_bytes: 0,
401 namespace_count: 0,
402 comment_count: 0,
403 processing_instruction_count: 0,
404 }
405 }
406}
407
408#[derive(Debug, Clone)]
499pub struct Builder {
500 config: DeterminismConfig,
501 presets: IndexMap<String, PartnerPreset>,
502 locked_preset: Option<String>,
503 version_manager: versions::VersionManager,
504 target_version: Option<DdexVersion>,
505 fidelity_options: FidelityOptions,
506 verification_config: VerificationConfig,
507}
508
509impl Default for Builder {
510 fn default() -> Self {
511 Self::new()
512 }
513}
514
515impl Builder {
516 pub fn new() -> Self {
539 Self {
540 config: DeterminismConfig::default(),
541 presets: Self::load_default_presets(),
542 locked_preset: None,
543 version_manager: versions::VersionManager::new(),
544 target_version: None,
545 fidelity_options: FidelityOptions::default(),
546 verification_config: VerificationConfig::default(),
547 }
548 }
549
550 pub fn with_config(config: DeterminismConfig) -> Self {
552 Self {
553 config,
554 presets: Self::load_default_presets(),
555 locked_preset: None,
556 version_manager: versions::VersionManager::new(),
557 target_version: None,
558 fidelity_options: FidelityOptions::default(),
559 verification_config: VerificationConfig::default(),
560 }
561 }
562
563 pub fn with_perfect_fidelity() -> Self {
565 let mut fidelity_options = FidelityOptions::default();
566 fidelity_options.enable_perfect_fidelity = true;
567 fidelity_options.preserve_comments = true;
568 fidelity_options.preserve_processing_instructions = true;
569 fidelity_options.preserve_extensions = true;
570 fidelity_options.preserve_attribute_order = true;
571 fidelity_options.preserve_namespace_prefixes = true;
572 fidelity_options.enable_verification = true;
573
574 Self {
575 config: DeterminismConfig::default(),
576 presets: Self::load_default_presets(),
577 locked_preset: None,
578 version_manager: versions::VersionManager::new(),
579 target_version: None,
580 fidelity_options,
581 verification_config: VerificationConfig::default(),
582 }
583 }
584
585 pub fn with_fidelity_options(fidelity_options: FidelityOptions) -> Self {
587 Self {
588 config: DeterminismConfig::default(),
589 presets: Self::load_default_presets(),
590 locked_preset: None,
591 version_manager: versions::VersionManager::new(),
592 target_version: None,
593 fidelity_options,
594 verification_config: VerificationConfig::default(),
595 }
596 }
597
598 pub fn for_round_trip() -> Self {
600 let mut fidelity_options = FidelityOptions::default();
601 fidelity_options.enable_perfect_fidelity = true;
602 fidelity_options.preserve_comments = true;
603 fidelity_options.preserve_processing_instructions = true;
604 fidelity_options.preserve_extensions = true;
605 fidelity_options.preserve_attribute_order = true;
606 fidelity_options.preserve_namespace_prefixes = true;
607 fidelity_options.canonicalization = CanonicalizationAlgorithm::DbC14N;
608 fidelity_options.enable_verification = true;
609 fidelity_options.collect_statistics = true;
610
611 let mut verification_config = VerificationConfig::default();
612 verification_config.enable_round_trip_verification = true;
613 verification_config.enable_canonicalization_verification = true;
614 verification_config.enable_determinism_verification = true;
615
616 Self {
617 config: DeterminismConfig::default(),
618 presets: Self::load_default_presets(),
619 locked_preset: None,
620 version_manager: versions::VersionManager::new(),
621 target_version: None,
622 fidelity_options,
623 verification_config,
624 }
625 }
626
627 pub fn apply_preset(&mut self, preset_name: &str, lock: bool) -> Result<(), error::BuildError> {
673 let preset = self
674 .presets
675 .get(preset_name)
676 .ok_or_else(|| error::BuildError::InvalidFormat {
677 field: "preset".to_string(),
678 message: format!("Unknown preset: {}", preset_name),
679 })?
680 .clone();
681
682 self.config = preset.determinism;
684
685 if lock {
687 self.locked_preset = Some(preset_name.to_string());
688 }
689
690 Ok(())
691 }
692
693 pub fn preset(&mut self, preset_name: &str) -> Result<&mut Self, error::BuildError> {
695 self.apply_preset(preset_name, false)?;
696 Ok(self)
697 }
698
699 pub fn available_presets(&self) -> Vec<String> {
701 self.presets.keys().cloned().collect()
702 }
703
704 pub fn get_preset(&self, preset_name: &str) -> Option<&PartnerPreset> {
706 self.presets.get(preset_name)
707 }
708
709 pub fn is_preset_locked(&self) -> bool {
711 self.locked_preset.is_some()
712 }
713
714 pub fn config(&self) -> &DeterminismConfig {
716 &self.config
717 }
718
719 pub fn fidelity_options(&self) -> &FidelityOptions {
721 &self.fidelity_options
722 }
723
724 pub fn set_fidelity_options(&mut self, options: FidelityOptions) -> &mut Self {
726 self.fidelity_options = options;
727 self
728 }
729
730 pub fn enable_perfect_fidelity(&mut self) -> &mut Self {
732 self.fidelity_options.enable_perfect_fidelity = true;
733 self.fidelity_options.preserve_comments = true;
734 self.fidelity_options.preserve_processing_instructions = true;
735 self.fidelity_options.preserve_extensions = true;
736 self.fidelity_options.preserve_attribute_order = true;
737 self.fidelity_options.preserve_namespace_prefixes = true;
738 self.fidelity_options.enable_verification = true;
739 self
740 }
741
742 pub fn disable_perfect_fidelity(&mut self) -> &mut Self {
744 self.fidelity_options.enable_perfect_fidelity = false;
745 self.fidelity_options.preserve_comments = false;
746 self.fidelity_options.preserve_processing_instructions = false;
747 self.fidelity_options.preserve_attribute_order = false;
748 self.fidelity_options.preserve_namespace_prefixes = false;
749 self.fidelity_options.enable_verification = false;
750 self
751 }
752
753 pub fn with_canonicalization(&mut self, algorithm: CanonicalizationAlgorithm) -> &mut Self {
755 self.fidelity_options.canonicalization = algorithm;
756 self
757 }
758
759 pub fn with_db_c14n(&mut self) -> &mut Self {
761 self.fidelity_options.canonicalization = CanonicalizationAlgorithm::DbC14N;
762 self
763 }
764
765 pub fn with_c14n(&mut self) -> &mut Self {
767 self.fidelity_options.canonicalization = CanonicalizationAlgorithm::C14N;
768 self
769 }
770
771 pub fn with_c14n11(&mut self) -> &mut Self {
773 self.fidelity_options.canonicalization = CanonicalizationAlgorithm::C14N11;
774 self
775 }
776
777 pub fn with_custom_canonicalization(
779 &mut self,
780 rules: CustomCanonicalizationRules,
781 ) -> &mut Self {
782 self.fidelity_options.canonicalization = CanonicalizationAlgorithm::Custom(rules.clone());
783 self.fidelity_options.custom_canonicalization_rules = Some(rules);
784 self
785 }
786
787 pub fn with_verification(&mut self, config: VerificationConfig) -> &mut Self {
789 self.fidelity_options.enable_verification = true;
790 self.verification_config = config;
791 self
792 }
793
794 pub fn with_statistics(&mut self) -> &mut Self {
796 self.fidelity_options.collect_statistics = true;
797 self
798 }
799
800 pub fn is_perfect_fidelity_enabled(&self) -> bool {
802 self.fidelity_options.enable_perfect_fidelity
803 }
804
805 pub fn canonicalization_algorithm(&self) -> &CanonicalizationAlgorithm {
807 &self.fidelity_options.canonicalization
808 }
809
810 pub fn with_version(&mut self, version: DdexVersion) -> &mut Self {
812 self.target_version = Some(version);
813 self
814 }
815
816 pub fn target_version(&self) -> Option<DdexVersion> {
818 self.target_version
819 }
820
821 pub fn detect_version(&self, xml_content: &str) -> Result<DdexVersion, error::BuildError> {
823 self.version_manager
824 .detect_version(xml_content)
825 .map(|detection| detection.detected_version)
826 .map_err(|e| error::BuildError::InvalidFormat {
827 field: "version".to_string(),
828 message: format!("Version detection failed: {}", e),
829 })
830 }
831
832 pub fn convert_version(
834 &self,
835 xml_content: &str,
836 from_version: DdexVersion,
837 to_version: DdexVersion,
838 options: Option<ConversionOptions>,
839 ) -> Result<versions::ConverterResult, error::BuildError> {
840 let converter = versions::VersionConverter::new();
841 Ok(converter.convert(xml_content, from_version, to_version, options))
842 }
843
844 pub fn is_version_compatible(&self, from: DdexVersion, to: DdexVersion) -> bool {
846 self.version_manager.is_conversion_supported(from, to)
847 }
848
849 pub fn supported_versions(&self) -> Vec<DdexVersion> {
851 versions::utils::supported_versions()
852 }
853
854 fn load_default_presets() -> IndexMap<String, PartnerPreset> {
855 presets::all_presets()
856 }
857
858 pub fn build_with_fidelity(
860 &self,
861 request: &builder::BuildRequest,
862 ) -> Result<FidelityBuildResult, error::BuildError> {
863 let start_time = std::time::Instant::now();
864 let mut statistics = BuildStatistics::default();
865
866 let build_options = builder::BuildOptions::default();
868
869 let ddex_builder = builder::DDEXBuilder::new();
871 let build_result = ddex_builder.build(request.clone(), build_options)?;
872
873 statistics.build_time = start_time.elapsed();
874 statistics.output_size_bytes = build_result.xml.len();
875
876 let verification_result = if self.fidelity_options.enable_verification {
878 let verification_config = verification::VerificationConfig {
880 enable_round_trip_verification: self
881 .verification_config
882 .enable_round_trip_verification,
883 enable_canonicalization_verification: self
884 .verification_config
885 .enable_canonicalization_verification,
886 enable_schema_validation: self.verification_config.enable_schema_validation,
887 enable_determinism_verification: self
888 .verification_config
889 .enable_determinism_verification,
890 determinism_test_iterations: self.verification_config.determinism_test_iterations,
891 verification_timeout: self.verification_config.verification_timeout,
892 };
893 let verifier = verification::BuildVerifier::new(verification_config);
894 let result = verifier.verify(&build_result.xml, &self.fidelity_options)?;
895
896 Some(VerificationResult {
898 success: result.success,
899 round_trip_success: result.round_trip_success,
900 canonicalization_success: result.canonicalization_success,
901 determinism_success: result.determinism_success,
902 schema_validation_success: result.schema_validation_success,
903 issues: result
904 .issues
905 .into_iter()
906 .map(|issue| VerificationIssue {
907 category: issue.category,
908 severity: match issue.severity {
909 verification::VerificationSeverity::Error => {
910 VerificationSeverity::Error
911 }
912 verification::VerificationSeverity::Warning => {
913 VerificationSeverity::Warning
914 }
915 verification::VerificationSeverity::Info => VerificationSeverity::Info,
916 },
917 message: issue.message,
918 path: issue.path,
919 suggestion: issue.suggestion,
920 })
921 .collect(),
922 verification_time: result.verification_time,
923 })
924 } else {
925 None
926 };
927
928 Ok(FidelityBuildResult {
929 xml: build_result.xml,
930 statistics: if self.fidelity_options.collect_statistics {
931 Some(statistics)
932 } else {
933 None
934 },
935 verification_result,
936 canonicalization_applied: self.fidelity_options.canonicalization
937 != CanonicalizationAlgorithm::None,
938 db_c14n_version: if self.fidelity_options.canonicalization
939 == CanonicalizationAlgorithm::DbC14N
940 {
941 Some(DB_C14N_VERSION.to_string())
942 } else {
943 None
944 },
945 })
946 }
947
948 pub fn verify_build(&self, xml_output: &str) -> Result<VerificationResult, error::BuildError> {
950 let verification_config = verification::VerificationConfig {
952 enable_round_trip_verification: self.verification_config.enable_round_trip_verification,
953 enable_canonicalization_verification: self
954 .verification_config
955 .enable_canonicalization_verification,
956 enable_schema_validation: self.verification_config.enable_schema_validation,
957 enable_determinism_verification: self
958 .verification_config
959 .enable_determinism_verification,
960 determinism_test_iterations: self.verification_config.determinism_test_iterations,
961 verification_timeout: self.verification_config.verification_timeout,
962 };
963
964 let verifier = verification::BuildVerifier::new(verification_config);
965 let result = verifier.verify(xml_output, &self.fidelity_options)?;
966
967 Ok(VerificationResult {
969 success: result.success,
970 round_trip_success: result.round_trip_success,
971 canonicalization_success: result.canonicalization_success,
972 determinism_success: result.determinism_success,
973 schema_validation_success: result.schema_validation_success,
974 issues: result
975 .issues
976 .into_iter()
977 .map(|issue| VerificationIssue {
978 category: issue.category,
979 severity: match issue.severity {
980 verification::VerificationSeverity::Error => VerificationSeverity::Error,
981 verification::VerificationSeverity::Warning => {
982 VerificationSeverity::Warning
983 }
984 verification::VerificationSeverity::Info => VerificationSeverity::Info,
985 },
986 message: issue.message,
987 path: issue.path,
988 suggestion: issue.suggestion,
989 })
990 .collect(),
991 verification_time: result.verification_time,
992 })
993 }
994
995 pub fn test_round_trip_fidelity(
997 &self,
998 original_xml: &str,
999 ) -> Result<RoundTripResult, error::BuildError> {
1000 let round_trip = round_trip::RoundTripTester::new(self.fidelity_options.clone());
1001 let result = round_trip.test_round_trip(original_xml)?;
1002
1003 Ok(RoundTripResult {
1005 success: result.success,
1006 original_xml: result.original_xml,
1007 rebuilt_xml: result.rebuilt_xml,
1008 byte_identical: result.byte_identical,
1009 differences: result.differences,
1010 test_time: result.test_time,
1011 })
1012 }
1013
1014 pub fn canonicalize(&self, xml_content: &str) -> Result<String, error::BuildError> {
1016 match &self.fidelity_options.canonicalization {
1017 CanonicalizationAlgorithm::None => Ok(xml_content.to_string()),
1018 CanonicalizationAlgorithm::C14N => {
1019 Ok(xml_content.to_string())
1021 }
1022 CanonicalizationAlgorithm::C14N11 => {
1023 Ok(xml_content.to_string())
1025 }
1026 CanonicalizationAlgorithm::DbC14N => {
1027 Ok(xml_content.to_string())
1029 }
1030 CanonicalizationAlgorithm::Custom(rules) => {
1031 let _ = rules; Ok(xml_content.to_string())
1034 }
1035 }
1036 }
1037
1038 pub fn db_c14n_config(&self) -> DbC14NConfig {
1040 DbC14NConfig {
1041 version: DB_C14N_VERSION.to_string(),
1042 algorithm: self.fidelity_options.canonicalization.clone(),
1043 deterministic_ordering: self.fidelity_options.enable_deterministic_ordering,
1044 preserve_comments: self.fidelity_options.preserve_comments,
1045 preserve_processing_instructions: self
1046 .fidelity_options
1047 .preserve_processing_instructions,
1048 namespace_handling: if self.fidelity_options.preserve_namespace_prefixes {
1049 NamespaceHandling::Preserve
1050 } else {
1051 NamespaceHandling::Minimize
1052 },
1053 }
1054 }
1055
1056 pub(crate) fn build_internal(
1058 &self,
1059 request: &builder::BuildRequest,
1060 ) -> Result<builder::BuildResult, error::BuildError> {
1061 let ddex_builder = builder::DDEXBuilder::new();
1062 let build_options = builder::BuildOptions::default();
1063
1064 ddex_builder.build(request.clone(), build_options)
1065 }
1066}
1067
1068#[derive(Debug, Clone, Serialize, Deserialize)]
1070pub struct FidelityBuildResult {
1071 pub xml: String,
1073 pub statistics: Option<BuildStatistics>,
1075 pub verification_result: Option<VerificationResult>,
1077 pub canonicalization_applied: bool,
1079 pub db_c14n_version: Option<String>,
1081}
1082
1083#[derive(Debug, Clone, Serialize, Deserialize)]
1085pub struct RoundTripResult {
1086 pub success: bool,
1088 pub original_xml: String,
1090 pub rebuilt_xml: String,
1092 pub byte_identical: bool,
1094 pub differences: Vec<String>,
1096 pub test_time: Duration,
1098}
1099
1100#[derive(Debug, Clone, Serialize, Deserialize)]
1102pub struct DbC14NConfig {
1103 pub version: String,
1105 pub algorithm: CanonicalizationAlgorithm,
1107 pub deterministic_ordering: bool,
1109 pub preserve_comments: bool,
1111 pub preserve_processing_instructions: bool,
1113 pub namespace_handling: NamespaceHandling,
1115}
1116
1117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1119pub enum NamespaceHandling {
1120 Preserve,
1122 Minimize,
1124 Normalize,
1126}
1127
1128pub fn version_info() -> String {
1130 format!(
1131 "DDEX Builder v{} • DB-C14N/{} • Perfect Fidelity Engine • Rust {}",
1132 env!("CARGO_PKG_VERSION"),
1133 DB_C14N_VERSION,
1134 env!("CARGO_PKG_RUST_VERSION", "unknown")
1135 )
1136}
1137
1138pub fn fidelity_engine_info() -> String {
1140 format!(
1141 "Perfect Fidelity Engine v{} • Round-trip: ✓ • DB-C14N/{} • Extensions: ✓",
1142 env!("CARGO_PKG_VERSION"),
1143 DB_C14N_VERSION
1144 )
1145}
1146
1147#[cfg(test)]
1148mod tests {
1149 use super::*;
1150
1151 #[test]
1152 fn test_builder_creation() {
1153 let builder = Builder::new();
1154 assert!(!builder.is_preset_locked());
1155 }
1156
1157 #[test]
1158 fn test_preset_application() {
1159 let mut builder = Builder::new();
1160 assert!(builder.apply_preset("audio_album", false).is_ok());
1161 assert!(!builder.is_preset_locked());
1162
1163 assert!(builder.apply_preset("audio_album", true).is_ok());
1164 assert!(builder.is_preset_locked());
1165 }
1166
1167 #[test]
1168 fn test_unknown_preset() {
1169 let mut builder = Builder::new();
1170 assert!(builder.apply_preset("unknown_preset", false).is_err());
1171 }
1172
1173 #[test]
1174 fn test_version_info() {
1175 let info = version_info();
1176 assert!(info.contains("DDEX Builder"));
1177 assert!(info.contains("DB-C14N/1.0"));
1178 }
1179}