Skip to main content

boxferry_engine/
lib.rs

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