use std::collections::HashMap;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::{json, Value};
use crate::{
assertion::{Assertion, AssertionBase, AssertionJson},
assertions::labels,
Error, Result,
};
#[doc(hidden)]
#[derive(Serialize, Deserialize, Debug)]
pub struct Exif {
#[serde(rename = "@context", skip_serializing_if = "Option::is_none")]
object_context: Option<Value>,
#[serde(flatten)]
value: HashMap<String, Value>,
}
impl Exif {
pub const LABEL: &'static str = labels::EXIF;
pub fn new() -> Self {
Self {
object_context: Some(json!({
"dc": "http://purl.org/dc/elements/1.1/",
"exifEX": "http://cipa.jp/exif/1.0/",
"exif": "http://ns.adobe.com/exif/1.0/",
"tiff": "http://ns.adobe.com/tiff/1.0/",
"xmp": "http://ns.adobe.com/xap/1.0/",
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
})),
value: HashMap::new(),
}
}
pub fn set_context(mut self, context: Value) -> Self {
self.object_context = Some(context);
self
}
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
self.value
.get(key)
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn insert<S: Into<String>, T: Serialize>(mut self, key: S, value: T) -> Result<Self> {
self.value.insert(key.into(), serde_json::to_value(value)?);
Ok(self)
}
pub fn insert_push<S: Into<String>, T: Serialize + DeserializeOwned>(
self,
key: S,
value: T,
) -> Result<Self> {
let key = key.into();
Ok(match self.get(&key) as Option<Vec<T>> {
Some(mut v) => {
v.push(value);
self
}
None => self.insert(&key, Vec::from([value]))?,
})
}
pub fn from_json_str(json: &str) -> Result<Self> {
serde_json::from_slice(json.as_bytes()).map_err(Error::JsonError)
}
}
impl Default for Exif {
fn default() -> Self {
Self::new()
}
}
impl AssertionJson for Exif {}
impl AssertionBase for Exif {
const LABEL: &'static str = labels::EXIF;
fn to_assertion(&self) -> Result<Assertion> {
Self::to_json_assertion(self)
}
fn from_assertion(assertion: &Assertion) -> Result<Self> {
Self::from_json_assertion(assertion)
}
}
#[cfg(test)]
pub mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use super::*;
use crate::Builder;
const SPEC_EXAMPLE: &str = r#"{
"@context" : {
"dc": "http://purl.org/dc/elements/1.1/",
"exifEX": "http://cipa.jp/exif/1.0/",
"exif": "http://ns.adobe.com/exif/1.0/",
"tiff": "http://ns.adobe.com/tiff/1.0/",
"xmp": "http://ns.adobe.com/xap/1.0/",
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
},
"exif:GPSVersionID": "2.2.0.0",
"exif:GPSLatitude": "39,21.102N",
"exif:GPSLongitude": "74,26.5737W",
"exif:GPSAltitudeRef": 0,
"exif:GPSAltitude": "100963/29890",
"exif:GPSTimeStamp": "2019-09-22T18:22:57Z",
"exif:GPSSpeedRef": "K",
"exif:GPSSpeed": "4009/161323",
"exif:GPSImgDirectionRef": "T",
"exif:GPSImgDirection": "296140/911",
"exif:GPSDestBearingRef": "T",
"exif:GPSDestBearing": "296140/911",
"exif:GPSHPositioningError": "13244/2207",
"exif:ExposureTime": "1/100",
"exif:FNumber": 4.0,
"exif:ColorSpace": 1,
"exif:DigitalZoomRatio": 2.0,
"tiff:Make": "CameraCompany",
"tiff:Model": "Shooter S1",
"exifEX:LensMake": "CameraCompany",
"exifEX:LensModel": "17.0-35.0 mm",
"exifEX:LensSpecification": { "@list": [ 1.55, 4.2, 1.6, 2.4 ] }
}"#;
#[test]
fn exif_new() {
let mut builder = Builder::default();
let original = Exif::new()
.insert("exif:GPSLatitude", "39,21.102N")
.unwrap();
builder
.add_assertion(Exif::LABEL, &original)
.expect("adding assertion");
let exif: Exif = builder.find_assertion(Exif::LABEL).expect("find_assertion");
let latitude: String = exif.get("exif:GPSLatitude").unwrap();
assert_eq!(&latitude, "39,21.102N")
}
#[test]
fn exif_from_json() {
let mut builder = Builder::default();
let original = Exif::from_json_str(SPEC_EXAMPLE).expect("from_json");
builder
.add_assertion(Exif::LABEL, &original)
.expect("adding assertion");
let exif: Exif = builder.find_assertion(Exif::LABEL).expect("find_assertion");
let latitude: String = exif.get("exif:GPSLatitude").unwrap();
assert_eq!(&latitude, "39,21.102N")
}
#[test]
fn exif_to_assertion() {
let original = Exif::from_json_str(SPEC_EXAMPLE).expect("from_json");
let assertion = original.to_assertion().expect("to_assertion");
assert_eq!(assertion.content_type(), "application/json");
let result = Exif::from_assertion(&assertion).expect("from_assertion");
let latitude: String = result.get("exif:GPSLatitude").unwrap();
assert_eq!(&latitude, "39,21.102N")
}
}