1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
use std::{any::TypeId, collections::HashMap, fmt::Debug};

use crate::schema::{
    collection::{self, Collection},
    view, Schema, Serialized, View,
};

/// A collection of defined collections and views.
#[derive(Default, Debug)]
pub struct Schematic {
    collections: HashMap<TypeId, collection::Id>,
    views: HashMap<TypeId, Box<dyn view::Serialized>>,
    views_by_name: HashMap<String, TypeId>,
    views_by_collection: HashMap<collection::Id, Vec<TypeId>>,
}

impl Schematic {
    /// Adds the collection `C` and its views.
    pub fn define_collection<C: Collection + 'static>(&mut self) {
        self.collections.insert(TypeId::of::<C>(), C::id());
        C::define_views(self)
    }

    /// Adds the view `V`.
    pub fn define_view<V: View + 'static>(&mut self, view: V) {
        let name = view.name();
        let collection = view.collection();
        self.views.insert(TypeId::of::<V>(), Box::new(view));
        self.views_by_name
            .insert(name.to_string(), TypeId::of::<V>());
        let views = self
            .views_by_collection
            .entry(collection)
            .or_insert_with(Vec::new);
        views.push(TypeId::of::<V>());
    }

    /// Returns `true` if this schema contains the collection `C`.
    #[must_use]
    pub fn contains<C: Collection + 'static>(&self) -> bool {
        self.collections.contains_key(&TypeId::of::<C>())
    }

    /// Looks up a [`view::Serialized`] by name.
    #[must_use]
    pub fn view_by_name(&self, name: &str) -> Option<&'_ dyn view::Serialized> {
        self.views_by_name
            .get(name)
            .and_then(|type_id| self.views.get(type_id))
            .map(AsRef::as_ref)
    }

    /// Looks up a [`view::Serialized`] through the the type `V`.
    #[must_use]
    pub fn view<V: View + 'static>(&self) -> Option<&'_ dyn view::Serialized> {
        self.views.get(&TypeId::of::<V>()).map(AsRef::as_ref)
    }

    /// Iterates over all registered views.
    pub fn views(&self) -> impl Iterator<Item = &'_ dyn view::Serialized> {
        self.views.values().map(AsRef::as_ref)
    }

    /// Iterates over all views that belong to `collection`.
    #[must_use]
    pub fn views_in_collection(
        &self,
        collection: &collection::Id,
    ) -> Option<Vec<&'_ dyn view::Serialized>> {
        self.views_by_collection.get(collection).map(|view_ids| {
            view_ids
                .iter()
                .filter_map(|id| self.views.get(id).map(AsRef::as_ref))
                .collect()
        })
    }
}

impl<T> Schema for T
where
    T: Collection + 'static,
{
    fn define_collections(collections: &mut Schematic) {
        collections.define_collection::<Self>();
    }
}

#[test]
fn schema_tests() {
    use crate::test_util::{Basic, BasicCount, BasicSchema};
    let mut schema = Schematic::default();
    BasicSchema::define_collections(&mut schema);

    assert_eq!(schema.collections.len(), 1);
    assert_eq!(schema.collections[&TypeId::of::<Basic>()], Basic::id());
    assert_eq!(schema.views.len(), 3);
    assert_eq!(
        schema.views[&TypeId::of::<BasicCount>()].name(),
        View::name(&BasicCount)
    );
}