1use std::{collections::HashMap, str};
4
5use paste::paste;
6use serde_json::Value;
7
8use super::{error::VOTableError, HasContent, HasContentElem, VOTableElement};
9
10#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
12pub struct Info {
13 #[serde(rename = "ID", skip_serializing_if = "Option::is_none")]
15 pub id: Option<String>,
16 pub name: String,
17 pub value: String,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub xtype: Option<String>,
20 #[serde(rename = "ref", skip_serializing_if = "Option::is_none")]
21 pub ref_: Option<String>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub unit: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub ucd: Option<String>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub utype: Option<String>,
28 #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
30 pub extra: HashMap<String, Value>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub content: Option<String>,
34}
35
36impl Info {
37 pub fn new<N: Into<String>, V: Into<String>>(name: N, value: V) -> Self {
38 Info {
39 id: None,
40 name: name.into(),
41 value: value.into(),
42 xtype: None,
43 ref_: None,
44 unit: None,
45 ucd: None,
46 utype: None,
47 extra: Default::default(),
48 content: None,
49 }
50 }
51
52 impl_builder_opt_string_attr!(id);
54 impl_builder_mandatory_string_attr!(name);
55 impl_builder_mandatory_string_attr!(value);
56 impl_builder_opt_string_attr!(xtype);
57 impl_builder_opt_string_attr!(ref_, ref);
58 impl_builder_opt_string_attr!(unit);
59 impl_builder_opt_string_attr!(ucd);
60 impl_builder_opt_string_attr!(utype);
61 impl_builder_insert_extra!();
63}
64
65impl_has_content!(Info);
66
67impl VOTableElement for Info {
68 const TAG: &'static str = "INFO";
69
70 type MarkerType = HasContentElem;
71
72 fn from_attrs<K, V, I>(attrs: I) -> Result<Self, VOTableError>
73 where
74 K: AsRef<str> + Into<String>,
75 V: AsRef<str> + Into<String>,
76 I: Iterator<Item = (K, V)>,
77 {
78 const DEFAULT_VALUE: &str = "@TBD";
79 Self::new(DEFAULT_VALUE, DEFAULT_VALUE)
80 .set_attrs(attrs)
81 .and_then(|info| {
82 if info.name.as_str() == DEFAULT_VALUE || info.value.as_str() == DEFAULT_VALUE {
83 Err(VOTableError::Custom(format!(
84 "Attributes 'name' and 'value' are mandatory in tag '{}'",
85 Self::TAG
86 )))
87 } else {
88 Ok(info)
89 }
90 })
91 }
92
93 fn set_attrs_by_ref<K, V, I>(&mut self, attrs: I) -> Result<(), VOTableError>
94 where
95 K: AsRef<str> + Into<String>,
96 V: AsRef<str> + Into<String>,
97 I: Iterator<Item = (K, V)>,
98 {
99 for (key, val) in attrs {
100 let key = key.as_ref();
101 match key {
102 "ID" => self.set_id_by_ref(val),
103 "name" => self.set_name_by_ref(val),
104 "value" => self.set_value_by_ref(val),
105 "xtype" => self.set_xtype_by_ref(val),
106 "ref" => self.set_ref_by_ref(val),
107 "unit" => self.set_unit_by_ref(val),
108 "ucd" => self.set_ucd_by_ref(val),
109 "utype" => self.set_utype_by_ref(val),
110 _ => self.insert_extra_str_by_ref(key, val),
111 }
112 }
113 Ok(())
114 }
115
116 fn for_each_attribute<F>(&self, mut f: F)
117 where
118 F: FnMut(&str, &str),
119 {
120 if let Some(id) = &self.id {
121 f("ID", id.as_str());
122 }
123 f("name", self.name.as_str());
124 f("value", self.value.as_str());
125 if let Some(xtype) = &self.xtype {
126 f("xtype", xtype.to_string().as_str());
127 }
128 if let Some(r) = &self.ref_ {
129 f("ref", r.as_str());
130 }
131 if let Some(unit) = &self.unit {
132 f("unit", unit.as_str());
133 }
134 if let Some(ucd) = &self.ucd {
135 f("ucd", ucd.as_str());
136 }
137 if let Some(utype) = &self.utype {
138 f("utype", utype.as_str());
139 }
140 for_each_extra_attribute!(self, f);
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use std::io::Cursor;
147
148 use quick_xml::{events::Event, Reader, Writer};
149
150 use crate::{info::Info, QuickXmlReadWrite, VOTableElement};
151
152 fn test_info_read(xml: &str) -> Info {
153 let mut reader = Reader::from_reader(Cursor::new(xml.as_bytes()));
154 let mut buff: Vec<u8> = Vec::with_capacity(xml.len());
155 loop {
156 let mut event = reader.read_event(&mut buff).unwrap();
157 match &mut event {
158 Event::Start(e) if e.local_name() == Info::TAG_BYTES => {
159 let info = Info::from_event_start(&e)
160 .and_then(|info| info.read_content(&mut reader, &mut buff, &()))
161 .unwrap();
162 return info;
163 }
164 Event::Empty(e) if e.local_name() == Info::TAG_BYTES => {
165 let info = Info::from_event_empty(&e).unwrap();
166 return info;
167 }
168 Event::Text(e) if e.escaped().is_empty() => (), _ => unreachable!(),
170 }
171 }
172 }
173
174 #[test]
175 fn test_info_readwrite_1() {
176 let xml = r#"<INFO ID="VERSION" name="votable-version" value="1.99+ (14-Oct-2013)"/>"#;
177 let mut info = test_info_read(xml);
179 assert_eq!(info.id.as_ref().map(|s| s.as_str()), Some("VERSION"));
180 assert_eq!(info.name.as_str(), "votable-version");
181 assert_eq!(info.value.as_str(), "1.99+ (14-Oct-2013)");
182 let mut writer = Writer::new(Cursor::new(Vec::new()));
184 info.write(&mut writer, &()).unwrap();
185 let output = writer.into_inner().into_inner();
186 let output_str = unsafe { std::str::from_utf8_unchecked(output.as_slice()) };
187 assert_eq!(output_str, xml);
188 }
189
190 #[test]
191 fn test_info_readwrite_2() {
192 let xml = r#"<INFO name="queryParameters" value="25">
193 -oc.form=dec
194 -out.max=50
195 -out.all=2
196 -nav=cat:J/ApJ/701/1219&tab:{J/ApJ/701/1219/table4}&key:source=J/ApJ/701/1219&HTTPPRM:&
197 -c.eq=J2000
198 -c.r= 2
199 -c.u=arcmin
200 -c.geom=r
201 -source=J/ApJ/701/1219/table4
202 -order=I
203 -out=ID
204 -out=RAJ2000
205 -out=DEJ2000
206 -out=Sep
207 -out=Dist
208 -out=Bmag
209 -out=e_Bmag
210 -out=Rmag
211 -out=e_Rmag
212 -out=Imag
213 -out=e_Imag
214 -out=z
215 -out=Type
216 -out=RMag
217 -out.all=2
218 </INFO>"#;
219 let mut info = test_info_read(xml);
221 assert_eq!(info.name.as_str(), "queryParameters");
222 assert_eq!(info.value.as_str(), "25");
223 assert_eq!(
224 info.content.as_ref().map(|s| s.as_str()),
225 Some(
226 r#"
227 -oc.form=dec
228 -out.max=50
229 -out.all=2
230 -nav=cat:J/ApJ/701/1219&tab:{J/ApJ/701/1219/table4}&key:source=J/ApJ/701/1219&HTTPPRM:&
231 -c.eq=J2000
232 -c.r= 2
233 -c.u=arcmin
234 -c.geom=r
235 -source=J/ApJ/701/1219/table4
236 -order=I
237 -out=ID
238 -out=RAJ2000
239 -out=DEJ2000
240 -out=Sep
241 -out=Dist
242 -out=Bmag
243 -out=e_Bmag
244 -out=Rmag
245 -out=e_Rmag
246 -out=Imag
247 -out=e_Imag
248 -out=z
249 -out=Type
250 -out=RMag
251 -out.all=2
252 "#
253 )
254 );
255 let mut writer = Writer::new(Cursor::new(Vec::new()));
257 info.write(&mut writer, &()).unwrap();
258 let output = writer.into_inner().into_inner();
259 let output_str = unsafe { std::str::from_utf8_unchecked(output.as_slice()) };
260 assert_eq!(output_str, xml);
261 }
262}