1use indexmap::IndexMap;
5use std::sync::Arc;
6
7use quillmark_core::{
8 normalize::normalize_document, Backend, Card, Diagnostic, Document, Frontmatter, OutputFormat,
9 QuillSource, QuillValue, RenderError, RenderOptions, RenderResult, RenderSession, Sentinel,
10 Severity,
11};
12
13use crate::form::{self, Form, FormCard};
14
15#[derive(Clone)]
18pub struct Quill {
19 source: Arc<QuillSource>,
20 backend: Arc<dyn Backend>,
21}
22
23struct PreparedRenderContext {
24 json_data: serde_json::Value,
25 plate_content: String,
26}
27
28impl Quill {
29 pub(crate) fn new(source: Arc<QuillSource>, backend: Arc<dyn Backend>) -> Self {
34 Self { source, backend }
35 }
36
37 pub fn source(&self) -> &QuillSource {
39 &self.source
40 }
41
42 pub fn backend_id(&self) -> &str {
44 self.backend.id()
45 }
46
47 pub fn supported_formats(&self) -> &'static [OutputFormat] {
49 self.backend.supported_formats()
50 }
51
52 pub fn name(&self) -> &str {
54 self.source.name()
55 }
56
57 pub fn render(
62 &self,
63 doc: &Document,
64 opts: &RenderOptions,
65 ) -> Result<RenderResult, RenderError> {
66 let session = self.open(doc)?;
67 let resolved = self.resolve_options(opts);
68 session.render(&resolved)
69 }
70
71 pub fn open(&self, doc: &Document) -> Result<RenderSession, RenderError> {
73 let context = self.prepare_render_context(doc)?;
74 let warnings: Vec<_> = self.ref_mismatch_warning(doc).into_iter().collect();
75 let session =
76 self.backend
77 .open(&context.plate_content, &self.source, &context.json_data)?;
78 Ok(session.with_warnings(warnings))
79 }
80
81 fn resolve_options(&self, opts: &RenderOptions) -> RenderOptions {
82 let output_format = opts
83 .output_format
84 .or_else(|| self.backend.supported_formats().first().copied());
85 RenderOptions {
86 output_format,
87 ppi: opts.ppi,
88 pages: opts.pages.clone(),
89 }
90 }
91
92 pub fn compile_data(&self, doc: &Document) -> Result<serde_json::Value, RenderError> {
97 let main_fields_map = doc.main().frontmatter().to_index_map();
99 let coerced_frontmatter = self
100 .source
101 .config()
102 .coerce_frontmatter(&main_fields_map)
103 .map_err(|e| RenderError::ValidationFailed {
104 diag: Box::new(
105 Diagnostic::new(Severity::Error, e.to_string())
106 .with_code("validation::coercion_failed".to_string())
107 .with_hint(
108 "Ensure all fields can be coerced to their declared types".to_string(),
109 ),
110 ),
111 })?;
112
113 let mut coerced_cards: Vec<Card> = Vec::new();
115 for card in doc.cards() {
116 let card_fields_map = card.frontmatter().to_index_map();
117 let coerced_fields = self
118 .source
119 .config()
120 .coerce_card(&card.tag(), &card_fields_map)
121 .map_err(|e| RenderError::ValidationFailed {
122 diag: Box::new(
123 Diagnostic::new(Severity::Error, e.to_string())
124 .with_code("validation::coercion_failed".to_string())
125 .with_hint(
126 "Ensure all card fields can be coerced to their declared types"
127 .to_string(),
128 ),
129 ),
130 })?;
131 coerced_cards.push(Card::new_with_sentinel(
132 Sentinel::Card(card.tag()),
133 Frontmatter::from_index_map(coerced_fields),
134 card.body().to_string(),
135 ));
136 }
137
138 let coerced_main = Card::new_with_sentinel(
139 Sentinel::Main(doc.quill_reference().clone()),
140 Frontmatter::from_index_map(coerced_frontmatter),
141 doc.main().body().to_string(),
142 );
143 let coerced_doc =
144 Document::from_main_and_cards(coerced_main, coerced_cards, doc.warnings().to_vec());
145
146 self.validate_document(&coerced_doc)?;
147
148 let normalized = normalize_document(coerced_doc)?;
150
151 let normalized_main_map = normalized.main().frontmatter().to_index_map();
153 let frontmatter_with_defaults = self.apply_frontmatter_defaults(&normalized_main_map);
154
155 let cards_with_defaults: Vec<Card> = normalized
157 .cards()
158 .iter()
159 .map(|card| {
160 let card_map = card.frontmatter().to_index_map();
161 let fields_with_defaults = self.apply_card_defaults(&card.tag(), &card_map);
162 Card::new_with_sentinel(
163 Sentinel::Card(card.tag()),
164 Frontmatter::from_index_map(fields_with_defaults),
165 card.body().to_string(),
166 )
167 })
168 .collect();
169
170 let final_main = Card::new_with_sentinel(
172 Sentinel::Main(normalized.quill_reference().clone()),
173 Frontmatter::from_index_map(frontmatter_with_defaults),
174 normalized.main().body().to_string(),
175 );
176 let final_doc = Document::from_main_and_cards(
177 final_main,
178 cards_with_defaults,
179 normalized.warnings().to_vec(),
180 );
181
182 Ok(final_doc.to_plate_json())
184 }
185
186 fn prepare_render_context(&self, doc: &Document) -> Result<PreparedRenderContext, RenderError> {
187 Ok(PreparedRenderContext {
188 json_data: self.compile_data(doc)?,
189 plate_content: self.plate_content().unwrap_or_default(),
190 })
191 }
192
193 fn ref_mismatch_warning(&self, doc: &Document) -> Option<Diagnostic> {
194 let doc_ref = doc.quill_reference().name.as_str();
195 if doc_ref != self.source.name() {
196 Some(
197 Diagnostic::new(
198 Severity::Warning,
199 format!(
200 "document declares QUILL '{}' but was rendered with '{}'",
201 doc_ref,
202 self.source.name()
203 ),
204 )
205 .with_code("quill::ref_mismatch".to_string())
206 .with_hint(
207 "the QUILL field is informational; ensure you are rendering with the intended quill"
208 .to_string(),
209 ),
210 )
211 } else {
212 None
213 }
214 }
215
216 fn apply_frontmatter_defaults(
217 &self,
218 frontmatter: &IndexMap<String, QuillValue>,
219 ) -> IndexMap<String, QuillValue> {
220 let mut result = frontmatter.clone();
221 for (field_name, default_value) in self.source.config().main.defaults() {
222 if !result.contains_key(&field_name) {
223 result.insert(field_name, default_value);
224 }
225 }
226 result
227 }
228
229 fn apply_card_defaults(
230 &self,
231 card_tag: &str,
232 fields: &IndexMap<String, QuillValue>,
233 ) -> IndexMap<String, QuillValue> {
234 let mut result = fields.clone();
235 if let Some(card) = self.source.config().card_type(card_tag) {
236 for (field_name, default_value) in card.defaults() {
237 if !result.contains_key(&field_name) {
238 result.insert(field_name, default_value);
239 }
240 }
241 }
242 result
243 }
244
245 fn plate_content(&self) -> Option<String> {
246 self.source
247 .plate()
248 .filter(|s| !s.is_empty())
249 .map(str::to_string)
250 }
251
252 pub fn form(&self, doc: &Document) -> Form {
267 form::build_form(self, doc)
268 }
269
270 pub fn blank_main(&self) -> FormCard {
277 FormCard::blank(&self.source.config().main)
278 }
279
280 pub fn blank_card(&self, card_type: &str) -> Option<FormCard> {
287 form::blank_card_for_tag(self, card_type)
288 }
289
290 pub fn dry_run(&self, doc: &Document) -> Result<(), RenderError> {
292 let main_fields_map = doc.main().frontmatter().to_index_map();
293 let coerced_frontmatter = self
294 .source
295 .config()
296 .coerce_frontmatter(&main_fields_map)
297 .map_err(|e| RenderError::ValidationFailed {
298 diag: Box::new(
299 Diagnostic::new(Severity::Error, e.to_string())
300 .with_code("validation::coercion_failed".to_string())
301 .with_hint(
302 "Ensure all fields and card values can be coerced to their declared types"
303 .to_string(),
304 ),
305 ),
306 })?;
307 let mut coerced_cards: Vec<Card> = Vec::new();
308 for card in doc.cards() {
309 let card_fields_map = card.frontmatter().to_index_map();
310 let coerced_fields = self
311 .source
312 .config()
313 .coerce_card(&card.tag(), &card_fields_map)
314 .map_err(|e| RenderError::ValidationFailed {
315 diag: Box::new(
316 Diagnostic::new(Severity::Error, e.to_string())
317 .with_code("validation::coercion_failed".to_string())
318 .with_hint(
319 "Ensure all card fields can be coerced to their declared types"
320 .to_string(),
321 ),
322 ),
323 })?;
324 coerced_cards.push(Card::new_with_sentinel(
325 Sentinel::Card(card.tag()),
326 Frontmatter::from_index_map(coerced_fields),
327 card.body().to_string(),
328 ));
329 }
330 let coerced_main = Card::new_with_sentinel(
331 Sentinel::Main(doc.quill_reference().clone()),
332 Frontmatter::from_index_map(coerced_frontmatter),
333 doc.main().body().to_string(),
334 );
335 let coerced_doc =
336 Document::from_main_and_cards(coerced_main, coerced_cards, doc.warnings().to_vec());
337 self.validate_document(&coerced_doc)?;
338 Ok(())
339 }
340
341 fn validate_document(&self, doc: &Document) -> Result<(), RenderError> {
342 match self.source.config().validate_document(doc) {
343 Ok(_) => Ok(()),
344 Err(errors) => {
345 let error_message = errors
346 .into_iter()
347 .map(|e| format!("- {}", e))
348 .collect::<Vec<_>>()
349 .join("\n");
350 Err(RenderError::ValidationFailed {
351 diag: Box::new(
352 Diagnostic::new(Severity::Error, error_message)
353 .with_code("validation::document_invalid".to_string())
354 .with_hint(
355 "Ensure all required fields are present and have correct types"
356 .to_string(),
357 ),
358 ),
359 })
360 }
361 }
362 }
363}
364
365impl std::fmt::Debug for Quill {
366 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367 f.debug_struct("Quill")
368 .field("name", &self.source.name())
369 .field("backend", &self.backend.id())
370 .finish()
371 }
372}