Skip to main content

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}