1use crate::errors::{check_status, Result};
3use crate::fitsfile::FitsFile;
4use crate::longnam::*;
5use crate::types::HasFitsDataType;
6use std::ffi;
7use std::ptr;
8
9mod constants;
10mod header_value;
11
12use constants::{MAX_COMMENT_LENGTH, MAX_VALUE_LENGTH};
13pub use header_value::HeaderValue;
14
15pub trait ReadsKey {
27 #[doc(hidden)]
28 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
29 where
30 Self: Sized;
31}
32
33macro_rules! reads_key_impl {
34 ($t:ty) => {
35 impl ReadsKey for $t {
36 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
37 let hv: HeaderValue<$t> = ReadsKey::read_key(f, name)?;
38 Ok(hv.value)
39 }
40 }
41 impl ReadsKey for HeaderValue<$t>
42 where
43 $t: Default,
44 {
45 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
46 let c_name = ffi::CString::new(name)?;
47 let mut status = 0;
48 let mut value: Self = Default::default();
49 let mut comment: Vec<c_char> = vec![0; MAX_COMMENT_LENGTH];
50
51 unsafe {
52 fits_read_key(
53 f.fptr.as_mut() as *mut _,
54 <$t as HasFitsDataType>::FITS_DATA_TYPE.into(),
55 c_name.as_ptr(),
56 &mut value.value as *mut $t as *mut c_void,
57 comment.as_mut_ptr(),
58 &mut status,
59 );
60 }
61
62 check_status(status).map(|_| {
63 let comment = {
64 let comment: Vec<u8> = comment
65 .iter()
66 .map(|&x| x as u8)
67 .filter(|&x| x != 0)
68 .collect();
69 if comment.is_empty() {
70 None
71 } else {
72 String::from_utf8(comment).ok()
73 }
74 };
75
76 value.comment = comment;
77
78 value
79 })
80 }
81 }
82 };
83}
84
85reads_key_impl!(i32);
86reads_key_impl!(i64);
87reads_key_impl!(f32);
88reads_key_impl!(f64);
89
90impl ReadsKey for bool {
91 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
92 where
93 Self: Sized,
94 {
95 i32::read_key(f, name).map(|v| v > 0)
96 }
97}
98
99impl ReadsKey for HeaderValue<bool> {
100 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self>
101 where
102 Self: Sized,
103 {
104 let hv: HeaderValue<i32> = ReadsKey::read_key(f, name)?;
105 Ok(hv.map(|v| v > 0))
106 }
107}
108
109impl ReadsKey for String {
110 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
111 let hv: HeaderValue<String> = ReadsKey::read_key(f, name)?;
112 Ok(hv.value)
113 }
114}
115
116impl ReadsKey for HeaderValue<String> {
117 fn read_key(f: &mut FitsFile, name: &str) -> Result<Self> {
118 let c_name = ffi::CString::new(name)?;
119 let mut status = 0;
120 let mut value: Vec<c_char> = vec![0; MAX_VALUE_LENGTH];
121 let mut comment: Vec<c_char> = vec![0; MAX_COMMENT_LENGTH];
122
123 unsafe {
124 fits_read_key_str(
125 f.fptr.as_mut() as *mut _,
126 c_name.as_ptr(),
127 value.as_mut_ptr(),
128 comment.as_mut_ptr(),
129 &mut status,
130 );
131 }
132
133 check_status(status).and_then(|_| {
134 let value: Vec<u8> = value.iter().map(|&x| x as u8).filter(|&x| x != 0).collect();
135 String::from_utf8(value)
136 .map(|value| {
137 let comment = {
138 let comment: Vec<u8> = comment
139 .iter()
140 .map(|&x| x as u8)
141 .filter(|&x| x != 0)
142 .collect();
143 if comment.is_empty() {
144 None
145 } else {
146 String::from_utf8(comment).ok()
147 }
148 };
149 HeaderValue { value, comment }
150 })
151 .map_err(From::from)
152 })
153 }
154}
155
156pub trait WritesKey {
158 #[doc(hidden)]
159 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()>;
160}
161
162macro_rules! writes_key_impl {
163 ($t:ty) => {
164 impl WritesKey for $t {
165 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
166 let c_name = ffi::CString::new(name)?;
167 let mut status = 0;
168
169 unsafe {
170 fits_write_key(
171 f.fptr.as_mut() as *mut _,
172 <$t as HasFitsDataType>::FITS_DATA_TYPE.into(),
173 c_name.as_ptr(),
174 &value as *const $t as *mut c_void,
175 ptr::null_mut(),
176 &mut status,
177 );
178 }
179 check_status(status)
180 }
181 }
182
183 impl WritesKey for ($t, &str) {
184 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
185 let (value, comment) = value;
186 let c_name = ffi::CString::new(name)?;
187 let c_comment = ffi::CString::new(comment)?;
188 let mut status = 0;
189
190 unsafe {
191 fits_write_key(
192 f.fptr.as_mut() as *mut _,
193 <$t as HasFitsDataType>::FITS_DATA_TYPE.into(),
194 c_name.as_ptr(),
195 &value as *const $t as *mut c_void,
196 c_comment.as_ptr(),
197 &mut status,
198 );
199 }
200 check_status(status)
201 }
202 }
203
204 impl WritesKey for ($t, String) {
205 #[inline(always)]
206 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
207 let (value, comment) = value;
208 WritesKey::write_key(f, name, (value, comment.as_str()))
209 }
210 }
211 };
212}
213
214writes_key_impl!(i8);
215writes_key_impl!(i16);
216writes_key_impl!(i32);
217writes_key_impl!(i64);
218writes_key_impl!(u8);
219writes_key_impl!(u16);
220writes_key_impl!(u32);
221writes_key_impl!(u64);
222writes_key_impl!(f32);
223writes_key_impl!(f64);
224
225impl WritesKey for String {
226 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
227 WritesKey::write_key(f, name, value.as_str())
228 }
229}
230
231impl WritesKey for &'_ str {
232 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
233 let c_name = ffi::CString::new(name)?;
234 let c_value = ffi::CString::new(value)?;
235 let mut status = 0;
236
237 unsafe {
238 fits_write_key_str(
239 f.fptr.as_mut() as *mut _,
240 c_name.as_ptr(),
241 c_value.as_ptr(),
242 ptr::null_mut(),
243 &mut status,
244 );
245 }
246
247 check_status(status)
248 }
249}
250
251impl WritesKey for (String, &str) {
252 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
253 let (value, comment) = value;
254 WritesKey::write_key(f, name, (value.as_str(), comment))
255 }
256}
257
258impl WritesKey for (String, String) {
259 #[inline(always)]
260 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
261 let (value, comment) = value;
262 WritesKey::write_key(f, name, (value.as_str(), comment.as_str()))
263 }
264}
265
266impl<'a> WritesKey for (&'a str, &'a str) {
267 fn write_key(f: &mut FitsFile, name: &str, value: Self) -> Result<()> {
268 let (value, comment) = value;
269 let c_name = ffi::CString::new(name)?;
270 let c_value = ffi::CString::new(value)?;
271 let c_comment = ffi::CString::new(comment)?;
272 let mut status = 0;
273
274 unsafe {
275 fits_write_key_str(
276 f.fptr.as_mut() as *mut _,
277 c_name.as_ptr(),
278 c_value.as_ptr(),
279 c_comment.as_ptr(),
280 &mut status,
281 );
282 }
283
284 check_status(status)
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::testhelpers::{duplicate_test_file, floats_close_f64, with_temp_file};
292
293 #[test]
294 fn test_reading_header_keys() {
295 let mut f = FitsFile::open("../testdata/full_example.fits").unwrap();
296 let hdu = f.hdu(0).unwrap();
297 match hdu.read_key::<i64>(&mut f, "INTTEST") {
298 Ok(value) => assert_eq!(value, 42),
299 Err(e) => panic!("Error reading key: {:?}", e),
300 }
301
302 match hdu.read_key::<f64>(&mut f, "DBLTEST") {
303 Ok(value) => assert!(
304 floats_close_f64(value, 0.09375),
305 "{:?} != {:?}",
306 value,
307 0.09375
308 ),
309 Err(e) => panic!("Error reading key: {:?}", e),
310 }
311
312 match hdu.read_key::<String>(&mut f, "TEST") {
313 Ok(value) => assert_eq!(value, "value"),
314 Err(e) => panic!("Error reading key: {:?}", e),
315 }
316 }
317
318 #[test]
319 fn test_writing_header_keywords() {
320 with_temp_file(|filename| {
321 {
323 let mut f = FitsFile::create(filename).open().unwrap();
324 f.hdu(0).unwrap().write_key(&mut f, "FOO", 1i64).unwrap();
325 f.hdu(0)
326 .unwrap()
327 .write_key(&mut f, "BAR", "baz".to_string())
328 .unwrap();
329 }
330
331 FitsFile::open(filename)
332 .map(|mut f| {
333 assert_eq!(f.hdu(0).unwrap().read_key::<i64>(&mut f, "foo").unwrap(), 1);
334 assert_eq!(
335 f.hdu(0).unwrap().read_key::<String>(&mut f, "bar").unwrap(),
336 "baz".to_string()
337 );
338 })
339 .unwrap();
340 });
341 }
342
343 #[test]
344 fn test_writing_with_comments() {
345 with_temp_file(|filename| {
346 {
348 let mut f = FitsFile::create(filename).open().unwrap();
349 f.hdu(0)
350 .unwrap()
351 .write_key(&mut f, "FOO", (1i64, "Foo value"))
352 .unwrap();
353 f.hdu(0)
354 .unwrap()
355 .write_key(&mut f, "BAR", ("baz".to_string(), "baz value"))
356 .unwrap();
357 }
358
359 FitsFile::open(filename)
360 .map(|mut f| {
361 let foo_header_value = f
362 .hdu(0)
363 .unwrap()
364 .read_key::<HeaderValue<i64>>(&mut f, "foo")
365 .unwrap();
366 assert_eq!(foo_header_value.value, 1);
367 assert_eq!(foo_header_value.comment, Some("Foo value".to_string()));
368 })
369 .unwrap();
370 });
371 }
372
373 #[test]
374 fn test_writing_reading_empty_comment() {
375 with_temp_file(|filename| {
376 {
378 let mut f = FitsFile::create(filename).open().unwrap();
379 f.hdu(0)
380 .unwrap()
381 .write_key(&mut f, "FOO", (1i64, ""))
382 .unwrap();
383 }
384
385 FitsFile::open(filename)
386 .map(|mut f| {
387 let foo_header_value = f
388 .hdu(0)
389 .unwrap()
390 .read_key::<HeaderValue<i64>>(&mut f, "foo")
391 .unwrap();
392 assert_eq!(foo_header_value.value, 1);
393 assert!(foo_header_value.comment.is_none());
394 })
395 .unwrap();
396 });
397 }
398
399 #[test]
400 fn test_writing_integers() {
401 duplicate_test_file(|filename| {
402 let mut f = FitsFile::edit(filename).unwrap();
403 let hdu = f.hdu(0).unwrap();
404 hdu.write_key(&mut f, "ONE", -1i8).unwrap();
405 hdu.write_key(&mut f, "TWO", -500i16).unwrap();
406 hdu.write_key(&mut f, "THREE", -1_000_000i32).unwrap();
407 hdu.write_key(&mut f, "FOUR", -99_000_000_000i64).unwrap();
408 hdu.write_key(&mut f, "UONE", 1u8).unwrap();
409 hdu.write_key(&mut f, "UTWO", 500u16).unwrap();
410 hdu.write_key(&mut f, "UTHREE", 1_000_000u32).unwrap();
411 hdu.write_key(&mut f, "UFOUR", 3_000_000_000u32).unwrap();
412 hdu.write_key(&mut f, "UFIVE", 99_000_000_000u64).unwrap();
413
414 assert_matches1!(hdu.read_key(&mut f, "ONE"), Ok(-1i32));
417 assert_matches1!(hdu.read_key(&mut f, "ONE"), Ok(-1i64));
418
419 assert_matches1!(hdu.read_key(&mut f, "TWO"), Ok(-500i32));
420 assert_matches1!(hdu.read_key(&mut f, "TWO"), Ok(-500i64));
421
422 assert_matches1!(hdu.read_key(&mut f, "THREE"), Ok(-1_000_000i32));
423 assert_matches1!(hdu.read_key(&mut f, "THREE"), Ok(-1_000_000i64));
424
425 assert_matches1!(hdu.read_key::<i32>(&mut f, "FOUR"), Err(_));
426 assert_matches1!(hdu.read_key(&mut f, "FOUR"), Ok(-99_000_000_000i64));
427
428 assert_matches1!(hdu.read_key(&mut f, "UONE"), Ok(1i32));
429 assert_matches1!(hdu.read_key(&mut f, "UONE"), Ok(1i64));
430
431 assert_matches1!(hdu.read_key(&mut f, "UTWO"), Ok(500i32));
432 assert_matches1!(hdu.read_key(&mut f, "UTWO"), Ok(500i64));
433
434 assert_matches1!(hdu.read_key(&mut f, "UTHREE"), Ok(1_000_000i32));
435 assert_matches1!(hdu.read_key(&mut f, "UTHREE"), Ok(1_000_000i64));
436
437 assert_matches1!(hdu.read_key::<i32>(&mut f, "UFOUR"), Err(_));
438 assert_matches1!(hdu.read_key(&mut f, "UFOUR"), Ok(3_000_000_000i64));
439
440 assert_matches1!(hdu.read_key::<i32>(&mut f, "UFIVE"), Err(_));
441 assert_matches1!(hdu.read_key(&mut f, "UFIVE"), Ok(99_000_000_000i64));
442 });
443 }
444
445 #[test]
446 fn boolean_header_values() {
447 let mut f = FitsFile::open("../testdata/full_example.fits").unwrap();
448 let hdu = f.primary_hdu().unwrap();
449
450 let res = hdu.read_key::<bool>(&mut f, "SIMPLE").unwrap();
451 assert!(res);
452 }
453}
454
455#[cfg(test)]
456mod headervalue_tests {
457 use super::HeaderValue;
458
459 #[test]
460 fn equate_different_types() {
461 let v = HeaderValue {
462 value: 1i64,
463 comment: Some("".to_string()),
464 };
465
466 assert_eq!(v, 1i64);
467 }
468}