1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct TagId(&'static str);
10
11impl TagId {
12 pub const fn new(name: &'static str) -> Self {
14 Self(name)
15 }
16
17 pub fn name(&self) -> &str {
19 self.0
20 }
21
22 pub const MAKE: Self = Self("Make");
24 pub const MODEL: Self = Self("Model");
25 pub const DATE_TIME_ORIGINAL: Self = Self("DateTimeOriginal");
26 pub const CREATE_DATE: Self = Self("CreateDate");
27 pub const MODIFY_DATE: Self = Self("ModifyDate");
28 pub const IMAGE_WIDTH: Self = Self("ImageWidth");
29 pub const IMAGE_HEIGHT: Self = Self("ImageHeight");
30 pub const ORIENTATION: Self = Self("Orientation");
31 pub const X_RESOLUTION: Self = Self("XResolution");
32 pub const Y_RESOLUTION: Self = Self("YResolution");
33 pub const RESOLUTION_UNIT: Self = Self("ResolutionUnit");
34 pub const SOFTWARE: Self = Self("Software");
35 pub const COPYRIGHT: Self = Self("Copyright");
36 pub const ARTIST: Self = Self("Artist");
37 pub const IMAGE_DESCRIPTION: Self = Self("ImageDescription");
38
39 pub const EXPOSURE_TIME: Self = Self("ExposureTime");
41 pub const F_NUMBER: Self = Self("FNumber");
42 pub const EXPOSURE_PROGRAM: Self = Self("ExposureProgram");
43 pub const ISO: Self = Self("ISO");
44 pub const SENSITIVITY_TYPE: Self = Self("SensitivityType");
45 pub const RECOMMENDED_EXPOSURE_INDEX: Self = Self("RecommendedExposureIndex");
46 pub const EXIF_VERSION: Self = Self("ExifVersion");
47 pub const DATE_TIME_DIGITIZED: Self = Self("DateTimeDigitized");
48 pub const COMPONENT_CONFIGURATION: Self = Self("ComponentConfiguration");
49 pub const SHUTTER_SPEED_VALUE: Self = Self("ShutterSpeedValue");
50 pub const APERTURE_VALUE: Self = Self("ApertureValue");
51 pub const BRIGHTNESS_VALUE: Self = Self("BrightnessValue");
52 pub const EXPOSURE_COMPENSATION: Self = Self("ExposureCompensation");
53 pub const MAX_APERTURE_VALUE: Self = Self("MaxApertureValue");
54 pub const SUBJECT_DISTANCE: Self = Self("SubjectDistance");
55 pub const METERING_MODE: Self = Self("MeteringMode");
56 pub const LIGHT_SOURCE: Self = Self("LightSource");
57 pub const FLASH: Self = Self("Flash");
58 pub const FOCAL_LENGTH: Self = Self("FocalLength");
59 pub const FOCAL_LENGTH_IN_35MM_FORMAT: Self = Self("FocalLengthIn35mmFormat");
60 pub const FLASH_ENERGY: Self = Self("FlashEnergy");
61 pub const SPATIAL_FREQUENCY_RESPONSE: Self = Self("SpatialFrequencyResponse");
62 pub const FOCAL_PLANE_X_RESOLUTION: Self = Self("FocalPlaneXResolution");
63 pub const FOCAL_PLANE_Y_RESOLUTION: Self = Self("FocalPlaneYResolution");
64 pub const FOCAL_PLANE_RESOLUTION_UNIT: Self = Self("FocalPlaneResolutionUnit");
65 pub const SUBJECT_LOCATION: Self = Self("SubjectLocation");
66 pub const EXPOSURE_INDEX: Self = Self("ExposureIndex");
67 pub const SENSING_METHOD: Self = Self("SensingMethod");
68 pub const FILE_SOURCE: Self = Self("FileSource");
69 pub const SCENE_TYPE: Self = Self("SceneType");
70 pub const CFA_PATTERN: Self = Self("CFAPattern");
71 pub const CUSTOM_RENDERED: Self = Self("CustomRendered");
72 pub const EXPOSURE_MODE: Self = Self("ExposureMode");
73 pub const WHITE_BALANCE: Self = Self("WhiteBalance");
74 pub const DIGITAL_ZOOM_RATIO: Self = Self("DigitalZoomRatio");
75 pub const FOCAL_LENGTH_35EFL: Self = Self("FocalLength35efl");
76 pub const SCENE_CAPTURE_TYPE: Self = Self("SceneCaptureType");
77 pub const GAIN_CONTROL: Self = Self("GainControl");
78 pub const CONTRAST: Self = Self("Contrast");
79 pub const SATURATION: Self = Self("Saturation");
80 pub const SHARPNESS: Self = Self("Sharpness");
81 pub const DEVICE_SETTING_DESCRIPTION: Self = Self("DeviceSettingDescription");
82 pub const SUBJECT_DISTANCE_RANGE: Self = Self("SubjectDistanceRange");
83
84 pub const GPS_LATITUDE_REF: Self = Self("GPSLatitudeRef");
86 pub const GPS_LATITUDE: Self = Self("GPSLatitude");
87 pub const GPS_LONGITUDE_REF: Self = Self("GPSLongitudeRef");
88 pub const GPS_LONGITUDE: Self = Self("GPSLongitude");
89 pub const GPS_ALTITUDE_REF: Self = Self("GPSAltitudeRef");
90 pub const GPS_ALTITUDE: Self = Self("GPSAltitude");
91 pub const GPS_TIMESTAMP: Self = Self("GPSTimeStamp");
92 pub const GPS_SATELLITES: Self = Self("GPSSatellites");
93 pub const GPS_STATUS: Self = Self("GPSStatus");
94 pub const GPS_MEASURE_MODE: Self = Self("GPSMeasureMode");
95 pub const GPS_DOP: Self = Self("GPSDOP");
96 pub const GPS_SPEED_REF: Self = Self("GPSSpeedRef");
97 pub const GPS_SPEED: Self = Self("GPSSpeed");
98 pub const GPS_TRACK_REF: Self = Self("GPSTrackRef");
99 pub const GPS_TRACK: Self = Self("GPSTrack");
100 pub const GPS_IMG_DIRECTION_REF: Self = Self("GPSImgDirectionRef");
101 pub const GPS_IMG_DIRECTION: Self = Self("GPSImgDirection");
102 pub const GPS_MAP_DATUM: Self = Self("GPSMapDatum");
103 pub const GPS_DEST_LATITUDE_REF: Self = Self("GPSDestLatitudeRef");
104 pub const GPS_DEST_LATITUDE: Self = Self("GPSDestLatitude");
105 pub const GPS_DEST_LONGITUDE_REF: Self = Self("GPSDestLongitudeRef");
106 pub const GPS_DEST_LONGITUDE: Self = Self("GPSDestLongitude");
107 pub const GPS_DEST_BEARING_REF: Self = Self("GPSDestBearingRef");
108 pub const GPS_DEST_BEARING: Self = Self("GPSDestBearing");
109 pub const GPS_DEST_DISTANCE_REF: Self = Self("GPSDestDistanceRef");
110 pub const GPS_DEST_DISTANCE: Self = Self("GPSDestDistance");
111 pub const GPS_PROCESSING_METHOD: Self = Self("GPSProcessingMethod");
112 pub const GPS_AREA_INFORMATION: Self = Self("GPSAreaInformation");
113 pub const GPS_DATE_STAMP: Self = Self("GPSDateStamp");
114 pub const GPS_DIFFERENTIAL: Self = Self("GPSDifferential");
115 pub const GPS_H_POSITIONING_ERROR: Self = Self("GPSHPositioningError");
116
117 pub const FILE_NAME: Self = Self("FileName");
119 pub const DIRECTORY: Self = Self("Directory");
120 pub const FILE_SIZE: Self = Self("FileSize");
121 pub const FILE_MODIFY_DATE: Self = Self("FileModifyDate");
122 pub const FILE_ACCESS_DATE: Self = Self("FileAccessDate");
123 pub const FILE_INODE_CHANGE_DATE: Self = Self("FileInodeChangeDate");
124 pub const FILE_PERMISSIONS: Self = Self("FilePermissions");
125 pub const FILE_TYPE: Self = Self("FileType");
126 pub const FILE_TYPE_EXTENSION: Self = Self("FileTypeExtension");
127 pub const MIME_TYPE: Self = Self("MIMEType");
128 pub const EXIF_BYTE_ORDER: Self = Self("ExifByteOrder");
129 pub const CURRENT_ICC_PROFILE: Self = Self("CurrentICCProfile");
130 pub const PROFILE_DATE_TIME: Self = Self("ProfileDateTime");
131 pub const PROFILE_FILE_SIGNATURE: Self = Self("ProfileFileSignature");
132 pub const PRIMARY_PLATFORM: Self = Self("PrimaryPlatform");
133 pub const CMM_TYPE: Self = Self("CMMType");
134 pub const PROFILE_VERSION: Self = Self("ProfileVersion");
135 pub const PROFILE_CLASS: Self = Self("ProfileClass");
136 pub const COLOR_SPACE_DATA: Self = Self("ColorSpaceData");
137 pub const PROFILE_CONNECTION_SPACE: Self = Self("ProfileConnectionSpace");
138 pub const PROFILE_CONNECTION_SPACE_ILLUMINANT: Self = Self("ProfileConnectionSpaceIlluminant");
139 pub const ICC_PROFILE_CREATOR: Self = Self("ICCProfileCreator");
140 pub const ICC_PROFILE_DESCRIPTION: Self = Self("ICCProfileDescription");
141 pub const ICC_VIEWING_CONDITIONS_DESCRIPTION: Self = Self("ICCViewingConditionsDescription");
142 pub const ICC_DEVICE_MODEL: Self = Self("ICCDeviceModel");
143 pub const ICC_DEVICE_MANUFACTURER: Self = Self("ICCDeviceManufacturer");
144
145 pub const IPTC_OBJECT_NAME: Self = Self("ObjectName");
147 pub const IPTC_EDIT_STATUS: Self = Self("EditStatus");
148 pub const IPTC_EDITORIAL_UPDATE: Self = Self("EditorialUpdate");
149 pub const IPTC_URGENCY: Self = Self("Urgency");
150 pub const IPTC_SUBJECT_REFERENCE: Self = Self("SubjectReference");
151 pub const IPTC_CATEGORY: Self = Self("Category");
152 pub const IPTC_SUPPLEMENTAL_CATEGORY: Self = Self("SupplementalCategory");
153 pub const IPTC_FIXTURE_IDENTIFIER: Self = Self("FixtureIdentifier");
154 pub const IPTC_KEYWORDS: Self = Self("Keywords");
155 pub const IPTC_CONTENT_LOCATION_CODE: Self = Self("ContentLocationCode");
156 pub const IPTC_CONTENT_LOCATION_NAME: Self = Self("ContentLocationName");
157 pub const IPTC_RELEASE_DATE: Self = Self("ReleaseDate");
158 pub const IPTC_RELEASE_TIME: Self = Self("ReleaseTime");
159 pub const IPTC_EXPIRATION_DATE: Self = Self("ExpirationDate");
160 pub const IPTC_EXPIRATION_TIME: Self = Self("ExpirationTime");
161 pub const IPTC_SPECIAL_INSTRUCTIONS: Self = Self("SpecialInstructions");
162 pub const IPTC_ACTION_ADVISED: Self = Self("ActionAdvised");
163 pub const IPTC_REFERENCE_SERVICE: Self = Self("ReferenceService");
164 pub const IPTC_REFERENCE_DATE: Self = Self("ReferenceDate");
165 pub const IPTC_REFERENCE_NUMBER: Self = Self("ReferenceNumber");
166 pub const IPTC_DATE_CREATED: Self = Self("DateCreated");
167 pub const IPTC_TIME_CREATED: Self = Self("TimeCreated");
168 pub const IPTC_DIGITAL_CREATION_DATE: Self = Self("DigitalCreationDate");
169 pub const IPTC_DIGITAL_CREATION_TIME: Self = Self("DigitalCreationTime");
170 pub const IPTC_ORIGINATING_PROGRAM: Self = Self("OriginatingProgram");
171 pub const IPTC_PROGRAM_VERSION: Self = Self("ProgramVersion");
172 pub const IPTC_OBJECT_CYCLE: Self = Self("ObjectCycle");
173 pub const IPTC_BY_LINE: Self = Self("By-line");
174 pub const IPTC_BY_LINE_TITLE: Self = Self("By-lineTitle");
175 pub const IPTC_CITY: Self = Self("City");
176 pub const IPTC_SUB_LOCATION: Self = Self("Sub-location");
177 pub const IPTC_PROVINCE_STATE: Self = Self("Province-State");
178 pub const IPTC_COUNTRY_PRIMARY_LOCATION_CODE: Self = Self("Country-PrimaryLocationCode");
179 pub const IPTC_COUNTRY_PRIMARY_LOCATION_NAME: Self = Self("Country-PrimaryLocationName");
180 pub const IPTC_ORIGINAL_TRANSMISSION_REFERENCE: Self = Self("OriginalTransmissionReference");
181 pub const IPTC_HEADLINE: Self = Self("Headline");
182 pub const IPTC_CREDIT: Self = Self("Credit");
183 pub const IPTC_SOURCE: Self = Self("Source");
184 pub const IPTC_COPYRIGHT_NOTICE: Self = Self("CopyrightNotice");
185 pub const IPTC_CONTACT: Self = Self("Contact");
186 pub const IPTC_CAPTION_ABSTRACT: Self = Self("Caption-Abstract");
187 pub const IPTC_WRITER_EDITOR: Self = Self("Writer-Editor");
188 pub const IPTC_IMAGE_TYPE: Self = Self("ImageType");
189 pub const IPTC_IMAGE_ORIENTATION: Self = Self("ImageOrientation");
190 pub const IPTC_LANGUAGE_IDENTIFIER: Self = Self("LanguageIdentifier");
191
192 pub const XMP_DC_TITLE: Self = Self("Title");
194 pub const XMP_DC_CREATOR: Self = Self("Creator");
195 pub const XMP_DC_SUBJECT: Self = Self("Subject");
196 pub const XMP_DC_DESCRIPTION: Self = Self("Description");
197 pub const XMP_DC_PUBLISHER: Self = Self("Publisher");
198 pub const XMP_DC_CONTRIBUTOR: Self = Self("Contributor");
199 pub const XMP_DC_DATE: Self = Self("Date");
200 pub const XMP_DC_TYPE: Self = Self("Type");
201 pub const XMP_DC_FORMAT: Self = Self("Format");
202 pub const XMP_DC_IDENTIFIER: Self = Self("Identifier");
203 pub const XMP_DC_SOURCE: Self = Self("Source");
204 pub const XMP_DC_LANGUAGE: Self = Self("Language");
205 pub const XMP_DC_RELATION: Self = Self("Relation");
206 pub const XMP_DC_COVERAGE: Self = Self("Coverage");
207 pub const XMP_DC_RIGHTS: Self = Self("Rights");
208
209 pub const XMP_XMP_RIGHTS_MANAGED: Self = Self("RightsManaged");
211 pub const XMP_XMP_RIGHTS_MARKED: Self = Self("RightsMarked");
212 pub const XMP_XMP_RIGHTS_WEB_STATEMENT: Self = Self("WebStatement");
213 pub const XMP_XMP_RIGHTS_USAGE_TERMS: Self = Self("UsageTerms");
214
215 pub const IMAGE_SIZE: Self = Self("ImageSize");
217 pub const MEGAPIXELS: Self = Self("Megapixels");
218 pub const QUALITY: Self = Self("Quality");
219 pub const BITS_PER_SAMPLE: Self = Self("BitsPerSample");
220 pub const COLOR_COMPONENTS: Self = Self("ColorComponents");
221 pub const Y_CB_CR_SUB_SAMPLING: Self = Self("YCbCrSubSampling");
222 pub const Y_CB_CR_POSITIONING: Self = Self("YCbCrPositioning");
223
224 pub const THUMBNAIL_IMAGE: Self = Self("ThumbnailImage");
226 pub const THUMBNAIL_LENGTH: Self = Self("ThumbnailLength");
227 pub const THUMBNAIL_OFFSET: Self = Self("ThumbnailOffset");
228 pub const PREVIEW_IMAGE: Self = Self("PreviewImage");
229 pub const PREVIEW_IMAGE_TYPE: Self = Self("PreviewImageType");
230 pub const JPG_FROM_RAW: Self = Self("JpgFromRaw");
231 pub const OTHER_IMAGE: Self = Self("OtherImage");
232
233 pub const COLOR_SPACE: Self = Self("ColorSpace");
235 pub const GAMMA: Self = Self("Gamma");
236}
237
238impl fmt::Display for TagId {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 write!(f, "{}", self.0)
241 }
242}
243
244impl From<&'static str> for TagId {
245 fn from(name: &'static str) -> Self {
246 Self(name)
247 }
248}
249
250impl AsRef<str> for TagId {
251 fn as_ref(&self) -> &str {
252 self.0
253 }
254}
255
256#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
258#[serde(untagged)]
259pub enum TagValue {
260 String(String),
262
263 Integer(i64),
265
266 Float(f64),
268
269 Boolean(bool),
271
272 Array(Vec<TagValue>),
274
275 Binary(String),
277
278 Null,
280}
281
282impl TagValue {
283 pub fn as_string(&self) -> Option<&String> {
285 match self {
286 Self::String(s) => Some(s),
287 _ => None,
288 }
289 }
290
291 pub fn as_integer(&self) -> Option<i64> {
293 match self {
294 Self::Integer(i) => Some(*i),
295 Self::Float(f) => Some(*f as i64),
296 Self::String(s) => s.parse().ok(),
297 _ => None,
298 }
299 }
300
301 pub fn as_float(&self) -> Option<f64> {
303 match self {
304 Self::Float(f) => Some(*f),
305 Self::Integer(i) => Some(*i as f64),
306 Self::String(s) => s.parse().ok(),
307 _ => None,
308 }
309 }
310
311 pub fn as_bool(&self) -> Option<bool> {
313 match self {
314 Self::Boolean(b) => Some(*b),
315 Self::Integer(0) => Some(false),
316 Self::Integer(_) => Some(true),
317 Self::String(s) => match s.to_lowercase().as_str() {
318 "true" | "yes" | "1" | "on" => Some(true),
319 "false" | "no" | "0" | "off" => Some(false),
320 _ => None,
321 },
322 _ => None,
323 }
324 }
325
326 pub fn as_array(&self) -> Option<&Vec<TagValue>> {
328 match self {
329 Self::Array(arr) => Some(arr),
330 _ => None,
331 }
332 }
333
334 pub fn to_string_lossy(&self) -> String {
336 match self {
337 Self::String(s) => s.clone(),
338 Self::Integer(i) => i.to_string(),
339 Self::Float(f) => f.to_string(),
340 Self::Boolean(b) => b.to_string(),
341 Self::Array(arr) => {
342 let items: Vec<String> = arr.iter().map(|v| v.to_string_lossy()).collect();
343 format!("[{}]", items.join(", "))
344 }
345 Self::Binary(b) => format!("[binary: {} bytes]", b.len()),
346 Self::Null => "null".to_string(),
347 }
348 }
349
350 pub fn is_null(&self) -> bool {
352 matches!(self, Self::Null)
353 }
354}
355
356impl fmt::Display for TagValue {
357 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358 write!(f, "{}", self.to_string_lossy())
359 }
360}
361
362#[derive(Debug, Clone, Default, Serialize, Deserialize)]
364pub struct Metadata {
365 #[serde(flatten)]
367 tags: HashMap<String, TagValue>,
368
369 #[serde(skip)]
371 groups: HashMap<String, Metadata>,
372}
373
374impl Metadata {
375 pub fn new() -> Self {
377 Self::default()
378 }
379
380 pub fn get(&self, tag: &str) -> Option<&TagValue> {
382 self.tags.get(tag)
383 }
384
385 pub fn get_tag(&self, tag: TagId) -> Option<&TagValue> {
387 self.get(tag.name())
388 }
389
390 pub fn set(&mut self, tag: impl Into<String>, value: impl Into<TagValue>) {
392 self.tags.insert(tag.into(), value.into());
393 }
394
395 pub fn set_tag(&mut self, tag: TagId, value: impl Into<TagValue>) {
397 self.set(tag.name(), value);
398 }
399
400 pub fn tags(&self) -> &HashMap<String, TagValue> {
402 &self.tags
403 }
404
405 pub fn tags_mut(&mut self) -> &mut HashMap<String, TagValue> {
407 &mut self.tags
408 }
409
410 pub fn group(&self, name: &str) -> Option<&Metadata> {
412 self.groups.get(name)
413 }
414
415 pub fn set_group(&mut self, name: impl Into<String>, metadata: Metadata) {
417 self.groups.insert(name.into(), metadata);
418 }
419
420 pub fn groups(&self) -> &HashMap<String, Metadata> {
422 &self.groups
423 }
424
425 pub fn contains(&self, tag: &str) -> bool {
427 self.tags.contains_key(tag)
428 }
429
430 pub fn contains_tag(&self, tag: TagId) -> bool {
432 self.contains(tag.name())
433 }
434
435 pub fn len(&self) -> usize {
437 self.tags.len()
438 }
439
440 pub fn is_empty(&self) -> bool {
442 self.tags.is_empty()
443 }
444
445 pub fn merge(&mut self, other: Metadata) {
447 self.tags.extend(other.tags);
448 self.groups.extend(other.groups);
449 }
450
451 pub fn iter(&self) -> impl Iterator<Item = (&String, &TagValue)> {
453 self.tags.iter()
454 }
455}
456
457impl IntoIterator for Metadata {
458 type Item = (String, TagValue);
459 type IntoIter = std::collections::hash_map::IntoIter<String, TagValue>;
460
461 fn into_iter(self) -> Self::IntoIter {
462 self.tags.into_iter()
463 }
464}
465
466impl<'a> IntoIterator for &'a Metadata {
467 type Item = (&'a String, &'a TagValue);
468 type IntoIter = std::collections::hash_map::Iter<'a, String, TagValue>;
469
470 fn into_iter(self) -> Self::IntoIter {
471 self.tags.iter()
472 }
473}
474
475impl From<String> for TagValue {
477 fn from(s: String) -> Self {
478 Self::String(s)
479 }
480}
481
482impl From<&str> for TagValue {
483 fn from(s: &str) -> Self {
484 Self::String(s.to_string())
485 }
486}
487
488impl From<i64> for TagValue {
489 fn from(i: i64) -> Self {
490 Self::Integer(i)
491 }
492}
493
494impl From<i32> for TagValue {
495 fn from(i: i32) -> Self {
496 Self::Integer(i as i64)
497 }
498}
499
500impl From<f64> for TagValue {
501 fn from(f: f64) -> Self {
502 Self::Float(f)
503 }
504}
505
506impl From<f32> for TagValue {
507 fn from(f: f32) -> Self {
508 Self::Float(f as f64)
509 }
510}
511
512impl From<bool> for TagValue {
513 fn from(b: bool) -> Self {
514 Self::Boolean(b)
515 }
516}
517
518impl From<Vec<TagValue>> for TagValue {
519 fn from(arr: Vec<TagValue>) -> Self {
520 Self::Array(arr)
521 }
522}
523
524impl<T: Into<TagValue>> From<Option<T>> for TagValue {
525 fn from(opt: Option<T>) -> Self {
526 match opt {
527 Some(v) => v.into(),
528 None => Self::Null,
529 }
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
538 fn test_tag_id() {
539 assert_eq!(TagId::MAKE.name(), "Make");
540 assert_eq!(TagId::MODEL.name(), "Model");
541 }
542
543 #[test]
544 fn test_tag_value_conversions() {
545 let str_val: TagValue = "test".into();
546 assert_eq!(str_val.as_string(), Some(&"test".to_string()));
547
548 let int_val: TagValue = 42i64.into();
549 assert_eq!(int_val.as_integer(), Some(42));
550
551 let float_val: TagValue = std::f64::consts::PI.into();
552 assert_eq!(float_val.as_float(), Some(std::f64::consts::PI));
553
554 let bool_val: TagValue = true.into();
555 assert_eq!(bool_val.as_bool(), Some(true));
556 }
557
558 #[test]
559 fn test_metadata() {
560 let mut meta = Metadata::new();
561 meta.set("Make", "Canon");
562 meta.set("Model", "EOS 5D");
563
564 assert_eq!(meta.len(), 2);
565 assert!(meta.contains("Make"));
566 assert_eq!(
567 meta.get("Make"),
568 Some(&TagValue::String("Canon".to_string()))
569 );
570 }
571
572 #[test]
573 fn test_metadata_iteration() {
574 let mut meta = Metadata::new();
575 meta.set("A", 1);
576 meta.set("B", 2);
577
578 let mut count = 0;
579 for (key, _value) in &meta {
580 count += 1;
581 assert!(key == "A" || key == "B");
582 }
583 assert_eq!(count, 2);
584 }
585}