1use super::*;
2
3#[derive(Clone, Debug, PartialEq)]
5pub struct PackagedArtifactProductionProfileSummary {
6 pub profile_id: &'static str,
8 pub label: &'static str,
10 pub artifact_version: u16,
12 pub time_range: TimeRange,
14 pub source_provenance: String,
16 pub body_coverage: PackagedBodyCoverageSummary,
18 pub artifact_profile: ArtifactProfile,
20 pub speed_policy: pleiades_compression::SpeedPolicy,
22 pub generation_policy: PackagedArtifactGenerationPolicy,
24 pub request_policy: PackagedRequestPolicySummary,
26 pub lookup_epoch_policy: PackagedLookupEpochPolicy,
28 pub frame_treatment: PackagedFrameTreatmentSummary,
30 pub storage_summary: PackagedArtifactStorageSummary,
32 pub target_thresholds: PackagedArtifactTargetThresholdSummary,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
38pub enum PackagedArtifactProductionProfileSummaryValidationError {
39 FieldOutOfSync { field: &'static str },
41}
42
43impl fmt::Display for PackagedArtifactProductionProfileSummaryValidationError {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 Self::FieldOutOfSync { field } => write!(
47 f,
48 "the packaged artifact production profile summary field `{field}` is out of sync with the current posture"
49 ),
50 }
51 }
52}
53
54impl std::error::Error for PackagedArtifactProductionProfileSummaryValidationError {}
55
56impl PackagedArtifactProductionProfileSummary {
57 pub fn summary_line(&self) -> String {
59 format!(
60 "Packaged artifact production profile draft: profile id={}; label={}; version={}; time range={}; source provenance={}; body coverage={}; artifact profile={}; output support={}; speed policy={}; generation policy={}; segment strategy={}; request policy={}; lookup epoch policy={}; frame treatment={}; storage/reconstruction={}; {}",
61 self.profile_id,
62 self.label,
63 self.artifact_version,
64 self.time_range,
65 self.source_provenance,
66 self.body_coverage,
67 self.artifact_profile,
68 self.artifact_profile.output_support_entries_summary_line(),
69 self.speed_policy,
70 self.generation_policy,
71 self.generation_policy.segment_strategy(),
72 self.request_policy,
73 self.lookup_epoch_policy.summary_line(),
74 self.frame_treatment,
75 self.storage_summary,
76 self.target_thresholds,
77 )
78 }
79
80 pub fn validate(&self) -> Result<(), PackagedArtifactProductionProfileSummaryValidationError> {
82 if self.profile_id != ARTIFACT_PROFILE_ID {
83 return Err(
84 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
85 field: "profile_id",
86 },
87 );
88 }
89 if self.label != ARTIFACT_LABEL {
90 return Err(
91 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
92 field: "label",
93 },
94 );
95 }
96 if self.artifact_version != packaged_artifact().header.version {
97 return Err(
98 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
99 field: "artifact_version",
100 },
101 );
102 }
103 if self.time_range != artifact_time_range(packaged_artifact()) {
104 return Err(
105 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
106 field: "time_range",
107 },
108 );
109 }
110 if self.source_provenance != production_generation_source_summary_for_report() {
111 return Err(
112 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
113 field: "source_provenance",
114 },
115 );
116 }
117 self.body_coverage.validate().map_err(|_| {
118 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
119 field: "body_coverage",
120 }
121 })?;
122 if self.artifact_profile != packaged_artifact_profile_summary_details().profile {
123 return Err(
124 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
125 field: "artifact_profile",
126 },
127 );
128 }
129 if self.speed_policy != self.artifact_profile.speed_policy {
130 return Err(
131 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
132 field: "speed_policy",
133 },
134 );
135 }
136 self.generation_policy.validate().map_err(|_| {
137 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
138 field: "generation_policy",
139 }
140 })?;
141 self.request_policy.validate().map_err(|_| {
142 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
143 field: "request_policy",
144 }
145 })?;
146 if self.lookup_epoch_policy != packaged_lookup_epoch_policy_summary_details().policy {
147 return Err(
148 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
149 field: "lookup_epoch_policy",
150 },
151 );
152 }
153 self.frame_treatment.validate().map_err(|_| {
154 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
155 field: "frame_treatment",
156 }
157 })?;
158 self.storage_summary.validate().map_err(|_| {
159 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
160 field: "storage_summary",
161 }
162 })?;
163 self.target_thresholds.validate().map_err(|_| {
164 PackagedArtifactProductionProfileSummaryValidationError::FieldOutOfSync {
165 field: "target_thresholds",
166 }
167 })?;
168
169 Ok(())
170 }
171
172 pub fn validated_summary_line(
174 &self,
175 ) -> Result<String, PackagedArtifactProductionProfileSummaryValidationError> {
176 self.validate()?;
177 Ok(self.summary_line())
178 }
179}
180
181impl fmt::Display for PackagedArtifactProductionProfileSummary {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 f.write_str(&self.summary_line())
184 }
185}
186
187pub fn packaged_artifact_production_profile_summary_details(
189) -> PackagedArtifactProductionProfileSummary {
190 let artifact = packaged_artifact();
191 let profile_summary = packaged_artifact_profile_summary_details();
192 let speed_policy = profile_summary.profile.speed_policy;
193 let summary = PackagedArtifactProductionProfileSummary {
194 profile_id: ARTIFACT_PROFILE_ID,
195 label: ARTIFACT_LABEL,
196 artifact_version: artifact.header.version,
197 time_range: artifact_time_range(artifact),
198 source_provenance: production_generation_source_summary_for_report(),
199 body_coverage: packaged_body_coverage_summary_details(),
200 artifact_profile: profile_summary.profile,
201 speed_policy,
202 generation_policy: PackagedArtifactGenerationPolicy::AdjacentSameBodyQuadraticWindows,
203 request_policy: packaged_request_policy_summary_details(),
204 lookup_epoch_policy: packaged_lookup_epoch_policy_summary_details().policy,
205 frame_treatment: packaged_frame_treatment_summary_details(),
206 storage_summary: packaged_artifact_storage_summary_details(),
207 target_thresholds: packaged_artifact_target_threshold_summary_details(),
208 };
209 debug_assert!(summary.validate().is_ok());
210 summary
211}
212
213pub fn packaged_artifact_production_profile_summary() -> &'static str {
215 static SUMMARY: OnceLock<String> = OnceLock::new();
216 SUMMARY
217 .get_or_init(|| {
218 let summary = packaged_artifact_production_profile_summary_details();
219 match summary.validated_summary_line() {
220 Ok(rendered) => rendered,
221 Err(error) => {
222 format!("Packaged artifact production profile draft: unavailable ({error})")
223 }
224 }
225 })
226 .as_str()
227}
228
229#[derive(Clone, Debug, PartialEq)]
231pub struct PackagedArtifactGeneratorParameters {
232 pub profile_id: &'static str,
234 pub label: &'static str,
236 pub artifact_version: u16,
238 pub time_range: TimeRange,
240 pub source_provenance: String,
242 pub checksum: u64,
244 pub artifact_size_bytes: usize,
246 pub body_coverage: PackagedBodyCoverageSummary,
248 pub residual_body_coverage: ArtifactResidualBodyCoverageSummary,
250 pub artifact_profile: ArtifactProfile,
252 pub speed_policy: pleiades_compression::SpeedPolicy,
254 pub generation_policy: PackagedArtifactGenerationPolicy,
256 pub request_policy: PackagedRequestPolicySummary,
258 pub lookup_epoch_policy: PackagedLookupEpochPolicy,
260 pub frame_treatment: PackagedFrameTreatmentSummary,
262 pub storage_summary: PackagedArtifactStorageSummary,
264 pub target_thresholds: PackagedArtifactTargetThresholdSummary,
266}
267
268impl PackagedArtifactGeneratorParameters {
269 pub fn summary_line(&self) -> String {
271 format!(
272 "Packaged artifact generator parameters: profile id={}; label={}; version={}; time range={}; source provenance={}; checksum=0x{:016x}; artifact size={} bytes; body coverage={}; residual bodies={}; artifact profile={}; output support={}; speed policy={}; generation policy={}; segment strategy={}; request policy={}; lookup epoch policy={}; frame treatment={}; storage/reconstruction={}; {}",
273 self.profile_id,
274 self.label,
275 self.artifact_version,
276 self.time_range,
277 self.source_provenance,
278 self.checksum,
279 self.artifact_size_bytes,
280 self.body_coverage,
281 self.residual_body_coverage.summary_line_with_body_count(),
282 self.artifact_profile,
283 self.artifact_profile.output_support_entries_summary_line(),
284 self.speed_policy,
285 self.generation_policy,
286 self.generation_policy.segment_strategy(),
287 self.request_policy,
288 self.lookup_epoch_policy.summary_line(),
289 self.frame_treatment,
290 self.storage_summary,
291 self.target_thresholds,
292 )
293 }
294
295 pub fn validate(&self) -> Result<(), pleiades_compression::CompressionError> {
297 let current = packaged_artifact_production_profile_summary_details();
298 let artifact = packaged_artifact();
299
300 if self.profile_id != current.profile_id {
301 return Err(pleiades_compression::CompressionError::new(
302 pleiades_compression::CompressionErrorKind::InvalidFormat,
303 "packaged artifact generator parameters profile id does not match the current production profile",
304 ));
305 }
306 if self.label != current.label {
307 return Err(pleiades_compression::CompressionError::new(
308 pleiades_compression::CompressionErrorKind::InvalidFormat,
309 "packaged artifact generator parameters label does not match the current production profile",
310 ));
311 }
312 if self.artifact_version != current.artifact_version {
313 return Err(pleiades_compression::CompressionError::new(
314 pleiades_compression::CompressionErrorKind::InvalidFormat,
315 "packaged artifact generator parameters version does not match the current production profile",
316 ));
317 }
318 if self.time_range != current.time_range {
319 return Err(pleiades_compression::CompressionError::new(
320 pleiades_compression::CompressionErrorKind::InvalidFormat,
321 "packaged artifact generator parameters time range does not match the current production profile",
322 ));
323 }
324 if self.source_provenance != current.source_provenance {
325 return Err(pleiades_compression::CompressionError::new(
326 pleiades_compression::CompressionErrorKind::InvalidFormat,
327 "packaged artifact generator parameters source provenance does not match the current production profile",
328 ));
329 }
330 if self.checksum != artifact.checksum {
331 return Err(pleiades_compression::CompressionError::new(
332 pleiades_compression::CompressionErrorKind::InvalidFormat,
333 "packaged artifact generator parameters checksum does not match the current packaged artifact",
334 ));
335 }
336 let expected_artifact_size_bytes = packaged_artifact_encoded_bytes(artifact);
337 if self.artifact_size_bytes != expected_artifact_size_bytes {
338 return Err(pleiades_compression::CompressionError::new(
339 pleiades_compression::CompressionErrorKind::InvalidFormat,
340 format!(
341 "packaged artifact generator parameters artifact size {} bytes does not match the current packaged artifact size {} bytes",
342 self.artifact_size_bytes,
343 expected_artifact_size_bytes
344 ),
345 ));
346 }
347 if self.body_coverage != current.body_coverage {
348 return Err(pleiades_compression::CompressionError::new(
349 pleiades_compression::CompressionErrorKind::InvalidFormat,
350 "packaged artifact generator parameters body coverage does not match the current production profile",
351 ));
352 }
353 if self.residual_body_coverage != artifact.residual_body_coverage_summary() {
354 return Err(pleiades_compression::CompressionError::new(
355 pleiades_compression::CompressionErrorKind::InvalidFormat,
356 "packaged artifact generator parameters residual body coverage does not match the current packaged artifact",
357 ));
358 }
359 if self.artifact_profile != current.artifact_profile {
360 return Err(pleiades_compression::CompressionError::new(
361 pleiades_compression::CompressionErrorKind::InvalidFormat,
362 "packaged artifact generator parameters artifact profile does not match the current production profile",
363 ));
364 }
365 if self.speed_policy != current.speed_policy {
366 return Err(pleiades_compression::CompressionError::new(
367 pleiades_compression::CompressionErrorKind::InvalidFormat,
368 "packaged artifact generator parameters speed policy does not match the current production profile",
369 ));
370 }
371 if self.generation_policy != current.generation_policy {
372 return Err(pleiades_compression::CompressionError::new(
373 pleiades_compression::CompressionErrorKind::InvalidFormat,
374 "packaged artifact generator parameters generation policy does not match the current production profile",
375 ));
376 }
377 if self.request_policy != current.request_policy {
378 return Err(pleiades_compression::CompressionError::new(
379 pleiades_compression::CompressionErrorKind::InvalidFormat,
380 "packaged artifact generator parameters request policy does not match the current production profile",
381 ));
382 }
383 if self.lookup_epoch_policy != current.lookup_epoch_policy {
384 return Err(pleiades_compression::CompressionError::new(
385 pleiades_compression::CompressionErrorKind::InvalidFormat,
386 "packaged artifact generator parameters lookup epoch policy does not match the current production profile",
387 ));
388 }
389 if self.frame_treatment != current.frame_treatment {
390 return Err(pleiades_compression::CompressionError::new(
391 pleiades_compression::CompressionErrorKind::InvalidFormat,
392 "packaged artifact generator parameters frame-treatment policy does not match the current production profile",
393 ));
394 }
395 if self.storage_summary != current.storage_summary {
396 return Err(pleiades_compression::CompressionError::new(
397 pleiades_compression::CompressionErrorKind::InvalidFormat,
398 "packaged artifact generator parameters storage summary does not match the current production profile",
399 ));
400 }
401 self.target_thresholds
402 .state
403 .validate_production_ready()
404 .map_err(|_| {
405 pleiades_compression::CompressionError::new(
406 pleiades_compression::CompressionErrorKind::InvalidFormat,
407 "packaged artifact generator parameters target thresholds do not match the current production profile",
408 )
409 })?;
410 if self.target_thresholds != current.target_thresholds {
411 return Err(pleiades_compression::CompressionError::new(
412 pleiades_compression::CompressionErrorKind::InvalidFormat,
413 "packaged artifact generator parameters target thresholds do not match the current production profile",
414 ));
415 }
416
417 Ok(())
418 }
419
420 pub fn validated_summary_line(&self) -> Result<String, pleiades_compression::CompressionError> {
422 self.validate()?;
423 Ok(self.summary_line())
424 }
425}
426
427impl fmt::Display for PackagedArtifactGeneratorParameters {
428 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429 f.write_str(&self.summary_line())
430 }
431}
432
433pub(crate) fn fnv1a64(bytes: &[u8]) -> u64 {
434 const OFFSET_BASIS: u64 = 0xcbf29ce484222325;
435 const PRIME: u64 = 0x100000001b3;
436
437 let mut hash = OFFSET_BASIS;
438 for byte in bytes {
439 hash ^= u64::from(*byte);
440 hash = hash.wrapping_mul(PRIME);
441 }
442 hash
443}
444
445fn packaged_artifact_generation_manifest_checksum(
446 parameters: &PackagedArtifactGeneratorParameters,
447 regeneration: &PackagedArtifactRegenerationSummary,
448) -> u64 {
449 let mut bytes = Vec::new();
450 bytes.extend_from_slice(parameters.summary_line().as_bytes());
451 bytes.push(b'\n');
452 bytes.extend_from_slice(regeneration.summary_line().as_bytes());
453 fnv1a64(&bytes)
454}
455
456#[derive(Clone, Debug, PartialEq)]
458pub struct PackagedArtifactGenerationManifest {
459 pub parameters: PackagedArtifactGeneratorParameters,
461 pub regeneration: PackagedArtifactRegenerationSummary,
463 pub manifest_checksum: u64,
465}
466
467impl PackagedArtifactGenerationManifest {
468 pub fn summary_line(&self) -> String {
470 format!(
471 "Packaged artifact generation manifest: manifest checksum=0x{:016x}; {}; regeneration={}",
472 self.manifest_checksum, self.parameters, self.regeneration,
473 )
474 }
475
476 pub fn validate(&self) -> Result<(), pleiades_compression::CompressionError> {
478 self.parameters.validate()?;
479 self.regeneration.validate()?;
480
481 let expected_checksum =
482 packaged_artifact_generation_manifest_checksum(&self.parameters, &self.regeneration);
483 if self.manifest_checksum != expected_checksum {
484 return Err(pleiades_compression::CompressionError::new(
485 pleiades_compression::CompressionErrorKind::InvalidFormat,
486 format!(
487 "packaged artifact generation manifest checksum 0x{:016x} does not match the current packaged-artifact manifest checksum 0x{:016x}",
488 self.manifest_checksum,
489 expected_checksum
490 ),
491 ));
492 }
493
494 Ok(())
495 }
496
497 pub fn validated_summary_line(&self) -> Result<String, pleiades_compression::CompressionError> {
499 self.validate()?;
500 Ok(self.summary_line())
501 }
502}
503
504impl fmt::Display for PackagedArtifactGenerationManifest {
505 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506 f.write_str(&self.summary_line())
507 }
508}
509
510pub fn packaged_artifact_generator_parameters_details() -> PackagedArtifactGeneratorParameters {
512 let summary = packaged_artifact_production_profile_summary_details();
513 let regeneration = packaged_artifact_regeneration_summary_details();
514 let parameters = PackagedArtifactGeneratorParameters {
515 profile_id: summary.profile_id,
516 label: summary.label,
517 artifact_version: summary.artifact_version,
518 time_range: summary.time_range,
519 source_provenance: summary.source_provenance,
520 checksum: regeneration.checksum,
521 artifact_size_bytes: regeneration.artifact_size_bytes,
522 body_coverage: summary.body_coverage,
523 residual_body_coverage: regeneration.residual_body_coverage_summary(),
524 artifact_profile: summary.artifact_profile,
525 speed_policy: summary.speed_policy,
526 generation_policy: summary.generation_policy,
527 request_policy: summary.request_policy,
528 lookup_epoch_policy: summary.lookup_epoch_policy,
529 frame_treatment: summary.frame_treatment,
530 storage_summary: summary.storage_summary,
531 target_thresholds: summary.target_thresholds,
532 };
533 debug_assert!(parameters.validate().is_ok());
534 parameters
535}
536
537pub fn packaged_artifact_generation_manifest_details() -> PackagedArtifactGenerationManifest {
539 let parameters = packaged_artifact_generator_parameters_details();
540 let regeneration = packaged_artifact_regeneration_summary_details();
541 let manifest = PackagedArtifactGenerationManifest {
542 manifest_checksum: packaged_artifact_generation_manifest_checksum(
543 ¶meters,
544 ®eneration,
545 ),
546 parameters,
547 regeneration,
548 };
549 debug_assert!(manifest.validate().is_ok());
550 manifest
551}
552
553pub fn packaged_artifact_generation_manifest() -> &'static str {
555 static SUMMARY: OnceLock<String> = OnceLock::new();
556 SUMMARY
557 .get_or_init(|| {
558 let manifest = packaged_artifact_generation_manifest_details();
559 match manifest.validated_summary_line() {
560 Ok(rendered) => rendered,
561 Err(error) => {
562 format!("Packaged artifact generation manifest: unavailable ({error})")
563 }
564 }
565 })
566 .as_str()
567}
568
569#[derive(Clone, Debug, PartialEq, Eq)]
570pub struct PackagedArtifactProfileSummary {
571 pub body_count: usize,
573 pub bodies: Vec<CelestialBody>,
575 pub endian_policy: EndianPolicy,
577 pub profile: ArtifactProfile,
579}
580
581impl PackagedArtifactProfileSummary {
582 pub fn profile_coverage_summary(&self) -> ArtifactProfileCoverageSummary {
584 ArtifactProfileCoverageSummary::new(self.profile.clone(), self.bodies.clone())
585 }
586
587 pub fn validate(&self) -> Result<(), pleiades_compression::CompressionError> {
590 let artifact = packaged_artifact();
591 if self.endian_policy != artifact.header.endian_policy {
592 return Err(pleiades_compression::CompressionError::new(
593 pleiades_compression::CompressionErrorKind::InvalidFormat,
594 "packaged artifact profile byte-order policy does not match the checked-in packaged artifact header",
595 ));
596 }
597 if self.profile != artifact.profile_coverage_summary().profile {
598 return Err(pleiades_compression::CompressionError::new(
599 pleiades_compression::CompressionErrorKind::InvalidFormat,
600 "packaged artifact profile metadata does not match the checked-in packaged artifact profile",
601 ));
602 }
603
604 if self.body_count != self.bodies.len() {
605 return Err(pleiades_compression::CompressionError::new(
606 pleiades_compression::CompressionErrorKind::InvalidFormat,
607 "packaged artifact profile body count does not match bundled body list",
608 ));
609 }
610
611 if self.bodies.is_empty() {
612 let coverage = self.profile_coverage_summary();
613 coverage.validate()?;
614 return Ok(());
615 }
616
617 if self
618 .bodies
619 .iter()
620 .enumerate()
621 .any(|(index, body)| self.bodies[..index].contains(body))
622 {
623 let coverage = self.profile_coverage_summary();
624 coverage.validate()?;
625 return Ok(());
626 }
627
628 if self.bodies.as_slice() != packaged_bodies() {
629 return Err(pleiades_compression::CompressionError::new(
630 pleiades_compression::CompressionErrorKind::InvalidFormat,
631 format!(
632 "packaged artifact profile bundled body list does not match the checked-in packaged body set: expected [{}]; got [{}]",
633 join_display(packaged_bodies()),
634 join_display(&self.bodies)
635 ),
636 ));
637 }
638
639 let coverage = self.profile_coverage_summary();
640 coverage.validate()?;
641
642 Ok(())
643 }
644
645 pub fn validated_summary_line(&self) -> Result<String, pleiades_compression::CompressionError> {
647 self.validate()?;
648 Ok(self.summary_line())
649 }
650
651 pub fn validated_summary_line_with_bodies(
653 &self,
654 ) -> Result<String, pleiades_compression::CompressionError> {
655 self.validate()?;
656 Ok(self.summary_line_with_bodies())
657 }
658
659 pub fn validated_summary_line_with_output_support(
661 &self,
662 ) -> Result<String, pleiades_compression::CompressionError> {
663 self.validate()?;
664 Ok(format!(
665 "{}; output support: {}",
666 self.summary_line_with_bodies(),
667 self.profile.validated_output_support_summary_line()?
668 ))
669 }
670
671 pub fn summary_line(&self) -> String {
673 let coverage = self.profile_coverage_summary();
674 format!(
675 "byte order: {}; {}",
676 self.endian_policy,
677 coverage.summary_line()
678 )
679 }
680
681 pub fn output_support_summary_line(&self) -> String {
683 self.profile.output_support_summary_line()
684 }
685
686 pub fn summary_line_with_bodies(&self) -> String {
688 let coverage = self.profile_coverage_summary();
689 format!(
690 "byte order: {}; {}",
691 self.endian_policy,
692 coverage.summary_line_with_bodies(),
693 )
694 }
695
696 pub fn summary_line_with_output_support(&self) -> String {
699 format!(
700 "{}; output support: {}",
701 self.summary_line_with_bodies(),
702 self.output_support_summary_line()
703 )
704 }
705}
706
707impl fmt::Display for PackagedArtifactProfileSummary {
708 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709 f.write_str(&self.summary_line())
710 }
711}
712
713pub fn packaged_artifact_profile_summary_details() -> PackagedArtifactProfileSummary {
715 let artifact = packaged_artifact();
716 let coverage = artifact.profile_coverage_summary();
717 let summary = PackagedArtifactProfileSummary {
718 body_count: coverage.body_count,
719 bodies: coverage.bodies,
720 endian_policy: artifact.header.endian_policy,
721 profile: coverage.profile,
722 };
723 debug_assert!(summary.validate().is_ok());
724 summary
725}
726
727pub fn packaged_artifact_profile_coverage_summary_details() -> ArtifactProfileCoverageSummary {
729 packaged_artifact_profile_summary_details().profile_coverage_summary()
730}
731
732pub fn packaged_artifact_profile_summary() -> String {
737 render_packaged_artifact_profile_summary(&packaged_artifact_profile_summary_details(), false)
738}
739
740pub fn packaged_artifact_profile_summary_with_body_coverage() -> String {
745 render_packaged_artifact_profile_summary(&packaged_artifact_profile_summary_details(), true)
746}
747
748pub fn packaged_artifact_profile_summary_with_output_support() -> String {
753 let summary = packaged_artifact_profile_summary_details();
754 match summary.validated_summary_line_with_output_support() {
755 Ok(line) => line,
756 Err(error) => {
757 format!("Packaged artifact profile with output support: unavailable ({error})")
758 }
759 }
760}
761
762#[derive(Clone, Debug, PartialEq, Eq)]
764pub struct PackagedArtifactOutputSupportSummary {
765 pub profile: ArtifactProfile,
767}
768
769fn validate_packaged_artifact_output_support_profile(
770 profile: &ArtifactProfile,
771) -> Result<(), pleiades_compression::CompressionError> {
772 let expected_states = [
773 (
774 ArtifactOutput::EclipticCoordinates,
775 ArtifactOutputSupport::Derived,
776 ),
777 (
778 ArtifactOutput::EquatorialCoordinates,
779 ArtifactOutputSupport::Derived,
780 ),
781 (
782 ArtifactOutput::ApparentCorrections,
783 ArtifactOutputSupport::Unsupported,
784 ),
785 (
786 ArtifactOutput::TopocentricCoordinates,
787 ArtifactOutputSupport::Unsupported,
788 ),
789 (
790 ArtifactOutput::SiderealCoordinates,
791 ArtifactOutputSupport::Unsupported,
792 ),
793 (ArtifactOutput::Motion, ArtifactOutputSupport::Derived),
794 ];
795
796 for (output, expected_support) in expected_states {
797 let actual_support = profile.output_support(output);
798 if actual_support != expected_support {
799 return Err(pleiades_compression::CompressionError::new(
800 pleiades_compression::CompressionErrorKind::InvalidFormat,
801 format!(
802 "packaged artifact output support summary is out of sync with the bundled artifact profile field `output_support[{output}]`: expected {expected_support}, found {actual_support}"
803 ),
804 ));
805 }
806 }
807
808 Ok(())
809}
810
811impl PackagedArtifactOutputSupportSummary {
812 pub fn validate(&self) -> Result<(), pleiades_compression::CompressionError> {
816 self.profile.validate()?;
817 validate_packaged_artifact_output_support_profile(&self.profile)
818 }
819
820 pub fn validated_summary_line(&self) -> Result<String, pleiades_compression::CompressionError> {
822 self.validate()?;
823 Ok(self.summary_line())
824 }
825
826 pub fn summary_line(&self) -> String {
828 self.profile.output_support_summary_line()
829 }
830}
831
832impl fmt::Display for PackagedArtifactOutputSupportSummary {
833 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
834 f.write_str(&self.summary_line())
835 }
836}
837
838pub fn packaged_artifact_output_support_summary_details() -> PackagedArtifactOutputSupportSummary {
840 let summary = PackagedArtifactOutputSupportSummary {
841 profile: packaged_artifact_profile_summary_details().profile,
842 };
843 debug_assert!(summary.validate().is_ok());
844 summary
845}
846
847#[derive(Clone, Copy, Debug, PartialEq, Eq)]
849pub struct PackagedArtifactSpeedPolicySummary {
850 pub policy: SpeedPolicy,
852}
853
854#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
856pub enum PackagedArtifactSpeedPolicySummaryValidationError {
857 FieldOutOfSync { field: &'static str },
859}
860
861impl fmt::Display for PackagedArtifactSpeedPolicySummaryValidationError {
862 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
863 match self {
864 Self::FieldOutOfSync { field } => write!(
865 f,
866 "the packaged artifact speed-policy summary field `{field}` is out of sync with the current posture"
867 ),
868 }
869 }
870}
871
872impl std::error::Error for PackagedArtifactSpeedPolicySummaryValidationError {}
873
874impl PackagedArtifactSpeedPolicySummary {
875 pub fn summary_line(self) -> String {
877 format!(
878 "{}; motion output support={}",
879 self.policy,
880 self.policy.motion_output_support()
881 )
882 }
883
884 pub fn validate(&self) -> Result<(), PackagedArtifactSpeedPolicySummaryValidationError> {
886 let current_policy = packaged_artifact_profile_summary_details()
887 .profile
888 .speed_policy;
889 if self.policy != current_policy {
890 return Err(
891 PackagedArtifactSpeedPolicySummaryValidationError::FieldOutOfSync {
892 field: "policy",
893 },
894 );
895 }
896 Ok(())
897 }
898
899 pub fn validated_summary_line(
901 &self,
902 ) -> Result<String, PackagedArtifactSpeedPolicySummaryValidationError> {
903 self.validate()?;
904 Ok(self.summary_line())
905 }
906}
907
908impl fmt::Display for PackagedArtifactSpeedPolicySummary {
909 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
910 f.write_str(&self.summary_line())
911 }
912}
913
914const PACKAGED_ARTIFACT_SPEED_POLICY_SUMMARY: PackagedArtifactSpeedPolicySummary =
915 PackagedArtifactSpeedPolicySummary {
916 policy: SpeedPolicy::FittedDerivative,
917 };
918
919pub fn packaged_artifact_speed_policy_summary_details() -> PackagedArtifactSpeedPolicySummary {
921 let summary = PACKAGED_ARTIFACT_SPEED_POLICY_SUMMARY;
922 debug_assert!(summary.validate().is_ok());
923 summary
924}
925
926pub(crate) fn render_packaged_artifact_profile_summary(
927 summary: &PackagedArtifactProfileSummary,
928 with_bodies: bool,
929) -> String {
930 if with_bodies {
931 match summary.validated_summary_line_with_bodies() {
932 Ok(line) => line,
933 Err(error) => {
934 format!("Packaged artifact profile with bundled bodies: unavailable ({error})")
935 }
936 }
937 } else {
938 match summary.validated_summary_line() {
939 Ok(line) => line,
940 Err(error) => format!("Packaged artifact profile: unavailable ({error})"),
941 }
942 }
943}