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}