nom_exif/image_metadata.rs
1//! Structured image-metadata view returned by
2//! `MediaParser::parse_image_metadata` (lands in phase 4).
3//!
4//! See [`ImageMetadata`].
5//!
6//! # Example: lazy and eager forms compose
7//!
8//! ```rust
9//! use nom_exif::{ImageMetadata, ImageFormatMetadata, ExifIter, Exif};
10//!
11//! // Default form (eager — type parameter defaults to Exif).
12//! let _eager_default: ImageMetadata = ImageMetadata::default();
13//!
14//! // Explicit lazy form.
15//! let lazy: ImageMetadata<ExifIter> = ImageMetadata {
16//! exif: None,
17//! format: None,
18//! };
19//!
20//! // Lazy → eager conversion via From.
21//! let _eager: ImageMetadata<Exif> = lazy.into();
22//! ```
23
24use crate::exif::png_text::PngTextChunks;
25
26mod sealed {
27 pub trait Sealed {}
28}
29
30/// Marker trait for the two valid "EXIF representations" held by
31/// [`ImageMetadata`]: [`Exif`](crate::Exif) (eager) and [`ExifIter`](crate::ExifIter)
32/// (lazy). Sealed — users cannot add their own implementations.
33pub trait ExifRepr: sealed::Sealed {}
34
35impl sealed::Sealed for crate::Exif {}
36impl ExifRepr for crate::Exif {}
37
38impl sealed::Sealed for crate::ExifIter {}
39impl ExifRepr for crate::ExifIter {}
40
41/// Structured image-metadata view: EXIF (if any) plus format-specific
42/// metadata (if any).
43///
44/// Default `E = Exif` — eager EXIF representation. The
45/// `MediaParser::parse_image_metadata` method returns
46/// `ImageMetadata<ExifIter>` (lazy); convert to the default eager form
47/// via `.into()` when desired.
48///
49/// **Forward-compat note**: this struct is shaped to be reused
50/// unchanged by a future v4 redesign of the [`Metadata`](crate::Metadata)
51/// enum (`Metadata::Image(ImageMetadata)`).
52#[derive(Debug, Clone)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54pub struct ImageMetadata<E: ExifRepr = crate::Exif> {
55 /// EXIF tags found in the source image, if any. For PNG, this
56 /// includes legacy `Raw profile type {exif,APP1}` hex-encoded
57 /// EXIF transparently merged.
58 pub exif: Option<E>,
59
60 /// Format-specific metadata that does not fit the EXIF/IFD
61 /// abstraction (e.g. PNG `tEXt` chunks).
62 pub format: Option<ImageFormatMetadata>,
63}
64
65impl<E: ExifRepr> Default for ImageMetadata<E> {
66 fn default() -> Self {
67 ImageMetadata {
68 exif: None,
69 format: None,
70 }
71 }
72}
73
74impl From<ImageMetadata<crate::ExifIter>> for ImageMetadata<crate::Exif> {
75 fn from(m: ImageMetadata<crate::ExifIter>) -> Self {
76 ImageMetadata {
77 exif: m.exif.map(Into::into),
78 format: m.format,
79 }
80 }
81}
82
83/// Format-specific image metadata. One variant per format that has
84/// metadata not expressible as EXIF tags.
85///
86/// Marked `#[non_exhaustive]` so future formats can be added
87/// (`Gif(...)`, `Webp(...)` etc.) without breaking exhaustive `match`
88/// statements in user code.
89#[derive(Debug, Clone)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91#[non_exhaustive]
92pub enum ImageFormatMetadata {
93 /// PNG `tEXt` chunks. Latin-1 key/value pairs in file order.
94 Png(PngTextChunks),
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn default_is_empty() {
103 let m: ImageMetadata = ImageMetadata::default();
104 assert!(m.exif.is_none());
105 assert!(m.format.is_none());
106 }
107
108 #[test]
109 fn generic_explicit_lazy_form() {
110 // ImageMetadata<ExifIter> compiles and is constructible.
111 let m: ImageMetadata<crate::ExifIter> = ImageMetadata {
112 exif: None,
113 format: None,
114 };
115 assert!(m.exif.is_none());
116 }
117
118 #[test]
119 fn from_lazy_to_eager_compiles() {
120 // We can't easily construct an ExifIter here; just verify the
121 // type-level conversion exists by going through Default.
122 let lazy: ImageMetadata<crate::ExifIter> = ImageMetadata::default();
123 let _eager: ImageMetadata<crate::Exif> = lazy.into();
124 }
125
126 #[test]
127 fn format_metadata_png_variant() {
128 let png_text = PngTextChunks::default();
129 let fm = ImageFormatMetadata::Png(png_text);
130 match fm {
131 ImageFormatMetadata::Png(t) => assert!(t.is_empty()),
132 }
133 }
134}