Skip to main content

boxferry_engine/
lib.rs

1//! Conversion planning, policy, capability, and diagnostics for `BoxFerry`.
2
3mod adapter;
4mod diagnostic;
5mod metadata;
6mod native_evidence;
7mod outcome;
8mod rule;
9mod target;
10
11pub use adapter::{
12    ConversionError, ExportAdapter, ImportAdapter, ImportResult, InMemoryAdapter, convert, convert_imported,
13};
14pub use diagnostic::{
15    Diagnostic, DiagnosticCode, DiagnosticField, DiagnosticValue, InvalidDiagnosticCode, NativeFinding,
16    NativeFindingLabel, NativeFindingLabelKind, Severity,
17};
18pub use metadata::vendor_metadata_losses;
19pub use native_evidence::retained_native_evidence_losses;
20pub use outcome::{ConversionKind, ConversionOutcome, ConversionPlan, ConversionResult, LossPolicy, PlanError};
21pub use rule::{DiagnosticRule, RULES, RuleId, find_rule};
22pub use target::{ParsePlatformVersionError, PlatformVersion, TargetProfile, TargetProfileError, VersionRange};
23
24#[cfg(test)]
25mod integration_tests {
26    use boxferry_model::{Application, Identifier};
27
28    use crate::{
29        ConversionKind, ConversionOutcome, ConversionPlan, Diagnostic, DiagnosticCode, ExportAdapter, ImportAdapter,
30        ImportResult, InMemoryAdapter, LossPolicy, PlanError, PlatformVersion, Severity, TargetProfile, convert,
31        convert_imported,
32    };
33
34    struct LossyImporter {
35        application: Application,
36    }
37
38    impl ImportAdapter for LossyImporter {
39        type Source = ();
40
41        fn import(&self, _source: &Self::Source) -> ImportResult {
42            let code = DiagnosticCode::new("BFE0010").ok();
43            let (outcomes, diagnostics) = code.map_or_else(
44                || (Vec::new(), Vec::new()),
45                |code| {
46                    let outcome =
47                        ConversionOutcome::loss("services.web.extension", ConversionKind::Unsupported, code.clone())
48                            .ok();
49                    let outcomes = outcome.into_iter().collect();
50                    let diagnostics = vec![Diagnostic::new(code, Severity::Warning, "extension omitted")];
51                    (outcomes, diagnostics)
52                },
53            );
54            ImportResult::new(Some(self.application.clone()), outcomes, diagnostics)
55        }
56    }
57
58    struct ApplicationNameExporter;
59
60    impl ExportAdapter for ApplicationNameExporter {
61        type Output = String;
62
63        fn plan(
64            &self,
65            application: &Application,
66            _target: &TargetProfile,
67        ) -> Result<ConversionPlan<Self::Output>, PlanError> {
68            ConversionPlan::new(
69                Some(application.name().as_str().to_owned()),
70                vec![ConversionOutcome::exact("export.sentinel")],
71                Vec::new(),
72            )
73        }
74    }
75
76    #[test]
77    fn import_losses_participate_in_output_authorization() -> Result<(), String> {
78        let application = Application::new(Identifier::new("example").map_err(|error| error.to_string())?);
79        let importer = LossyImporter { application };
80        let exporter = InMemoryAdapter::exact("candidate".to_owned());
81        let target =
82            TargetProfile::new("target", PlatformVersion::new(1, 0, 0), None).map_err(|error| error.to_string())?;
83
84        let strict =
85            convert(&importer, &(), &exporter, &target, LossPolicy::ExactOnly).map_err(|error| error.to_string())?;
86        assert!(strict.is_blocked());
87
88        let partial =
89            convert(&importer, &(), &exporter, &target, LossPolicy::AllowPartial).map_err(|error| error.to_string())?;
90        assert_eq!(partial.output().map(String::as_str), Some("candidate"));
91        assert!(partial.outcomes().iter().any(|outcome| {
92            outcome.kind() == ConversionKind::Unsupported && outcome.subject() == "services.web.extension"
93        }));
94        Ok(())
95    }
96
97    #[test]
98    fn imported_boundary_passes_the_neutral_application_and_combines_outcomes() -> Result<(), String> {
99        let application = Application::new(Identifier::new("neutral-sentinel").map_err(|error| error.to_string())?);
100        let imported = ImportResult::new(
101            Some(application),
102            vec![ConversionOutcome::exact("import.sentinel")],
103            Vec::new(),
104        );
105        let target =
106            TargetProfile::new("target", PlatformVersion::new(1, 0, 0), None).map_err(|error| error.to_string())?;
107
108        let result = convert_imported(imported, &ApplicationNameExporter, &target, LossPolicy::ExactOnly)
109            .map_err(|error| error.to_string())?;
110
111        assert_eq!(result.output().map(String::as_str), Some("neutral-sentinel"));
112        assert_eq!(
113            result
114                .outcomes()
115                .iter()
116                .map(ConversionOutcome::subject)
117                .collect::<Vec<_>>(),
118            ["import.sentinel", "export.sentinel"]
119        );
120        Ok(())
121    }
122}