1use std::{error::Error, fmt};
4
5use boxferry_model::Application;
6
7use crate::{ConversionOutcome, ConversionPlan, ConversionResult, Diagnostic, LossPolicy, PlanError, TargetProfile};
8
9#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct ImportResult {
12 application: Option<Application>,
13 outcomes: Vec<ConversionOutcome>,
14 diagnostics: Vec<Diagnostic>,
15}
16
17impl ImportResult {
18 #[must_use]
20 pub const fn new(
21 application: Option<Application>,
22 outcomes: Vec<ConversionOutcome>,
23 diagnostics: Vec<Diagnostic>,
24 ) -> Self {
25 Self {
26 application,
27 outcomes,
28 diagnostics,
29 }
30 }
31
32 #[must_use]
34 pub const fn success(application: Application) -> Self {
35 Self::new(Some(application), Vec::new(), Vec::new())
36 }
37
38 #[must_use]
40 pub const fn application(&self) -> Option<&Application> {
41 self.application.as_ref()
42 }
43
44 #[must_use]
46 pub fn outcomes(&self) -> &[ConversionOutcome] {
47 &self.outcomes
48 }
49
50 #[must_use]
52 pub fn diagnostics(&self) -> &[Diagnostic] {
53 &self.diagnostics
54 }
55
56 #[must_use]
58 pub fn into_parts(self) -> (Option<Application>, Vec<ConversionOutcome>, Vec<Diagnostic>) {
59 (self.application, self.outcomes, self.diagnostics)
60 }
61}
62
63pub trait ImportAdapter {
65 type Source: ?Sized;
67
68 fn import(&self, source: &Self::Source) -> ImportResult;
70}
71
72pub trait ExportAdapter {
74 type Output;
76
77 fn plan(
83 &self,
84 application: &Application,
85 target: &TargetProfile,
86 ) -> Result<ConversionPlan<Self::Output>, PlanError>;
87}
88
89#[derive(Clone, Debug, Eq, PartialEq)]
91#[non_exhaustive]
92pub enum ConversionError {
93 Import(Vec<Diagnostic>),
95 InvalidPlan(PlanError),
97}
98
99impl fmt::Display for ConversionError {
100 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
101 match self {
102 Self::Import(diagnostics) => {
103 write!(
104 formatter,
105 "source import failed with {} diagnostic(s)",
106 diagnostics.len()
107 )
108 }
109 Self::InvalidPlan(error) => write!(formatter, "target adapter returned an invalid plan: {error}"),
110 }
111 }
112}
113
114impl Error for ConversionError {}
115
116pub fn convert<I, E>(
124 importer: &I,
125 source: &I::Source,
126 exporter: &E,
127 target: &TargetProfile,
128 policy: LossPolicy,
129) -> Result<ConversionResult<E::Output>, ConversionError>
130where
131 I: ImportAdapter,
132 E: ExportAdapter,
133{
134 convert_imported(importer.import(source), exporter, target, policy)
135}
136
137pub fn convert_imported<E>(
149 import: ImportResult,
150 exporter: &E,
151 target: &TargetProfile,
152 policy: LossPolicy,
153) -> Result<ConversionResult<E::Output>, ConversionError>
154where
155 E: ExportAdapter,
156{
157 let (application, import_outcomes, import_diagnostics) = import.into_parts();
158 if application.is_none()
159 || import_diagnostics
160 .iter()
161 .any(|diagnostic| diagnostic.severity() == crate::Severity::Error)
162 {
163 return Err(ConversionError::Import(import_diagnostics));
164 }
165 let Some(application) = application else {
166 return Err(ConversionError::Import(import_diagnostics));
167 };
168 let mut plan = exporter
169 .plan(&application, target)
170 .map_err(ConversionError::InvalidPlan)?;
171 plan.extend_import(import_outcomes, import_diagnostics)
172 .map_err(ConversionError::InvalidPlan)?;
173 Ok(plan.authorize(policy))
174}
175
176#[derive(Clone, Debug, Eq, PartialEq)]
178pub struct InMemoryAdapter<T> {
179 output: T,
180 outcomes: Vec<ConversionOutcome>,
181 diagnostics: Vec<Diagnostic>,
182}
183
184impl<T> InMemoryAdapter<T> {
185 #[must_use]
187 pub fn exact(output: T) -> Self {
188 Self {
189 output,
190 outcomes: vec![ConversionOutcome::exact("application")],
191 diagnostics: Vec::new(),
192 }
193 }
194
195 #[must_use]
197 pub const fn new(output: T, outcomes: Vec<ConversionOutcome>, diagnostics: Vec<Diagnostic>) -> Self {
198 Self {
199 output,
200 outcomes,
201 diagnostics,
202 }
203 }
204}
205
206impl<T> ImportAdapter for InMemoryAdapter<T> {
207 type Source = Application;
208
209 fn import(&self, source: &Self::Source) -> ImportResult {
210 ImportResult::success(source.clone())
211 }
212}
213
214impl<T: Clone> ExportAdapter for InMemoryAdapter<T> {
215 type Output = T;
216
217 fn plan(
218 &self,
219 _application: &Application,
220 _target: &TargetProfile,
221 ) -> Result<ConversionPlan<Self::Output>, PlanError> {
222 ConversionPlan::new(
223 Some(self.output.clone()),
224 self.outcomes.clone(),
225 self.diagnostics.clone(),
226 )
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use boxferry_model::{Application, Identifier};
233
234 use super::{InMemoryAdapter, convert};
235 use crate::{LossPolicy, PlatformVersion, TargetProfile};
236
237 #[test]
238 fn in_memory_adapter_proves_the_public_orchestration_path() -> Result<(), String> {
239 let application = Application::new(Identifier::new("example").map_err(|error| error.to_string())?);
240 let adapter = InMemoryAdapter::exact("rendered target".to_owned());
241 let target = TargetProfile::new("test-target", PlatformVersion::new(1, 0, 0), None)
242 .map_err(|error| error.to_string())?;
243
244 let result = convert(&adapter, &application, &adapter, &target, LossPolicy::ExactOnly)
245 .map_err(|error| error.to_string())?;
246 assert_eq!(result.output().map(String::as_str), Some("rendered target"));
247 assert!(!result.is_blocked());
248 Ok(())
249 }
250}