1use crate::error::Result;
3use crate::pipeline::StepStateParams;
4use crate::XvcEntity;
5use anyhow::anyhow;
6
7use xvc_core::types::diff::Diffable;
8use xvc_core::XvcPathMetadataMap;
9
10use xvc_core::{Diff, HashAlgorithm, TextOrBinary, XvcPath, XvcRoot};
11use xvc_core::{HStore, Storable};
12
13use super::glob::GlobDep;
14use super::line_items::LineItemsDep;
15use super::lines::LinesDep;
16use super::regex::RegexDep;
17use super::regex_items::RegexItemsDep;
18use super::sqlite_query::SqliteQueryDep;
19use super::step::StepDep;
20use super::{ParamDep, XvcDependency};
21
22use super::file::FileDep;
23use super::generic::GenericDep;
24use super::glob_items::GlobItemsDep;
25use super::url::UrlDigestDep;
26
27#[derive(Clone, Debug)]
28pub struct DependencyComparisonParams<'a> {
30 pub xvc_root: &'a XvcRoot,
32 pub pipeline_rundir: &'a XvcPath,
34 pub pmm: &'a XvcPathMetadataMap,
36 pub algorithm: &'a HashAlgorithm,
38 pub step_dependencies: &'a HStore<XvcDependency>,
40}
41
42impl Diffable for XvcDependency {
43 type Item = XvcDependency;
44
45 fn diff(record: Option<&XvcDependency>, actual: Option<&XvcDependency>) -> Diff<XvcDependency> {
46 match (record, actual) {
47 (None, None) => Diff::Skipped,
48 (None, Some(actual)) => Diff::RecordMissing {
49 actual: actual.clone(),
50 },
51 (Some(record), None) => Diff::ActualMissing {
52 record: record.clone(),
53 },
54 (Some(record), Some(actual)) => match (record, actual) {
55 (XvcDependency::Step(record), XvcDependency::Step(actual)) => {
56 diff_of_dep(StepDep::diff(Some(record), Some(actual)))
57 }
58
59 (XvcDependency::Generic(record), XvcDependency::Generic(actual)) => {
60 diff_of_dep(GenericDep::diff(Some(record), Some(actual)))
61 }
62
63 (XvcDependency::File(record), XvcDependency::File(actual)) => {
64 diff_of_dep(FileDep::diff(Some(record), Some(actual)))
65 }
66
67 (XvcDependency::GlobItems(record), XvcDependency::GlobItems(actual)) => {
68 diff_of_dep(GlobItemsDep::diff(Some(record), Some(actual)))
69 }
70
71 (XvcDependency::Glob(record), XvcDependency::Glob(actual)) => {
72 diff_of_dep(GlobDep::diff(Some(record), Some(actual)))
73 }
74
75 (XvcDependency::RegexItems(record), XvcDependency::RegexItems(actual)) => {
76 diff_of_dep(RegexItemsDep::diff(Some(record), Some(actual)))
77 }
78
79 (XvcDependency::Regex(record), XvcDependency::Regex(actual)) => {
80 diff_of_dep(RegexDep::diff(Some(record), Some(actual)))
81 }
82
83 (XvcDependency::Param(record), XvcDependency::Param(actual)) => {
84 diff_of_dep(ParamDep::diff(Some(record), Some(actual)))
85 }
86
87 (XvcDependency::LineItems(record), XvcDependency::LineItems(actual)) => {
88 diff_of_dep(LineItemsDep::diff(Some(record), Some(actual)))
89 }
90
91 (XvcDependency::UrlDigest(record), XvcDependency::UrlDigest(actual)) => {
92 diff_of_dep(UrlDigestDep::diff(Some(record), Some(actual)))
93 }
94
95 (XvcDependency::Lines(record), XvcDependency::Lines(actual)) => {
96 diff_of_dep(LinesDep::diff(Some(record), Some(actual)))
97 }
98 _ => unreachable!("All dependencies should be of the same type"),
99 },
100 }
101 }
102
103 fn diff_superficial(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
104 match (record, actual) {
105 (XvcDependency::Step(record), XvcDependency::Step(actual)) => {
106 diff_of_dep(StepDep::diff_superficial(record, actual))
107 }
108
109 (XvcDependency::Generic(record), XvcDependency::Generic(actual)) => {
110 diff_of_dep(GenericDep::diff_superficial(record, actual))
111 }
112
113 (XvcDependency::File(record), XvcDependency::File(actual)) => {
114 diff_of_dep(FileDep::diff_superficial(record, actual))
115 }
116
117 (XvcDependency::GlobItems(record), XvcDependency::GlobItems(actual)) => {
118 diff_of_dep(GlobItemsDep::diff_superficial(record, actual))
119 }
120
121 (XvcDependency::Glob(record), XvcDependency::Glob(actual)) => {
122 diff_of_dep(GlobDep::diff_superficial(record, actual))
123 }
124
125 (XvcDependency::RegexItems(record), XvcDependency::RegexItems(actual)) => {
126 diff_of_dep(RegexItemsDep::diff_superficial(record, actual))
127 }
128
129 (XvcDependency::Regex(record), XvcDependency::Regex(actual)) => {
130 diff_of_dep(RegexDep::diff_superficial(record, actual))
131 }
132
133 (XvcDependency::Param(record), XvcDependency::Param(actual)) => {
134 diff_of_dep(ParamDep::diff_superficial(record, actual))
135 }
136
137 (XvcDependency::LineItems(record), XvcDependency::LineItems(actual)) => {
138 diff_of_dep(LineItemsDep::diff_superficial(record, actual))
139 }
140
141 (XvcDependency::Lines(record), XvcDependency::Lines(actual)) => {
142 diff_of_dep(LinesDep::diff_superficial(record, actual))
143 }
144
145 (XvcDependency::UrlDigest(record), XvcDependency::UrlDigest(actual)) => {
146 diff_of_dep(UrlDigestDep::diff_superficial(record, actual))
147 }
148
149 _ => unreachable!("All dependencies should be of the same type"),
150 }
151 }
152
153 fn diff_thorough(record: &Self::Item, actual: &Self::Item) -> Diff<Self::Item> {
154 match (record, actual) {
155 (XvcDependency::Step(record), XvcDependency::Step(actual)) => {
156 diff_of_dep(StepDep::diff_thorough(record, actual))
157 }
158
159 (XvcDependency::Generic(record), XvcDependency::Generic(actual)) => {
160 let actual = actual.clone().update_output_digest().unwrap();
161 diff_of_dep(GenericDep::diff_thorough(record, &actual))
162 }
163
164 (XvcDependency::File(record), XvcDependency::File(actual)) => {
165 diff_of_dep(FileDep::diff_thorough(record, actual))
166 }
167
168 (XvcDependency::GlobItems(record), XvcDependency::GlobItems(actual)) => {
169 diff_of_dep(GlobItemsDep::diff_thorough(record, actual))
170 }
171
172 (XvcDependency::Glob(record), XvcDependency::Glob(actual)) => {
173 diff_of_dep(GlobDep::diff_thorough(record, actual))
174 }
175
176 (XvcDependency::RegexItems(record), XvcDependency::RegexItems(actual)) => {
177 diff_of_dep(RegexItemsDep::diff_thorough(record, actual))
178 }
179
180 (XvcDependency::Regex(record), XvcDependency::Regex(actual)) => {
181 diff_of_dep(RegexDep::diff_thorough(record, actual))
182 }
183
184 (XvcDependency::Param(record), XvcDependency::Param(actual)) => {
185 diff_of_dep(ParamDep::diff_thorough(record, actual))
186 }
187
188 (XvcDependency::LineItems(record), XvcDependency::LineItems(actual)) => {
189 diff_of_dep(LineItemsDep::diff_thorough(record, actual))
190 }
191
192 (XvcDependency::Lines(record), XvcDependency::Lines(actual)) => {
193 diff_of_dep(LinesDep::diff_thorough(record, actual))
194 }
195
196 (XvcDependency::UrlDigest(record), XvcDependency::UrlDigest(actual)) => {
197 diff_of_dep(UrlDigestDep::diff_thorough(record, actual))
198 }
199
200 _ => unreachable!("All dependencies should be of the same type"),
201 }
202 }
203}
204
205pub fn thorough_compare_dependency(
212 cmp_params: &StepStateParams,
213 stored_dependency_e: XvcEntity,
214) -> Result<Diff<XvcDependency>> {
215 let stored = if cmp_params.all_steps.contains_key(&stored_dependency_e) {
216 let step = cmp_params.all_steps[&stored_dependency_e].clone();
217 Ok(XvcDependency::Step(StepDep {
218 name: step.name.clone(),
219 }))
220 } else {
221 cmp_params
222 .recorded_dependencies
223 .children
224 .get(&stored_dependency_e)
225 .cloned()
226 .ok_or(anyhow!(
227 "Stored dependency {:?} not found in step dependencies",
228 stored_dependency_e
229 ))
230 }?;
231
232 let diff = match stored {
233 XvcDependency::Step(_) => Diff::Skipped,
235 XvcDependency::Generic(generic) => diff_of_dep(thorough_compare_generic(&generic)?),
236 XvcDependency::File(file_dep) => diff_of_dep(thorough_compare_file(cmp_params, &file_dep)?),
237 XvcDependency::GlobItems(glob_dep) => {
238 diff_of_dep(thorough_compare_glob_items(cmp_params, &glob_dep)?)
239 }
240 XvcDependency::UrlDigest(url_dep) => diff_of_dep(thorough_compare_url(&url_dep)?),
241 XvcDependency::Param(param_dep) => {
242 diff_of_dep(thorough_compare_param(cmp_params, ¶m_dep)?)
243 }
244 XvcDependency::RegexItems(dep) => {
245 diff_of_dep(thorough_compare_regex_items(cmp_params, &dep)?)
246 }
247 XvcDependency::LineItems(lines_dep) => {
248 diff_of_dep(thorough_compare_line_items(cmp_params, &lines_dep)?)
249 }
250 XvcDependency::Glob(dep) => diff_of_dep(thorough_compare_glob(cmp_params, &dep)?),
251 XvcDependency::Regex(dep) => diff_of_dep(thorough_compare_regex(cmp_params, &dep)?),
252 XvcDependency::Lines(dep) => diff_of_dep(thorough_compare_lines(cmp_params, &dep)?),
253 XvcDependency::SqliteQueryDigest(dep) => {
254 diff_of_dep(thorough_compare_query_digest(cmp_params, &dep)?)
255 }
256 };
257
258 Ok(diff)
259}
260
261fn thorough_compare_generic(record: &GenericDep) -> Result<Diff<GenericDep>> {
263 let mut actual = GenericDep::new(record.generic_command.clone());
264 actual = actual.update_output_digest()?;
265 Ok(GenericDep::diff_thorough(record, &actual))
266}
267
268fn thorough_compare_file(cmp_params: &StepStateParams, record: &FileDep) -> Result<Diff<FileDep>> {
270 let actual = FileDep::from_pmp(&record.path, cmp_params.pmp)?;
271 let actual = actual.calculate_content_digest(
272 cmp_params.xvc_root,
273 cmp_params.algorithm,
274 TextOrBinary::Auto,
275 )?;
276 Ok(FileDep::diff_thorough(record, &actual))
277}
278
279fn thorough_compare_url(record: &UrlDigestDep) -> Result<Diff<UrlDigestDep>> {
280 let actual = UrlDigestDep::new(record.url.clone()).update_content_digest()?;
281 Ok(UrlDigestDep::diff_thorough(record, &actual))
282}
283
284fn thorough_compare_param(
285 cmp_params: &StepStateParams,
286 record: &ParamDep,
287) -> Result<Diff<ParamDep>> {
288 let actual = ParamDep::new(&record.path, Some(record.format), record.key.clone())?
289 .update_metadata(cmp_params.pmp)?
290 .update_value(cmp_params.xvc_root)?;
291 Ok(ParamDep::diff_thorough(record, &actual))
292}
293
294fn thorough_compare_line_items(
295 cmp_params: &StepStateParams,
296 record: &LineItemsDep,
297) -> Result<Diff<LineItemsDep>> {
298 let actual = LineItemsDep::new(record.path.clone(), record.begin, record.end)
299 .update_metadata(cmp_params.pmp.get(&record.path))
300 .update_lines(cmp_params.xvc_root);
301 Ok(LineItemsDep::diff(Some(record), Some(&actual)))
302}
303
304fn thorough_compare_glob_items(
306 cmp_params: &StepStateParams,
307 record: &GlobItemsDep,
308) -> Result<Diff<GlobItemsDep>> {
309 let glob_root = cmp_params.pipeline_rundir;
310 let xvc_root = cmp_params.xvc_root;
311 let pmp = cmp_params.pmp;
312 let algorithm = cmp_params.algorithm;
313 let actual = record
315 .clone()
316 .update_changed_paths_digests(record, xvc_root, glob_root, pmp, algorithm)?;
317 Ok(GlobItemsDep::diff(Some(record), Some(&actual)))
318}
319
320fn thorough_compare_glob(cmp_params: &StepStateParams, record: &GlobDep) -> Result<Diff<GlobDep>> {
321 let actual = GlobDep::new(record.glob.clone()).update_collection_digests(cmp_params.pmp)?;
322 match GlobDep::diff_superficial(record, &actual) {
323 Diff::Different { record, actual } => {
324 let actual = actual.update_content_digest(cmp_params.xvc_root, cmp_params.pmp)?;
325 Ok(GlobDep::diff_thorough(&record, &actual))
326 }
327 Diff::RecordMissing { actual } => {
328 let actual = actual.update_content_digest(cmp_params.xvc_root, cmp_params.pmp)?;
329 Ok(GlobDep::diff_thorough(record, &actual))
330 }
331 diff => Ok(diff),
332 }
333}
334
335fn thorough_compare_regex(
336 cmp_params: &StepStateParams,
337 record: &RegexDep,
338) -> Result<Diff<RegexDep>> {
339 let actual = RegexDep::new(record.path.clone(), record.regex.clone())
340 .update_metadata(cmp_params.pmp.get(&record.path));
341 match RegexDep::diff_superficial(record, &actual) {
343 Diff::Different { record, actual } => {
344 let actual = actual.update_digest(cmp_params.xvc_root, cmp_params.algorithm);
345 Ok(RegexDep::diff_thorough(&record, &actual))
346 }
347 Diff::RecordMissing { actual } => {
348 let actual = actual.update_digest(cmp_params.xvc_root, cmp_params.algorithm);
349 Ok(Diff::RecordMissing { actual })
350 }
351 diff => Ok(diff),
352 }
353}
354
355fn thorough_compare_regex_items(
356 cmp_params: &StepStateParams,
357 record: &RegexItemsDep,
358) -> Result<Diff<RegexItemsDep>> {
359 let actual = RegexItemsDep::new(record.path.clone(), record.regex.clone())
360 .update_metadata(cmp_params.pmp.get(&record.path));
361 match RegexItemsDep::diff_superficial(record, &actual) {
363 Diff::Different { record, actual } => {
364 let actual = actual.update_lines(cmp_params.xvc_root);
365 Ok(RegexItemsDep::diff_thorough(&record, &actual))
366 }
367 Diff::RecordMissing { actual } => {
368 let actual = actual.update_lines(cmp_params.xvc_root);
369 Ok(Diff::RecordMissing { actual })
370 }
371 diff => Ok(diff),
372 }
373}
374
375fn thorough_compare_lines(
376 cmp_params: &StepStateParams,
377 record: &LinesDep,
378) -> Result<Diff<LinesDep>> {
379 let actual = LinesDep::new(record.path.clone(), record.begin, record.end)
380 .update_metadata(cmp_params.pmp.get(&record.path));
381
382 match LinesDep::diff_superficial(record, &actual) {
384 Diff::Different { record, actual } => {
385 let actual = actual.update_digest(cmp_params.xvc_root, cmp_params.algorithm);
386 Ok(LinesDep::diff_thorough(&record, &actual))
387 }
388 Diff::RecordMissing { actual } => {
389 let actual = actual.update_digest(cmp_params.xvc_root, cmp_params.algorithm);
390 Ok(Diff::RecordMissing { actual })
391 }
392 diff => Ok(diff),
393 }
394}
395
396fn thorough_compare_query_digest(
397 cmp_params: &StepStateParams,
398 record: &SqliteQueryDep,
399) -> Result<Diff<SqliteQueryDep>> {
400 let actual = SqliteQueryDep::new(record.path.clone(), record.query.clone())
401 .update_metadata(cmp_params.pmp.get(&record.path));
402
403 match SqliteQueryDep::diff_superficial(record, &actual) {
405 Diff::Different { record, actual } => {
406 let actual = actual.update_digest(cmp_params.xvc_root, cmp_params.algorithm)?;
407 Ok(SqliteQueryDep::diff_thorough(&record, &actual))
408 }
409 Diff::RecordMissing { actual } => {
410 let actual = actual.update_digest(cmp_params.xvc_root, cmp_params.algorithm)?;
411 Ok(Diff::RecordMissing { actual })
412 }
413 diff => Ok(diff),
414 }
415}
416
417pub fn superficial_compare_dependency(
425 cmp_params: &StepStateParams,
426 stored_dependency_e: XvcEntity,
427) -> Result<Diff<XvcDependency>> {
428 let stored = if cmp_params.all_steps.contains_key(&stored_dependency_e) {
431 let step = cmp_params.all_steps[&stored_dependency_e].clone();
432 Ok(XvcDependency::Step(StepDep { name: step.name }))
433 } else {
434 cmp_params
435 .recorded_dependencies
436 .children
437 .get(&stored_dependency_e)
438 .cloned()
439 .ok_or(anyhow!(
440 "Stored dependency {:?} not found in step dependencies",
441 stored_dependency_e
442 ))
443 }?;
444 let diff = match &stored {
445 XvcDependency::Step(_) => Diff::Skipped,
447 XvcDependency::Generic(generic) => {
448 diff_of_dep(superficial_compare_generic(cmp_params, generic)?)
449 }
450 XvcDependency::File(file_dep) => {
451 diff_of_dep(superficial_compare_file(cmp_params, file_dep)?)
452 }
453 XvcDependency::GlobItems(glob_dep) => {
454 diff_of_dep(superficial_compare_glob_items(cmp_params, glob_dep)?)
455 }
456 XvcDependency::UrlDigest(dep) => diff_of_dep(superficial_compare_url(dep)?),
457 XvcDependency::Param(dep) => diff_of_dep(superficial_compare_param(cmp_params, dep)?),
458 XvcDependency::RegexItems(dep) => {
459 diff_of_dep(superficial_compare_regex_items(cmp_params, dep)?)
460 }
461 XvcDependency::LineItems(dep) => {
462 diff_of_dep(superficial_compare_line_items(cmp_params, dep)?)
463 }
464 XvcDependency::Glob(dep) => diff_of_dep(superficial_compare_glob(cmp_params, dep)?),
465 XvcDependency::Regex(dep) => diff_of_dep(superficial_compare_regex(cmp_params, dep)?),
466 XvcDependency::Lines(dep) => diff_of_dep(superficial_compare_lines(cmp_params, dep)?),
467 XvcDependency::SqliteQueryDigest(dep) => {
468 diff_of_dep(superficial_compare_query_digest(cmp_params, dep)?)
469 }
470 };
471
472 Ok(diff)
473}
474
475fn superficial_compare_generic(
477 _cmp_params: &StepStateParams,
478 record: &GenericDep,
479) -> Result<Diff<GenericDep>> {
480 let actual = GenericDep::new(record.generic_command.clone());
481 let actual = actual.update_output_digest()?;
482 Ok(GenericDep::diff_superficial(record, &actual))
483}
484
485fn superficial_compare_file(
487 cmp_params: &StepStateParams,
488 record: &FileDep,
489) -> Result<Diff<FileDep>> {
490 let actual = FileDep::from_pmp(&record.path, cmp_params.pmp)?;
491 Ok(FileDep::diff_superficial(record, &actual))
492}
493
494fn superficial_compare_url(record: &UrlDigestDep) -> Result<Diff<UrlDigestDep>> {
495 let actual = UrlDigestDep::new(record.url.clone()).update_headers()?;
496 Ok(UrlDigestDep::diff_superficial(record, &actual))
497}
498
499fn superficial_compare_param(
500 cmp_params: &StepStateParams,
501 record: &ParamDep,
502) -> Result<Diff<ParamDep>> {
503 let actual = ParamDep::new(&record.path, Some(record.format), record.key.clone())?
504 .update_metadata(cmp_params.pmp)?;
505 Ok(ParamDep::diff_superficial(record, &actual))
506}
507
508fn superficial_compare_regex_items(
509 cmp_params: &StepStateParams,
510 record: &RegexItemsDep,
511) -> Result<Diff<RegexItemsDep>> {
512 let actual = RegexItemsDep::new(record.path.clone(), record.regex.clone())
513 .update_metadata(cmp_params.pmp.get(&record.path));
514 Ok(RegexItemsDep::diff_superficial(record, &actual))
515}
516
517fn superficial_compare_line_items(
518 cmp_params: &StepStateParams,
519 record: &LineItemsDep,
520) -> Result<Diff<LineItemsDep>> {
521 let actual = LineItemsDep::new(record.path.clone(), record.begin, record.end)
522 .update_metadata(cmp_params.pmp.get(&record.path));
523 Ok(LineItemsDep::diff_superficial(record, &actual))
524}
525
526fn superficial_compare_glob_items(
528 cmp_params: &StepStateParams,
529 record: &GlobItemsDep,
530) -> Result<Diff<GlobItemsDep>> {
531 let actual = record
533 .clone()
534 .update_paths(cmp_params.pipeline_rundir, cmp_params.pmp)?;
535 Ok(GlobItemsDep::diff_superficial(record, &actual))
536}
537
538fn superficial_compare_glob(
539 cmp_params: &StepStateParams,
540 record: &GlobDep,
541) -> Result<Diff<GlobDep>> {
542 let actual = GlobDep::new(record.glob.clone()).update_collection_digests(cmp_params.pmp)?;
543 Ok(GlobDep::diff_superficial(record, &actual))
544}
545
546fn superficial_compare_regex(
547 cmp_params: &StepStateParams,
548 record: &RegexDep,
549) -> Result<Diff<RegexDep>> {
550 let actual = RegexDep::new(record.path.clone(), record.regex.clone())
551 .update_metadata(cmp_params.pmp.get(&record.path));
552 let diff = RegexDep::diff_superficial(record, &actual);
553 Ok(diff)
554}
555
556fn superficial_compare_lines(
557 cmp_params: &StepStateParams,
558 record: &LinesDep,
559) -> Result<Diff<LinesDep>> {
560 let actual = LinesDep::new(record.path.clone(), record.begin, record.end)
561 .update_metadata(cmp_params.pmp.get(&record.path));
562
563 Ok(LinesDep::diff_superficial(record, &actual))
564}
565
566fn superficial_compare_query_digest(
567 cmp_params: &StepStateParams,
568 record: &SqliteQueryDep,
569) -> Result<Diff<SqliteQueryDep>> {
570 let actual = SqliteQueryDep::new(record.path.clone(), record.query.clone())
571 .update_metadata(cmp_params.pmp.get(&record.path));
572
573 Ok(SqliteQueryDep::diff_superficial(record, &actual))
574}
575
576fn diff_of_dep<T>(dep: Diff<T>) -> Diff<XvcDependency>
577where
578 T: Storable + Into<XvcDependency>,
579{
580 match dep {
581 Diff::Identical => Diff::Identical,
582 Diff::RecordMissing { actual } => Diff::RecordMissing {
583 actual: actual.into(),
584 },
585 Diff::ActualMissing { record } => Diff::ActualMissing {
586 record: record.into(),
587 },
588 Diff::Different { record, actual } => Diff::Different {
589 record: record.into(),
590 actual: actual.into(),
591 },
592 Diff::Skipped => Diff::Skipped,
593 }
594}