Skip to main content

boxferry_engine/
lib.rs

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