Skip to main content

katana_document_viewer/
forge.rs

1use crate::artifact::{ArtifactBytes, ArtifactFactory};
2use crate::export_payload::ExportPayloadFactory;
3pub use crate::forge_types::{
4    BuildGraph, BuildProfile, BuildRequest, ExportFormat, ExportOutput, ExportRequest,
5    ForgeBackend, ForgeDiagnostics, ForgeError, MarkdownEvaluationTarget, RenderedDiagram,
6    TransformStep,
7};
8
9pub struct ForgePipeline<B> {
10    backend: B,
11}
12
13impl BuildProfile {
14    pub fn markdown_export() -> Self {
15        Self {
16            evaluation_targets: vec![
17                MarkdownEvaluationTarget::CommonMark,
18                MarkdownEvaluationTarget::Gfm,
19                MarkdownEvaluationTarget::Math,
20                MarkdownEvaluationTarget::GitHubAlert,
21                MarkdownEvaluationTarget::KatanaCompatibility,
22                MarkdownEvaluationTarget::ExternalRendering,
23            ],
24            transform_steps: vec![
25                TransformStep::EvaluateMarkdown,
26                TransformStep::RenderDiagrams,
27                TransformStep::BuildArtifactManifest,
28            ],
29        }
30    }
31}
32
33impl BuildGraph {
34    pub fn from_request(request: &BuildRequest) -> Self {
35        Self {
36            snapshot: request.snapshot.clone(),
37            profile: request.profile.clone(),
38            theme: request.theme.clone(),
39            diagnostics: ForgeDiagnostics {
40                messages: Vec::new(),
41            },
42            rendered_diagrams: Vec::new(),
43        }
44    }
45
46    pub fn with_rendered_diagrams(mut self, rendered_diagrams: Vec<RenderedDiagram>) -> Self {
47        self.rendered_diagrams = rendered_diagrams;
48        self
49    }
50}
51
52impl<B: ForgeBackend> ForgePipeline<B> {
53    pub fn new(backend: B) -> Self {
54        Self { backend }
55    }
56
57    pub fn build(&self, request: &BuildRequest) -> Result<BuildGraph, ForgeError> {
58        self.backend.build(request)
59    }
60
61    pub fn export(&self, request: &ExportRequest) -> Result<ExportOutput, ForgeError> {
62        validate_export_output(self.backend.export(request), request.format)
63    }
64}
65
66fn validate_export_output(
67    result: Result<ExportOutput, ForgeError>,
68    format: ExportFormat,
69) -> Result<ExportOutput, ForgeError> {
70    match result {
71        Ok(output) => reject_empty_export_output(output, format),
72        Err(error) => Err(error),
73    }
74}
75
76fn reject_empty_export_output(
77    output: ExportOutput,
78    format: ExportFormat,
79) -> Result<ExportOutput, ForgeError> {
80    if output.artifact.bytes.bytes.is_empty() {
81        Err(ForgeError::EmptyExportArtifact(format))
82    } else {
83        Ok(output)
84    }
85}
86pub struct ManifestOnlyBackend;
87
88impl ForgeBackend for ManifestOnlyBackend {
89    fn build(&self, request: &BuildRequest) -> Result<BuildGraph, ForgeError> {
90        Ok(BuildGraph::from_request(request))
91    }
92
93    fn export(&self, request: &ExportRequest) -> Result<ExportOutput, ForgeError> {
94        let snapshot = &request.graph.snapshot;
95        ExportPayloadFactory::create(&request.graph, request.format, &request.theme).map(|bytes| {
96            let artifact = ArtifactFactory::export(
97                request.format.artifact_format(),
98                snapshot.id.clone(),
99                snapshot.revision.clone(),
100                ArtifactBytes { bytes },
101            );
102            ExportOutput {
103                artifact,
104                diagnostics: request.graph.diagnostics.clone(),
105            }
106        })
107    }
108}
109
110#[cfg(test)]
111#[path = "forge_tests.rs"]
112mod tests;