imessage_database/message_types/
placemark.rs1use plist::Value;
6
7use crate::{
8 error::plist::PlistParseError,
9 message_types::variants::{BalloonProvider, HasUrl},
10 util::plist::{
11 get_string_from_dict, get_string_from_nested_dict, rich_link_metadata_and_nested,
12 },
13};
14
15#[derive(Debug, PartialEq, Eq, Default)]
17pub struct Placemark<'a> {
18 pub name: Option<&'a str>,
20 pub address: Option<&'a str>,
22 pub state: Option<&'a str>,
24 pub city: Option<&'a str>,
26 pub iso_country_code: Option<&'a str>,
28 pub postal_code: Option<&'a str>,
30 pub country: Option<&'a str>,
32 pub street: Option<&'a str>,
34 pub sub_administrative_area: Option<&'a str>,
36 pub sub_locality: Option<&'a str>,
38}
39
40impl<'a> Placemark<'a> {
41 fn new(payload: &'a Value) -> Result<Self, PlistParseError> {
43 let address_components = payload
44 .as_dictionary()
45 .ok_or_else(|| {
46 PlistParseError::InvalidType(
47 "specialization2".to_string(),
48 "dictionary".to_string(),
49 )
50 })?
51 .get("addressComponents")
52 .ok_or_else(|| PlistParseError::MissingKey("addressComponents".to_string()))?;
53 Ok(Self {
54 name: get_string_from_dict(payload, "name"),
55 address: get_string_from_dict(payload, "address"),
56 state: get_string_from_dict(address_components, "_state"),
57 city: get_string_from_dict(address_components, "_city"),
58 iso_country_code: get_string_from_dict(address_components, "_ISOCountryCode"),
59 postal_code: get_string_from_dict(address_components, "_postalCode"),
60 country: get_string_from_dict(address_components, "_country"),
61 street: get_string_from_dict(address_components, "_street"),
62 sub_administrative_area: get_string_from_dict(
63 address_components,
64 "_subAdministrativeArea",
65 ),
66 sub_locality: get_string_from_dict(address_components, "_subLocality"),
67 })
68 }
69}
70
71#[derive(Debug, PartialEq, Eq)]
74pub struct PlacemarkMessage<'a> {
75 pub url: Option<&'a str>,
77 pub original_url: Option<&'a str>,
79 pub place_name: Option<&'a str>,
81 pub placemark: Placemark<'a>,
83}
84
85impl<'a> BalloonProvider<'a> for PlacemarkMessage<'a> {
86 fn from_map(payload: &'a Value) -> Result<Self, PlistParseError> {
87 if let Ok((body, placemark)) = rich_link_metadata_and_nested(payload, "specialization2") {
88 if get_string_from_dict(placemark, "address").is_none() {
90 return Err(PlistParseError::WrongMessageType);
91 }
92
93 return Ok(Self {
94 url: get_string_from_nested_dict(body, "URL"),
95 original_url: get_string_from_nested_dict(body, "originalURL"),
96 place_name: get_string_from_dict(body, "title"),
97 placemark: Placemark::new(placemark).unwrap_or_default(),
98 });
99 }
100 Err(PlistParseError::NoPayload)
101 }
102}
103
104impl<'a> PlacemarkMessage<'a> {
105 #[must_use]
107 pub fn get_url(&self) -> Option<&str> {
108 <Self as HasUrl>::get_url(self)
109 }
110}
111
112impl HasUrl for PlacemarkMessage<'_> {
113 fn url(&self) -> Option<&str> {
114 self.url
115 }
116
117 fn original_url(&self) -> Option<&str> {
118 self.original_url
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use crate::{
125 message_types::{
126 placemark::{Placemark, PlacemarkMessage},
127 variants::BalloonProvider,
128 },
129 util::plist::{parse_ns_keyed_archiver, rich_link_metadata_and_nested},
130 };
131 use plist::Value;
132 use std::env::current_dir;
133 use std::fs::File;
134
135 #[test]
136 fn test_parse_app_store_link() {
137 let plist_path = current_dir()
138 .unwrap()
139 .as_path()
140 .join("test_data/shared_placemark/SharedPlacemark.plist");
141 let plist_data = File::open(plist_path).unwrap();
142 let plist = Value::from_reader(plist_data).unwrap();
143 let parsed = parse_ns_keyed_archiver(&plist).unwrap();
144
145 let balloon = PlacemarkMessage::from_map(&parsed).unwrap();
146 let expected = PlacemarkMessage {
147 url: Some(
148 "https://maps.apple.com/?address=Cherry%20Cove,%20Avalon,%20CA%20%2090704,%20United%20States&ll=33.450858,-118.508212&q=Cherry%20Cove&t=m",
149 ),
150 original_url: Some(
151 "https://maps.apple.com/?address=Cherry%20Cove,%20Avalon,%20CA%20%2090704,%20United%20States&ll=33.450858,-118.508212&q=Cherry%20Cove&t=m",
152 ),
153 place_name: Some("Cherry Cove Avalon CA 90704 United States"),
154 placemark: Placemark {
155 name: Some("Cherry Cove"),
156 address: Some("Cherry Cove, Avalon"),
157 state: Some("CA"),
158 city: Some("Avalon"),
159 iso_country_code: Some("US"),
160 postal_code: Some("90704"),
161 country: Some("United States"),
162 street: Some("Cherry Cove"),
163 sub_administrative_area: Some("Los Angeles County"),
164 sub_locality: Some("Santa Catalina Island"),
165 },
166 };
167
168 assert_eq!(balloon, expected);
169 }
170
171 #[test]
172 fn can_parse_placemark() {
173 let plist_path = current_dir()
174 .unwrap()
175 .as_path()
176 .join("test_data/shared_placemark/SharedPlacemark.plist");
177 let plist_data = File::open(plist_path).unwrap();
178 let plist = Value::from_reader(plist_data).unwrap();
179 let parsed = parse_ns_keyed_archiver(&plist).unwrap();
180
181 let (_, placemark_data) =
182 rich_link_metadata_and_nested(&parsed, "specialization2").unwrap();
183
184 let placemark = Placemark::new(placemark_data).unwrap();
185 let expected = Placemark {
186 name: Some("Cherry Cove"),
187 address: Some("Cherry Cove, Avalon"),
188 state: Some("CA"),
189 city: Some("Avalon"),
190 iso_country_code: Some("US"),
191 postal_code: Some("90704"),
192 country: Some("United States"),
193 street: Some("Cherry Cove"),
194 sub_administrative_area: Some("Los Angeles County"),
195 sub_locality: Some("Santa Catalina Island"),
196 };
197
198 assert_eq!(placemark, expected);
199 }
200}