rustronomy_core/
meta.rs

1/*
2  Copyright© 2023 Raúl Wolters(1)
3
4  This file is part of rustronomy-core.
5
6  rustronomy is free software: you can redistribute it and/or modify it under
7  the terms of the European Union Public License version 1.2 or later, as
8  published by the European Commission.
9
10  rustronomy is distributed in the hope that it will be useful, but WITHOUT ANY
11  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12  A PARTICULAR PURPOSE. See the European Union Public License for more details.
13
14  You should have received a copy of the EUPL in an/all official language(s) of
15  the European Union along with rustronomy.  If not, see
16  <https://ec.europa.eu/info/european-union-public-licence_en/>.
17
18  (1) Resident of the Kingdom of the Netherlands; agreement between licensor and
19  licensee subject to Dutch law as per article 15 of the EUPL.
20*/
21
22//! This module defines all the traits and types used by `rustronomy` to enable
23//! cross-format metadata. Crates in the rustronomy can translate these types
24//! into format-specific representations. This way, metadata can be transferred
25//! from one storage format to another.
26//! 
27//! ## A quick overview of the `rustronomy` metadata system
28//! The rustronomy metadata system is built from two fundamental traits:
29//! - `MetaTag` is implemented by types that specify metadata
30//! - `MetaContainer` is implemented by types that contain metadata.
31//! 
32//! In addition to the "typed" metadata constructed from rust types implementing the
33//! `MetaTag` trait, rustronomy also supports "untyped" (or "stringly-typed")
34//! metadata consisting of simple `String` (key, value) pairs. `MetaContainer`
35//! has methods for interacting with both.
36//! 
37//! ## Strongly-typed metadata vs stringly-typed metadata
38//! The most important distinction between the typed metadata and the simple
39//! (key, value) string pairs is how rustronomy crates implementing specific
40//! storage formats are expected to handle them:
41//! - *Strongly typed* metadata tags are to be encoded into a format-specific
42//! representation that conveys their meaning in a machine-readable manner, if
43//! this is supported by the specific format.
44//! - *Stringly typed* metadata key-value pairs are to be interpreted as simple
45//! key-value pairs.
46//! As an example, the metadata struct `Author` is encoded by `rustronomy-fits`
47//! using the FITS `AUTHOR` keyword, whereas a string metadata entry with the
48//! key `"Author"` wouldn't be parsed in any special way.
49//! 
50//! Furthermore, if a key-value string pair were to conflict with a strongly typed
51//! metadata entry, storage format implementations may ignore the conflicting
52//! string entry: strongly-typed entries take precedence over stringly-typed ones.
53//! 
54//! ## built-in metadata tags
55//! In addition to these two traits, a bunch of pre-made types that implement the
56//! `MetaTag` trait can be found in the `tags` module, all of which are
57//! transferable between data storage formats.
58//! 
59//! The strongly-typed metadata system is *in principle* user-extendable. You can
60//! implement `MetaTag` for you own custom metadata tags, but there is no
61//! guarantee that these tags will be respected or parsed properly by
62//! implementations (since they are most likely not aware of your custom data type). 
63
64//Types that already implement MetaTag
65mod auto;
66mod custom;
67
68/// This module contains all pre-defined strongly typed tags supported by rustronomy.
69pub mod tags {
70  pub use super::auto::*;
71  pub use super::custom::*;
72}
73
74// Re-export the impl_meta_container macro defined in this module to this module's
75// path!
76pub use crate::impl_meta_container;
77
78use std::{
79  any::*,
80  fmt::{Debug, Display},
81};
82
83/*
84  A lot of the following code took inspiration/was copied from the anymap crate.
85
86  Creds to them for coming up with most of the workarounds presented here.
87*/
88
89/// Core trait that must be implemented by all structs representing metadata.
90///
91/// This trait requires implementers to also implement `MetaTagClone`, which is
92/// required to enable cloning of `Box<dyn MetaTag>` objects. The `Display` impl
93/// of the metadata type is used by rustronomy when printing the metadata contents
94/// of a container.
95///
96/// # Methods
97/// Although the `MetaTag` trait itself doesn't contain any methods, there *are*
98/// a couple methods implemented for `dyn MetaTag`. Most of these are copies from
99/// methods implemented in `std` for `dyn Any`.
100pub trait MetaTag: Any + Debug + Display + MetaTagClone + Send + Sync {}
101
102/// This trait is a hack to enable cloning `Box<dyn MetaTag>` objects.
103///
104/// Trait objs.are DST's and therefore cannot impl `Sized`, which is a bound for
105/// the `Clone` trait. This is annoying, because `Box<dyn Trait>` *is* sized and
106/// can definitely be cloned if the type implementing `Trait` implements `Clone`.
107/// But, since we cannot use `Clone` as a bound on `MetaTag` directly, we cannot
108/// simply `impl Clone for Box<dyn MetaTag>`. Instead, we have to use this silly
109/// sub trait to express the `Clone` bound separately.
110pub trait MetaTagClone {
111  fn clone_hack(&self) -> Box<dyn MetaTag>;
112}
113
114impl<T: MetaTag + Clone> MetaTagClone for T {
115  #[inline(always)]
116  fn clone_hack(&self) -> Box<dyn MetaTag> {
117    Box::new(self.clone())
118  }
119}
120
121impl Clone for Box<dyn MetaTag> {
122  #[inline(always)]
123  fn clone(&self) -> Self {
124    self.clone_hack()
125  }
126}
127
128impl dyn MetaTag {
129  /// Returns `true` if `self` is of type `T`, `false` otherwise
130  pub fn is<T: Any>(&self) -> bool {
131    TypeId::of::<T>() == self.type_id()
132  }
133
134  /// Casts `&self` to `T` by cloning `&T`. This requires `T` to impl `Clone`.
135  /// If `self` does not have type `T`, this method returns `None`.
136  pub fn downcast<T: Any + Clone>(&self) -> Option<T> {
137    Some(self.downcast_ref::<T>()?.clone())
138  }
139
140  /// Casts `&self` to `&T`. If `self` does not have type `T`, this method returns `None`.
141  pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
142    //Safety: these ptr casts are valid because self *is* of type T
143    if self.is::<T>() {
144      Some(unsafe { &*(self as *const dyn MetaTag as *const T) })
145    } else {
146      None
147    }
148  }
149
150  /// Casts `&mut self` to `&mut T`. If `self` does not have type `T`, this method returns `None`.
151  pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
152    if self.is::<T>() {
153      //Safety: these ptr casts are valid because self *is* of type T
154      Some(unsafe { &mut *(self as *mut dyn MetaTag as *mut T) })
155    } else {
156      None
157    }
158  }
159}
160
161/// Core trait that is implemented by all containers of rustronomy metadata types.
162///
163/// `MetaDataContainer`s consist of two different types of metadata:
164///   - strongly typed metadata: these are rust data structures that implement
165///     the `MetaTag` trait. These metadata may be accessed using the `*operation*_tag`
166///     methods.
167///   - stringly typed metadata: these are just string key-value pairs. These
168///     metadata may be accessed using the `*operation*_string_tag` methods.
169///
170/// # For data storage format implementers
171/// Strongly typed metadata are the key to how rustronomy can transfer metadata
172/// between incompatible data storage formats. Implementers of data storage
173/// formats should take special care in storing this kind of metadata.
174pub trait MetaContainer: Clone + Debug {
175  /// Inserts strongly typed tag with type `T` into the container. If the
176  /// container already had this type of metadata, its previous value will be
177  /// returned.
178  fn insert_tag<T: MetaTag + Clone>(&mut self, tag: &T) -> Option<T>;
179  /// Returns `true` if container contains a tag of type `T`, `false` otherwise
180  fn contains_tag<T: MetaTag + Clone>(&self) -> bool;
181  /// Returns `true` if container contains a tag with a type that matches the
182  /// provided `TypeId`, `false` otherwise
183  fn contains_type_id(&self, type_id: &TypeId) -> bool;
184  /// Returns a reference to the strongly typed tag of type `T`, if it exists.
185  fn get_tag<T: MetaTag + Clone>(&self) -> Option<&T>;
186  /// Returns a mutable reference to the strongly typed tag of type `T`, if it exists.
187  fn get_tag_mut<T: MetaTag + Clone>(&mut self) -> Option<&mut T>;
188  /// Removes the strongly typed tag with type `T` from the container if it
189  /// is present. Returns the value of the removed entry.
190  fn remove_tag<T: MetaTag + Clone>(&mut self) -> Option<T>;
191  /// Returns `true` if this container has typed metadata, `false` otherwise
192  fn has_typed_metadata(&self) -> bool;
193
194  /// Insert string tag with key `key` into the container. If the container already
195  /// contained an entry with this key, its previous value will be returned.
196  fn insert_string_tag(&mut self, key: &str, value: &str) -> Option<String>;
197  /// Returns `true` if container contains key `key`, `false` otherwise
198  fn contains_string_tag(&self, key: &str) -> bool;
199  /// Returns a `&str` reference to the string tag with key `key`, if it exists.
200  fn get_string_tag(&self, key: &str) -> Option<&str>;
201  /// Returns a `&mut String` mutable reference to the string tag with key `key`,
202  /// if it exists.
203  fn get_string_tag_mut(&mut self, key: &str) -> Option<&mut String>;
204  /// Removes tag with key `key` from the container, if it is present. Returns
205  /// the value of the removed entry.
206  fn remove_string_tag(&mut self, key: &str) -> Option<String>;
207  /// Returns `true` if this container has string metadata, `false` otherwise
208  fn has_string_metadata(&self) -> bool;
209}
210
211#[macro_export]
212macro_rules! impl_meta_container {
213  () => {
214    fn insert_tag<T: MetaTag + Clone>(&mut self, tag: &T) -> Option<T> {
215      self.meta.insert_tag(tag)
216    }
217
218    fn contains_tag<T: MetaTag + Clone>(&self) -> bool {
219      self.meta.contains_tag::<T>()
220    }
221
222    fn contains_type_id(&self, type_id: &std::any::TypeId) -> bool {
223      self.meta.contains_type_id(type_id)
224    }
225
226    fn get_tag<T: MetaTag + Clone>(&self) -> Option<&T> {
227      self.meta.get_tag()
228    }
229
230    fn get_tag_mut<T: MetaTag + Clone>(&mut self) -> Option<&mut T> {
231      self.meta.get_tag_mut()
232    }
233
234    fn remove_tag<T: MetaTag + Clone>(&mut self) -> Option<T> {
235      self.meta.remove_tag()
236    }
237
238    fn has_typed_metadata(&self) -> bool {
239      self.meta.has_typed_metadata()
240    }
241
242    fn insert_string_tag(&mut self, key: &str, value: &str) -> Option<String> {
243      self.meta.insert_string_tag(key, value)
244    }
245
246    fn contains_string_tag(&self, key: &str) -> bool {
247      self.meta.contains_string_tag(key)
248    }
249
250    fn get_string_tag(&self, key: &str) -> Option<&str> {
251      self.meta.get_string_tag(key)
252    }
253
254    fn get_string_tag_mut(&mut self, key: &str) -> Option<&mut String> {
255      self.meta.get_string_tag_mut(key)
256    }
257
258    fn remove_string_tag(&mut self, key: &str) -> Option<String> {
259      self.meta.remove_string_tag(key)
260    }
261
262    fn has_string_metadata(&self) -> bool {
263      self.meta.has_string_metadata()
264    }
265  };
266}